思维之海

——在云端,寻找我的星匙。

P2P系统后端设计

P2P系统设计项目 - 服务器后端。使用JavaScript + MySQL搭建后端接口,提供数据交互功能。

References

【回形针PaperClip】别再问我什么是 BT 种子

【万物4分钟】磁力下载是什么原理?跟用种子下载的区别大不大?

求求你,别再问你的种子磁力为什么下不动了!

种子文件 .torrent

Bencoding编码格式

第二篇:BEncoding

BT种子文件格式

BEncoding 是一种用于组织、描述数据的简洁编码格式,它支持 4 种数据类型 / 元素:

  • 字节串(Byte String)
  • 整数(Integer)
  • 列表(List, 线性表)
  • 字典(Dictionary)

字节串

格式为 长度:内容

例如:

  • 4:abcd:”abcd”
  • 0::空串

BE字节串,不是字符串,因此在字典中 key 的比较是二进制比较而不是字符串比较。

整数

格式为 i + 整数 + e

例如:

  • i1234e:1234
  • i-1e:-1
  • i0e:0

列表

格式为 l + 元素:元素:...:元素 + e

例如:

  • l4:spam4:eggse:[ “spam”, “eggs” ]
  • li123e5:helloi111ee:[ 123, “hello”, 111 ]
  • le:空列表

字典

格式为 d + 字节串:元素:...:字节串:元素 + e

例如:

  • d3:cow3:moo4:spam4:eggse:{ “cow” => “moo”, “spam” => “eggs” }
  • d4:name5:Angus3:agei23ee:{ “name” => “Angus”, “age” => 23 }
  • d4:spaml1:a1:bee:{ “spam” => [ “a”, “b” ] }
  • de:空列表

Torrent文件结构

Torrent文件的解析与转换

Torrent整体是一个Bencoding编码的字典,其中有以下的属性名。

键名称 数据类型 可选项 键值含义
announce string required Tracker的Url
info dictionary required 该条映射到一个字典,该字典的键将取决于共享的一个或多个文件
announce-list array[] optional 备用Tracker的Url,以列表形式存在
comment string optional 备注
created by string optional 创建人或创建程序的信息

Info字段

在总体结构中有个info,其为字典格式。

单文件、多文件的有不同的info属性。

单文件Info结构

键名称 数据类型 可选项 键值含义
name string required 建议保存到的文件名称
piceces byte[] required 每个文件块的SHA-1的集成Hash。
piece length long required 每个文件块的字节数

多文件Info结构

键名称 数据类型 可选项 键值含义
name string required 建议保存到的目录名称
piceces byte[] required 每个文件块的SHA-1的集成Hash。
piece length long required 每个文件块的字节数
files array[] required 文件列表,列表存储的内容是字典结构

files字典结构:

键名称 数据类型 可选项 键值含义
path array[] required 一个对应子目录名的字符串列表,最后一项是实际的文件名称
length long required 文件的大小(以字节为单位)

Torrent解析

https://github.com/ndroi/pytorrent 不太好用

parse-torrent 这个是JS版本的

torrent_parser python版本

既可以解析磁力链接,也可以解析种子文件。

1
2
3
4
5
6
7
8
9
10
11
const parseTorrent = require('parse-torrent')
const fs = require('fs')

console.log("----------------------------------------------------------")
console.log("----------------------------------------------------------")
console.log("----------------------------------------------------------")
console.log(parseTorrent('magnet:?xt=urn:btih:d2474e86c95b19b8bcfdb92bc12c9d44667cfa36&tr=http%3A%2F%2Ftracker.example.com%2Fannounce'))
console.log("----------------------------------------------------------")
console.log("----------------------------------------------------------")
console.log("----------------------------------------------------------")
console.log(parseTorrent(fs.readFileSync(__dirname + '/example.torrent')))

开源torrent

libtorrent 开源P2P

libtorrent-开源代码P2P协议库(BitTorrent)-linux下编译,测试

opentracker – An open and free bittorrent tracker

1
sudo apt-get install python3-libtorrent

运行示例:https://github.com/arvidn/libtorrent/blob/libtorrent-1_0_7/bindings/python/client.py

1
python3 client.py demo.torrent

demo.torrent为一个测试种子文件。

WebTorrent 开源P2P

https://webtorrent.io/

https://github.com/webtorrent/webtorrent

WebTorrent is a streaming torrent client for Node.js and the web. WebTorrent provides the same API in both environments.

The WebTorrent protocol works just like BitTorrent protocol, except it uses WebRTC instead of TCP/uTP as the transport protocol.

Webtorrent的API中支持直接访问种子中的各个字段:

JSON文件 .json

JSON 语法

使用JSON

json文件格式详解

JSON在线解析及格式化验证 - JSON.cn

JSON(JavaScript Object Notation)是一个数据交换协议规范。它的设计简约,从而在Web开发中得到大量的应用。JSON文件后缀为.json。不同的语言中存在不同的对JSON文件的解析器,如JavaScript中的eval()函数。

什么是数据交换语言?

在不同的系统不同的语言间交换数据时,我们一般倾向于使用无关于平台及语言的数据交换语言。此类语言主要包括XML,JSON,YAML,Protobuf等,常用于接口调用,配置文件,数据存储等场景。

元素

JSON允许以下基本元素 / 值(value):

  • 字符串(string)
  • 数值(number)
  • 布尔值(true、false)
  • 空值(null)

以及嵌套元素:

  • 对象(object)
  • 数组(array)

一个JSON文件就是一个元素(具有任意的复杂度)。


在JavaScript中可以直接创建JSON格式的变量。

1
2
3
4
5
var employees = [
{ "firstName":"Bill" , "lastName":"Gates" },
{ "firstName":"George" , "lastName":"Bush" },
{ "firstName":"Thomas" , "lastName": "Carter" }
];

可以像这样访问 JavaScript 对象数组中的第一项:

1
employees[0].lastName;

返回的内容是:

1
Gates

可以像这样修改数据:

1
employees[0].lastName = "Jobs";

对象 {}

一个JSON对象就是一个字典,即,“键-值对”(key: value)。

1
2
3
4
{
"firstName":"John",
"lastName":"Doe"
}

数组 []

JSON 数组在方括号中书写。

1
2
3
4
5
6
7
{
"employees": [
{ "firstName":"John" , "lastName":"Doe" },
{ "firstName":"Anna" , "lastName":"Smith" },
{ "firstName":"Peter" , "lastName":"Jones" }
]
}

NPM 前端套件管理器

npm 是干什么的?(非教程)

NPM 使用介绍

NPM是什么?了解Node Package Manager套件管理机制

NPMNode Package Manager),前端套件管理工具。除此之外还有类似的Yarn

Node.js:一种javascript的运行环境,能够使得javascript脱离浏览器运行。它是一种提供给后端服务器的技术。

Node.js是用来做什么的? - Gavin的回答 - 知乎

Node.js - 廖雪峰

Package.json

package.json文件— JavaScript 标准参考教程(alpha)

  • name - 包名。
  • version - 包的版本号。
  • description - 包的描述。
  • homepage - 包的官网 url 。
  • author - 包的作者姓名。
  • contributors - 包的其他贡献者姓名。
  • scripts - 指定了运行脚本命令的npm命令行缩写。比如start指定了运行npm run start时,所要执行的命令。
  • dependencies - 依赖包列表。如果依赖包没有安装,npm 会自动将依赖包安装在 node_module 目录下。
  • repository - 包代码存放的地方的类型,可以是 git 或 svn,git 可在 Github 上。
  • main - main 字段指定了程序的主入口文件,require(‘moduleName’) 就会加载这个文件。这个字段的默认值是模块根目录下面的 index.js。
  • keywords - 关键字

npm init

NPM通过初始化项目来填写基本信息,并创建Package.json文件。

npm install

npm 模块安装机制简介

npm install 检查当前位置下的package.json,并根据该文件计算缺少的套件,自动更新相关的依赖。并且生成node_modules文件夹用于保存下载的依赖包。

安装指定模块:

1
npm install <Module Name>

安装并记录到Package.json

1
2
npm install express --save	 # 将该模块写入dependencies属性
npm install express --save-dev # 将该模块写入devDependencies属性

全局安装:

1
npm install express -g   # 全局安装

卸载:

1
npm uninstall <Module Name>

查看依赖树:

1
npm ls

版本检查:

1
npm -v

升级:

1
sudo npm install npm -g

npm run <scripts>

运行Package.json中”scripts”属性所设置的脚本。比如:

1
npm run start

Qingqing Share 前端

https://github.com/zouyansong/qingqingshare 清清共享

安装

1
git clone https://github.com/zouyansong/qingqingshare

试运行

1
2
3
4
5
6
7
8
9
sudo apt install npm

cd qingqingshare/
git checkout dev

cd qqshare/

npm install
npm run dev

Cannot run NPM Commands

PATH=$(echo "$PATH" | sed -e 's/:\/mnt.*//g') # strip out problematic Windows %PATH% 添加到 ~/.bashrc.

Tracker服务器

使用OpenTracker自建高性能Tracker服务器

OpenTracker

资源服务器

Python实现简单的web服务器

由浅入深 | 如何一步步地搭建一个Web服务器

数据库服务器

搭建MySQL服务器

MySQL 教程

MySQL安装

检查本地环境:

1
2
rpm -qa | grep mysql
# sudo apt install rpm

获取Mysql:

1
sudo wget http://dev.mysql.com/get/mysql-apt-config_0.8.1-1_all.deb

安装软件包:(dpkg « Linux命令大全

1
sudo dpkg -i mysql-apt-config_0.8.1-1_all.deb

安装mysql-server,选择5.7的稳定版本

更新软件源:

1
sudo apt update

安装mysql-server:

1
sudo apt install mysql-server

设置数据库root用户的密码

MySQL服务管理

1
2
3
4
5
6
# 启动mysql服务
service mysql start
# 停止mysql服务
service mysql stop
# 查看mysql服务当前状态
service mysql status

mysql默认监听3306端口

MySQL登录

以root用户进行登录:

1
mysql -u root -p

然后输入密码

查看用户列表:

1
2
USE mysql;
SELECT User, Host, plugin FROM mysql.user;

https://stackoverflow.com/questions/39281594/error-1698-28000-access-denied-for-user-rootlocalhost 初次安装后无法登录的处理

管理员强制登陆:

1
sudo mysql -u root # I had to use "sudo" since is new installation

https://stackoverflow.com/questions/50093144/mysql-8-0-client-does-not-support-authentication-protocol-requested-by-server JavaSccript mysql连接数据库失败的处理

用户生成

MySQL 管理 新版MySQL不再支持直接向user表中添加用户。需要使用CREATE USER声明。

MySQL中的用户帐户简介

1
CREATE USER username@hostname IDENTIFIED BY password;

例如:下面这个用户只允许从localhost主机并使用密码为guest233连接到MySQL数据库服务器。

1
CREATE USER guest@localhost IDENTIFIED BY 'guest233';

@localhost相当于访问地址的约束。

MySQL user DB does not have password columns - Installing MySQL on OSX mysql> describe user;

权限管理

MySQL GRANT语句简介

1
2
3
4
GRANT privilege,[privilege],.. ON privilege_level 
TO user [IDENTIFIED BY password]
[REQUIRE tsl_option]
[WITH [GRANT_OPTION | resource_option]];

赋予auditor在某个数据库上的所有权限:

1
GRANT ALL ON somedb.* TO auditor@localhost;

创建数据库和表单

1
2
CREATE DATABASE qqshare;
use qqshare;

创建qqshare_info数据表:

1
2
3
4
5
6
7
8
9
10
11
12
CREATE TABLE IF NOT EXISTS qqshare_info(
filename VARCHAR(200) NOT NULL,
course VARCHAR(60),
teacher VARCHAR(40),
downloadtime INT,
filesize INT,
uploadtime VARCHAR(40),
fileformat VARCHAR(25),
magnetURI VARCHAR(700) NOT NULL,
description VARCHAR(200),
PRIMARY KEY ( magnetURI )
) DEFAULT CHARSET=utf8;

创建user_info数据表:

1
2
3
4
5
6
7
8
CREATE TABLE IF NOT EXISTS user_info(
id VARCHAR(50) NOT NULL,
password VARCHAR(50) NOT NULL,
name VARCHAR(100) DEFAULT 'Anonymous',
downloadrecord VARCHAR(10000),
uploadrecord VARCHAR(10000),
PRIMARY KEY ( id )
) DEFAULT CHARSET=utf8;

删除表

1
2
DROP TABLE qqshare_info;
DROP TABLE user_info;

查看数据表

1
2
3
4
show tables;

desc qqshare_info;
desc user_info;
1
2
3
4
5
6
+-------------------+
| Tables_in_qqshare |
+-------------------+
| qqshare_info |
+-------------------+
1 row in set (0.00 sec)

插入数据

1
2
3
INSERT INTO qqshare_info ( field1, field2,...fieldN )
VALUES
( value1, value2,...valueN );

清洗测试:

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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
use qqshare;
DROP TABLE qqshare_info;
DROP TABLE user_info;
CREATE TABLE IF NOT EXISTS qqshare_info(
filename VARCHAR(200) NOT NULL,
course VARCHAR(60),
teacher VARCHAR(40),
downloadtime INT,
filesize INT,
uploadtime VARCHAR(40),
fileformat VARCHAR(25),
magnetURI VARCHAR(700) NOT NULL,
description VARCHAR(200),
PRIMARY KEY ( magnetURI )
) DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS user_info(
id VARCHAR(50) NOT NULL,
password VARCHAR(50) NOT NULL,
name VARCHAR(100) DEFAULT 'Anonymous',
downloadrecord VARCHAR(10000) DEFAULT ',',
uploadrecord VARCHAR(10000) DEFAULT ',',
PRIMARY KEY ( id )
) DEFAULT CHARSET=utf8;

INSERT INTO qqshare_info ( filename,course,teacher,fileformat,magnetURI,downloadtime ,description,uploadtime,filesize)
VALUES
( "Sintel", "组合数学", "马昱春", ".docx", "magnet:?xt=urn:btih:08ada5a7a6183aae1e09d831df6748d566095a10&dn=Sintel&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F&xs=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fsintel.torrent",3,"这课真的难","2020-12-01",124923 );

INSERT INTO qqshare_info ( filename,course,teacher,fileformat,magnetURI,downloadtime ,description,uploadtime,filesize)
VALUES
( "组合数学2017期末A2我觉得很无聊", "组合数学", "马昱春", ".pdf", "magnet:?xt=urn:btih:c12fe1c06bba25559dc9f519b345aa7c1367a88a",10,"太难了~~","2020-10-01",845634 );

INSERT INTO qqshare_info ( filename,course,teacher,fileformat,magnetURI,downloadtime ,description,uploadtime)
VALUES
( "微积分A2020期末答案", "微积分A", "苏爷爷", ".zip", "magnet:?xt=urn:btih:c12fe1c06bba25559dc9f519b345aa7c1361288a",17,"平均分90分","2019-12-01" );

INSERT INTO qqshare_info ( filename,course,teacher,fileformat,magnetURI,downloadtime ,description,uploadtime)
VALUES
( "毛概展示PPT", "毛概", "Unknown", ".ppt", "magnet:?xt=urn:btih:c12fe1c06bba25559dc9f515a345aa7c1367a88a",103,"参考看看吧","2020-11-09" );

INSERT INTO qqshare_info ( filename,course,teacher,fileformat,magnetURI,downloadtime ,description,uploadtime)
VALUES
( "高等计算机网络项目3", "高等计算机网络", "徐恪", ".pdf", "magnet:?xt=urn:btih:c12fe1c06bba25559dc9f519b345aa7c1361188a",10,"zys永远滴神","2020-12-01" );

INSERT INTO qqshare_info ( filename,course,teacher,fileformat,magnetURI,downloadtime ,description,uploadtime)
VALUES
( "高等计算机网络项目1", "高等计算机网络", "徐恪", ".pdf", "magnet:?xt=urn:btih:c12fe1c06bba25559dc9f519b345aa7c1362188a",10,"wxh永远滴神","2020-12-03" );

INSERT INTO qqshare_info ( filename,course,teacher,fileformat,magnetURI,downloadtime ,description,uploadtime)
VALUES
( "高等计算机网络项目2", "高等计算机网络", "徐恪", ".zip", "magnet:?xt=urn:btih:c12fe1c06bga25559dc9f519b345aa7c1362180a",11,"tql","2020-12-02" );

INSERT INTO qqshare_info ( filename,course,teacher,fileformat,magnetURI,downloadtime ,description,uploadtime)
VALUES
( "复变函数期末总结", "复变函数", "YY", ".zip", "magnet:?xt=urn:btih:c12fe1c06bga25559dc9f519b345aa7c1362187a",111,"背出来你就不会挂了,拜杨幂是没用的","2020-12-04" );

INSERT INTO qqshare_info ( filename,course,teacher,fileformat,magnetURI,downloadtime ,description,uploadtime)
VALUES
( "硕士生英语Schedule", "硕士生英语", "Kam", ".pdf", "magnet:?xt=urn:btih:c12fe8c06bga25559dc9f519b345aa7c1362182a",1,"学英语~~","2020-12-02" );

INSERT INTO qqshare_info ( filename,course,teacher,fileformat,magnetURI,downloadtime ,description,uploadtime,filesize)
VALUES
( "离散数学A课件", "离散数学", "ZYSyyds!", ".ppt", "magnet:?xt=urn:btih:c12fe1c06bga25559dc9f519b345aa1c1362182a",121,"数理逻辑与集合论","2020-12-09",3145134 );

INSERT INTO qqshare_info ( filename,course,teacher,fileformat,magnetURI,downloadtime ,description,uploadtime)
VALUES
( "操作系统2020年期中A卷", "操作系统", "ABC", ".zip", "magnet:?xt=urn:btih:c12fe1c06bga25559dc9f519b345aa7c1362982a",61,"送分题","2020-10-01" );

INSERT INTO qqshare_info ( filename,course,teacher,fileformat,magnetURI,downloadtime ,description,uploadtime)
VALUES
( "人工智能导论大作业", "人工智能", "马少平", ".zip", "magnet:?xt=urn:btih:a12fe1c06bga25553dc9f519b345aa7c1362182a",51,"四子棋的AI算法","2020-12-14" );

INSERT INTO qqshare_info ( filename,course,teacher,fileformat,magnetURI,downloadtime ,description,uploadtime)
VALUES
( "线性代数2018年秋", "线性代数", "XD", ".zip", "magnet:?xt=urn:btih:c32fe1c06bga25559dc9f519b345aa7c1362182a",81,"好多人挂科","2020-12-16" );

INSERT INTO qqshare_info ( filename,course,teacher,fileformat,magnetURI,downloadtime ,description,uploadtime)
VALUES
( "高等计算机网络项目5", "高等计算机网络", "徐恪", ".zip", "magnet:?xt=urn:btih:a12fe1c06bba2g559dc9f519b345aa7c1362180a",11,"永远滴神","2020-12-06" );

INSERT INTO qqshare_info ( filename,course,teacher,fileformat,magnetURI,downloadtime ,description,uploadtime)
VALUES
( "数电A4小抄", "数字电路", "XX", ".pdf", "magnet:?xt=urn:btih:cg2fe1c06bba25559dc9f519b345aa7c1362180a",71,"要带放大镜去看","2020-12-05" );

INSERT INTO qqshare_info ( filename,course,teacher,fileformat,magnetURI,downloadtime ,description,uploadtime)
VALUES
( "高等计算机网络课件", "高等计算机网络", "徐恪", ".pptx", "magnet:?xt=urn:btih:cg2fe1c06bba15559dc9f519b345aa7c1362180a",9,"看不懂又能怎么办呢QAQ","2020-12-11" );

INSERT INTO qqshare_info ( filename,course,teacher,fileformat,magnetURI,downloadtime ,description,uploadtime)
VALUES
( "自然辨证法作业", "自然辨证法", "XX", ".zip", "magnet:?xt=urn:btih:cg2fe1c06bba25559dc9f549b345aa7c1362180a",23,"4000字以上,太卷了吧","2020-11-05" );

INSERT INTO qqshare_info ( filename,course,teacher,fileformat,magnetURI,downloadtime ,description,uploadtime)
VALUES
( "高等计算机网络报告", "高等计算机网络", "徐恪", ".zip", "magnet:?xt=urn:btih:cg2fe1c06bba25559dd9f519b345aa7c1362180a",14,"做一个项目花挺多时间","2020-12-08" );

INSERT INTO qqshare_info ( filename,course,teacher,fileformat,magnetURI,downloadtime ,description,uploadtime)
VALUES
( "深入浅出统计学", "统计学原理", "Mr.H", ".zip", "magnet:?xt=urn:btih:cg2fe1c06bba25559dc9f619b345aa2c1362180a",7,"统计学是AI的基础啊","2020-12-09" );

INSERT INTO qqshare_info ( filename,course,teacher,fileformat,magnetURI,downloadtime ,description,uploadtime)
VALUES
( "疯狂学英语", "大学英语", "超神", ".zip", "magnet:?xt=urn:btih:cg2fe1c06bba255594c9f119b345aa7c1362190a",8,"英语咋学","2020-12-10" );

INSERT INTO qqshare_info ( filename,course,teacher,fileformat,magnetURI,downloadtime ,description,uploadtime)
VALUES
( "测试文件", "测试", "测", ".test", "magnet:?fake",7,"测试~~~","2020-12-01" );
INSERT INTO qqshare_info ( filename,course,teacher,fileformat,magnetURI,downloadtime ,description,uploadtime)
VALUES
( "测试文件2", "测试2", "测2", ".test2", "magnet:?fake2",5,"测试2~~~","2020-12-02" );



INSERT INTO user_info ( id,password,name )
VALUES
( "2020210942","123456","zouyansong" );

INSERT INTO user_info ( id,password,name,downloadrecord )
VALUES
( "2020210041","654321","shenxi", "magnet:?xt=urn:btih:c12fe1c06bga25559dc9f519b345aa7c1362180a" );

INSERT INTO user_info ( id,password,name,downloadrecord,uploadrecord )
VALUES
( "2020000000","123456","robot", ",magnet:?xt=urn:btih:c12fe1c06bga25559dc9f519b345aa7c1362180a,magnet:?xt=urn:btih:cg2fe1c06bba25559dc9f549b345aa7c1362180a,magnet:?xt=urn:btih:c32fe1c06bga25559dc9f519b345aa7c1362182a", "magnet:?xt=urn:btih:c12fe1c06bba25559dc9f519b345aa7c1361288a" );

INSERT INTO user_info ( id,password,name,downloadrecord,uploadrecord )
VALUES
( "123","123","test123","magnet:?xt=urn:btih:cg2fe1c06bba255594c9f119b345aa7c1362190a", ",magnet:?xt=urn:btih:c12fe1c06bba25559dc9f519b345aa7c1367a88a,magnet:?xt=urn:btih:c12fe1c06bba25559dc9f519b335aa7c1367a88a" );

INSERT INTO user_info ( id,password,name,downloadrecord,uploadrecord )
VALUES
( "321","321","test321","magnet:?fake,magnet:?fake2", "" );

INSERT INTO user_info ( id,password,name,downloadrecord,uploadrecord )
VALUES
( "912","912","test912","magnet:?fake2", "magnet:?fake2,magnet:?fake" );

select * from user_info;

查看数据项

1
2
SELECT * FROM qqshare_info;
SELECT * FROM user_info;

搜索初探

django学习——如何实现简单的搜索功能

查询

用SQL查询代替搜索。

1
2
3
SELECT *
FROM qqshare_info
WHERE filename REGEXP "组合数学" OR course REGEXP "组合数学" OR teacher REGEXP "马昱春";
1
2
3
SELECT filename, course, teacher
FROM qqshare_info
WHERE filename REGEXP "组合数学" OR course REGEXP "组合数学" OR teacher REGEXP "马昱春";

Local种子匹配

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
import socket
import torrent_parser

def main():
HOST, PORT = '', 8888

listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
listen_socket.bind((HOST, PORT))
listen_socket.listen(1)
print('Serving HTTP on port %s ...' % PORT)
while True:
client_connection, client_address = listen_socket.accept()
request = client_connection.recv(1024)
#print(request.decode("utf-8"))

http_response = """\
HTTP/1.1 200 OK

"""
http_response += json_search_result_file
client_connection.sendall(http_response.encode("utf-8"))
client_connection.close()

def search(keyword):
torrent_path = "./torrents/"
import os
torrent_list = os.listdir(torrent_path)
for torrent_file_name in torrent_list:
torrent_parser.parse_torrent_file(torrent_file_name)



if __name__ == "__main__":
main()

Node Express 后端

Node.js Express 框架

https://www.npmjs.com/package/express

https://expressjs.com/en/5x/api.html#req

Node项目配置

检查Node版本:

1
node -v

测试JS程序server.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var http = require('http');

http.createServer(function (request, response) {

// 发送 HTTP 头部
// HTTP 状态值: 200 : OK
// 内容类型: text/plain
response.writeHead(200, {'Content-Type': 'text/plain'});

// 发送响应数据 "Hello World"
response.end('Hello World\n');
}).listen(8880);

// 终端打印如下信息
console.log('Server running at http://localhost:8880/');

运行测试程序server.js

1
node server.js

访问http://localhost:8880/即可得到结果。页面返回'Hello World\n'

在测试完成之后按Ctrl C退出程序,Ctrl Z只是退出界面,会导致程序继续运行,端口号被占用。

Node.js 从命令行接收参数

Express风格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//express_demo.js 文件
var express = require('express');
var app = express();

app.get('/', function (req, res) {
res.send('Hello World');
})

var server = app.listen(8081, function () {

var host = server.address().address
var port = server.address().port

console.log("应用实例,访问地址为 http://%s:%s", host, port)

})

访问http://localhost:8081/得到结果。页面返回'Hello World'

服务器设计

https://github.com/stellarkey/server_engine

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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
// 测试查询链接:
// http://localhost:8081/api/search?filename=%E7%BB%84%E5%90%88%E6%95%B0%E5%AD%A6&course=%E7%BB%84%E5%90%88%E6%95%B0%E5%AD%A6&teacher=%E9%A9%AC%E6%98%B1%E6%98%A5
// 测试插入链接:(自行)

// 命令行:node server.js --port=8023 可自定义端口
const args = require('minimist')(process.argv.slice(2));
var port_number= args['port'] ? args['port'] : 8081; // 端口号

var express = require('express');
var querystring = require('querystring');
var app = express();



app.post('/api/uservalid', function (req, res) {
console.log("----------------------------------------");
console.log('/api/uservalid');
var postData = "";

req.on("data", (chunk) => {
postData = postData + chunk;
});

req.on("end", () => {
postData = querystring.parse(postData);

console.log("----------------------------------------");
console.log('postData:', postData);

var post_id = postData.id;
var post_pwd = postData.pwd;

var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : '123456',
database : 'qqshare'
});

connection.connect();

var SQL_query = 'SELECT id,password,name FROM user_info WHERE id=\'' + post_id + '\';';

connection.query(SQL_query,function (err, result) {
if(err){
console.log('[SELECT ERROR] - ',err.message);
resData = {flag: 0};
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(resData));
return;
}
console.log('----------------------SELECT------------------------');
console.log("SQL_query:", SQL_query);
console.log("result:", result);

if(result.length){
console.log("用户存在");
if (result[0]['password'] == Number(postData['pwd'])){
console.log("密码正确");
resData = {flag:1,name:result[0]['name']};
}
else{
console.log("密码错误");
resData = {flag:2};
}
console.log("resData:", resData);
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(resData));
}
else{
console.log("用户不存在");
resData = {flag:3};
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(resData));
}
});
connection.end();
});
})

app.get('/api/hotfile', function (req, res){
console.log("----------------------------------------");
console.log('/api/hotfile');
// 暂时设计为选择【所有时间中下载量最高的10个文件】
// 如果需要获得近期的热门文件,可能需要维护一个定期更新的下载量数组,然后循环更新,暂时搁置
var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : '123456',
database : 'qqshare'
});

connection.connect();

var SQL_query = 'SELECT filename,description FROM qqshare_info ORDER BY downloadtime DESC LIMIT 10;';

connection.query(SQL_query, function (err, results, fields) {
if(err){
console.log('[SELECT ERROR] - ',err.message);
resData = {flag: 0};
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(resData));
return;
}

res.setHeader("Content-Type", "application/json");
console.log("----------------------------------------");
console.log(JSON.stringify(results));
res.end(JSON.stringify(results));
});

connection.end();
})

app.get('/api/search', function (req, res) {
console.log("----------------------------------------");
console.log('/api/search');

console.log("----------------------------------------");
console.log("req.query: ", req.query);
//console.log(req.query.filename);
//res.send(req.query.filename);

var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : '123456',
database : 'qqshare'
});

connection.connect();

var filename_filter = req.query.filename ? req.query.filename : " ";
var course_filter = req.query.course ? req.query.course : " ";
var teacher_filter = req.query.teacher ? req.query.teacher : " ";
// if it's empty, then use: " " (with a space)

var SQL_query = 'SELECT * FROM qqshare_info WHERE filename REGEXP "'
+ filename_filter
+ '" OR course REGEXP "'
+ course_filter
+ '" OR teacher REGEXP "'
+ teacher_filter
+ '";';

connection.query(SQL_query, function (err, results, fields) {
if(err){
console.log('[SELECT ERROR] - ',err.message);
resData = {flag: 0};
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(resData));
return;
}
//console.log(results);
//console.log('The solution is: ', results[0].filename);

res.setHeader("Content-Type", "application/json");
console.log("----------------------------------------");
console.log(JSON.stringify(results));
res.end(JSON.stringify(results));
});

connection.end();
})

app.post('/api/upload', function (req, res) {
console.log("----------------------------------------");
console.log('/api/upload');
// magnetURI中有特殊字符,可能需要转义:
// https://segmentfault.com/a/1190000009492789
var postData = "";
req.on("data", (chunk) => {
postData = postData + chunk;
});
req.on("end", () => {
postData = querystring.parse(postData);

console.log("----------------------------------------");
console.log('postData:',postData);

// 在文件数据表中加入该文件
var filename = postData.filename;
var course = postData.course;
var teacher = postData.teacher;
var downloadtime = 0;
var filesize = postData.filesize;
var uploadtime = getCurrDate();
var fileformat = postData.fileformat;
var description = postData.description;
var magnetURI = postData.magnetURI;

console.log("---------------------");
console.log("magnetURI.length", magnetURI.length);

var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : '123456',
database : 'qqshare'
});

connection.connect();

var addSql = 'INSERT INTO qqshare_info (filename,course,teacher,downloadtime,filesize,uploadtime,fileformat,description,magnetURI) VALUES(?,?,?,?,?,?,?,?,\''
+ magnetURI +'\')';
var addSqlParams = [filename,course,teacher,downloadtime,filesize,uploadtime,fileformat,description];

connection.query(addSql,addSqlParams,function (err, result) {
if(err){
console.log('[INSERT ERROR] - ',err.message);
resData = {flag: 0};
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(resData));
return;
}

console.log('--------------INSERT----------------');
//console.log('INSERT ID:',result.insertId);
console.log('INSERT ID:',result);

// resData = {flag: 1};
// res.setHeader("Content-Type", "application/json");
// res.end(JSON.stringify(resData));
});

// connection.query("SELECT * FROM qqshare_info;",function (err, result) {
// if(err){
// console.log('[SELECT ERROR] - ',err.message);
// return;
// }
// });




// 在用户数据表中更新用户的历史上传记录
// 为避免多次重传,这部分放在后,只有在成功插入文件数据库后才可能执行
var id = postData.id;

// UPDATE user_info SET uploadrecord=concat(uploadrecord,',magnet:?fake') WHERE id='123';
var SQL_query = 'UPDATE user_info SET uploadrecord=concat(uploadrecord,\','
+ magnetURI
+ '\') WHERE id=\''
+ id
+ '\';';
console.log('SQL:', SQL_query);


connection.query(SQL_query,function (err, result) {
if(err){
console.log('[UPDATE ERROR] - ',err.message);
resData = {flag: 0};
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(resData));
return;
}
console.log('--------------------------UPDATE----------------------------');
console.log('SQL:', SQL_query);
resData = {flag: 1};
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(resData));
});

connection.end();
});
})

app.get('/api/uploadrecord', function (req, res){
console.log("----------------------------------------");
console.log('/api/uploadrecord');
console.log("req.query: ", req.query);

if(req.query.id == ''){ // 空查询处理
resData = {flag: 0};
console.log("查询为空,失败。");
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(resData));
return;
}

var mysql = require('mysql');
var connection1 = mysql.createConnection({
host : 'localhost',
user : 'root',
password : '123456',
database : 'qqshare'
});

connection1.connect();

var SQL_query1 = 'SELECT uploadrecord FROM user_info WHERE id=\''
+ req.query.id + '\';';
console.log("SQL_query1:", SQL_query1);

var uploadrecord = "";
connection1.query(SQL_query1, function (err, results, fields) {
if(err){
console.log('[SELECT ERROR] - ',err.message);
resData = {flag: 0};
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(resData));
return;
}
console.log("results: ", results);

uploadrecord = results[0].uploadrecord;
console.log("uploadrecord: ", uploadrecord);
console.log("----------------------------------------");
if(uploadrecord == ""){
// 当没有下载过文件时返回[]
resData = [];
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(resData));
return;
}

var connection2 = mysql.createConnection({
host : 'localhost',
user : 'root',
password : '123456',
database : 'qqshare'
});

connection2.connect();


var upload_magnet_list = uploadrecord.split(',').filter(function (el) { return el != ''; }); // 清洗
console.log("upload_magnet_list: ", upload_magnet_list);

upload_magnet_list = JSON.stringify(upload_magnet_list).replace('[','(').replace(']',')').replace(/\"/g,'\'');
console.log("upload_magnet_list(after modify): ", upload_magnet_list);

var SQL_query2 = 'SELECT filename,downloadtime,uploadtime FROM qqshare_info WHERE magnetURI IN '
+ upload_magnet_list + ';';
console.log("SQL_query2:", SQL_query2);

connection2.query(SQL_query2, function (err, results, fields) {
if(err){
console.log('[SELECT ERROR] - ',err.message);
resData = {flag: 0};
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(resData));
return;
}

res.setHeader("Content-Type", "application/json");
console.log("----------------------------------------");
console.log(JSON.stringify(results));
res.end(JSON.stringify(results));
})

connection2.end();

});

connection1.end();

})

app.post('/api/download', function (req, res){
console.log("----------------------------------------");
console.log('/api/download');
var postData = "";
req.on("data", (chunk) => {
postData = postData + chunk;
});
req.on("end", () => {
postData = querystring.parse(postData);

console.log("----------------------------------------");
console.log('postData:', postData);

// 在文件数据表给该文件的downloadtime加一
var magnetURI = postData.magnetURI;
console.log("magnetURI:", magnetURI);

var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : '123456',
database : 'qqshare'
});

connection.connect();

var SQL_query = 'UPDATE qqshare_info SET downloadtime=downloadtime+1 WHERE magnetURI=\''
+ magnetURI + '\';';
console.log('SQL:', SQL_query);

connection.query(SQL_query,function (err, result) {
if(err){
console.log('[UPDATE ERROR] - ',err.message);
resData = {flag: 0};
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(resData));
return;
}
console.log('----------------UPDATE1----------------');

console.log('SQL:', SQL_query);
// resData = {flag: 1};
// res.setHeader("Content-Type", "application/json");
// res.end(JSON.stringify(resData));
});



// 在用户数据表中把该文件的加入到用户的历史下载记录中
var id = postData.id;
console.log("id:", id);

// UPDATE user_info SET downloadrecord=concat(downloadrecord,',magnet:?fake') WHERE id='123';
var SQL_query = 'UPDATE user_info SET downloadrecord=concat(downloadrecord,\','
+ magnetURI
+ '\') WHERE id=\''
+ id
+ '\';';
console.log('SQL:', SQL_query);

connection.query(SQL_query,function (err, result) {
if(err){
console.log('[UPDATE ERROR] - ',err.message);
resData = {flag: 0};
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(resData));
return;
}
console.log('----------------UPDATE2----------------');
console.log('SQL:', SQL_query);
resData = {flag: 1};
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(resData));
});


connection.end();
});

})

app.get('/api/downloadrecord', function (req, res){
console.log("----------------------------------------");
console.log('/api/downloadrecord');
console.log("req.query: ", req.query);

if(req.query.id == ''){ // 空查询处理
resData = {flag: 0};
console.log("查询为空,失败。");
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(resData));
return;
}

var mysql = require('mysql');
var connection1 = mysql.createConnection({
host : 'localhost',
user : 'root',
password : '123456',
database : 'qqshare'
});

connection1.connect();

var SQL_query1 = 'SELECT downloadrecord FROM user_info WHERE id=\''
+ req.query.id + '\';';
console.log("SQL_query1:", SQL_query1);

var downloadrecord = "";
connection1.query(SQL_query1, function (err, results, fields) {
if(err){
console.log('[SELECT ERROR] - ',err.message);
resData = {flag: 0};
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(resData));
return;
}
console.log("results: ", results);

downloadrecord = results[0].downloadrecord;
console.log("downloadrecord: ", downloadrecord);
console.log("----------------------------------------");
if(downloadrecord == ""){
// 当没有下载过文件时返回[]
resData = [];
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(resData));
return;
}

var connection2 = mysql.createConnection({
host : 'localhost',
user : 'root',
password : '123456',
database : 'qqshare'
});

connection2.connect();


var download_magnet_list = downloadrecord.split(',').filter(function (el) { return el != ''; }); // 清洗
console.log("download_magnet_list: ", download_magnet_list);

download_magnet_list = JSON.stringify(download_magnet_list).replace('[','(').replace(']',')').replace(/\"/g,'\'');
console.log("download_magnet_list(after modify): ", download_magnet_list);

var SQL_query2 = 'SELECT filename,downloadtime,uploadtime FROM qqshare_info WHERE magnetURI IN '
+ download_magnet_list + ';';
console.log("SQL_query2:", SQL_query2);

connection2.query(SQL_query2, function (err, results, fields) {
if(err){
console.log('[SELECT ERROR] - ',err.message);
resData = {flag: 0};
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(resData));
return;
}

res.setHeader("Content-Type", "application/json");
console.log("----------------------------------------");
console.log(JSON.stringify(results));
res.end(JSON.stringify(results));
})

connection2.end();

});

connection1.end();

})

app.post('/api/changename', function (req, res){
console.log("----------------------------------------");
console.log('/api/changename');
var postData = "";
req.on("data", (chunk) => {
postData = postData + chunk;
});
req.on("end", () => {
postData = querystring.parse(postData);

var id = postData.id;
var newname = postData.newname;

var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : '123456',
database : 'qqshare'
});

connection.connect();

var SQL_query = 'UPDATE user_info SET name=\''+newname+'\' WHERE id=\''+id+'\';';

connection.query(SQL_query,function (err, result) {
if(err){
console.log('[UPDATE ERROR] - ',err.message);
resData = {flag: 0};
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(resData));
return;
}
console.log('--------------------------UPDATE----------------------------');
console.log("SQL_query:", SQL_query);
console.log("newname:", postData.newname);

resData = {flag: 1};
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(resData));
});


connection.end();
});
})

app.get('/api/test_getCurrDate', function (req, res) {
console.log("----------------------------------------");
console.log('/api/test_getCurrDate');
console.log(getCurrDate());
res.send(getCurrDate());

})




var server = app.listen(port_number, function () {

var host = server.address().address
var port = server.address().port

console.log("QQShare is running at http://%s:%s", host, port)

})


// https://blog.csdn.net/itmyhome1990/article/details/89372292
function getCurrDate() {
var date = new Date();
var sep = "-";
var year = date.getFullYear(); //获取完整的年份(4位)
var month = date.getMonth() + 1; //获取当前月份(0-11,0代表1月)
var day = date.getDate(); //获取当前日
if (month <= 9) {
month = "0" + month;
}
if (day <= 9) {
day = "0" + day;
}
var currentdate = year + sep + month + sep + day;
return currentdate;
}

服务器持续运行

关闭shell后如何保持程序继续运行

在执行的命令之前增加nohup,在命令后增加&。然后点任意键退回到shell,再exit,node.js程序仍然会在后台运行。

如;nohup npm start &

Linux下SSH远程连接断开后让程序继续运行解决办法、在后台运行

Linux ssh状态下如何后台运行程序?

ssh连接服务器中断,如何让命令继续在服务器执行

解决Linux关闭终端(关闭SSH等)后运行的程序自动停止

nohup命令

不挂断地运行命令。

1
nohup Command [ Arg … ] [ & ]

nohup命令将当前指令的所有输出都输出到了当前文件夹下的nohup.out文件。加不加&并不会影响这个命令。只是让程序前台或者后台运行而已。

使用 jobs 查看任务。
使用 fg %n 关闭。

screen命令

1
screen

Screen将创建一个执行shell的全屏窗口。你可以执行任意shell程序,就像在ssh窗口中那样。在该窗口中键入exit退出该窗口,如果这是该screen会话的唯一窗口,该screen会话退出,否则screen自动切换到前一个窗口。

1
screen vi test.c

Screen创建一个执行vi test.c的单窗口会话,退出vi将退出该窗口/会话。

byobu命令

Unbuntu图形界面可以使用,更加集成化,且可以使用快捷键。

1
sudo apt install byobu

【pm2】

pm2的基本使用

安装

1
sudo npm install -y pm2 -g

运行

如有必要,修改Package.json中的start接口:

1
"start": "node server.js"

通过pm2运行项目:

1
sudo pm2 start server.js

不要忘记添加项目启动脚本server.js

这个可以用~

查看

查看当前运行的项目:

1
sudo pm2 ls  # 或: sudo pm2 list
日志

pm2生成的日志文件的默认路径在$ HOME /.pm2/logs /下面。

1
2
cd ~/.pm2/logs/
ll

查看历史日志:

1
sudo pm2 logs --lines 200         # 查看历史日志

查看实时日志:

1
sudo pm2 logs             # 实时显示日志

其他日志操作:

1
2
3
sudo pm2 logs --raw			# 显示流中的所有进程日志
sudo pm2 flush # 清空所有日志文件
sudo pm2 reloadLogs # 重新加载所有日志
仪表盘

仪表盘可以显示实时监控的动态数据,退出Ctrl+C

1
sudo pm2 monit          # 查看仪表盘

停止

停止当前运行的项目:

1
2
3
4
5
sudo pm2 stop server       # 停止项目名为server的应用程序

sudo pm2 stop 0 # 停止项目id为0的应用程序

sudo pm2 stop all # 停止pm2管理的所有应用程序

重启

1
2
3
sudo pm2 restart all      # 重启pm2管理的所有应用程序

sudo pm2 reload all # 重载pm2管理的所有应用程序

删除

1
2
3
4
5
sudo pm2 delete server     # 删除项目名为sever的应用程序的进程

sudo pm2 delete 0 # 删除项目id为0的应用程序的进程

sudo pm2 delete all # 删除pm2管理的所有应用程序的进程

自启动

服务器开机自启动。

1
sudo pm2 startup