Java Socket 传输文件

描述

基于Java Socket传输文件/文件夹,包括客户端、服务端,具备以下特点。

  • 一次连接,传输多文件,在传输大量小文件场景下,可节约大量连接创建时间及资源消耗
  • 支持传输文件夹,但会跳过空文件夹
  • 服务端支持多线程
  • 支持结果回显

实现

依赖

程序集成在Maven工程内,lombok非必需,仅用于提升代码整洁度,如剔除需少量改动代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>

基础信息

用于传送每次请求附带参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import lombok.Data;
import lombok.experimental.Accessors;

@Data
@Accessors(fluent = true)
public class FileTransferInfo {
public static final String TYPE_DELETE = "delete";
public static final String TYPE_WRITE = "write";
public static final String TYPE_CLOSE = "close";
public static final String MSG_DONE = "done";
public static final String MSG_ERROR = "error";

private String name;
private String path;
private String type;
}

服务端

服务端包含FileTransferServer.javaFileTransferServerThread.java 两个文件,用于实现多线程支持。

FileTransferServer

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
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.channels.ServerSocketChannel;

public class FileTransferServer {
private ServerSocket ss;
private final int port;
private final String wd;

/**
* @param port network port for listening
* @param wd working directory, files will be saved into this folder
*/
public FileTransferServer(int port, String wd) {
this.port = port;
this.wd = wd;
}

public void start() throws Exception {
FileUtils.forceMkdir(new File(wd));

InetSocketAddress listenAddr = new InetSocketAddress(port);
ServerSocketChannel listener = ServerSocketChannel.open();
ss = listener.socket();
ss.setReuseAddress(true);
ss.bind(listenAddr);

while (true) {
Socket socket = ss.accept();
System.out.println("Received request: " + socket);
FileTransferServerThread thread = new FileTransferServerThread(socket, wd);
thread.start();
}
}

public void close() {
if (ss != null && !ss.isClosed()) {
try {
ss.close();
} catch (Exception ignored) {
}
}
}
}

FileTransferServerThread

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
import org.apache.commons.io.FileUtils;
import com.google.gson.Gson;

import java.io.*;
import java.net.Socket;
import java.nio.file.Path;
import java.nio.file.Paths;

public class FileTransferServerThread extends Thread {
private static final Gson GSON = new Gson();
Socket socket;
String wd;

public FileTransferServerThread(Socket socket, String wd) {
this.socket = socket;
this.wd = wd;
}

@Override
public void run() {
try {
boolean loopFlag = true;
FileTransferInfo info;
DataOutputStream dot = new DataOutputStream(socket.getOutputStream());
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
DataInputStream dis = new DataInputStream(bis);

while (loopFlag) {
String msg = dis.readUTF();
info = GSON.fromJson(msg, FileTransferInfo.class);

if (FileTransferInfo.TYPE_CLOSE.equals(info.type())) {
loopFlag = false;
} else if (FileTransferInfo.TYPE_DELETE.equals(info.type())) {
Path fnl = Paths.get(wd, info.path());
FileUtils.deleteQuietly(fnl.toFile());
} else {
long length = dis.readLong();

Path fnl = Paths.get(wd, info.path(), info.name());
System.out.println("Receiving file: " + fnl.toAbsolutePath() + ", length: " + length);
FileUtils.forceMkdir(Paths.get(wd, info.path()).toFile());
FileUtils.touch(fnl.toFile());

FileOutputStream fos = new FileOutputStream(fnl.toFile());
BufferedOutputStream bos = new BufferedOutputStream(fos);
for (long j = 0; j < length; j++) {
bos.write(bis.read());
}
bos.close();
System.out.println("Saved file: " + fnl.toAbsolutePath());
}

dot.writeUTF(FileTransferInfo.MSG_DONE);
dot.flush();
}

System.out.println("Closing socket...");
dis.close();
dot.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (socket != null) {
try {
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}

客户端

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
import java.io.*;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Paths;

import com.google.gson.Gson;

public class FileTransferClient {
private static final Gson GSON = new Gson();
Socket socket;
DataOutputStream dos;
DataInputStream dis;

public FileTransferClient(Socket socket) throws IOException {
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
this.socket = socket;
this.dos = new DataOutputStream(bos);
this.dis = new DataInputStream(socket.getInputStream());
}

public void send(String path, File file) throws IOException {
if (file.isDirectory()) {
File[] files = file.listFiles();
for (int i = 0; files != null && i < files.length; ++i) {
File f = files[i];
send(Paths.get(path, file.getName()).toString(), f);
}
} else {
System.out.println("Sending file: " + file.getAbsolutePath() + ", length: " + file.length());
dos.writeUTF(GSON.toJson(new FileTransferInfo().name(file.getName()).path(path)));
dos.writeLong(file.length());
Files.copy(file.toPath(), dos);
dos.flush();
System.out.println(dis.readUTF());
}
}

public void close() throws IOException {
FileTransferInfo info = new FileTransferInfo().type(FileTransferInfo.TYPE_CLOSE);
dos.writeUTF(GSON.toJson(info));
dos.flush();
System.out.println(dis.readUTF());
dis.close();
dos.close();
socket.close();
}

public void delete(String path) throws IOException {
FileTransferInfo info = new FileTransferInfo().path(path).type(FileTransferInfo.TYPE_DELETE);
dos.writeUTF(GSON.toJson(info));
dos.flush();
System.out.println(dis.readUTF());
}
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
// 启动服务端
FileTransferServer server = new FileTransferServer(2221, "/tmp");
server.start();

// 客户端传送文件
String host = "127.0.0.1";
int port = 2221;
String path = "receiver";
Socket socket = new Socket(host, port);
FileTransferClient client = new FileTransferClient(socket);
client.delete(path);
client.send(path, new File("/Users/wii/Downloads/cat.jpg"));
client.close();

更多

  • 优化
    • 零拷贝
  • 代码

shell 编程

快捷键

1
2
3
4
5
6
7
8
9
10
11
12
Ctrl + R : 搜索历史命令
Ctrl + a : 指针跳到命令行开始
Ctrl + e : 指针跳到命令行结尾
Ctrl + b : 向后移动一个字符
Ctrl + f : 向前移动一个字符

Alt + right : 向右移动一个词
Alt + left : 向右移动一个词
Ctrl + X, Ctrl + X : 在当前位置和开始位置切换

Ctrl + s : 暂停终端输出
Ctrl + q : 恢复终端输出

特殊参数

1
2
3
4
5
6
7
8
9
10
11
12
13
$!  最后运行的后台线程 id
$? 最后运行的命令的结束码
$$ 脚本本身进程 id

$# 参数个数
$@ 参数列表. "$@" = "$1" "$2" "$3" "$..."
$* 参数列表. "$*" = "$1 $2 $3 ..."
$0 脚本文件名
$1~$n 参数

# 参数列表截取
${*:2} 截取第二个参数及之后
${*:2:3} 截取第二、三个参数

getopts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# opts
:前缀 忽略错误
:后缀 参数后必须有值

# example
:abc:de: 忽略参数错误,-c、-e后必须有值

while getopts ":ab:Bc:" opt; do
case $opt in
a) echo "found -a" ; a="ok" ;;
b) echo "found -b and value is: $OPTARG" ;;
*) "echo usage" ;;
esac
done

函数

1
2
3
4
5
echo() {
echo "$1"
}

echo Hello

test

test 命令通常被 [...] 替代

1
2
3
4
5
6
7
8
# [ ... ]
[ ... ] :

# if ... else ...
if [ expression ]
then
...
fi

[] 和 [[]] 的区别

  • 兼容性

    • [] 是内置命令 test 的可选项,在 Linux、Unix、POSIX 系统兼容

    • [[]] 是 Korn Shell 作为增强功能引入的,被 base、zsh 等支持,但在 POSIX 系统中不兼容

  • [[]][] 的差异

    • 比较运算符
      • 比如,[[ 1 < 2 ]],等价于 [ 1 \< 2 ]
    • 布尔运算符(-a -> &&-o -> ||
      • 比如,[[ 3 -eq 3 && 4 -eq 4 ]] 等价于 [ 3 -eq 3 -a 4 -eq 4 ]
    • 组合表达式
      • 比如,[[ 3 -eq 3 && (2 -eq 2 || 1 -eq 1) ]] 等价于 [ 3 -eq 3 -a \( 2 -eq 2 -o 1 -eq 1 \) ]
    • 模式匹配
      • 比如,[[ $name = *c* ]][] 中没有对应的匹配运算
    • 正则匹配
      • 比如,[ $name =~ ^Ali ][] 中没有对应的匹配运算
    • 单词切分([[]] 不会对变量值按空白符切分,[] 对应得需要添加 "" 来达到相似的效果)
      • 比如,name = "Wukong Sun"[[ $name ]] 等价于 [ "$name" ]

参数

字符串

1
2
3
4
5
6
-n 不为空
-z 为空
{string1} = {string2} string1 和 string2 相同
{string1} != {string2} string1 和 string2 不相同
{string1} < {string2} 基于 ASCII 码比较
{string1} > {string2} 基于 ASCII 码比较

正则匹配

1
2
3
4
"{data}" =~ ^{regex}$

# 示例
[[ "$date" =~ ^[0-9]{8}$ ]] && echo "YES"

变量

1
-v 变量是否 set

数字比较

1
2
3
4
5
6
7
{number1} -eq/-ne/-lt/-le/-gt/-ge {number2}
{number1} -eq {number2} 相等
{number1} -ne {number2} 不相等
{number1} -lt {number2} 小于
{number1} -le {number2} 小于等于
{number1} -gt {number2} 大于
{number1} -ge {number2} 大于等于

文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-a/-e 文件是否存在
-b 文件存储在块存储设备
-d 是否是文件夹
-f 文件存在且是常规文件
-h/-L 文件是符号链接
-p 文件是否是使用 mkfifo 创建的命名管道
-r 是否可读(运行命令的用户)
-s 文件存在且不为空
-S 是否是 Socket 文件
-t fd (file descriptor) 是否在终端中打开
-w 是否可写(运行命令的用户)
-x 是否可执行(运行命令的用户)
-O 运行命令的用户是否是文件 Owner
-G 文件是否被运行命令的用户 Group 拥有
{file1} -nt {file2} file1 是否比 file2 更新
{file1} -nt {file2} file1 是否比 file2 更旧
{file1} -ef {file2} file1 是否是 file2 的硬链接

多表达式

1
2
{expr1} -o {expr2} 或
{expr1} -a {expr2} 且

路径及文件

1
2
3
4
5
6
7
8
9
10
11
# 提取文件名
$(basename "/path/to/file") # file

# 提取文件路径(非绝对路径)
$(dirname "/path/to/file") # /path/to

# 获取绝对路径
$(pwd)/$(dirname "./path/to/file")

# 脚本路径
BASE=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)

数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 定义数组
ARR=(a b c)

# ARR[@]: 数组转换为字符串
echo "${ARR[@]}"
a b c

# ARR[*]
echo "${ARR[*]}"
a b c

# 数组赋值
ANOTHER=("${ARR[@]}")

# 长度
echo ${#ARR[@]}
3

# 序号
for idx in "${!ARR[@]}"; do
echo "$idx"
done

[@] VS [*]

数组的 [@][*] 都是取所有元素,但是有所差别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 定义
$ ARR=(a b c)
$ A=("${ARR[@]}")
$ B=("${ARR[*]}")

# A
$ echo ${#A[@]}
3
$ echo ${A[@]}
a b c

# B
$ echo ${#B[@]}
1
$ echo ${B[@]}
a b c

布尔

1
2
3
4
FLAG=true
if [[ $FLAG == true ]]; then
# do something
fi

字符串

比较

1
[ "$v" = "value" ]

截取

1
2
3
4
5
$ s=abcd
# 截取区间是 [left, right]
$ echo ${s:1}
bcd
$ echo ${s:1:2}

循环

while

语法

1
2
3
4
while command
do
Statement(s) to be executed if command is true
done

示例

1
2
3
4
5
6
7
8
9
a=0
while [ $a -lt 10 ]
do
echo $a
a=`expr $a + 1`
done

# 等待 port
while ! lsof -i:8080; do echo "wait for server ready"; sleep 1; done

数组

1
2
3
4
5
6
7
8
9
10
# 命令行
$ countries=(china us); for country in ${countries[@]}; echo $country
china
us

# 脚本
countries=(china us)
for country in ${countries[@]}; do
echo $country
done

范围

1
2
3
4
5
6
7
8
9
10
11
12
13
$ for id in {1..3}; echo $id   # 1 2 3

# 脚本
for id in {1..3}; do
echo $id
done

# seq [首数] [增量] 尾数
$ seq 1 3 # [1, 3]
$ seq 3 # [1, 3]
$ seq 1 2 5 # 1, 3, 5

$ for i in `seq 3`; do echo $i; done # 1 2 3

case…esac

1
2
3
4
5
6
7
8
9
10
11
12
word=a
case $word in
a)
echo "a $word"
;;
b)
echo "b $word"
;;
*)
echo "* $word"
;;
esac

getopts

参数

1
2
3
4
5
6
# opts
:前缀 忽略错误
:后缀 参数后必须有值

# example
:abc:de: 忽略参数错误,-c、-e后必须有值

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
usage() {
cat <<EOF
Usage: $0 [-a] [-b name] msg
EOF
}

while getopts ":ab:Bc:" opt; do
case $opt in
a) echo "found -a" ; a="hello" ;;
b) echo "found -b and value is: $OPTARG" ;;
c) echo "found -c and value is: $OPTARG" ;;
*) usage ;;
esac
done

shift $(($OPTIND - 1))

数据存储对比

数据库

KV存储

服务 备注
Redis
HBase
Pegasus

关系型数据库

服务 备注
MySQL

时序数据库

服务 备注
TDengine
InfluxDB

查询引擎

  • Impala
  • Presto

其他

OLAP & OLTP

OLTP OLAP
特性 操作处理 信息处理
面向 事务 分析
用户 操作人员,底层管理人员 决策人员,高级管理人员
功能 日程操作处理 分析决策
DB设计 面向应用 面向主题
数据 当前的,最新的,细节的,二维的,分离的 历史的,聚焦的,多维的,集成的,统一的
存取 读/写数十条记录 读上百万条数据
工作单位 简单的事务 复杂的查询
用户数 上千个 上百万个
规模 GB TB
时效 具有时效性要求 时效要求不严格
主要应用 数据库 数据仓库
优先 事务吞吐量 查询吞吐量、响应时间

待分类

  • Impala
  • Druid
  • ClickHouse
  • Hive
  • Impala
  • InfluxDB

参考

mac usage

初始化

homebrew

1
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

node

参见 前端/node

工具

homebrew

commond line tools

1
2
3
4
5
6
7
8
9
10
# install
$ xcode-select --install

# delete
$ sudo rm -rf `xcode-select -p` # 一般会在文件夹 /Library/Developer/CommandLineTools 内

# Problems
## Can’t install the software because it is not currently available from the Software Update server.
# 手动下载
https://developer.apple.com/download/more/?=command%20line%20tools

问题

拷贝文件导致图标变灰无法访问

尝试 这里 无果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 仅适用文件夹
# 命令行输入如下内容
exitFun() {
echo "ERROR: failed, due to $1"
exit 1
}

fix() {
[ ! -d "${1}" ] && exitFun "target is not folder"
mv "${1}" "${1}_back"
mkdir "${1}"
mv "${1}_back"/* "${1}/"
mv "${1}_back"/.[^.]* "${1}/"
rmdir "${1}_back"
}

# 修复,输入 fix <文件夹目录> OR 输入 fix [拖拽文件夹至终端]
$ fix <path>

mysql/mariadb usage

[toc]

MySQL

**注 : **有些操作和操作系统有关。

CLI登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mysql -h localhost --port 3306 -u root -p --socket=/var/lib/mysql/mysql.sock  

#### 示例
bovenson@MBP:~/Git/notes/MySQL$ mysql -h localhost -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 14
Server version: 5.7.20 MySQL Community Server (GPL)

Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

启动/停止

1
2
3
4
### Mac
sudo /usr/local/mysql/support-files/mysql.server start # 启动
sudo /usr/local/mysql/support-files/mysql.server stop # 停止
sudo /usr/local/mysql/support-files/mysql.server restart # 重启

修改密码认证规则

1
2
3
4
5
6
7
SET GLOBAL validate_password.LENGTH = 4;
SET GLOBAL validate_password.policy = 0;
SET GLOBAL validate_password.mixed_case_count = 0;
SET GLOBAL validate_password.number_count = 0;
SET GLOBAL validate_password.special_char_count = 0;
SET GLOBAL validate_password.check_user_name = 0;
FLUSH PRIVILEGES;

修改密码

SET PASSWORD

1
2
3
4
5
# set password for 用户名@localhost = password('新密码'); 
set password for root@localhost = password('123');

# 修改
ALTER USER 'user'@'localhost' IDENTIFIED BY 'pass';

系统用户

添加

1
2
CREATE USER 'myuser'@'localhost' IDENTIFIED BY 'mypass';
CREATE USER 'myuser'@'%' IDENTIFIED BY 'mypass';

赋予权限

1
2
3
GRANT ALL ON *.* TO 'myuser'@'localhost';
GRANT ALL ON *.* TO 'myuser'@'%';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;

刷新权限

1
> FLUSH PRIVILEGES;

允许远程连接

设置bind-address

1
2
3
4
5
6
7
# mysql
sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf
# mariadb
/etc/mysql/mariadb.conf.d/50-server.cnf

# 修改bind-address
bind-address = 0.0.0.0

添加/修改账户

添加/修改账户允许的Host%

命令行远程登录mysql服务器

1
mysql -u root -p -h 10.154.0.43 -P 3306

设置

1
2
> USE mysql;
> UPDATE user SET Host='%' WHERE User='root' AND Host='localhost';

workbench

时间设置默认当前

默认设置为CURRENT_TIMESTAMP

添加管理账户

lower_case

错误

Can’t get hostname from your ip address

Just add below in my.ini or my.cnf.

1
2
[mysqld]
skip-name-resolve

使用

执行 SQL 文件

1
> SOURCE /path/to/file.sql;

创建数据库

1
> CREATE DATABASE 'database-name';

Pretty 查询结果

1
> select * from <table> \G

安装

CentOS

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
# 下载 rpm 源
$ wget https://repo.mysql.com//mysql57-community-release-el7-11.noarch.rpm

# 安装 rpm 源
$ yum localinstall mysql57-community-release-el7-11.noarch.rpm

# 安装 MySQL
$ yum install mysql-community-server

# 启动 MySQL
$ systemctl start mysqld

# 开机启动
$ systemctl enable mysqld
$ systemctl daemon-reload

# 获取root密码
$ cat /var/log/mysqld.log | grep password
... [Note] A temporary password is generated for root@localhost: ...

# 登录mysql
$ mysql -u root -p
# 输入获取到的临时密码

# 重置root密码
mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY '{password}';

# 更新密码
mysql > UPDATE mysql.user SET Password=PASSWORD('root') WHERE User='root' AND Host='%';

# 新建远程登录用户
mysql> CREATE USER 'root'@'%' IDENTIFIED BY '{password}';

# 授权
mysql> GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;

# 刷新权限
mysql> FLUSH PRIVILEGES;

# 防火墙放行3306端口
$ firewall-cmd --zone=public --add-port=3306/tcp --permanent
$ firewall-cmd --reload

导入导出

参数

1
2
-B, --databases : 导出创建语句
--skip-add-locks : 导出的文件中跳过添加 LOCK TABLE 语句

示例

1
2
3
$ mysqldump -uroot -p<password> -h 'host' -P port <database>  # 导出数据库
$ mysqldump -uroot -p<password> -h 'host' -P port <database> <table> # 导出表
$ mysqldump -uroot -p<password> d <database>

术语

编码

  • OOP 面向对象编程
  • AOP 面向切面编程
  • IOC 控制反转
  • DO (Data Object) 数据对象
  • DTO (Data Transfer Object) 数据传输对象
  • BO(Business Obejct) 业务对象
  • AO(Application Object) 应用对象
  • VO(View Object) 显示层对象
  • POJO (Plain Ordinary Java Object) 简单Java对象
  • DAO(Data Access Object) 数据存取对象

并行计算

  • MPP(Massively parallel Processing) 大规模并行处理

架构

  • RPS (Request Per Second) 每秒请求量
  • Ad-Hoc Query 海量实时计算/查询
  • OLAP (Online Analytical Processing) 联机分析处理
  • OLTP (Online Transaction Processing) 联机事务处理
  • LSM-tree (Log Structured-Merge Tree) 是一种分层,有序,面向磁盘的数据结构
  • WAL (Write-ahead logging) 预写式日志
  • LSN (Log Sequence Number) 日志序列号

大数据

  • BI (Business Intelligence) 智能商业
  • ETL (Extract-Transform-Load) 数据仓库技术,用于描述将数据从源端经过抽取、转换、加载到目的端的过程
  • UDF & UDAF & UDTF
    • UDF (User-Defined Function) 用户自定义函数
    • UDAF (User-Defined Aggregation Function) 用户自定义聚合函数
    • UDTF (User-Defined Table Generating Function)
  • HSAP (Hybrid Serving and Analytical Processing) 混合服务/分析处理
  • HTAP (Hybrid Transactional/Analytical Processing) 混合事务/分析处理
  • DW (Data Warehouse) 数据仓库 参考一 参考二 数据仓库分层
    • DM (Data Mart) 数据集市
    • 分层
      • ODS (Operational Data Store) 操作性数据 / 数据准备层 / 数据运营层
      • DW 数据仓库层
        • DWD (Data Warehouse Detail) 细节数据层
        • DWB (Data Warehouse Base) 数据基础层
        • DWS (Data Warehouse Service) 数据服务层
      • ADS (Apoplication Data Service) 应用数据层
  • Wide Table 宽表,业务主题相关的指标、维度、属性关联在一起的一张数据库表

广告

  • CVR(Conversion Rate) 转化率
  • CPA(Cost Per Action) 广告计费方式,指按广告投放实际效果收费
  • CPM(Cost Per Mille) 成本/千人,在投放广告领域,是一种按投放量收费的计费方式

产品

  • PV(page view) 页面浏览量,用户多次打开同一页面累计多次
  • UV(unique visitor) 独立访客,一天内访问网站去重用户数
  • IP 独立IP,一天内访问网站去重IP数
  • VV(video view) 视频播放量,统计周期内视频被播放次数
  • CV(content view) 内容播放量,是一个周期内,视频被打开且成功播放次数

口语

  • trade-offs 权衡

hexo usage

访问

官网

安装

1
$ npm install hexo-cli -g

初始化站点

1
2
3
$ hexo init <folder>
$ cd <folder>
$ npm install

文件说明

1
2
3
4
5
6
7
8
.
├── _config.yml # 配置
├── package.json # 依赖描述
├── scaffolds # 模板文件夹
├── source # 资源文件夹,除_posts外,_开头文件夹会被忽略
| ├── _drafts
| └── _posts
└── themes # 主题

Front Matter

参数 描述 默认值
layout 布局
title 标题
date 建立日期 文件建立日期
updated 更新日期 文件更新日期
comments 开启文章的评论功能 true
tags 标签(不适用于分页)
categories 分类(不适用于分页)
permalink 覆盖文章网址

注意

分类

Hexo不支持多统计分类,如下分类。

1
2
3
categories:
- Diary
- Life

会使分类Life成为Diary的子分类,而不是并列分类。

如需为文章添加多个分类,尝试如下方式。

1
2
3
4
categories:
- [Diary, PlayStation]
- [Diary, Games]
- [Life]

示例

1
2
3
4
5
categories:
- Diary
tags:
- PS3
- Gamesa

生成静态文件

1
$ hexo generate

Github Webpage

  • 创建仓库,仓库名称为 ${user}.github.io

  • 上传静态文件至仓库

  • 仓库设置中配置 GitHub Pages

    • 此时,可通过域名 ${user}.github.io 访问
  • 配置自定义域名

    • 在 Hexo source 目录下,创建CNAME文件,写入自定义域名

    • 在仓库设置中配置 GitHub Pages 中的Custom domain 中输入自定义域名 [可能无需该操作]

    • 域名服务商配置CNAME,以阿里云为例

静态资源

1
2
3
4
5
6
7
8
# _config.yml 中开启/添加如下配置
post_asset_folder: true
marked:
prependRoot: true
postAsset: true

# 放置: 在文档相同路径下,创建和项目相同名称文件夹,静态资源放置于此
# 引用示例: ![](doc/img.jpg) OR ![](img.jpg)

vsftpd usage

安装

1
2
# ubuntu
$ sudo apt install vsftpd

配置

配置文件路径

1
2
# ubuntu
/etc/vsftpd.conf

配置项

1
listen=YES    # 是否监听网络端口, 默认NO

用户

本地用户

1
local_enable=YES    # 允许使用本地用户账号密码登录

虚拟用户

仅用于登录ftp服务器的账号密码。

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
# [required] 安装 pam & htpasswd
$ sudo apt install libpam-pwdfile apache2-utils

# [required] 创建目录
$ sudo mkdir /home/ftp
$ sudo mkdir /etc/vsftpd

# [required] /etc/vsftpd.conf 修改为如下配置
listen=YES
anonymous_enable=NO
local_enable=YES
write_enable=YES
local_umask=022
nopriv_user=vsftpd
virtual_use_local_privs=YES
guest_enable=YES
user_sub_token=$USER
local_root=/var/www/$USER # root, 登录前确保用户目录已存在
chroot_local_user=YES
hide_ids=YES
guest_username=vsftpd
user_config_dir=/etc/vsftpd_user_conf
allow_writeable_chroo=YES

# [required] 创建ftp用户
$ sudo htpasswd -cd /etc/vsftpd/ftpd.passwd user-name # then enter password
# -c: create, 如需额外添加,去除该参数

# [required] /etc/pam.d/vsftpd 替换为如下内容
auth required pam_pwdfile.so pwdfile /etc/vsftpd/ftpd.passwd
account required pam_permit.so

# [required] 创建没有shell登录权限的本地用户 vsftpd,用于虚拟用户登录
$ sudo useradd --home /home/vsftpd --gid nogroup -m --shell /bin/false vsftpd
# [optional] 验证
$ id vsftpd

# [optional untested] 用户独立配置,配置文件路径为 ${user_config_dir}/${user},以 wii 为例
$ sudo vim /etc/vsftpd_user_conf

# [required] 重启vsftpd
$ sudo service vsftpd restart

参考

书籍分享

心理学

  • 缓解压力 / 摆脱迷茫系列
    • 《人间值得》,中村恒子、 奥田弘美
    • 勇气三部曲,岸见一郎
      • 《被讨厌的勇气》
      • 《被拒绝的勇气》
      • 《幸福的勇气》
  • 马斯洛心理学系列
    • 《动机与人格》
    • 《人性能达到的境界》
    • 《寻找内在的自我:马斯洛谈幸福》

科幻

  • 《三体》系列,刘慈欣

小说

  • 《杀死一只知更鸟》,哈珀·李
  • 《追风筝的人》,卡勒德·胡赛尼