LJKのBlog

学无止境

zend_array 和 HashTable 都是 _zend_array 结构体的别名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
185 struct _zend_array {
186 zend_refcounted_h gc; // 垃圾回收相关
187 union {
188 struct {
189 ZEND_ENDIAN_LOHI_4(
190 zend_uchar flags,
191 zend_uchar nApplyCount,
192 zend_uchar nIteratorsCount,
193 zend_uchar consistency)
194 } v;
195 uint32_t flags;
196 } u; // 主要是flags
197 uint32_t nTableMask; // 它本身的值就是几个桶, 和h进行或运算可以计算最终落在哪个桶里
198 Bucket *arData; // 指向一个数组, 数组中的每一个元素都是一个bucket, buket是一个结构体, 其中存储key-value对,*arData表示这个数组的内存首地址
199 uint32_t nNumUsed; // bucket中有多少个被使用了
200 uint32_t nNumOfElements; // 真正有意义的值
201 uint32_t nTableSize; // bucket的大小, 初始值是8
202 uint32_t nInternalPointer; // 内部指针
203 zend_long nNextFreeElement; // 不指定key赋值value的时候, key就从这里获取, 初始化为0
204 dtor_func_t pDestructor; // 函数指针, 用来析构用的
205 };
1
2
3
4
5
typedef struct _Bucket {
zval val; // key-value的value
zend_ulong h; /* hash value (or numeric index) */ // 通过hash算法算出来的hash值
zend_string *key; /* string key or NULL for numerics */ // key-value的key
} Bucket;

数组初始化:

数组添加数据:

数组删除数据:

数组获取数据:


数组初始化$arr = [1,2,3];,打印 gc 发现 refcount=2;

数组初始化$arr = ['time'=>time()];, 打印 gc 发现 refcount=1;

这是为什么呢?

这牵扯到 PHP7 中的另一个概念,叫做 immutable array (不可变数组),在不可变数组下,使用一个伪计数值 2。类型是这种直接申明规定数组

摘抄一段 PHP7 数组开发成员的一段原话:

For arrays the not-refcounted variant is called an “immutable array”. If you use opcache, then constant array literals in your code will be converted into immutable arrays. Once again, these live in shared memory and as such must not use refcounting. Immutable arrays have a dummy refcount of 2, as it allows us to optimize certain separation paths.

写时复制:

1
2
$a = 'this is string';
$b = $a;

此时,$a和$b 在内存中指向同一地址, 当修改$a或者$b 的时候, 才会复制一份, 然后对复制的这份进行修改

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
146 typedef struct _zend_refcounted_h {
147 uint32_t refcount; /* reference counter 32-bit
148 php7.1中常量字符串refcount=0,依靠u来标记常量,在7.3中已经改掉了*/
149 union {
150 struct {
151 ZEND_ENDIAN_LOHI_3(
152 zend_uchar type,
153 zend_uchar flags, /* used for strings & objects
154 存储字符串的类型,例如:常量字符串2,变量字符串0 */
155 uint16_t gc_info) /* keeps GC root number (or 0) and color */
156 } v;
157 uint32_t type_info;
158 } u;
159 } zend_refcounted_h;
160
161 struct _zend_refcounted {
162 zend_refcounted_h gc;
163 };
165 // 写时复制:对于整型或者其他简单类型的,因为zval 16个字节就可以表示了,所以是直接复制的
166 // 而对于zend_string, `$a = 'string'; $b = $a;` $a和$b的zval指向的是同一个zend_string,用zend_string中的
167 // gc中的refcount进行标记,2,表示这个zeng_string被两个zval引用了,当我们修改$b的时候,就是修改$b指向的zval,
168 // $b指向zval的地址没变,但是zval的内容变了
169 // 同时原先的zend_string的gc也会修改,refcount减1
170 struct _zend_string {
171 zend_refcounted_h gc;
172 zend_ulong h; /* hash value 以空间换时间 */
173 size_t len;
174 char val[1]; // 柔性数组一定放在结构体的尾部
175 };

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$a = "string";
$b = &$a;
echo $a;
echo $b;

$b = "hello!";
echo $a;
echo $b;

unset($b);
echo $b;
echo $a;

4、5 行输出$a和$b 都是引用类型,$b引用$a,但是$a也会变成引用类型 zend_reference,$a 和$b 的 zval 都指向同一个 zend_reference。zend_reference 中的 zval 中的 u1.type=6,表示字符串类型, p *z.value.ref.val.value.str.val@6 string

8、9 行$a和$b 指向的 zend_reference 中的 zval 类型还是字符串,但是打印出来已经都是 helle!了

12 行 $b 指向的 zval 显示类型是 0,也就是 IS_UNDEF

13 行 $a 的 zval 的 type 依然是 10(引用类型), 打印仍然是 hello!

可以看出, 11 行 unset($b)的操作,仅仅是把$b 的 zval 中的 type 由 10 改成 0, 其他的完全不动。

zval 是一个结构体,其中包含三个联合体(共同体):value、u1、u2;vaule8 字节,u1 和 u2 都是 4 个字节,正好 8 字节对其,所以 zval 的大小是 16 字节。

zval 用 16 字节可以表示 PHP 中的任意一个变量。

如何表示的呢?

line84 typedef struct _zval_struct zval; 为_zval_struct 这个结构体定义别名为 zval, ctrl + ] 跳转到 line121 查看_zval_struct:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct _zval_struct {
zend_value value; /* value */
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar type, /* active type */ // unsigned char 占用一个字节,v这个结构体中有四个,所以v占用4个字节
zend_uchar type_flags,
zend_uchar const_flags,
zend_uchar reserved) /* call info for EX(This) */
} v;
uint32_t type_info; // unsigned int 占用4个字节
} u1; // v四个字节, type_info也是四个字节,所以u1占用4个字节
union {
uint32_t next; /* hash collision chain */
uint32_t cache_slot; /* literal cache slot */
uint32_t lineno; /* line number (for ast nodes) */
uint32_t num_args; /* arguments number for EX(This) */
uint32_t fe_pos; /* foreach position */
uint32_t fe_iter_idx; /* foreach iterator index */
uint32_t access_flags; /* class constant access flags */
uint32_t property_guard; /* single property guard */
uint32_t extra; /* not further specified */
} u2; // 所有的值都是 unsigned int 所以u2占用4个字节
};

然后定位 zend_value, ctrl+]跳转:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
typedef union _zend_value {
zend_long lval; /* long value 64位 8字节*/
double dval; /* double value 64位 8字节*/
zend_refcounted *counted; /* */
zend_string *str; /* */
zend_array *arr; /* */
zend_object *obj; /* */
zend_resource *res; /* */
zend_reference *ref; /* */
zend_ast_ref *ast; /* */ /* */
zval *zv; /* */
void *ptr; /* */ /* */
zend_class_entry *ce; /* */
zend_function *func; /* */
struct {
uint32_t w1;
uint32_t w2;
} ww; /* 显然4字节 */
} zend_value;

接下来我们详细说明一下 zval 的结构: 显然, 它是一个结构体,包含三个值, 其类型都是联合体。

先看 value 的这个联合体 zend_value, 包含 14 个值, 经过 tags 的 ctrl+]跳转,我们发现:

  1. zend_long 是 int64_t 的别名, 而 int64_t 又是__int64 的别名(关于__int64 可以查看笔记: 备忘/bit B(byte) KB.md)。

  2. zend_refcounted 是_zend_refcounted 的别名,而_zend_refcounted 是一个结构体,其中只有一个值 gc,gc 是 zend_refcounted_h 类型的数据,而 zend_refcounted_h 是一个结构体:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    typedef struct _zend_refcounted_h {
    uint32_t refcount; /* reference counter 32-bit */
    union {
    struct {
    ZEND_ENDIAN_LOHI_3(
    zend_uchar type,
    zend_uchar flags, /* used for strings & objects */
    uint16_t gc_info) /* keeps GC root number (or 0) and color */
    } v;
    uint32_t type_info;
    } u;
    } zend_refcounted_h;

    uint32_t 是 unsigned int 的别名。refcount 表示。。。,u 这个联合体中包含一个结构体 v 和一个 32 位正整型 type_info。

    zend_uchar 是 unsigned char 的别名。

    。。。未完待续

  3. str 是指针,zend_string 是_zend_string 的别名

    1
    2
    3
    4
    5
    6
    struct _zend_string {
    zend_refcounted_h gc;
    zend_ulong h; /* hash value */
    size_t len;
    char val[1];
    };

    gc 和垃圾回收有关。zend_ulong 是 uint32_t 的别名,表示 hash 值,防止冲突。

    关于 size_t:

    size_t 是一些 C/C++标准在 stddef.h 中定义的。这个类型足以用来表示对象的大小。size_t 的真实类型与操作系统有关。

    在 32 位架构中被普遍定义为:typedef unsigned int size_t;

    而在 64 位架构中被定义为:typedef unsigned long size_t;

    为什么有时候不用 int,而是用 size_type 或者 size_t: 与 int 固定四个字节不同有所不同,size_t 的取值 range 是目标平台下最大可能的数组尺寸,一些平台下 size_t 的范围小于 int 的正数范围,又或者大于 unsigned int. 使用 Int 既有可能浪费,又有可能范围不够大。

    len 用来储存字符串的长度。

    val 是一个柔性数组,用来储存字符串。

  4. arr 是指针,zend_array 是_zend_array 的别名

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    struct _zend_array {
    zend_refcounted_h gc;
    union {
    struct {
    ZEND_ENDIAN_LOHI_4(
    zend_uchar flags,
    zend_uchar nApplyCount,
    zend_uchar nIteratorsCount,
    zend_uchar consistency)
    } v;
    uint32_t flags;
    } u;
    uint32_t nTableMask;
    Bucket *arData;
    uint32_t nNumUsed;
    uint32_t nNumOfElements;
    uint32_t nTableSize;
    uint32_t nInternalPointer;
    zend_long nNextFreeElement;
    dtor_func_t pDestructor;
    };

    gc 与垃圾回收有关。

    联合体 v。。。

    。。。未完待续

  5. obj 是指针,zend_object 是_zend_object 的别名

    1
    2
    3
    4
    5
    6
    7
    8
    struct _zend_object {
    zend_refcounted_h gc;
    uint32_t handle; // TODO: may be removed ???
    zend_class_entry *ce;
    const zend_object_handlers *handlers;
    HashTable *properties;
    zval properties_table[1];
    };

    。。。未完待续

  6. res 是指针,zend_resource 是_zend_resource 的别名

    1
    2
    3
    4
    5
    6
    struct _zend_resource {
    zend_refcounted_h gc;
    int handle; // TODO: may be removed ???
    int type;
    void *ptr;
    };

    。。。未完待续

  7. ref 是指针,zend_reference 是_zend_reference 的别名

    1
    2
    3
    4
    struct _zend_reference {
    zend_refcounted_h gc;
    zval val;
    };

    。。。未完待续

  8. ast 是指针,zend_ast_ref 是_zend_ast_ref 的别名

    1
    2
    3
    4
    struct _zend_ast_ref {
    zend_refcounted_h gc;
    zend_ast *ast;
    };

    zend_ast 是_zend_ast 的别名

    1
    2
    3
    4
    5
    6
    7
    8
    9
    typedef uint16_t zend_ast_kind;
    typedef uint16_t zend_ast_attr;

    struct _zend_ast {
    zend_ast_kind kind; /* Type of the node (ZEND_AST_* enum constant) */
    zend_ast_attr attr; /* Additional attribute, use depending on node type */
    uint32_t lineno; /* Line number */
    zend_ast *child[1]; /* Array of children (using struct hack) */
    };

    。。。未完待续

  9. zv 是指针

    。。。未完待续

  10. ptr 是指针

    。。。未完待续

  11. ce 是指针,zend_class_entry 是_zend_class_entry 的别名

    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
    struct _zend_class_entry {
    char type;
    zend_string *name;
    struct _zend_class_entry *parent;
    int refcount;
    uint32_t ce_flags;

    int default_properties_count;
    int default_static_members_count;
    zval *default_properties_table;
    zval *default_static_members_table;
    zval *static_members_table;
    HashTable function_table;
    HashTable properties_info;
    HashTable constants_table;

    union _zend_function *constructor;
    union _zend_function *destructor;
    union _zend_function *clone;
    union _zend_function *__get;
    union _zend_function *__set;
    union _zend_function *__unset;
    union _zend_function *__isset;
    union _zend_function *__call;
    union _zend_function *__callstatic;
    union _zend_function *__tostring;
    union _zend_function *__debugInfo;
    union _zend_function *serialize_func;
    union _zend_function *unserialize_func;

    zend_class_iterator_funcs iterator_funcs;

    /* handlers */
    zend_object* (*create_object)(zend_class_entry *class_type);
    zend_object_iterator *(*get_iterator)(zend_class_entry *ce, zval *object, int by_ref);
    int (*interface_gets_implemented)(zend_class_entry *iface, zend_class_entry *class_type); /* a class implements this interface */
    union _zend_function *(*get_static_method)(zend_class_entry *ce, zend_string* method);

    /* serializer callbacks */
    int (*serialize)(zval *object, unsigned char **buffer, size_t *buf_len, zend_serialize_data *data);
    int (*unserialize)(zval *object, zend_class_entry *ce, const unsigned char *buf, size_t buf_len, zend_unserialize_data *data);

    uint32_t num_interfaces;
    uint32_t num_traits;
    zend_class_entry **interfaces;

    zend_class_entry **traits;
    zend_trait_alias **trait_aliases;
    zend_trait_precedence **trait_precedences;

    union {
    struct {
    zend_string *filename;
    uint32_t line_start;
    uint32_t line_end;
    zend_string *doc_comment;
    } user;
    struct {
    const struct _zend_function_entry *builtin_functions;
    struct _zend_module_entry *module;
    } internal;
    } info;
    };

    。。。未完待续

  12. func 是指针

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    union _zend_function {
    zend_uchar type; /* MUST be the first element of this struct! */
    uint32_t quick_arg_flags;

    struct {
    zend_uchar type; /* never used */
    zend_uchar arg_flags[3]; /* bitset of arg_info.pass_by_reference */
    uint32_t fn_flags;
    zend_string *function_name;
    zend_class_entry *scope;
    union _zend_function *prototype;
    uint32_t num_args;
    uint32_t required_num_args;
    zend_arg_info *arg_info;
    } common;

    zend_op_array op_array;
    zend_internal_function internal_function;
    };

    。。。未完待续

  13. ww 结构体

综上可以看出,value 中存储的是变量内容,接下来我们看两个联合体 u1 和 u2。

1
2
3
4
5
6
7
8
9
10
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar type, /* active type */
zend_uchar type_flags,
zend_uchar const_flags,
zend_uchar reserved) /* call info for EX(This) */
} v;
uint32_t type_info;
} u1;

u1 中有一个结构体 v 和一个 32 位整型 type_info,这个 type_info 的作用就是获取 v 的值。这么写是一个小技巧。

zend_uchar 是 unsigned char 的别名,type 这个值表示不同的变量类型,有:

#define IS_UNDEF 0
#define IS_NULL 1
#define IS_FALSE 2
#define IS_TRUE 3
#define IS_LONG 4
#define IS_DOUBLE 5
#define IS_STRING 6
#define IS_ARRAY 7
#define IS_OBJECT 8
#define IS_RESOURCE 9
#define IS_REFERENCE 10

以上,整型就是 IS_LONG。

。。。未完待续

1
2
3
4
5
6
7
8
9
10
11
union {
uint32_t next; /* hash collision chain */
uint32_t cache_slot; /* literal cache slot */
uint32_t lineno; /* line number (for ast nodes) */
uint32_t num_args; /* arguments number for EX(This) */
uint32_t fe_pos; /* foreach position */
uint32_t fe_iter_idx; /* foreach iterator index */
uint32_t access_flags; /* class constant access flags */
uint32_t property_guard; /* single property guard */
uint32_t extra; /* not further specified */
} u2;

u2 中的 next 用来解决数组中的冲突。

通过内存地址快速知道内存大小

通过内存管理申请一个内存地址,这个内存地址属于 small 或 large 或 huge 内存,那如何判断呢?

page 4KB

chunk 2MB

small 小于等于 3KB 的内存。

large 大于 3KB 且小于等于(2MB-4KB)的内存,可以对应整数倍的 page。

huge 大于 2MB-4KB 的内存,可以对应整数倍的 chunk。

如果是 0x8 打头,

如果是 0xc 打头的,看两段

0x4 打头的是 large 内存。

如果一个内存是 2M 的整数倍,一定是 huge 内存。

2MB=2×1024*1024=2×2^10×2^10=2×2^20

16=2^4

(2×2^20) / 2^4

最终得到 2MB 的 16 进制是 200000

所以看到 0x…00000 这种后面是 5 个 0 的,然后看一看倒数第六位能否被 2 整除,就一定是 huge 内存。

例如:0x7ffff5e00000,它属于 huge 内存。

如果一个内存地址不是 2MB 的整数倍,那我们根据地址,去掉偏移,找到一个 chunk,另外我们也可以知道,它属于哪个 page(偏移除以 4K)。

malloc 函数申请内存, 返回指针;

free 释放内存

1
2
void * ptr = malloc(size);
free(ptr)

问题: 申请内存的时候需要传入 size, 为什么 free 的时候没有传入 size, 那么是怎么做到准确释放 size 大小的内存的呢?

其实呢, malloc 在分配内存的时候, 返回的地址的指针指向了程序可用内存的位置, 在 ptr 前面会多分配 32 位的头部, 头部的低三位表示是否分配。

当 free 的时候,会向前搜寻 32 的头,取高 29 位为块的大小,所以 free 的时候就不需要传递 size 了,当然这只是一种 malloc 的实现,当前的 linux 的 malloc 事件已经不是这样了,但原理是一样的,也是维护了一个头部。

基本概念

  1. chunk
  2. page
  3. 各种规格的内存

chunk:2MB 大小的内存;

page:4KB 大小的内存;

一个 chunk 可以分成 512 个 page。

内存规格

  1. small (size <= 3KB)30 个规格
  2. large (3KB < size < 2MB-4KB) large 是 4K 的整数倍
  3. huge (size > 2MB - 4KB) huge 一定是 2M 的整数倍, 例如申请 3M 内存, 那么返回的一定是比 3M 大,且是 2M 的整数倍, 是 4M

内存分配

zend_mm_alloc_heap 判断申请内存的大小;

申请内存大于 2M-4K, 则属于 huge, 执行 zend_mm_alloc_huge, 调用 zend_mm_chunk_alloc, 最终调用 mmap 取申请大内存;

申请内存大于 3K 且小于 2M-4K, 则属于 large, 执行 zend_mm_alloc_large, 调用 zend_mm_alloc_pages

申请内存小于等于 3K, 则属于 small, 执行 zend_mm_alloc_small 函数, 如果当前有 small 内存, 则直接返回, 如果没有, 则执行 zend_mm_alloc_small_slow, 需要先去 zend_mm_alloc_pages 看看, 如果 large 也没有余量了, 就去 zend_mm_chunk_alloc 开辟新的内存。

small 内存管理

chunk 内存对齐

4k 对其:申请的内存只能是 4K 的整数倍;

内存对其的好处,当已知一个内存地址,就可以算出此内存(page)的起始地址;例如有一内存地址 0x103c61120,4k 对齐,通过宏计算,此内存所在 page 的起始地址为 0x103c61000,在此 page 的偏移量是 0x120,能过快速定位内存地址所在的 page,提高效率。

内存管理的代码比较多,但是我们只需要掌握两个核心:当我们 free 的时候,不需要传递 size,

参考链接:https://www.cnblogs.com/wuyuegb2312/archive/2013/06/08/3126510.html

大端 => 高尾端

小端 => 低尾端

计算机系统中,以字节为单位,每个地址单元都对应这一个字节,一个字节为 8bit。

大端:(Big-Endian):就是把数值的高位字节放在内存的低位地址上,把数值的低位字节放在内存的高位地址上。

小端:(Little-Endian):就是把数值的高位字节放在高位的地址上,低位字节放在低位地址上。

1
2
3
4
5
6
#include <stdio.h>
int main() {
int i = 0x12345678;
printf("内存地址:%p\n", &i);
return 0;
}

输出:0x7fff86f78684

【注】不管是大端法还是小端法存储,计算机在内存中存放数据的顺序都是从低地址到高地址。

大小端比较

  1. 小端规则

    地址 0x7fff86f78684 0x7fff86f78685 0x7fff86f78686 0x7fff86f78687
    数值 0x78 0x56 0x34 0x12
  2. 大端规则

    地址 0x7fff86f78684 0x7fff86f78685 0x7fff86f78686 0x7fff86f78687
    数值 0x12 0x34 0x56 0x78

通过上面的表格,可以看出来大小端的不同。(注:其实在计算机内存中并不存在所谓的数据类型,比如 char,int 等的。这个类型在代码中的作用就是让编译器知道每次应该从那个地址起始读取多少位的数据,赋值给相应的变量。)

PHP 源码部分宏函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifdef WORDS_BIGENDIAN
# define ZEND_ENDIAN_LOHI(lo, hi) hi; lo;
# define ZEND_ENDIAN_LOHI_3(lo, mi, hi) hi; mi; lo;
# define ZEND_ENDIAN_LOHI_4(a, b, c, d) d; c; b; a;
# define ZEND_ENDIAN_LOHI_C(lo, hi) hi, lo
# define ZEND_ENDIAN_LOHI_C_3(lo, mi, hi) hi, mi, lo,
# define ZEND_ENDIAN_LOHI_C_4(a, b, c, d) d, c, b, a
#else
# define ZEND_ENDIAN_LOHI(lo, hi) lo; hi;
# define ZEND_ENDIAN_LOHI_3(lo, mi, hi) lo; mi; hi;
# define ZEND_ENDIAN_LOHI_4(a, b, c, d) a; b; c; d;
# define ZEND_ENDIAN_LOHI_C(lo, hi) lo, hi
# define ZEND_ENDIAN_LOHI_C_3(lo, mi, hi) lo, mi, hi,
# define ZEND_ENDIAN_LOHI_C_4(a, b, c, d) a, b, c, d
#endif

# define ZEND_ENDIAN_LOHI_4(a, b, c, d) d; c; b; a;为例,这里涉及到一个大小端的问题:

正向代理和反向代理的本质区别是是什么?

https://www.zhihu.com/question/36412304

正向代理:客户机必须指定代理服务器,并将本来要直接发送到 Web 服务器上的 http 请求发送到代理服务器中

反向代理:代理服务器接受 Internet 上的连接请求,然后将请求转发给内部网络的服务器;并将从服务器上得到的结果返回给 Internet 上请求连接的客户端,此时代理服务器对外就表现为一个服务器

正向代理对客户端负责,反向代理对其代理的服务器负责,搭梯子就是典型的正向代理,而 LVS、haproxy、nginx 等都是反向代理

我数据库版本为 5.7.16

新建表结构:

1
2
3
4
5
6
7
8
DROP TABLE IF EXISTS `yx_test`;
CREATE TABLE `yx_test` (
`mobile` char(11) NOT NULL DEFAULT '' COMMENT '手机号',
`is_delete` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态,0.删除,1.正常',
`deleted_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '删除时间',
`created_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '创建时间',
`updated_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '更新时间'
) ENGINE=InnoDB AUTO_INCREMENT=10000043 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='测试表';

报错信息:ERROR 1067 (42000): Invalid default value for 'deleted_at'

解决方案:

使用 root 登陆数据库

  1. 查看 sql_mode:
1
select @@sql_mode;

获得结果:

ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION

NO_ZERO_IN_DATE, NO_ZERO_DATE 是无法默认为‘0000-00-00 00:00:00’的根源,去掉之后再次新建表就可以了

1
SET GLOBAL sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';

注:

NO_ZERO_IN_DATE:在严格模式下,不允许日期和月份为零

NO_ZERO_DATE:设置该值,mysql 数据库不允许插入零日期,插入零日期会抛出错误而不是警告。

测试新建表,ok

1. 安装系统

分区

/boot 300MB 逻辑分区

/ 自动补全 主分区

其他的不用设置

更新

不要勾选更新,等安装完系统,更新源之后再更新比较快

2. 更新源

装完系统后第一件事就是更新国内源

将 apt 的源更换为国内的源,我选择的是阿里云

https://developer.aliyun.com/mirror/ubuntu?spm=a2c6h.13651102.0.0.3e221b11OyziSW

让更新后的源生效

1
2
sudo apt-get update  // 更新软件列表
sudo apt-get upgrade // 更新软件

3. 安装常用软件

1. gnome-tweaks

可以通过 ubuntu 软件 搜索安装,或者

1
sudo apt  install    gnome-tweaks

2. WPS

https://www.wps.cn/product/wpslinux/

下载.dep 包,然后双击安装即可

3. chrome

https://www.google.cn/chrome/

下载.dep 包,然后双击安装即可

4. vscode

https://code.visualstudio.com/

下载.dep 包,然后双击安装即可

5. typora

https://www.typora.io/

1
2
3
4
5
6
7
8
# or run:
# sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys BA300B7755AFCFAE
wget -qO - https://typora.io/linux/public-key.asc | sudo apt-key add -
# add Typora's repository
sudo add-apt-repository 'deb https://typora.io/linux ./'
sudo apt-get update
# install typora
sudo apt-get install typora

6. sublime_text3

https://www.sublimetext.com/docs/3/linux_repositories.html

1
2
3
4
5
wget -qO - https://download.sublimetext.com/sublimehq-pub.gpg | sudo apt-key add -
sudo apt-get install apt-transport-https
echo "deb https://download.sublimetext.com/ apt/stable/" | sudo tee /etc/apt/sources.list.d/sublime-text.list
sudo apt-get update
sudo apt-get install sublime-text

7. phpstorm

https://www.jetbrains.com/phpstorm/download/#section=linux

两种安装方式:

1
sudo snap install phpstorm --classic

或者直接下载 .tar.gz 包,然后将解压后的目录移动到 /opt 下,再执行 phpstorm.sh 即可

1
2
3
4
tar -zxvf PhpStorm-2019.2.3.tar.gz
sudo mv PhpStorm-2019.2.3.tar.gz /opt/PhpStorm
cd /opt
./PhpStorm/bin/phpstorm.sh

激活码:百度搜一个

删除: 删除 /opt/PhpStorm 和 ~/.PhpStorm2019.2 即可

重装:删除~/.PhpStorm2019.2 然后重新执行 phpstorm.sh 即可

8. PyCharm

两种安装方式:

1
sudo snap install pycharm-professional --classic

或者

或者直接下载 .tar.gz 包,然后将解压后的目录移动到 /opt 下,再执行 pycharm.sh 即可

1
2
3
4
tar -zxvf pycharm-professional-2019.2.3.tar.gz
sudo mv pycharm-2019.2.3/ /opt/pycharm
cd /opt
./pycharm/bin/pycharm.sh

激活码:百度搜一个

删除: 删除 /opt/pycharm 和 ~/.PyCharm2019.2 即可

重装:删除~/.PyCharm2019.2 然后重新执行 pycharm.sh 即可

奇怪的是,pycharm 不会自己生成快捷方式,所以需要自己手动添加

1
2
cd /usr/share/applications/
sudo vim PyCharm.desktop

将以下代码添加到 PyCharm.desktop

1
2
3
4
5
6
7
8
9
[Desktop Entry]
Type=Application
Name=PyCharm
GenericName=pycharm-2019.2
Comment=Pycharm3:The Python IDE
Exec=sh /opt/pycharm/bin/pycharm.sh
Icon=/opt/pycharm/bin/pycharm.png
Terminal=pycharm
Categories=PyCharm;

9. SecureCRT

https://www.vandyke.com/cgi-bin/releases.php?product=securecrt

下载之前需要先注册,这个有点麻烦

两种安装方式,.dep 或者.tar.gz,这里选择 .tar.gz,将下载的 scrt-8.5.4.1942.ubuntu18-64.tar.gz 解压到/opt 下

1
2
3
tar -zxvf scrt-8.5.4.1942.ubuntu18-64.tar.gz
sudo mv scrt-8.5.4 /opt/
/opt/scrt-8.5.4/SecureCRT // 启动软件

添加快捷方式到启动器

1
2
3
4
sudo mv /opt/scrt-8.5.4/SecureCRT.desktop /usr/share/applications/
sudo vim /usr/share/applications/SecureCRT.desktop
// 修改 Exec=/opt/scrt-8.5.4/SecureCRT
// Icon=/opt/scrt-8.5.4/securecrt_64.png

安装完毕

如果采用双击.dep 安装包的方式,安装完成后,双击 SecureCTR 图标, 打不开也不报错

1
2
lujinkai@lujinkai-TM1703:/usr/lib/x86_64-linux-gnu$ whereis SecureCRT
SecureCRT: /usr/bin/SecureCRT

回车执行:

1
/usr/bin/SecureCRT

报错: error while loading shared libraries: libpython2.7.so.1.0: cannot open shared object file: No such file or directory

检测:

1
locate libpython2.7.so.1.0

显示正常: /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0

奇怪, 但是查找不到这文件

所以直接安装这个库:

1
sudo apt-get install libpython2.7-dev

问题解决

10. 搜狗输入法

https://pinyin.sogou.com/linux/?r=pinyin

因为搜狗输入法依赖 fcitx,所以要先安装 fcitx

1
sudo apt install fcitx

下载搜狗输入法,使用 dpkg 安装

1
sudo dpkg -i sogoupinyin_2.2.0.0108_amd64.deb

报错,解决依赖关系

1
sudo apt --fix-broken install

启动器打开 输入法,配置为 fcitx。

设置 -> 区域和语言 -> 输入源:删除多余语言,只保留一个 汉语-> 管理已安装的语言 -> 添加或删除语言… :只安装一个 中文简体

重启

注:搜狗输入法目前支持 ubuntu18.04,在 ubuntu20.04 上推荐使用谷歌输入法

11. 网易云音乐

https://music.163.com/#/download

下载全部客户端,选择 Linux 版,双击下载的.dep 安装包即可

12. 百度网盘

https://pan.baidu.com/download

下载.dep 包,然后双击安装即可

13. Remmina

软件中心搜索 “Remmina”

14. Rhythmbox

软件中心搜索 “Rhythmbox”

15. SVN

这个有点麻烦,没有可视化客户端,只能用命令行了

1
sudo apt-get install subversion

先略过。。。

16. nfs

1
sudo apt install nfs-common

这个有点麻烦,暂时略过,用坚果云代替先

17. 坚果云

https://www.jianguoyun.com/s/downloads

下载 Linux 版,双击.dep 安装包即可

18. 配置 ssh

1
sudo vim /etc/ssh/ssh_config // 修改配置文件

修改&&添加

1
2
3
4
GSSAPIAuthentication no  // 默认yes,修改为no

ServerAliveInterval 55 // 每隔55秒发送一次请求
ServerAliveCountMax 9 // 9次没有响应就断开连接

重启

1
service ssh restart

如果无法重启,则安装openssh-server后再重启

1
sudo apt-get install openssh-server

19. phpMyAdmin

因为 phpMyAdimin 默认只能访问本地数据库,但是我们的需求是可以连接远程服务器的数据库。

打开/phpmyadmin/libraries 目录,

修改 config.default.php 文件

1
$cfg['AllowArbitraryServer'] = false;  修改成:$cfg['AllowArbitraryServer'] = true;

然后把 phpMyAdmin 目录移动到/data/wwwroot 下,单独给它配置访问域名,因为如果通过二级目录去访问,有时候会报如下的错误:

20. postman

https://www.getpostman.com/downloads/

解压,进入解压后的目录,直接执行 Postman 即可

但是没有桌面图标,这个我就不添加图标了,只在 /ust/local/bin 目录下添加一个软链接指向 /opt/Postman/Postman

未完待续。。。

安装为双系统后,默认为 ubuntu 启动

现在将其设为,默认启动上次选择启动的系统(让 grub 记住上次启动时选择的系统):

1
sudo vim /etc/default/grub

GRUB_DEFAULT=0(0 为 ubuntu 启动)改为 GRUB_DEFAULT=saved

文件末尾添加 GRUB_SAVEDEFAULT=true(这行命令可以修复只修改“grub_deault=saved”,但是启动时依然不能记住上次的启动的 GRUB2 的 bug)

保存退出

终端更新配置文件:sudo update-grub

sudo reboot 重启

注:

GRUB_TIMEOUT=10 可调整自动进入时间

要固定默认启动系统,只需调整 GRUB_DEFAULT=0 为 windows10 所在序号(ubuntu 为 0 以此类推),然后更新配置文件

附/etc/default/grub 配置文件详解:

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
# 设定默认启动项,推荐使用数字
GRUB_DEFAULT=0

# 注释掉下面这行将会显示引导菜单
#GRUB_HIDDEN_TIMEOUT=0

# 黑屏,并且不显示GRUB_HIDDEN_TIMEOUT过程中的倒计时
GRUB_HIDDEN_TIMEOUT_QUIET=true

# 设定超时时间,默认为10秒
# 设定为-1取消倒计时
GRUB_TIMEOUT=10

# 获得发行版名称(比如Ubuntu, Debian)
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`

# 将会导入到每个启动项(包括recovery mode启动项)的'linux'命令行
GRUB_CMDLINE_LINUX=""

# 同上,但是只会添加到 normal mode 的启动项
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"

# 取消注释以允许图形终端(只适合grub-pc)
#GRUB_TERMINAL=console

# 分辨率设定,否则采用默认值
#GRUB_GFXMODE=640x480

# 取消注释以阻止GRUB将传递参数 "root=UUID=xxx" 传递给 Linux
#GRUB_DISABLE_LINUX_UUID=true

# 取消启动菜单中的“Recovery Mode”选项
#GRUB_DISABLE_LINUX_RECOVERY="true"

# 当GRUB菜单出现时发出鸣音提醒
#GRUB_INIT_TUNE="480 440 1"

参考资料:

CMAKE_BUILD_TYPE

cmake 的构建模式

1
CMAKE_BUILD_TYPE=Debug | Release | RelWithDebInfo | MinSizeRel

CMAKE_INSTALL_PREFIX

mysql 的安装目录

1
CMAKE_INSTALL_PREFIX = /usr/local/mysql

COMMUNITY_BUILD

是否为社区版本

1
COMMUNITY_BUILD = ON

DOWNLOAD_BOOST

是否从下载开源 boost

1
DOWNLOAD_BOOST = 1

DWITH_BOOST

This CMake script will look for boost in .

1
DWITH_BOOST = <directory>

DOWNLOAD_BOOST_TIMEOUT

下载 boost 的超时时间, 单位 秒

1
DOWNLOAD_BOOST_TIMEOUT = 600

ENABLED_PROFILING

是否启用查询分析代码 参考

1
ENABLED_PROFILING = ON

ENABLE_GCOV

是否包括 gcov 支持, 不太懂这是什么, 默认关闭.

1
ENABLE_GCOV = OFF

ENABLE_GPROF

启用 gprof, (仅优化 Linux 版本), 不太懂, 默认关闭

1
ENABLE_GPROF = OFF

ENABLE_MEMCACHED_SASL

不太懂, 默认关闭

1
ENABLE_MEMCACHED_SASL = OFF

ENABLE_MEMCACHED_SASL_PWDB

不太懂, 看起来和上面一起是配套的, 也默认关闭

1
ENABLE_MEMCACHED_SASL_PWDB:BOOL=OFF

FEATURE_SET

这也不太懂, 默认就好了, 它的注释说这个选项好像已经被弃用了

1
FEATURE_SET=community

INSTALL_LAYOUT

选择预定义的安装布局, STANDALONE

  • STANDALONE:与用于.tar.gz.zip 包的布局相同 。这是默认值。
  • RPM:布局类似于 RPM 包
  • SVR4:Solaris 包布局
  • DEB:DEB 封装布局(实验)
1
INSTALL_LAYOUT = STANDALONE

MYSQL_DATADIR

默认的 mysql 数据目录

1
MYSQL_DATADIR = /usr/local/mysql/data

MYSQL_KEYRINGDIR

默认的 mysql 手册目录

1
MYSQL_KEYRINGDIR=/usr/local/mysql/keyring

OPTIMIZER_TRACE

optimizer_trace 是 mysql5.6 之后加入的新功能, explain 是各种执行计划选择的结果, 如果想看整个执行计划以及对于多种索引方案之间是如何选择的, 就使用 optimizer_trace 这个功能

1
OPTIMIZER_TRACE=ON

REPRODUCIBLE_BUILD

不太懂, 默认关闭

1
REPRODUCIBLE_BUILD = OFF

TMPDIR

临时文件的目录,P_tmpdir 的值可以在 /usr/include/stdio.h 中查看,默认 /tmp

1
TMPDIR = P_tmpdir

WITH_xxx_STORAGE_ENGINE

1
2
3
4
5
WITH_ARCHIVE_STORAGE_ENGINE=ON
WITH_BLACKHOLE_STORAGE_ENGINE=ON
WITH_FEDERATED_STORAGE_ENGINE=ON
WITH_INNOBASE_STORAGE_ENGINE=ON
WITH_PARTITION_STORAGE_ENGINE=ON

分别开启 ARCHIVE 、BLACKHOLE 、FEDERATED、INNOBASE、PARTITION 引擎,默认都是开启的

WITH_ASAN

是否启用 AddressSanitizer , 不太懂, 默认是关闭的

1
WITH_ASAN = OFF

WITH_ASAN_SCOPE

不太懂, 看起来像是和上面一个选项是配套的, 默认关闭

1
WITH_ASAN_SCOPE = OFF

WITH_CLIENT_PROTOCOL_TRACING

是否将客户端协议跟踪框架构建到客户端库中。默认情况下,此选项被启用

1
WITH_CLIENT_PROTOCOL_TRACING = ON

WITH_DEBUG:BOOL

是否包括调试支持,默认关闭

1
WITH_DEBUG=OFF

WITH_DEFAULT_COMPILER_OPTIONS

是否使用默认的编译器来编译, 也就是 cmake

1
WITH_DEFAULT_COMPILER_OPTIONS=ON

WITH_DEFAULT_FEATURE_SET

是否使用 cmake 的特性集

1
WITH_DEFAULT_FEATURE_SET=ON

WITH_EDITLINE

要使用 哪个libedit/ editline库。允许的值为 bundled(默认值)和 systemWITH_EDITLINE被添加到 MySQL 5.7.2 中。它取而代之WITH_LIBEDIT,已被删除

1
WITH_EDITLINE = bundled

WITH_EMBEDDED_SERVER

是否构建libmysqld嵌入式服务器库。 注意: 从libmysqldMySQL 5.7.17 起,嵌入式服务器库已被弃用,MySQL 8.0 中将被删除

1
WITH_EMBEDDED_SERVER=ON

WITH_EXTRA_CHARSETS

哪些额外的字符集包括: all, complex, none

1
WITH_EXTRA_CHARSETS=all

WITH_INNODB_MEMCACHED

是否生成 memcached 共享库(libmemcached.soinnodb_engine.so

1
WITH_INNODB_MEMCACHED=OFF

WITH_LZ4:STRING

  • bundled:使用 LZ4 与发行版捆绑在一起的库。这是默认值
  • system:使用系统 LZ4 库。如果 WITH_LZ4 设置为此值,则不构建 lz4_decompress 实用程序。在这种情况下, 可以使用系统 lz4 命令
1
WITH_LZ4 = bundled

WITH_MSAN

是否启用 MemorySanitizer,支持它的编译器。默认是关闭。对于此选项,如果启用该功能,则所有连接到 MySQL 的库也必须已经通过启用该选项进行编译。此选项已添加到 MySQL 5.7.4 中

1
WITH_MSAN=OFF

WITH_RAPID

是否构建快速开发周期插件

1
WITH_RAPID=ON

WITH_SASL

不太懂

1
WITH_SASL = system

WITH_SSL

要包含的 SSL 支持类型或要使用的 OpenSSL 安装的路径名。

  • ssl_type 可以是以下值之一:
    • yes:使用系统 SSL 库(如果存在),否则与发行版捆绑在一起的库
    • bundled:使用与发行版捆绑在一起的 SSL 库。这是默认值
    • system:使用系统 SSL 库
  • path_name是要使用的 OpenSSL 安装的路径名。使用这个可能比使用这个ssl_type值 更好 system,因为它可以防止 CMake 检测并使用系统上安装的较旧或不正确的 OpenSSL 版本。(另一个允许的方式做同样的事情是设置 CMAKE_PREFIX_PATH选项 path_name。)
1
WITH_SSL = ssl_type | path_name

WITH_TEST_TRACE_PLUGIN

是否构建测试协议跟踪客户端插件. 默认情况下,此选项被禁用。启用此选项不起作用,除非该WITH_CLIENT_PROTOCOL_TRACING 选项被启用。如果 MySQL 配置启用了这两个选项,libmysqlclient客户端库将内置测试协议跟踪插件构建,所有标准的 MySQL 客户端都会加载该插件。但是,即使启用测试插件,默认情况下也不起作用。使用环境变量来控制插件; 请参见第 28.2.4.11.1 节“使用测试协议跟踪插件”

不要启用 WITH_TEST_TRACE_PLUGIN,如果你想使用自己的协议跟踪的插件,因为只有一个这样的插件可以在同一时间被加载并出现错误尝试加载第二个选项。如果您已经使用启用了测试协议跟踪插件的 MySQL 来构建 MySQL,以了解它是如何工作的,那么在使用自己的插件之前,您必须重新构建 MySQL。

1
WITH_TEST_TRACE_PLUGIN = OFF

WITH_UBSAN

是否为支持它的编译器启用 Undefined Behavior Sanitizer。默认是关闭。此选项已添加到 MySQL 5.7.6

1
WITH_UBSAN=OFF

WITH_UNIT_TESTS

如果启用,则使用单元测试编译 MySQL。默认值为 ON,除非服务器未被编译

1
WITH_UNIT_TESTS=ON

WITH_VALGRIND

是否在 Valgrind 头文件中编译,这将 Valgrind API 暴露给 MySQL 代码。默认是 OFF

要生成一个 Valgrind 感知的调试构建, -DWITH_VALGRIND=1通常与之结合-DWITH_DEBUG=1。请参阅 构建调试配置

1
WITH_VALGRIND=OFF

WITH_ZLIB

某些功能要求服务器使用压缩库支持(如功能COMPRESS()UNCOMPRESS()功能)以及客户端/服务器协议的压缩来构建 。这 WITH_ZLIB表明zlib支持的来源:

  • bundled:使用zlib与发行版捆绑在一起的 库。这是默认值。
  • system:使用系统 zlib库。
1
WITH_ZLIB=bundled

按 resful 风格设计接口: 按资源来设置接口的请求地址, 通过不同的请求类型来区分要对这个资源进行什么操作

get 语义是获取数据

post 语义是添加信息

delete 语义是删除数据

put 语义是更新数据(是更新这个数据的【所有信息】)

patch 语义是更新数据(是更新数据的【部分信息】)

假如要做注册

请求类型: post

请求地址: ‘/users’ (必须表示的是资源)

假如要做登录

请求类型: post (因为登录之后服务器的变化就添加了一个 session)

请求地址: ‘/sessions’

假如要做退出登录

请求类型: delete

请求地址: ‘/sessions/id’ (退出登录就是服务器删除 session)

假如要判断用户有没有登录

请求类型: get

请求地址 ‘/sessions’

总结: restful 风格 api

  1. 我们使用一个能够表示资源的地址(资源是数据库里的一条数据, 或是数组里的一个元素, 或者文件夹中的一个文件)

  2. 我们通过不同的请求类型(get, post, delete, put, patch), 对这个地址对应的资源进行 CRUL, 让后端代码根据不同的请求类型, 对数据做不同的操作

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
107
108
109
/**
* 路由处理
* 最终这里我们只配置路由, 不实现对应回调函数的具体代码(放到controllers文件夹中)
* 按 restful 风格设计接口:
* 按资源来设计我们的接口的请求地址, 通过不同的请求类型,来区分要对这个资源进行什么操作
* 示例:
* '/gamerooms/998' 对应998这个房间号
* // 获取房间的信息
* router.get('/gamerooms/998', handler001)
* // 删除该房间
* router.delete('/gamerooms/998, handler002)
* // 修改房间信息
* router.put('/gamerooms/998, handler003)
* // 添加新房间
* router.post('/gamerooms', handler004)
*
* // get 语义是获取数据
* // delete 语义是删除数据
* // put 语义是更新数据(是更新这个数据的【所有信息】)
* // patch 语义是更数据(是更【新部分信息】)
*/
const express = require("express");
const gameRoomController = require("../controllers/gameRoomController.js");
const friendController = require("../controllers/friendController.js");
const userController = require("../controllers/userController.js");
const router = (module.exports = express.Router());

// 注册
router.post("/users", userController.postSignUp);
// 登陆
router.post("/session", userController.postSignIn);

// 游戏房间
router.post("/gamerooms", gameRoomController.postGameRooms);
router.get("/gamerooms", gameRoomController.getGameRooms);
// 搜索用户, 和获取好友
router.get("/users", userController.getUsers);
router.get("/friends", friendController.getFriends);
router.post("/friends", friendController.postFirends);

/**
* 游戏房间创建,删除,修改,查询
*/
// router.post('/gamerooms', gameRoomController.postGameRooms)
// router.get('/gamerooms', gameRoomController.getGameRooms)

/**
* 游戏记录,查询游戏记录,删除游戏记录,修改记录, 添加记录
*/
// 假设游戏记录地址是: /gamerecords/10002 (包含: 游戏房间号,玩家,游戏时间)

// 获取一条游戏记录详细信息
// router.get('/gamerecords/10002', xxxx001)

// 删除这条游戏记录信息
// router.delete('/gamerecords/10002', xxxx002)

// 修改记录(想修改游戏的所有信息: 房间号,玩家, 游戏时间)
// router.put('/gamerecords/10002', xxxx003)

// 修改记录(只想修改房间号)
// router.patch('/gamerecords/10002', xxxx04)

// 添加一条游戏记录
// router.post('/gamerecords/10002', xxxx05)

// 获取所有游戏记录(也可能包含分页)
// router.get('/gamerecords', xxxx07)

// 对于前端来说 restful api 有没有开发难度:
// $.ajax({
// url: '',
// // type: 'delete',
// type: 'put',
// data: {},
// success: function () {

// }
// })

// users/10002
// 假如要做注册:
// 请求类型: post
// 请求地址: '/users' (必需表示的是资源)

// 假如要做登陆:
// 请求类型: post
// 请求地址: /sessions // 因为登陆其实是添加的 session

// 假如退出登陆
// 请求类型: delete
// 请求地址: /sessions/id // 退出登陆,服务器是要删除session

// 假如要判断用户有没有登陆
// 请求类型: get
// 请求地址: /sessions

// '/deluser'
// '/getuser'

// 总结: restful 风格 api
// 1. 我们使用一个能够表示资源的地址(资源是数据库里的一条数据,或者数组里的一个元素,或者文件夹中的一个文件)
// 2. 我们通过不同的请求类型(get, post, delete, put, patch), 对这个地址对应的资源
// 进行CRUD, 让后端代码根据不同的请求类型,对数据做不同的操作。

// 我遵循的风格: javascrtip standard style
// function xxx () {
// var aa = 11;
// }
1

WebSocket 是 HTML5 出的东西(协议)

WebSocket 协议的目标是在一个独立的持久连接上提供全双工双向通信。客户端和服务器可以向对方主动发送和接受数据。在 JS 中创建 WebSocket 后,会有一个 HTTP 请求发向浏览器以发起请求。在取得服务器响应后,建立的连接会使用 HTTP 升级将 HTTP 协议转换为 WebSocket 协议。也就是说,使用标准的 HTTP 协议无法实现 WebSocket,只有支持那些协议的专门浏览器才能正常工作。

由于 WebScoket 使用了自定义协议,所以 URL 与 HTTP 协议略有不同。未加密的连接为 ws://,而不是 http://。加密的连接为 wss://,而不是 https://。

WebSocket 是应用层协议,是 TCP/IP 协议的子集,通过 HTTP/1.1 协议的 101 状态码进行握手。也就是说,WebSocket 协议的建立需要先借助 HTTP 协议,在服务器返回 101 状态码之后,就可以进行 websocket 全双工双向通信了,就没有 HTTP 协议什么事情了

WebSocket 是基于 TCP 的,TCP 的握手和 WebSocket 的握手是不同层次的。
TCP 的握手用来保证链接的建立,WebSocket 的握手是在 TCP 链接建立后告诉服务器这是个 WebSocket 链接,服务器你要按 WebSocket 的协议来处理这个 TCP 链接。

windows

1
2
3
4
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout]
"Scancode Map"=hex:00,00,00,00,00,00,00,00,02,00,00,00,01,00,3a,00,00,00,00,00

将以上内容复制到 reg 后缀的文件中, 双击, 然后重启电脑即可

ubuntu

1
echo '/usr/bin/setxkbmap -option "caps:escape"' >> ~/.profile

或者:

修改 /etc/default/keyboard

1
XKBOPTIONS="caps:escape"

externals 方案

将体积大而且不需要按需加载的包通过CDN的方式加载, 不参与打包, 例如: vueaxiosvue-router , 通过CDN加载的包不能使用Vue.use()

引入资源

在 index.html 中通过 link 和 script 方法引入所需的 js 和 css

配置 externals

webpack.dev.conf.jswebpack.prod.conf.jsmodule下均添加externals, 这样webpack编译打包时不处理它, 却可以 import 引用到它。

vue-cli3.0 不需要以上此配置。

按需加载

每个包按需加载的方式都不同, 这里以 element-ui 为例, 在main.js主入口文件中给element-ui组件库做按需导入设置,去除没有使用的组件,进一步精简项目的总代码量。
参考: https://element.eleme.cn/#/zh-CN/component/quickstart

安装 babel-plugin-component

1
npm install babel-plugin-component -D

修改 .babelrc

按需引入

完整组件列表和引入方式(完整组件列表以 components.json 为准)

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
import Vue from "vue"
import {
Pagination,
Dialog,
Autocomplete,
Dropdown,
DropdownMenu,
DropdownItem,
Menu,
Submenu,
MenuItem,
MenuItemGroup,
Input,
InputNumber,
Radio,
// ...
Loading,
MessageBox,
Message,
Notification,
} from "element-ui"

Vue.prototype.$ELEMENT = { size: "small" } // element-ui的全局配置

Vue.use(Pagination)
Vue.use(Dialog)
Vue.use(Autocomplete)
Vue.use(Dropdown)
Vue.use(DropdownMenu)
Vue.use(DropdownItem)
Vue.use(Menu)
Vue.use(Submenu)
Vue.use(MenuItem)
Vue.use(MenuItemGroup)
Vue.use(Input)
Vue.use(InputNumber)
Vue.use(Radio)
// ...
Vue.use(Loading.directive)

Vue.prototype.$loading = Loading.service
Vue.prototype.$msgbox = MessageBox
Vue.prototype.$alert = MessageBox.alert
Vue.prototype.$confirm = MessageBox.confirm
Vue.prototype.$prompt = MessageBox.prompt
Vue.prototype.$notify = Notification
Vue.prototype.$message = Message

Vuex是实现组件之间数据共享的一种机制

父子传值或者兄弟传值,不好管理,而且有些需求实现不了

vuex提供了一种全新的数据共享管理机制, 该模式相比较简单的组件传值更加高端、大气、上档次

初始化 vuex

1
npm i vuex -S

导入并注册

1
2
import Vuex from "vuex";
Vue.use(Vuex);

创建 store 公共状态对象

在 main.js 文件中添加:

1
2
3
4
5
6
const store = new Vuex.Store({
// state 中存放的,就是全局共享的数据,可以把 state 认为 是组件中的 data
state: {
count: 0,
},
});

将创建的 store 挂载到 vm 实例上

1
2
3
4
5
6
new Vue({
el: "#app",
render: (c) => c(app),
router,
store, // 将 创建的共享状态对象,挂载到 Vue 实例中,这样,所有的组件,就可以直接从 store 中获取 全局的数据了
});

访问信息

1
this.$store.state.xxx;

修改信息

不要直接修改this.$store.state.xxx,容易造成数据混乱

mutations 方式

同步

1
2
3
4
5
6
7
8
9
mutations:{
// 参数state是固定的,代表vuex本身的state,用以获取共享数据
add(state){
state.count++
}
}
...
// 调用
this.$store.commit('add')

actions 方式

异步

总结:

store 中存储的数据,不要放到 data 中, 而是要放到计算你属性中,因为:

  1. 如果将字符串, 数字这些普通值赋值给 data 中的属性时,当我们使用双向数据绑定来修改 data 中的数据时, 原来 store 中的数据是不会被影响的
  2. vue 官方推荐使用 mutation 的方式来统一修改 store 中的数据, mutation 是需要我们定义的

全局安装脚手架

1
npm install -g vue-cli

初始化项目

1
vue init webpack my-project

Vue build 选择 Runtime-only 选项
Use ESLint to lint your code? 这个本应该应该 yes, 但是作为个人开发者, 选择 no 比较方便
Should we run npm install for you after the project has been created? 选择 No, I will handle that myself

安装依赖包

1
2
cd my-project // 切换到项目根目录下
npm install // 安装依赖包

webpack做简单的配置

默认 8080 端口, 可以选择修改

1568166821268

修改打包后的 js 和 css 为相对路径引入

build 下的 webpack.prod.conf.js 中得 output 中添加 publicPath。

webpack.dev.conf.js 中添加如下配置:

1
2
3
4
5
6
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true,
favicon: 'src/assets/img/ico.png', // logo图标
}),

新建.prettierrc 文件

1
2
3
4
5
{
"tabWidth": 2,
"semi": false,
"singleQuote": true
}

运行项目

1
npm run dev

打包项目

1
npm run build

安装项目

开发过程中, 用到的包需要安装

1
2
npm i -D // 工程构建(开发时、“打包”时)依赖 ;例:xxx-cli , less-loader , babel-loader...
npm i -S // 项目(运行时、发布到生产环境时)依赖;例:vue, element-ui, react...

常用的需要手动安装的扩展包

  • less
    npm i less-loader less -D
  • 使jsx支持v-model
    • npm i babel-plugin-jsx-v-model -D
    • .babelrc"plugins"下添加"jsx-v-model"

非零开始

以上 1-7 是从零开始构建项目, 如果是已有项目拷贝复制, 肯定不能把 node_modules 目录一同复制, 所以我们可以复制除了node_modules以外的所有文件, 然后再执行 npm install 即可