CSS艺术 背景与边框

半透明边框

背景颜色会延伸到边框的下面,如果给元素一个虚线边框就能看到

使用 background-clip 让元素的背景被内边框裁掉

1
2
3
4
5
width: 100px;
height: 100px;
border: 20px dashed hsla(0, 0%, 100%, 0.5);
background: darkgoldenrod;
background-clip: padding-box;

多重边框

box-shadow 模拟

原理就是让扩张半径增大,偏移量以及模糊值都为 0,需要注意,阴影并不会占据空间大小,需要处理元素的位置。

1
2
3
4
5
width: 100px;
height: 100px;
background: darkgoldenrod;
box-shadow: 0 0 0 10px #0000ff, 0 0 0 20px #00ff00, 0 0 0 30px #ff0000;
margin: 30px 0 0 30px;

outline

使用 outline + border 可以实现两侧边框,比 box-shadow 更灵活。

注意,outline 的边框可能和圆角不贴和。一些老的浏览器版本中可能存在

1
2
3
4
5
6
width: 100px;
height: 100px;
background: darkgoldenrod;
border: 10px solid #0000ff;
outline: 10px solid #00ff00;
border-radius: 10px;

使用负的 outline-offset 实现缝线的效果

1
2
3
4
5
6
width: 100px;
height: 100px;
background: darkgoldenrod;
outline: 1px dashed #fff;
border-radius: 10px;
outline-offset: -10px;

背景图片

background 简写属性如下

1
background:bg-color bg-image position/bg-size bg-repeat bg-origin bg-clip bg-attachment initial|inherit;

background-position 允许指定每个方向的偏移量,在简写属性中添 right bottom,可以防止background-position不被支持的时候位置误差过大

1
2
3
4
width: 100px;
height: 100px;
background: darkgoldenrod url(./cover1.jpg) right bottom / 20px 20px no-repeat;
background-position: right 20px bottom 20px;

如果你想让背景图片出现在右下角,但是又要空出与 padding 相等的距离,可以使用 background-origin 可以修改出现背景出现的区域

1
2
3
4
5
width: 100px;
height: 100px;
padding:20px;
background: darkgoldenrod url(./cover1.jpg) right bottom / 20px 20px no-repeat;
background-origin: content-box;

也可使用 calc() 函数

1
2
3
4
5
width: 100px;
height: 100px;
box-sizing: border-box;
background: darkgoldenrod url(./cover1.jpg) right bottom / 20px 20px no-repeat;
background-position: calc(100% - 20px) calc(100% - 20px);

条纹背景

首先我们想实现的是一个实色过度的背景,也就是没有渐变的效果

当下个颜色的起点在,上一个颜色的终点时,这时候没有空间让渐变产生就会是一个实色的背景

1
2
background:linear-gradient(#58a 50%,#fba 50%);
background-size:100% 30px;

还有一个简写的方法,如果某个颜色的位置比整个列表中他前面颜色的位置都要小,这个颜色的起始位置会被设置为前面颜色中的最大位置

1
2
background:linear-gradient(#58a 50%,#fba 0);
background-size:100% 30px;

如果想要垂直方向的条纹,需要给定一个角度,并修改背景大小

1
2
background:linear-gradient(90deg,#58a 50%,#fba 50%);
background-size:30px 100% ;

另一种常用的技巧是,把条纹的主色设置为背景色,再用另一种半透明的颜色覆盖,更容易修改

45度斜向条纹

如果理所当然的把角度改为其他角度,就以为能得到条纹背景,那就错了

因为旋转的只是一个背景单元(贴片)中的背景,而不是整个背景,他们拼在一起的时候会产生锯齿

所以需要在一个单元(贴片)中完整的画出条纹,在使用这个单元(贴片)去铺满背景

现在需要加几个锚点,在单元中画出相间的四条背景线,把这些单元拼接在一起的时候就会形成45度的斜向条纹

1
2
background:linear-gradient(45deg,#58a 25%,#fba 0,#fba 50%,#58a 0,#58a 75%,#fba 0);
background-size:30px 30px ;

其他角度的斜向条纹

但是其他角度的时候,会法相还是无法实现,比如 60度

所以css还提供了一个加强版的线性渐变,可以将你画出的部分当作单元并重复铺满整个背景

1
background:repeating-linear-gradient(60deg,#58a,#58a 15px,#fba 0,#fba 30px);

在实现条纹背景的时候,通常两个颜色属于一个色系,可以将主色设置为背景,副色作为条纹背景盖在上面,而且好处是不支持的时候可以显示主色的背景

1
2
3
4
5
6
7
8
background: repeating-linear-gradient(
60deg,
hsla(0, 0%, 100%, 0.1),
hsla(0, 0%, 100%, 0.1) 15px,
transparent 0,
transparent 30px
),
#58a;

网格背景

利用半透明的叠加,可以创建对比更明显的网格

1
2
3
4
background: white;
background: linear-gradient(90deg,rgba(200, 0, 0, 0.5) 50%,transparent 0),
linear-gradient(rgba(200, 0, 0, 0.5) 50%, transparent 0);
background-size: 30px 30px;

也可以让渐变的起始宽度为1px.创建更细的网格线

1
2
3
4
background: white;
background: linear-gradient(90deg, #58a 1px, transparent 0),
linear-gradient(#58a 1px, transparent 0);
background-size: 30px 30px;

也可以加重一些边框,形成层次更深的网格

1
2
3
4
5
6
7
background:  
linear-gradient(white 2px,transparent 0),
linear-gradient(90deg, white 2px,transparent 0),
linear-gradient(hsla(0,0%,100%,0.3) 1px,transparent 0),
linear-gradient(90deg,hsla(0,0%,100%,0.3) 1px,transparent 0)
#58a;
background-size: 75px 75px,75px 75px,15px 15px,15px 15px;

更复杂的背景案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
background: radial-gradient(
circle at 0% 50%,
rgba(96, 16, 48, 0) 9px,
#613 10px,
rgba(96, 16, 48, 0) 11px
)
0px 10px,
radial-gradient(
at 100% 100%,
rgba(96, 16, 48, 0) 9px,
#613 10px,
rgba(96, 16, 48, 0) 11px
),
#8a3;
background-size: 20px 20px;

波点背景

想实现这样的效果,我们从图形中切出一个小方块.用这个方块铺满整个背景

1
2
3
background:  
radial-gradient(tan 30%,transparent 0) #58a;
background-size:30px 30px;

但是现在看着还不是很饱满,我们可以生成两层图案,通过背景定位放到稍微错开的位置

1
2
3
4
5
6
background:  
radial-gradient(tan 30%,transparent 0),
radial-gradient(tan 30%,transparent 0),
#58a;
background-size:30px 30px;
background-position:0 0 ,15px 15px;

使用一个 mixin 让代码更容易维护

1
2
3
4
5
6
7
8
9
/*          单元格大小, 点的半径, 回退颜色, 点的颜色*/
@mixin polka($size, $dot, $base, $accent){
background:$base;
background-image:
radial-gradient($accent $dot,transparent 0),
radial-gradient($accent $dot,transparent 0)
background-size:$size $size;
background-position:0 0,$size/2 $size/2;
}

棋盘

期盼的效果看起来简单但实际上有一点麻烦,因为没有一种渐变能实现一个正方形中的1/4个小正方形的效果.

所以需要换一种思路,先实现正方形的两个对角,再用这两个对角,和其他正方形中的对角拼成一个小正方形

1
2
3
4
5
6
7
8
background: linear-gradient(
45deg,
#ccc 25%,
transparent 0,
transparent 75%,
#ccc 0
);
background-size: 30px 30px;

但是在一个渐变里面连续实现两个三角型,没有办法控制他们的位置进行拼接,所以需要分成两个渐变,并控制第二个渐变的背景位置

1
2
3
4
5
6
background: linear-gradient(45deg, #ccc 25%, transparent 0),
linear-gradient(45deg, transparent 75%, #ccc 75%),
linear-gradient(-45deg, #ccc 25%, transparent 0),
linear-gradient(-45deg, transparent 75%, #ccc 75%);
background-position: 0 0, -15px 15px, 0 -15px, -15px 0;
background-size: 30px 30px;

部分浏览器已经支持角向渐变,可以直接画出1/4个正方形

1
2
background: conic-gradient(red,yellow,lime,aqua,blue,fuchsia,red);
background-size: 30px 30px;

伪随机背景

如果背景是不透明的,而且是连续的,那就会每隔background-size指定的像素后就会重复一次.

所以可以考虑将背景大小设为不同的数值,并且渐变不会铺满整个背景,让他们相互覆盖,形成随机

1
2
3
4
background: linear-gradient(90deg, #fb3, 10px, transparent 0),
linear-gradient(90deg, #ab4, 20px, transparent 0),
linear-gradient(90deg, #655, 30px, transparent 0);
background-size: 40px 100%, 60px 100%, 80px 100%;

但是使用整数还是容易被察觉,每隔240px也就是各个背景大小的最小公倍数,所以这里可以把背景大小换成质数

1
2
3
4
background: linear-gradient(90deg, #fb3, 10px, transparent 0),
linear-gradient(90deg, #ab4, 20px, transparent 0),
linear-gradient(90deg, #655, 30px, transparent 0);
background-size: 41px 100%, 61px 100%, 71px 100%;

图像边框

如何实现把一张照片中间部分当作内容区域,剩下区域当作背景的效果.

最简单的想法是,通过两个元素下面的元素用上面的元素遮挡.这个方法是可行的.但是如果只用一个元素呢?

如果你想到的 background-image 那可能会有一点问题, background-image 会将背景图片按九宫格划分,在四个边上的背景会被拉伸或者重复.

其实还可以用多重背景来做, 用 background-clip 控制背景的显示区域,下面用一个夸张的样式看下现在的效果

1
2
3
4
5
6
7
width: 320px;
height: 180px;
border: 100px solid transparent;
background: linear-gradient(white, transparent), url(./cover1.jpg);
background-size: cover;
background-clip: padding-box, border-box;
background-origin: padding-box;

在边框上已经有指定的背景,但是背景没有从边框的左上角开始,这是因为 background-origin 默认是 padding-box 会从内边框的左上角开始,所以边框上的图片是重复平铺之后扩展出来的图片,下面稍微修改一下

1
2
3
4
5
6
7
width: 320px;
height: 180px;
border: 20px solid transparent;
background: linear-gradient(#fff, #fff), url(./cover1.jpg);
background-size: cover;
background-clip: padding-box, border-box;
background-origin: border-box;

下面是简化后的属性

1
2
3
4
5
6
width: 320px;
height: 180px;
border: 20px solid transparent;
background:
linear-gradient(#fff, #fff) padding-box,
url(./cover1.jpg) border-box 0 / cover;

信封效果边框

可以利用背景的渐变并应用在边框上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
width: 320px;
height: 180px;
border: 10px solid transparent;
background: linear-gradient(#fff, #fff) padding-box,
repeating-linear-gradient(
-45deg,
transparent 0,
transparent 12.5%,
red 0,
red 25%,
transparent 0,
transparent 37.5%,
#58a 0,
#58a 50%,
transparent 0
) 0 / 5em 5em;

动态虚线边框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
width: 320px;
height: 180px;
border: 1px solid transparent;
background:
linear-gradient(#fff, #fff) padding-box,
repeating-linear-gradient(
-45deg,
transparent 0,
transparent 25%,
#000 0,
#000 50%
) 0 / 0.5em 0.5em;
animation: ani 10s linear infinite;
}

@keyframes ani {
0% {
background-position:0
}
100% {
background-position:100%
}
}

Gitlab 私有化部署

最后更新

2024-08-12

官方安装包

安装必要依赖

1
2
sudo apt-get update
sudo apt-get install -y curl openssh-server ca-certificates tzdata perl

安装邮件服务,配置项选择 Internet Site,mail name 填写当前服务器 DNS.

1
sudo apt-get install -y postfix

添加仓库

1
curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.deb.sh | sudo bash

安装, 填写需要访问的域名

1
sudo apt-get install gitlab-ee

前置nginx 配置, 非 gitlab 内部 nginx

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
stream {
server {
listen 24922;
proxy_pass 192.168.48.227:22;
}
}

http {
server {
listen 9348 ssl;
listen [::]:9348 ssl;
http2 on;
server_name gitlab.iftrue.club gitlab.iftrue.me;

location / {
proxy_pass http://192.168.48.227;

proxy_read_timeout 300s;
proxy_connect_timeout 300s;
proxy_redirect off;

# Pass along essential headers
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Ssl on; #
}
}
}

修改配置文件 /etc/gitlab/gitlab.rb

1
2
3
4
5
6
7
8
9
10
11
# 修改外部访问地址,用于项目的下载地址,需要有完整的协议和端口号
external_url "https://gitlab.iftrue.me"

# 修改ssh端口,用于 ssh 克隆项目

gitlab_rails['gitlab_shell_ssh_port'] = 24922

# 保存后应用配置
sudo gitlab-ctl stop
sudo gitlab-ctl reconfigure
sudo gitlab-ctl start

如果在 Gitlab 前面有统一的反向代理,无需 Gitlab 本身处理 SSL 证书,可以将 Gitlab 的 nginx 配置为 80 端口。[文档]

1
2
3
4
5
nginx['enable'] = true
nginx['listen_port'] = 80
nginx['listen_https'] = false

sudo gitlab-ctl reconfigure

Docker 方式安装

只需要准备好证书文件,配置.yml 文件即可使用

/docker/gitlab/docker-compose.yml

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
version: '3.6'
services:
gitlab:
image: gitlab/gitlab-ee:latest
container_name: gitlab
restart: always
# 必填:访问gitlab的域名
hostname: 'gitlab.iftrue'
environment:
GITLAB_OMNIBUS_CONFIG: |
# 必填: 外部访问gitlab的地址
# 用于内部生成外部访问的链接,例如 clone地址
# 即使通过nginx代理访问gitlab, 协议也必须相同
external_url 'https://gitlab.iftrue.com'
# 首次登录时的免密
gitlab_rails['initial_root_password']='xxxx'
# ssh 端口
gitlab_rails['gitlab_shell_ssh_port'] = 24922
ports:
# 外部和内部端口必须与external_url端口相同
- '9348:9348'
- '24922:22'
volumes:
- './config:/etc/gitlab'
- './logs:/var/log/gitlab'
- './data:/var/opt/gitlab'
shm_size: '256m'
# 设置日志大小,避免磁盘写满
logging:
driver: "json-file"
options:
max-size: "50m" # 单个日志文件最大为 50MB
max-file: "5" # 最多保留 5 个日志文件

启动服务,需要等待一段时间,观察 docker 状态是否是 healthy

1
2
3
#  拉取最新镜像
docker compose pull
docker compose up -d

获取/修改 初始密码

1
docker exec -it  gitlab /bin/bash

查看初始密码,安装 gitlab 后 24 小时会自动删除

1
cat /etc/gitlab/initial_root_password

修改初始密码

1
2
3
4
5
gitlab-rails console                   # 进入命令行
u=User.where(id:1).first # 查找root用户
u.password='12345678' # 修改密码
u.password_confirmation='12345678' # 确认密码
u.save # 保存配置

nginx 配置

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
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name gitlab.iftrue.me;

location / {
# 如果 external_url 设置了 https 就要访问https地址
# 可以选择关闭强制https跳转的配置
proxy_pass https://192.168.48.213:9348;
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}


# 因为社区版的 nginx 不支持 tcp 流量转发,因此下面配置无效
# 可以使用防火墙进行转发

# server {
# listen 24922 ssl;
# listen [::]:24922 ssl;
# server_name gitlab.iftrue.me;
# location / {
# proxy_pass https://192.168.48.213:24922;
# }
# }

Docker 安装 nextcloud

配置文件

/docker/nextCloud/docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
version: '3'

services:
db:
image: mariadb:10.5
command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
restart: always
volumes:
- ./db:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=myPassword # db.env环境变量中的相同
env_file:
- db.env

redis:
image: redis:alpine
restart: always

app:
image: nextcloud:apache
restart: always
volumes:
- ./nextcloud:/var/www/html
environment:
- VIRTUAL_HOST=nextcloud.iftrue.me
- LETSENCRYPT_HOST=nextcloud.iftrue.me
- LETSENCRYPT_EMAIL=sunzhiqi@live.com
- MYSQL_HOST=db
- REDIS_HOST=redis
env_file:
- db.env
depends_on:
- db
- redis
networks:
- proxy-tier
- default

cron:
image: nextcloud:apache
restart: always
volumes:
- ./nextcloud:/var/www/html
entrypoint: /cron.sh
depends_on:
- db
- redis

proxy:
build: ./proxy
restart: always
ports:
- 7186:80
- 37186:443
labels:
com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy: "true"
volumes:
- ./certs:/etc/nginx/certs:ro
- ./vhost.d:/etc/nginx/vhost.d
- ./html:/usr/share/nginx/html
- /var/run/docker.sock:/tmp/docker.sock:ro
networks:
- proxy-tier

letsencrypt-companion:
image: nginxproxy/acme-companion
restart: always
volumes:
- ./certs:/etc/nginx/certs
- ./acme:/etc/acme.sh
- ./vhost.d:/etc/nginx/vhost.d
- ./html:/usr/share/nginx/html
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- proxy-tier
depends_on:
- proxy

# self signed
# omgwtfssl:
# image: paulczar/omgwtfssl
# restart: "no"
# volumes:
# - certs:/certs
# environment:
# - SSL_SUBJECT=servhostname.local
# - CA_SUBJECT=my@example.com
# - SSL_KEY=/certs/servhostname.local.key
# - SSL_CSR=/certs/servhostname.local.csr
# - SSL_CERT=/certs/servhostname.local.crt
# networks:
# - proxy-tier

volumes:
db:
nextcloud:
certs:
acme:
vhost.d:
html:

networks:
proxy-tier:

/docker/nextCloud/db.env

1
2
3
MYSQL_PASSWORD=myPassword
MYSQL_DATABASE=nextcloud
MYSQL_USER=nextcloud

/docker/nextCloud/proxy/Dockerfile

1
2
FROM nginxproxy/nginx-proxy:alpine
COPY uploadsize.conf /etc/nginx/conf.d/uploadsize.conf

/docker/nextCloud/proxy/uploadsize.conf

1
2
client_max_body_size 10G;
proxy_request_buffering off;

启动

1
2
cd /docker/nextCloud
docker-compose

国内服务器中转流量

firewalld 流量转发

firewalld是CentOS7/8默认的防火墙前端软件,绝大多数主机商提供的镜像都已经安装。

判断防火墙是否已经开启

1
firewall-cmd --state

如果状态不是 running,使用下面命令安装或开启防火墙

1
2
3
yum install -y firewalld         # 安装
systemctl enable firewalld # 开机启动
systemctl start firewalld # 开启防火墙

配置转发。假设你将国内服务器9090端口流量转发到国外vps的9091端口,转发命令如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf
sysctl -p # 使配置生效

# 启用伪装 --permanent 配置永久生效
firewall-cmd --permanent --add-masquerade

# 查看伪装是否生效
firewall-cmd --permanent --query-masquerade

# 开启端口
firewall-cmd --permanent --add-port=8080/tcp
firewall-cmd --permanent --add-port=8080/udp

# 配置转发
firewall-cmd --permanent --add-forward-port=port=9090:proto=tcp:toaddr=xxx.xx.xxx.xx:toport=9091
firewall-cmd --permanent --add-forward-port=port=9090:proto=udp:toaddr=xxx.xx.xxx.xx:toport=9091

# 重启防火墙生效
firewall-cmd --reload

注意:

云服务器需要在控制页面开启端口

ssr 等工具,ip和端口填写为国内转发服务器,链接时间和加密方式,和外网服务器配置相同

inotify + rsync 实现数据本地实时备份

实现过程

inotify 工具监听文件改变,将改变的文件使用 rsync 同步

inotify介绍

inotify 是一种强大的、细粒度的、异步文件系统监控机制,它满足各种各样的文件监控需要,可以监控文件系统的访问属性、读写属性、权限属性、创建删除、移动等操作,也可以监控文件发生的一切变化。

inotify-tools 是一个C库和一组命令行的工作提供Linux下inotify的简单接口。

inotify-tools 中包含 inotifywait 和 inotifywatch 两个命令

inotifywatch 命令用于收集关于被监控的文件系统的统计数据,包括每个inotify事件发生多少次。

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
[root@backup ~]# inotifywait --help
inotifywait 3.14
Wait for a particular event on a file or set of files.
Usage: inotifywait [ options ] file1 [ file2 ] [ file3 ] [ ... ]
Options:
-h|--help Show this help text.
@<file> Exclude the specified file from being watched.
--exclude <pattern>
Exclude all events on files matching the
extended regular expression <pattern>.指定排除部分文件
--excludei <pattern>
Like --exclude but case insensitive.(同上,排除且忽略大小写)
-m|--monitor Keep listening for events forever. Without
this option, inotifywait will exit after one
event is received.(持续监听)
-d|--daemon Same as --monitor, except run in the background
logging events to a file specified by --outfile.
Implies --syslog.(daemon模式)
-r|--recursive Watch directories recursively.(递归子目录)
--fromfile <file>
Read files to watch from <file> or '-' for stdin.
-o|--outfile <file>
Print events to <file> rather than stdout. (将事件输出到文件,而不是屏幕)
-s|--syslog Send errors to syslog rather than stderr.
-q|--quiet Print less (only print events).(打印事件)
-qq Print nothing (not even events).(不打印事件)
--format <fmt> Print using a specified printf-like format
string; read the man page for more details. (设置打印格式%T时间;%w触发事件文件所在绝对路径;%f触发事件文件名称;%e触发的事件名称;)
--timefmt <fmt> strftime-compatible format string for use with
%T in --format string.(指定输出内容,相当于将时间赋值给%T)
-c|--csv Print events in CSV format.
-t|--timeout <seconds>
When listening for a single event, time out after
waiting for an event for <seconds> seconds.
If <seconds> is 0, inotifywait will never time out.
-e|--event <event1> [ -e|--event <event2> ... ]
Listen for specific event(s). If omitted, all events are
listened for.(指定要监听的事件,多个事件用逗号隔开)

Exit status:
0 - An event you asked to watch for was received.
1 - An event you did not ask to watch for was received
(usually delete_self or unmount), or some error occurred.
2 - The --timeout option was given and no events occurred
in the specified interval of time.

Events: (事件)
access file or directory contents were read
modify file or directory contents were written
attrib file or directory attributes changed
close_write file or directory closed, after being opened in
writeable mode
close_nowrite file or directory closed, after being opened in
read-only mode
close file or directory closed, regardless of read/write mode
open file or directory opened
moved_to file or directory moved to watched directory
moved_from file or directory moved from watched directory
move file or directory moved to or from watched directory
create file or directory created within watched directory
delete file or directory deleted within watched directory
delete_self file or directory was deleted
unmount file system containing file or directory unmounted

示例 监听/backup/目录下所有文件和目录的增删改操作

1
inotifywait -mrq -e 'create,delete,close_write,attrib,moved_to' --timefmt '%Y-%m-%d %H:%M' --format '%T %w%f %e' /backup/

rsync介绍

rsync是可以实现增量备份的工具。配合任务计划,rsync能实现定时或间隔同步,配合inotify或sersync,可以实现触发式的实时同步。

Rsync的命令格式可以为以下六种:

1
2
3
4
5
6
rsync [OPTION]... SRC DEST
rsync [OPTION]... SRC [USER@]HOST:DEST
rsync [OPTION]... [USER@]HOST:SRC DEST
rsync [OPTION]... [USER@]HOST::SRC DEST
rsync [OPTION]... SRC [USER@]HOST::DEST
rsync [OPTION]... rsync://[USER@]HOST[:PORT]/SRC [DEST]

对应于以上六种命令格式,rsync有六种不同的工作模式:

  1)拷贝本地文件。当SRC和DES路径信息都不包含有单个冒号”:”分隔符时就启动这种工作模式。如:rsync -a /data /backup

  2)使用一个远程shell程序(如rsh、ssh)来实现将本地机器的内容拷贝到远程机器。当DST路径地址包含单个冒号”:”分隔符时启动该模式。如:rsync -avz *.c foo:src

  3)使用一个远程shell程序(如rsh、ssh)来实现将远程机器的内容拷贝到本地机器。当SRC地址路径包含单个冒号”:”分隔符时启动该模式。如:rsync -avz foo:src/bar /data

  4)从远程rsync服务器中拷贝文件到本地机。当SRC路径信息包含”::”分隔符时启动该模式。如:rsync -av root@172.16.78.192::www /databack

  5)从本地机器拷贝文件到远程rsync服务器中。当DST路径信息包含”::”分隔符时启动该模式。如:rsync -av /databack root@172.16.78.192::www

  6)列远程机的文件列表。这类似于rsync传输,不过只要在命令中省略掉本地机信息即可。如:rsync -v rsync://172.16.78.192/www

rsync参数的具体解释如下:

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
-v, --verbose 详细模式输出
-q, --quiet 精简输出模式
-c, --checksum 打开校验开关,强制对文件传输进行校验
-a, --archive 归档模式,表示以递归方式传输文件,并保持所有文件属性,等于-rlptgoD
-r, --recursive 对子目录以递归模式处理
-R, --relative 使用相对路径信息
-b, --backup 创建备份,也就是对于目的已经存在有同样的文件名时,将老的文件重新命名为~filename。可以使用--suffix选项来指定不同的备份文件前缀。
--backup-dir 将备份文件(如~filename)存放在在目录下。
-suffix=SUFFIX 定义备份文件前缀
-u, --update 仅仅进行更新,也就是跳过所有已经存在于DST,并且文件时间晚于要备份的文件。(不覆盖更新的文件)
-l, --links 保留软链结
-L, --copy-links 想对待常规文件一样处理软链结
--copy-unsafe-links 仅仅拷贝指向SRC路径目录树以外的链结
--safe-links 忽略指向SRC路径目录树以外的链结
-H, --hard-links 保留硬链结
-p, --perms 保持文件权限
-o, --owner 保持文件属主信息
-g, --group 保持文件属组信息
-D, --devices 保持设备文件信息
-t, --times 保持文件时间信息
-S, --sparse 对稀疏文件进行特殊处理以节省DST的空间
-n, --dry-run现实哪些文件将被传输
-W, --whole-file 拷贝文件,不进行增量检测
-x, --one-file-system 不要跨越文件系统边界
-B, --block-size=SIZE 检验算法使用的块尺寸,默认是700字节
-e, --rsh=COMMAND 指定使用rsh、ssh方式进行数据同步
--rsync-path=PATH 指定远程服务器上的rsync命令所在路径信息
-C, --cvs-exclude 使用和CVS一样的方法自动忽略文件,用来排除那些不希望传输的文件
--existing 仅仅更新那些已经存在于DST的文件,而不备份那些新创建的文件
--delete 删除那些DST中SRC没有的文件
--delete-excluded 同样删除接收端那些被该选项指定排除的文件
--delete-after 传输结束以后再删除
--ignore-errors 及时出现IO错误也进行删除
--max-delete=NUM 最多删除NUM个文件
--partial 保留那些因故没有完全传输的文件,以是加快随后的再次传输
--force 强制删除目录,即使不为空
--numeric-ids 不将数字的用户和组ID匹配为用户名和组名
--timeout=TIME IP超时时间,单位为秒
-I, --ignore-times 不跳过那些有同样的时间和长度的文件
--size-only 当决定是否要备份文件时,仅仅察看文件大小而不考虑文件时间
--modify-window=NUM 决定文件是否时间相同时使用的时间戳窗口,默认为0
-T --temp-dir=DIR 在DIR中创建临时文件
--compare-dest=DIR 同样比较DIR中的文件来决定是否需要备份
-P 等同于 --partial
--progress 显示备份过程
-z, --compress 对备份的文件在传输时进行压缩处理
--exclude=PATTERN 指定排除不需要传输的文件模式
--include=PATTERN 指定不排除而需要传输的文件模式
--exclude-from=FILE 排除FILE中指定模式的文件
--include-from=FILE 不排除FILE指定模式匹配的文件
--version 打印版本信息
--address 绑定到特定的地址
--config=FILE 指定其他的配置文件,不使用默认的rsyncd.conf文件
--port=PORT 指定其他的rsync服务端口
--blocking-io 对远程shell使用阻塞IO
-stats 给出某些文件的传输状态
--progress 在传输时现实传输过程
--log-format=formAT 指定日志文件格式
--password-file=FILE 从FILE中得到密码
--bwlimit=KBPS 限制I/O带宽,KBytes per second
-h, --help 显示帮助信息

示例1 将/etc/fstab拷贝到/tmp目录下。

1
rsync /etc/fstab /tmp

示例2 将/etc/cron.d目录拷贝到/tmp下。

1
rsync -r /etc/cron.d /tmp

说明:为了保持文件夹一致,可以将路径写为

1
rsync -avr /src/ /backup/

这样src下面的所有目录会被备份到 backup目录下面,而不会在backup文件夹下面创建src文件夹

创建同步脚本

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
#!/bin/bash

fn() {

src=$1 # 需要同步的源路径
des=$2 # 目标路径

cd ${src} #定位到源文件下面

# 此方法中,由于rsync同步的特性,这里必须要先cd到源目录,inotify再监听 ./ 才能rsync同步后目录结构一致

# 把监控到有发生更改的"文件路径列表"循环
/usr/bin/inotifywait -mrq --format '%Xe %w%f' -e modify,create,delete,attrib,close_write,move ./ | while read file; do
INO_EVENT=$(echo $file | awk '{print $1}') # 把inotify输出切割 把事件类型部分赋值给INO_EVENT
INO_FILE=$(echo $file | awk '{print $2}') # 把inotify输出切割 把文件路径部分赋值给INO_FILE
echo "-------------------------------$(date)------------------------------------"
echo $file
#增加、修改、写入完成、移动进事件
#增、改放在同一个判断,因为他们都肯定是针对文件的操作,即使是新建目录,要同步的也只是一个空目录,不会影响速度。
if [[ $INO_EVENT =~ 'CREATE' ]] || [[ $INO_EVENT =~ 'MODIFY' ]] || [[ $INO_EVENT =~ 'CLOSE_WRITE' ]] || [[ $INO_EVENT =~ 'MOVED_TO' ]]; then # 判断事件类型
echo 'CREATE or MODIFY or CLOSE_WRITE or MOVED_TO'
# INO_FILE变量代表路径 -c校验文件内容
rsync -avzcR $(dirname ${INO_FILE}) ${des}

#上面的rsync同步命令 源是用了$(dirname ${INO_FILE})变量 即每次只针对性的同步发生改变的文件的目录
#只同步目标文件的方法在生产环境的某些极端环境下会漏文件 现在可以在不漏文件下也有不错的速度 做到平衡)
#然后用-R参数把源的目录结构递归到目标后面 保证目录结构一致性
fi
#删除、移动出事件
if [[ $INO_EVENT =~ 'DELETE' ]] || [[ $INO_EVENT =~ 'MOVED_FROM' ]]; then
echo 'DELETE or MOVED_FROM'
#并加上--delete来删除目标上有而源中没有的文件,这里不能做到指定文件删除,如果删除的路径越靠近根,则同步的目录月多,同步删除的操作就越花时间。
rsync -avzr --delete $(dirname ${INO_FILE}) ${des}
fi
#修改属性事件 指 touch chmod chown等操作
if [[ $INO_EVENT =~ 'ATTRIB' ]]; then
echo 'ATTRIB'
if [ ! -d "$INO_FILE" ]; then # 如果修改属性的是目录 则不同步,因为同步目录会发生递归扫描,等此目录下的文件发生同步时,rsync会顺带更新此目录。
rsync -avzcR $(dirname ${INO_FILE}) ${des}
fi
fi
done
}

fn /home/supreme/Workspace/ /media/supreme/yes/Workspace/ & #加上&符号表示在后台执行
fn 源路径2 目标路径2 & #加上&符号表示在后台执行

添加开机启动项

将写好的脚本放到指定目录中

1
sudo cp ./sync.sh /usr/sbin

创建一个服务文件 sync.service

systemd有系统和用户区分;系统(/user/lib/systemd/system/)、用户(/etc/lib/systemd/user/).

一般系统管理员手工创建的单元文件建议存放在/etc/systemd/system/目录下面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Unit]
Description= 服务的简单描述
Documentation= 服务文档
# Before、After:定义启动顺序。Before=xxx.service,代表本服务在xxx.service启动之前启动。After=xxx.service,代表本服务在xxx.service之后启动。
After=network.target

[Service]
# systemd认为当该服务进程fork,且父进程退出后服务启动成功。对于常规的守护进程 daemon
# 除非你确定此启动方式无法满足需求,使用此类型启动即可。使用此启动类型应同时指定 PIDFile=,以便systemd能够跟踪服务的主进程。
Type=forking

ExecStart=/usr/sbin/sync.sh

[Install]
# 单元被允许运行需要的弱依赖性单元,WantBy从Want列表获得依赖信息。
WantedBy=multi-user.target

添加开机启动并重载服务

1
2
3
4
5
6
7
8
sudo systemctl daemon-reload

sudo systemctl enable sync.service

sudo systemctl start sync.service
sudo systemctl stop sync.service
sudo systemctl reload sync.service

Docker 部署Jira + 破解

创建必要目录

1
mkdir -p docker-compose/jira

创建 docker-compose.yml

  • 修改映射路径为当前文件夹下的相对路径
  • JIRA_PROXY_NAME = 域名
  • JIRA_PROXY_PORT = 外部端口
  • JIRA_PROXY_SCHEME = 协议
  • POSTGRES_PASSWORD 修改数据库密码
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
services:
jira_db:
image: mysql:8.0
container_name: jira_mysql
environment:
MYSQL_ROOT_PASSWORD: w.521@@ong.COM
MYSQL_DATABASE: jira
MYSQL_USER: jira
MYSQL_PASSWORD: w.521@@ong.COM
ports:
- 19306:3306
volumes:
- ./data:/var/lib/mysql
networks:
- jira_network # 将 mysql 服务连接到 mynetwork 网络
restart: always
jira:
image: atlassian/jira-software
container_name: jira
ports:
- 19140:8080
volumes:
- ./jiraVolume:/var/atlassian/application-data/jira
- ./lib:/opt/atlassian/jira/lib #驱动的目录,需要映射
networks:
- jira_network # 将 jira 服务连接到 mynetwork 网络
restart: unless-stopped
volumes:
mysql-data:
driver: local
jiraVolume:
driver: local
networks:
jira_network:
# 定义名为 mynetwork 的网络
driver: bridge # 使用默认的 bridge 驱动

破解

  • 下载 atlassian-agent.jar 文件压缩包,并解压

  • atlassian-agent.jar 复制到容器内 docker cp ./atlassian-agent.jar jira容器名称:/opt/jira

  • docker-compose/jira 目录下执行 docker-compose up 启动动容器

  • 进入容器 docker exec -it jira 容器名称 /bin/bash

  • 修改环境变量 cd /opt/jira/bin vi setenv.sh

export JAVA_OPTS 修改为 export JAVA_OPTS=”-javaagent:/opt/jira/atlassian-agent.jar ${JAVA_OPTS}”

  • 重启容器, 在日志中可以看到 ========= agent working ========= 字样表示成功

  • 再次进入容器 执行 java -jar atlassian-agent.jar -p jsm -m aaa@bbb.com -n my_name -o https://zhile.io -s ABCD-1234-EFGH-5678

特别注意 -p 参数设置,通过 java -jar atlassian-agent.jar 查看使用帮助,每种产品有不同的标识

-m 邮箱任意填写
-n 名称任意填写
-o 网址任意填写
-s server id 再安装时查看

复制执行命令之后产生的激活码,复制到激活码的窗口完成激活。

FAQ

  • 连接时报错 Could not find driver with class name: com.mysql.cj.jdbc.Driver

    下载 对应版本的驱动

    将驱动放在 jira 安装目录(或 docker 映射的目录)/lib 下面

    重启 jira 服务

③悄无声息的扩展-装饰者模式

简单说装饰可以让你不用修改底层代码给对象赋予新的职责。

从分离改变,更进一步

看了前两个设计模式,相信你一定感觉到继承只能解决静态时对类的扩展,如果想动态的对类的行为扩展就需要用到组合。

既然我们已经可以用策略模式分离改变的部分,还有什么做不到的么? 看看下面一个问题:

  • 奶茶店有几十种品种的饮料,他们都需要继承自一个抽象类 Beverage,因为每种饮料有自己的产品说明,并且各自实现了一个计算金额的方法 cost。
  • 每种奶茶除了自己特有的配料外,还可以额外付费添加配料,比如加两份的珍珠,加一份椰果,并且需要计算总价。

如果想枚举出店内的每一种产品是不现实的,那时非常庞大的一个排列组合,因为不可能知道客户要加那些配料。并且严重的违反了设计中的两个原则:

1
2
3
4
5
6
7
8
9
abstract class BeverageAbstract {
description = "some description";
cost() {}
}
// 果茶加牛奶
class FruitTeaWithMilk extends BeverageAbstract {}
// 柠檬茶加两份珍珠
class LemonTeaWith2Pearls extends BeverageAbstract {}
//...

既然不能枚举考虑是不是应该有一个统一的 Beverage 类,用于实现抽象类 BeverageAbstract,并且把所有的配料都添加在 Beverage 上,并记录配料的数量。
子类会调用父类 cost 方法计算所有配料,并加上自己品种的价格。

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
abstract class BeverageAbstract {
description = "some description";
cost() {}
}
class Beverage extends BeverageAbstract {
milk = 2;
milkCount = 0;
setMilk(count: number) {
this.milkCount = count;
}
coffee = 5;
coffeeCount = 0;
setCoffee(count: number) {
this.coffeeCount = count;
}
cost() {
let total = 0;
if (this.milkCount > 0) {
total += this.milkCount * this.milk;
}
if (this.coffeeCount > 0) {
total += this.coffeeCount * this.coffee;
}
return total;
}
}

class FruitTea extends Beverage {
cost() {
return 10 + super.cost();
}
}

const fruitTea = new FruitTea();
fruitTea.setMilk(2);
console.log(fruitTea.cost());

const fruitTea2 = new FruitTea();
fruitTea2.setMilk(1);
console.log(fruitTea.cost());

这样的设计还有一些问题:

  • 并不是所有的配料都需要继承,每种饮料都有自己特有的配料。
  • 一但需要新的配料或新的品种或价钱的改变,就需要修改父类。
  • 当有新的品种出现的时候,他可能继承了不必要的方法。

定义装饰者

设计类的一个原则是 ❤‍🔥 类应该对扩展开放,对修改关闭,也就是类设计中提到的开放关闭原则

开放且关闭并不冲突,想想观察中模式中的案例,可以通过调用类的方法添加观察者,而不改变原有的类的方法。并不需要每一个类都遵循开放关闭原则,避免过度设计,只需要针对可能经常会发生变化的类应用开发-关闭原则。

装饰者模式:动态的将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案

对于上面的问题,按以下的方式思考:

  • 我们已经有一个果茶类 FruitTea
  • 需要加两份额外的牛奶,用两个牛奶配料的类去修饰它
  • 需要加一份额外的咖啡,用一个咖啡配料的类去修饰它
  • 依赖修饰者的 cost 方法计算价格
  • 在编码的时候为了明确修饰者和被修饰者的关系,需要让修饰者和被修饰者继承同样的类或实现相同的接口,因为被修饰的类仍然被视作原有的类,它的属性以及方法的作用不应该发生变化,只是扩展了方法实现。
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
interface BeverageInterface {
description: string;
cost(): number;
getDescription(): string;
}
class MilkDecorator implements BeverageInterface {
beverage: BeverageInterface;
description = "MilkDecorator";
constructor(beverage: BeverageInterface) {
this.beverage = beverage;
}
getDescription() {
return this.description;
}
cost() {
return 2 + this.beverage.cost();
}
}
class CoffeeDecorator implements BeverageInterface {
beverage: BeverageInterface;
description = "CoffeeDecorator";
constructor(beverage: BeverageInterface) {
this.beverage = beverage;
}
getDescription() {
return this.description;
}
cost() {
return 5 + this.beverage.cost();
}
}
class FruitTea implements BeverageInterface {
description = "FruitTea";
getDescription() {
return this.description;
}
cost() {
return 10;
}
}

let fruitTea = new FruitTea();
// 原价fruitTea
console.log(fruitTea.cost());
// 添加配料一份牛奶 一份咖啡
fruitTea = new MilkDecorator(fruitTea);
fruitTea = new CoffeeDecorator(fruitTea);
console.log(fruitTea.cost());

现在已经分离了装饰着对象,但是使用装饰者模式是基于一下几个前提:

  • 被修饰的对象是可以抽象的,也就是说被修饰的对象不会轻易改变,这样针对它的修饰类才有意义
  • 修饰对象是不关心外部状态的,它只关心被修饰的对象,因为你想让他控制修饰链中的每个节点,需要更好的设计。
  • 被装饰的对象可能拥有特定的类型,在使用装饰的时候需要小心,避免功能或类型丢失。

与 ES6 修饰器的区别

ES6 提供修饰器方提案,在 babel 转译后支持,可以修饰类或类的属性和方法,但并不是一种设计模式。

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
interface BeverageInterface {
description: string;
cost(): number;
getDescription(): string;
}
function milkDecorator(target: () => number): () => number {
return () => 2 + target();
}

class FruitTea implements BeverageInterface {
description = "FruitTea";
getDescription() {
return this.description;
}
cost() {
return 10;
}
}

class FruitTeaWithMilk extends FruitTea {
@milkDecorator
cost(): number {
return super.cost();
}
}
let fruitTea = new FruitTeaWithMilk();
console.log(fruitTea.cost());
  • 继承属于扩展形式之一,但不见得是达到弹性设计的最佳方式。
  • 在我们的设计中,应该允许行为可以被扩展,而无须修改现有的代码。
  • 组合和委托可用于在运行时动态地加上新的行为。
  • 除了继承,装饰者模式也可以让我们扩展行为。
  • 装饰者模式意味着一群装饰者类,这些类用来包装具体组件。
  • 开放一关闭原则
  • 装饰者类反映出被装饰的组件类型(事实上,他们具有相同的类型,都经过接口或继承实)
  • 装饰者可以在被装饰者的行为前面与/或后面加上自己的行为,甚至将被装饰者的行为整个取代掉,而达到特定的目的。
  • 你可以用无数个装饰者包装一个组件。
  • 装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型。
  • 装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂。

MySQL DQL查询语言

DQL (Data Query Language)数据查询语言;

基本查询

1、查询的结果集 是一个虚拟表
2、select 查询列表 类似于System.out.println(打印内容);

select后面跟的查询列表,可以有多个部分组成,中间用逗号隔开
例如:select 字段1,字段2,表达式 from 表;

System.out.println()的打印内容,只能有一个。

3、执行顺序

① from子句
② select子句

4、查询列表可以是:字段、表达式、常量、函数等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119

USE myemployees;

#一、查询常量
SELECT 100 ;

#二、查询表达式
SELECT 100%3;

#三、查询单个字段
SELECT `last_name` FROM `employees`;

#四、查询多个字段
SELECT `last_name`,`email`,`employee_id` FROM employees;

#五、查询所有字段
SELECT * FROM `employees`;


#F12:对齐格式
SELECT
`last_name`,
`first_name`,
`last_name`,
`commission_pct`,
`hiredate`,
`salary`
FROM
employees ;

#六、查询函数(调用函数,获取返回值)
SELECT DATABASE();
SELECT VERSION();
SELECT USER();

#七、起别名
#方式一:使用as关键字

SELECT USER() AS 用户名;
SELECT USER() AS "用户名";
SELECT USER() AS '用户名';

SELECT last_name AS "姓 名" FROM employees;


#方式二:使用空格


SELECT USER() 用户名;
SELECT USER() "用户名";
SELECT USER() '用户名';

SELECT last_name "姓 名" FROM employees;


#八、+的作用
-- 需求:查询 first_name 和last_name 拼接成的全名,最终起别名为:姓 名

#方案1:使用+ pass×
SELECT first_name+last_name AS "姓 名"
FROM employees;



#方案2:使用concat拼接函数

SELECT CONCAT(first_name,last_name) AS "姓 名"
FROM employees;



/*

Java中+的作用:
1、加法运算
100+1.5 'a'+2 1.3+'2'

2、拼接符
至少有一个操作数为字符串
"hello"+'a'


mysql中+的作用:
1、加法运算

①两个操作数都是数值型
100+1.5

②其中一个操作数为字符型
将字符型数据强制转换成数值型,如果无法转换,则直接当做0处理

'张无忌'+100===>100


③其中一个操作数为null

null+null====》null

null+100====》 null



*/



#九、distinct的使用 去重

#需求:查询员工涉及到的部门编号有哪些


SELECT DISTINCT department_id FROM employees;


#十、查看表的结构

DESC employees;
SHOW COLUMNS FROM employees;

练习

显示出表 employees 的全部列,各个列之间用逗号连接,列头显示成 OUT_PUT

1
2
3
4
5
6
7
8
9
10
11
12
13
SELECT CONCAT(employee_id,',',first_name,',',last_name,',',salary,',',IFNULL(commission_pct,''))  AS "OUT_PUT"
FROM employees;

#ifnull(表达式1,表达式2)
/*
表达式1:可能为null的字段或表达式
表达式2:如果表达式1为null,则最终结果显示的值

功能:如果表达式1为null,则显示表达式2,否则显示表达式1

*/

SELECT commission_pct,IFNULL(commission_pct,'空') FROM employees;

条件查询

select 查询列表
from 表名
where 筛选条件;

执行顺序:
①from子句
②where子句
③select子句

1、按关系表达式筛选

关系运算符:> < >= <= = <>
补充:也可以使用!=,但不建议

2、按逻辑表达式筛选

逻辑运算符:and or not
补充:也可以使用&& || ! ,但不建议

3、模糊查询

like
in
between and
is null

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
#一、按关系表达式筛选
#案例1:查询部门编号不是100的员工信息
SELECT *
FROM employees
WHERE department_id <> 100;


#案例2:查询工资<15000的姓名、工资
SELECT last_name,salary
FROM employees
WHERE salary<15000;


#二、按逻辑表达式筛选

#案例1:查询部门编号不是 50-100之间员工姓名、部门编号、邮箱
#方式1
SELECT last_name,department_id,email
FROM employees
WHERE department_id <50 OR department_id>100;

#方式2


SELECT last_name,department_id,email
FROM employees
WHERE NOT(department_id>=50 AND department_id<=100);



#案例2:查询奖金率>0.03 或者 员工编号在60-110之间的员工信息
SELECT *
FROM employees
WHERE commission_pct>0.03 OR (employee_id >=60 AND employee_id<=110);


#三、模糊查询

#1like

/*
功能:一般和通配符搭配使用,对字符型数据进行部分匹配查询
常见的通配符:
_ 任意单个字符
% 任意多个字符,支持0-多个
like/not like
*/

#案例1:查询姓名中包含字符a的员工信息
SELECT *
FROM employees
WHERE last_name LIKE '%a%';

#案例2:查询姓名中包含最后一个字符为e的员工信息
SELECT *
FROM employees
WHERE last_name LIKE '%e';

#案例3:查询姓名中包含第一个字符为e的员工信息
SELECT *
FROM employees
WHERE last_name LIKE 'e%';

#案例4:查询姓名中包含第三个字符为x的员工信息
SELECT *
FROM employees
WHERE last_name LIKE '__x%';

#案例5:查询姓名中包含第二个字符为_的员工信息
SELECT *
FROM employees
WHERE last_name LIKE '_\_%';

SELECT *
FROM employees
WHERE last_name LIKE '_$_%' ESCAPE '$';


#2in
/*
功能:查询某字段的值是否属于指定的列表之内

a in(常量值1,常量值2,常量值3,...)
a not in(常量值1,常量值2,常量值3,...)

in/not in
*/

#案例1:查询部门编号是30/50/90的员工名、部门编号


#方式1
SELECT last_name,department_id
FROM employees
WHERE department_id IN(30,50,90);

#方式2

SELECT last_name,department_id
FROM employees
WHERE department_id = 30
OR department_id = 50
OR department_id = 90;


#案例2:查询工种编号不是SH_CLERK或IT_PROG的员工信息
#方式1
SELECT *
FROM employees
WHERE job_id NOT IN('SH_CLERK','IT_PROG');

#方式2
SELECT *
FROM employees
WHERE NOT(job_id ='SH_CLERK'
OR job_id = 'IT_PROG');


#3between and

/*
功能:判断某个字段的值是否介于xx之间

between and/not between and

*/


#案例1:查询部门编号是30-90之间的部门编号、员工姓名

#方式1
SELECT department_id,last_name
FROM employees
WHERE department_id BETWEEN 30 AND 90;

#方式2

SELECT department_id,last_name
FROM employees
WHERE department_id>=30 AND department_id<=90;


#案例2:查询年薪不是100000-200000之间的员工姓名、工资、年薪

SELECT last_name,salary,salary*12*(1+IFNULL(commission_pct,0)) 年薪
FROM employees
WHERE salary*12*(1+IFNULL(commission_pct,0))<100000 OR salary*12*(1+IFNULL(commission_pct,0))>200000;



SELECT last_name,salary,salary*12*(1+IFNULL(commission_pct,0)) 年薪
FROM employees
WHERE salary*12*(1+IFNULL(commission_pct,0)) NOT BETWEEN 100000 AND 200000;



#4is null/is not null

#案例1:查询没有奖金的员工信息
SELECT *
FROM employees
WHERE commission_pct IS NULL;



#案例2:查询有奖金的员工信息
SELECT *
FROM employees
WHERE commission_pct IS NOT NULL;


SELECT *
FROM employees
WHERE salary IS 10000;

#----------------对比------------------------------------

= 只能判断普通的内容

IS 只能判断NULL

<=> 安全等于,既能判断普通内容,又能判断NULL




SELECT *
FROM employees
WHERE salary <=> 10000;

SELECT *
FROM employees
WHERE commission_pct <=> NULL;

排序查询

select 查询列表
from 表名
【where 筛选条件】
order by 排序列表

执行顺序:

①from子句
②where子句
③select子句
④order by 子句

特点:

1、排序列表可以是单个字段、多个字段、表达式、函数、列数、以及以上的组合
2、升序 ,通过 asc ,默认行为
降序 ,通过 desc

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
#一、按单个字段排序

#案例1:将员工编号>120的员工信息进行工资的升序
SELECT *
FROM employees

ORDER BY salary ;

#案例1:将员工编号>120的员工信息进行工资的降序
SELECT *
FROM employees
WHERE employee_id>120
ORDER BY salary DESC;

#二、按表达式排序
#案例1:对有奖金的员工,按年薪降序

SELECT *,salary*12*(1+IFNULL(commission_pct,0)) 年薪
FROM employees
WHERE commission_pct IS NOT NULL
ORDER BY salary*12*(1+IFNULL(commission_pct,0)) DESC;


#三、按别名排序
#案例1:对有奖金的员工,按年薪降序

SELECT *,salary*12*(1+IFNULL(commission_pct,0)) 年薪
FROM employees

ORDER BY 年薪 DESC;

#四、按函数的结果排序

#案例1:按姓名的字数长度进行升序


SELECT last_name
FROM employees
ORDER BY LENGTH(last_name);


#五、按多个字段排序

#案例1:查询员工的姓名、工资、部门编号,先按工资升序,再按部门编号降序

SELECT last_name,salary,department_id
FROM employees
ORDER BY salary ASC,department_id DESC;


#六、补充选学:按列数排序


SELECT * FROM employees
ORDER BY 2 DESC;


SELECT * FROM employees
ORDER BY first_name;

函数

函数:类似于java中学过的“方法”,
为了解决某个问题,将编写的一系列的命令集合封装在一起,对外仅仅暴露方法名,供外部调用

1、自定义方法(函数)
2、调用方法(函数)★
叫什么 :函数名
干什么 :函数功能

单行函数
  • 字符函数
    concat
    substr
    length(str)
    char_length
    upper
    lower
    trim
    left
    right
    lpad
    rpad
    instr
    strcmp

  • 数学函数
    abs
    ceil
    floor
    round
    truncate
    mod

  • 日期函数
    now
    curtime
    curdate
    datediff
    date_format
    str_to_date

  • 流程控制函数
    if
    case

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
1、CONCAT 拼接字符

SELECT CONCAT('hello,',first_name,last_name) 备注 FROM employees;

2、LENGTH 获取字节长度

SELECT LENGTH('hello,郭襄');

3CHAR_LENGTH 获取字符个数
SELECT CHAR_LENGTH('hello,郭襄');

4、SUBSTRING 截取子串
/*
注意:起始索引从1开始!!!
substr(str,起始索引,截取的字符长度)
substr(str,起始索引)
*/
SELECT SUBSTR('张三丰爱上了郭襄',1,3);
SELECT SUBSTR('张三丰爱上了郭襄',7);

5、INSTR获取字符第一次出现的索引

SELECT INSTR('三打白骨精aaa白骨精bb白骨精','白骨精');

6、TRIM去前后指定的字符,默认是去空格


SELECT TRIM(' 虚 竹 ') AS a;
SELECT TRIM('x' FROM 'xxxxxx虚xxx竹xxxxxxxxxxxxxxxxxx') AS a;

7、LPAD/RPAD 左填充/右填充
SELECT LPAD('木婉清',10,'a');
SELECT RPAD('木婉清',10,'a');

8、UPPER/LOWER 变大写/变小写

#案例:查询员工表的姓名,要求格式:姓首字符大写,其他字符小写,名所有字符大写,且姓和名之间用_分割,最后起别名“OUTPUT”


SELECT UPPER(SUBSTR(first_name,1,1)),first_name FROM employees;
SELECT LOWER(SUBSTR(first_name,2)),first_name FROM employees;
SELECT UPPER(last_name) FROM employees;

SELECT CONCAT(UPPER(SUBSTR(first_name,1,1)),LOWER(SUBSTR(first_name,2)),'_',UPPER(last_name)) "OUTPUT"
FROM employees;

9、STRCMP 比较两个字符大小

SELECT STRCMP('aec','aec');


10LEFT/RIGHT 截取子串
SELECT LEFT('鸠摩智',1);
SELECT RIGHT('鸠摩智',1);


#二、数学函数

1、ABS 绝对值
SELECT ABS(-2.4);
2、CEIL 向上取整 返回>=该参数的最小整数
SELECT CEIL(-1.09);
SELECT CEIL(0.09);
SELECT CEIL(1.00);

3、FLOOR 向下取整,返回<=该参数的最大整数
SELECT FLOOR(-1.09);
SELECT FLOOR(0.09);
SELECT FLOOR(1.00);

4、ROUND 四舍五入
SELECT ROUND(1.8712345);
SELECT ROUND(1.8712345,2);

5TRUNCATE 截断

SELECT TRUNCATE(1.8712345,1);

6、MOD 取余

SELECT MOD(-10,3);
a%b = a-(INT)a/b*b
-10%3 = -10 - (-10)/3*3 = -1

SELECT -10%3;
SELECT 10%3;
SELECT -10%-3;
SELECT 10%-3;


#三、日期函数


1、NOW
SELECT NOW();

2、CURDATE

SELECT CURDATE();

3、CURTIME
SELECT CURTIME();


4、DATEDIFF
SELECT DATEDIFF('1998-7-16','2019-7-13');

5、DATE_FORMAT

SELECT DATE_FORMAT('1998-7-16','%Y年%M月%d日 %H小时%i分钟%s秒') 出生日期;



SELECT DATE_FORMAT(hiredate,'%Y年%M月%d日 %H小时%i分钟%s秒')入职日期
FROM employees;



6、STR_TO_DATE 按指定格式解析字符串为日期类型
SELECT * FROM employees
WHERE hiredate<STR_TO_DATE('3/15 1998','%m/%d %Y');


#四、流程控制函数


1、IF函数

SELECT IF(100>9,'好','坏');


#需求:如果有奖金,则显示最终奖金,如果没有,则显示0
SELECT IF(commission_pct IS NULL,0,salary*12*commission_pct) 奖金,commission_pct
FROM employees;



2CASE函数

①情况1 :类似于switch语句,可以实现等值判断
CASE 表达式
WHEN1 THEN 结果1
WHEN2 THEN 结果2
...
ELSE 结果n
END


案例:
部门编号是30,工资显示为2
部门编号是50,工资显示为3
部门编号是60,工资显示为4
否则不变

显示 部门编号,新工资,旧工资

SELECT department_id,salary,
CASE department_id
WHEN 30 THEN salary*2
WHEN 50 THEN salary*3
WHEN 60 THEN salary*4
ELSE salary
END newSalary
FROM employees;


②情况2:类似于多重IF语句,实现区间判断
CASE
WHEN 条件1 THEN 结果1
WHEN 条件2 THEN 结果2
...

ELSE 结果n

END



案例:如果工资>20000,显示级别A
工资>15000,显示级别B
工资>10000,显示级别C
否则,显示D

SELECT salary,
CASE
WHEN salary>20000 THEN 'A'
WHEN salary>15000 THEN 'B'
WHEN salary>10000 THEN 'C'
ELSE 'D'
END
AS a
FROM employees;
分组函数

说明:分组函数往往用于实现将一组数据进行统计计算,最终得到一个值,又称为聚合函数或统计函数

分组函数清单:

sum(字段名):求和
avg(字段名):求平均数
max(字段名):求最大值
min(字段名):求最小值
count(字段名):计算非空字段值的个数

特点:

sum,avg 一般处理数值类型,max,min,count 可以处理任意类型,以上分组函数都忽略null值。

可以和 distinct 搭配实现去重的运算

count函数一般使用count(*)用作统计行数

和分组函数一同查询的字段有限制,只能是 group by 后面的函数

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
#案例1 :查询员工信息表中,所有员工的工资和、工资平均值、最低工资、最高工资、有工资的个数

SELECT SUM(salary),AVG(salary),MIN(salary),MAX(salary),COUNT(salary) FROM employees;

#案例2:添加筛选条件
#①查询emp表中记录数:
SELECT COUNT(employee_id) FROM employees;

#②查询emp表中有佣金的人数:

SELECT COUNT(salary) FROM employees;


#③查询emp表中月薪大于2500的人数:
SELECT COUNT(salary) FROM employees WHERE salary>2500;


#④查询有领导的人数:
SELECT COUNT(manager_id) FROM employees;


#count的补充介绍★


#1、统计结果集的行数,推荐使用count(*)

SELECT COUNT(*) FROM employees;
SELECT COUNT(*) FROM employees WHERE department_id = 30;


SELECT COUNT(1) FROM employees;
SELECT COUNT(1) FROM employees WHERE department_id = 30;


#2、搭配distinct实现去重的统计

#需求:查询有员工的部门个数

SELECT COUNT(DISTINCT department_id) FROM employees;


#思考:每个部门的总工资、平均工资?

SELECT SUM(salary) FROM employees WHERE department_id = 30;
SELECT SUM(salary) FROM employees WHERE department_id = 50;


SELECT SUM(salary) ,department_id
FROM employees
GROUP BY department_id;

分组查询

select 查询列表
from 表名
where 筛选条件
group by 分组列表
having 分组后筛选
order by 排序列表;

执行顺序:
①from子句
②where子句
③group by 子句
④having子句
⑤select子句
⑥order by子句

特点:
①查询列表往往是 分组函数和被分组的字段 ★
②分组查询中的筛选分为两类
筛选的基表 使用的关键词 位置
分组前筛选 原始表 where group by 的前面

分组后筛选 分组后的结果集 having group by的后面

where——group by ——having

问题:分组函数做条件只可能放在having后面!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#1)简单的分组
#案例1:查询每个工种的员工平均工资

SELECT AVG(salary),job_id
FROM employees
GROUP BY job_id;

#案例2:查询每个领导的手下人数

SELECT COUNT(*),manager_id
FROM employees
WHERE manager_id IS NOT NULL
GROUP BY manager_id;





#2)可以实现分组前的筛选
#案例1:查询邮箱中包含a字符的 每个部门的最高工资
SELECT MAX(salary) 最高工资,department_id
FROM employees
WHERE email LIKE '%a%'
GROUP BY department_id;


#案例2:查询每个领导手下有奖金的员工的平均工资
SELECT AVG(salary) 平均工资,manager_id
FROM employees
WHERE commission_pct IS NOT NULL
GROUP BY manager_id;


#3)可以实现分组后的筛选
#案例1:查询哪个部门的员工个数>5
#分析1:查询每个部门的员工个数
SELECT COUNT(*) 员工个数,department_id
FROM employees
GROUP BY department_id

#分析2:在刚才的结果基础上,筛选哪个部门的员工个数>5

SELECT COUNT(*) 员工个数,department_id
FROM employees

GROUP BY department_id
HAVING COUNT(*)>5;


#案例2:每个工种有奖金的员工的最高工资>12000的工种编号和最高工资

SELECT job_id,MAX(salary)
FROM employees
WHERE commission_pct IS NOT NULL
GROUP BY job_id
HAVING MAX(salary)>12000;


#案例3:领导编号>102的 每个领导手下的最低工资大于5000的最低工资
#分析1:查询每个领导手下员工的最低工资
SELECT MIN(salary) 最低工资,manager_id
FROM employees
GROUP BY manager_id;

#分析2:筛选刚才1的结果
SELECT MIN(salary) 最低工资,manager_id
FROM employees
WHERE manager_id>102
GROUP BY manager_id
HAVING MIN(salary)>5000 ;




#4)可以实现排序
#案例:查询没有奖金的员工的最高工资>6000的工种编号和最高工资,按最高工资升序
#分析1:按工种分组,查询每个工种有奖金的员工的最高工资
SELECT MAX(salary) 最高工资,job_id
FROM employees
WHERE commission_pct IS NULL
GROUP BY job_id


#分析2:筛选刚才的结果,看哪个最高工资>6000
SELECT MAX(salary) 最高工资,job_id
FROM employees
WHERE commission_pct IS NULL
GROUP BY job_id
HAVING MAX(salary)>6000


#分析3:按最高工资升序
SELECT MAX(salary) 最高工资,job_id
FROM employees
WHERE commission_pct IS NULL
GROUP BY job_id
HAVING MAX(salary)>6000
ORDER BY MAX(salary) ASC;


#5)按多个字段分组
#案例:查询每个工种每个部门的最低工资,并按最低工资降序
#提示:工种和部门都一样,才是一组

工种 部门 工资
1 10 10000
1 20 2000
2 20
3 20
1 10
2 30
2 20


SELECT MIN(salary) 最低工资,job_id,department_id
FROM employees
GROUP BY job_id,department_id;

链接查询

说明:又称多表查询,当查询语句涉及到的字段来自于多个表时,就会用到连接查询

笛卡尔乘积现象:表1 有m行,表2有n行,结果=m*n行

发生原因:没有有效的连接条件
如何避免:添加有效的连接条件

  • 分类:

    • 按年代分类:
      • sql92标准:仅仅支持内连接
        • 内连接:
          • 等值连接
          • 非等值连接
          • 自连接
      • sql99标准【推荐】:支持内连接+外连接(左外和右外)+交叉连接
    • 按功能分类:
      • 内连接:
        • 等值连接
        • 非等值连接
        • 自连接
      • 外连接:
        • 左外连接
        • 右外连接
        • 全外连接
      • 交叉连接
内链接

语法:
select 查询列表
from 表1 别名,表2 别名
where 连接条件
and 筛选条件
group by 分组列表
having 分组后筛选
order by 排序列表

执行顺序:

1、from子句
2、where子句
3、and子句
4、group by子句
5、having子句
6、select子句
7、order by子句

SQL92和SQL99的区别:
SQL99,使用JOIN关键字代替了之前的逗号,并且将连接条件和筛选条件进行了分离,提高阅读性!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
语法:
SELECT 查询列表
FROM 表名1 别名
INNERJOIN 表名2 别名
ON 连接条件
WHERE 筛选条件
GROUP BY 分组列表
HAVING 分组后筛选
ORDER BY 排序列表;

#一)等值连接
#①简单连接
#案例:查询员工名和部门名

SELECT last_name,department_name
FROM departments d
JOIN employees e
ON e.department_id =d.department_id;



#②添加筛选条件
#案例1:查询部门编号>100的部门名和所在的城市名
SELECT department_name,city
FROM departments d
JOIN locations l
ON d.`location_id` = l.`location_id`
WHERE d.`department_id`>100;


#③添加分组+筛选
#案例1:查询每个城市的部门个数

SELECT COUNT(*) 部门个数,l.`city`
FROM departments d
JOIN locations l
ON d.`location_id`=l.`location_id`
GROUP BY l.`city`;




#④添加分组+筛选+排序
#案例1:查询部门中员工个数>10的部门名,并按员工个数降序

SELECT COUNT(*) 员工个数,d.department_name
FROM employees e
JOIN departments d
ON e.`department_id`=d.`department_id`
GROUP BY d.`department_id`
HAVING 员工个数>10
ORDER BY 员工个数 DESC;


#二)非等值连接

#案例:查询部门编号在10-90之间的员工的工资级别,并按级别进行分组
SELECT * FROM sal_grade;


SELECT COUNT(*) 个数,grade
FROM employees e
JOIN sal_grade g
ON e.`salary` BETWEEN g.`min_salary` AND g.`max_salary`
WHERE e.`department_id` BETWEEN 10 AND 90
GROUP BY g.grade;




#三)自连接

#案例:查询员工名和对应的领导名

SELECT e.`last_name`,m.`last_name`
FROM employees e
JOIN employees m
ON e.`manager_id`=m.`employee_id`;
外连接

说明:查询结果为主表中所有的记录,如果从表有匹配项,则显示匹配项;如果从表没有匹配项,则显示 null

应用场景:一般用于查询主表中有但从表没有的记录

特点:

1、外连接分主从表,两表的顺序不能任意调换
2、左连接的话,left join 左边为主表
右连接的话,right join 右边为主表

语法:

select 查询列表
from 表 1 别名
left|right|full 【outer】 join 表 2 别名
on 连接条件
where 筛选条件;

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
#案例1:查询所有女神记录,以及对应的男神名,如果没有对应的男神,则显示为null

#左连接
SELECT b.*,bo.*
FROM beauty b
LEFT JOIN boys bo ON b.`boyfriend_id` = bo.`id`;

#右连接
SELECT b.*,bo.*
FROM boys bo
RIGHT JOIN beauty b ON b.`boyfriend_id` = bo.`id`;



#案例2:查哪个女生没有男朋友

#左连接
SELECT b.`name`
FROM beauty b
LEFT JOIN boys bo ON b.`boyfriend_id` = bo.`id`
WHERE bo.`id` IS NULL;

#右连接
SELECT b.*,bo.*
FROM boys bo
RIGHT JOIN beauty b ON b.`boyfriend_id` = bo.`id`
WHERE bo.`id` IS NULL;


#案例3:查询哪个部门没有员工,并显示其部门编号和部门名

SELECT COUNT(*) 部门个数
FROM departments d
LEFT JOIN employees e ON d.`department_id` = e.`department_id`
WHERE e.`employee_id` IS NULL;

子查询

说明:当一个查询语句中又嵌套了另一个完整的 select 语句,则被嵌套的 select 语句称为子查询或内查询
外面的 select 语句称为主查询或外查询。

分类:

按子查询出现的位置进行分类:

1、select 后面
要求:子查询的结果为单行单列(标量子查询)
2、from 后面
要求:子查询的结果可以为多行多列
3、where 或 having 后面 ★
要求:子查询的结果必须为单列
单行子查询
多行子查询
4、exists 后面
要求:子查询结果必须为单列(相关子查询)
特点:
1、子查询放在条件中,要求必须放在条件的右侧
2、子查询一般放在小括号中
3、子查询的执行优先于主查询
4、单行子查询对应了 单行操作符:> < >= <= = <>
多行子查询对应了 多行操作符:any/some all in

单行子查询
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
#一)单行子查询

#案例1:谁的工资比 Abel 高?


#①查询Abel的工资
SELECT salary
FROM employees
WHERE last_name = 'Abel'
#②查询salary>①的员工信息
SELECT last_name,salary
FROM employees
WHERE salary>(
SELECT salary
FROM employees
WHERE last_name <> 'Abel'

);

#案例2:返回job_id与141号员工相同,salary比143号员工多的员工姓名,job_id 和工资
#①查询141号员工的job_id
SELECT job_id
FROM employees
WHERE employee_id = 141

#②查询143号员工的salary

SELECT salary
FROM employees
WHERE employee_id = 143

#③查询job_id=① and salary>②的信息
SELECT last_name,job_id,salary
FROM employees
WHERE job_id = (
SELECT job_id
FROM employees
WHERE employee_id = 141
) AND salary>(

SELECT salary
FROM employees
WHERE employee_id = 143

);
多行子查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
/*
in:判断某字段是否在指定列表内
x in(10,30,50)


any/some:判断某字段的值是否满足其中任意一个

x>any(10,30,50)
x>min()

x=any(10,30,50)
x in(10,30,50)

all:判断某字段的值是否满足里面所有的

x >all(10,30,50)
x >max()

*/


#案例1:返回location_id是1400或1700的部门中的所有员工姓名

#①查询location_id是1400或1700的部门
SELECT department_id
FROM departments
WHERE location_id IN(1400,1700)


#②查询department_id = ①的姓名
SELECT last_name
FROM employees
WHERE department_id IN(
SELECT DISTINCT department_id
FROM departments
WHERE location_id IN(1400,1700)

);



#题目:返回其它部门中比job_id为‘IT_PROG’部门任一工资低的员工的员工号、姓名、job_id 以及salary

#①查询job_id为‘IT_PROG’部门的工资
SELECT DISTINCT salary
FROM employees
WHERE job_id = 'IT_PROG'


#②查询其他部门的工资<任意一个①的结果

SELECT employee_id,last_name,job_id,salary
FROM employees
WHERE salary<ANY(

SELECT DISTINCT salary
FROM employees
WHERE job_id = 'IT_PROG'


);



等价于

SELECT employee_id,last_name,job_id,salary
FROM employees
WHERE salary<(

SELECT MAX(salary)
FROM employees
WHERE job_id = 'IT_PROG'


);




#案例3:返回其它部门中比job_id为‘IT_PROG’部门所有工资都低的员工 的员工号、姓名、job_id 以及salary

#①查询job_id为‘IT_PROG’部门的工资
SELECT DISTINCT salary
FROM employees
WHERE job_id = 'IT_PROG'


#②查询其他部门的工资<所有①的结果

SELECT employee_id,last_name,job_id,salary
FROM employees
WHERE salary<ALL(

SELECT DISTINCT salary
FROM employees
WHERE job_id = 'IT_PROG'


);



等价于

SELECT employee_id,last_name,job_id,salary
FROM employees
WHERE salary<(

SELECT MIN(salary)
FROM employees
WHERE job_id = 'IT_PROG'


);


#二、放在select后面

#案例;查询部门编号是50的员工个数

SELECT
(
SELECT COUNT(*)
FROM employees
WHERE department_id = 50
) 个数;


#三、放在from后面

#案例:查询每个部门的平均工资的工资级别
#①查询每个部门的平均工资

SELECT AVG(salary),department_id
FROM employees
GROUP BY department_id



#②将①和sal_grade两表连接查询

SELECT dep_ag.department_id,dep_ag.ag,g.grade
FROM sal_grade g
JOIN (

SELECT AVG(salary) ag,department_id
FROM employees
GROUP BY department_id

) dep_ag ON dep_ag.ag BETWEEN g.min_salary AND g.max_salary;


#四、放在exists后面

#案例1 :查询有无名字叫“张三丰”的员工信息
SELECT EXISTS(
SELECT *
FROM employees
WHERE last_name = 'Abel'

) 有无Abel;


#案例2:查询没有女朋友的男神信息

USE girls;

SELECT bo.*
FROM boys bo
WHERE bo.`id` NOT IN(
SELECT boyfriend_id
FROM beauty b
)



SELECT bo.*
FROM boys bo
WHERE NOT EXISTS(
SELECT boyfriend_id
FROM beauty b
WHERE bo.id = b.boyfriend_id
);

React v16 源码分析 ② 设计理念

状态渲染 UI

1
UI = react(state);

React 程序设计哲学

  • 将设计好的 UI 划分为组件层级
  • 确定 UI state 的最小(且完整)表示
  • 确定 state 放置的位置
  • 添加反向数据流,低层层级组件更新高层级组件状态

使用组合而不是继承

Props 和组合为你提供了清晰而安全地定制组件外观和行为的灵活方式。注意:组件可以接受任意 props,包括基本数据类型,React 元素以及函数。

如果你想要在组件间复用非 UI 的功能,我们建议将其提取为一个单独的 JavaScript 模块,如函数、对象或者类。组件可以直接引入(import)而无需通过 extend 继承它们。

Fiber

Fiber 其实就是 Virtual DOM 的一种实现,相比于通过 React.createElement 创建的 Virtual DOM,Fiber 在此基础上添加了更多的属性,例如 return, current 等指针,用于将 Fiber 对象链接为链表。最终形成一颗树状结构,也就是 Fiber 树,他对应着真实 DOM 树的结构。

而 Fiber 对象上的属性还不止这些,还有像 updateQueue 更新队列等属性,但到目前位置知道 Fiber 是对 DOM 树的一种描述,已经足够了。而让 React 设计 Fiber 的原因,则是因为下面的协调过程。

协调 reconciler

这一概念应该是当我们对 React 执行过程深入思考的时候最容易想到的一部分,通过 JSX 创建的 Virtual DOM 如何与真实的 DOM 同步,真实 DOM 属性改变的时候,又如何被记录到 Virtual DOM 上,这个过程就叫做协调

reconciler 模块,用于处理协调相关的事务。Diff 算法也在这个期间发生。

React15 之前的协调过程是同步的,也叫 stack reconciler。

JS 的执行是单线程的,由于浏览器器触发的事件(用户交互触发的事件回调)是一个宏任务,所以会等待同步任务执行完成,在更新比较耗时的任务时,会阻塞用户的交互。

也许会考虑将耗时任务放到异步任务中执行,但最终还是会回到主线程中执行,所以比较好的解决办法就是任务分割,当其他优先级比较高的任务到来时,将正在执行的任务打断让出执行权。之后再从中断的部分开始异步执行剩下的计算。

为了将老的同步更新的架构变为异步可中断更新,所以需要一套数据结构让它既能对应真实的 dom 又能作为分隔的单元,这就是 Fiber。

Scheduler

有了 Fiber,就需要用浏览器的时间片异步执行这些 Fiber 的工作单元,浏览器有一个 api 叫做 requestIdleCallback,它可以在浏览器空闲的时候执行一些任务,我们用这个 api 执行 react 的更新,让高优先级的任务优先响应不就可以了吗,但事实是 requestIdleCallback 存在着浏览器的兼容性和触发不稳定的问题,所以我们需要用 js 实现一套时间片运行的机制,在 react 中这部分叫做 scheduler。

下面用伪代码理解一下 分割,异步执行,让出执行权

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let firstFiber; // 代表Fiber树的头节点
let nextFiber = firstFiber; // 用于遍历子节点

function performUnitOfWork() {
// 处理节点相关逻辑
return nextFiber.next; // 返回下一个节点
}

function workLoop(deadline) {
while (nextFiber && !shouldYield) {
nextFiber = performUnitOfWork();
// 如果没有剩余时间处理下一个节点
// 则暂停执行,让出主线程,给优先级更高的任务
shouldYield = deadline < 1;
}
requestIdleCallback(workLoop);
}
requestIdleCallback(workLoop);

为什么不使用统的异步控制:

  • setTimeout setTimeout 在嵌套超过 5 层之后有默认 4ms 的延时
  • requestFrameAnimation 执行时机不确定,chrome 和 firefox 是在渲染前执行,safari 是在渲染之后执行。
  • promise 微任务会在主进程执行结束后释放掉有所得微任务,不能控制什么时候需要执行。

Lane

有了异步调度,我们还需要细粒度的管理各个任务的优先级,让高优先级的任务优先执行,各个 Fiber 工作单元还能比较优先级,相同优先级的任务可以一起更新。

代数效应

(algebraic effects) 可能翻译成 可以当做参数传递的副作用 更容易理解。 它是函数式编程中的一个概念,用于将副作用从函数调用中分离。

从实用的角度上举例,假如我们有这样一段代码,其主要目的是进行一大段精妙的运算:

1
2
3
4
5
6
7
async function biz(id) {
const infoId = /* do some calc */ id; // 这里可以理解为是一大段计算逻辑
const info = await getInfo(infoId); // 副作用,与 server 通信
const dataId = /* do some calc */ info.dataId; // 这里可以理解为是一大段计算逻辑
const data = getData(dataId); // 副作用,非幂等操作
return /* do some calc */ data.finalCalcData; // 这里可以理解为是一大段计算逻辑
}

尽管运算逻辑很优美,但美中不足的是有两段副作用,导致它不能成为一个干净的纯函数被单元测试。而且这里会导致严重的逻辑耦合:『做什么』与『怎么做』没有拆的很干净:你的一大段计算逻辑是在处理做什么;两个副作用更关心怎么做:比如线上是接口调用,单测里是 mock 数据;但是由于这两块副作用代码,导致整个糅杂的逻辑都无法复用。直接把两个副作用传进来不就行了?

1
2
3
4
5
6
7
async function biz(id, getInfo, getData) {
const infoId = /* do some calc */ id; // 这里可以理解为是一大段计算逻辑
const info = await getInfo(infoId); // 副作用,与 server 通信
const dataId = /* do some calc */ info.dataId; // 这里可以理解为是一大段计算逻辑
const data = getData(dataId); // 副作用,非幂等操作
return /* do some calc */ data.finalCalcData; // 这里可以理解为是一大段计算逻辑
}

是的,这样确实可以复用,但还有一个叫函数染色的问题没有解决:明明是一大段干净的同步运算逻辑,因为 getInfo 是异步的,导致整个函数都得加个 async。而且很有可能在我单元测试里,这个 getInfo 是直接同步取内存数据,还得因此弄个 Promise……这时候如果 JS 里有这样一种语法就好了:

当函数执行到perform的时候,会被暂停,并被handle捕获,当异步执行的结果被返回,函数在继续执行

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

function biz(id) {
const infoId = /* do some calc */ id; // 这里可以理解为是一大段计算逻辑
const info = perform { type: 'getInfo', payload: infoId };
const dataId = /* do some calc */ info.dataId; // 这里可以理解为是一大段计算逻辑
const data = perform { type: 'getData', payload: dataId };
return /* do some calc */ data.finalCalcData; // 这里可以理解为是一大段计算逻辑
}

// 正常业务逻辑
async function runBiz() {
try {
biz();
} handle(effect) {
if (effect.type === 'getInfo') {
resume await getInfo(effect.payload);
} else if (effect.type === 'getData') {
resume await getData(effect.payload)
}
}
}

// 单元测试逻辑
function testBiz() {
try {
biz();
} handle(effect) {
if (effect.type === 'getInfo') {
resume testInfo;
} else if (effect.type === 'getData') {
resume testData;
}
}
}

分离副作用在函数编程中非常常见,redux-saga也会将副作用分离出来,只负责发起请求

1
2
3
4
5
6
7
8
function* fetchUser(action) {
try {
const user = yield call(Api.fetchUser, action.payload);
yield put({ type: "SUCCESS", user: user });
} catch (err) {
yield put({ type: "ERROR" });
}
}

这样业务逻辑代码即摆脱了副作用,完成了做什么与怎么做的解耦;又完全不必担心异步副作用带来的染色问题,可以愉快的单测和复用了。Suspense 也是这种概念的延伸:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const ProductResource = createResource(fetchProduct);

const Product = (props) => {
const p = ProductResource.read(
// 用同步的方式来编写异步代码!
props.id
);
return <h3>{p.price}</h3>;
};

function App() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<Product id={123} />
</Suspense>
</div>
);
}

可以看到 ProductResource.read 完全是同步的写法,把获取数据的部分完全分离出了 Product 组件之外。在源码中, ProductResource.read 会在获取数据之前会 throw 一个特殊的 Promise, 由于 scheduler 的存在, scheduler 可以捕获这个 promise,暂停更新等数据获取之后交还执行权。ProductResource 可以是 localStorage 甚至是 redismysql 等数据库,也就是组件即服务,可能以后会有 server Component 的出现。

MySQl 服务基础

MySQL 服务启动和停止

查看系统内是否有 mysql 服务

1
ps aux | grep mysql

停止 mysql 服务

1
sudo service mysql stop

启动 mysql 服务

1
sudo service mysql start

重启 mysql 服务

1
sudo service mysql restart

登录与退出

登录命令,属性和属性值之间可以省略空格

1
mysql -h主机名 -P端口号 -u有户名 -p密码

退出 exit

常见命令

查看当前所有数据库

1
show databases;

打开指定的库

1
use 库名;

查看当前所在库

1
select database();

查看所有表

1
show tables;

查看其他库的所有表

1
show tables from 库名;

创建表

1
2
3
4
create table 表名 {
列名 列类型,
...
}

查看表结构

1
desc 表名;

查看表结构

1
2
3
4
5
6
7

// 进入mysql服务器中
select version();

// 在命令行中
mysql --version
mysql -V

语法规范

  • 不区分大小写,建议关键字大写,表名,列名小写
  • 每条命令;结尾
  • 命令可以换行
  • 注释
    单行注释: #注释文字
    单行注释: -- 注释文字
    多行注释: /* 注释文字 */
  • Copyrights © 2015-2026 SunZhiqi

此时无声胜有声!

支付宝
微信