Qeuroal's Blog

静幽正治

概述

Linux 简介

Linux 内核最初只是由芬兰人林纳斯·托瓦兹(Linus Torvalds)在赫尔辛基大学上学时出于个人爱好而编写的。

Linux 是一套免费使用和自由传播的类 Unix 操作系统,是一个基于 POSIX(可移植操作系统接口) 和 UNIX多用户多任务支持多线程CPU 的操作系统。

Linux 能运行主要的 UNIX 工具软件、应用程序和网络协议。它支持 32 位和 64 位硬件。Linux 继承了 Unix网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。

Linux 发行版

Linux 的发行版说简单点就是将 Linux 内核与应用软件做一个打包。

目前市面上较知名的发行版有:UbuntuRedHatCentOSDebianFedoraSuSEOpenSUSEArch LinuxSolusOSkali(安全渗透测试使用) 等。

Linux-distribution

今天各种场合都有使用各种 Linux 发行版,从嵌入式设备到超级计算机,并且在服务器领域确定了地位,通常服务器使用 LAMPLinux + Apache + MySQL + PHP)或 LNMPLinux + Nginx+ MySQL + PHP)组合。

目前 Linux 不仅在家庭与企业中使用,并且在政府中也很受欢迎。

  • 巴西联邦政府由于支持 Linux 而世界闻名。
  • 有新闻报道俄罗斯军队自己制造的 Linux 发布版的,做为 G.H.ost 项目已经取得成果。
  • 印度的 Kerala 联邦计划在向全联邦的高中推广使用 Linux
  • 中华人民共和国为取得技术独立,在龙芯处理器中排他性地使用 Linux
  • 在西班牙的一些地区开发了自己的 Linux 发布版,并且在政府与教育领域广泛使用,如 Extremadura 地区的 gnuLinEx 和 Andalusia 地区的 Guadalinex
  • 葡萄牙同样使用自己的 Linux 发布版 Caixa Mágica,用于 Magalh?es 笔记本电脑和 e-escola 政府软件。
  • 法国和德国同样开始逐步采用 Linux

Linux vs Windows

环境搭建

Linux 的安装,安装步骤比较繁琐(操作系统本身也是一个软件),现在其实云服务器挺普遍的,价格也便宜,如果直接不想搭建,也可以直接买一台学习用用!

安装CentOS(不推荐)

虚拟机安装,耗资源,在本地安装,这个不建议;

Linux 是一个操作系统,你也可以把自己电脑安装成双系统!建议使用虚拟机(VM

  1. 可以通过镜像进行安装!下载地址教程

  2. 可以使用已经制作好的镜像!CentOS7网盘地址:链接,提取码:76x5,登录密码: 123456

  3. VMware 虚拟机软件,然后打开镜像即可使用!

  4. 安装完成后,有如下界面:

  5. VMware 使用方式

    • 点击屏幕进入虚拟机;
    • ctrl+alt将聚焦退出虚拟机;

购买云服务器(推荐)

虚拟机安装后占用空间,也会有些卡顿,我们作为程序员其实可以选择购买一台自己的服务器,这样的话更加接近真实线上工作;

  1. 阿里云购买服务器:点击这里

  2. 购买完毕后,获取服务器的ip地址,重置服务器密码,就可以远程登录了

  3. 下载 xShell 工具,进行远程连接使用!连接成功效果如下:

注意事项

如果要打开端口,需要在阿里云的安全组面板中开启对应的出入规则,不然的话会被阿里拦截!

新手

如果前期不好操作,可以推荐安装宝塔面板,傻瓜式管理服务器

安装教程:点击这里

  1. 开启对应的端口

  2. 一键安装

  3. 安装完毕后会得到远程面板的地址,账号,密码,就可以登录了

  4. 登录之后就可以可视化的安装环境和部署网站!

走近Linux系统

开机登录

开机会启动许多程序。它们在Windows叫做”服务“(service),在Linux就叫做”守护进程“(daemon)。

开机成功后,它会显示一个文本登录界面,这个界面就是我们经常看到的登录界面,在这个登录界面中会提示用户输入用户名,而用户输入的用户将作为参数传给login程序来验证用户的身份,密码是不显示的,输完回车即可!

一般来说,用户的登录方式有三种:

  • 命令行登录
  • ssh登录
  • 图形界面登录

最高权限账户为 root,可以操作一切!

关机

linux领域内大多用在服务器上,很少遇到关机的操作。毕竟服务器上跑一个服务是永无止境的,除非特殊情况下,不得已才会关机。

关机指令为:shutdown

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
sync # 将数据由内存同步到硬盘中。

shutdown # 关机指令,你可以man shutdown 来看一下帮助文档。例如你可以运行如下命令关机:

shutdown –h 10 # 这个命令告诉大家,计算机将在10分钟后关机

shutdown –h now # 立马关机

shutdown –h 20:25 # 系统会在今天20:25关机

shutdown –h +10 # 十分钟后关机

shutdown –r now # 系统立马重启

shutdown –r +10 # 系统十分钟后重启

reboot # 就是重启,等同于 shutdown –r now

halt # 关闭系统,等同于shutdown –h now 和 poweroff

最后总结一下,不管是重启系统还是关闭系统,首先要运行 sync 命令,把内存中的数据写到磁盘中。

系统目录结构

登录系统后,在当前命令窗口下输入命令:

1
ls /

你会看到如下图所示:

树状目录结构:(Linux的一切资源都挂载在这个 / 根节点下)

目录 说明 备注
/bin binBinary的缩写, 这个目录存放着最经常使用的命令。 不要动
/boot 这里存放的是启动Linux时使用的一些核心文件,包括一些连接文件以及镜像文件。 不要动
/dev devDevice(设备)的缩写, 存放的是Linux的外部设备,在Linux中访问设备的方式和访问文件的方式是相同的。 挂载第三方设备
/etc 这个目录用来存放所有的系统管理所需要的配置文件和子目录。
/home 用户的主目录,在Linux中,每个用户都有一个自己的目录,一般该目录名是以用户的账号命名的。
/lib 这个目录里存放着系统最基本的动态连接共享库,其作用类似于Windows里的DLL文件。 不要动
/lost+found 这个目录一般情况下是空的,当系统非法关机后,这里就存放了一些文件。 存放突然关机的一些文件
/media linux系统会自动识别一些设备,例如U盘、光驱等等,当识别后,Linux会把识别的设备挂载到这个目录下。
/mnt 系统提供该目录是为了让用户临时挂载别的文件系统的,我们可以将光驱挂载在/mnt/上,然后进入该目录就可以查看光驱里的内容了。 挂载第三方设备,我们一般会把一些本地文件挂载在这个目录下
/opt 这是给主机额外安装软件所摆放的目录。比如你安装一个ORACLE数据库则就可以放到这个目录下。默认是空的。
/proc 这个目录是一个虚拟的目录,它是系统内存的映射,我们可以通过直接访问这个目录来获取系统信息。 不用管
/root 该目录为系统管理员,也称作超级权限者的用户主目录。
/sbin s就是Super User的意思,这里存放的是系统管理员使用的系统管理程序。
/srv 该目录存放一些服务启动之后需要提取的数据。
/sys 这是linux2.6内核的一个很大的变化。该目录下安装了2.6内核中新出现的一个文件系统 sysfs
/tmp 这个目录是用来存放一些临时文件的。 用完即丢的文件,可以放在这个目录下,如:安装包
/usr 这是一个非常重要的目录,用户的很多应用程序和文件都放在这个目录下,类似于windows下的program files目录。
/usr/bin 系统用户使用的应用程序。
/usr/sbin 超级用户使用的比较高级的管理程序和系统守护程序。
/usr/src 内核源代码默认的放置目录。
/var 这个目录中存放着在不断扩充着的东西,我们习惯将那些经常被修改的目录放在这个目录下。包括各种日志文件。
/run 是一个临时文件系统,存储系统启动以来的信息。当系统重启时,这个目录下的文件应该被删掉或清除。
/www 存放服务器网站相关的资源,如:环境、网站项目等;

补充

  • 很多的大型项目都是部署在 Linux 服务器上;
  • 社会的生存法则:优胜劣汰;
  • 服务器都是使用命令行的,我们也是基于命令行来学习的;
  • Linux 中没有错误就代表操作成功;
  • 一切皆文件;
  • 根目录: /,所有的文件都挂载在这个节点下;

目录解释

  • /mnt: 后面会把一些本地文件挂载在这个目录下
  • /etc:配置文件:
  • /home
  • lost+found:存放 突然关机的一些文件
  • /opt:安装额外的软件
  • /root
  • /tmp:用完即丢

常用的基本命令

目录管理

  • 绝对路径:路径的全称,如:D:\anaconda3\condabin\xxx.xxx
  • 相对路径:比如说在 condabin 目录下,那这个 xxx.xxx 文件,对应我们的相对位置就是 ./xxx.xxx

cd:切换目录命令

  • cd 目录名:绝对路径:都是以 / 开头;相对路径:对于当前目录该如何寻找 .././

  • ./:当前目录

  • cd ..:返回上一级目录

ls:列出目录(常常被使用)

参数:

  • -a参数:all,查看全部文件,包括隐藏文件
  • -l参数:列出所有文件,包含文件的属性和权限,没有隐藏文件

实践:

pwd:显示当前用户所在的目录

mkdir:创建一个目录

rmdir:删除目录

  • rmdir 仅能删除空的目录,如果下面存在文件,需要先删除文件;递归删除多个目录加入 -p 参数即可

cp:复制文件或者目录

  • cp 原来的文件 目标目录或者目标目录/要改成的文件名

rm:移除文件或者目录!

参数:

  • -f:忽略不存在的文件,不会出现警告,强制删除;
  • -r:递归删除目录;
  • -i:互动,删除询问是否删除;

实践:

  • rm -rf /:系统中所有的文件就被删除了(删库跑路)

mv:移动文件或者目录;重命名文件

参数:

  • -f:强制
  • -u:只替换已经更新过的文件

实践:

基本属性

文件属性

root > user

Linux系统是一种典型的多用户系统,不同的用户处于不同的地位,拥有不同的权限。为了保护系统的安全性,Linux系统对不同的用户访问同一文件(包括目录文件)的权限做了不同的规定。

在Linux中我们可以使用ll或者ls –l命令来显示一个文件的属性以及文件所属的用户和组,如:

实例中,boot文件的第一个属性用”d“表示。”d“在Linux中代表该文件是一个目录文件。

Linux中第一个字符代表这个文件是目录、文件或链接文件等等:

  • 当为[ d ]则是目录
  • 当为[ - ]则是文件;
  • 若是[ l ]则表示为链接文档 ( link file )(类似于window的快捷方式);
  • 若是[ b ]则表示为装置文件里面的可供储存的接口设备 ( 可随机存取装置 );
  • 若是[ c ]则表示为装置文件里面的串行端口设备,例如键盘、鼠标 ( 一次性读取装置 )。

接下来的字符中,以三个为一组,且均为『rwx』 的三个参数的组合。

其中,[ r ]代表可读(read)、[ w ]代表可写(write)、[ x ]代表可执行(execute)。

要注意的是,这三个权限的位置不会改变,如果没有权限,就会出现减号[ - ]而已。

每个文件的属性由左边第一部分的10个字符来确定(如下图):

从左至右用0-9这些数字来表示。

第0位确定文件类型,第1-3位确定属主(该文件的所有者)拥有该文件的权限。第4-6位确定属组(所有者的同组用户)拥有该文件的权限,第7-9位确定其他用户拥有该文件的权限。

其中:

1、4、7位表示读权限,如果用”r“字符表示,则有读权限,如果用”-“字符表示,则没有读权限;

2、5、8位表示写权限,如果用”w“字符表示,则有写权限,如果用”-“字符表示没有写权限;

3、6、9位表示可执行权限,如果用”x“字符表示,则有执行权限,如果用”-“字符表示,则没有执行权限。

对于文件来说,它都有一个特定的所有者,也就是对该文件具有所有权的用户。

同时,在Linux系统中,用户是按组分类的,一个用户属于一个或多个组。

文件所有者以外的用户又可以分为文件所有者的同组用户和其他用户。

因此,Linux系统按文件所有者、文件所有者同组用户和其他用户来规定了不同的文件访问权限。

在以上实例中,boot 文件是一个目录文件,属主和属组都为 root

个人理解

  • 属主:这个文件/目录属于那个用户的

  • 属组:这个文件/目录属于哪个组的(属主不一定在这个组内)

修改文件属性

chgrp

更改文件属组

1
chgrp [-R] 属组名 文件名

-R:递归更改文件属组,就是在更改某个目录文件的属组时,如果加上-R的参数,那么该目录下的所有文件的属组都会更改。

chown

更改文件属主,也可以同时更改文件属组

1
2
chown [–R] 属主名 文件名
chown [-R] 属主名:属组名 文件名

chmod(必须要掌握)

更改文件9个属性

遇到:“你没有权限操作此文件!”,就要使用 chmod

1
chmod [-R] xyz 文件或目录

Linux文件属性有两种设置方法,一种是数字(常用的是数字),一种是符号。

Linux文件的基本权限就有九个,分别是owner/group/others三种身份各有自己的read/write/execute权限。

先复习一下刚刚上面提到的数据:文件的权限字符为:『-rwxrwxrwx』, 这九个权限是三个三个一组的!其中,我们可以使用数字来代表各个权限,各权限的分数对照表如下:

1
r:4     w:2         x:1

每种身份(owner/group/others)各自的三个权限(r/w/x)分数是需要累加的,例如当权限为:[-rwxrwx---] 分数则是:

  • owner = rwx = 4+2+1 = 7
  • group = rwx = 4+2+1 = 7
  • others= --- = 0+0+0 = 0
1
chmod 770 filename

实例

权限 命令 对应数字 命令
可读可写不可执行 rw- 6
可读可写可执行 rwx 7
文件赋予所有用户可读可写可执行权限! rwxrwxrwx 777 chmod 777 filename

内容查看

概述

Linux系统中使用以下命令来查看文件的内容:

命令 说明 备注 示例
cat 由第一行开始显示文件内容 用来读文章,或者读取配置文件,都使用 cat 命令
tac 从最后一行开始显示,可以看出 taccat 的倒着写! image-20210605164825483
nl 显示的时候,顺道输出行号! image-20210605164854211
more 一页一页的显示文件内容,带余下内容的 空格:翻页;enter:向下看一行;:f 显示行号 image-20210605165154807
less more 类似,但是比 more 更好的是,他可以往前翻页! 空格:翻页;上下键:滚动行;pageDown, pageUp:翻动页面;q:退出;/字符串:向下查询字符串;?字符串:向上查询字符串;n/N:向下/向上查找下一个 image-20210605165847793
head 只看头几行 通过 -n 参数来控制显示几行 image-20210605165921635
tail 只看尾巴几行 head image-20210605170013384

你可以使用 *man [命令]*来查看各个命令的使用文档,如 :man cp

  • 网络配置目录:/etc/sysconfig/network-scripts/

  • 默认网络配置文件:

  • 查看网络配置:

    • win : ipconfig
    • linux : ifconfig

cat

由第一行开始显示文件内容

语法:

1
cat [-AbEnTv]

选项与参数:

  • -A :相当於 -vET 的整合选项,可列出一些特殊字符而不是空白而已;
  • -b :列出行号,仅针对非空白行做行号显示,空白行不标行号!
  • -E :将结尾的断行字节 $ 显示出来;
  • -n :列印出行号,连同空白行也会有行号,与 -b 的选项不同;
  • -T :将 [tab] 按键以 ^I 显示出来;
  • -v :列出一些看不出来的特殊字符

测试:

1
2
3
4
5
# 查看网络配置: 文件地址 /etc/sysconfig/network-scripts/
[root@qeuroal ~]# cat /etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE=eth0
BOOTPROTO=dhcp
ONBOOT=yes

tac

taccat命令刚好相反,文件内容从最后一行开始显示,可以看出 taccat 的倒着写!如:

1
2
3
4
[root@qeuroal ~]# tac /etc/sysconfig/network-scripts/ifcfg-eth0
ONBOOT=yes
BOOTPROTO=dhcp
DEVICE=eth0

nl

显示行号

语法:

1
nl [-bnw] 文件

选项与参数:

  • -b :指定行号指定的方式,主要有两种:-b a :表示不论是否为空行,也同样列出行号(类似 cat -n);-b t :如果有空行,空的那一行不要列出行号(默认值);
  • -n :列出行号表示的方法,主要有三种:-n ln :行号在荧幕的最左方显示;-n rn :行号在自己栏位的最右方显示,且不加 0-n rz :行号在自己栏位的最右方显示,且加 0
  • -w :行号栏位的占用的位数。

测试:

1
2
3
4
[root@qeuroal ~]# nl /etc/sysconfig/network-scripts/ifcfg-eth0
1DEVICE=eth0
2BOOTPROTO=dhcp
3ONBOOT=yes

more

一页一页翻动

在 more 这个程序的运行过程中,你有几个按键可以按的:

  • 空白键 (space):代表向下翻一页;
  • Enter :代表向下翻『一行』;
  • /字串 :代表在这个显示的内容当中,向下搜寻『字串』这个关键字;
  • :f :立刻显示出档名以及目前显示的行数;
  • q :代表立刻离开 more ,不再显示该文件内容。
  • b[ctrl]-b :代表往回翻页,不过这动作只对文件有用,对管线无用。
1
2
3
[root@qeuroal etc]# more /etc/csh.login
....(中间省略)....
--More--(28%) # 重点在这一行喔!你的光标也会在这里等待你的命令

less

一页一页翻动,以下实例输出/etc/man.config文件的内容:

less运行时可以输入的命令有:

  • 空白键 :向下翻动一页;
  • [pagedown]:向下翻动一页;
  • [pageup] :向上翻动一页;
  • /字串 :向下搜寻『字串』的功能;
  • ?字串 :向上搜寻『字串』的功能;
  • n :重复前一个搜寻 (与 /? 有关!)
  • N :反向的重复前一个搜寻 (与 /? 有关!)
  • q :离开 less 这个程序;
1
2
3
[root@qeuroal etc]# more /etc/csh.login
....(中间省略)....
: # 这里可以等待你输入命令!

取出文件前面几行

语法:

1
head [-n number] 文件

选项与参数:**-n** 后面接数字,代表显示几行的意思!

默认的情况中,显示前面 10 行!若要显示前 20 行,就得要这样:

1
[root@qeuroal etc]# head -n 20 /etc/csh.login

tail

取出文件后面几行

语法:

1
tail [-n number] 文件

选项与参数:

  • -n :后面接数字,代表显示几行的意思

默认的情况中,显示最后 10 行!若要显示最后 20 行,就得要这样:

1
[root@qeuroal etc]# tail -n 20 /etc/csh.login

拓展:Linux 链接概念(了解即可)

Linux 链接分两种:

  • 硬链接(Hard Link):A------B:假设BA的硬链接,那么他们两个指向了同一个文件,允许一个文件拥有多个路径,用户可以通过这种机制建立硬链接到一些重要文件上,防止误删!
  • 符合链接(Symbolic Link)或叫软链接:类似 windows 下的快捷方式,即删除了源文件,快捷方式也访问不了。

命令

  • ln 命令产生硬链接;
  • touch 创建文件;
  • echo 输入字符串,也可以输入到文件中;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@iZbp16w4b9baac6xlzfcdmZ home]# ls
admin
[root@iZbp16w4b9baac6xlzfcdmZ home]# touch f1 # 创建一个f1文件
[root@iZbp16w4b9baac6xlzfcdmZ home]# ls
admin f1
[root@iZbp16w4b9baac6xlzfcdmZ home]# ln f1 f2 # 创建一个硬链接f2
[root@iZbp16w4b9baac6xlzfcdmZ home]# ls
admin f1 f2
[root@iZbp16w4b9baac6xlzfcdmZ home]# ln -s f1 f3 # 创建一个软链接(符号链接)f3
[root@iZbp16w4b9baac6xlzfcdmZ home]# ls
admin f1 f2 f3
[root@iZbp16w4b9baac6xlzfcdmZ home]# echo "hello" >> f1 # 给f1文件中写入一段字符串
[root@iZbp16w4b9baac6xlzfcdmZ home]# ls
admin f1 f2 f3
[root@iZbp16w4b9baac6xlzfcdmZ home]# cat f1
hello
[root@iZbp16w4b9baac6xlzfcdmZ home]# cat f2
hello
[root@iZbp16w4b9baac6xlzfcdmZ home]# ls
admin f1 f2 f3
[root@iZbp16w4b9baac6xlzfcdmZ home]# cat f3
hello

删除f1后,查看 f2f3 的区别:

1
2
3
4
5
6
7
[root@iZbp16w4b9baac6xlzfcdmZ home]# rm -rf f1
[root@iZbp16w4b9baac6xlzfcdmZ home]# ls
admin f2 f3
[root@iZbp16w4b9baac6xlzfcdmZ home]# cat f2 # f2硬链接还在
hello
[root@iZbp16w4b9baac6xlzfcdmZ home]# cat f3 # f3(软连接、符号链接)快捷方式失效
cat: f3: No such file or directory

注意:只要源文件发生改变,链接文件也会发生改变

硬连接

硬连接指通过索引节点来进行连接。在 Linux 的文件系统中,保存在磁盘分区中的文件不管是什么类型都给它分配一个编号,称为索引节点号(Inode Index)。在 Linux 中,多个文件名指向同一索引节点是存在的。比如:AB 的硬链接(AB 都是文件名),则 A 的目录项中的 inode 节点号与 B 的目录项中的 inode 节点号相同,即一个 inode 节点对应两个不同的文件名,两个文件名指向同一个文件,AB 对文件系统来说是完全平等的。删除其中任何一个都不会影响另外一个的访问。

硬连接的作用是允许一个文件拥有多个有效路径名,这样用户就可以建立硬连接到重要文件,以防止“误删”的功能。其原因如上所述,因为对应该目录的索引节点有一个以上的连接。只删除一个连接并不影响索引节点本身和其它的连接,只有当最后一个连接被删除后,文件的数据块及目录的连接才会被释放。也就是说,文件真正删除的条件是与之相关的所有硬连接文件均被删除。

软连接

另外一种连接称之为符号连接(Symbolic Link),也叫软连接。软链接文件有类似于 Windows 的快捷方式。它实际上是一个特殊的文件。在符号连接中,文件实际上是一个文本文件,其中包含的有另一文件的位置信息。比如:AB 的软链接(AB 都是文件名),A 的目录项中的 inode 节点号与 B 的目录项中的 inode 节点号不相同,AB 指向的是两个不同的 inode,继而指向两块不同的数据块。但是 A 的数据块中存放的只是 B 的路径名(可以根据这个找到 B 的目录项)。AB 之间是“主从”关系,如果 B 被删除了,A 仍然存在(因为两个是不同的文件),但指向的是一个无效的链接。

测试:

1
2
3
4
5
6
7
8
9
10
[root@qeuroal /]# cd /home
[root@qeuroal home]# touch f1 # 创建一个测试文件f1
[root@qeuroal home]# ls
f1
[root@qeuroal home]# ln f1 f2 # 创建f1的一个硬连接文件f2
[root@qeuroal home]# ln -s f1 f3 # 创建f1的一个符号连接文件f3
[root@qeuroal home]# ls -li # -i参数显示文件的inode节点信息
397247 -rw-r--r-- 2 root root 0 Mar 13 00:50 f1
397247 -rw-r--r-- 2 root root 0 Mar 13 00:50 f2
397248 lrwxrwxrwx 1 root root 2 Mar 13 00:50 f3 -> f1

从上面的结果中可以看出,硬连接文件 f2 与原文件 f1inode 节点相同,均为 397247,然而符号连接文件的 inode 节点不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
# echo 字符串输出 >> f1 输出到 f1文件
[root@qeuroal home]# echo "I am f1 file" >>f1
[root@qeuroal home]# cat f1
I am f1 file
[root@qeuroal home]# cat f2
I am f1 file
[root@qeuroal home]# cat f3
I am f1 file
[root@qeuroal home]# rm -f f1
[root@qeuroal home]# cat f2
I am f1 file
[root@qeuroal home]# cat f3
cat: f3: No such file or directory

通过上面的测试可以看出:当删除原始文件 f1 后,硬连接 f2 不受影响,但是符号连接 f1 文件无效;

依此您可以做一些相关的测试,可以得到以下全部结论:

  • 删除符号连接f3,对f1,f2无影响;
  • 删除硬连接f2,对f1,f3也无影响;
  • 删除原文件f1,对硬连接f2没有影响,导致符号连接f3失效;
  • 同时删除原文件f1,硬连接f2,整个文件会真正的被删除。

Vim编辑器

简介

Vim是从 vi 发展出来的一个文本编辑器。代码补完、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用。尤其是Linux中,必须要会使用vim查看内容、编辑内容、保存内容

简单的来说, vi 是老式的字处理器,不过功能已经很齐全了,但是还是有可以进步的地方。

vim 则可以说是程序开发者的一项很好用的工具。

所有的 Unix Like 系统都会内建 vi 文书编辑器,其他的文书编辑器则不一定会存在。

连 vim 的官方网站 (http://www.vim.org) 自己也说 vim 是一个程序开发工具而不是文字处理软件。

补充:

  1. vim可以看成vi的升级版;
  2. vim 通过一些插件可以实现和IDE一样的功能;

Vim键盘图

三种使用模式

基本上 vi/vim 共分为三种模式,分别是命令模式(Command mode)输入模式(Insert mode)底线命令模式(Last line mode)。这三种模式的作用分别是:

命令模式:

用户刚刚启动 vi/vim,便进入了命令模式。

此状态下敲击键盘动作会被Vim识别为命令,而非输入字符。比如我们此时按下i,并不会输入一个字符,i被当作了一个命令。

以下是常用的几个命令:

命令 说明 备注
i 切换到输入模式,以输入字符。 image-20210606161102527
x 删除当前光标所在处的字符。
: 切换到底线命令模式,以在最底一行输入命令。 如果是编辑模式,需要先退出编辑模式:ESC

若想要编辑文本:启动Vim,进入了命令模式,按下i,切换到输入模式。

命令模式只有一些最基本的命令,因此仍要依靠底线命令模式输入更多命令。

输入模式:

在命令模式下按下i就进入了输入模式。

在输入模式中,可以使用以下按键:

命令 说明 备注
字符按键以及Shift组合 输入字符
ENTER 回车键,换行
BACK SPACE 退格键,删除光标前一个字符
DEL 删除键,删除光标后一个字符
方向键 在文本中移动光标
HOME/END 移动光标到行首/行尾
Page Up/Page Down 上/下翻页
Insert 切换光标为输入/替换模式,光标将变成竖线/下划线
ESC 退出输入模式,切换到命令模式

底线命令模式

在命令模式下按下:(英文冒号)就进入了底线命令模式。光标就移动到了最下面,就可以在这里输入一些底线命令了!

底线命令模式可以输入单个或多个字符的命令,可用的命令非常多。

在底线命令模式中,基本的命令有(已经省略了冒号):

  • q 退出程序
  • w 保存文件

ESC键可随时退出底线命令模式。

简单的说,我们可以将这三个模式想成底下的图标来表示:

体验

如果你想要使用 vi 来建立一个名为 test.txt 的文件时,你可以这样做:

1
[root@qeuroal home]# vim test.txt

然后就会进入文件

按下 i 进入输入模式(也称为编辑模式),开始编辑文字

在一般模式之中,只要按下 i, o, a 等字符就可以进入输入模式了!

在编辑模式当中,你可以发现在左下角状态栏中会出现 –INSERT- 的字样,那就是可以输入任意字符的提示。

这个时候,键盘上除了 Esc 这个按键之外,其他的按键都可以视作为一般的输入按钮了,所以你可以进行任何的编辑。

image-20210606160732328

按下 ESC 按钮回到一般模式

好了,假设我已经按照上面的样式给他编辑完毕了,那么应该要如何退出呢?是的!没错!就是给他按下 Esc 这个按钮即可!马上你就会发现画面左下角的 – INSERT – 不见了!

在一般模式中按下 :wq 储存后离开 vim!

OK! 这样我们就成功创建了一个 test.txt 的文件。

Vim按键说明

除了上面简易范例的 i, Esc, :wq 之外,其实 vim 还有非常多的按键可以使用。

第一部分:一般模式可用的光标移动、复制粘贴、搜索替换等

移动光标的方法 说明 备注
h 或 向左箭头键() 光标向左移动一个字符
j 或 向下箭头键() 光标向下移动一个字符
k 或 向上箭头键() 光标向上移动一个字符
l 或 向右箭头键() 光标向右移动一个字符
[Ctrl] + [f] 屏幕『向下』移动一页,相当于 [Page Down]按键 (常用)
[Ctrl] + [b] 屏幕『向上』移动一页,相当于 [Page Up] 按键 (常用)
[Ctrl] + [d] 屏幕『向下』移动半页
[Ctrl] + [u] 屏幕『向上』移动半页
+ 光标移动到非空格符的下一行
- 光标移动到非空格符的上一行 配置文件中,空格较多
n< space> 那个 n 表示『数字』,例如 20 。按下数字后再按空格键,光标会向右移动这一行的 n 个字符。 快捷切换光标
0功能键[Home] 这是数字『 0 』:移动到这一行的最前面字符处 (常用)
$功能键[End] 移动到这一行的最后面字符处(常用)
H 光标移动到这个屏幕的最上方那一行的第一个字符
M 光标移动到这个屏幕的中央那一行的第一个字符
L 光标移动到这个屏幕的最下方那一行的第一个字符
G 移动到这个档案的最后一行(常用)
nG n 为数字。移动到这个档案的第 n 行。例如 20G 则会移动到这个档案的第 20 行(可配合 :set nu)
gg 移动到这个档案的第一行,相当于 1G 啊!(常用)
n< Enter> n 为数字。光标向下移动 n 行(常用)
搜索替换
/word 向光标之下寻找一个名称为 word 的字符串。例如要在档案内搜寻 vbird 这个字符串,就输入 /vbird 即可!(常用)
?word 向光标之上寻找一个字符串名称为 word 的字符串。
n 这个 n 是英文按键。代表重复前一个搜寻的动作。举例来说, 如果刚刚我们执行 /vbird 去向下搜寻 vbird 这个字符串,则按下 n 后,会向下继续搜寻下一个名称为 vbird 的字符串。如果是执行 ?vbird 的话,那么按下 n 则会向上继续搜寻名称为 vbird 的字符串!
N 这个 N 是英文按键。与 n 刚好相反,为『反向』进行前一个搜寻动作。例如 /vbird 后,按下 N 则表示『向上』搜寻 vbird
删除、复制与粘贴 说明
x, X 在一行字当中,x 为向后删除一个字符 (相当于 [del] 按键), X 为向前删除一个字符(相当于 [backspace] 亦即是退格键) (常用)
nx n 为数字,连续向后删除 n 个字符。举例来说,我要连续删除 10 个字符, 『10x』
dd 删除游标所在的那一整行(常用)
ndd n 为数字。删除光标所在的向下 n 行,例如 20dd 则是删除 20 行 (常用)
d1G 删除光标所在到第一行的所有数据
dG 删除光标所在到最后一行的所有数据
d$ 删除游标所在处,到该行的最后一个字符
d0 那个是数字的 0 ,删除游标所在处,到该行的最前面一个字符
yy 复制游标所在的那一行(常用)
nyy n 为数字。复制光标所在的向下 n 行,例如 20yy 则是复制 20 行(常用)
y1G 复制游标所在行到第一行的所有数据
yG 复制游标所在行到最后一行的所有数据
y0 复制光标所在的那个字符到该行行首的所有数据
y$ 复制光标所在的那个字符到该行行尾的所有数据
p, P p 为将已复制的数据在光标下一行贴上,P 则为贴在游标上一行!举例来说,我目前光标在第 20 行,且已经复制了 10 行数据。则按下 p 后, 那 10 行数据会贴在原本的 20 行之后,亦即由 21 行开始贴。但如果是按下 P 呢?那么原本的第 20 行会被推到变成 30 行。(常用)
J 将光标所在行与下一行的数据结合成同一行
c 重复删除多个数据,例如向下删除 10 行,[ 10cj ]
u 复原前一个动作。(常用)
[Ctrl]+r 重做上一个动作。(常用)

第二部分:一般模式切换到编辑模式的可用的按钮说明

进入输入或取代的编辑模式 说明
i, I 进入输入模式(Insert mode):i 为『从目前光标所在处输入』, I 为『在目前所在行的第一个非空格符处开始输入』。(常用)
a, A 进入输入模式(Insert mode):a 为『从目前光标所在的下一个字符处开始输入』, A 为『从光标所在行的最后一个字符处开始输入』。(常用)
o, O 进入输入模式(Insert mode):这是英文字母 o 的大小写。o 为『在目前光标所在的下一行处输入新的一行』;O 为在目前光标所在处的上一行输入新的一行!(常用)
r, R 进入取代模式(Replace mode):r 只会取代光标所在的那一个字符一次;R会一直取代光标所在的文字,直到按下 ESC 为止;(常用)
[Esc] 退出编辑模式,回到一般模式中(常用)

第三部分:一般模式切换到指令行模式的可用的按钮说明

指令行的储存、离开等指令 说明 备注
:w 将编辑的数据写入硬盘档案中(常用)
:w! 若文件属性为『只读』时,强制写入该档案。不过,到底能不能写入, 还是跟你对该档案的档案权限有关啊!
:q 离开 vi (常用)
:q! 若曾修改过档案,又不想储存,使用 ! 为强制离开不储存档案。 注意一下啊,那个惊叹号 (!) 在 vi 当中,常常具有『强制』的意思~
:wq 储存后离开,若为 :wq! 则为强制储存后离开 (常用)
ZZ 这是大写的 Z 喔!若档案没有更动,则不储存离开,若档案已经被更动过,则储存后离开!
:w [filename] 将编辑的数据储存成另一个档案(类似另存新档)
:r [filename] 在编辑的数据中,读入另一个档案的数据。亦即将 『filename』 这个档案内容加到游标所在行后面
:n1,n2 w [filename] n1n2 的内容储存成 filename 这个档案。
:! command 暂时离开 vi 到指令行模式下执行 command 的显示结果!例如 『:! ls /home』即可在 vi 当中看 /home 底下以 ls 输出的档案信息!
:set nu 显示行号,设定之后,会在每一行的前缀显示该行的行号 设置行号,代码中经常会使用
:set nonu set nu 相反,为取消行号!

账号管理

一般在公司中国,用的应该都不是root账户!

简介

Linux系统是一个多用户多任务的分时操作系统,任何一个要使用系统资源的用户,都必须首先向系统管理员申请一个账号,然后以这个账号的身份进入系统。

用户的账号一方面可以帮助系统管理员对使用系统的用户进行跟踪,并控制他们对系统资源的访问;另一方面也可以帮助用户组织文件,并为用户提供安全性保护。

每个用户账号都拥有一个唯一的用户名和各自的口令。

用户在登录时键入正确的用户名和口令后,就能够进入系统和自己的主目录。

实现用户账号的管理,要完成的工作主要有如下几个方面:

  • 用户账号的添加、删除与修改。
  • 用户口令的管理。
  • 用户组的管理。

用户账号的管理

用户账号的管理工作主要涉及到用户账号的添加、修改和删除。

添加用户账号就是在系统中创建一个新账号,然后为新账号分配用户号、用户组、主目录和登录Shell等资源。

属主,属组

添加账号

useradd

1
useradd 选项 用户名

参数说明:

  • 选项 :
    • -c comment 指定一段注释性描述。
    • -d 目录 指定用户主目录,如果此目录不存在,则同时使用-m选项,可以创建主目录。
    • -g 用户组 指定用户所属的用户组。
    • -G 用户组,用户组 指定用户所属的附加组。
    • -m 使用者目录如不存在则自动建立。自动创建这个用户的主目录 /home/qeuroal/
    • -s Shell文件 指定用户的登录Shell。
    • -u 用户号 指定用户的用户号,如果同时有-o选项,则可以重复使用其他用户的标识号。
  • 用户名 :
    • 指定新账号的登录名。

测试:

1
2
# 此命令创建了一个用户qeuroal,其中-m选项用来为登录名qeuroal产生一个主目录 /home/qeuroal
[root@iZbp16w4b9baac6xlzfcdmZ ~]# useradd -m qeuroal

增加用户账号就是在/etc/passwd文件中为新用户增加一条记录,同时更新其他系统文件如/etc/shadow, /etc/group等。

理解本质:Linux中一切皆文件,这里的添加用户说白了就是往某一个文件中写入用户的信息了!/etc/passwd

adduser 命令添加新用户

在Ubuntu中,有两个命令可用于创建新的用户。分别是useraddadduseruseradd是一个用于添加用户的最普遍命令,所有发行版都支持。

adduseruseradd的友好交互式前端,adduser是用Perl语言编写的。我们建议如果你在编写shell脚本时使用useradd添加用户。

如果你只是手动创建一个或者几个用户,在ubuntu中建议你使用adduser,adduser可以在一条命令完成创建用户的过程。

如果你需要在批量创建用户请参考我们的教程,Linux useradd命令创建用户

要在Ubuntu创建用户,请运行命令adduser,后跟用户名作为参数。例如命令sudo adduser myfreax将会创建用户myfreax。

命令将向你询问一系列的问题。密码是必需的,其他字段都是可选的。

1
sudo adduser myfreax

区别

使用 sudo useradd newuser/home 里并没有 newuser 的目录。

查询得知,可以用 useradd -m newuser,但是-m这个命令只有在你创建用户的时候才有用。

如果已经创建了用户且没有目录的话,useradd -m newuser是不会为用户创建目录的,也就是说必须删掉这个用户再重新useradd -m newuser才可以。

但是!使用adduser的话直接adduser username也会有目录,不用像useradd那样用-m了。但是 adduser一创建就会要求输入密码。useradd创建的时候就不会要求输入密码。

超级用户

默认情况下,在Ubuntu,sudo组的成员被授予sudo访问权限。如果您希望新创建的用户具有sudo权限,请将用户添加到sudo组

修改用户所属组的命令是usermod命令,我们不建议直接修改用户主要组。这可能导致某些权限问题。最好的方式将用户追加到sudo组中。

因此你将使用usermod命令的-aG选项,添加用户到sudo组,-a表示追加用户到指定组,-G选项表示不要将用户从其它组中移除。

1
sudo usermod -aG sudo username

删除帐号

userdel命令删除用户

如果一个用户的账号不再使用,可以从系统中删除。

删除用户账号就是要将/etc/passwd等系统文件中的该用户记录删除,必要时还删除用户的主目录。

删除一个已有的用户账号使用userdel命令,其格式如下:

1
userdel 选项 用户名

常用的选项是 -r,它的作用是把用户的主目录一起删除。

1
[root@iZbp16w4b9baac6xlzfcdmZ ~]# useradd -m qeuroal

此命令删除用户qeuroal在系统文件中(主要是/etc/passwd, /etc/shadow, /etc/group等)的记录,同时删除用户的主目录。

deluser 命令删除用户

如果不再需要用户,可以从命令行或通过GUI删除它。在ubuntu中删除用户方式也是有两种。

您可以使用两个命令来删除用户,分别是userdeldeluser。在Ubuntu,建议您使用deluser命令,因为它比userdel更友好。

同样在脚本中,我们建议你使用userdel而不是deluser。因为其它发行版不存在deluserdeluser仅在基于Ubuntu的发行版存在。

deluser仅在指定用户参数时,deluser将删除用户而不删除用户文件。如果你需要用户的家目录和邮件等信息请使用--remove-home选项。

1
2
3
sudo deluser username

sudo deluser --remove-home username

步骤

删除用户的操作分为 3 步:

  1. 执行userdelsudo userdel dongyuanxin_2016150127
  2. 删除用户目录:sudo rm -rf /home/dongyuanxin_2016150127
  3. 删除用户权限相关配置:删除或者注释掉/etc/sudoers中关于要删除用户的配置,否则无法再次创建同名用户。

修改帐号

修改用户账号就是根据实际情况更改用户的有关属性,如用户号、主目录、用户组、登录Shell等。

修改已有用户的信息使用usermod命令,其格式如下:

1
usermod 选项 用户名

常用的选项包括-c, -d, -m, -g, -G, -s, -u以及-o等,这些选项的意义与useradd命令中的选项一样,可以为用户指定新的资源值。

例如:

1
# usermod -s /bin/ksh -d /home/z –g developer qeuroal

修改完毕后查看配置文件即可:

1
cat /etc/passwd

此命令将用户qeuroal的登录Shell修改为ksh,主目录改为/home/z,用户组改为developer。

Linux下如何切换用户

root 用户

  1. 切换用户的命令为:su usernameusername是你的用户名哦】

  2. 从普通用户切换到root用户,还可以使用命令:sudo su

  3. 在终端输入exitlogout或使用快捷方式ctrl+d,可以退回到原来用户,其实ctrl+d也是执行的exit命令

  4. 在切换用户时,如果想在切换用户之后使用新用户的工作环境,可以在suusername之间加-,例如:【su - root

符号 说明 备注
$ 表示普通用户
# 表示超级用户,也就是root用户

补充

在阿里云买的服务器是一段随机字符串,可以通过以下方式修改:

用户口令的管理

用户管理的一项重要内容是用户口令的管理。用户账号刚创建时没有口令,但是被系统锁定,无法使用,必须为其指定口令后才可以使用,即使是指定空口令。

指定和修改用户口令的Shell命令是passwd。超级用户(root)可以为自己和其他用户指定口令,普通用户只能用它修改自己的口令。

linux上输入密码不会显示,正常输入即可,并不是系统的问题!

在公司中,一般拿不到公司服务器的root权限,都是一些分配的账号!

超级用户:

命令的格式为:

1
passwd 选项 用户名

可使用的选项:

  • -l 锁定口令,即禁用账号。
  • -u 口令解锁。
  • -d 使账号无口令。
  • - 强迫用户下次登录时修改口令。

普通用户名:

修改当前用户的口令。

例如,假设当前用户是qeuroal,则下面的命令修改该用户自己的口令:

1
2
3
4
$ passwd
Old password:******
New password:*******
Re-enter new password:*******

如果是超级用户,可以用下列形式指定任何用户的口令:

1
2
3
# passwd qeuroal
New password:*******
Re-enter new password:*******

普通用户修改自己的口令时,passwd命令会先询问原口令,验证后再要求用户输入两遍新口令,如果两次输入的口令一致,则将这个口令指定给用户;而超级用户为用户指定口令时,就不需要知道原口令。

为了系统安全起见,用户应该选择比较复杂的口令,例如最好使用8位长的口令,口令中包含有大写、小写字母和数字,并且应该与姓名、生日等不相同。

锁定账户

例如:test_user辞职了,冻结这个账号,一旦冻结,这个人就登录不上系统了。

为用户指定空口令时,执行下列形式的命令:

1
passwd -d test_user   # 没有密码不能登录

此命令将用户 test_user的口令删除,这样用户 test_user下一次登录时,系统就不再允许该用户登录了。

passwd 命令还可以用 -l(lock) 选项锁定某一用户,使其不能登录,例如:

1
passwd -l test_user	# 锁定之后这个用户就不能登录了

在公司中一般触及不到 root 用户!作为一个开发一般拿不到!如果拿到root账号后,首先把自己的权限提到最高,自己用户的组提到root组。

用户组管理

属主、属组

每个用户都有一个用户组,系统可以对一个用户组中的所有用户进行集中管理(开发、测试、运维、root)。不同Linux 系统对用户组的规定有所不同,如Linux下的用户属于与它同名的用户组,这个用户组在创建用户时同时创建。

用户组的管理涉及用户组的添加、删除和修改。组的增加、删除和修改实际上就是对/etc/group文件的更新。

增加用户组

增加一个新的用户组使用groupadd命令

1
groupadd 选项 用户组

可以使用的选项有:

  • -g GID 指定新用户组的组标识号(GID)。
  • -o 一般与-g选项同时使用,表示新用户组的GID可以与系统已有用户组的GID相同。

实例1:

1
# groupadd group1

此命令向系统中增加了一个新组group1,新组的组标识号是在当前已有的最大组标识号的基础上加1。

创建完一个用户组后可以得到一个组的 id,这个 id 是可以指定的!

实例2:

1
# groupadd -g 101 group2

此命令向系统中增加了一个新组group2,同时指定新组的组标识号是101

如果不指定就是自增 1

删除用户组

如果要删除一个已有的用户组,使用groupdel命令

1
groupdel 用户组

例如:

1
# groupdel group1

此命令从系统中删除组group1

修改用户组的属性

使用groupmod命令

1
groupmod 选项 用户组

常用的选项有:

  • -g GID 为用户组指定新的组标识号。
  • -o-g选项同时使用,用户组的新GID可以与系统已有用户组的GID相同。
  • -n 新用户组 将用户组的名字改为新名字
1
2
3
4
5
# 此命令将组group2的组标识号修改为102。
groupmod -g 102 group2

# 将组group2的标识号改为10000,组名修改为group3。
groupmod –g 10000 -n group3 group2

拓展:文件的查看

/etc/passwd

完成用户管理的工作有许多种方法,但是每一种方法实际上都是对有关的系统文件进行修改。

与用户和用户组相关的信息都存放在一些系统文件中,这些文件包括/etc/passwd, /etc/shadow, /etc/group等。

下面分别介绍这些文件的内容。

/etc/passwd文件是用户管理工作涉及的最重要的一个文件。

Linux系统中的每个用户都在/etc/passwd文件中有一个对应的记录行,它记录了这个用户的一些基本属性(主目录、属组)。

这个文件对所有用户都是可读的。它的内容类似下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
# cat /etc/passwd

root:x:0:0:Superuser:/:
daemon:x:1:1:System daemons:/etc:
bin:x:2:2:Owner of system commands:/bin:
sys:x:3:3:Owner of system files:/usr/sys:
adm:x:4:4:System accounting:/usr/adm:
uucp:x:5:5:UUCP administrator:/usr/lib/uucp:
auth:x:7:21:Authentication administrator:/tcb/files/auth:
cron:x:9:16:Cron daemon:/usr/spool/cron:
listen:x:37:4:Network daemon:/usr/net/nls:
lp:x:71:18:Printer administrator:/usr/spool/lp:

从上面的例子我们可以看到,/etc/passwd中一行记录对应着一个用户,每行记录又被冒号(:)分隔为7个字段,其格式和具体含义如下:

1
用户名:口令(登录密码,我们不可见):用户标识号:组标识号:注释性描述:主目录:登录Shell
  1. “用户名”是代表用户账号的字符串。

    通常长度不超过8个字符,并且由大小写字母和/或数字组成。登录名中不能有冒号(:),因为冒号在这里是分隔符。

    为了兼容起见,登录名中最好不要包含点字符(.),并且不使用连字符(-)和加号(+)打头。

  2. “口令”一些系统中,存放着加密后的用户口令字。

    虽然这个字段存放的只是用户口令的加密串,不是明文,但是由于/etc/passwd文件对所有用户都可读,所以这仍是一个安全隐患。因此,现在许多Linux 系统(如SVR4)都使用了shadow技术,把真正的加密后的用户口令字存放到/etc/shadow文件中,而在/etc/passwd文件的口令字段中只存放一个特殊的字符,例如“x”或者“*”。

  3. “用户标识号”是一个整数,系统内部用它来标识用户。

    一般情况下它与用户名是一一对应的。如果几个用户名对应的用户标识号是一样的,系统内部将把它们视为同一个用户,但是它们可以有不同的口令、不同的主目录以及不同的登录Shell等。

    通常用户标识号的取值范围是0~65 535。0是超级用户root的标识号,1~99由系统保留,作为管理账号,普通用户的标识号从100开始。在Linux系统中,这个界限是500

  4. “组标识号”字段记录的是用户所属的用户组。

    它对应着/etc/group文件中的一条记录。

  5. “注释性描述”字段记录着用户的一些个人情况。

    例如用户的真实姓名、电话、地址等,这个字段并没有什么实际的用途。在不同的Linux 系统中,这个字段的格式并没有统一。在许多Linux系统中,这个字段存放的是一段任意的注释性描述文字,用作finger命令的输出。

  6. “主目录”,也就是用户的起始工作目录。

    它是用户在登录到系统之后所处的目录。在大多数系统中,各用户的主目录都被组织在同一个特定的目录下,而用户主目录的名称就是该用户的登录名。各用户对自己的主目录有读、写、执行(搜索)权限,其他用户对此目录的访问权限则根据具体情况设置。

  7. 用户登录后,要启动一个进程,负责将用户的操作传给内核,这个进程是用户登录到系统后运行的命令解释器或某个特定的程序,即Shell。

    Shell是用户与Linux系统之间的接口。LinuxShell有许多种,每种都有不同的特点。常用的有sh(Bourne Shell), csh(C Shell), ksh(Korn Shell), tcsh(TENEX/TOPS-20 type C Shell), bash(Bourne Again Shell)等。

    系统管理员可以根据系统情况和用户习惯为用户指定某个Shell。如果不指定Shell,那么系统使用sh为默认的登录Shell,即这个字段的值为/bin/sh

    用户的登录Shell也可以指定为某个特定的程序(此程序不是一个命令解释器)。

    利用这一特点,我们可以限制用户只能运行指定的应用程序,在该应用程序运行结束后,用户就自动退出了系统。有些Linux 系统要求只有那些在系统中登记了的程序才能出现在这个字段中。

  8. 系统中有一类用户称为伪用户(pseudo users

    这些用户在/etc/passwd文件中也占有一条记录,但是不能登录,因为它们的登录Shell为空。它们的存在主要是方便系统管理,满足相应的系统进程对文件属主的要求。

    常见的伪用户如下所示:

    伪用户 含 义
    bin 拥有可执行的用户命令文件
    sys 拥有系统文件
    adm 拥有帐户文件
    uucp UUCP使用
    lp lp或lpd子系统使用
    nobody NFS使用

/etc/shadow

1、除了上面列出的伪用户外,还有许多标准的伪用户,例如:audit, cron, mail, usenet等,它们也都各自为相关的进程和文件所需要。

由于/etc/passwd文件是所有用户都可读的,如果用户的密码太简单或规律比较明显的话,一台普通的计算机就能够很容易地将它破解,因此对安全性要求较高的Linux系统都把加密后的口令字分离出来,单独存放在一个文件中,这个文件是/etc/shadow文件。有超级用户才拥有该文件读权限,这就保证了用户密码的安全性。

**2、/etc/shadow中的记录行与/etc/passwd**中的一一对应,它由pwconv命令根据/etc/passwd中的数据自动产生

它的文件格式与/etc/passwd类似,由若干个字段组成,字段之间用”:”隔开。这些字段是:

1
登录名:加密口令:最后一次修改时间:最小时间间隔:最大时间间隔:警告时间:不活动时间:失效时间:标志
  1. “登录名”是与/etc/passwd文件中的登录名相一致的用户账号
  2. “口令”字段存放的是加密后的用户口令字,长度为13个字符。如果为空,则对应用户没有口令,登录时不需要口令;如果含有不属于集合 { ./0-9A-Za-z }中的字符,则对应的用户不能登录。
  3. “最后一次修改时间”表示的是从某个时刻起,到用户最后一次修改口令时的天数。时间起点对不同的系统可能不一样。例如在SCO Linux 中,这个时间起点是1970年1月1日。
  4. “最小时间间隔”指的是两次修改口令之间所需的最小天数。
  5. “最大时间间隔”指的是口令保持有效的最大天数。
  6. “警告时间”字段表示的是从系统开始警告用户到用户密码正式失效之间的天数。
  7. “不活动时间”表示的是用户没有登录活动但账号仍能保持有效的最大天数。
  8. “失效时间”字段给出的是一个绝对的天数,如果使用了这个字段,那么就给出相应账号的生存期。期满后,该账号就不再是一个合法的账号,也就不能再用来登录了。

/etc/group

用户组的所有信息都存放在/etc/group文件中。

将用户分组是Linux 系统中对用户进行管理及控制访问权限的一种手段。

每个用户都属于某个用户组;一个组中可以有多个用户,一个用户也可以属于不同的组。

当一个用户同时是多个组中的成员时,在/etc/passwd文件中记录的是用户所属的主组,也就是登录时所属的默认组,而其他组称为附加组。

用户要访问属于附加组的文件时,必须首先使用newgrp命令使自己成为所要访问的组中的成员。

用户组的所有信息都存放在/etc/group文件中。此文件的格式也类似于/etc/passwd文件,由冒号(:)隔开若干个字段,这些字段有:

1
组名:口令:组标识号:组内用户列表
  1. “组名”是用户组的名称,由字母或数字构成。与/etc/passwd中的登录名一样,组名不应重复。
  2. “口令”字段存放的是用户组加密后的口令字。一般Linux 系统的用户组都没有口令,即这个字段一般为空,或者是*。(当然,你看不到明文密码)
  3. “组标识号”与用户标识号类似,也是一个整数,被系统内部用来标识组。即: 组的ID, GID
  4. “组内用户列表”是属于这个组的所有用户的列表/b],不同用户之间用逗号(,)分隔。这个用户组可能是用户的主组,也可能是附加组。
1
adm:x:4:syslog,admin

以下为各字段的说明:

  • adm 为组名称;
  • x 代表密码字段(当然你不会看到明文的密码);
  • 4 是组的ID即GID;
  • syslog 和 admin 是属于组 adm 中的用户。

切换组

如果一个用户同时属于多个用户组,那么用户可以在用户组之间切换,以便具有其他用户组的权限。

用户可以在登录后,使用命令newgrp切换到其他用户组,这个命令的参数就是目的用户组。例如:

1
$ newgrp root

这条命令将当前用户切换到root用户组,前提条件是root用户组确实是该用户的主组或附加组。

磁盘管理

概述

Linux磁盘管理好坏直接关系到整个系统的性能问题。

Linux磁盘管理常用命令为 dfdu

  • df :列出文件系统的整体磁盘使用量
  • du:检查磁盘空间使用量

df

df:列出文件系统整体的磁盘使用量

df命令参数功能:检查文件系统的磁盘空间占用情况。可以利用该命令来获取硬盘被占用了多少空间,目前还剩下多少空间等信息。

语法:

1
df [-ahikHTm] [目录或文件名]

选项与参数:

  • -a :列出所有的文件系统,包括系统特有的 /proc 等文件系统;
  • -k :以 KBytes 的容量显示各文件系统;
  • -m :以 MBytes 的容量显示各文件系统;
  • -h :以人们较易阅读的 GBytes, MBytes, KBytes 等格式自行显示;
  • -H :以 M=1000K 取代 M=1024K 的进位方式;
  • -T :显示文件系统类型, 连同该 partitionfilesystem 名称 (例如 ext3) 也列出;
  • -i :不用硬盘容量,而以 inode 的数量来显示

测试:

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
# 将系统内所有的文件系统列出来!
# 在 Linux 底下如果 df 没有加任何选项
# 那么默认会将系统内所有的 (不含特殊内存内的文件系统与 swap) 都以 1 Kbytes 的容量来列出来!
# Mounted on: 挂载目录
[root@qeuroal /]# df
Filesystem 1K-blocks Used Available Use% Mounted on
devtmpfs 889100 0 889100 0% /dev
tmpfs 899460 704 898756 1% /dev/shm
tmpfs 899460 496 898964 1% /run
tmpfs 899460 0 899460 0% /sys/fs/cgroup
/dev/vda1 41152812 6586736 32662368 17% /
tmpfs 179896 0 179896 0% /run/user/0
# 将容量结果以易读的容量格式(M/G)显示出来
[root@qeuroal /]# df -h
Filesystem Size Used Avail Use% Mounted on
devtmpfs 869M 0 869M 0% /dev
tmpfs 879M 708K 878M 1% /dev/shm
tmpfs 879M 496K 878M 1% /run
tmpfs 879M 0 879M 0% /sys/fs/cgroup
/dev/vda1 40G 6.3G 32G 17% /
tmpfs 176M 0 176M 0% /run/user/0
# 将系统内的所有特殊文件格式及名称都列出来
[root@qeuroal /]# df -aT
Filesystem Type 1K-blocks Used Available Use% Mounted on
sysfs sysfs 0 0 0 - /sys
proc proc 0 0 0 - /proc
devtmpfs devtmpfs 889100 0 889100 0% /dev
securityfs securityfs 0 0 0 - /sys/kernel/security
tmpfs tmpfs 899460 708 898752 1% /dev/shm
devpts devpts 0 0 0 - /dev/pts
tmpfs tmpfs 899460 496 898964 1% /run
tmpfs tmpfs 899460 0 899460 0% /sys/fs/cgroup
cgroup cgroup 0 0 0 - /sys/fs/cgroup/systemd
pstore pstore 0 0 0 - /sys/fs/pstore
cgroup cgroup 0 0 0 - /sys/fs/cgroup/freezer
cgroup cgroup 0 0 0 - /sys/fs/cgroup/cpuset
cgroup cgroup 0 0 0 - /sys/fs/cgroup/hugetlb
cgroup cgroup 0 0 0 - /sys/fs/cgroup/blkio
cgroup cgroup 0 0 0 - /sys/fs/cgroup/net_cls,net_prio
cgroup cgroup 0 0 0 - /sys/fs/cgroup/memory
cgroup cgroup 0 0 0 - /sys/fs/cgroup/pids
cgroup cgroup 0 0 0 - /sys/fs/cgroup/cpu,cpuacct
cgroup cgroup 0 0 0 - /sys/fs/cgroup/devices
cgroup cgroup 0 0 0 - /sys/fs/cgroup/perf_event
configfs configfs 0 0 0 - /sys/kernel/config
/dev/vda1 ext4 41152812 6586748 32662356 17% /
systemd-1 - - - - - /proc/sys/fs/binfmt_misc
mqueue mqueue 0 0 0 - /dev/mqueue
debugfs debugfs 0 0 0 - /sys/kernel/debug
hugetlbfs hugetlbfs 0 0 0 - /dev/hugepages
tmpfs tmpfs 179896 0 179896 0% /run/user/0
binfmt_misc binfmt_misc 0 0 0 - /proc/sys/fs/binfmt_misc
# 将 /etc 底下的可用的磁盘容量以易读的容量格式显示

[root@qeuroal /]# df -h /etc
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 40G 6.3G 32G 17% /

du

du:检查当前磁盘空间使用量

Linux du命令也是查看使用空间的,但是与df命令不同的是Linux du命令是对文件和目录磁盘使用的空间的查看,还是和df命令有一些区别的,这里介绍Linux du命令。

语法:

1
du [-ahskm] 文件或目录名称

选项与参数:

  • -a :列出所有的文件与目录容量,可以看到子文件夹,因为默认仅统计目录底下的文件量而已。
  • -h :以人们较易读的容量格式 (G/M) 显示;
  • -s :列出总量而已,而不列出每个各别的目录占用容量;
  • -S :不包括子目录下的总计,与 -s 有点差别。
  • -k :以 KBytes 列出容量显示;
  • -m :以 MBytes 列出容量显示;

测试:

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
# 只列出当前目录下的所有文件夹容量(包括隐藏文件夹):
# 直接输入 du 没有加任何选项时,则 du 会分析当前所在目录的文件与目录所占用的硬盘空间。
[root@qeuroal home]# du
16./redis
8./www/.oracle_jre_usage # 包括隐藏文件的目录
24./www
48. # 这个目录(.)所占用的总量
# 将文件的容量也列出来
[root@qeuroal home]# du -a
4./redis/.bash_profile
4./redis/.bash_logout
....中间省略....
4./kuangstudy.txt # 有文件的列表了
48.
# 检查根目录底下每个目录所占用的容量
[root@qeuroal home]# du -sm /*
0/bin
146/boot
.....中间省略....
0/proc
.....中间省略....
1/tmp
3026/usr # 系统初期最大就是他了啦!
513/var
2666/www

通配符 * 来代表每个目录。

df 不一样的是,du 这个命令其实会直接到文件系统内去搜寻所有的文件数据。

磁盘挂载与卸除

起因:MacLinux 挂载本地磁盘或者文件

根文件系统之外的其他文件要想能够被访问,都必须通过“关联”至根文件系统上的某个目录来实现,此关联操作即为“挂载”,此目录即为“挂载点”,解除此关联关系的过程称之为“卸载”

Linux 的磁盘挂载使用mount命令,卸载使用umount命令。

磁盘挂载语法:

1
mount [-t 文件系统] [-L Label名] [-o 额外选项] [-n] 装置文件名 挂载点

测试:

1
2
3
4
5
6
# 将 /dev/hdc6 挂载到 /mnt/hdc6 上面!
[root@www ~]# mkdir /mnt/hdc6
[root@www ~]# mount /dev/hdc6 /mnt/hdc6
[root@www ~]# df
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/hdc6 1976312 42072 1833836 3% /mnt/hdc6

磁盘卸载命令 umount 语法:

1
umount [-fn] 装置文件名或挂载点

选项与参数:

  • -f :强制卸除!可用在类似网络文件系统 (NFS) 无法读取到的情况下;
  • -n :不升级 /etc/mtab 情况下卸除。

卸载/dev/hdc6

1
[root@www ~]# umount /dev/hdc6

除了这个之外,以后安装了JDK ,可以使用 java 中的一些命令来查看信息。

进程管理

Linux中一切皆文件(Linux:系统:(磁盘、进程)。文件:读写执行(查看,创建,删除,移动,复制,编辑),权限(用户、用户组)。)

对于我们开发人员来说,其实 Linux 更多偏向于使用即可!

进程基本概念

  1. Linux 中,每一个程序都有自己的一个进程,每一个进程都有一个 id 号;
  2. 每个进程都有一个父进程(被谁创建的);
  3. 进程可以有两种存在方式:前台!后台运行!
  4. 一般的话服务都是后台运行的,基本的程序都是前台运行的;

命令

查看进程

ps : 查看当前系统中正在执行的各种进程的信息

ps -xxx : 通过 ps -help man ps 查看帮助文档

  • -a : 显示当前终端运行的所有的进程信息(当期的进程,1个);
  • -u : 以用户的信息显示进程();
  • -x : 显示后台运行进程的参数;

实例

  • ps -aux | grep mysql

补充

  • |: 在 Linux 中,这个叫做管道符,如: A|B: 把 A 命令的结果作为输出操作 B 命令

  • grep: 查找文件中符合条件的字符串

  • 对于我们来说,这里目前只需要记住一个命令即可: ps -xxx | grep 进程名 过滤进程信息

  • ps -ef : 可以查到父进程信息

    1
    ps -ef | grep mysql # 看父进程我们一般可以通过目录树结构查看
  • pstree : 进程树

    • -p : 显示父 id

    • -u : 显示用户组

    • 实践

      1
      pstree -pu

结束进程

杀死进程,等价于 window 结束任务

1
kill -9 进程id	# 表示强制结束该进程

在服务器很少手动结束进程,平时写的代码死循环了,可以选择结束进程!杀进程!

shell

getopts

getpots是Shell命令行参数解析工具,旨在从Shell Script的命令行当中解析参数。

getopt

SSH

上传

上传本地文件到服务器

格式:scp 要上传的文件路径 用户名@服务器地址:服务器保存路径

例如:把本机 /home/test.txt 文件上传到 192.168.0.101 这台服务器上的 /data/ 目录中

1
scp /home/test.txt root@192.168.0.101:/data/

上传目录到服务器

格式:scp -r 要上传的目录 用户名@服务器地址:服务器的保存目录

例如:把 /home 目录上传到服务器的 /data/ 目录

1
scp -r /home root@192.168.0.101:/data/

下载

从服务器上下载文件

格式:scp 用户名@服务器地址:要下载的文件路径 保存文件的文件夹路径

例如:把 192.168.0.101 上的 /data/test.txt 的文件下载到 /home(本地目录)

1
scp root@192.168.0.101:/data/test.txt /home

从服务器下载整个目录

格式:scp -r 用户名@服务器地址:要下载的服务器目录 保存下载的目录

例如:把 192.168.0.101 上的 /data 目录下载到 /home(本地目录)

1
scp -r root@192.168.0.101:/data  /home/

注:目标服务器要开启写入权限。

命令

总览

  • 基础命令相关一:

    1
    2
    cd、ls、pwd、help、man、if、for、while、case、select、read、test、ansible、iptables、
    firewall-cmd、salt、mv、cut、uniq、sort、wc、source、sestatus、setenforce;
  • 基础命令相关二:

    1
    2
    date、ntpdate、crontab、rsync、ssh、scp、nohup、sh、bash、hostname、hostnamectl、
    source、ulimit、export、env、set、at、dir、db_load、diff、dmsetup、declare;
  • 用户权限相关:

    1
    2
    3
    Useradd、userdel、usermod、groupadd、groupmod、groupdel、Chmod、chown、
    chgrp、umask、chattr、lsattr、id、who、whoami、last、su、sudo、w、chpasswd、
    chroot;
  • 文件管理相关:

    1
    2
    Touch、mkdir、rm、rmdi、vi、vim、cat、head、tail、less、more、find、sed、
    grep、awk、echo、ln、stat、file;
  • 软件资源管理:

    1
    2
    Rpm、yum、tar、unzip、zip、gzip、wget、curl、rz、sz、jar、apt-get、bzip2、
    service、systemctl、make、cmake、chkconfig;
  • 系统资源管理:

    1
    2
    fdisk、mount、umount、mkfs.ext4、fsck.ext4、parted、lvm、dd、du、df、top、
    iftop、free、w、uptime、iostat、vmstat、iotop、ps、netstat、lsof、ss、sar;
  • 网络管理相关:

    1
    2
    ping、ifconfig、ip addr、ifup、ifdown、nmcli、route、nslookup、traceroute、
    dig、tcpdump、nmap、brctl、ethtool、setup、arp、ab、iperf;

grep

基本格式

1
grep [option] pattern file

常用参数

参数 含义
-b 显示匹配行距文件头部的偏移量
-c 只显示匹配的行数
-e 实现多个选项间的逻辑 or 关系
-E 支持扩展正则表达式
-f 从文件获取 PATTERN 匹配
-F 匹配固定字符串的内容
-h 搜索多文件时不显示文件名
-i 忽略关键词大小写
-l 只显示符合匹配条件的文件名
-n 显示所有匹配行及其行号
-o 显示匹配词距文件头部的偏移量
-q 静默执行模式
-r 递归搜索模式
-s 不显示没有匹配文本的错误信息
-v 显示不包含匹配文本的所有行,==相当于[^] 反向匹配==
-w 精准匹配整词
-x 精准匹配整行
-A <行数 x> 除了显示符合范本样式的那一列之外,并显示该行之后的 x 行内容。
-B <行数 x> 除了显示符合样式的那一行之外,并显示该行之前的 x 行内容
-C<行数 x> 除了显示符合样式的那一行之外,并显示该行之前后的 x 行内容

sed

功能

用于利用语法/脚本对文本文件进行批量的编辑操作

sed 会根据脚本命令来处理文本文件中的数据,这些命令要么从命令行中输入,要么存储在一个文本文件中,此命令执行数据的顺序如下:

  1. 每次==仅读取一行==内容;
  2. 根据提供的规则命令匹配并修改数据。注意,sed ==默认不会直接修改源文件数据==,而是会将数据复制到缓冲区中,修改也仅限于缓冲区中的数据;
  3. 将执行结果输出。

当一行数据匹配完成后,它会继续读取下一行数据,并重复这个过程,直到将文件中所有数据处理完毕。

语法

1
sed [选项] [脚本命令] 文件名

选项

参数 含义
-e 使用指定脚本来处理输入的文本文件
以选项中指定的 script 来处理输入的文本文件,这个-e可以省略,直接写表达式。
-f 使用指定脚本文件处理输入的文本文件
-h 显示帮助信息
-i 直接修改文件内容,而不输出到终端
-n 仅显示脚本处理后的结果
默认情况下,sed 会在所有的脚本指定执行完毕后,会自动输出处理后的内容,而该选项会屏蔽启动输出,需使用 print 命令来完成输出。
–quiet 仅显示脚本处理后的结果
–silent 仅显示脚本处理后的结果
-r 支持扩展正则表达式
-V 显示版本信息

动作说明

动作 说明
a 新增, a 的后面可以接字串,而这些字串会在新的一行出现(目前的下一行)~
c 取代, c 的后面可以接字串,这些字串可以取代 n1,n2 之间的行!
d 删除,因为是删除啊,所以 d 后面通常不接任何咚咚;
i 插入, i 的后面可以接字串,而这些字串会在新的一行出现(目前的上一行);
p 打印,亦即将某个选择的数据印出。通常 p 会与参数 sed -n 一起运行~
s 取代,通常这个 s 的动作可以搭配正规表示法,例如 1,20s/old/new/g 。

sed脚本命令

sed s 替换脚本命令

基本格式

1
[address]s/pattern/replacement/flags
  • address 表示指定要操作的具体行
  • pattern 指的是需要替换的内容
  • replacement 指的是要替换的新内容

常用的 flags 标记

flags 标记 功能
n 1~512 之间的数字,表示指定要替换的字符串出现第几次时才进行替换,例如,一行中有 3 个 A,但用户只想替换第二个 A,这是就用到这个标记;
g 对数据中所有匹配到的内容进行替换,如果没有 g,则只会在第一次匹配成功时做替换操作。例如,一行数据中有 3 个 A,则只会替换第一个 A;
p 会打印与替换命令中指定的模式匹配的行。此标记通常与 -n 选项一起使用。
w file 将缓冲区中的内容写到指定的 file 文件中;
& 用正则表达式匹配的内容进行替换;
\n 匹配第 n 个子串,该子串之前在 pattern 中用 () 指定。
\ 转义(转义替换部分包含:&、\ 等)。

注意

替换类似文件路径的字符串会比较麻烦,需要将路径中的正斜线进行转义,如:

1
sed 's/\/bin\/bash/\/bin\/csh/' /etc/passwd

sed d 替换脚本命令

基本格式

1
[address]d

sed a 和 i 脚本命令

  • a 命令表示在指定行的后面附加一行
  • i 命令表示在指定行的前面插入一行

基本格式

1
[address]a \新文本内容
1
[address]i \新文本内容

多行

如果你想将一个多行数据添加到数据流中,只需对要插入或附加的文本中的每一行末尾(除最后一行)添加反斜线即可

sed c 替换脚本命令

c 命令表示将指定行中的所有内容,替换成该选项后面的字符串。该命令的基本格式为:

1
[address]c\用于替换的新文本

sed y 转换脚本命令

y 转换命令是唯一可以处理单个字符的 sed 脚本命令,其基本格式如下:

1
[address]y/inchars/outchars/

转换命令会对 inchars 和 outchars 值进行一对一的映射,即 inchars 中的第一个字符会被转换为 outchars 中的第一个字符,第二个字符会被转换成 outchars 中的第二个字符…这个映射过程会一直持续到处理完指定字符。如果 inchars 和 outchars 的长度不同,则 sed 会产生一条错误消息。

sed p 打印脚本命令

p 命令表示搜索符号条件的行,并输出该行的内容,此命令的基本格式为:

1
[address]p

sed w 脚本命令

w 命令用来将文本中指定行的内容写入文件中,此命令的基本格式如下:

1
[address]w filename

这里的 filename 表示文件名,可以使用相对路径或绝对路径,但不管是哪种,运行 sed 命令的用户都必须有文件的写权限。

sed r 脚本命令

r 命令用于将一个独立文件的数据插入到当前数据流的指定位置,该命令的基本格式为:

1
[address]r filename

sed 命令会将 filename 文件中的内容插入到 address 指定行的后面

sed q 退出脚本命令

q 命令的作用是使 sed 命令在第一次匹配任务结束后,退出 sed 程序,不再进行对后续数据的处理。

sed 脚本命令的寻址方式

对各个脚本命令来说,address 用来表明该脚本命令作用到文本中的具体行

默认情况下,sed 命令会作用于文本数据的所有行。如果只想将命令作用于特定行或某些行,则必须写明 address 部分,表示的方法有以下 2 种:

  1. 以数字形式指定行区间;
  2. 用文本模式指定具体行区间。

以上两种形式==都可以使用如下这 2 种格式==,分别是:

1
[address]脚本命令

或者

1
2
3
address {
多个脚本命令
}

例如:

1
2
3
4
$ sed -n '/3/{
> p
> s/line/test/p
> }' data6.txt

以数字形式指定行区间

当使用数字方式的行寻址时,可以用行在文本流中的行位置来引用。sed 会将文本流中的第一行编号为 1,然后继续按顺序为接下来的行分配行号。

在脚本命令中,指定的地址可以是 单个行号,或是 用起始行号、逗号以及结尾行号指定的一定区间范围内的行

用文本模式指定行区间

sed 允许指定文本模式来过滤出命令要作用的行,格式如下:

1
/pattern/command

注意,必须用正斜线将要指定的 pattern 封起来,sed 会将该命令作用到包含指定文本模式的行上。

sed 允许在文本模式==使用正则表达式指明作用的具体行==

sed 多行命令

  1. Next 命令(N):将数据流中的下一行加进来创建一个多行组来处理。
  2. Delete(D):删除多行组中的一行。
  3. Print(P):打印多行组中的一行。

注意,以上命令的缩写,都为大写。

awk

和 sed 命令类似,awk 命令也是逐行扫描文件(从第 1 行到最后一行),寻找含有目标文本的行,如果匹配成功,则会在该行上执行用户想要的操作;反之,则不对行做任何处理。

awk 命令的基本格式为

1
awk [选项] '脚本命令' 文件名

awk 命令选项以及含义

选项 含义
-F fs 指定以 fs 作为输入行的分隔符,awk 命令默认分隔符为空格或制表符。
-f file 从脚本文件中读取 awk 脚本指令,以取代直接在命令行中输入指令。
-v var=val 在执行处理过程之前,设置一个变量 var,并给其设备初始值为 val。

脚本命令

awk 的强大之处在于脚本命令,它由 2 部分组成,分别为匹配规则和执行命令,如下所示

1
'匹配规则{执行命令}'

匹配规则

和 sed 命令中的 address 部分作用相同:用来指定脚本命令可以作用到文本内容中的具体行,可以使用字符串(比如 /demo/,表示查看含有 demo 字符串的行)或者正则表达式指定

注意

  • 整个==脚本命令是用单引号(’’)括起==,而其中的==执行命令部分需要用大括号({})括起来==
  • 在 awk 程序执行时,如果没有指定执行命令,则==默认会把匹配的行输出==;如果不指定匹配规则,则==默认匹配文本中所有的行==

awk 使用数据字段变量

awk 的主要特性之一是其处理文本文件中数据的能力,它会自动给一行中的每个数据元素分配一个变量。

默认情况下,awk 会将如下变量分配给它在文本行中发现的数据字段:

变量 数据字段
$0 代表整个文本行;
$1 代表文本行中的第 1 个数据字段;
$2 代表文本行中的第 2 个数据字段;
$n 代表文本行中的第 n 个数据字段。

在 awk 中,==默认的字段分隔符是任意的空白字符==(例如空格或制表符)。 在文本行中,每个数据字段都是通过字段分隔符划分的。awk 在读取一行文本时,会用预定义的字段分隔符划分每个数据字段。

awk 脚本命令使用多个命令

方法1

awk 允许将多条命令组合成一个正常的程序。要在命令行上的程序脚本中使用多条命令,只要在命令之间放个分号即可,例如:

1
echo "My name is Rich" | awk '{$4="Christine"; print $0}'

第一条命令会给字段变量 $4 赋值。第二条命令会打印整个数据字段。

方法2

可以一次一行地输入程序脚本命令,比如说:

1
2
3
echo "My name is Rich" | awk '{
> $4="Christine"
> print $0}'

在你用了表示起始的单引号后,bash shell 会使用 > 来提示输入更多数据,我们可以每次在每行加一条命令,直到输入了结尾的单引号。

awk从文件中读取程序

跟 sed 一样,awk 允许将脚本命令存储到文件中,然后再在命令行中引用,比如:

1
awk -F : -f awk.sh /etc/passwd

注意,在程序文件中,也可以指定多条命令,==只要一条命令放一行即可,之间不需要用分号==。

awk BEGIN关键字

awk 中还可以指定脚本命令的运行时机。默认情况下,awk 会从输入中读取一行文本,然后针对该行的数据执行程序脚本,但有时可能需要==在处理数据前运行一些脚本命令==,这就需要使用 BEGIN 关键字。

BEGIN 会强制 awk 在读取数据前执行该关键字后指定的脚本命令

例如:

1
2
awk 'BEGIN {print "The data3 File Contents:"}
> {print $0}' data3.txt

BEGIN 部分的脚本指令会在 awk 命令处理数据前运行,而真正用来处理数据的是第二段脚本命令。

awk END关键字

和 BEGIN 关键字相对应,END 关键字允许我们指定一些脚本命令,awk 会在==读完数据后执行它们==,例如:

1
2
3
awk 'BEGIN {print "The data3 File Contents:"}
> {print $0}
> END {print "End of File"}' data3.txt

当 awk 程序打印完文件内容后,才会执行 END 中的脚本命令。

awk 使用变量

在 awk 的脚本程序中,支持使用变量来存取值。awk 支持两种不同类型的变量:

  • 内建变量:awk 本身就创建好,用户可以直接拿来用的变量,这些变量用来存放处理数据文件中的某些字段和记录的信息。
  • 自定义变量:awk 支持用户自己创建变量。

内建变量

awk 程序使用内建变量来引用程序数据里的一些特殊功能

变量 功能
$0 代表整个文本行;
$1 代表文本行中的第 1 个数据字段;
$2 代表文本行中的第 2 个数据字段;
$n 代表文本行中的第 n 个数据字段。
FIELDWIDTHS 由空格分隔的一列数字,定义了每个数据字段的确切宽度。
一旦设定了 FIELDWIDTHS 变量的值,就不能再改变了,因此,这种方法并==不适用于变长的字段==
FNR 当前输入文档的记录编号,常在有多个输入文档时使用。
NR 输入流的当前记录编号。
FS 输入字段分隔符
RS 输入记录分隔符,默认为换行符 \n。
OFS 输出字段分隔符,默认为空格。
print 命令会自动将 OFS 变量的值放置在输出中的每个字段间
ORS 输出记录分隔符,默认为换行符 \n。
ARGC 命令行参数个数。
ARGIND 当前文件在 ARGC 中的位置。
ARGV 包含命令行参数的数组。
CONVFMT 数字的转换格式,默认值为 %.6g。
ENVIRON 当前 shell 环境变量及其值组成的关联数组。
ERRNO 当读取或关闭输入文件发生错误时的系统错误号。
FILENAME 当前输入文档的名称。
FNR ==当前数据文件==中的数据行数。
IGNORECASE 设成非 0 值时,忽略 awk 命令中出现的字符串的字符大小写。
NF 数据文件中的字段总数。
NR 已处理的输入记录==总数==。会持续计数直到处理完所有的数据文件
OFMT 数字的输出格式,默认值为 %.6g。
RLENGTH 由 match 函数所匹配的子字符串的长度。
TSTART 由 match 函数所匹配的子字符串的起始位置。

理解

  • 字段分隔符:作用于 awk 每次处理的一个单元
  • 记录分隔符:用于区分 awk 每次要处理的一个单元

自定义变量

awk 允许用户定义自己的变量在脚本程序中使用。awk 自定义变量名可以是任意数目的字母、数字和下划线,但不能以数字开头。更重要的是,awk 变量名==区分大小写==

也可以用 awk 命令行来给程序中的变量赋值,这允许我们在正常的代码之外赋值,即时改变变量的值,比如:

1
2
3
4
5
6
7
8
9
[root@localhost ~]# awk '
\> BEGIN{
\> testing="This is a test"
\> print testing
\> testing=45
\> print testing
\> }'
This is a test
45

需要注意的是,使用命令行参数来定义变量值会有一个问题,即设置了变量后,这个值在代码的 BEGIN 部分不可用,如下所示:

1
2
3
4
5
6
7
8
[root@localhost ~]$ cat script2
BEGIN{print "The starting value is",n; FS=","}
{print $n}
[root@localhost ~]$ awk -f script2 n=3 data1
The starting value is
data13
data23
data33

解决这个问题,可以用 -v 命令行参数,它可以实现在 BEGIN 代码之前设定变量。在命令行上,-v 命令行参数必须放在脚本代码之前,如下所示:

1
2
3
4
5
[root@localhost ~]$ awk -v n=3 -f script2 data1
The starting value is 3
data13
data23
data33

awk 使用数组

awk 使用分支结构

awk 使用循环结构

awk 使用函数

wget

wget是一个命令行的下载工具。对于我们这些 Linux 用户来说,几乎每天都在使用它。下面为大家介绍几个有用的 wget 小技巧,可以让你更加高效而灵活的使用 wget。

1
$ wget -r -np -nd http://example.com/packages/

这条命令可以下载 http://example.com 网站上 packages 目录中的所有文件。其中,-np的作用是不遍历父目录,-nd表示不在本机重新创建目录结构。

1
$ wget -r -np -nd --accept=iso http://example.com/centos-5/i386/

与上一条命令相似,但多加了一个–accept=iso选项,这指示 wget 仅下载 i386 目录中所有扩展名为 iso 的文件。你也可以指定多个扩展名,只需用逗号分隔即可。

1
$ wget -i filename.txt

此命令常用于批量下载的情形,把所有需要下载文件的地址放到 filename.txt 中,然后 wget 就会自动为你下载所有文件了。

1
$ wget -c http://example.com/really-big-file.iso

这里所指定的-c选项的作用为断点续传。

1
$ wget -m -k (-H) http://www.example.com/

该命令可用来镜像一个网站,wget 将对链接进行转换。如果网站中的图像是放在另外的站点,那么可以使用-H选项。

函数

函数指针

  • 函数的地址是存储其机器语言代码的内存开始的地址

  • 好处:可在在不同的时间使用不同的函数(可以把函数当做参数传递)

  • 声明:

    1
    2
    3
    4
    // 函数原型
    double sum(double, double)
    // 函数指针声明
    double (*ptrSum)(double, double)

    注意:

    • 该语句声明了一个指针ptrSum,指向一个函数
    • double *ptrSum(double, double)不是函数指针,而是:声明了一个函数ptrSum,返回 double *类型
  • 用法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 声明power函数
    int power(int, int);
    // 声明函数指针
    int (*powerPtr)(int, int);
    // 让声明的函数指针指向函数,以便调用
    powerPtr = power;
    // 调用
    powerPtr(3, 4)
    (*powerPtr)(3, 4)
  • 应用

    • 题目

      用函数指针实现加减乘除

    • 代码

      • Caculator.h

        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
        /**
        * author Qeuroal
        * date 2021/5/19
        * description
        */
        #ifndef BLOG141_CACULATOR_H
        #define BLOG141_CACULATOR_H
        #include <iostream>
        using namespace std;

        // 自定义计算器,使用函数指针

        /**
        * 加法
        * @return
        */
        double addition(double, double);
        /**
        * 减法
        * @return
        */
        double subtraction(double, double);

        /**
        * 乘法
        * @return
        */
        double multiplication(double, double);

        /**
        * 除法
        * @return
        */
        double division(double, double);

        /**
        * 打印计算结果
        * param double (*)(double , double ) 函数指针
        */
        void printResult(double (*)(double , double ), double, double);

        void printResult(double (*calcPtr)(double, double), double num1, double num2) {
        // 调用函数,打印结果
        double result = calcPtr(num1, num2);
        cout << "运算结果为:" << result << endl;
        return;
        }

        double addition(double num1, double num2) {
        return num1 + num2;
        }

        double subtraction(double num1, double num2) {
        return num1 - num2;
        }

        double multiplication(double num1, double num2) {
        return num1 * num2;
        }

        double division(double num1, double num2) {
        if (num2 == 0) {
        cout << "除数不能为0!!" << endl;
        return 0;
        }
        return num1 / num2;
        }



        #endif //BLOG141_CACULATOR_H
      • FuncPtr.cpp

        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
        /**
        * author Qeuroal
        * date 2021/5/19
        * description
        */
        #include <iostream>
        #include "Caculator.h"

        using namespace std;

        int main() {
        // 定义函数指针
        double (*calcPtr)(double, double) = nullptr;
        // 定义函数指针数组
        double (*calcPtrArray[])(double, double) = {};
        cout << "请输入两个操作数:";
        double num1, num2;
        cin >> num1 >> num2;
        cout << "请输入运算符:";
        char op;
        cin >> op;
        switch (op) {
        case '+':
        calcPtr = addition;
        break;
        case '-':
        calcPtr = subtraction;
        break;
        case '*':
        calcPtr = multiplication;
        break;
        case '/':
        calcPtr = division;
        break;
        }
        printResult(calcPtr, num1, num2);
        }
  • 注意

    • 要用函数指针的话,先定义函数,再定义函数指针;

    • C11 中可以使用 auto funcPtr = addition; 自动推断类型(自动类型推断需要确保变量的类型与初值类型一致);

    • 使用auto时,需要初始化,否则会报错;

    • 可以使用 typedef 简化声明:

      1
      2
      3
      typedef double (*funcPtr)(double, double);
      // 此时funcPtr是一个新的类型,而不是一个函数指针,真正的函数指针是calcPtr
      funcPtr = calcPtr;

面向对象

注意点。

  • 类一般是在头文件定义
  • .h文件 声明类的结构,.cpp文件实现类的成员函数

实例化方法(以无参构造为例)

假设定义了 Person 类。

  • 省略了默认构造:Person person; (在栈内存中直接分配空间)

  • 标准写法:Person person(); (在栈内存中直接分配空间)

  • 指针写法:Person *person = new Person(); (在堆内存分配空间,并用指针 person 指向该内存空间)

  • 如果构造函数中只有一个是带唯一的参数,如下所示:

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      class Person {

      public:
      Person(int age) {
      this->age = age;
      }

      Person(string name, int age) {
      this->name = name;
      this->age = age;
      }
      private:
      string name;
      int age;
      };
    • 实例化

      1
      Person person =20;

构造函数

  • 正常写法(略)

  • 简化写法:初始化参数列表写法

    1
    Person::Person(string name, int age) : name(name), age(age) {}

简单介绍

  • HTML + CSS + JavaScript
  • 结构 + 表现 + 交互

如何学习

  1. css 是什么
  2. css怎么用(快速入门)
  3. css选择器(重点+难点)
  4. 美化网页(文字、阴影、超链接、列表、渐变……)
  5. 盒子模型
  6. 浮动
  7. 定位
  8. 网页动画(特效效果),教程:菜鸟教程

什么是css

  • cssCascading Style Sheet层叠(级联)样式表

  • css:表现(美化网页):字体、颜色、边距、高度、宽度、背景图片、网页定位、网页浮动……

发展史

  • css1.0
  • css2.0DIV(块)+ CSS,提出了htmlcss 结构分离的思想,网页变得简单,利于 SEO
  • css2.1:浮动和定位
  • css3.0:圆角、阴影、动画……(浏览器兼容性)

练习格式

快速入门

style

  • 基本入门

    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>

    <!--规范,<style>可以编写css代码,每一个声明最好使用分号结尾
    语法:
    选择器 {
    声明1;
    声明2;
    声明3;
    }
    -->
    <style>
    h1 {
    color: rebeccapurple;
    }
    </style>

    </head>
    <body>
    <h1>我是标题</h1>
    </body>
    </html>
  • 建议使用这种方式

css优势

  • 内容和表现分离
  • 网页结构表现统一,可以实现复用
  • 样式十分丰富
  • 建议使用独立于 HTMLCSS 文件
  • 利于 SEO,容易被搜索引擎收录

css的3种导入方式

行内样式

1
2
<!--行内样式:在标签元素中,编写一个style属性,编写样式即可-->
<h1 style="color:aqua;">我是标题</h1>

内部样式:style标签

外部样式(推荐)

优先级:

就近原则:最近的是:行内样式,然后看内部样式和外部样式谁在上面

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
/*注释*/
h1 {
color: green;
}
</style>

<!--外部样式-->
<link rel="stylesheet" href="css/style.css">
</head>
<body>

<!--行内样式:在标签元素中,编写一个style属性,编写样式即可-->
<h1>我是标题</h1>
</body>
</html>

拓展:外部样式两种写法

  • 链接式:html(推荐)

    1
    <link rel="stylesheet" href="css/style.css">
  • 导入式:css2.1css2.1 特有的

    1
    2
    3
    <style>
    @import url("css/style.css");
    </style>
  • 缺点:导入式会先显示结构,再去渲染;链接式是一起生效

选择器

作用:选择页面上的某一个或者某一类元素

基本选择器

标签选择器

选择一类标签

1
2
3
4
5
6
7
8
9
10
/*标签选择器,会选择到页面上所有的这个标签的元素*/
h1 {
color: red;
background: antiquewhite;
border-radius: 24px;
}
p {
font-size: 80px;

}

类选择器:class

  • 语法

    1
    2
    3
    4
    类选择器的格式:
    .类的名称 {
    样式
    }
  • 好处

    好处:可以多个标签归类,是同一个class,可以复用;可以跨标签

  • 示例

    1
    2
    3
    4
    5
    6
    7
    */
    .title1 {
    color: red;
    }
    .title2 {
    color: blueviolet;
    }

ID选择器

  • ID 一定要唯一

  • 语法

    1
    2
    3
    4
    5
    id选择器:id必须保证全局唯一
    格式:
    #id名称 {

    }
  • 示例

    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
    /*
    id选择器:id必须保证全局唯一
    格式:
    #id名称 {

    }
    */
    #title1 {
    color: blueviolet;
    }

    .title2 {
    color: red;
    }

    h1 {
    color: green;
    }
    </style>
    </head>
    <body>
    <h1 id="title1" class="title2">标题1</h1>
    <h1 class="title2">标题2</h1>
    <h1 class="title3">标题3</h1>
    <h1>标题4</h1>
    <h1>标题5</h1>
    </body>
    </html>

优先级:

不遵循就近原则,是固定的:

id选择器 > class选择器 > 标签选择器

层次选择器

模型

后代选择器:空格

在某个元素的后面,如:祖爷爷->爷爷->爸爸->你

1
2
3
4
/* 后代选择器*/
body p {
background: red;
}

子选择器: >

只有一代,即儿子

1
2
3
4
/*子选择器*/
body>p {
background: blueviolet;
}

相邻兄弟选择器:+

同辈:哥哥,姐姐

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
/*相邻兄弟选择器:只有一个,相邻:对下*/
.activate + p {
background: aqua;
}
</style>
</head>
<body>
<p>p0</p>
<p class="activate">p1</p>
<p>p2</p>
<p>p3</p>
<ul>
<li>
<p>p4</p>
</li>
<li>
<p>p5</p>
</li>
<li>
<p>p6</p>
</li>
</ul>

<p class="activate">p7</p>
<p>p8</p>
</body>
</html>

通用选择器:~

1
2
3
4
/*通用兄弟选择器:当前选中元素的向下的所有兄弟元素*/
.activate ~ p {
background: green;
}

结构伪类选择器

伪类选择器:xx:xxx {}

示例

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--避免使用class和id选择器-->
<style>
/*1. ul的第一个子元素*/
ul li:first-child {
background: red;
}
/*2. ul的最后一个子元素*/
ul li:last-child {
background: green;
}
/*3. 选中p1:定位到父元素,选择当前的第一个元素*/
/*选择当前p元素的父级元素,选中父级元素的第一个元素,并且是当前元素才生效*/
p:nth-child(1) {
background: aqua;
}
/*选择当前p元素的父级元素,选中父级元素类型是当前元素的第2个元素*/
p:nth-of-type(2) {
background: blueviolet;
}
</style>
</head>
<body>
<h1>h1</h1>
<p>p1</p>
<p>p2</p>
<p>p3</p>
<ul>
<li>li1</li>
<li>li2</li>
<li>li3</li>
</ul>

</body>
</html>

属性选择器(常用)

idclass 结合

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.demo a {
float: left;
display: block;
height: 50px;
width: 50px;
border-radius: 10px;
background: blue;
text-align: center;
color: white;
text-decoration: none;
margin-right: 5px;
font: blod 20px/50px Arial;
line-height: 50px;
}


/*存在id属性的元素
格式:
a[属性名、属性名=属性值(值可以用正则)、属性名*=属性值(值可以用正则)] {}
=绝对等于
*= 包含这个值
^= 以这个值开头
$= 以这个结尾
*/
a[id] {
background: black;
}
/*id=first的元素*/
a[id="first"] {
background: red;
}
/*class有links的元素*/
a[class*="links"] {
background: yellow;
}
/*选中href中以http开头的元素*/
a[href^="http"] {
background: blueviolet;
}
a[href$="pdf"] {
background: rebeccapurple;
}
</style>
</head>
<body>

<p class="demo">
<a href="http://www.baidu.com" class="links item first" id="first">1</a>
<a href="https://qeuroal.top" class="links item activate" target="_blank" title="test">2</a>
<a href="images/123.html" class="links item">3</a>
<a href="images123.png" class="links item">4</a>
<a href="images123.png" class="links item">5</a>
<a href="abc">6</a>
<a href="/a.pdf">7</a>
<a href="/abc.pdf">8</a>
<a href="/abc.doc">9</a>
<a href="/abcd.doc" class="links item last">10</a>
</p>

</body>
</html>

美化网页元素

为什么要美化网页

  • 有效的传递页面信息
  • 美化网页,页面漂亮才能吸引用户
  • 凸显页面的主题
  • 提高用户的体验

span标签

重点要突出的字,使用 span 套起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#title1 {
font-size: 50px;
}
</style>
</head>
<body>
欢迎学习 <span id="title1">css</span>
</body>
</html>

字体样式

  • px:像素

  • em:缩进

  • 分开写:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <!--
    font-family 字体
    font-size 字体大小
    font-weight 字体粗细
    color 字体颜色
    -->
    <style>
    body {
    color: #a13d30;
    font-family: "Consolas", 楷体;
    }
    h1 {
    font-size: 50px;
    }
    .p1 {
    font-weight: bold;
    }
    </style>
  • 合并写

    1
    2
    3
    4
    5
    6
    <!--字体风格-->
    <style>
    p {
    font: oblique bolder 18px "楷体" ;
    }
    </style>

文本样式

颜色

color

  • 单词
  • rgb0~F
  • rgba:透明度, 0~1

文本对齐方式

text-align=center排版:居中、左、右

首行缩进

text-indent: 段落首行缩进,一般是2em(首行缩进2字符)

块高

height 块高

行高

line-height:单行文字上下居中line-height=height

装饰

text-decoration

文本图片水平对齐

vertical-align: middle;

示例

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--
颜色:
单词
RGB:0~F
RGBA:透明度, 0~1

text-align 排版:居中、左、右
text-indent 段落首行缩进,一般是2em(首行缩进2字符)
height 块高
line-height 行高;和块的高度一致,就可以上下居中
-->
<style>
h1 {
/*color: rgba(0, 255, 255, 0.5);*/
color: rgb(0, 255, 255);
text-align: center;
}

.p1 {
text-indent: 2em;
}
.p3 {
background: red;
height: 300px;
line-height: 300px;
}

/*下划线*/
.l1 {
text-decoration: underline;
}
/*中划线*/
.l2 {
text-decoration: line-through;
}
/*上划线*/
.l2 {
text-decoration: underline;
}
/*a标签(超链接)去下划线*/
a {
text-decoration: none;
}
.img1 {
width: 500px;
height: 400px;
}
/*水平对齐:参照物,a和b对齐*/
img, span {
vertical-align: middle;
}
</style>
</head>
<body>

<h1>故事介绍</h1>
<a href="">hell</a>
<p class="l1">123123421</p>
<p class="l2">123123421</p>
<p class="l3">123123421</p>

<p class="p1">
平静安详的元泱境界,每隔333年,总会有一个神秘而恐怖的异常生物重生,它就是魁拔!魁拔的每一次出现,都会给元泱境界带来巨大的灾难!即便是天界的神族,也在劫难逃。在天地两界各种力量的全力打击下,魁拔一次次被消灭,但又总是按333年的周期重新出现。魁拔纪元1664年,天神经过精确测算后,在第六代魁拔苏醒前一刻对其进行毁灭性打击。但谁都没有想到,由于一个差错导致新一代魁拔成功地逃脱了致命一击。很快,天界魁拔司和地界神圣联盟均探测到了魁拔依然生还的迹象。因此,找到魁拔,彻底消灭魁拔,再一次成了各地热血勇士的终极目标。
</p>

<p class="p2">
在偏远的兽国窝窝乡,蛮大人和蛮吉每天为取得象征成功和光荣的妖侠纹耀而刻苦修炼,却把他们生活的村庄搅得鸡犬不宁。村民们绞尽脑汁把他们赶走。一天,消灭魁拔的征兵令突然传到窝窝乡,村长趁机怂恿蛮大人和蛮吉从军参战。然而,在这个一切都凭纹耀说话的世界,仅凭蛮大人现有的一块杂牌纹耀,不要说参军,就连住店的资格都没有。受尽歧视的蛮吉和蛮大人决定,混上那艘即将启程去消灭魁拔的巨型战舰,直接挑战魁拔,用热血换取至高的荣誉。
</p>

<p class="p3">
If you eone to wipe your tears, guess what? I'll be there. William Shakespeare
</p>

<p>
<img src="images/img1.png" alt="" class="img1">
<span>aaaaaaaaaaa</span>
</p>


</body>
</html>

阴影

1
2
3
4
5
6
/*text-shadow
params: 阴影颜色,水平偏移,垂直偏移,阴影半径
*/
#price {
text-shadow: #20c1b4 10px 10px 10px;
}

超链接伪类

正常情况下,aa:hover

1
2
3
4
5
6
7
8
9
10
11
12
/*鼠标悬浮的状态(只需要记住hover)*/
a:hover {
color: orange;
font-size: 20px;
}
/*鼠标按住未释放的状态*/
a:active {
color: mediumseagreen;
}
a:visited {
color: blueviolet;
}

列表

1
2
3
4
5
6
7
8
9
10
11
12
/*
list-style:
none 去掉圆点
circle 空心圆
decimal 有序列表(数字)
square 正方形
*/
ul li {
height: 30px;
list-style: none;
text-indent: 1em;
}

背景

背景颜色

1
background: #a0a0a0;

背景图片

法一

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
div {
width: 1000px;
height: 700px;
border: 1px solid red;
/*默认:全部平铺 repeat*/
background-image: url("images/pdx.jpg");
}

.div1 {
background-repeat: repeat-x;
}
.div2 {
background-repeat: repeat-y;
}
.div3 {
background-repeat: no-repeat;
}
</style>
</head>
<body>
<div class="div1"></div>
<div class="div2"></div>
<div class="div3"></div>
</body>
</html>

法二

1
2
/*颜色 图片 图片位置 平铺方式*/
background: red url("../images/down.png") 270px 10px no-repeat;

渐变

网站

盒子模型

什么是盒子

  • margin:外边距

    所有的 bodymargin 默认为8

  • padding:内边距

  • border:边框

边框

  • 边框的粗细
  • 边框的样式
  • 边框的颜色

实现

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
/*常见操作*/
h1, ul, li, a, body {
/*body总有一个默认的外边距margin: 0*/
margin: 0;
padding: 0;
text-decoration: none;
}
#app {
width: 300px;
/*border
params:粗细,样式(实线/虚线),颜色
*/
border: 3px solid red;
}

h2 {
font-size: 18px;
background: #4986ee;
text-align: center;
line-height: 40px;
color: white;
}
form {
background: #a0a0a0;
}
div:nth-of-type(1) input {
border: 2px solid black;
}
div:nth-of-type(2) input {
border: 2px dashed blueviolet;
}
div:nth-of-type(3) input {
border: 2px dashed green;
}

内外边距

margin, padding 参数

  • 一个参数:上下左右
  • 两个参数:上下、左右
  • 四个参数:上、左、下、右(顺时针)

盒子的计算方式

你这个元素到底多大?

美工+前端

盒子最终大小:margin + border + padding + 内容的大小(上图内容的大小为:296*22

示例

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--外边距的妙用:居中-->
<style>

#app {
width: 300px;
/*border
params:粗细,样式(实线/虚线),颜色
*/
border: 3px solid red;
padding: 0 0 0 0;
/*margin
一个参数:上下左右
两个参数:上下、左右
四个参数:上、左、下、右(顺时针)
*/
margin: 0 auto ;
}

h2 {
font-size: 18px;
background: #4986ee;
text-align: center;
line-height: 40px;
/*上下左右都为0*/
margin: 0;
color: white;
}
form {
background: #a0a0a0;
}
input {
border: 1px solid black;
}
div:nth-of-type(1) {
border: 1px solid blueviolet;
padding: 10px;
}
</style>
</head>
<body>
<!--div为了层次更清楚;id="app":在vue里面id设置为app,代表一个应用,整个应用不是在body里面,而是在div里面,是需要定位的-->
<div id="app">
<h2>会员登录</h2>
<form action="#">
<!--里面的属性都用div包起来-->
<div>
<span>姓名:</span>
<input type="text">
</div>
<div>
<span>密码:</span>
<input type="password">
</div>
<div>
<span>邮箱:</span>
<input type="text">
</div>
</form>
</div>
</body>
</html>

圆角边框

有4个角

1
2
3
4
5
6
7
8
9
10
11
12
div {
width: 100px;
height: 100px;
border: 10px solid red;
/*
一个参数:所有角
两个参数:左上 右下、右上 左下
四个参数:左上 右上 右下 左下(顺时针方向);而且一个参数管一个角,以及对应的两边的边长如下图
*/
/*圆圈:圆角=宽度的一半*/
border-radius: 50px 20px;
}

圆形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
div {
width: 50px;
height: 50px;
margin: 100px;
background: red;
/*border不在设定的长宽,所以得用border-radius也得算上去,这里给去了
/*border: 10px solid red;*/
border-radius: 50px 0px 0px 0px;
}
img {
width: 50px;
height: 50px;
border-radius: 25px;
}

这里是半圆形:可以这样理解:border-radius只控制半径为参数的那个 1/4的圆,widthheight 控制的是区域的大小

阴影

补充

  • img是内联元素 ,所以无法使用 margin: 0 auto; 居中

    1
    2
    3
    4
    5
    6
    7
    img {
    width: 150px;
    height: 150px;
    border-radius: 75px;
    margin: 0 auto;
    box-shadow: 10px 10px 100px yellow;
    }

    所以用 margin: 0 auto; 居中

    要求:

    • 外面是块元素
    • 块元素有固定的宽度

div的外层是body可以使用 margin: 0 auto;居中,因为body 是块元素,如果想让body里面的所有元素居中,可以这样

1
2
3
4
5
6
div {
width: 1000px;
border: 10px solid red;
margin: 0 auto;
text-align: center;
}

设置:text-align:center

  • 让内联 元素居中方法:

    • 块元素设置 text-align:center,使块内所有元素居中

    • 将内联元素设置为块元素: display: block

      1
      2
      3
      4
      5
      6
      7
      8
      img {
      width: 150px;
      height: 150px;
      border-radius: 75px;
      margin: 0 auto;
      box-shadow: 10px 10px 100px yellow;
      display: block;
      }

浮动

标准文档流

  • 不局部:自上而下的拼

  • 布局过:

  • 块级元素:独占一行,且只有块元素设置大小才能被显示

    • h1~h6
    • p
    • div
    • 列表
    • ……
  • 行内元素(内联元素):不独占一行

    • span
    • a
    • img
    • strong
    • ……

行内元素可以包含在块级元素里面,但是块级元素不能包含在行内元素里面

display

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
div {
width: 100px;
height: 100px;
border: 1px solid red;
display: inline-block;
}

span {
width: 100px;
height: 100px;
border: 1px solid red;
/*
inline-block 既是行内元素又是块元素(保持块元素的特性,可以写在一行)
block 块元素
inline 行内元素
none 消失
*/
display: inline-block;
}

这个也是一种实现行内元素排列的方式,但我们很多情况都是用 float

float

  • 左右浮动:float

    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
    div {
    margin: 10px;
    padding: 5px;
    }
    #app {
    border: 1px #000 solid;
    }

    .layer01 {
    border: 1px #f00 dashed;
    display: inline-block;
    float: right;
    clear:both;

    }
    .layer02 {
    border: 1px #00f dashed;
    display: inline-block;
    float: right;
    clear:both;

    }

    .layer03 {
    border: 1px #060 dashed;
    display: inline-block;
    float: right;
    clear:both;

    }

    .layer04 {
    border: 1px #666 dashed;
    font-size: 12px; display: inline-block;
    /*float: right;*/
    line-height: 23px;
    clear:both;
    }

父级边框塌陷的问题

clear

  • right: 右侧不允许有浮动元素,如果有,排到下面去(下同)
  • left: 左侧不允许有浮动元素
  • both: 两侧允许有浮动元素
  • none:

解决方案

  1. 增加父级元素高度

    1
    2
    3
    4
    #app {
    border: 1px #000 solid;
    height: 800px;
    }
  2. 增加一个空的 div 标签,清除浮动

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <div id="app">
    <div class="layer01"><img src="images/1.jpg" alt=""></div>
    <div class="layer02"><img src="images/5.jpg" alt=""></div>
    <div class="layer03"><img src="images/3.jpg" alt=""></div>
    <div class="layer04">
    浮动的盒子可以向左浮动,也可以向右浮动,直到它的外边缘碰到包含框或另一个浮动盒子位置
    </div>
    <div class="clear"></div>
    </div>
    1
    2
    3
    4
    5
    .clear {
    clear: both;
    margin: 0;
    padding: 0;
    }
  3. overflow

    • params:

      1
      2
      3
      hidden: 隐藏
      scroll: 滚动条
      auto:
    • 在父级元素中增加一个overflow: hidden

      1
      2
      3
      4
      5
      #app {
      border: 1px #000 solid;
      /*height: 800px;*/
      overflow: hidden;
      }
  4. 父类添加一个伪类:after (推荐)

    可以避免空 div

    1
    2
    3
    4
    5
    #app:after {
    content: "";
    display: block;
    clear: both;
    }
  5. 小结

    • 浮动元素后面增加空 div

      优势

      • 简单

      劣势:

      • 代码中尽量避免空 div
    • 设置父元素的高度

      优势:

      • 简单

      劣势:

      • 元素假设有了固定的高度,就会被限制
    • overflow: hidden

      优势:

      • 简单

      劣势:

      • 下拉的一些场景避免使用

      • 如果超出,就会被切掉,但是宁愿被切掉,也不要滚动条,很诡异

    • 父类添加一个伪类:after

      优势:

      • 写法稍微复杂一点,但是没有副作用,推荐使用

对比

  • display
    • 方向不可控
    • 不用管理父级边框塌陷
  • float
    • 浮动起来的话会脱离标准文档流,所以要解决父级边框塌陷的问题

定位

相对定位

  • position: reletive

  • 相对定位:相对于自己原来的位置进行偏移,相对定位的话,它仍然在标准文档流中,它原来的位置会被保留

    1
    2
    3
    4
    5
    /*距离上面-20px*/
    top: -20px;
    left: 10px;
    bottom: -10px;
    right: 20px;
  • 示例

    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!--相对定位:相对于自己原来的位置进行偏移-->
    <style>
    body {
    padding: 20px;
    }
    div {
    margin: 10px;
    padding: 5px;
    font-size: 12px;
    line-height: 25px;
    }

    #app {
    border: 1px solid #666;
    padding: 0;
    }
    #first {
    background-color: #a0a0a0;
    border: 1px dashed #b61818;
    /*相对定位:上下左右*/
    position: relative;
    /*距离上面-20px*/
    top: -20px;
    left: 10px;
    }
    #second {
    background-color: #e2cccc;
    border: 1px dashed #59b631;
    }
    #third {
    background-color: #eade86;
    border: 1px dashed #bb21d5;
    position: relative;
    bottom: -10px;
    right: 20px;
    }
    </style>

    </head>
    <body>

    <div id="app">
    <div id="first">第1个盒子</div>
    <div id="second">第2个盒子</div>
    <div id="third">第3个盒子</div>
    </div>
    </body>
    </html>

练习

实现

代码

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#app {
width: 300px;
height: 300px;
border: 2px solid red;
padding: 10px;
}
a {
width: 100px;
height: 100px;
text-align: center;
line-height: 100px;
background-color: #ea85c2;
/*加了border位置就不准了,因为border+内容的大小才是整个元素的大小*/
/*border: 1px solid #000000;*/
color: white;
text-decoration: none;
display: block;
}

a:hover {
background-color: #0c91ea;
}

.a2, .a4 {
position: relative;
left: 200px;
top: -100px;
}
.a3 {
position: relative;
}
.a5 {
position: relative;
left: 100px;
top: -300px;
}
</style>
</head>
<body>
<div id="app">
<a href="#" class="a1">链接1</a>
<a href="#" class="a2">链接2</a>
<a href="#" class="a3">链接3</a>
<a href="#" class="a4">链接4</a>
<a href="#" class="a5">链接5</a>
</div>
</body>
</html>

绝对定位

  • 定位:基于xxx定位,只有上下左右
    • 没有父级元素定位的前提下,相对于浏览器定位;
    • 父级元素存在定位,我们通常相对于父级元素进行偏移;
    • 在父级元素范围内移动
  • 相对于父级或浏览器的位置进行偏移,绝对定位的话,它不在标准文档流中,它原来的位置不会被保留

固定定位

  • 定位:无论怎么走,都在固定位置

  • 示例

    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
    body {
    height: 1000px;
    }
    div:nth-of-type(1) {
    width: 100px;
    height: 100px;
    background-color: #ef67ad;
    text-align: center;
    line-height: 100px;
    /*绝对定位:相对于浏览器*/
    position: absolute;
    right: 0;
    bottom: 0;
    display: block;
    }

    div:nth-of-type(2) {
    width: 50px;
    height: 50px;
    background-color: #3383d9;
    /*固定定位:定死了;很多导航栏不动,就是通过固定定位来实现的*/
    position: fixed;
    right: 0;
    bottom: 0;
    display: block;
    }
    </style>
    </head>
    <body>

    <div>div1</div>
    <div>div2</div>

    </body>
    </html>

z-index

理解图层

实践

z-index:默认是0,最高无限;习惯用999代表最高层

  • html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="css/style.css">
    </head>
    <body>

    <div id="app">
    <ul>
    <li><img src="images/bg.webp" alt=""></li>
    <li class="tipText">学习微服务,找狂神</li>
    <li class="tipBg"></li>
    <li>时间:2021-1-1</li>
    <li>地点:卢浮宫</li>
    </ul>
    </div>
    </body>
    </html>
  • css

    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
    #app {
    width: 320px;
    padding: 0;
    margin: 0;
    overflow: hidden;
    font-size: 12px;
    line-height: 25px;
    border: 1px black solid;
    }

    ul, li {
    padding: 0;
    margin: 0;
    list-style: none;
    }

    /*父级元素相对定位*/
    #app ul {
    position: relative;
    }

    .tipText, .tipBg {
    position: absolute;
    width: 320px;
    height: 25px;
    top: 167px;
    color: white;
    }

    .tipText {
    /*最低为0*/
    /*z-index: 999;*/
    }

    .tipBg {
    background: black;
    /*背景透明度*/
    opacity: 0.5;
    /*现在一般不可以用,早期可以用*/
    filter: alpha(opacity=50);
    }

动画

菜鸟教程

canvas 动画:点击这里

卡巴斯基安全监控网站点击这里

源码之家点击这里

前端

  • HTML:网页基本结构

  • CSS:美化网页

  • JS:使得网页动起来产生交互性行为

初识HTML

什么是HTML

Hyper Text Markup Language(超文本标记语言)

  • 超文本:文字、图片、音频、视频、动画等

W3C标准

w3c: 万维网联盟

标准:

  • 结构化标准语言(HTMLXML)
  • 表现标准语言(css
  • 行为标准 (DOMECMAScript

基本信息

  • 注释:<!-- 注释 -->
  • <!DOCTYPE html>:告诉浏览器,我们要使用什么规范
  • <html> </html>: 总标签,只有在里面写才有意义
  • head标签:网页头部
  • body标签:网页主体
  • title标签:网页标题
  • meta 标签:描述性标签,用来描述网站的一些信息,可以用来被搜索到;一般用来做 SEO

基本标签

标题标签

1
2
3
4
5
6
<h1>1级标签</h1>
<h2>2级标签</h2>
<h3>3级标签</h3>
<h4>4级标签</h4>
<h5>5级标签</h5>
<h6>6级标签</h6>

段落标签

1
2
3
<p>hello world</p>
<p>hello world2</p>
<p>hello world3</p>

换行标签

即便换行了,也是一个段落

1
2
3
hello world <br/>
hello world2 <br/>
hello world3 <br/>

水平线标签

1
<hr/>

字体样式标签

  • 粗体斜体

    1
    2
    粗体:<strong>Hello html</strong>
    斜体:<em>Hello html</em>

注释和特殊符号

注释

1
<!--注释-->

特殊符号

  • 空格:空 格:空&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;格

  • 大于号:&gt;

  • 小于号:&lt;

  • 版权符号:&copy;

  • 记忆方式:

    • &字母:可以直接调
    • 百度查(不推荐)

图像标签

常见图像格式

  • JPG
  • GIF
  • PNG
  • BMP:位图
  • ……

嵌入图片

1
2
3
4
5
6
7
8
<!--
src:图片地址(必填):
绝对地址:E:\SourceCode\HTMLSpace\learnProject\resources\images\pdx.jpg
相对路径:../resources/images/pdx.jpg

alt: 图片名字(必填)
-->
<img src="../resources/images/pdx.jpg" alt="Qeuroal" title="悬停文字" width="300" height="480">

链接标签

可以点的东西都是链接

页面间跳转:从一个页面跳转到另一个页面

文本超链接

1
2
3
4
5
6
7
8
<!--a标签
href:必填,表示要跳转到哪个页面
target:表示窗口在哪里打开
_blank: 在新标签页中打开
_self:在自己的网页打开
-->
<a href="test1.html" target="_blank">点击我跳转test1</a>
<a href="https://qeuroal.top/" target="_self">点击我跳转到主页</a>

图像超链接

1
2
3
<a href="test1.html">
<img src="../resources/images/pdx.jpg" alt="Qeuroal" title="点击我跳转test1" width="300" height="480">
</a>

页面内跳转:页面内跳转

锚链接

1
2
3
4
5
6
7
8
9
10
<!--使用name作为标记-->
<a name="top">顶部</a>

<!--锚链接:页面内跳转
1. 需要一个锚标记
2. 跳转到标记
3. #: 通过#跳到标记
-->
<a href="#top">回到顶部</a>
<a href="链接标签.html#down">跳转</a>

功能性连接

邮件链接

1
2
3
4
<!--功能性连接
邮件链接:mailto
-->
<a href="mailto:xxxxx@163.com"></a>

qq链接

1
2
3
<a target="_blank" href="http://wpa.qq.com/msgrd?v=3&uin=&site=qq&menu=yes">
<img border="0" src="http://wpa.qq.com/pa?p=2::53" alt="你好,加我领取资料" title="你好,加我领取资料"/>
</a>

块元素和行内元素

块元素

  • 无论内容多少,该元素独占一行
  • ph1-h6、…)

行内元素

  • 内容撑开宽度,左右都是行内元素的可以排在一行
  • astrongem、…)

列表标签

什么是列表

列表的分类

有序列表

1
2
3
4
5
6
7
8
9
10
<!--有序列表
应用:试卷、问答……
-->
<ol>
<li>java</li>
<li>python</li>
<li>运维</li>
<li>前端</li>
<li>c/c++</li>
</ol>

无序列表

1
2
3
4
5
6
7
8
9
10
<!--无序列表
应用:导航、侧边栏……
-->
<ul>
<li>java</li>
<li>python</li>
<li>运维</li>
<li>前端</li>
<li>c/c++</li>
</ul>

自定义列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--自定义列表
dl:标签
dt:列表名称
dd:列表选项

应用:公司网站底部
-->
<dl>
<dt>学科</dt>
<dd>java</dd>
<dd>python</dd>
<dd>Linux</dd>
<dd>C</dd>

<dt>位置</dt>
<dd>唐山</dd>
<dd>石家庄</dd>
</dl>

表格标签

为什么使用表格

  • 简单通用
  • 结构稳定

基本结构

  • 单元格
  • 跨行
  • 跨列

实现

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
<!--表格table
行:tr
列:td
-->
<table border="1px">
<tr>
<!--colspan 跨列-->
<td colspan="4">1</td>
<td>2</td>
<td>3</td>
<td>123</td>

</tr>
<tr>
<!--colspan 跨行-->
<td rowspan="2">4</td>
<td>5</td>
<td>6</td>
</tr>
<tr>
<td>7</td>
<td>8</td>
<td>9</td>
</tr>
</table>

媒体元素

视频元素video

1
2
3
4
5
6
<!--视频
src: 资源路径
controls: 控制条
autoplay: 自动播放
-->
<video src="../resources/video/起风了.mp4" controls autoplay></video>

音频元素audio

1
2
<!--音频-->
<audio src="../resources/audio/起风了.mp3" controls autoplay></audio>

页面结构分析

1
2
3
4
5
6
7
8
9
10
11
<header>
<h2>网页头部</h2>
</header>

<section>
<h2>网页主体</h2>
</section>

<footer>
<h2>网页脚部</h2>
</footer>

iframe内联框架

1
2
3
4
5
6
7
8
<!--iframe内联框架
src: 地址
frameborder:
w-h:宽度-高度
-->
<iframe src="https://www.baidu.com" name="baidu" frameborder="0" width="1000px" height="800px"></iframe>

<a href="https://qeuroal.top" target="baidu">点击跳转</a>

表单语法

规定

  • input标签 都要有一个name

表单元素格式

文本输入框

1
2
3
4
5
6
7
8
<!--文本输入框:input type="text"
value="Qeuroal" 默认初始值
maxlength="8" 最长能写几个字符
size="30" 文本框的长度
-->
<p>用户名:<input type="text" placeholder="请输入用户名" name="username" ></p>
<!--密码框:input type="password"-->
<p>密码:<input type="password" placeholder="请输入密码" name="pwd"></p>

单选框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--单选框
input type="radio"
value: 单选框的值
name: 表示组,组一样才能选择一个
-->
<p>性别:
<input type="radio" value="boy" name="sex" checked/>
<input type="radio" value="girl" name="sex"/>
</p>

<p>
<input type="submit">
<input type="reset">
</p>

多选框

1
2
3
4
5
6
7
8
9
<!--多选框
input type="checkbox"
-->
<p> 爱好:
<input type="checkbox" value="sleep" name="hobby">睡觉
<input type="checkbox" value="chat" name="hobby">聊天
<input type="checkbox" value="code" name="hobby" checked>码代码
<input type="checkbox" value="game" name="hobby">游戏
</p>

按钮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--
按钮
input type="button" 普通按钮
input type="image" 图像按钮
input type="submit" 提交按钮
input type="reset" 充值
-->
<p>按钮:
<input type="button" name="btn1" value="点击变长">
<input type="image" name="imgBtn1" src="../resources/images/pdx.jpg">
</p>

<p>
<input type="submit">
<input type="reset" value="清空表单">
</p>

下拉框(列表框)

1
2
3
4
5
6
7
8
9
10
<!--下拉框,列表框
-->
<p>国家:
<select name="countries">
<option value="china">中国</option>
<option value="eth" selected>瑞士ETH</option>
<option value="us">美国</option>
<option value="dg">德国</option>
</select>
</p>

文本域

1
2
3
<p>反馈 <br>
<textarea name="textarea" cols="30" rows="10" placeholder="请输入反馈信息"></textarea>
</p>

文件域

1
2
3
4
<p>
<input type="file" name="files">
<input type="button" value="上传" name="upload">
</p>

搜索框及简单验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!--邮件验证-->
<p> 邮箱:
<input type="email" name="email">
</p>

<!--url-->
<p> url:
<input type="url" name="url">
</p>

<!--数字验证-->
<p> 数字:
<input type="number" name="num" max="100" min="0" step="1">
</p>

<!--滑块-->
<p> 音量:
<input type="range" name="voice" min="0" max="100">
</p>

<!--搜索框-->
<p> 搜索:
<input type="search" name="search">
</p>

表单的应用

只读 readonly

1
<p>用户名:<input type="text" placeholder="请输入用户名" name="username" value="admin" readonly></p>

禁用 disabled

1
<input type="radio" value="boy" name="sex" checked disabled/>

隐藏域 hidden

1
<p>密码:<input type="password" placeholder="请输入密码" name="pwd" hidden></p>

效果

增强鼠标可用性

1
2
3
4
<p>
<label for="mark">你点我试试</label>
<input type="text" id="mark">
</p>

表单初级验证

为什么要进行表单验证

  • 减轻服务器的压力
  • 保证数据的安全性

常用方式

  • placeholder :提示信息

    1
    <textarea name="textarea"  cols="30" rows="10" placeholder="请输入反馈信息"></textarea>
  • required:非空判断

    1
    <input type="search" name="search" required>
  • pattern:正则表达式

    • 如何查:常用正则表达式点击这里

    • 使用:

      1
      2
      3
      4
      <!--自定义邮箱-->
      <p>自定义邮箱
      <input type="email" name="diyMail" pattern="\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*">
      </p>

总结

第一个flask程序

Flask(__name__)

1
2
3
4
5
# 初始化一个Flask对象
# Flask()
# 需要传递一个参数 __name__
# 1. 方便flask框架去寻找资源
# 2. 方便flask插件比如Flask-Sqlalchemy出现错误的时候,好去寻找问题所在的位置

@app.route()

1
2
3
4
5
6
7
# @app.route是一个装饰器(可以看成url的后缀:127.0.0.1:5000/login 的 /login)
# @开头,并且在函数的上面,说明是装饰器
# 这个装饰器的作用是做一个url和视图函数(习惯性的命名,就是这里的hello_world函数)的映射
# 127.0.0.1:5000 -> 去请求hello_world这个函数然后将结果返回给浏览器
@app.route('/')
def hello_world():
return “我是第一个flask程序”

app.run():

启动一个应用服务器,来接收用户的请求。

类似于:

1
2
while (True):
listen()

设置debug模式

  1. app.run() 中传入一个关键字参数debugapp.run(debug=True), 就设置当前项目为 debug 模式
  2. debug 模式的两大功能
    • 当程序出现问题时,可以在也米那种看到错误信息和出错的位置
    • 只要修改了项目中的 Python 文件,程序会自动加载,不需要手动重新启动服务器

使用配置文件

  1. 新建一个 .py文件,这里设置为 config.py

  2. 然后设置大写,如:DEBUG=True

  3. app进行关联

    1
    2
    3
    4
    # 假设配置文件为config
    import config

    app.config.from_object(config)
  4. 还有许多的其他参数,都是放在这个配置文件中,比如 SECRET_KEYSQLALCHEMY 这些配置都是在这个文件中

URL传参

  1. 参数的作用:可以在相同的url,但是指定不同的参数,来加载不同的数据

  2. 在flask中如何使用参数

    1
    2
    3
    @app.route("/article/<id>")
    def article(id):
    return "您请求的参数是:%s" %id
    • 参数需要放在两个尖括号中
    • 视图函数中需要放和url中的参数同名的参数

反转URL

正转

通过url可以取到视图函数的内容

反转

  1. 从视图函数到url的转换叫做反转url(通过视图函数的名称,可以得到指向的url
  2. 用处
    • 在页面重定向的时候,会使用url反转
    • 在模板中也会使用url反转

函数讲解

  • url_for():
    • params:视图函数名称, 视图函数对应的参数(如果视图函数有参数的话),如 url_for("article", id="123")
    • return: 视图函数对应的url

页面跳转和重定向

  1. 用处:在用户访问一些需要登录的页面的时候,如果用户没有登录,那么可以让他重定向到登录页面

  2. 实现

    1
    2
    3
    4
    5
    6
    7
    8
    from flask import Flask, redirect, url_for

    @app.route("/question/<isLogin>/")
    def question(isLogin):
    if isLogin == '1':
    return "这是发布问答页面"
    else:
    return redirect(url_for("login"))

函数讲解

  • redirect()
    • params: url,如 redirect("/login123/")或者 redirect(url_for("login")),建议使用第二种,只要视图函数名字不变, @app.route(/login123/)里面的 login123怎么变都没关系

模板渲染和参数

如何渲染模板

  1. 模板放在 templates 文件夹下

  2. flask 中导入 render_template 函数

  3. 在试图函数中,使用 render_template 函数渲染模板。

    注意:只需要填写模板的名字,不要填写 templates 这个文件夹的路径。如果在 templates 文件夹下创建了一个新的文件夹,那么就要添加上文件名(render_template() 函数的文件名是相对路径)

模板传参

  • 如果只有一个或者少量参数,直接在 render_template() 函数中添加关键字参数就可以了
  • 如果有多个参数的时候,那么可以先把所有的参数放在字典中,然后在 render_template 中,使用 **字典名 把字典转换为关键参数传递进去,这样的代码更方便管理和应用

函数讲解

  • render_template()
    • params: 渲染文件名(渲染文件放在template文件夹下), username = 用户名 (username 保证和渲染文件中的变量名保持一致), … 。如 render_template("index.html", username = "Qeuroal")
    • 如果参数特别多的话,可以先定义一个字典(context),然后把字典传入函数内,如 render_template("index.html", **contet)

模板访问属性和字典

在模板中如果要使用一个变量,语法是 {{ params }}

  • 访问类的属性:
    • 和在Python文件中一致
    • 或者类似于这样: Person["name"]
  • 访问字典:
    • py文件一样: dict[key]
    • dict.key

实现

template1.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@app.route("/")
def index():
class Person():
name = "Qeuroal"
age = 24

p = Person()
testDict = {
"key": "key",
"value": "value"
}
context = {
"username": "Qeuroal",
"gender": "男",
"age": 24,
"person": p,
"testDict": testDict
}

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
这是HTML文件中出现的文字
<p>用户名: {{ username }}</p>
<hr>
<p>用户名: {{ person.name }}</p>
<p>年龄: {{ person["age"] }}</p>
<hr>
<p>key: {{ testDict.key }}</p>
<p>value: {{ testDict['value'] }}</p>
</body>
</html>

HTML

if判断

语法

1
2
3
4
5
6
7
{% if xxx and xxx %}
xxxx
{% elif xxx and xxx%}
xxxx
{% else %}
xxxx
{% endif %}

if的使用和python中相差无几。

for循环遍历列表和字典

字典遍历

字典的遍历,语法和 python 一样,可以使用 items(), keys(), values(), iteritems(), iterkeys(), itervalues()

1
2
3
{% for k,v in user.items() %}
<p>{{ k }}: {{ v }}</p>
{% endfor%}

列表遍历

语法和 python 一样

1
2
3
{% for website in websites %}
<p>{{ website }}</p>
{% endfor %}

过滤器

作用对象是模板中的变量:

介绍和语法

  • 介绍:过滤器可以处理变量,把原始的遍历经过处理后再展示出来。作用的对象是变量

  • 语法:

    1
    {{ avator|default("xxx")}}

default过滤器

如果当前变量不存在,这时候可以指定默认值。

length过滤器

求列表,字符串,字典,元组的长度。类似于:python 中的 len()

常用的过滤器

  • abs(value)
  • default(value, defaultValue, boolean=false)
  • escape(value)
  • first(value)
  • format(value, *args, **kwargs)
  • last(value)
  • length(value)
  • join(value, d="")
  • safe(value)

继承和block

继承

  • 作用:可以把一些公共的代码放在父模板中,避免每个模板写同样的代码

  • 语法:

    1
    {% extends "base.html" %}
  • 如果想在子模板中实现自己的内容:需要定义接口,类似于 java 的接口:html子模板需要实现自己内容,必须要放在父模板定义的接口里面,即block,不能在接口外面写代码

block

  • 作用:可以让子模板实现一些自己的需求。父模板需要提前定义好。
  • 注意点:
    • 子模板中的代码,必须放在block块中;
    • 可以定义多个 block

URL链接

使用 url_for(视图函数名称) 可以反转成url。

加载静态文件

1
2
3
4
5
6
<style>
a {
background: red;
color: black;
}
</style>

样式代码一般不会写在模板中,因为:维护性差,一般样式文件抽取成 css 文件(static 文件夹中是存放静态文件的)

语法

url_for("static", filename="路径")

  • 如:url_for("static", filename="css/index.css")

  • 静态文件,flask会从 static 文件夹中开始寻找,所以不需要写 static 这个路径了

  • 可以加载 css文件, js 文件,image 文件。

示例代码

1
2
3
4
5
6
{# 加载css文件 #}
<link rel="stylesheet" href="{{ url_for("static", filename="css/index.css") }}">
{# 加载js文件 #}
<script src="{{ url_for("static", filename="js/index.js") }}"></script>
{# 加载image文件 #}
<img src="{{ url_for("static", filename="images/pdx.jpg") }}" alt="">

注意点

装饰器

  • 装饰器为 @app.route('/login/'),网址可以是:http://127.0.0.1:5000/loginhttp://127.0.0.1:5000/login/
  • 装饰器为 @app.route('/login'),网址只能是:http://127.0.0.1:5000/login/

MySQL

安装(win10)

  • 我选择的是 Server only

  • 设置密码:

  • 打开命令行,并输入上一步设置好的密码

  • 下载地址

  • 一般直接下一步

  • 设置初始密码命令:

    1
    mysqladmin -uroot password [password]
  • 如果没有安装 .net Framework 4,就在那个提示框中,找到下载的 url 下载;

  • 如果没有安装 Microsoft visual C++ x64,那么就需要谷歌或者百度下载这个软件进行安装即可。

MySQL-python中间件介绍与安装

linux, macos

  1. 进入虚拟环境
  2. 输入 pip install mysql-python

windows系统

  • windows 在这里下载,看 python 安装的是32位还是64位,对应下载
  • 进入虚拟环境: conda activate 虚拟环境名
  • 安装:pip install mysqlclient-1.4.6-cp37-cp37m-win_amd64.whl

Flask-SQLAlchemy的介绍与安装

  • ORM: Object Relationship Mapping (关系模型映射);

  • flask-sqlalchemy 是一套 ORM框架

  • 模型关系映射

    • delete(article1):把 article表中和article1属性对应相同的那条数据删除。
    • 好处:ORM使得我们操作数据库非常简单,就像操作对象是一样的,非常方便。因为一个表就抽象成了一个类,一条数据就抽象成了该类的一个对象。
  • 安装:pip install flask-sqlalhemy,如果是在 macos 或者 linux 中没有权限的话这样安装: sudo pip install flask-sqlalhemy

Flask-SQLAlchemy的使用

  1. 初始化和设置数据库配置信息:

    • 使用 flask_sqlalchemy 中的 SQLAlchemy 进行初始化

      1
      2
      3
      from flask_sqlalchemy import SQLAlchemy
      app = Flask(__name__)
      db = SQLAlchemy(app)
  2. 设置配置信息:在 config.py 文件中添加以下配置信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # dialect+driver://username:password@host:port/database
    DIALECT = "mysql"
    DRIVER = "mysqldb"
    USERNAME = "root"
    PASSWORD = "toor"
    HOST = "127.0.0.1"
    PORT = "3306"
    DATABASE = "dbDemo1"

    SQLALCHEMY_DATABASE_URI = "{}+{}://{}:{}@{}:{}/{}?charset=utf8".format(DIALECT, DRIVER, USERNAME, PASSWORD, HOST, PORT, DATABASE)
    # 去除警告
    SQLALCHEMY_TRACK_MODIFICATIONS = False
  3. 添加配置文件

    1
    2
    3
    import config

    app.config.from_object(config)
  4. 测试是否连接成功

    1
    db.create_all()

    如果没有报错,说明配置没有问题,如果有错误,可以根据错误进行修改。

建表

  1. cmd 进入 mysqlmysql -uroot -p
  2. create database dbDemo1 charset utf8

SQLAlchemy创建模型与表的映射

步骤

  1. 模型需要继承自 db.Model,然后需要映射到表中的属性,必须写成 db.Column()的数据类型

  2. 数据类型:

    • db.Integer: 整型
    • db.Sting: varcharchar,需要指定最长的长度
    • db.Text: text
  3. 其他参数

    • primary_key: 将这个字段设置为主键
    • autoincrement: 这个主键为自增长
    • nullable: 这个字段是否可以为空,默认为空,可以将这个值设置为 False ,在数据库中,这个值就不能为空了
  4. 最后需要调用 db.create_all() 来将模型真正的创建到数据库中,db.create_all()只会映射一次。

实例

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
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
import config

app = Flask(__name__)
app.config.from_object(config)
db = SQLAlchemy(app)

"""
创建article表
create table article (
id int primary key autoincrement,
title varchar(100) not null,
content text not null
)
"""
# 自己写的模型一定要继承sqlalchemy属性下的model模型, 字段->属性
class Article(db.Model):
# 如果不指定表名,默认为类名
__tablename__ = "article"
# 属性id映射到字段id
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
# varchar, char -> db.String(长度)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text, nullable=False)

# 所有的模型映射到数据库的一张表
db.create_all()


@app.route('/')
def index():
return "index"

if __name__ == '__main__':
app.run()

SQLAlchemy数据增删改查

1
2
3
4
5
# 增加
article1 = Article(title="aaa", content="bbb")
db.session.add(article1)
# 事务
db.session.commit()

1
2
3
4
5
6
# 返回的是Query(<==> list); .all()查找所有数据, .first()返回的第一条数据,如果没有,返回null
result = Article.query.filter(Article.title == "aaa").all()
print(result[0], type(result[0]))
article1 = result[0]
print(article1.title)
print(article1.content)

1
2
3
4
5
6
# 1. 先把要修改的地方查找出来
article1 = Article.query.filter(Article.title == "aaa").first()
# 2. 把这条数据,你需要修改的地方进行修改
article1.title = "new title"
# 3. 做事务的提交
db.session.commit()

1
2
3
4
5
6
# 1. 把需要删除的数据查找出来
article1 = Article.query.filter(Article.content == "bbb").first()
# 2. 把这条数据删除掉
db.session.delete(article1)
# 3. 做事务提交
db.session.commit()

注意

  • 如果把增删改查放在 @app.route("/") 类似的视图函数中,访问一次 url,就会执行一遍相应的增删改查

Flask-SQLAlchemy外键及其关系

外键

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
"""
用户表
sql语句:
create table users (
id int primary key autoincrement,
username varchar(100) not null
)
"""
class User(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(100), nullable=False)
# password = db.Column(db.String(100), nullable=False)

"""
文章表
create table article (
id int primary key autoincrement,
title varchar(100) not null,
content text not null,
authorId int,
foreign key "authorId" references "user.id"
)
"""
class Article(db.Model):
__tablename__ = "article"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text, nullable=False)
# 设置外键
authorId = db.Column(db.Integer, db.ForeignKey("user.id"))
# params: 模型名, backref:反向引用;db.backref(), params: 反向引用的名字,
author = db.relationship("User", backref=db.backref("articles"))

解释

  • authorId = db.Column(db.Integer, db.ForeignKey("user.id"))
    • Article 这个模型添加一个 author 属性,可以访问这篇文章的作者的数据,像访问普通模型一样
    • backref:定义反向引用,可以通过 User.articles 这个模型访问这个模型缩写的所有文章

补充

  • 添加数据(方法二):

    1
    2
    3
    4
    article = Article(title="aaa", content="bbb")
    article.author = User.query.filter(User.id==1).first()
    db.session.add(article)
    db.session.commit()

多对多

例子

在这张图中:

使用

  • 多对多的关系,要通过一个中间表进行关联;

  • 中间表,不能通过 class 的方式实现,只能通过 db.Table 的方式实现

  • 设置关联:

    1
    tags = db.relationship("Tag", secondary=articleTagRelation, backref=db.backref("articles"))

    需要使用一个关键字参数 secondary=中间表 进行关联

  • 访问和添加可以通过以下方式操作:

    • 添加数据

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      article1 = Article(title="aaa")
      article2 = Article(title="bbb")
      tag1 = Tag(name="111")
      tag2 = Tag(name="222")

      article1.tags.append(tag1)
      article1.tags.append(tag2)

      article2.tags.append(tag1)
      article2.tags.append(tag2)

      db.session.add(article1)
      db.session.add(article2)
      db.session.add(tag1)
      db.session.add(tag2)
      db.session.commit()
    • 访问数据

      1
      2
      3
      4
      article1 = Article.query.filter(Article.title=="aaa").first()
      tags = article1.tags
      for tag in tags:
      print(tag.name)

Flask-Script的介绍和安装

介绍

Flask-Script 的作用是可以通过命令行的形式来操作 Flask。例如通过命令跑一个开发版本的服务器、设置数据库、定时任务。

安装

  1. 进入到虚拟环境中
  2. pip install flask-script进行安装

使用

  1. 如果直接在主 manage.py 中写命令,那么在终端就只需要 python manage.py commandName 就可以了。

  2. 如果把一些命令集中在一个文件中,那么在终端就需要输入一个父命令,如 python manage.py db init

  3. 例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    from flask_script import Manager
    from flaskScriptDemo import app
    from dbScript import dbManager

    manager = Manager(app)

    @manager.command
    def runserver():
    print("服务器跑起来了")

    # params: 前缀(类似于别名),dbManager
    # 使用:python manage.py db init
    manager.add_command("db", dbManager)

    if __name__ == '__main__':
    manager.run()
  4. 有子命令的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    from flask_script import Manager

    dbManager = Manager()

    @dbManager.command
    def init():
    print("数据库初始化完成")

    @dbManager.command
    def migrate():
    print("数据表迁移成功")

分开 models 及解决循环引用

循环引用

解决办法:

db 放在一个单独的文件中,切断循环引用的线条就可以了。

分开 models

  • 目的:为了让代码更加方便的管理

注意:

  • 使用了 db.init_app(app),就不能直接使用 db.create_all()了,会报错。

    1
    2
    3
    app = Flask(__name__)
    app.config.from_object(modelSepConfig)
    db.init_app(app)

    因为涉及到了上下文的问题:

    解决办法看 flask-migrate

flask-migrate

db.init_app(app)不能直接使用 db.create_all() 解决办法

手动推动 appapp栈

介绍

因为采用 db.create_all() 在后期修改字段的时候,不会自动的映射到数据库中,必须删除表,然后重新运行 db.create_all() 才会重新映射,这样不符合我们的需求。因此 flask-migrate 就是为了解决这个问题,它可以在每次修改模型后,可将修改的东西映射到数据库中

安装

  1. 进入虚拟环境
  2. pip install flask-migrate

使用

使用 flask_migrate 必须借助 flask-scripts,这个包的 MigrateCommand 中包含了所有和数据库相关的命令。

相关命令

  • python manage.py db init: 初始化一个迁移脚本的环境,只需要执行一次;
  • python manage.py db migrate: 将模型生成迁移文件,只要模型更改了,就需要执行一遍这个命令;
  • python manage.py db upgrade: 将掐你文件真正映射到数据库中,每次运行了 migrate 命令后,就记得要运行这个命令

注意点

  • 如果需要将你想要映射到数据库中的模型,都要导入到 manage.py 文件中,如果没有导入进去,就不会映射到数据库中

manage.py 的相关代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from flask_script import Manager
from migrateDemo import app
from flask_migrate import Migrate, MigrateCommand
from exts import db
from models import Article

# 模型 => 迁移文件 => 表
# init: 初始化迁移的环境
# migrate: 将模型生成迁移文件
# upgrade: 将迁移文件映射成表

manager = Manager(app)

# 1. 要使用 flask_migrate,必须绑定app和db
migrate = Migrate(app, db)
# 2. 把MigrateCommand命令添加到manager中
manager.add_command("db", MigrateCommand)




if __name__ == '__main__':
manager.run()

cookie和session

  • 出现的原因:在网站中,http 请求是无状态的。也就是说即使第一次和服务器连接后并且登录成功后,第二次请求服务器依然不知道当前请求是哪个用户。cookie的出现就是我了解决这个问题,第一次登录后服务器返回一些数据(cookie)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求的时候,就会自动的把上次请求存储的 cookie 数据自动的携带给服务器,服务器通过浏览器携带的数据就能判断当前用户是那个了;

  • 图解:

  • 如果服务器返回了 cookie 给浏览器,那么浏览器下次再请求相同的服务器的时候,就会自动的把 cookie 发送给服务器,这个过程,用户根本不需要管;

  • cookie 是保存在浏览器中的,相对对的是浏览器;

session

  • 介绍:sessioncookie 的作用有点类似,都是为了存储用户相关的信息。不同的是,cookie 是存储在本地服务器的,而 session 存储在服务器。存储在服务器的数据会更加安全,不容易被窃取。但存储在服务器也有一定的弊端,就是会占用服务器的资源,但现在服务器已经发展至今,一些 session 信息还是绰绰有余的。

  • 图解:

  • 使用 session 的好处:

    • 敏感数据不是直接发送给服务器,而是发送回一个 session_id ,服务器将 session_id 和敏感数据做一个映射存储在 session (服务器上面)中,更加安全;
    • session 可以设置过期时间,也可以从另外一方面,保证用户的账号安全。

注意

  • session_id:返回给浏览器的时候,是放在 cookie 中的。

flask中session工作机制

  • 讲解:把敏感数据经过加密后放入 session 中,然后再把 session 存放到 cookie 中,下次请求的时候,再从浏览器发送过来的 cookie 中读取 session,然后再从 session 中读取敏感数据,并进行解密,获取最终的用户数据;

  • flask 的这种 session 机制,可以节省服务器的开销,以内把所有的信息都存储到了客户端(浏览器);

  • 安全是相对的,没有绝对的安全,把 session 放到 cookie 中,经过机密,也是比较安全的;

  • 图解:

flask操作session

session的操作方式:

  • 使用 session 需要从 flask 中导入 session,以后所有和 session 相关的操作都是通过这个变量来的;
  • 使用 session 需要设置 SECRET_KEY,用来作为加密用的。并且这个 SECRET_KEY 如果每次服务器启动后都变化的话,那么之前的 session 就不能通过当前这个 SECRET_KEY 进行解密了;
  • 操作 session ,跟操作字典是一样的;
  • 添加 sessionsession[key]=value
  • 删除:session.pop(key) 或者 del session[key]
  • 清除所有 sessionsession.clear()
  • 获取 sessionsession.get(key)

设置session的过期时间

  • 如果没有指定session的过期时间,那么默认是浏览器关闭后就自动结束

  • 如果设置了 sessionpermanent 属性为 True,那么过期时间是31天

  • 可以通过给 app.config 设置 PERMANENT_SESSION_LIFETIME 来更改过期时间,这个值的数据类型是 datatime.timedelay 类型:

    1
    2
    3
    from datetime import timedelta

    PERMANENT_SESSION_LIFETIME = timedelta(days=7)

get请求和post请求

get请求

  • 使用场景:如果只对服务器获取数据,并没有对服务器产生任何影响,那么这时候使用 get 请求
  • 传参:get请求传参是防砸url中,并且是通过 ?的形式来指定 keyvalue

post请求

  • 使用场景:如果要对服务器产生影响,那么使用post请求,如: 登录:服务器要记录用户什么时候登陆过,要把数据保存在本地;
  • 传参:post请求传参不是放在 url 中,而是通过 form data 的形式发送给服务器的

获取参数

  • get请求:通过 flask.request.args 来获取
  • post请求:通过 flask.request.form 来获取

注意

  • post请求在模板中要注意几点:

    • input标签中,要写name来标识这个 valuekey,方便后台获取

    • 在写 form 表单的时候,要指定 method='post',并且要指定 action='/login/'

    • 示例代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      <form action="{{ url_for("login") }}" method="post">
      <table>
      <tbody>
      <tr>
      <td>用户名</td>
      <td><input type="text" placeholder="请输入用户名" name="username"></td>
      </tr>

      <tr>
      <td>密码</td>
      <td><input type="text" placeholder="请输入密码" name="password"></td>
      </tr>

      <tr>
      <td></td>
      <td><input type="submit" placeholder="登录"></td>
      </tr>

      </tbody>
      </table>
      </form>

保存全局遍历的g属性

g: global

  • g对象 是专门用来保存用户的数据的
  • g对象 在一次请求中的所有的代码的地方,都是可以使用的

钩子函数(hook)

钩子函数

正常执行情况:先执行A,然后再执行B。

钩子函数:可以在运行时,把C直接插入到AB之间

before_request

  • before_request:在请求之前执行的函数,且每次请求都会执行一遍
  • before_request是在视图函数执行之前执行的
  • before_request这个函数只是一个装饰器,他可以把需要设置为钩子函数的代码放到视图函数执行之前来执行

context_processor(上下文处理器)

  • 出现的原因:多个不同的页面需要相同的参数,如:username
  • 上下文处理器应该返回一个字典,字典中的 key 会被模板当成变量来渲染
  • 上下文处理器中返回的字典,在所有页面中都是可用的。
  • 被这个装饰器修饰的钩子函数,必须要返回一个字典,即使为空,也要返回

装饰器

  • 需求1:在打印run之前,先要打印hello world

    1
    2
    3
    4
    5
    def run():
    print("hello world")
    print("run")

    run()
  • 需求2:在所有函数执行之前,都要打印一个 hello world

    • 方法一:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      def run():
      print("hello world")
      print("run")

      def run1():
      print("hello world")
      print("run1")

      run()
    • 方法二(如果成千上万个函数,比较麻烦,且难以维护):装饰器

      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
      # 装饰器的两个特别之处:
      # 1. 参数是一个函数
      # 2. 返回值是一个函数
      def myLog(func):

      def wrapper():
      print("hello world")
      func()

      """
      补充:
      wrapper: 函数体
      wrapper(): 执行wrapper这个函数
      """
      return wrapper

      # 如何用
      @myLog
      def run():
      print("run")
      """
      等价于上面
      run = myLog(run) <==> wrapper
      run() <==> wrapper() <==> print("hello world"); func() <==> print("hello world"); print("run")
      理解:func() 执行的才是真的 run() 方法
      """
      run()

讲解

  • 装饰器的两个特别之处:
    • 参数是一个函数
    • 返回值是一个函数
  • 上面的 myLog() 是无参情况下的装饰器

进阶:有参情况下的装饰器

需求:在所有函数执行之前,都要打印一个 hello world

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 装饰器的两个特别之处:
# 1. 参数是一个函数
# 2. 返回值是一个函数
def myLog(func):

def wrapper(a, b):
print("hello world")
func(a, b)
return wrapper

@myLog
def add(a, b):
print("结果是", a + b)
"""
等价于上面
add = myLog(add) <==> wrapper
add(1, 2) <==> wrapper(1, 2) # 因此,如果wrapper()无参,就会报错 `TypeError: wrapper() takes 0 positional arguments but 2 were given`
"""
add(1, 2)

再进阶:有无参同时存在情况下的装饰器

  • 解决办法: *args, **kwargs:可以表示任何参数

    • *args: 位置参数,如:add(a, b) 中的ab,参数是一一对应的
    • **kwargs:关键字参数(key=value),如:add(a=abc)a就是传入的abc
    1
    2
    3
    4
    5
    6
    def myLog(func):

    def wrapper(*args, **kwargs):
    print("hello world")
    func(*args, **kwargs)
    return wrapper

    run(), add() 同上

再进阶

  • 问题:加完装饰器后,函数名被偷偷更改掉了,因此如何保留住原来的函数的名字(把名字改掉是很危险的行为)?

    • 没有装饰器的情况

      1
      2
      3
      def run():
      print("run")
      print(run.__name__) # run.__name__代表的是run这个函数的名称

      输出:

      run

    • 有装饰器的情况

      1
      2
      3
      4
      @myLog
      def run():
      print("run")
      print(run.__name__)

      输出:

      wrapper

  • 解决办法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    from functools import wraps

    def myLog(func):

    @wraps(func)
    def wrapper(*args, **kwargs):
    print("hello world")
    func(*args, **kwargs)
    return wrapper
    • 没加 @wraps 的情况
      run() <=> myLog(run)() <=> wrapper()

    • 加了 @wraps(func) 的情况

      • run <=> myLog(run) <=> @wraps(func)装饰的wrapper <=> wraps(wrapper) <=> wrapsFunction(这个函数是透明的,即看不到的,但是不管怎么样,返回的函数的__name__是run) => wrapsFunction.__name__ == "run"
      • ``run() <=> myLog(run)() <=> @wraps装饰的wrapper() <=> wraps(wrapper)() <=> wrapsFunction()`

再进阶

  • 需求:如果run()有返回值,该怎么办?

  • 解决办法:在 wrapper()return func(*args, **kwargs)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    from functools import wraps

    def myLog(func):

    @wraps(func)
    def wrapper(*args, **kwargs):
    print("hello world")
    return func(*args, **kwargs)
    return wrapper

    @myLog
    def run():
    return 1+1
  • 讲解:

    1
    2
    3
    4
    5
    run = myLog(run) = wrapper
    ==>(推出,下同) run = wrapper
    ==> run() = wrapper() = print("hello world"); return run() = print("hello world"); return 1+1
    因为run()需要返回1+1,所以wrapper()也需要返回1+1
    如果wrapper没有return,那么func(*args, **kwargs),就只是执行了一下1+1,别的什么都没有了

小结

  • 装饰器的使用是通过 @ 符号,放在函数上面;
  • 装饰器中定义的函数,要使用 *args**kwargs 两对兄弟的组合,并且在这个函数中执行原始函数的时候也要把 *args**kwargs 传进去;
  • 需要使用 functools.wraps 在装饰器中的函数上把传进来的这个函数进行一个包裹,这样就不会丢失原来的函数 __name__ 等属性

实践

概述

地球村:现代科技缩小世界的时空距离

信件 ——> 网络编程

计算机网络

计算机网络是指将地理位置不同的具有独立功能的多台计算机机器外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及光网络通信协议(类似于方言、语言)的管理和协调下,实现资源共享和信息传递的计算机系统。

网络编程的目的

无线电台… :传播交流信息,数据交换。(通信

想要达到这个效果需要什么

  1. 如何准确的定位网络上的一台主机 :192.168.16.124:port,定位到这个计算机上的某个资源
  2. 找到了这个主机,如何传输数据呢?

javaweb: 网页编程 B/S架构

网络编程: TCP/IP

网络通信的要素

如何实现网络的通信?

通信双方的地址

  • ip (唯一(指的是公网,不是局域网))
  • port
  • 192.168.16.124:5900ip:port):就可以定位到某台计算机上的某一个应用

规则:网络通信的协议

http, ftp, smtp, tcp, udp, ….

TCP/IP参考模型:

本章目的:

小结

  • 网络编程中有两个主要的问题
    • 如何准确的定位网络上的一台或多台主机
    • 找到主机之后如何进行通信
  • 网络编程中的要素
    • IPportip
    • 网络通信协议:udp, tcp
  • 万物皆对象

IP

ip地址:InetAddress

用处

  • 唯一定位一台网络上计算机
  • 127.0.0.1(localhost): 本机
  • ip地址的分类
    • IP地址分类:IPV4/IPV6
      • IPV4: 如127.0.0.1。4个字节(32位)组成(0-255),全球42亿个(30亿都在北美,亚洲4亿,2011年就用尽了)
      • IPV6: 如 2001:0bb2:aaaa:0015:0000:00000:1aaa:1312。16字节(128位)组成,8个 无符号整数(4个字节),用的是16进制(16进制占4位)。
    • 公网(互联网使用)和私网(局域网使用)
      • 192.168.xx.xx:局域网,专门给组织内部使用
      • ABCD类地址
    • 域名:记忆 IP 问题
      • IP: www.vip.com

端口

端口表示计算机上的一个程序的进程(类似于:一栋楼代表一个 ip,门牌号代表 端口)

  • 不同的进程有不同的端口号,用来区分软件

  • 被规定:0~65535

  • TCP/UDP端口: 65535 * 2,单个协议下端口号不能冲突,不同协议下,端口可以冲突

  • 端口分类

    • 公有端口:(0~1023)最好不要用

      • HTTP: 80
      • HTTPS:43
      • FTP:21
      • SSH:22
      • Telent: 23
    • 程序注册端口:1024~49151,分配给用户或者程序,建议不要用

      • Tomcat: 8080
      • Mysql:3306
      • Oracle: 1521
    • 动态、私有:49152~65535,建议不要用

      • Idea:63342

      • 查看所有接口

        1
        2
        3
        netstat -ano # 查看所有端口
        netstat -ano|findstr "5900"# 管道流: |,查看指定的端口
        tasklist|findstr "8696" # 查看指定端口的进程

        打开任务管理器: ctrl+shift+esc

    • 代码

      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
      package com.kuangstudy.net.module4;

      import java.net.InetSocketAddress;

      /**
      * @author Qeuroal
      * @date 2021-03-21 16:15
      * @description
      * @since
      */
      public class TestInetSocketAddress {
      public static void main(String[] args) {

      InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8080);
      System.out.println(inetSocketAddress);
      InetSocketAddress inetSocketAddress2 = new InetSocketAddress("localhost", 8080);
      System.out.println(inetSocketAddress2);

      System.out.println(inetSocketAddress.getAddress());
      // 地址,可以更改hosts文件来更改映射
      System.out.println(inetSocketAddress.getHostName());
      // 端口
      System.out.println(inetSocketAddress.getPort());
      }
      }
    • 图片

通信协议

  • 协议: 约定,就好比我们现在说的普通话
  • 网络通信协议:针对于网络所产生的协议,如:速率,传输码率,代码结构,传输控制……
  • 问题:非常的复杂
  • 大事化小:分层
  • TCP/IP协议簇:实际上是一组协议,不止两个协议。

重要的协议:

  • TCP: 用户传输协议,类似于打电话
  • UDP: 用户数据报协议,类似于发短信

出名的协议:

  • TCP: 用户传输协议
  • IP:网络互连协议

TCP, UDP对比

  • TCP: 打电话

    • 连接:稳定

    • 连接:三次握手四次挥手

      • 三次握手:

        最少需要三次,保证稳定连接!

        A:你瞅啥?

        B:瞅你咋地?

        A:干一场!

      • 四次挥手

        A:我要走了!

        B:你真的要走了吗!

        B:你真的真的要走了吗?

        A:我真的要走了!

    • 客户端、服务端连接

    • 传输完成,释放连接,效率低

  • UDP: 发短信

    • 不连接:不稳定
    • 客户端、服务端连接:没有明确的界限
    • 不管有没有准备好,都可以发给你
    • 类似于导弹攻击
    • DDOS: 洪水攻击(饱和攻击)

TCP

客户端

  1. 连接服务器 socket
  2. 发送消息
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
package com.kuangstudy.net.module6;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;

/**
* @author Qeuroal
* @date 2021-03-21 16:53
* @description 客户端
* @since
*/
public class TestTcpClient {
public static void main(String[] args) {
Socket socket = null;
OutputStream os = null;
try {
// 1. 要知道服务器的地址
InetAddress serverIP = InetAddress.getByName("127.0.0.1");
// 2. 端口号
int port = 9999;
// 3. 创建一个socket连接
socket = new Socket(serverIP, port);
// 4. 发送消息: io流
os = socket.getOutputStream();
os.write("你好,欢迎学习网络编程".getBytes());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}

if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

服务器

  1. 建立服务的端口 ServerSocket
  2. 等待用户的连接 accept
  3. 接受用户消息
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
package com.kuangstudy.net.module6;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
* @author Qeuroal
* @date 2021-03-21 16:53
* @description 服务端
* @since
*/
public class TestTcpSever {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket socket = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
// 1. 我得有一个地址
serverSocket = new ServerSocket(9999);
while (true) {
// 2. 等待客户端连接过来
socket = serverSocket.accept();
// 3. 读取客户端的消息
is = socket.getInputStream();

// 管道流
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
System.out.println(baos.toString());
}

} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭资源
if (baos != null) {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}

if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

文件上传

读取文件->流->传出去

服务器端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.kuangstudy.net.module7;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
* @author Qeuroal
* @date 2021-03-22 16:13
* @description
* @since
*/
public class TestTcpFileServer {
public static void main(String[] args) throws Exception {
// 1. 创建服务
ServerSocket serverSocket = new ServerSocket(9000);
// 2. 监听客户端的连接
Socket socket = serverSocket.accept();// 阻塞式监听,会一直等待客户端连接
// 3. 获取输入流
InputStream is = socket.getInputStream();
// 4. 文件输出
FileOutputStream fos = new FileOutputStream(new File("receive.png"));
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
fos.write(buffer, 0, len);

}
// 5. 通知客户端接收完毕
OutputStream os = socket.getOutputStream();
os.write("我接收完毕了,你可以断开了".getBytes());
// 6. 关闭资源
fos.close();
is.close();
socket.close();
serverSocket.close();
}
}

客户端

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
package com.kuangstudy.net.module7;

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;

/**
* @author Qeuroal
* @date 2021-03-22 16:07
* @description
* @since
*/
public class TestTcpFileClient {
public static void main(String[] args) throws Exception {
// 1. 创建一个socket连接
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9000);
// 2. 创建一个输出流
OutputStream os = socket.getOutputStream();
// 3. 读取文件
FileInputStream fis = new FileInputStream(new File("src/resource/xly2.png"));
// 4. 写出文件
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
// 5. 通知服务器,我已经结束了
socket.shutdownOutput(); // 我已经传输完了
// 5. 确定服务器接收完毕,才能够断开连接
InputStream is = socket.getInputStream();
// String byte[]
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer2 = new byte[1024];
int len2;
while ((len2 = is.read(buffer2)) != -1) {
baos.write(buffer2, 0, len2);
}
System.out.println(baos.toString());

// 5. 关闭资源
baos.close();
is.close();
fis.close();
os.close();
socket.close();
}
}

Tomcat

服务端

  • 自定义 S
  • Tomcat服务器 S: Java后台开发!

客户端

  • 自定义 S
  • 浏览器 B

UDP

发短信:不用连接,需要知道对方的地址


涉及到两个类:

  • DatagramPacket
  • DatagramSocket

发送端

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
package com.kuangstudy.net.module9;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/**
* @author Qeuroal
* @date 2021-03-22 17:29
* @description 不需要连接服务器
* @since
*/
public class TestUdpClient {
public static void main(String[] args) throws Exception {
// 1. 建立一个Socket
DatagramSocket socket = new DatagramSocket(); // 用来发东西的
// 2. 建个包
String msg = "你好啊,服务器!";
// 3. 发送给谁
InetAddress localhost = InetAddress.getByName("localhost");
int port = 9090;
// 数据,数据的长度起始,要发送给谁
DatagramPacket packet = new DatagramPacket(msg.getBytes(), 0, msg.getBytes().length, localhost, port);
// 4. 发送包
socket.send(packet);

// 5. 关闭
socket.close();
}
}

接收端

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
package com.kuangstudy.net.module9;

import java.net.DatagramPacket;
import java.net.DatagramSocket;

/**
* @author Qeuroal
* @date 2021-03-22 17:35
* @description 还是要等待客户端的连接
* @since
*/
public class TestUdpServer {
public static void main(String[] args) throws Exception {
// 开放端口
DatagramSocket socket = new DatagramSocket(9090);
// 接收数据包
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length); // 接收
socket.receive(packet); // 阻塞接收
System.out.println(packet.getAddress().getHostName());
System.out.println(new String(packet.getData(), 0, packet.getLength()));

// 关闭连接
socket.close();

}
}

本质上没有服务器:因为可以互相发送,因此就没有服务器的概念

咨询

类似于:广告的客服

xxx: 你好

xxx: 你好

  • BufferedReader : 包装流包装 System.in,为了控制台读取

    1
    new BufferedReader(new InputStreamReader(System.in))

循环发送消息

1

循环接收消息

1

在线咨询

两个人都是发送方,同时也都是接收方

TalkSend

1

TalkReceive

1

TalkStudent

1

TalkTeacher

1

URL

如:https://www.baidu.com/

统一资源定位符:定位资源的,定位互联网上的某一个资源

DNS域名解析: 将 www.baidu.com ==> xxx.xx.xx.xx

组成(可以少,但不能多)

1
协议://ip地址:端口号/项目名/资源
  • URL() : 网络类,代表一个地址
    • param: String
    • url.getProtocol: 得到协议名
    • url.getHost(): 得到主机ip
    • url.getPort(): 得到端口
    • url.getPath(): 文件地址
    • url.getFile(): 得到文件全路径
    • url.getQuery: 得到参数(如:查询的名字)
    • url.openConnection(): 打开连接
    • urlConnection.getInputStream(): 得到流
    • urlConnection.disconnect(): 断开连接

下载文件

  1. 下载地址
  2. 连接到这个资源,用 HTTP 连接
  3. 下载

getResource

getResource读取的是 out 下的文件,即 classpath

  • 相对路径: 即在当前包内的路径,如: Test.class.getResource("xly2.png");, xly2.png在当前包内,或者说和运行的class在同一目录下
  • 绝对路径: 用 / 表示,代表是当前项目下,如 Test.class.getResource("/resource/xly2.png"),如上图可见 resource 的位置

new File

读取的是 目录文件,如下所示

1
new FileInputStream(new File("src/resource/xly2.png"));

Git设置

分辨需要设置的代理

  • HTTP形式:

    git clone https://github.com/user/test.git

  • SSH形式

    git clone git@github.com:user/test.git

HTTP形式

我的端口为

HTTP 代理

1
2
git config --global http.proxy "http://127.0.0.1:8080"
git config --global https.proxy "http://127.0.0.1:8080"

socks5 代理(如 Shadowsocks

1
2
git config --global http.proxy "socks5://127.0.0.1:8080"
git config --global https.proxy "socks5://127.0.0.1:8080"

取消设置

1
2
git config --global --unset http.proxy
git config --global --unset https.proxy

建议

建议走 HTTP 代理,socks代理不知道为啥,还是慢

  • HTTP 代理

  • socks5 代理

SSH 形式

修改 ~/.ssh/config 文件(不存在则新建):

1
2
3
4
5
6
7
8
9
# github
Host github.com
HostName github.com
PreferredAuthentications publickey
# 私钥位置
IdentityFile ~/.ssh/id_rsa
# 设置代理, 127.0.0.1:8080 换成你自己代理软件监听的本地地址
# HTTPS使用-H,SOCKS使用-S
ProxyCommand connect -H 127.0.0.1:8080 -a none %h %p
  • HTTPS 代理速度

  • socks 代理速度

或者

1
2
3
4
5
6
7
8
9
# github.com
Host github.com
Hostname ssh.github.com
User git
Port 443
PreferredAuthentications publickey
IdentityFile ~/.ssh/id_rsa # id_rsa改成你的ssh密钥的文件名
ProxyCommand nc -X connect -x 127.0.0.1:3128 %h %p # 127.0.0.1:3128改成你的代理的ip:port
ServerAliveInterval 20

Note:

  • gitee 不一定支持 443 接口

总结

  • HTTP 形式和 SSH形式速度相差不大

  • 一定要保证端口号和你的电脑的端口号一致

  • HTTP 形式的缺点:需要认证 GitHub,而 SSH形式则不需要

  • SSH形式的缺点:目前表现为:hexo d部署速度慢

    • 使用 SSH形式:

    • 若不使用 SSH形式

如何查看端口号

设置 - 网络和Internet - 代理,如下所示

Reference

linux 系统设置代理

图形界面GUI设置方式

打开 设置 -> 网络 -> 网络代理 -> 手动Settings -> Network -> Network Proxy -> Manual

设置完点击Apply system wide

命令行CLI设置方式

系统代理设置

虚拟机设置使用宿主机(10.10.1.10)的代理

我们将在 /etc/profile.d/proxy.sh 下添加一个shell脚本文件,这将确保设置适用于所有已登录的用户:

1
sudo vim  /etc/profile.d/proxy.sh

将以下内容写到文档中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# set proxy config via profie.d - should apply for all users
export http_proxy="http://10.10.1.10:7890/"
export https_proxy="http://10.10.1.10:7890/"
export ftp_proxy="http://10.10.1.10:7890/"
export no_proxy="127.0.0.1,localhost"

# For curl
export HTTP_PROXY="http://10.10.1.10:7890/"
export HTTPS_PROXY="http://10.10.1.10:7890/"
export FTP_PROXY="http://10.10.1.10:7890/"
export NO_PROXY="127.0.0.1,localhost"

#将要从代理中排除的其他IP添加到NO_PROXY和no_proxy环境变量中


PS: 附本地机器如下设置 (本地机器是虚拟机, 设置使用宿主机(10.10.1.10)的代理)

1
2
3
4
5
6
7
8
9
10
11
export HTTP_PROXY="http://10.10.1.10:7890/"
export HTTPS_PROXY="http://10.10.1.10:7890/"
export FTP_PROXY="http://10.10.1.10:7890/"
export ALL_PROXY="socks://10.10.1.10:7890/"
export NO_PROXY="localhost,127.0.0.0/8,::1"

export http_proxy="http://10.10.1.10:7890/"
export https_proxy="http://10.10.1.10:7890/"
export ftp_proxy="http://10.10.1.10:7890/"
export all_proxy="socks://10.10.1.10:7890/"
export no_proxy="localhost,127.0.0.0/8,::1"

为该文件填加执行权限:

1
sudo chmod +x  /etc/profile.d/proxy.sh

激活文件以开始使用代理设置,或者注销并重新登录:

1
2
3
source /etc/profile.d/proxy.sh
#查看环境变量进行确认是否生效
env | grep -i proxy

GUI简介

简介

GUI核心开发技术:Swing、AWT
不流行原因:

  • 界面不美观
  • 需要 jre 环境

为什么要学习?

  1. 可以写出自己心中想要的小工具
  2. 工作时候,也可能小维护到 swing 界面,概率极小
  3. 了解MVC架构,了解监听

AWT

AWT介绍

  1. 包含了很多类和接口用于GUI编程!GUI:图形用户界面
  2. 元素:窗口,按钮,文本框
  3. java.awt

组件和容器

Frame

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
import java.awt.*;

/**
* @author QeuroIzo
* @date 2021-03-03 17:04
* @TODO GUI的第一个界面
* @since
*/
public class TestFrame {
public static void main(String[] args) {
// Frame 看源码
Frame frame = new Frame("我的第一个Java图像界面窗口");
// 设置可见性
frame.setVisible(true);
// 设置大小
frame.setSize(400, 400);
frame.setBackground(new Color(0, 255, 0));
// 设置弹出的初始位置
frame.setLocation(200, 200);
// 设置大小固定
frame.setResizable(false);

}

}

尝试封装

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
package com.kuangstudy.gui.module3;

import java.awt.*;

/**
* @author QeuroIzo
* @date 2021-03-03 17:24
* @TODO
* @since
*/
public class TestFrame2 {
public static void main(String[] args) {
MyFrame myFrame1 = new MyFrame(100, 100, 200, 200, Color.BLUE);
MyFrame myFrame2 = new MyFrame(300, 100, 200, 200, Color.YELLOW);
MyFrame myFrame3 = new MyFrame(100, 300, 200, 200, Color.RED);
MyFrame myFrame4 = new MyFrame(300, 300, 200, 200, Color.MAGENTA);

}
}

class MyFrame extends Frame {
// 可能存在多个窗口,需要一个计数器
static int id = 0;
public MyFrame(int x, int y, int w, int h, Color color) {
super("MyFrame" + (++id));
setVisible(true);
setBounds(x, y, w, h);
setBackground(color);

}
}

面板Panel

Panel 可以看成是一个空间,但是不能单独存在,需要放在 Frame

解决了窗口关闭事件

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
package com.kuangstudy.gui.module4;

import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

/**
* @author QeuroIzo
* @date 2021-03-03 18:38
* @TODO
* @since
*/
public class TestPanel1 {
public static void main(String[] args) {
Frame frame = new Frame();
frame.setTitle("hello");
// 布局的概念
Panel panel = new Panel();

// 设置布局
frame.setLayout(null);
// 坐标
frame.setBounds(300, 300, 500, 500);
frame.setBackground(new Color(0, 255, 0));
// panel 设置坐标,相对位置
panel.setBounds(50, 50, 400, 400);
panel.setBackground(new Color(255, 0, 0));

// frame 添加 panel
frame.add(panel);
// 设置可见
frame.setVisible(true);

// 监听事件:监听窗口关闭事件 System.exit(0)
// 适配器模式:
frame.addWindowListener(new WindowAdapter() {
// 窗口点击关闭的时候需要做的事情
@Override
public void windowClosing(WindowEvent e) {
// 结束程序
System.exit(0);

}
});
}
}

布局管理器

  • 流式布局
  • 东西南北中
  • 表格布局

流式布局

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
package com.kuangstudy.gui.module5;

import java.awt.*;

/**
* @author QeuroIzo
* @date 2021-03-03 19:01
* @TODO 流式布局
* @since
*/
public class TestFlowLayout1 {
public static void main(String[] args) {
Frame frame = new Frame();

// 组件-按钮
Button button1 = new Button("Button1");
Button button2 = new Button("Button2");
Button button3 = new Button("Button3");

// 设置为流式布局
// frame.setLayout(new FlowLayout()); // 默认center
// frame.setLayout(new FlowLayout(FlowLayout.LEFT));
frame.setLayout(new FlowLayout(FlowLayout.RIGHT));
frame.setSize(200, 200);
// 把按钮添加上去
frame.add(button1);
frame.add(button2);
frame.add(button3);
frame.setVisible(true);
}
}

东西南北中

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
package com.kuangstudy.gui.module5;

import java.awt.*;

/**
* @author QeuroIzo
* @date 2021-03-03 21:02
* @TODO 东西南北中
* @since
*/
public class TestBorderLayout {
public static void main(String[] args) {
Frame frame = new Frame("Test");

Button east = new Button("East");
Button west = new Button("West");
Button south = new Button("South");
Button north = new Button("North");
Button center = new Button("Center");

frame.add(east, BorderLayout.EAST);
frame.add(west, BorderLayout.WEST);
frame.add(south, BorderLayout.SOUTH);
frame.add(north, BorderLayout.NORTH);
frame.add(center, BorderLayout.CENTER);

frame.setSize(200, 200);
frame.setVisible(true);



}
}

表格布局

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
package com.kuangstudy.gui.module5;

import java.awt.*;

/**
* @author QeuroIzo
* @date 2021-03-03 21:20
* @TODO
* @since
*/
public class TestGridLayout {
public static void main(String[] args) {
Frame frame = new Frame("GridLayout");

Button btn1 = new Button("btn1");
Button btn2 = new Button("btn2");
Button btn3 = new Button("btn3");
Button btn4 = new Button("btn4");
Button btn5 = new Button("btn5");
Button btn6 = new Button("btn6");

frame.setLayout(new GridLayout(3, 2));
frame.add(btn1);
frame.add(btn2);
frame.add(btn3);
frame.add(btn4);
frame.add(btn5);
frame.add(btn6);

// java函数:自动布局
frame.pack();
frame.setSize(300, 300);
frame.setVisible(true);

}
}

练习

切记直接动手

正常的方式:构思(80%) -> 代码(20%)

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
package com.kuangstudy.gui.module6;

import java.awt.*;

/**
* @author QeuroIzo
* @date 2021-03-03 21:32
* @TODO
* @since
*/
public class TestDemo {
public static void main(String[] args) {
Frame frame = new Frame("表格布局测试");
frame.setLayout(new GridLayout(2, 3));

Panel panelUp = new Panel();
panelUp.setLayout(new GridLayout(2, 1));
Panel panelDown = new Panel(new GridLayout(2, 2));
Button btn1 = new Button("btn");
Button btn2 = new Button("btn");
Button btn3 = new Button("btn");
Button btn4 = new Button("btn");
Button btn5 = new Button("btn");
Button btn6 = new Button("btn");
Button btn7 = new Button("btn");
Button btn8 = new Button("btn");
Button btn9 = new Button("btn");
Button btn10 = new Button("btn");

// panelUp 添加 btn
panelUp.add(btn2);
panelUp.add(btn3);
// panelDown 添加 Btn
panelDown.add(btn6);
panelDown.add(btn7);
panelDown.add(btn8);
panelDown.add(btn9);

frame.add(btn1);
frame.add(panelUp);
frame.add(btn4);
frame.add(btn5);
frame.add(panelDown);
frame.add(btn10);
frame.pack();

frame.setBounds(300, 300, 500, 400);
frame.setBackground(Color.BLUE);
frame.setVisible(true);
}
}

总结

  1. Frame 是一个顶级窗口
  2. Panel 无法单独显示,必须添加到某个容器中
  3. 布局管理器
    1. 流式
    2. 东西南北中
    3. 表格
  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
package com.kuangstudy.gui.module7;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

/**
* @author QeuroIzo
* @date 2021-03-05 15:39
* @TODO
* @since
*/
public class TestActionEvent {
public static void main(String[] args) {
// 按下按钮是,触发一些事件
Frame frame = new Frame();
// frame.
Button button = new Button("button");
// 因为 addActionListener() 需要一个 ActionListener,所以我们需要构造一个ActionListener
MyActionListener myActionListener = new MyActionListener();
button.addActionListener(myActionListener);

frame.add(button, BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
windowClose(frame);

}

/**
* 关闭窗体的事件
* @param frame Frame
*/
private static void windowClose(Frame frame) {
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
}

/**
* 事件监听
*/
class MyActionListener implements ActionListener {

@Override
public void actionPerformed(ActionEvent e) {
System.out.println("aaa");
}
}

多个按钮共享一个事件

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
package com.kuangstudy.gui.module7;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
* @author QeuroIzo
* @date 2021-03-05 16:20
* @TODO
* @since
*/
public class TestActionEvent2 {
public static void main(String[] args) {
// 两个按钮,实现同一个监听
// 开始-停止
Frame frame = new Frame("开始-停止");
Button beginButton = new Button("start");
Button stopButton = new Button("stop");

// 可以显示的定义触发会返回的命令,如果不显示定义,则会走默认的只
// 可以多个按钮只写一个监听类
stopButton.setActionCommand("button-stop");
MyMonitor myMonitor = new MyMonitor();
beginButton.addActionListener(myMonitor);
stopButton.addActionListener(myMonitor);

frame.add(beginButton, BorderLayout.NORTH);
frame.add(stopButton, BorderLayout.SOUTH);

frame.pack();
frame.setVisible(true);


}
}

class MyMonitor implements ActionListener {

@Override
public void actionPerformed(ActionEvent e) {
// e.getActionCommand() 获得按钮的信息
System.out.println("按钮被点击了:msg=>" + e.getActionCommand());

}
}

输入框 TextField、监听

main方法里面只有一行代码:启动

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
package com.kuangstudy.gui.module8;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
* @author QeuroIzo
* @date 2021-03-05 16:32
* @TODO
* @since
*/
public class TestTextField {
public static void main(String[] args) {
// 启动
new MyFrame();
}
}

class MyFrame extends Frame {
public MyFrame() {
TextField textField = new TextField();
add(textField);
// 监听这个文本框输入的文字
MyActionListener myActionListener = new MyActionListener();
// 按下enter,就会触发这个输入框的事件
textField.addActionListener(myActionListener);
// 设置替换编码
textField.setEchoChar('*');

setVisible(true);
pack();
}
}

class MyActionListener implements ActionListener {

@Override
public void actionPerformed(ActionEvent e) {
// 获得一些资源,返回的一个对象(为什么Object可以向下转型,有的时候不是会报错吗- runtime error! ClassCastException?)
TextField field = (TextField) e.getSource();
// 获得输入框中的文本
System.out.println(field.getText());
// 设置清空
field.setText("");
}
}

简单计算器、组合+内部类回顾

oop原则:组合大于继承

继承

1
2
3
class A extends B {

}

组合

1
2
3
class A {
public B b;
}

目前代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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
package com.kuangstudy.gui.module9;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
* @author QeuroIzo
* @date 2021-03-05 18:06
* @TODO 简易计算器
* @since
*/
public class CalculateDemo {
public static void main(String[] args) {
new Calculator();
}
}

/**
* 计算器类
*/
class Calculator extends Frame {
public Calculator() {
// 三个文本框
TextField num1 = new TextField(10);
TextField num2 = new TextField(10);
TextField num3 = new TextField(20);
// 一个按钮
Button button = new Button("=");
button.addActionListener(new MyCalculatorListener(num1, num2, num3));
// 一个标签
Label label = new Label("+");

// 布局
setLayout(new FlowLayout());

// 添加组件
add(num1);
add(label);
add(num2);
add(button);
add(num3);

pack();
setVisible(true);
}
}

/**
* 监听器类
*/
class MyCalculatorListener implements ActionListener {

/**
* 获取三个变量
*/
private TextField num1, num2, num3;
public MyCalculatorListener(TextField num1, TextField num2, TextField num3) {
this.num1 = num1;
this.num2 = num2;
this.num3 = num3;
}

@Override
public void actionPerformed(ActionEvent e) {
// 1. 获得加数和被加数
int n1 = Integer.parseInt(num1.getText());
int n2 = Integer.parseInt(num2.getText());

// 2. 将这个值加法运算后,放到第三个框
num3.setText("" + (n1 + n2));
// 3. 清楚前两个框
num1.setText("");
num2.setText("");
}
}

优化代码

在一个类中调用另一个类的引用

多用组合,最好不要用继承、多态:继承,增强了代码的耦合性;多态,使代码更麻烦,理解容易出错。使用组合的方式把代码拿过来。

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
package com.kuangstudy.gui.module9;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
* @author QeuroIzo
* @date 2021-03-05 18:06
* @TODO 简易计算器
* @since
*/
public class CalculateDemo {
public static void main(String[] args) {
new Calculator().loadFrame();
}
}

/**
* 计算器类
*/
class Calculator extends Frame {

/**
* 属性
*/
public TextField num1, num2, num3;

/**
* 方法
*/
public void loadFrame() {
// 三个文本框
num1 = new TextField(10);
num2 = new TextField(10);
num3 = new TextField(20);
// 一个按钮
Button button = new Button("=");
// 一个标签
Label label = new Label("+");
button.addActionListener(new MyCalculatorListener(this));

// 布局
setLayout(new FlowLayout());

// 添加组件
add(num1);
add(label);
add(num2);
add(button);
add(num3);

pack();
setVisible(true);
}

}

/**
* 监听器类
*/
class MyCalculatorListener implements ActionListener {

/**
* 获取计算器这个对象,在一个类中组合另外一个类
*/
private Calculator calculator = null;
public MyCalculatorListener(Calculator calculator) {
this.calculator = calculator;
}

@Override
public void actionPerformed(ActionEvent e) {
// 1. 获得加数和被加数
// 2. 将这个值加法运算后,放到第三个框
// 3. 清楚前两个框

int n1 = Integer.parseInt(calculator.num1.getText());
int n2 = Integer.parseInt(calculator.num2.getText());
calculator.num3.setText("" + (n1 + n2));
calculator.num1.setText("");
calculator.num2.setText("");
}
}

完全改造为OOP写法

内部类

  • 更好的包装
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
package com.kuangstudy.gui.module9;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
* @author QeuroIzo
* @date 2021-03-05 18:06
* @TODO 简易计算器
* @since
*/
public class CalculateDemo {
public static void main(String[] args) {
new Calculator().loadFrame();
}
}

/**
* 计算器类
*/
class Calculator extends Frame {

/**
* 属性
*/
private TextField num1, num2, num3;

/**
* 方法
*/
public void loadFrame() {
// 三个文本框
num1 = new TextField(10);
num2 = new TextField(10);
num3 = new TextField(20);
// 一个按钮
Button button = new Button("=");
// 一个标签
Label label = new Label("+");
button.addActionListener(new MyCalculatorListener());

// 布局
setLayout(new FlowLayout());

// 添加组件
add(num1);
add(label);
add(num2);
add(button);
add(num3);

pack();
setVisible(true);
}

/**
* 监听器(内部类)
* 内部类最大的好处,就是可以畅通无阻的访问外部类的属性和方法
*/
private class MyCalculatorListener implements ActionListener {

@Override
public void actionPerformed(ActionEvent e) {
// 1. 获得加数和被加数
// 2. 将这个值加法运算后,放到第三个框
// 3. 清楚前两个框

int n1 = Integer.parseInt(num1.getText());
int n2 = Integer.parseInt(num2.getText());
num3.setText("" + (n1 + n2));
num1.setText("");
num2.setText("");
}
}
}

画笔

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
package com.kuangstudy.gui.module10;

import java.awt.*;

/**
* @author QeuroIzo
* @date 2021-03-05 20:18
* @TODO
* @since
*/
public class TestPaint {
public static void main(String[] args) {
new MyPaint().loadFrame();
}
}

class MyPaint extends Frame {

public void loadFrame() {
setBounds(200, 200, 600, 500);
setVisible(true);
}
/**
* 画笔
* @param g
*/
@Override
public void paint(Graphics g) {
// super.paint(g);
// 画笔,需要有颜色,可以画画
g.setColor(Color.RED);
g.drawOval(100, 100, 100, 200);
// 实心圆
g.fillOval(100, 300, 100, 100);

g.setColor(Color.GREEN);
g.fillRect(200, 100, 100, 100);

// 养成习惯:画笔用完,将它还原到最初的颜色。
}
}

鼠标监听

目的:想要实现鼠标画画

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
package com.kuangstudy.gui.module11;

import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Iterator;

/**
* @author QeuroIzo
* @date 2021-03-05 21:54
* @TODO 测试鼠标监听事件
* @since
*/
public class TestMouseListener {
public static void main(String[] args) {
new MyFrame("画图");
}
}

class MyFrame extends Frame {
// 画画需要画笔,需要监听鼠标当前的位置,需要集合来存储这个点
/**
* 存鼠标点击的点
*/
private ArrayList<Point> points;
public MyFrame(String title) {
super(title);
setBounds(200, 200, 600, 500);
// 存鼠标的点
points = new ArrayList<>();
// points.add(new Point(100, 100));
points.add(new Point(0, 0));
// points.add(new Point(200, 200));
// points.add(new Point(300, 300));


// 鼠标监听器,针对这个窗口
this.addMouseListener(new MyMouseListener());

setVisible(true);
}

@Override
public void paint(Graphics g) {
// 画画需要监听鼠标的事件
Iterator iterator = points.iterator();
while (iterator.hasNext()) {
Point point = (Point) iterator.next();
g.setColor(Color.BLUE);
g.fillOval(point.x, point.y, 100, 100);
}
}

/**
* 添加一个点到界面上
*/
public void addPaint(Point point) {
points.add(point);
}

/**
* 适配器模式
*/
private class MyMouseListener extends MouseAdapter {
// 只需要鼠标按下、弹起、按住不放

@Override
public void mouseClicked(MouseEvent e) {
MyFrame myFrame = (MyFrame) e.getSource();
// 点击的时候,就会在界面上产生一个点!
// 这个点就是鼠标的点
myFrame.addPaint(new Point(e.getX(), e.getY()));

// 每次点击鼠标都需要重画一遍(《=》刷新),每秒刷新 30帧或60帧
myFrame.repaint();
}
}
}

代码的思维导图,下图:

窗口监听

关掉某个窗口:即隐藏这个窗口

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
package com.kuangstudy.gui.module12;

import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

/**
* @author Qeuroal
* @date 2021-03-09 16:28
* @description
* @since
*/
public class TestWindowListener {
public static void main(String[] args) {
new WindowFrame();
}
}

class WindowFrame extends Frame {
public WindowFrame() {
setVisible(true);
setBounds(200, 300, 300, 400);
setBackground(Color.RED);
// addWindowListener(new MyWindowListener());
//最好使用匿名内部类
this.addWindowListener(new WindowAdapter() {
// 监听不到
// @Override
// public void windowOpened(WindowEvent e) {
// System.out.println("windowOpened");
// }

/**
* 关闭窗口
* @param e
*/
@Override
public void windowClosing(WindowEvent e) {
System.out.println("windowClosing");
System.exit(0);
}

// 监听不到
// @Override
// public void windowClosed(WindowEvent e) {
// System.out.println("windowClosed");
// }

/**
* 激活窗口
* @param e
*/
@Override
public void windowActivated(WindowEvent e) {
// 获取事件所作用的对象,(获得事件监听的对象)即你所与该事件绑定的控件,例如你点击了按钮,那么得到的source就是按钮对象了
WindowFrame source = (WindowFrame) e.getSource();
source.setTitle("被激活了");
System.out.println("windowActivated");
}

/**
* 未被激活窗口,即切出去了
* @param e
*/
@Override
public void windowDeactivated(WindowEvent e) {
System.out.println("windowDeactivated");
}
});
}

/** 成员内部类
class MyWindowListener extends WindowAdapter {
@Override
public void windowClosing(WindowEvent e) {
// 隐藏窗口,通过按钮隐藏窗口
setVisible(false);
// 正常退出:0,非正常退出:1
System.exit(0);
}
}
*/
}

键盘监听

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
package com.kuangstudy.gui.module13;

import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

/**
* @author Qeuroal
* @date 2021-03-09 17:04
* @description
* @since
*/
public class TestKeyListener {
public static void main(String[] args) {
new KeyFrame();
}
}

class KeyFrame extends Frame {
public KeyFrame() {
setBounds(300, 400, 300, 400);
setVisible(true);

this.addKeyListener(new KeyAdapter() {
/**
* 键盘按下
* @param e
*/
@Override
public void keyPressed(KeyEvent e) {
// 获得键盘下的键是哪个,当前键盘的码
int keyCode = e.getKeyCode();
// 不需要记录这个数值,直接使用静态属性 VK_xxx
System.out.println(keyCode);
if (keyCode == KeyEvent.VK_UP) {
System.out.println("你按下了上键");
}
// 根据按下的不同操作,产生不同结果
}
});
}
}

Swing

AWT 是底层,Swing 是给封装了

窗口、面板

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
package com.kuangstudy.gui.module14;

import javax.swing.*;
import java.awt.*;

/**
* @author Qeuroal
* @date 2021-03-09 17:17
* @description
* @since
*/
public class TestJFrame {
/**
* 初始化
*/
public void init() {
// 顶级窗口
JFrame jf = new JFrame("这是一个JFrame窗口");
jf.setVisible(true);
jf.setBounds(100, 100, 400, 300);

// 设置文字: Jlabel
JLabel label = new JLabel("欢迎来到JAVA GUI");

jf.add(label);

// 容器:需要实例化,JFrame本身也是一个容器,需要实例化
Container contentPane = jf.getContentPane();
contentPane.setBackground(Color.RED);

// 关闭事件
jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
// 建立一个窗口
new TestJFrame().init();
}
}

标签居中

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
package com.kuangstudy.gui.module14;

import javax.swing.*;
import java.awt.*;

/**
* @author Qeuroal
* @date 2021-03-09 17:32
* @description
* @since
*/
public class TestJFrame2 {
public static void main(String[] args) {
new MyJFrame2().init();
}
}

class MyJFrame2 extends JFrame {
public void init() {
this.setVisible(true);
setBounds(300, 300, 400, 300);
// 设置文字: Jlabel
JLabel label = new JLabel("欢迎来到JAVA GUI");
// add(label) 和 this.add(label) 一样
add(label);
//设置水平对齐
label.setHorizontalAlignment(SwingConstants.CENTER);
// 获得一个容器
Container contentPane = this.getContentPane();
contentPane.setBackground(Color.RED);
}
}

弹窗

JDialog,用来被弹出,默认就有关闭事件

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
package com.kuangstudy.gui.module15;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
* @author Qeuroal
* @date 2021-03-09 20:46
* @description 主窗口
* @since
*/
public class TestDialog extends JFrame {
public TestDialog() {
this.setVisible(true);
this.setBounds(400, 400, 400, 300);
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

// JFrame放东西:容器
Container contentPane = this.getContentPane();
// 绝对布局
contentPane.setLayout(null);

// 按钮
JButton jButton = new JButton("点击弹出一个对话框");
jButton.setBounds(30, 30, 200, 50);

// 点击这个按钮的时候,弹出一个弹窗
jButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// 弹窗
new MyDialogDemo();
}
});
contentPane.add(jButton);
}

public static void main(String[] args) {
new TestDialog();
}
}

/**
* 弹窗的窗口
*/
class MyDialogDemo extends JDialog{
public MyDialogDemo() {
this.setVisible(true);
this.setBounds(300, 300, 300, 200);
// this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

Container contentPane = this.getContentPane();
contentPane.setLayout(null);

contentPane.add(new Label("学Swing"));
}
}

标签

label

  • 创建
1
new JLabel("xxx")
  • 实例
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
package com.kuangstudy.gui.module16;

import javax.swing.*;
import java.awt.*;

/**
* @author Qeuroal
* @date 2021-03-09 21:49
* @description 图标是一个接口,需要实现类,Frame继承
* @since
*/
public class TestIcon extends JFrame implements Icon {

public static void main(String[] args) {
// 首先生成TestIcon实例,通过这个实例再去生成新的TestIcon实例
new TestIcon().init();
}

private int width;
private int height;

public TestIcon() {}

public TestIcon(int width, int height) {
this.width = width;
this.height = height;
}

public void init() {
TestIcon testIcon = new TestIcon(30, 30);
// 图标放在标签上,也可以放在按钮上
JLabel iconTest = new JLabel("iconTest", testIcon, SwingConstants.CENTER);
Container contentPane = getContentPane();
contentPane.add(iconTest);
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}

@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
g.fillOval(x,y,width,height);
}

@Override
public int getIconWidth() {
return width;
}

@Override
public int getIconHeight() {
return height;
}
}

Icon

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
package com.kuangstudy.gui.module16;

import javax.swing.*;
import java.awt.*;
import java.net.URL;

/**
* @author Qeuroal
* @date 2021-03-09 22:14
* @description
* @since
*/
public class TestImageIcon extends JFrame {
public TestImageIcon() {
JLabel imageIconLabel = new JLabel("ImageIcon");
// 获取图片的地址
System.out.println(TestImageIcon.class);
URL resourceURL = TestImageIcon.class.getResource("/resource/xly2.png");
// 命名不要冲突了
ImageIcon imageIcon = new ImageIcon(resourceURL);

imageIconLabel.setIcon(imageIcon);
imageIconLabel.setHorizontalAlignment(SwingConstants.CENTER);

Container container = getContentPane();
container.add(imageIconLabel);

setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBounds(100, 100, 300, 300);

}


public static void main(String[] args) {
new TestImageIcon();
}
}

面板

JPanel

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
package com.kuangstudy.gui.module17;

import javax.swing.*;
import java.awt.*;

/**
* @author Qeuroal
* @date 2021-03-09 22:36
* @description
* @since
*/
public class TestJPanel extends JFrame {
public TestJPanel() {
Container container = getContentPane();
//后面参数的意思是间距
container.setLayout(new GridLayout(2, 1, 10, 10));

JPanel panel1 = new JPanel(new GridLayout(1, 3));
JPanel panel2 = new JPanel(new GridLayout(1, 2));
JPanel panel3 = new JPanel(new GridLayout(2, 2));

panel1.add(new JButton("1"));
panel1.add(new JButton("1"));
panel1.add(new JButton("1"));
panel2.add(new JButton("2"));
panel2.add(new JButton("2"));
panel3.add(new JButton("3"));
panel3.add(new JButton("3"));
panel3.add(new JButton("3"));
panel3.add(new JButton("3"));

container.add(panel1);
container.add(panel2);
container.add(panel3);

setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBounds(300, 300, 400, 300);
}

public static void main(String[] args) {
new TestJPanel();
}
}

JScrollPanel

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
package com.kuangstudy.gui.module17;

import javax.swing.*;
import java.awt.*;

/**
* @author Qeuroal
* @date 2021-03-09 22:45
* @description
* @since
*/
public class TestJScrollPanel extends JFrame {
public static void main(String[] args) {
new TestJScrollPanel();
}

public TestJScrollPanel() {
Container container = getContentPane();

// 文本域
JTextArea jTextArea = new JTextArea(20, 50);
jTextArea.setText("请输入文本");

// Scroll面板
JScrollPane jScrollPane = new JScrollPane(jTextArea);
container.add(jScrollPane);

setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBounds(300, 300, 400, 30);
}
}

按钮

图片按钮

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
package com.kuangstudy.gui.module18;

import javax.swing.*;
import java.awt.*;
import java.net.URL;

/**
* @author Qeuroal
* @date 2021-03-15 22:34
* @description
* @since
*/
public class TestButton extends JFrame {

public TestButton() {
Container container = this.getContentPane();
// 将一个图片变为图标
URL resource = TestButton.class.getResource("/resource/xly2.png");
Icon imageIcon = new ImageIcon(resource);

// 把图标放在按钮上
JButton btn = new JButton();
btn.setIcon(imageIcon);
btn.setToolTipText("图片按钮");

// add
container.add(btn);
this.setVisible(true);
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.setBounds(300, 300, 400, 300);
}

public static void main(String[] args) {
new TestButton();
}
}

单选按钮

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
package com.kuangstudy.gui.module18;

import javax.swing.*;
import java.awt.*;
import java.net.URL;

/**
* @author Qeuroal
* @date 2021-03-15 22:42
* @description
* @since
*/
public class TestButton2 extends JFrame {

public TestButton2() {
Container container = this.getContentPane();
// 将一个图片变为图标
URL resource = TestButton.class.getResource("/resource/xly2.png");
Icon imageIcon = new ImageIcon(resource);

// 单选框
JRadioButton jRadioButton1 = new JRadioButton("JRadioButton1");
JRadioButton jRadioButton2 = new JRadioButton("JRadioButton2");
JRadioButton jRadioButton3 = new JRadioButton("JRadioButton3");

// 由于单选框只能选个一个,所以:分组,一个组中只能选一个
ButtonGroup buttonGroup = new ButtonGroup();
buttonGroup.add(jRadioButton1);
buttonGroup.add(jRadioButton2);
buttonGroup.add(jRadioButton3);

container.add(jRadioButton1, BorderLayout.CENTER);
container.add(jRadioButton2, BorderLayout.NORTH);
container.add(jRadioButton3, BorderLayout.SOUTH);

this.setVisible(true);
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.setBounds(300, 300, 400, 300);
}

public static void main(String[] args) {
new TestButton2();
}
}

复选按钮

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
package com.kuangstudy.gui.module18;

import javax.swing.*;
import java.awt.*;
import java.net.URL;

/**
* @author Qeuroal
* @date 2021-03-15 22:48
* @description
* @since
*/
public class TestButton3 extends JFrame {

public TestButton3() {
Container container = this.getContentPane();
// 将一个图片变为图标
URL resource = TestButton.class.getResource("/resource/xly2.png");
Icon imageIcon = new ImageIcon(resource);

// 多选框
JCheckBox jCheckBox1 = new JCheckBox("jCheckBox1");
JCheckBox jCheckBox2 = new JCheckBox("jCheckBox2");

container.add(jCheckBox1, BorderLayout.NORTH);
container.add(jCheckBox2, BorderLayout.SOUTH);

this.setVisible(true);
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.setBounds(300, 300, 400, 300);
}

public static void main(String[] args) {
new TestButton3();
}
}

列表

下拉框

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
package com.kuangstudy.gui.module19;

import javax.swing.*;
import java.awt.*;

/**
* @author Qeuroal
* @date 2021-03-15 22:53
* @description
* @since
*/
public class TestCombobox extends JFrame {
public TestCombobox() {
super("TestCombobox");
Container container = this.getContentPane();

JComboBox status = new JComboBox();
status.addItem(null);
status.addItem("正在热播");
status.addItem("已下架");
status.addItem("即将上映");

container.add(status);

setVisible(true);
setBounds(300, 300, 500, 300);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}

public static void main(String[] args) {
new TestCombobox();
}
}

列表框

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
package com.kuangstudy.gui.module19;

import javax.swing.*;
import java.awt.*;

/**
* @author Qeuroal
* @date 2021-03-15 23:03
* @description
* @since
*/
public class TestCombobox2 extends JFrame {
public TestCombobox2() {
super("TestCombobox");
Container container = this.getContentPane();

// 生成列表的内容
String[] contents = {"1", "2", "3"};

JList jList = new JList(contents);
container.add(jList);

setVisible(true);
setBounds(300, 300, 500, 300);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}

public static void main(String[] args) {
new TestCombobox2();
}
}
  • 应用场景
    • 下拉框:选择地区,或者一些单个选项
    • 列表框:展示信息,一般是动态扩容

文本框

文本框

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
package com.kuangstudy.gui.module20;

import javax.swing.*;
import java.awt.*;

/**
* @author Qeuroal
* @date 2021-03-15 23:11
* @description
* @since
*/
public class TestText extends JFrame {
public TestText() {
super("TestCombobox");
Container container = this.getContentPane();
container.setLayout(null);

JTextField jTextField1 = new JTextField("hello");
JTextField jTextField2 = new JTextField("world", 20);

// 东西南北中布局,会自动填充满
container.add(jTextField1, BorderLayout.NORTH);
container.add(jTextField2, BorderLayout.SOUTH);


setVisible(true);
setBounds(300, 300, 500, 300);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}

public static void main(String[] args) {
new TestText();
}
}

密码框

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
package com.kuangstudy.gui.module20;

import javax.swing.*;
import java.awt.*;

/**
* @author Qeuroal
* @date 2021-03-15 23:14
* @description
* @since
*/
public class TestText2 extends JFrame {
public TestText2() {
super("TestCombobox");
Container container = this.getContentPane();

// 默认 ····
JPasswordField jPasswordField = new JPasswordField();
// 手动设置 ***
jPasswordField.setEchoChar('*');

container.add(jPasswordField);

setVisible(true);
setBounds(300, 300, 500, 300);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}

public static void main(String[] args) {
new TestText2();
}
}

文本域

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
package com.kuangstudy.gui.module17;

import javax.swing.*;
import java.awt.*;

/**
* @author Qeuroal
* @date 2021-03-09 22:45
* @description
* @since
*/
public class TestJScrollPanel extends JFrame {
public static void main(String[] args) {
new TestJScrollPanel();
}

public TestJScrollPanel() {
Container container = getContentPane();

// 文本域
JTextArea jTextArea = new JTextArea(20, 50);
jTextArea.setText("请输入文本");

// Scroll面板
JScrollPane jScrollPane = new JScrollPane(jTextArea);
container.add(jScrollPane);

setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBounds(300, 300, 400, 30);
}
}

游戏实践:贪吃蛇

如果时间片足够小,就是动画:一秒30帧(人眼就是动画了)

连起来是动画,拆开就是静态的图片。如:动漫,1秒24张画

键盘监听

定时器 Timer

代码

StartGame

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
package com.kuangstudy.gui.snake;

import javax.swing.*;

/**
* @author Qeuroal
* @date 2021-03-15 23:29
* @description 游戏的主启动类
* @since
*/
public class StartGame {
public static void main(String[] args) {
JFrame frame = new JFrame();

// 是算出来的,不能被拉伸,否则就会变形了
frame.setBounds(200, 100, 900, 720);
// 窗口大小不可变
frame.setResizable(false);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

// 整车游戏界面都在面板上
frame.add(new GamePanel());

frame.setVisible(true);
}
}

GamePanel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
package com.kuangstudy.gui.snake;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Random;

/**
* @author Qeuroal
* @date 2021-03-15 23:32
* @description 游戏的面板
* @since
*/
public class GamePanel extends JPanel implements KeyListener, ActionListener {

/**
* 定义蛇的数据结构
*/
// 蛇的长度
int length;
// 蛇的x坐标 25*25
int[] snakeX = new int[100];
// 蛇的Y坐标 25*25
int[] snakeY = new int[100];
// 初始方向
String fx;
// 游戏当前状态:开始,停止
boolean isStart= false;
// 食物的坐标
int foodX;
int foodY;
Random random = new Random();
// 积分
int score;
// 游戏失败状态
boolean isFail = false;
// 定时器:ms为单位,监听this这个对象。100毫秒执行一次。
Timer timer = new Timer(100, this);
/**
* 构造器
*/
public GamePanel() {
init();
// 获得焦点事件
this.setFocusable(true);
// 获取键盘事件
this.addKeyListener(this);
// 游戏一开始定时器就启动
timer.start();
}


/**
* 初始化方法
*/
public void init() {
length = 3;
// 脑袋的坐标
snakeX[0] = 100; snakeY[0] = 100;
// 第一个身体的坐标
snakeX[1] = 75; snakeY[1] = 100;
// 第二个身体的坐标
snakeX[2] = 50; snakeY[2] = 100;
fx = "R";
// 把食物随机放在界面上
foodX = 25 + 25 * random.nextInt(34);
foodY = 75 + 25 * random.nextInt(24);
// 积分
score = 0;
}


/**
* 绘制面板,我们游戏中的所有东西,都是用这个笔来画
* @param g
*/
@Override
protected void paintComponent(Graphics g) {
// 清屏
super.paintComponent(g);
setBackground(Color.WHITE);
// 绘制静态面板,头部广告栏画上去
Data.header.paintIcon(this, g, 25, 11);
// 默认的游戏界面
g.fillRect(25, 75, 850, 600);

// 画积分
g.setColor(Color.WHITE);
g.setFont(new Font("微软雅黑", Font.BOLD, 15));
g.drawString("长度: " + length,750, 35 );
g.drawString("分数: " + score, 750, 50);

// 画食物
Data.food.paintIcon(this, g, foodX, foodY);

// 把小蛇画上去
if (fx.equals("R")) {
Data.right.paintIcon(this, g, snakeX[0], snakeY[0]);
} else if (fx.equals("L")) {
Data.left.paintIcon(this, g, snakeX[0], snakeY[0]);
} else if (fx.equals("U")) {
Data.up.paintIcon(this, g, snakeX[0], snakeY[0]);
} else if (fx.equals("D")) {
Data.down.paintIcon(this, g, snakeX[0], snakeY[0]);
}
for (int i = 1; i < length; i++) {
Data.body.paintIcon(this, g, snakeX[i], snakeY[i]);
}

// 游戏状态
if (isStart == false) {
g.setColor(Color.WHITE);
// 设置字体
g.setFont(new Font("微软雅黑", Font.BOLD, 40));
g.drawString("按下空格开始游戏", 300, 300);
}

if (isFail) {
g.setColor(Color.RED);
// 设置字体
g.setFont(new Font("微软雅黑", Font.BOLD, 40));
g.drawString("失败,按下空格重新开始游戏", 300, 300);
}
}



/**
* 键盘监听事件
* @param e
*/
@Override
public void keyPressed(KeyEvent e) {
// 获得键盘按键是哪一个
int keyCode = e.getKeyCode();
// 如果按下的是空格键
if (keyCode == KeyEvent.VK_SPACE) {
if (isFail) {
// 重新开始
isFail = false;
init();
} else {
isStart = !isStart;
}
repaint();

}
// 小蛇移动
if (keyCode == KeyEvent.VK_UP) {
fx = "U";
} else if (keyCode == KeyEvent.VK_DOWN) {
fx = "D";
} else if (keyCode == KeyEvent.VK_LEFT) {
fx = "L";
} else if (keyCode == KeyEvent.VK_RIGHT) {
fx = "R";
}
}

@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyTyped(KeyEvent e) {
}

/**
* 事件监听——需要通过固定事件来刷新:10次/1s
* @param e
*/
@Override
public void actionPerformed(ActionEvent e) {
// 如果游戏是开始状态,就让小蛇动起来
if (isStart && isFail == false) {
// 吃食物
if (snakeX[0] == foodX && snakeY[0] == foodY) {
// 长度+1
length++;
// 分数+10
score += 10;
// 重新生成食物
foodX = 25 + 25 * random.nextInt(34);
foodY = 75 + 25 * random.nextInt(24);
}

// 移动:后一节移到前一节的位置
for (int i = length - 1; i > 0; i--) {
snakeX[i] = snakeX[i - 1];
snakeY[i] = snakeY[i - 1];
}
// 走向
if (fx.equals("R")) {
snakeX[0] += 25;
// 边界判断
if (snakeX[0] > 850) {
snakeX[0] = 25;
}
} else if (fx.equals("L")){
snakeX[0] -= 25;
if (snakeX[0] < 25) {
snakeX[0] = 850;
}
} else if (fx.equals("U")){
snakeY[0] -= 25;
if (snakeY[0] < 75) {
snakeY[0] = 650;
}
} else if (fx.equals("D")){
snakeY[0] += 25;
if (snakeY[0] > 650) {
snakeY[0] = 75;
}
}

// 失败判定:撞到自己就算失败
for (int i = 1; i < length; i++) {
if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) {
isFail = true;
}
}

// 重画页面
repaint();
}
// 定时器开始
timer.start();
}
}

总结

补充

C/S:客户端+服务器 (主流:C++)

B/S:浏览器+服务器 (主流:Java)

0%