Skip to content

Latest commit

 

History

History
3258 lines (3029 loc) · 122 KB

Tip.org

File metadata and controls

3258 lines (3029 loc) · 122 KB

Code Tips

Linux

查看 Linux 系统信息

  • 使用 top 命令来查看各个进程的使用情况
常用交互命令解释
q退出程序
I切换显示平均负载和启动时间的信息
P根据CPU使用百分比大小进行排序
M根据驻留内存大小进行排序
k终止一个进程,系统提示输入 PID
i忽略闲置和僵死的进程
  • 查看 CPU 的个数与核心数
# 查看物理 CPU 的个数
cat /proc/cpuinfo | grep "physical id" | sort | uniq | wc -l
# 查看每个 CPU 的核心数
cat /proc/cpuinfo | grep "physical id" | grep "0" | wc -l

Linux 解压 (tar 命令、unzip 命令)

1. 解压 .tar 文件
$ tar -xf *.tar

2. 解压 .tar.gz 文件
$ tar -xzf *.tar.gz

3. 解压 .tar.xz 文件
$ xz -d *.tar.xz
$ tar -xf *.tar

4. 解压 .tar.bz2 文件 
$ tar -jxvf *.tar.bz2
- 如果tar不支持j选项,就用下面方式解压
$ bzip2 -d  *.tar.bz2
$ tar -xvf  *.tar.bz2

5. 解压 .tar.Z 文件
$ tar -xZf *.tar.Z

6. 解压 .zip 文件
$ unzip *.zip

7. 解压到指定路径
$ tar -xzvf *.tar.gz -C ~/dic/
$ unzip *.zip -d ~/dic/

Linux 下 nginx 安装、启动、重启、停止

安装

tar -xzf nginx-${version}.tar.gz
cd nginx-${version}
yum install -y pcre-devel openssl-devel
./configure 
make
make install

nginx 默认安装路径

/usr/local/nginx

启动

${nginx_path}/sbin/nginx

启动指定配置文件

${nginx_path}/sbin/nginx -c ${conf_path}

重启

${nginx_path}/sbin/nginx -s reload

停止

${nginx_path}/sbin/nginx -s stop

Nginx 反向代理配置

在 http-server-location / 下填写反向代理配置

proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

Nginx 配置 http 请求重定向到 https

  • 修改 Nginx 的配置文件
# ...
http {
     # ...
     server {
            listen 80;
            server_name www.saisimon.net;
            # 重定向到 https
            return 301 https://$server_name$request_uri;
     }
     server {
            listen 443 ssl;
            # ...
     }
     # ...
}
# ...
  • 测试配置文件是否正确,重启 Nginx 服务
$ sudo service nginx configtest
$ sudo service nginx restart

Nginx 配置 Let`s Encrypt 提供的免费 SSL 证书

  • 安装脚本

    acme.sh

    # 直接通过 url 安装
    $ curl https://get.acme.sh | sh
    
    # 或者从 git 中安装
    $ git clone https://github.com/Neilpang/acme.sh.git
    $ cd ./acme.sh
    $ ./acme.sh --install
        
  • 为域名申请 SSL 证书,以 www.saisimon.net 为例
    # www.saisimon.net 需要申请 SSL 证书的域名地址
    # /home/saisimon/public nginx 配置的 root 目录,acme.sh 会在 Nginx 根目录下生成验证文件,验证完成后自动删除验证文件
    $ acme.sh --issue -d www.saisimon.net -w /home/saisimon/public
    Your cert is in  /home/saisimon/.acme.sh/www.saisimon.net/www.saisimon.net.cer 
    Your cert key is in  /home/saisimon/.acme.sh/www.saisimon.net/www.saisimon.net.key 
    The intermediate CA cert is in  /home/saisimon/.acme.sh/www.saisimon.net/ca.cer 
    And the full chain certs is there:  /home/saisimon/.acme.sh/www.saisimon.net/fullchain.cer
    # 若提示 acme.sh 不存在,请重新加载当前用户环境
    $ source ~/.bashrc
        
  • 复制/安装 SSL 证书
    # 指定 key 与 fullchain 的安装路径,配置重启 Nginx 的命令,使用 force-reload 命令来加载证书
    acme.sh --installcert -d www.saisimon.net \
             --keypath       /home/saisimon/ssl/www.saisimon.net.key  \
             --fullchainpath /home/saisimon/ssl/www.saisimon.net.key.pem \
             --reloadcmd     "sudo service nginx force-reload"
        
  • 配置 DH 密钥
    # 指定 dh 保存路径
    openssl dhparam -out /home/saisimon/ssl/dhparam.pem 2048
        
  • 配置 Nginx 启用 SSL
    # 请确保 Nginx 带有 http_ssl_module 的 module,不知道有什么 module,可以使用以下命令查看
    $ sudo nginx -V
    # 如果没有 http_ssl_module,请带上 ssl module 重新编译安装 Nginx
    $ cd $NGINX_HOME
    # 记得带上 nginx -V 获得的原来的参数
    $ ./configure --prefix=/usr/share/nginx ... --with-http_ssl_module
    $ make
        
    #...
    http {
         #...
         # 使用的 SSL 协议版本
         ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
         # 服务器密码优先
         ssl_prefer_server_ciphers on;
         # 禁止使用不安全的加密算法
         ssl_ciphers 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4';
         #...
         server {
                listen                  443 ssl;
                server_name             www.saisimon.net;
                root                    /home/saisimon/public;
                ssl_certificate         /home/saisimon/ssl/www.saisimon.net.key.pem;
                ssl_certificate_key     /home/saisimon/ssl/www.saisimon.net.key;
                ssl_dhparam             /home/saisimon/ssl/dhparam.pem;
                #...
         }
         #...
    }
        
    # 测试 Nginx 配置文件是否正确
    $ sudo nginx -t
    # 重启 Nginx 服务
    $ sudo nginx -s reload
        
  • 验证 SSL 是否生效

    使用 SSL Labs 测试

  • 证书有效期 Let`s Encrypt 的证书有效期为 90 天,acme.sh 脚本已经往 crontab 增加了一行每天执行的命令,当证书快过期时去自动更新证书内容
    $ crontab -l
    6 0 * * * "/home/saisimon/.acme.sh"/acme.sh --cron --home "/home/saisimon/.acme.sh" > /dev/null
        

Linux 下安装 cheat 命令

  • 通过 pip 安装 cheat
- 安装 python 和 pip
  $ yum install python-pip -y
- 更新 pip
  $ pip install --upgrade pip
- 安装 cheat
  $ pip install cheat
  • 通过 github 下载源码安装 cheat
- 安装 python 和 pip
  $ yum install python-pip -y
- 更新 pip
  $ pip install --upgrade pip
- 安装编译工具
  $ pip install docopt pygments appdirs
- clone cheat 源码
  $ git clone git@github.com:chrisallenlane/cheat.git
- 进入源码目录
  $ cd cheat
- 编译安装 cheat
  $ python setup.py install

Linux 设置命令别名

  • 列出目前所有的别名设置
    $ alias
    alias l.='ls -d .* --color=auto'
    alias ll='ls -l --color=auto'
    alias ls='ls --color=auto'
    alias vi='vim'
        
  • 设置指令的别名(本次登录有效)
    $ alias [别名]=[指令名称]
    $ alias la='ls -a'
    $ alias cp='cp -i'
        
  • 每次登录自动设置,修改自己的配置文件
    # 编辑配置文件
    $ vim ~/.bash_profile
    # 配置别名
    alias ..='cd ..'
    alias cp='cp -i'
    alias df='df -h'
    alias home='cd ~'
    alias l.='ls -d .* --color=auto'
    alias la='ls -a --color=auto'
    alias ll='ls -l --color=auto'
    alias ls='ls --color=auto'
    alias targz='tar -xzvf'
    alias vi='vim'
    # 重新加载配置文件
    $ source .bash_profile
        

Shell 脚本中运用 Linux 中设置的别名

#!/bin/bash

# 引入加载存有 alias 的 profile
source ~/.bash_profile
# 设置别名在 shell 脚本中可用
shopt -s expand_aliases
# 运用 alias
...

判断用户是否登录 Linux

#!/bin/bash

# x 表示 username 当前在线的个数
x=$(who | grep -c $username)
if [ $x -lt 1 ]
then
    echo "$username not login"
else
    echo "$username login"

Linux 创建新用户,并设置 sudo 权限

# one
sudo useradd -m -g sudo username # 创建名为 username 的新用户,创建默认 home 目录,指定用户组为 sudo
# two
sudo adduser --home /home/username username # 创建名为 username 的新用户,并指定 home 目录位置
sudo usermod -aG sudo username # 加入 sudo 组

Shell 脚本查询指定文件夹下的所有文件

#!/bin/bash
function searchFile() {
    for file in `ls $1`
    do
        if [ -d $1"/"$file ];then
           searchFile $1"/"$file
        elif [ -f $1"/"$file ];then
           echo $1"/"$file
        fi
    done
}
searchFile "/home"

Linux SSH 证书登录

# 客户端生成公钥与私钥
[client@localhost]$ ssh-keygen -t rsa 
# 设置生成的目录位置,设置私钥密码
# 默认在 ~/.ssh 目录下生成 id_rsa 和 id_rsa.pub

# 服务端配置 ssh 配置文件
[server@localhost]$ vim /etc/ssh/sshd_config
#使用成对的密钥系统进行登录
RSAAuthentication yes
PubkeyAuthentication yes
AuthorizedKeysFile %h/.ssh/authorized_keys
#禁用密码登录
PasswordAuthentication no
# 重新启动 ssh 服务
[server@localhost]$ service ssh restart

# 客户端将公钥上传至服务端
[client@localhost]$ scp ~/.ssh/id_rsa.pub <sever-user>@<server-ip>:~
# 服务端添加客户端的公钥到 authorized_keys 中
[server@localhost]$ cat id_rsa.pub >> ~/.ssh/authorized_keys

Linux 让进程在后台运行的方法

# 1. Ctrl + z, bg
[root@localhost]$ mvn -Djetty.port=8888 jetty:run > jetty.log 2>&1
# 按下 Ctrl + z 挂起到后台暂停运行
^Z
[1]+  Stopped  mvn -Djetty.port=8888 jetty:run > jetty.log 2>&1
# bg 命令将挂起的进程放在后台
[root@localhost]$ bg
[1]+  mvn -Djetty.port=8888 jetty:run > jetty.log 2>&1

# 2. setsid 命令使执行进程不属于接受 HUP 信号的终端的子进程
[root@localhost]$ setsid mvn -Djetty.port=8888 jetty:run > jetty.log 2>&1

# 3. & 将命令放入后台运行
[root@localhost]$ (mvn -Djetty.port=8888 jetty:run > jetty.log 2>&1 &)

参考

CentOS 7 开放防火墙的 80 等端口

# 防火墙开启作用域为 public,80 端口,并且永久生效
[root@localhost]$ firewall-cmd --zone=public --add-port=80/tcp --permanent

# 重新启动防火墙
[root@localhost]$ systemctl restart firewalld.service

使用 GoAccess 分析 Nginx 日志

  • 安装 GoAccess
# 安装 NCurses 依赖
$ sudo yum install ncurses-devel
# 到 Home 目录
$ cd ~
# 下载 GoAccess 安装包
$ wget http://tar.goaccess.io/goaccess-1.2.tar.gz
# 解压
$ tar -xzvf goaccess-1.2.tar.gz
$ cd goaccess-1.2/
# 检查依赖,配置参数,生成 Makefile 文件
$ sudo ./configure --enable-utf8 --enable-geoip=legacy
# 编译
$ sudo make
# 安装
$ sudo make install
  • 分析 Nginx 日志
# 将分析结果输出到当前终端
$ goaccess access.log -c
# 将分析结果输出为 HTML 文件,-o 指定输出位置,--log-format 指定 Nginx 日志的格式
$ goaccess access.log -o report.html --log-format=COMBINED

Crontab 命令使用

  • Crontab 命令参数说明
# 用法
$ crontab <选项> 参数
# 编辑当前用户的定时器任务
$ crontab -e
# 显示当前用户的定时器任务列表
$ crontab -l
# 清空当前用户的定时器任务
$ crontab -r
# 指定 someone 用户的定时器任务操作
$ crontab -u someone -e
# 指定 someone 用户的定时器任务
$ crontab -u someone /home/someone/cronfile
  • Crontab 用户任务格式
# 任务格式: cron表达式 + 命令
# cron表达式:
# .---------------- 分钟 (0 - 59)
# |  .------------- 小时 (0 - 23)
# |  |  .---------- 天 (1 - 31)
# |  |  |  .------- 月 (1 - 12)
# |  |  |  |  .---- 星期 (0 - 6), 0或者7代表星期日
# |  |  |  |  |
# *  *  *  *  * command
  • 一些常用的 cron 表达式
# 每一分钟执行一次
# 2018/01/23 00:00:00 
# 2018/01/23 00:01:00
* * * * * * date > date.log

# 每五分钟执行一次
# 2018/01/23 00:00:00 
# 2018/01/23 00:05:00
0/5 * * * * * date > date.log

# 每一小时执行一次
# 2018/01/23 00:00:00 
# 2018/01/23 01:00:00
0/5 * * * * * date > date.log

# 每天01:30执行一次
# 2018/01/23 01:30:00 
# 2018/01/24 01:30:00
30 1 * * * * date > date.log

# 每月12号01:30执行一次
# 2018/01/12 01:30:00 
# 2018/02/12 01:30:00
30 1 12 * * * date > date.log

# 每周三01:30执行一次
# 2018/01/24 01:30:00 
# 2018/01/30 01:30:00
30 1 * * * 3 date > date.log

**使用 crontab 定时执行脚本时要注意当前的环境变量,避免出现在定时执行时命令不存在的问题,当定时任务执行有误时,而没有将错误重定向到其他位置,cron默认会通过邮件的形式将执行结果保存在 /var/mail/{user} 文件下**

crontab命令

Windows

右键添加命令提示符(CMD)

  1. Win + R 运行 regedit
  2. 找到 计算机 > HKEY_CLASSES_ROOT > Folder > shell
  3. 右键shell 新建 -> 项 -> 命令提示符
  4. 右键命令提示符 新建 -> 项 -> command
  5. 点击(默认)项 输入 C:/Windows/System32/cmd.exe /k cd %1 然后点击确认

修改 XShell Alt 键作为 Meta 键, Backspace 键作为删除键

  1. 打开XShell 选择 文件 -> 属性
  2. 选择 终端 -> 键盘
  3. 选中 将 Alt 用作 Meta 键
  4. BACKSPACE 键序列 中选择 ASCII 127 确认

常见文件头特征(魔数)

jpgFF D8 FFpng89 50 4E 47
gif47 49 46 38bmp42 4D
pdf25 50 44 46xml3C 3F 78 6D 6C
docx/xlsx/pptx50 4B 03 04 14 00 06 00 08 00doc/xls/pptD0 CF 11 E0
zip50 4B 03 04rar52 61 72 21
avi41 56 49 20classca fe ba be
exe4D 5A 90 00 03psd38 42 50 53

更多

批处理脚本运行程序

ECHO "Start QQ..."
CALL ${qq path}
ECHO "Start Chrome..."
START ${chrome path} ${url path}
ECHO "Start Xshell..."
START ${xshell path} ${session path}
ECHO "..."

命令提示符(CMD)显示中文乱码

  1. Win + R 运行 regedit
  2. 找到 计算机 > HKEY_CURRENT_USER > Console > %SystemRoot%_system32_cmd.exe
  3. 将 CodePage 的数值数据修改为十六进制 3a8 或十进制 936 (简体中文 GBK) 或者十六进制 fde9 或十进制 65001 (UTF-8)

BAT批处理文件判断程序是否运行

REM 开启指定可执行程序函数,若已运行则提示,反之则运行该程序
REM %1 程序名 %2 可执行文件路径 %3、%4 程序参数
:openProgram
ECHO Open %1...
REM 判断指定程序名是否运行,名称忽略大小写
tasklist | find /i "%1.exe"
if %errorlevel%==0 (ECHO %1 already running...) else (START %2 %3 %4)
goto:eof

REM 调用函数,启动 Eclipse
call:openProgram Eclipse D:\eclipse\eclipse.exe

Database

Mysql

查询 Mysql 数据库大小

  • 选择指定 information_schema 数据库
    use information_schema;
        
  • 查询整个数据库大小
    select concat(round(sum(DATA_LENGTH/1024/1024),2),'MB') as data from TABLES;
        
  • 查询指定数据库大小
    select concat(round(sum(DATA_LENGTH/1024/1024),2),'MB') as data from TABLES where table_schema='your_database_name';
        
  • 查询指定数据库下某个表的大小
    select concat(round(sum(DATA_LENGTH/1024/1024),2),'MB') as data from TABLES where table_schema='your_database_name' and table_name='your_table_name';
        

Mysql 数据库存中文字符乱码解决方法

  • 修改 jdbc.url 配置
    jdbc.url=jdbc:mysql://ip-address:port/your_database_name?useUnicode=true&characterEncoding=utf8
        

Mysql 新增用户, 并附指定权限

  • 新增用户
    create user ['username']@['localhost'] identified by ['password'];
        
  • 附指定权限
    grant all privileges on [database].[table] to ['username']@['localhost'];
        
  • 删除用户
    drop user ['username']@['localhost'];
        

Mysql 导入本地指定文件数据

-- 登录时开启导入本地文件功能。不开启这个功能,导入文件时会报“当前 mysql 版本不支持导入文件功能”的错误
mysql -u ${username} --local-infile=1 -p

-- 建表
create table ${tablename}(id int auto_increment not null, username varchar(20) not null, age int not null, primary key(id));

-- 导入 cvs 文件数据。指定编码为 utf-8 ,按照','隔开字段,'\n'换行符隔开一行,指定对应导入的字段,id 字段自增长
load data local infile '${filepath}' into table ${tablename} character set utf8 fields terminated by ',' lines terminated by '\n' (username, age) set id = NULL;

LOAD DATA INFILE Syntax

Mysql 导出表数据到指定文件

-- 导出表数据到指定文件
select * info outfile '${filepath}' fields terminated by ',' optionally enclosed by '"' lines terminated by '\n' from ${tablename};

SELECT … INTO Syntax

重置 Mysql root 密码

# 停止 Mysql 服务
sudo service mysql stop
# 以 mysql 的安全模式启动服务
sudo mysqld_safe --skip-grant-tables&
# 直接登录 mysql 
mysql -uroot mysql
# 修改 root 密码
mysql > UPDATE user SET password=PASSWORD("password") WHERE user="root";
mysql > FLUSH PRIVILIGES;
# 重启 mysql 服务
sudo service mysql restart

Mysql 对于 zero date time 的处理

Java 连接 Mysql 数据库,字段日期为 0 时,会抛出异常 java.sql.SQLException: Cannot convert value ‘0000-00-00 00:00:00’ from column n to TIMESTAMP

# 解决方法为在配置 JDBC 链接时,添加 zeroDateTimeBehavior 属性来处理
# 1.zeroDateTimeBehavior=exception 默认值,抛出异常
# 2.zeroDateTimeBehavior=convertToNull 将值转为 NULL
# 3.zeroDateTimeBehavior=round 将值转为最近的正确值,即'0001-01-01'
jdbc:mysql://${mysql.serverUrl}?useUnicode=true&amp;characterEncoding=utf-8&amp;zeroDateTimeBehavior=convertToNull

Mysql 设置 max_allowed_packet

  • 查看 max_allowed_packet
    show VARIABLES like '%max_allowed_packet%';
        
  • 修改 max_allowed_packet 值
    SET GLOBAL max_allowed_packet=20 * 1024 * 1024
        
  • 修改 /etc/mysql/my.cnf 文件中 [mysqld] 下的配置
    max_allowed_packet=20M
        

C

C

位操作

int value;
// 将指定位设置为1
value = value | (1 << bit_number);
// 将指定位设置为0
value = value & ~ (1 << bit_number);
// 判断指定位是否为1,为1时表达式结果为非零,0时表达式结果为0
int flag = value & (1 << bit_number);
if (flag) {
        printf("第%d位值为1", bit_number);
} else {
        printf("第%d位值为0", bit_number);
}

Java

Java

操作 jar 包

- 查看 jar 包中的内容
  $ jar -tf *.jar
- 解压出 jar 包中的内容
  $ jar -xf *.jar

byte 数组与 int 互转

// byte 数组转 int
public static int bytes2Int(byte[] bytes) {
    if (null == bytes) {
        return 0;
    }
    if (bytes.length > 4) {
        throw new IllegalArgumentException("byte array length must be less than 4");
    }
    int value = 0;
    for (int i = 0; i < bytes.length; i++) {
        int shift = (bytes.length - 1 - i) * 8;
        value += (bytes[i] & 0xFF) << shift;
    }
    return value;
}

// int 转 byte 数组
public static byte[] int2Bytes(int i) {
    byte[] result = new byte[4];
    result[0] = (byte) ((i >> 24) & 0xFF);
    result[1] = (byte) ((i >> 16) & 0xFF);
    result[2] = (byte) ((i >> 8) & 0xFF);
    result[3] = (byte) (i & 0xFF);
    return result;
}

byte 数组与 char 数组互转

// byte 数组转 char 数组
public static char[] bytes2Chars(byte[] bytes) {
    if (null == bytes) {
        return null;
    }
    Charset cs = Charset.forName("UTF-8");
    ByteBuffer bb = ByteBuffer.allocate(bytes.length);
    bb.put(bytes);
    bb.flip();
    CharBuffer cb = cs.decode(bb);
    return cb.array();
}

// char 数组转 byte 数组
public static byte[] chars2Bytes(char[] chars) {
    if (null == chars) {
        return null;
    }
    Charset cs = Charset.forName("UTF-8");
    CharBuffer cb = CharBuffer.allocate(chars.length);
    cb.put(chars);
    cb.flip();
    ByteBuffer bb = cs.encode(cb);
    return bb.array();
}

倒序遍历 LinkedHashMap 集合

Map<Integer, String> data = new LinkedHashMap<>();
for (int i = 0; i < 5; i++) {
    data.put(i, "A" + i);
}
ListIterator<Map.Entry<Integer, String>> it = new ArrayList<>(data.entrySet()).listIterator(data.size());
while (it.hasPrevious()) {
    Map.Entry<Integer, String> entry = it.previous();
    System.out.println("key : " + entry.getKey() + " value : " + entry.getValue());
}

使用异或操作交换两个数字

/**
  a = a ^ b;
  b = b ^ a;
  a = a ^ b;
*/
private void swap(int[] nums, int a, int b) {
        nums[a] = nums[a] ^ nums[b];
        nums[b] = nums[b] ^ nums[a];
        nums[a] = nums[a] ^ nums[b];
}

分页计算页数

// 每页记录数
int pageSize;
// 总记录数
int rowCount;
// 页数
int pageCount = (rowCount - 1) / pageSize + 1;

分割 Map

/**
* 当数据量较少时,该方法较全部遍历的效率要低
* 当数据量较大且分割大小远小于总数据量时,该方法效率较高
*/
import com.google.common.base.Predicates;
import com.google.common.collect.Maps;

// map 为待分割 map 集合
// 分割大小
int size = 10000;
// map 总大小
int all = map.size();
// 分割结果
List<Map<String, Integer>> res = new ArrayList<>();
// key 的 list 集合
List<String> list = new ArrayList<>(map.keySet());
// 遍历次数
for (int j = 0; j < all / size; j++) {
        // Maps,Predicates 为 Google 的 guava 库中的类
        Map<String, Integer> subMap = Maps.filterKeys(map, Predicates.in(list.subList(j * size, (j + 1) * size)));
        res.add(subMap);
}

Java 获取系统的临时文件夹路径

// 获取系统的临时文件夹路径
String tmp = System.getProperty("java.io.tmpdir");

Java 通过反射获取泛型的类型

  • 由于 Java 的泛型在运行时会被擦除,不能够直接获取泛型的类型,但是其实在 class 字节码中还是保存着泛型的信息,可以通过特殊的方式获取到泛型的类型
  • 获取父类中的泛型类型
/**
 * 定义一个抽象的父类
 * 获取父类中的泛型类型 T
 */
public abstract class SuperClass<T> {

    // 泛型类型
    private Class<T> clazz;

    public SuperClass() {
        super();
        // 根据实现类反射获取包含泛型的父类,然后获取泛型的类型
        this.clazz = (Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }

    public Class<T> getClazz() {
        return this.clazz;
    }

    public static void main(String[] args) {
        // 构造匿名子类
        SuperClass<String> superClassString = new SuperClass<String>(){};
        System.out.println(superClassString.getClazz()); // class java.lang.String

        // 构造匿名子类
        SuperClass<Entity> superClassEntity = new SuperClass<Entity>(){};
        System.out.println(superClassEntity.getClazz()); // class Entity
    }
    
}
  • 获取父接口中的泛型类型
public interface SuperInterface<T> {
    
    @SuppressWarnings("unchecked")
    default Class<T> getEntityClass() {
        Type[] types = getClass().getGenericInterfaces();
        for (Type type : types) {
            if (type.getTypeName().startsWith(SuperInterface.class.getName())) {
                ParameterizedType pt = (ParameterizedType) type;
                return (Class<T>)pt.getActualTypeArguments()[0];
            }
        }
        return null;
    }
    
}

public class Children implements SuperInterface<Entity> {

    public static void main() {
        Children children = new Children();
        System.out.println(children.getClass()); // class Entity
    }
     
}
  • Entity 对象
/**
 * 简单 Java 对象 POJO
 */
class Entity {
    
    private int id;
    private String name;
    
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    
}

Java 8 接口中的默认方法的“劫持”问题

  • IFoo 接口中有一默认方法 bar
// Interface IFoo
public interface IFoo {
       default void bar(int i) {
               System.out.println("IFoo.bar(int)");
       }
}
  • Foo 实现 IFoo 接口,并也有一个公共方法 bar
// Class Foo
public class Foo implements IFoo {
        public void bar(long l) {
                System.out.println("Foo.bar(long)");
        }

        public static void main(String[] args) {
                Foo foo = new Foo();
                foo.bar(42); // 1 IFoo.bar(int)
                IFoo ifoo = foo;
                ifoo.bar(42); // 2 IFoo.bar(int)
        }
}
  • main 方法中 1,2 位置调用的方法为都是接口中默认的方法,因为接口中的默认方法提供了更加准确的匹配

来源

使用 commons-beanutils 复制属性,当对象中的数值类型属性为 null 时复制成 0 的问题

// 当对象的属性对象为 null 时,复制对象中的数值类型属性被初始化为 0
BeanUtils.copyProperties(entity, copyEntity);

/* 方式1 */
// 设置所有类型的默认值为 null
BeanUtilsBean.getInstance().getConvertUtils().register(false, true, 0);

/* 方式2 */
// 根据需要,注册对应的 Converter 对象, 并设置对应的默认值
ConvertUtils.register(new LongConverter(null), Long.class); // Long
ConvertUtils.register(new DoubleConverter(null), Double.class); // Double
ConvertUtils.register(new IntegerConverter(null), Integer.class); // Integer
ConvertUtils.register(new FloatConverter(null), Float.class); // Float
...

// 再复制属性
BeanUtils.copyProperties(entity, copyEntity);

Java AES 加密解密

  • 加密
/**
 * @param content 待加密内容
 * @param password 密钥
 * @return 密文,加密异常时返回 null 
 */
public static byte[] encrypt(String content, String password) {
    try {
        KeyGenerator kgen = KeyGenerator.getInstance("AES");
        SecureRandom random=SecureRandom.getInstance("SHA1PRNG");
        random.setSeed(password.getBytes());
        kgen.init(128, random);
        SecretKey secretKey = kgen.generateKey();
        byte[] enCodeFormat = secretKey.getEncoded();
        SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        byte[] byteContent = content.getBytes("utf-8");
        cipher.init(Cipher.ENCRYPT_MODE, key);
        return cipher.doFinal(byteContent);
    } catch (NoSuchAlgorithmException | NoSuchPaddingException | 
            InvalidKeyException | UnsupportedEncodingException |
            IllegalBlockSizeException | BadPaddingException e) {
        e.printStackTrace();
    }
    return null;
}
  • 解密
/**
 * @param content 待解密内容
 * @param password 密钥
 * @return 原内容,解密异常时返回 null
 */
public static byte[] decrypt(byte[] content, String password) {
    try {
        KeyGenerator kgen = KeyGenerator.getInstance("AES");
        SecureRandom random=SecureRandom.getInstance("SHA1PRNG");
        random.setSeed(password.getBytes());
        kgen.init(128, random);
        SecretKey secretKey = kgen.generateKey();
        byte[] enCodeFormat = secretKey.getEncoded();
        SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, key);
        return cipher.doFinal(content);
    } catch (NoSuchAlgorithmException | NoSuchPaddingException | 
            InvalidKeyException | IllegalBlockSizeException | 
            BadPaddingException e) {
        e.printStackTrace();
    }
    return null;
}
  • 使用
public static void main(String[] args) {
    String content = "test"; // 待加密内容
    String pwd = "AES"; // 密钥
    String encryptContent = Base64.getEncoder().encodeToString(encrypt(content, pwd)); // 密文
    String decryptContent = new String(decrypt(Base64.getDecoder().decode(encryptContent), pwd)); // 解密密文
    System.out.println(content.equals(decryptContent));
}

Java 常用正则表达式

/**
 * 匹配国际电话号码
 * 13987654321
 * +8613987654321
 * +86139-876-54321
 */
public static final Pattern PHONE_PATTERN = Pattern.compile("([+]?\\d{1,2}[.\\-\\s]?)?(\\d{3}[.-]?){2}\\d{2,5}");

/**
 * 匹配电子邮箱
 * saisimon@gmail.com
 */
public static final Pattern EMAIL_PATTERN = Pattern.compile("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[\\w](?:[\\w-]*[\\w])?");

/**
 * 匹配 18 位身份证号
 * 43011319990216213X
 */
public static final Pattern ID_CARD_PATTERN = Pattern.compile("^(\\d{6})(\\d{4})(\\d{2})(\\d{2})(\\d{3})([0-9]|X)$");

正则表达式中需要转义的特殊字符

* . ? + $ ^ [ ] ( ) { } | \ /

Java 根据文件名获取其 Content-Type

  • 使用 Java 1.7 中 Files 提供的方法
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

private static final String defaultType = "application/octet-stream";

public static String parseContentType(File file) {
    String contentType = defaultType;
    if (file != null) {
        Path path = Paths.get(file.getAbsolutePath());
        try {
            contentType = Files.probeContentType(path);
        } catch (IOException e) {
            LOG.error("Unknown Content-Type", e);
        }
    }
    return contentType;
}

使用 JSch 库通过 SSH 连接受限 MySQL 数据库(基于 Spring Boot)

  • 添加 JSch 依赖
<dependency>
    <groupId>com.jcraft</groupId>
    <artifactId>jsch</artifactId>
    <version>${jsch.version}</version>
</dependency>
  • SSH 属性类
import org.springframework.boot.context.properties.ConfigurationProperties;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString(exclude="password")
@ConfigurationProperties(prefix="ssh")
public class SshProperties {
    
    private String host;
    private Integer port;
    private String username;
    private String password;
    private Forward forward;
    
    @Getter
    @Setter
    @ToString
    public static class Forward {
        
        private String fromHost;
        private Integer fromPort;
        private String toHost;
        private Integer toPort;
        
    }
    
}
  • SSH 配置类
import javax.annotation.PreDestroy;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.weaving.LoadTimeWeaverAware;
import org.springframework.instrument.classloading.LoadTimeWeaver;

import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;

import cn.comein.ssh.config.SshProperties.Forward;
import lombok.extern.slf4j.Slf4j;

@Configuration
@EnableConfigurationProperties(SshProperties.class)
@ConditionalOnProperty(prefix = "ssh", value = "enabled", havingValue = "true", matchIfMissing = false)
@Slf4j
// 实现 LoadTimeWeaverAware 接口是因为需要 SSH 正向代理需要在EntityManagerFactory加载前运行
public class SshConfiguration implements LoadTimeWeaverAware {
    
    private final Session session;
    
    public SshConfiguration(SshProperties sshProperties) {
        Session session = null;
        try {
            // 可以自行为 JSch 添加日志,需要实现 com.jcraft.jsch.Logger 接口
            // JSch.setLogger(new JSchLogger())
            session = new JSch().getSession(sshProperties.getUsername(), sshProperties.getHost(), sshProperties.getPort());
            session.setConfig("StrictHostKeyChecking", "no");
            session.setPassword(sshProperties.getPassword());
            session.connect();
            Forward forward = sshProperties.getForward();
            if (forward != null) {
                session.setPortForwardingL(forward.getFromHost(), forward.getFromPort(), forward.getToHost(), forward.getToPort());
                log.info("{}:{} -> {}:{}", forward.getFromHost(), forward.getFromPort(), forward.getToHost(), forward.getToPort());
            }
        } catch (JSchException e) {
            log.error("ssh " + sshProperties.getHost() + " failed.", e);
        }
        this.session = session;
    }
    
    @PreDestroy
    // 配置销毁时,断开 SSH 链接
    public void disconnect() {
        if (session != null) {
            session.disconnect();
        }
    }

    @Override
    public void setLoadTimeWeaver(LoadTimeWeaver loadTimeWeaver) {
        
    }

}
  • application.properties 文件中添加 SSH 相关属性值
ssh.enabled=false # 是否启用 SSH 配置
ssh.host=127.0.0.1 # SSH 地址
ssh.port=22 # SSH 端口
ssh.username= # SSH 用户名
ssh.password= # SSH 密码
ssh.forward.from_host= # 绑定的本地地址
ssh.forward.from_port= # 绑定的本地端口
ssh.forward.to_host= # 正向代理的远程地址
ssh.forward.to_port= # 正向代理的远程端口
  • 修改数据源为绑定的本地地址与端口
  • SSH 连接失败可能的原因
    1. 用户名或密码错误
    2. sshd_config 中需要配置 PasswordAuthentication yes,允许使用密码登陆
    3. 使用 root 用户登陆,需要配置 PermitRootLogin yes,才能登陆

Jackson JSON 工具类,支持泛型解析

package net.saisimon.util;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;

import lombok.extern.slf4j.Slf4j;

/**
 * <p>Jackson JSON 工具类</p>
 * 
 * @author Saisimon
 */
@Slf4j
public class JsonUtils {
    
    /**
     * <p>对象转 JSON</p>
     * 
     * @param obj 目标对象
     * @return JSON 串
     */
    public static String toJson(Object obj) {
        try {
            ObjectMapper objectMapper = new ObjectMapper();
            return objectMapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            log.error("生成 json 失败", e);
            return null;
        }
    }
    
    /**
     * <p>解析 JSON 串,支持泛型</p>
     * <p>目标类为  Map<String, List<Long>></p>
     * <p>targetClass => Map.class</p>
     * <p>genericClasses => String.class, List.class, Long.class</p>
     * 
     * @param json JSON 串
     * @param targetClass 目标类
     * @param genericClasses 目标类的泛型数组
     * @return 目标对象
     */
    public static <T> T fromJson(String json, Class<T> targetClass, Class<?>... genericClasses) {
        try {
            ObjectMapper objectMapper = new ObjectMapper();
            JavaType javaType = parse(targetClass, genericClasses);
            return objectMapper.readValue(json, javaType);
        } catch (IOException e) {
            log.error("解析 json 失败", e);
            return null;
        }
    }
    
    private static JavaType parse(Class<?> targetClass, Class<?>... genericClasses) {
        if (genericClasses == null) {
            return TypeFactory.defaultInstance().constructType(targetClass);
        } else {
            List<Class<?>> genericClassList = new ArrayList<>(Arrays.asList(genericClasses));
            return construct(targetClass, parse(genericClassList, genericClassList.iterator()));
        }
    }
    
    private static List<JavaType> parse(List<Class<?>> genericClassList, Iterator<Class<?>> it) {
        List<JavaType> javaTypes = new ArrayList<>();
        while (it.hasNext()) {
            Class<?> cls = it.next();
            int genericLenght = cls.getTypeParameters().length;
            if (genericLenght > 0) {
                List<JavaType> subJavaTypes = parse(genericClassList.subList(1, genericClassList.size()), it);
                JavaType javaType = construct(cls, subJavaTypes.subList(0, genericLenght));
                javaTypes.add(javaType);
                javaTypes.addAll(subJavaTypes.subList(genericLenght, subJavaTypes.size()));
            } else {
                JavaType javaType = TypeFactory.defaultInstance().constructType(cls);
                javaTypes.add(javaType);
            }
        }
        return javaTypes;
    }
    
    private static JavaType construct(Class<?> cls, List<JavaType> javaTypes) {
        return TypeFactory.defaultInstance().constructParametricType(cls, javaTypes.toArray(new JavaType[javaTypes.size()]));
    }
    
}

JUnit

JUnit 4 中实现测试用例按指定顺序执行

JUnit 中提供了三种方式来决定执行顺序

  1. MethodSorters.NAME_ASCENDING 按照测试方法的方法名的字母表顺序进行排序
  2. MethodSorters.JVM 交由 JVM 决定执行顺序
  3. MethodSorters.DEFAULT 按照测试方法的方法名的 hashcode 进行排序,这个为默认值

具体实现可在 org.junit.internal.MethodSorter 中找到,可以通过在测试用例类上添加 FixMethodOrder 注解来改变默认值。但这种方式不能按照指定的顺序执行测试用例,下面提供一个方法来实现这个功能。

  • 定义 Order 注解,来指定测试用例的执行顺序
package net.saisimon.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Order {
    
    int value() default 0;
    
}
  • 继承 JUnit 的 org.junit.runners.BlockJUnit4ClassRunner 运行器,来重新实现获取测试用例顺序的方法,如果是测试 Spring 应用,可继承 org.springframework.test.context.junit4.SpringJUnit4ClassRunner 运行器
package net.saisimon.test;

import java.util.List;
import java.util.stream.Collectors;

import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
// Spring 
// import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
// JUnit 
import org.junit.runners.BlockJUnit4ClassRunner;

// Spring 继承
// public class OrderedRunner extends SpringJUnit4ClassRunner {
// JUnit 继承
public class OrderedRunner extends BlockJUnit4ClassRunner {

    // 测试用例的方法集合
    private static List<FrameworkMethod> testMethodList;
    
    public OrderedRunner(Class<?> clazz) throws InitializationError {
        super(clazz);
    }

    // 重写 computeTestMethods 方法,按指定顺序排序
    @Override
    protected List<FrameworkMethod> computeTestMethods() {
        if (testMethodList == null) {
            testMethodList = super.computeTestMethods().stream()
                .sorted((m1, m2) -> {
                    // 根据测试用例上的 Order 注解来决定执行顺序
                    Order o1 = m1.getAnnotation(Order.class);
                    Order o2 = m2.getAnnotation(Order.class);
                    if (o1 == null || o2 == null) {
                        return 0;
                    }
                    return o1.value() - o2.value();
                }).collect(Collectors.toList());
        }
        return testMethodList;
    }
}
  • 测试用例,使用 Order 注解来决定执行顺序
package net.saisimon.test;

import org.junit.Test;
import org.junit.runner.RunWith;

import net.saisimon.annotation.Order;

@RunWith(OrderedRunner.class)
public class OrderedRunnerTest {
    
    @Test
    @Order(1)
    public void test2() {
        System.out.println(2);
    }
    
    @Test
    @Order(2)
    public void test1() {
        System.out.println(1);
    }
    
    @Test
    @Order(3)
    public void test3() {
        System.out.println(3);
    }

    // 输出
    // 2
    // 1
    // 3
}

Web

前端页面传中文字符乱码解决方法

  • 修改 web.xml 文件, 添加 CharacterEncodingFilter
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    
    <filter-mapping>
          <filter-name>CharacterEncodingFilter</filter-name>
          <url-pattern>/*</url-pattern>
    </filter-mapping>
        
  • filter 需要放在所有 filter 的前面才会生效

常见 ContentType 与文件后缀名对应关系

文件扩展名ContentType
.htmltext/html
.docapplication/msword
.pptapplication/vnd.ms-powerpoint
.xlsapplication/vnd.ms-excel
.xlsxapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet
.xmltext/xml
.txttext/plain
.pdfapplication/pdf
.jpegimage/jpeg
.jsapplication/x-javascript
.csstext/css
.*(未知二进制流)application/octet-stream

更多

xpath 基础

/*
    <body>
      <div>
        <ul id="meun">
          <li class="sub_meun" name="food"></li>
          <li class="sub_meun" name="phone">
            <p>// Phone</p>
             <span>
               <a>    go   </a>
             <span>
           </li>
           <li class="sub_meun" name="ring"></li>
         </ul>
       </div>
     </body>
     选取内容为 go 的 a 标签
 */
String xpath = "//ul[@id='meun']/li[@class='sub_meun' and @name='phone']/p/parent::li/span/a[normalize-space(text())='go']";

XPath 教程

生成自签名 HTTPS 证书

# 生成 CA 私钥
$ openssl genrsa -out ca.key 2048

# 生成 CA 证书
$ openssl req -x509 -new -key ca.key -out ca.crt

# 生成服务端私钥
$ openssl genrsa -out server.key 2048

# 生成服务端证书请求文件
$ openssl req -new -key server.key -out server.csr

# 使用CA证书生成服务端证书
$ openssl x509 -req -sha256 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 365 -out server.crt

# 服务端证书转为 pkcs12 格式
$ openssl pkcs12 -export -in server.crt -inkey server.key -out server.pkcs12

# 生成服务端的keystore
$ keytool -importkeystore -srckeystore server.pkcs12 -destkeystore server.jks -srcstoretype pkcs12

不同浏览器下载文件时文件名乱码问题

  • 根据 User-Agent 获取编码后的 Content-Disposition 值
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

import javax.mail.internet.MimeUtility;

public static String encodeDownloadContentDisposition(String userAgent, String filename) {
    try {
        String newFilename = URLEncoder.encode(filename, "UTF8");
        // 如果没有UA,则默认使用IE的方式进行编码
        String cd = "attachment; filename=\"" + newFilename + "\"";
        if (userAgent != null) {
            userAgent = userAgent.toLowerCase();
            if (userAgent.indexOf("msie") != -1) { // IE浏览器,只能采用URLEncoder编码
                cd = "attachment; filename=\"" + newFilename + "\"";
            } else if (userAgent.indexOf("opera") != -1) { // Opera浏览器只能采用filename*
                cd = "attachment; filename*=UTF-8''" + newFilename;
            } else if (userAgent.indexOf("safari") != -1) { // Safari浏览器,只能采用ISO编码的中文输出
                cd = "attachment; filename=\"" + new String(filename.getBytes("UTF-8"), "ISO8859-1") + "\"";
            } else if (userAgent.indexOf("applewebkit") != -1) { // Chrome浏览器,只能采用MimeUtility编码或ISO编码的中文输出
                newFilename = MimeUtility.encodeText(filename, "UTF8", "B");
                cd = "attachment; filename=\"" + newFilename + "\"";
            } else if (userAgent.indexOf("mozilla") != -1) { // FireFox浏览器,可以使用MimeUtility或filename*或ISO编码的中文输出
                cd = "attachment; filename*=UTF-8''" + newFilename;
            }
        }
        return cd;
    } catch (UnsupportedEncodingException e) {
        return "attachment; filename=\"" + filename + "\"";
    }
}

参考

Maven

Maven 基本操作

- 创建Maven的普通java项目
  $ mvn archetype:create -DgroupId=[packageName] -DartifactId=[projectName]
- 创建Maven的Web项目
  $ mvn archetype:create -DgroupId=[packageName] -DartifactId=[webappName] -DarchetypeArtfactId=maven-archetype-webapp
- 编译源码
  $ mvn compile
- 打包
  $ mvn package
- 在本地Repository中安装jar
  $ mvn install
- 清理项目
  $ mvn clean
- 生成eclipse/idea项目
  $ mvn eclipse:eclipse
  $ mvn idea:idea
- 生成站点信息
  $ mvn site

Maven 跳单元测试

  • 跳过单元测试
    # 直接跳过测试,测试类不会被编译
    $ mvn install -Dmaven.test.skip=true
    # 跳过测试运行,但会编译测试类
    $ mvn install -DskipTests
        

    Skipping Tests

Maven 指定编译版本

  • 添加编译插件
    <build>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.5.1</version>
          <configuration>  
            <source>1.X</source>  
            <target>1.X</target>  
            <encoding>UTF-8</encoding>  
          </configuration>  
        </plugin>
      </plugins>
    </build> 
        

pom.xml 文件 - Missing artifact jdk.tools:jdk.tools:jar:1.x

  • pom 文件添加 tools 依赖
    <dependency>
			<groupId>jdk.tools</groupId>
			<artifactId>jdk.tools</artifactId>
			<version>1.x</version>
			<scope>system</scope>
 		<systemPath>${JAVA_HOME}/lib/tools.jar</systemPath>
	  </dependency>

Maven 添加本地 jar 包依赖

<dependency>
    <groupId>net.saisimon</groupId>
    <artifactId>local-jar</artifactId>
    <version>1.0</version>
    <scope>system</scope>
    <systemPath>/local/path/local-jar-1.0.jar</systemPath>
</dependency>

Maven 上传 jar 包到 Nexus 私服仓库

  • $MAVEN_HOME/conf/settings.xml
<settings>
  ...
  <servers>
    ...
    <server>
      <id>nexus-snapshots</id>
      <username>admin</username>
      <password>admin123</password>
    </server>
    ...
  </servers>
  ...
</settings>
$ mvn deploy:deploy-file 
   -DgroupId=net.saisimon                                  # groupId
   -DartifactId=test                                       # artifactId
   -Dversion=0.0.1-SNAPSHOT                                # version
   -Dpackaging=jar                                         # packaging
   -Dfile=/file/test/target/test-0.0.1-SNAPSHOT.jar        # file path
   -Durl=http://127.0.0.1:8081/repository/maven-3rd/       # deploy url
   -DrepositoryId=nexus-snapshots                          # settings.xml 中 Servers 配置的 ID 名称

deploy:deploy-file

Maven 编译构建模块化项目

#!/bin/bash
profileName=$1
if [ ! ${profileName} ]; then
    profileName='dev'
fi
# -DskipTests 跳过测试
# -P 激活指定的 profile
# -U 强制更新releases、snapshots类型的插件或依赖库
# -pl 选择需要构建的项目,选项后可跟随{groupId}:{artifactId}或者所选模块的相对路径
mvn clean install -DskipTests -P ${profileName} -U && mvn spring-boot:run -DskipTests -pl {groupId}:{artifactId}/{relativePath}

Tomcat

导入 Web 项目,Tomcat 无法添加部署问题 - Tomcat version X.0 only supports J2EE 1.2, 1.3, 1.4, and Java EE X…

  • 其主要原因为当前 Tomcat 版本与该 Web 项目的Web版本不兼容,Tomcat 6支持 Web 2.5及以下版本,tomcat 7支持 Web 3.0及以下版本
  • 在 Eclipse 中:Project -> Properties -> Project Facets -> Dynamic Web Module,检查 Web 项目的Web版本
  1. Eclipse 环境下的修改方法为:项目根目录找到 .setting 文件夹中的 org.eclipse.wst.common.project.facet.core.xml 文件,修改其中 jst.web 的 version 的值至当前 Tomcat 支持的版本
  2. 更新 Tomcat 版本,使其与 Web 版本兼容

配置从根目录访问 Tomcat 下的 Web 项目

<!-- docBase为webapp的路径 path为发布的路径,根目录访问这里留空  -->
<!-- Context 标签配置在 Tomcat 目录下 conf 文件里的 Server.xml 配置文件中  -->
<Server>
  <Service>
    <Engine>
      <Host>
        <Context docBase="[webapp_path]" path="" reloadable="true"/>
      </Host>
    </Engine>
  </Service>
</Server>

eclipse 中 Web 项目配置根目录访问

  • 修改 Web 项目的 Context Path
  1. 打开 web project folder >> .setting >> org.eclipse.wst.common.component 文件
  2. 编辑该文件,修改其中 content-root 属性为空值
<project-modules id="moduleCoreId" project-version="1.5.0">
    <wb-module deploy-name="webapp">
        ...
        <property name="context-root" value=""/>
    </wb-module>
</project-modules>

JNI

使用 javah 生成头文件问题 - Error: Could not find class file for “X”

- HelloWorld.class 在 net.saisimon.jni 包中
  $ javah HelloWorld
  Error: Could not find class file for 'HelloWorld'.
- HelloWorld 在 Java 包中,需要到包的根目录执行 javah 命令
  $ cd ../../../
  $ javah net.saisimon.jni.HelloWorld
- 即可生成头文件 net_saisimon_jni_HelloWorld.h

Thread

统计所有线程消耗的总时间

package net.saisimon.test

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.commons.codec.digest.DigestUtils;

public class Test implements Runnable {
    
    volatile int vote = 0;
    CountDownLatch cdl = new CountDownLatch(5);
        
    @Override
    public void run() {
        parse();
        // 递减计数器
        cdl.countDown();
    }

    public void parse() {
        while (vote < 10) {
            int x = 0;
            int v = vote;
            vote++;
            String tmp = "Saisimon" + v + x;
            String md5 = DigestUtils.md5Hex(tmp);
            while (!md5.startsWith("000000")) {
                x++;
                tmp = "Saisimon" + v + x;
                md5 = DigestUtils.md5Hex(tmp);
            }
            System.out.println("thread : " + Thread.currentThread().getName() + " , vote : " + v + " , x : " + x);
        }
    }

    public static void main(String[] args) {
        Test t = new Test();
        long start = System.currentTimeMillis();
        ExecutorService es = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            es.execute(t);
        }
        es.shutdown();
        try {
            // 计数器减至零时,await 会被执行
            p.cdl.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("多线程耗时:" + (System.currentTimeMillis() - start));
    }
}

Solr

Solr 导入 csv 文件数据

http://localhost:8983/solr/item/update?commit=true&stream.file=d:/tmp/solr_data.csv&stream.contentType=application/csv

Dubbo

Fail to decode request due to: RpcInvocation 问题

  1. 参数中有没有序列化的对象。所有参数必须继承 Serializable 接口实现序列化
  2. 参数对象中有不能序列化的属性。改变属性,使所有属性可以序列化
  3. 服务提供者与服务消费者依赖版本不一致,导致序列化异常。保证提供者和消费者依赖版本一致

Hadoop

Hadoop 版本依赖关系

<!-- Java 要求 -->
<!-- Hadoop 2.7 以及之后的版本需要 JDK 7 -->
<!-- Hadoop 2.6 以及之前的版本支持 JDK 6 -->

<!-- Hadoop 1.x.y 依赖 hadoop-core 包 -->
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-core</artifactId>
    <version>1.x.y</version>
</dependency>

<!-- Hadoop 2.x.y 依赖 hadoop-common、hadoop-hdfs、hadoop-mapreduce-client-core、hadoop-client -->
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-common</artifactId>
    <version>2.x.y</version>
</dependency>
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-hdfs</artifactId>
    <version>2.x.y</version>
</dependency>
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-mapreduce-client-core</artifactId>
    <version>2.x.y</version>
</dependency>
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-client</artifactId>
    <version>2.x.y</version>
</dependency>

Eclipse

显示 Eclipse 内存堆占用条,手动 GC

  • 修改配置文件 {workspaceHome}/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.prefs
SHOW_MEMORY_MONITOR=true

修改 Eclipse 格式化代码自动换行

Project - Properties - Java Code Style - Formatter - Enable project specific settings - 新建一个配置 - Line Wrapping - 设置 Maximum line width

Eclipse console 控制台 log4j 日志支持多种颜色

  • 在 Eclipse Marketplace 安装 ANSI Escape in Console 插件
  • 下载 java-color-loggers.jar 包,并加入到 build path 中
  • 在 log4j.properties 文件中添加如下代码
log4j.appender.CONSOLE=com.colorlog.log4j.AnsiColorConsoleAppender

# You can change the default colors  
# log4j.appender.CONSOLE.FatalColour={esc}[1;35m  
# log4j.appender.CONSOLE.ErrorColour={esc}[0;31m  
# log4j.appender.CONSOLE.WarnColour ={esc}[0;33m  
# log4j.appender.CONSOLE.InfoColour ={esc}[1;32m  
# log4j.appender.CONSOLE.DebugColour={esc}[1;36m  
# log4j.appender.CONSOLE.TraceColour={esc}[1;30m

Eclipse 导入项目未自动识别为 web 项目

  • Project - Predicates - Project Facets - 选择 Java 和 Dynamic Web Module (选择对应版本)
  • 当选择了 Dynamic Web Module 后,下方选择 Further Configuration availabe (没有这个选项的话,删除项目中 eclipse 生成的 .settings 文件夹,在 eclipse 中 refresh 项目,然后重复上一步)
  • 设置 classes 路径 和 webroot 的路径 - 保存

Eclipse 部署 web 项目时 lib 为空

  • Project - Predicates - Deployment Assembly - Add 添加 Libraries

Eclipse 隐藏/显示已关闭项目

Project Explorer 标签栏 -> v(View Menu) -> Customize View… -> Filters -> 勾选/取消勾选 Closed projects

Spring

Spring Boot 中添加自定义的监听器,拦截器,过滤器

/**
 * 添加自定义的监听器,拦截器,过滤器
 */
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
    
    private static final Logger LOG = LoggerFactory.getLogger(WebConfig.class);

    /**
     * 添加监听器
     */
    @Bean
    public ServletListenerRegistrationBean<EventListener> doMyListener() {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Do My Listener");
        }
        ServletListenerRegistrationBean<EventListener> registrationBean = new ServletListenerRegistrationBean<>();
        registrationBean.setListener(new MyListener());
        return registrationBean;
    }

    /**
     * 添加过滤器
     */
    @Bean
    public FilterRegistrationBean doMyFilter() {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Do My Filter");
        }
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new MyFilter());
        registration.addUrlPatterns("/*"); //拦截路径,可以添加多个
        registration.setName("myFilter");
        registration.setOrder(1);
        return registration;
    }

    /**
     * 添加拦截器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Do My Interceptors");
        }
        registry.addInterceptor(new MyInterceptor());
        super.addInterceptors(registry);
    }
}

Spring 获取项目 classpath 路径

  • 该方法在 Spring Boot 以 java -jar 启动时获取的路径不正确,改用文件流的形式
import org.springframework.util.ClassUtils;

// need handler URISyntaxException
String classpath = ClassUtils.getDefaultClassLoader().getResource("").toURI().getPath();
  • ClassUtils#getDefaultClassLoader()
public static ClassLoader getDefaultClassLoader() {
    ClassLoader cl = null;
    try {
        cl = Thread.currentThread().getContextClassLoader();
    } catch (Throwable ex) {
        // Cannot access thread context ClassLoader - falling back...
    }
    if (cl == null) {
        // No thread context class loader -> use class loader of this class.
        cl = ClassUtils.class.getClassLoader();
        if (cl == null) {
            // getClassLoader() returning null indicates the bootstrap ClassLoade
            try {
                cl = ClassLoader.getSystemClassLoader();
            } catch (Throwable ex) {
                // Cannot access system ClassLoader - oh well, maybe the caller can live with null...
            }
        }
    }
    return cl;
}

Spring Boot 国际化配置

  • 简单在 application.properties 中配置国际化信息
# INTERNATIONALIZATION (MessageSourceAutoConfiguration)
spring.messages.always-use-message-format=false # Set whether to always apply the MessageFormat rules, parsing even messages without arguments.
spring.messages.basename=package # 默认为 messages 
spring.messages.cache-seconds=60 # 默认为 -1
spring.messages.encoding=UTF-8
spring.messages.fallback-to-system-locale=true # Set whether to fall back to the system Locale if no files for a specific Locale have been found.
  • 自定义配置国际化信息
@Configuration
public class I18NConfig extends WebMvcConfigurerAdapter {

    @Bean
    public ReloadableResourceBundleMessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        // 设置 Spring 读取语言包的位置 src/main/resources/package_*.properties 文件
        messageSource.setBasename("classpath:package");
        // 设置默认编码为 UTF-8
        messageSource.setDefaultEncoding("UTF-8");
        // 设置当 code 没找到对应的文本时默认使用 code 作为其文本
        messageSource.setUseCodeAsDefaultMessage(true);
        // 设置缓存时长
        messageSource.setCacheSeconds(60);
        return messageSource;
    }

    @Bean
    public CookieLocaleResolver localeResolver() {
        CookieLocaleResolver localeResolver = new CookieLocaleResolver();
        // 设置默认地区,对应文件名为 package_cn.properties
        localeResolver.setDefaultLocale(new Locale("cn"));
        return localeResolver;
    }
    
    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
        // 设置改变地区的参数名
        // http://www.xxx.com/index?language=cn 语言为中文
        // http://www.xxx.com/index?language=en 语言为English
        lci.setParamName("language");
        return lci;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 添加拦截器
        registry.addInterceptor(localeChangeInterceptor());
    }
        
}

Spring Boot 配置首页跳转

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // 设置默认首页跳转至 /login
        registry.addViewController("/").setViewName("redirect:/login");
    }
        
}

Spring Boot 获取 ApplicationContext

  • 将 SpringContext 放在 Spring 的扫描路径内
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

@Component
@Lazy(false)
public class SpringContext implements ApplicationContextAware {
    
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (null == SpringContext.applicationContext) {
            SpringContext.applicationContext = applicationContext;
        }
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }
}
  • 如果 SpringContext 无法被 Spring 扫描到,则需要在启动类里直接引入,并且 SpringContext 中不需要添加@Component注解
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;

@SpringBootApplication
@Import(SpringContext.class)
public class Application {
    
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    
}

Spring Boot 读取 Properties 文件

  • config.properties 放在 classpath 路径下
# 超时时间
config.timeout=10000
  • Properties.java 使用 PropertySource 注解设置文件路径,使用 Value 注解注入属性值
package net.saisimon.utils

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

@Component
@PropertySource("classpath:email/config.properties") // 设置 Properties 文件路径
public class Properties {

    // 设置默认值为 10000
    @Value("${config.timeout:10000}")
    private Long timeout;

    public Long getTimeout() {
        return timeout;
    }
        
}

Spring Boot 配置 HTTPS ,HTTP 重定向到 HTTPS (Tomcat)

  • https.properties 放在 classpath 路径下
    https.port=443
    https.secure=true
    https.scheme=https
    https.ssl=true
    https.keystore=/keystore/dir/server.jks
    https.keystore-password=********
        
  • WebConfig.java Web 配置类中添加处理 HTTPS 请求的 Container
    package net.saisimon.config;
    
    import java.io.File;
    
    import org.apache.catalina.Context;
    import org.apache.catalina.connector.Connector;
    import org.apache.tomcat.util.descriptor.web.SecurityCollection;
    import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
    import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
    import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.PropertySource;
    
    import lombok.Data;
    
    @Configuration
    @PropertySource("classpath:https.properties")
    @EnableConfigurationProperties(WebConfig.SslProperties.class)
    public class WebConfig {
        
        @Bean
        public EmbeddedServletContainerFactory servletContainer(SslProperties properties) {
            TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory() {
                @Override
                protected void postProcessContext(Context context) {
                    SecurityConstraint securityConstraint = new SecurityConstraint();
                    securityConstraint.setUserConstraint("CONFIDENTIAL");
                    SecurityCollection collection = new SecurityCollection();
                    collection.addPattern("/*");
                    securityConstraint.addCollection(collection);
                    context.addConstraint(securityConstraint);
                }
            };
            tomcat.addAdditionalTomcatConnectors(createSslConnector(properties));
            return tomcat;
        }
    
        private Connector createSslConnector(SslProperties properties) {
            Connector connector = new Connector();
            properties.configureConnector(connector);
            return connector;
        }
        
        @ConfigurationProperties(prefix = "https")
        @Data
        public static class SslProperties {
            
            private Integer port;
            private Boolean ssl = true;
            private Boolean secure = true;
            private String scheme = "https";
            private File keystore;
            private String keystorePassword;
            
            public void configureConnector(Connector connector) {
                if (port != null) {
                    connector.setPort(port);
                }
                if (secure != null) {
                    connector.setSecure(secure);
                }
                if (scheme != null) {
                    connector.setScheme(scheme);
                }
                if (ssl != null) {
                    connector.setProperty("SSLEnabled", ssl.toString());
                }
                if (keystore != null && keystore.exists()) {
                    connector.setProperty("keystoreFile", keystore.getAbsolutePath());
                    connector.setProperty("keystorePass", keystorePassword);
                }
            }
        }
        
    }
        

Spring Boot 配置 HTTPS ,HTTP 重定向到 HTTPS (Jetty 9)

  • https.properties 放在 classpath 路径下,内容同上
  • WebConfig.java Web 配置类中添加处理 HTTPS 请求的 Connector
    package net.saisimon.config;
    
    import java.io.File;
    
    import org.eclipse.jetty.http.HttpVersion;
    import org.eclipse.jetty.security.ConstraintMapping;
    import org.eclipse.jetty.security.ConstraintSecurityHandler;
    import org.eclipse.jetty.server.Connector;
    import org.eclipse.jetty.server.HttpConfiguration;
    import org.eclipse.jetty.server.HttpConnectionFactory;
    import org.eclipse.jetty.server.SecureRequestCustomizer;
    import org.eclipse.jetty.server.Server;
    import org.eclipse.jetty.server.ServerConnector;
    import org.eclipse.jetty.server.SslConnectionFactory;
    import org.eclipse.jetty.util.security.Constraint;
    import org.eclipse.jetty.util.ssl.SslContextFactory;
    import org.eclipse.jetty.webapp.WebAppContext;
    import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
    import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
    import org.springframework.boot.context.embedded.jetty.JettyServerCustomizer;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.PropertySource;
    
    import lombok.Data;
    
    @Configuration
    @PropertySource("classpath:https.properties")
    @EnableConfigurationProperties(WebConfig.SslProperties.class)
    public class WebConfig {
    
        @Bean
        public EmbeddedServletContainerFactory servletContainer(SslProperties properties) {
            JettyEmbeddedServletContainerFactory jetty = new JettyEmbeddedServletContainerFactory() {
                @Override
                protected void postProcessWebAppContext(WebAppContext webAppContext) {
                    // 所有请求都必须为 Https 协议
                    ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
                    ConstraintMapping mapping = new ConstraintMapping();
                    Constraint constraint = new Constraint();
                    constraint.setDataConstraint(Constraint.DC_CONFIDENTIAL);
                    mapping.setConstraint(constraint);
                    mapping.setPathSpec("/*");
                    securityHandler.addConstraintMapping(mapping);
                    webAppContext.setSecurityHandler(securityHandler);
                }
            };
            jetty.addServerCustomizers(new JettyServerCustomizer() {
                @Override
                public void customize(Server server) {
                    // 移除Spring Boot 生成的 Connector
                    int httpPort = 80;
                    Connector[] connectors = server.getConnectors();
                    for (Connector connector : connectors) {
                        if (connector instanceof ServerConnector) {
                            httpPort = ((ServerConnector) connector).getPort();
                        }
                        server.removeConnector(connector);
                    }
                    
                    // 配置 Http 协议的 Connector
                    HttpConfiguration httpConfig = new HttpConfiguration();
                    // 重定向
                    httpConfig.setSecureScheme(properties.getScheme());
                    httpConfig.setSecurePort(properties.getPort());
                    httpConfig.addCustomizer(new SecureRequestCustomizer());
                    ServerConnector httpConnector = new ServerConnector(server, new HttpConnectionFactory(httpConfig));
                    httpConnector.setPort(httpPort);
                    server.addConnector(httpConnector);
    
                    // 配置 Https 协议的 Connector
                    HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
                    httpsConfig.addCustomizer(new SecureRequestCustomizer());
                    HttpConnectionFactory connectionFactory = new HttpConnectionFactory(httpsConfig);
                    SslContextFactory sslContextFactory = new SslContextFactory();
                    if (null != properties.getKeystore() && properties.getKeystore().exists()) {
                        sslContextFactory.setKeyStorePath(properties.getKeystore().getAbsolutePath());
                        sslContextFactory.setKeyStorePassword(properties.getKeystorePassword());
                    }
                    SslConnectionFactory sslConnectionFactory = new SslConnectionFactory(sslContextFactory,
                            HttpVersion.HTTP_1_1.asString());
                    ServerConnector serverConnector = new ServerConnector(server, sslConnectionFactory, connectionFactory);
                    serverConnector.setPort(properties.getPort());
                    server.addConnector(serverConnector);
                }
            });
            return jetty;
        }
    
        @ConfigurationProperties(prefix = "https")
        @Data
        public static class SslProperties {
    
            private Integer port;
            private Boolean ssl = true;
            private Boolean secure = true;
            private String scheme = "https";
            private File keystore;
            private String keystorePassword;
    
        }
    
    }
        

Spring Boot 中获取 classpath 下的文件资源

<<Spring Boot 中获取 classpath 下的文件资源>>

import org.springframework.core.io.ClassPathResource;

ClassPathResource classPathResource = new ClassPathResource("path/file.xml");
InputStream in = classPathResource.getInputStream();

Spring Boot 中将非 Spring 管理对象装配成 Spring 管理对象

/**
 * 装配非 Spring 管理对象
 *
 * 获取 ApplicationContext 请参考[Spring Boot 获取 ApplicationContext]
 * 
 * @param obj non-spring object
 * @return spring object
 * @see SpringContext#getApplicationContext
 */
public static Object autowire(Object obj) {
    if (obj == null) {
        return null;
    }
    getApplicationContext().getAutowireCapableBeanFactory().autowireBean(obj);
    return obj;
}

Spring Boot 中配置使用 WebSocket

  • POM 文件中添加 WebSocket 依赖
...
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
...
  • 配置注册 WebSocket 的处理器
package net.saisimon.config

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

import net.saisimon.handler.MyHandler;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        // 注册处理器,映射到 /websocket
        // 并添加 HttpSessionHandshakeInterceptor 拦截器,获取 HttpSession
        registry.addHandler(myHandler(), "/websocket")
                .addInterceptors(new HttpSessionHandshakeInterceptor());
    }

    @Bean
    public WebSocketHandler myHandler() {
        // 处理类
        return new MyHandler();
    }
        
}
  • 实现处理类
package net.saisimon.handler;

import org.springframework.web.socket.BinaryMessage;
import org.springframework.web.socket.PongMessage;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;

// 继承抽象处理类,实现对应的处理方法
public class MyHandler extends AbstractWebSocketHandler {

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // 处理文本文件
        String msg = new String(message.asBytes(), "UTF-8");
        System.out.println(msg);
        // 发送消息
        session.sendMessage(new TextMessage(msg));
    }

    @Override
    protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
        // 处理二进制文件
    }

    @Override
    protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
        // 处理 Pone 消息
    }
      
}

更多

Spring Boot 通过 Hibernate 生成 Mysql 表时指定引擎与编码

  1. 直接在 Mysql 指定默认引擎和编码
  2. 代码指定生成的引擎和编码
  • 继承 MySQL5Dialect,重写 getTableTypeString 方法,返回引擎和编码
package net.saisimon.config;

import org.hibernate.dialect.MySQL5Dialect;

public class MySQL5UTF8Dialect extends MySQL5Dialect {

    @Override
    public String getTableTypeString() {
        // 指定引擎和编码
        return " ENGINE=InnoDB DEFAULT CHARSET=utf8";
    }
    
}
  • 在 application.properties 中配置 Hibernate 方言
# JPA
spring.jpa.properties.hibernate.dialect=net.saisimon.config.MySQL5UTF8Dialect

使用 Spring Validator 验证对象属性

import org.springframework.validation.BindingResult;
import org.springframework.validation.DataBinder;
import org.springframework.validation.Validator;

/**
 * 验证对象属性是否符合预期,支持 validation-api 与 hibernate-validator 验证注解
 *
 * @param target 待验证对象
 * @param validator Spring 验证器,可直接注入获取 Validator 对象或自行实现 Validator 接口
 * @param groups 分组标识类,对应验证注解中的 groups 属性
 * @return 返回验证结果
 */
public static BindingResult validate(Object target, Validator validator, Class<?>... groups) {
    DataBinder binder = new DataBinder(target);
    binder.setValidator(validator);
    List<Class<?>> list = new ArrayList<>();
    if (groups != null) {
        for (Class<?> group : groups) {
            if (group != null) {
                list.add(group);
            }
        }
    }
    binder.validate(list.toArray());
    return binder.getBindingResult();
}

Spring 读取 properties 与 yml 文件

package net.saisimon.util;

import java.io.IOException;
import java.util.List;

import org.springframework.boot.env.PropertiesPropertySourceLoader;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ClassPathResource;

public class PropertyUtils {
	
    public static Object fetchProperties(PropertySourceLoader loader, String name, String key) {
        try {
            ClassPathResource classPathResource = new ClassPathResource(name);
            List<PropertySource<?>> propertySources = loader.load(name, classPathResource);
            if (propertySources.size() == 0) {
                return null;
            }
            return propertySources.get(0).getProperty(key);
        } catch (IOException e) {
            return null;
        }
    }
	
    /**
     * 读取 application.yml 文件的属性
     * 
     */
    public static Object fetchYaml(String key, Object defaultValue) {
        Object value = fetchProperties(new YamlPropertySourceLoader(), "application.yml", key);
        return value == null ? defaultValue : value;
    }
	
    /**
     * 读取 application.properties 文件的属性
     * 
     */
    public static Object fetchProperty(String key, Object defaultValue) {
        Object value = fetchProperties(new PropertiesPropertySourceLoader(), "application.properties", key);
        return value == null ? defaultValue : value;
    }
	
}

Spring 多数据源下设置默认注解事务管理器

  • 使用 @EnableTransactionManagement 注解开启事物管理,使用 @Transactional 注解控制事物,未指定 value 值,则使用默认指定事务管理器
package net.saisimon.config;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;

@Configuration
public class TransactionManagementConfig implements TransactionManagementConfigurer {
    
    @Autowired
    private PlatformTransactionManager transactionManager;
    
    @Bean(name = "transactionManager")
    @Primary
    public PlatformTransactionManager transactionManager(@Qualifier("devDB") DataSource devDB) {
        return new DataSourceTransactionManager(devDB);
    }
    
    @Bean(name = "testDBTransactionManager")
    public PlatformTransactionManager testDBTransactionManager(@Qualifier("testDB") DataSource testDB) {
        return new DataSourceTransactionManager(testDB);
    }

    @Override
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        // 设置默认注解事物管理器
        return transactionManager;
    }
    
}

Python

Python

Windows 上 Python2 和 Python3 兼容

  • 使用 -2 和 -3 区分 Python 版本
# python2 运行 main.py
py -2 main.py
# python3 运行 main.py
py -3 main.py
  • 代码中指明使用的 Python 解释器版本
# 指定 Python2 解释器 写在代码第一行
#! python2

# 指定 Python3 解释器 写在代码第一行
#! python3

指定 Python 源码编码

# -*- coding:utf-8 -*-

Windows 平台读取和打开二进制文件模式选择

# Python 在 Windows 平台上区分文本文件和二进制文件;读取或写入文本文件中时, 行尾字符会被自动地稍加改变。
# 在读写二进制文件时,要选择二进制模式打开。
file = open(filename, 'wb')

SCons

CentOS 下编译安装 SCons

- 确定系统中安装了 Python
  $ python -V
  Python 2.6.6
- 方式一: 使用 yum 安装 SCons
  $ yum install scons
- 方式二: 使用安装包安装 SCons
  - 去官网下载 SCons 安装包,解压
  $ tar -xzvf scons-2.4.1.tar.gz
  - 编译安装,默认安装路径 /usr/lib/scons-2.4.1
  $ python setup.py install [--prefix=/xx/xx]
- 查看版本号
  $ scons -v

Web

HTML

HTML 转义字符

HTML Entities

CSS

CSS 文本两端对齐

.justify {
    text-align:justify;
    text-justify:distribute-all-lines; /*ie6-8*/
    text-align-last:justify; /* ie9*/
    -moz-text-align-last:justify; /*ff*/
    -webkit-text-align-last:justify; /*chrome 20+*/
}

CSS 文本溢出省略

<div class="text">This is some long text that will not fit in the box</div>
<style>
  .text {
    width: 100px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
</style>

滚动条样式

body::-webkit-scrollbar {
    width: 8px;
    height: 8px;
    background-color: #F5F5F5;
}

body::-webkit-scrollbar-thumb {
    background-color: #666;
}

CSS3 文字循环滚动

  • HTML
 <div class="content">
    <div class="marquee">
        <span>A超出文本测试1 B超出文本测试2 C超出文本测试3 D超出文本测试4</span>
    </div>
</div>
  • 样式, 鼠标移动到文字上时执行滚动动画
.content {
    width: 300px;
    border: 1px solid #000;
    font-size: 20px;
    margin: auto;
    height: 50px;
    line-height: 50px;
    padding: 0px 5px;
}
.marquee {
    white-space: nowrap; /* 不换行 */
    overflow-x: hidden;
    width: 100%;
}
.marquee span:hover {
    display: inline-block;
    animation: marquee 5s linear infinite; /* 执行 marquee 动画,5秒一次,线性,无限循环 */
}
@keyframes marquee {
    0% { 
        transform: translate(0, 0); 
    }
    100% { 
        transform: translate(-100%, 0); 
    }
}

JavaScript

JavaScript 类型判断

  • 是否为数字
    function isNumber(obj) {
        return obj === +obj;
    }
        
  • 是否为字符串
    function isString(obj) {
        return obj === obj + '';
    }
        
  • 是否为布尔类型
    function isBoolean(obj) {
        return obj === !!obj;
    }
        
  • 是否为数组
    function isArray(obj) {
        return Object.prototype.toString.call(obj) === '[object Array]';   
    }
        

JavaScript 首字母大写

function toCapitalizeCase(str) {
    if (!str || str.length === 0) {
        return str;
    }
    if (str.length == 1) {
        return str.toUpperCase();
    } else {
        return str[0].toUpperCase() + str.substring(1);
    }
}

JavaScript 日期格式化

Date.prototype.format = function(style) {
    var o = {
        "M+" : this.getMonth() + 1, //month
        "d+" : this.getDate(),      //day
        "h+" : this.getHours(),     //hour
        "m+" : this.getMinutes(),   //minute
        "s+" : this.getSeconds(),   //second
        "w+" : "\u65e5\u4e00\u4e8c\u4e09\u56db\u4e94\u516d".charAt(this.getDay()),   //week
        "q+" : Math.floor((this.getMonth() + 3) / 3),  //quarter
        "S"  : this.getMilliseconds() //millisecond
    }
    if (/(y+)/.test(style)) {
        style = style.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
    }
    for(var k in o){
        if (new RegExp("("+ k +")").test(style)){
            style = style.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length));
        }
    }
    return style;
};

/*
 * 示例
 * 2016-12-01 19:12:28
 */
var formatDate = new Date().format("yyyy-MM-dd hh:mm:ss");

JavaScript 过滤数组中的假值

  • 假值有 false, null, 0, “”, undefined 和 NaN
function filterFalse(arr) {
    return arr.filter(function(value) {
        return Boolean(value);
    });
}

console.log(filterFalse([false, null, "aaa", 7, 0, "", NaN]));
// -> ["aaa", 7]

HTML5 LocalStorage 本地存储

  • 各浏览器的对 LocalStorage 的支持情况
IEFirefoxChromeOperaSafariIOSAndroid
8.0+3.5+4.0+11.5+4.0+3.2+2.1+

数据来源

  • LocalStorage 的使用
// 判断浏览器是否支持 LocalStorage
if(window.localStorage){
    localStorage.username = "Saisimon"; // 设置 username 为"Saisimon"
    localStorage["username"] = "Simon"; // 设置 username 为 "Simon",会覆盖上面的设置
    localStorage.setItem("username", "Saisimon"); // 同上
    var username = localStorage["username"]; // 获取 username 的值
    username = localStorage.username; // 同上
    username = localStorage.getItem("username"); // 同上
    localStorage.removeItem("username"); // 清除 username 的值
    localStorage.clear(); // 清除 LocalStorage 中的所有值

    // 打印出 LocalStorage 中的所有值
    var storage = window.localStorage;
    for (var i = 0; i < storage.length; i++) {
        console.log(storage.key(i) + " : " + storage.getItem(storage.key(i));
    }
} else {
    alert('Your browser does not support LocalStorage.');
}

JavaScript 数组中插入、删除指定位置

// index 添加/删除值的位置
// howmany 要删除的数量,添加时设置为0
// item1, ..., itemX 向数组添加的新值
arrayObject.splice(index, howmany, item1, ....., itemX)

var arrayObject = ['1', '2', '3']
// 插入
arrayObject.splice(2, 0, '2') // ['1', '2', '2', '3']
arrayObject.splice(4, 0, '2') // ['1', '2', '2', '3', '2']
arrayObject.splice(-1, 0, '5') // ['1', '2', '2', '3', '5', '2']
// 删除
arrayObject.splice(4, 0) // ['1', '2', '2', '3', '2']
arrayObject.splice(0, 3) // ['3', '2']

splice()

JavaScript 对象深拷贝

  • 遍历对象所有属性进行深拷贝
function cloneObj(obj) {
    var newObj = {};
    if (obj instanceof Array) {
        newObj = [];
    }
    for (var key in obj) {
        var val = obj[key];
        newObj[key] = typeof val === 'object' ? cloneObj(val) : val;
    }
    return newObj;
}
  • 使用 JSON 进行序列化与反序列化,但深拷贝对象中不能有函数
function cloneObj(obj) {
    return JSON.parse(JSON.stringify(obj));
}

JavaScript 判断给定输入是否符合 JSON 格式

function isJSON(str) {
    if (typeof str == 'string') {
        try {
            var obj = JSON.parse(str);
            if (typeof obj == 'object' && obj) {
                return true;
            } else {
                return false;
            }
        } catch (e) {
            return false;
        }
    }
}

JavaScript 控制浏览器全屏模式

  • 指定对象开启全屏模式
function launchFullscreen(element) {
    if (element.requestFullscreen) {
        // Default
        element.requestFullscreen();
    } else if (element.mozRequestFullScreen) {
        // FireFox 10+
        element.mozRequestFullScreen();
    } else if (element.webkitRequestFullscreen) {
        // Google Chrome 15+, Microsoft Edge, Opera 15+, Safari 5.1+
        element.webkitRequestFullscreen();
    } else if (element.msRequestFullscreen) {
        // Internet Explorer 11
        element.msRequestFullscreen();
    } else {
        console.log('不支持全屏API');
    }
}
  • 退出全屏模式
function exitFullscreen() {
    if (document.exitFullscreen) {
        // Default
        document.exitFullscreen();
    } else if (document.mozCancelFullScreen) {
        // FireFox 10+
        document.mozCancelFullScreen();
    } else if (document.webkitExitFullscreen) {
        // Google Chrome 15+, Microsoft Edge, Opera 15+, Safari 5.1+
        document.webkitExitFullscreen();
    } else if (document.msExitFullscreen) {
        // Internet Explorer 11
        document.msExitFullscreen();
    } else {
        console.log('不支持全屏API');
    }
}
  • 全屏模式改变事件
function fullscreenChange() {
    document.addEventListener('fullscreenchange', function(e) {
        console.log('fullscreenchange');
        console.log(e);
    });
    document.addEventListener('mozfullscreenchange', function(e) {
        console.log('mozfullscreenchange');
        console.log(e);
    });
    document.addEventListener('webkitfullscreenchange', function(e) {
        console.log('webkitfullscreenchange');
        console.log(e);
    });
    document.addEventListener('msfullscreenchange', function(e) {
        console.log('msfullscreenchange');
        console.log(e);
    });
}

注:基于浏览器安全考虑,全屏模式无法直接调用,必须有用户行为才能调用全屏API。直接调用会提示警告,并调用失败。

Fullscreen API

JavaScript 函数防抖与节流

  • 防抖与节流都是对多次调用的函数做次数限制。例如函数请求后台,调用频率要做限制,或用户输入、键入、滑动、滚动等事件会大量调用回调函数

防抖(debounce): 在指定时间后调用指定函数,若指定时间内调用该函数,将重置计时器直到指定时间后才会调用指定函数。适用于会大量调用回调函数的事件。

/**
 * 防抖(debounce)
 * @param method {Function} 被防抖函数
 * @param delay {Number(ms)} 延迟时间,单位为毫秒
 * @param context 执行函数的上下文
 */
function debounce(method, delay, context) {
    var args = arguments; 
    clearTimeout(method.timer);
    method.timer = setTimeout(function () {
        method.apply(context, args);
    }, delay);
}

节流(throttle): 以指定时间为一个周期,调用指定函数,限制调用频率。适用于有后台请求的函数。

/**
 * 节流(throttle)
 * @param method {Function} 被节流函数
 * @param delay {Number(ms)} 延迟时间,单位为毫秒
 * @param context 执行函数的上下文
 */
function throttle(method, delay, context){
    var args = arguments;
    // 标志位
    var canRun = true;
    return function() {
        if (!canRun) {
            return;
        }
        setTimeout(function() {
            method.apply(context, args);
            canRun = true;
        }, delay);
    };
}

JQuery

JQuery 与 Prototype 中 $ 符号冲突解决方法

  • JQuery 在 prototype 之后引入,即:
    <script src="prototype.js" type="text/javascript"/> 
    <script src="jquery.js" type="text/javascript"/>
        
    // 改变 JQuery 的选择标识符,将 $ 的控制权交还给 Prototype 。
    var jq = jQuery.noConflict();
    // 使用 JQuery 选择器的方式改为如下:
    jq("#id").text();
        
  • JQuery 在 prototype 之前引入,即:
    <script src="jquery.js" type="text/javascript"/> 
    <script src="prototype.js" type="text/javascript"/>
        
    // 这种情况 $ 为 Prototype 中定义的标识符,要想使用 JQuery 的选择器,需用如下形式:
    jQuery("#id").text();
        
  • 通用解决方案,不管引入的先后顺序:
    // JQuery 放弃 $ 所有权
    jQuery.noConflict();
    (function($){ 
            .....
            //此时在这个语句块中使用的都是 JQuery 中定义的 $
            $('#id').text(); 
    })(jQuery)
        

JQuery 获取 url 参数

$.extend({
    getUrlVars: function(){
        var vars = [], hash;
        var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
        for(var i = 0; i < hashes.length; i++) {
            hash = hashes[i].split('=');
            vars.push(hash[0]);
            vars[hash[0]] = hash[1];
        }
        return vars;
    },
    getUrlVar: function(name){
        return $.getUrlVars()[name];
    }
});

// 调用方法
$(document).ready(function() {
    var args = $.getUrlVars();
    var arg1 = $.getUrlVar('argName1');
    var arg2 = $.getUrlVar('argName2');
});

JQuery 重复绑定问题

$("#id").die().live("click", function() {
    // click event
    ...
);

$("#id").unbind("click").click(function() {
    // click event
    ...
});

JQuery 动态加载 JavaScript 文件

  1. 加载单个 JavaScript 文件
    $.ajax({
        url : "js file path",
        dataType : "script",
        cache : true,
        success : function () {
            // 成功加载 js 文件后的回调函数
        }
    });
    
    $.getScript("js file path", function() {
        // 成功加载 js 文件后的回调函数
    });
        
  2. 加载多个 Javascript 文件
    $.when(
        $.getScript("js file path 1"),
        $.getScript("js file path 2"),
        $.getScript("js file path 3")
    ).done(function() {
        // 全部 js 文件加载后的回调函数
    });
        

使用 jQuery Form Plugin 进行异步表单提交

<form id="test" method="post" action="/form/submit" >
  <input type="text" name="username" id="username" />
  <input type="password" name="password" id="password" />
  <div id="btn">Sumbit</div>
</form>

<script type="text/javascript" src="jquery.min.js" ></script>
<script type="text/javascript" src="jquery.form.min.js" ></script>
<script>
$(function() {
    $("#btn").click(function() {
        $("#test").ajaxSubmit({
            beforeSubmit: function(arr, $form, options) { // 提交表单前的回调函数
                // arr: 表单数据
                // $form: 表单对象
                // options: 表单提交的可选对象
                // 回调函数返回 false 表单将不会提交
                console.log(arr);
            },
            url: '/form/sumbit', // 表单提交的链接
            dataType: 'json', // 预期响应的数据类型
            data: { // 和表单一起提交的额外数据对象
                sumbitType: 'save',
                otherDate: 'otherDate'
            },
            type: 'post', // 提交表单的类型(GET, POST, PUT)
            clearForm: true, // 提交表单成功后是否清除表单中的数据
            uploadProgress: function(event, position, total, percentComplete) {
                // event: 事件对象
                // position: 上传的位置
                // total: 上传的总量
                // percentComplete: 完成的百分数
                console.log(position, total, percentComplete + "%");
            },
            success: function(responseText) { // 提交成功后的回调函数
                alert(responseText);
            },
            error: function () { //提交错误后的回调函数
                alert("error");
            }
        });
    });
});
</script>

jQuery Form Plugin

Prototype

Prototype 获取 select 选择框中选中文本

// 选择框中选中文本的下标
var idx = $(id).selectedIndex;
// 获取文本
var text = $(id).options[idx].text.strip();

Docker

进入 Docker 容器

  • 运行下面的 .bashrc_docker 文件
# .bashrc_docker
alias docker-pid="sudo docker inspect --format '{{.State.Pid}}'"
alias docker-ip="sudo docker inspect --format '{{ .NetworkSettings.IPAddress }}'"

# the implementation refs from https://github.com/jpetazzo/nsenter/blob/master/docker-enter
function docker-enter() {
    #if [ -e $(dirname "$0")/nsenter ]; then
    #Change for centos bash running
    if [ -e $(dirname '$0')/nsenter ]; then
        # with boot2docker, nsenter is not in the PATH but it is in the same folder
        NSENTER=$(dirname "$0")/nsenter
    else
        # if nsenter has already been installed with path notified, here will be clarified
        NSENTER=$(which nsenter)
        #NSENTER=nsenter
    fi
    [ -z "$NSENTER" ] && echo "WARN Cannot find nsenter" && return

    if [ -z "$1" ]; then
        echo "Usage: `basename "$0"` CONTAINER [COMMAND [ARG]...]"
        echo ""
        echo "Enters the Docker CONTAINER and executes the specified COMMAND."
        echo "If COMMAND is not specified, runs an interactive shell in CONTAINER."
    else
        PID=$(sudo docker inspect --format "{{.State.Pid}}" "$1")
        if [ -z "$PID" ]; then
            echo "WARN Cannot find the given container"
            return
        fi
        shift

        OPTS="--target $PID --mount --uts --ipc --net --pid"

        if [ -z "$1" ]; then
            # No command given.
            # Use su to clear all host environment variables except for TERM,
            # initialize the environment variables HOME, SHELL, USER, LOGNAME, PATH,
            # and start a login shell.
            #sudo $NSENTER "$OPTS" su - root
            sudo $NSENTER --target $PID --mount --uts --ipc --net --pid su - root
        else
            # Use env to clear all host environment variables.
            sudo $NSENTER --target $PID --mount --uts --ipc --net --pid env -i $@
        fi
    fi
}

文件来源

  • 进入指定 Docker 容器
$ docker ps # 查看容器的名称或者 ID
$ docker-pid <containerId> # 查看容器的 PID
$ docker-enter <containerId> # 进入指定容器

批量删除 <none> 镜像

# 删除所有停止的容器,防止删除镜像时应有依赖的容器存在,而导致删除失败
$ docker rm $(docker ps -a | grep "Exited" | awk '{print $1}')

# 删除所有 <none> 镜像
$ docker rmi $(docker images | grep "<none>" | awk '{print $3}')

Emacs

安装 Emacs 时,error: The required function `tputs’ was not found in any library

  • 缺少 libncurses-dev 包
$ yum install libncurses-dev -y
或
$ apt-get install libncurses-dev

轻量级标记语言对比

轻量级标记语言是一种 语法简单 的标记语言。它使用易于理解的格式标记,没有古怪的<标签>。 md为Markdown,gfm是GitHub风格的Markdown,rst为reStructedText,ttl为Textile,asc为AsciiDoc,org为Org-mode

常用轻量级标记语言对比

Vim

Vim 查询时忽略大小写

  • Vim 默认查询是区分大小写的,忽略大小写的配置
// 忽略大小写查询
: set ignorecase
  • 智能忽略大小写,输入为全小写时,忽略大小写进行查询,当输入中有至少一个大写时,则进行大小写敏感查询
// 智能忽略大小写
// 模式        匹配
// vim        vim,Vim,vIm,viM,VIm,ViM,vIM,VIM
// Vim        Vim
// VIm        VIm
: set ignorecase smartcase

Git

Git 基本操作

- 初始化
  $ git init
- clone别人的库
  $ git clone ssh://user@domain.com/repo.git
- 查看库的状态
  $ git status
- 查看工作区与暂存区文件的修改
  $ git diff
- 添加文件到暂存区
  $ git add .
- 提交文件到本地库
  $ git commit
- 提交历史纪录
  $ git log
- 查看库的分支
  $ git branch
- 切换分支
  $ git checkout <branch>
- 将本地库推送至远程库中
  $ git push <remote> <branch>
- 将指定分支合并至当前分支
  $ git merge <branch>

更新 .gitignore 后,清理 Git 仓库

- 清理暂存区的文件
  $ git rm -r --cached .
- 添加所有文件
  $ git add .
- 提交
  $ git commit -m ".gitignore is now working"

Git 在 push 之前撤回最近一次 commit 命令

$ git reset --soft HEAD^

修改 Git 别名

  • 修改 .gitconfig 文件中[alias]属性
    $ vi ~/.gitconfig
      [alias]
          st = status
          ci = commit
          co = checkout
          br = branch
        
  • 使用 git config –global alias.[alias-name] [operation-name]
    # 表示将 st 作为 status 的别名,可以直接使用 git st 命令
    $ git config --global alias.st status
        

Git 推送代码

- 首次推送,添加远程代码库至配置
  $ git remote add tip https://github.com/Saisimon/tip.git
- 推送代码至远程代码库
  $ git push tip master
- 输入账号密码进行确认

Git 拉取代码

- 暂存工作区
 $ git stash
- 拉取远程代码
 $ git pull origin master
- 还原最近一次工作区的内容
 $ git stash pop
- 出现冲突时,解决冲突提交即可

添加新 ssh_key 至 Github

- 检查是否存在 .ssh 文件夹
  $ cd ~/.ssh
- 生成 ssh_key
  $ ssh-keygen -t rsa -C "youremail@email.com"
- 输入密码
- 启动 ssh-agent
  $ eval "$(ssh-agent -s)"
- 添加 ssh_key 到 ssh-agent
  $ ssh-add ~/.ssh/id_rsa
- 将 id_rsa.pub 中的key添加进 Github 中
  github >> Settings >> SSH and GPG keys >> new SSH key
- 测试联通性
  $ ssh git@github.com
  Hi Saisimon! You've successfully authenticated, but GitHub does not provide shell access.
  Connection to github.com closed.

Git status 输出中文为 UNICODE 解决方法

  • 修改 git config 属性
    $ git config --global core.quotepath false
        
  • 修改修改 git 配置文件
    $ vi ~/.gitconfig
      [core]
          quotepath = false
        

Git push 时,fatal: Authentication failed

  • 修改 remote 地址
    $ git remote set-url origin <ssh url>
        
  • 查看 remote 地址
    $ git remote -v
        

GitLab 服务启动与关闭

  • 开机自动开启 GitLab 服务
    $ systemctl enable gitlab-runsvdir.service
    $ systemctl start gitlab-runsvdir.service
        
  • 关闭开机自启动 GitLab 服务
    $ systemctl disable gitlab-runsvdir.service
    $ systemctl stop gitlab-runsvdir.service
        

修改 Git 仓库提交作者的信息

  • 批量修改提交作者信息脚本,替换 [xxx] 为对应值
    #!/bin/sh
    
    git filter-branch --env-filter '
    
    an="$GIT_AUTHOR_NAME"
    am="$GIT_AUTHOR_EMAIL"
    cn="$GIT_COMMITTER_NAME"
    cm="$GIT_COMMITTER_EMAIL"
    
    if [ "$GIT_COMMITTER_EMAIL" = "[旧邮箱地址]" ]
    then
        cn="[新作者名称]"
        cm="[新邮箱地址]"
    fi
    if [ "$GIT_AUTHOR_EMAIL" = "[旧邮箱地址]" ]
    then
        an="[新作者名称]"
        am="[新邮箱地址]"
    fi
    
    export GIT_AUTHOR_NAME="$an"
    export GIT_AUTHOR_EMAIL="$am"
    export GIT_COMMITTER_NAME="$cn"
    export GIT_COMMITTER_EMAIL="$cm"
    '
        
  • 在待修改的 Git 仓库运行以上脚本
  • 推送修改后的 Git 仓库
    git push --force --tags origin 'refs/heads/*'
        

Git 还原被错误 Merge 的分支

# 切换到 merge 操作时的分支
$ git checkout [分支名]
# 回退到 merge 前的版本
$ git reset --hard [merge 前的版本号]
# 撤回暂存区提交的修改文件
$ git reset HEAD .
# 还原所有被追踪的修改文件
$ git checkout .
# 确定需要删除的未被追踪的新建文件与目录
$ git clean -ndf
# 删除所有未被追踪的新建文件与目录
$ git clean -df