代码拉取完成,页面将自动刷新
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>JWT</title>
<url>/2021/04/22/JWT/</url>
<content><![CDATA[<p>Json Web Token (JWT)是为了在网络应用环境间传递声明而执行的一种基于 JSON 的开放标准(RFC 7519)。该 token 被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT 的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该 token 也可直接被用于认证,也可被加密。笔记基于官方文档书写,<a href="https://jwt.io/introduction">查阅官方文档请点击这里</a> 。<span id="more"></span></p>
<h2 id="认证机制"><a href="#认证机制" class="headerlink" title="认证机制"></a>认证机制</h2><p>在实际开发项目中,由于 HTTP 是一种无状态的协议,我们想要记录用户的登录状态,或者为用户创建身份认证的凭证,可以使用 Session 认证机制或者 JWT 认证机制。</p>
<h3 id="传统的基于-session-认证"><a href="#传统的基于-session-认证" class="headerlink" title="传统的基于 session 认证"></a>传统的基于 session 认证</h3><p>HTTP 协议本身是一种无状态的协议,这意味着如果用户向应用提供用户名和密码来进行用户认证,那么在下一次请求时,用户还要再一次进行用户认证才行,因为根据 http 协议,我们并不能知道是哪个用户发出的请求,所以为了让应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在请求响应时传递给浏览器,告诉客户端保存为 cookie,以便下次请求时发送给应用,这样应用就能识别请求来自哪个用户了,这就是传统的基于 session 认证。</p>
<p>但是这种基于 session 的认证使应用本身很难得到扩展,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户,这时基于 session 认证应用的问题就会暴露出来。</p>
<ul>
<li><strong>资源占用</strong> :每个用户经过应用认证之后,应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言 session 都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大</li>
<li><strong>扩展性</strong> :用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力,这也意味着限制了应用的扩展能力</li>
<li><strong>CSRF</strong> :因为是基于 cookie 来进行用户识别的,cookie 如果被截获,用户就会很容易受到跨站请求伪造的攻击</li>
</ul>
<h3 id="基于-token-的鉴权机制"><a href="#基于-token-的鉴权机制" class="headerlink" title="基于 token 的鉴权机制"></a>基于 token 的鉴权机制</h3><p>基于 token 的鉴权机制类似于 HTTP 协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于 token 认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。</p>
<p>流程上是这样的:</p>
<ul>
<li>用户使用用户名密码来请求服务器</li>
<li>服务器进行验证用户的信息</li>
<li>服务器通过验证发送给用户一个 token</li>
<li>客户端存储 token,并在每次请求时附送上这个 token 值</li>
<li>服务端验证 token 值,并返回数据</li>
</ul>
<p>这个 token 必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持 CORS(跨来源资源共享)策略,一般我们在服务端这么做就可以了 <code>Access-Control-Allow-Origin: *</code>。</p>
<h2 id="JWT-介绍"><a href="#JWT-介绍" class="headerlink" title="JWT 介绍"></a>JWT 介绍</h2><p>Json Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,可以将各方之间的信息作为 JSON 对象安全地传输。此信息可以验证和信任,因为它是数字签名的。JWT 可以使用秘密(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对进行签名。</p>
<p>尽管 JWT 可以被加密以在各方之间提供保密性,但我们将重点关注签名令牌。签名令牌可以验证其中包含的声明的完整性,而加密令牌对其他方隐藏这些声明。当使用公钥/私钥对令牌进行签名时,签名还证明只有持有私钥的一方才是对其进行签名的一方。</p>
<h3 id="使用场景"><a href="#使用场景" class="headerlink" title="使用场景"></a>使用场景</h3><ul>
<li><strong>授权</strong> :这是使用 JWT 的最常见场景。一旦用户登录,每个后续请求将包括 JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是目前广泛使用 JWT 的一个特性,因为它的开销很小,并且能够很容易地跨不同的域使用。</li>
<li><strong>信息交流</strong> :Json Web Token 是在各方之间安全传输信息的好方法。因为 JWT 可以使用公钥/私钥对进行签名,因此可以确保发送者是他们所说的人。另外,由于签名是使用报头和有效负载计算的,所以您还可以验证内容没有被篡改</li>
</ul>
<h3 id="结构"><a href="#结构" class="headerlink" title="结构"></a>结构</h3><p>Json Web Token 由三部分组成,这三部分由点(.)分隔,分别是:<code>Header</code>、<code>Payload</code> 和 <code>Signature</code>。就像这样:</p>
<figure class="highlight markdown"><table><tr><td class="code"><pre><span class="line"> eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9</span><br><span class="line">.eyJpZCI6IjYiLCJleHAiOjE2MTg5OTMxMjcsInVzZXJuYW1lIjoiYXpoZW5Zb28ifQ</span><br><span class="line">.MDt2q9KZWp1v<span class="emphasis">_jPZf5iCoz3vjxlsTVMOpOW36ouVqbI</span></span><br></pre></td></tr></table></figure>
<h4 id="Header"><a href="#Header" class="headerlink" title="Header"></a>Header</h4><p>头部由两部分组成:令牌的类型(JWT)和正在使用的签名算法(如 HMAC SHA256 或 RSA)。</p>
<p>完整的头部就像下面这样的 JSON:</p>
<figure class="highlight"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> 'typ': 'JWT',</span><br><span class="line"> 'alg': 'HS256'</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>然后对这个 JSON 进行 <code>Base64Url</code> 编码,形成 JWT 的第一部分:<code>eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9</code>。</p>
<h4 id="Payload"><a href="#Payload" class="headerlink" title="Payload"></a>Payload</h4><p>令牌的第二部分是有效负载,它包含一些声明。这些声明是包含一个实体(通常是用户)和其他的声明数据。这里有三种类型的声明:标准中注册的声明、公共的声明和私有的声明。</p>
<ul>
<li><strong>标准中注册的声明</strong> :这些是一组预定义的声明,它们不是强制性的,而是推荐的,以提供一组有用的、可互操作的声明。<strong>iss</strong>(JWT 签发者)、<strong>sub</strong>(JWT 所面向的用户)、<strong>aud</strong>(接收 JWT 的一方)、<strong>exp</strong>(JWT 的过期时间,这个过期时间必须要大于签发时间)、<strong>nbf</strong>(定义在什么时间之前,该 JWT 都是不可用的)、<strong>iat</strong>( JWT 的签发时间)、<strong>jti</strong>(JWT 的唯一身份标识,主要用来作为一次性 token,从而回避重放攻击)</li>
<li><strong>公共的声明</strong> :公共的声明可以由使用JWT 的人随意定义,但是为了避免冲突,应该在 IANA JSON Web Token Registry 中定义它们,或者将它们定义为包含防冲突命名空间的 URI。并且一般添加用户的相关信息或其他业务需要的必要信息,不建议添加敏感信息,因为该部分在客户端可解密</li>
<li><strong>私有的声明</strong> :私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为 base64 是对称解密的,意味着该部分信息可以归类为明文信息</li>
</ul>
<p>一个有效载荷示例如下:</p>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"sub"</span>: <span class="string">"1234567890"</span>,</span><br><span class="line"> <span class="attr">"name"</span>: <span class="string">"John Doe"</span>,</span><br><span class="line"> <span class="attr">"admin"</span>: <span class="literal">true</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>然后对有效负载进行 Base64Url 编码,形成 JWT 的第二部分。</p>
<h4 id="Signature"><a href="#Signature" class="headerlink" title="Signature"></a>Signature</h4><p>JWT 的第三部分是一个签证信息,要创建签名部分,您必须获取编码的报头、编码的有效负载、一个秘密(盐)、报头中指定的算法,并对其进行签名。</p>
<p>例如,如果要使用 HMAC SHA256 算法,将按以下方式创建签名:</p>
<figure class="highlight"><table><tr><td class="code"><pre><span class="line">HMACSHA256(</span><br><span class="line"> base64UrlEncode(header) + "." +</span><br><span class="line"> base64UrlEncode(payload),</span><br><span class="line"> secret</span><br><span class="line">)</span><br></pre></td></tr></table></figure>
<p>签名用于验证消息在发送过程中没有发生更改,对于使用私钥签名的令牌,它还可以验证 JWT 的发送者是它所说的发送者。</p>
<blockquote>
<p><strong>注意</strong> :secret 是保存在服务器端的,JWT 的签发生成也是在服务器端的,secret 就是用来进行 JWT 的签发和 JWT 的验证,所以它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个 secret, 那就意味着客户端是可以自我签发JWT 了。</p>
</blockquote>
<p>把所有的东西放在一起,输出是三个 Base64Url 字符串,用点分隔,可以在 HTML 和 HTTP 环境中轻松地传递这些字符串,但与基于 XML 的标准(如SAML)相比,它更紧凑。如果您想使用 JWT 并将这些概念付诸实践,可以使用 <a href="https://jwt.io/#debugger-io">Jwt.io</a> 调试器解码、验证和生成 JWT。</p>
<h3 id="应用"><a href="#应用" class="headerlink" title="应用"></a>应用</h3><p>整个基于 token 认证机制流程就是这样的:</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/JWT/%E8%AE%A4%E8%AF%81%E6%B5%81%E7%A8%8B.png" alt="认证流程"></p>
<h2 id="整合-SpringBoot"><a href="#整合-SpringBoot" class="headerlink" title="整合 SpringBoot"></a>整合 SpringBoot</h2><h3 id="搭建数据库"><a href="#搭建数据库" class="headerlink" title="搭建数据库"></a>搭建数据库</h3><p>本项目选择 MySQL 数据库,创建数据库 <code>jwt</code>,创建用户表 <code>t_user</code>,表结构和数据如下:</p>
<figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- ----------------------------</span></span><br><span class="line"><span class="comment">-- Table structure for t_user</span></span><br><span class="line"><span class="comment">-- ----------------------------</span></span><br><span class="line"><span class="keyword">DROP</span> <span class="keyword">TABLE</span> IF <span class="keyword">EXISTS</span> `t_user`;</span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> `t_user` (</span><br><span class="line"> `id` <span class="type">bigint</span>(<span class="number">20</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span> COMMENT <span class="string">'主键ID'</span>,</span><br><span class="line"> `name` <span class="type">varchar</span>(<span class="number">30</span>) <span class="type">CHARACTER</span> <span class="keyword">SET</span> utf8 <span class="keyword">COLLATE</span> utf8_general_ci <span class="keyword">NULL</span> <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">'姓名'</span>,</span><br><span class="line"> `password` <span class="type">varchar</span>(<span class="number">255</span>) <span class="type">CHARACTER</span> <span class="keyword">SET</span> utf8 <span class="keyword">COLLATE</span> utf8_general_ci <span class="keyword">NULL</span> <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">'密码'</span>,</span><br><span class="line"> `age` <span class="type">int</span>(<span class="number">11</span>) <span class="keyword">NULL</span> <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">'年龄'</span>,</span><br><span class="line"> `email` <span class="type">varchar</span>(<span class="number">50</span>) <span class="type">CHARACTER</span> <span class="keyword">SET</span> utf8 <span class="keyword">COLLATE</span> utf8_general_ci <span class="keyword">NULL</span> <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">'邮箱'</span>,</span><br><span class="line"> <span class="keyword">PRIMARY</span> KEY (`id`) <span class="keyword">USING</span> BTREE</span><br><span class="line">) ENGINE <span class="operator">=</span> InnoDB <span class="type">CHARACTER</span> <span class="keyword">SET</span> <span class="operator">=</span> utf8 <span class="keyword">COLLATE</span> <span class="operator">=</span> utf8_general_ci ROW_FORMAT <span class="operator">=</span> <span class="keyword">Dynamic</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- ----------------------------</span></span><br><span class="line"><span class="comment">-- Records of t_user</span></span><br><span class="line"><span class="comment">-- ----------------------------</span></span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> `t_user` <span class="keyword">VALUES</span> (<span class="number">1</span>, <span class="string">'Jone'</span>, <span class="string">'123456'</span>, <span class="number">18</span>, <span class="string">'test1@baomidou.com'</span>);</span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> `t_user` <span class="keyword">VALUES</span> (<span class="number">2</span>, <span class="string">'Jack'</span>, <span class="string">'123456'</span>, <span class="number">20</span>, <span class="string">'test2@baomidou.com'</span>);</span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> `t_user` <span class="keyword">VALUES</span> (<span class="number">3</span>, <span class="string">'Tom'</span>, <span class="string">'123456'</span>, <span class="number">28</span>, <span class="string">'test3@baomidou.com'</span>);</span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> `t_user` <span class="keyword">VALUES</span> (<span class="number">4</span>, <span class="string">'Sandy'</span>, <span class="string">'123456'</span>, <span class="number">21</span>, <span class="string">'test4@baomidou.com'</span>);</span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> `t_user` <span class="keyword">VALUES</span> (<span class="number">5</span>, <span class="string">'Billie'</span>, <span class="string">'123456'</span>, <span class="number">24</span>, <span class="string">'test5@baomidou.com'</span>);</span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> `t_user` <span class="keyword">VALUES</span> (<span class="number">6</span>, <span class="string">'azhenYoo'</span>, <span class="string">'123456'</span>, <span class="number">28</span>, <span class="string">'azhen_work@126.com'</span>);</span><br></pre></td></tr></table></figure>
<h3 id="初始化项目"><a href="#初始化项目" class="headerlink" title="初始化项目"></a>初始化项目</h3><p>创建 jwt 的 SpringBoot 项目,项目包结构为 <code>com.azhen</code>,创建项目时记得选择 <code>SpringWeb</code> 和 <code>lombok</code> 相关的依赖。项目创建成功后,第一件事当然是引入其他相关依赖啦!!!</p>
<h4 id="MySQL-数据库驱动依赖"><a href="#MySQL-数据库驱动依赖" class="headerlink" title="MySQL 数据库驱动依赖"></a>MySQL 数据库驱动依赖</h4><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="comment"><!-- 数据库驱动 --></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>mysql<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>mysql-connector-java<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
<h4 id="JWT-依赖"><a href="#JWT-依赖" class="headerlink" title="JWT 依赖"></a>JWT 依赖</h4><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="comment"><!-- JWT --></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>com.auth0<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>java-jwt<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>3.15.0<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
<h4 id="mybatis-plus-相关依赖"><a href="#mybatis-plus-相关依赖" class="headerlink" title="mybatis-plus 相关依赖"></a>mybatis-plus 相关依赖</h4><blockquote>
<p><strong>温馨提示</strong>:为了自动化生成 mapper、service、entity、controller 层的代码,本项目引入了 <code>mybatis-plus</code> 的相关依赖。如果不想自动生成代码的小伙伴可以跳过教程中关于 mybatis-plus 的所有内容,相应的代码也需要自己编写哦!</p>
</blockquote>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="comment"><!-- mybatis-plus-generator --></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>com.baomidou<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>mybatis-plus-generator<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>3.4.1<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"></span><br><span class="line"><span class="comment"><!-- velocity --></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.apache.velocity<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>velocity-engine-core<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>2.2<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"></span><br><span class="line"><span class="comment"><!-- mybatis-plus --></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>com.baomidou<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>mybatis-plus-boot-starter<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>3.4.2<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
<h3 id="编写公共类"><a href="#编写公共类" class="headerlink" title="编写公共类"></a>编写公共类</h3><p>在一般的项目中都会定义统一的结果响应结构以及响应状态码,公共包路径:<code>com.azhen.common</code>。该部分为可选看章节,在后续代码中可用 Map 来做统一结果响应结构。</p>
<h4 id="ResultEntity-结果响应结构"><a href="#ResultEntity-结果响应结构" class="headerlink" title="ResultEntity 结果响应结构"></a>ResultEntity 结果响应结构</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@AllArgsConstructor</span></span><br><span class="line"><span class="meta">@NoArgsConstructor</span></span><br><span class="line"><span class="meta">@Accessors(chain = true)</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ResultEntity</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> code;</span><br><span class="line"> <span class="keyword">private</span> String message;</span><br><span class="line"> <span class="keyword">private</span> Map<String, Object> data = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ResultEntity <span class="title">ok</span><span class="params">(String message)</span> </span>{</span><br><span class="line"> ResultEntity resultEntity = <span class="keyword">new</span> ResultEntity();</span><br><span class="line"> resultEntity.setCode(ResultCode.SUCCESS.getCode()).setMessage(message);</span><br><span class="line"> <span class="keyword">return</span> resultEntity;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ResultEntity <span class="title">ok</span><span class="params">(String message, Map<String, Object> data)</span> </span>{</span><br><span class="line"> ResultEntity resultEntity = <span class="keyword">new</span> ResultEntity();</span><br><span class="line"> resultEntity.setCode(ResultCode.SUCCESS.getCode()).setMessage(message).setData(data);</span><br><span class="line"> <span class="keyword">return</span> resultEntity;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ResultEntity <span class="title">error</span><span class="params">(String message)</span> </span>{</span><br><span class="line"> ResultEntity resultEntity = <span class="keyword">new</span> ResultEntity();</span><br><span class="line"> resultEntity.setCode(ResultCode.FAILED.getCode()).setMessage(message);</span><br><span class="line"> <span class="keyword">return</span> resultEntity;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="ResultCode-响应状态码"><a href="#ResultCode-响应状态码" class="headerlink" title="ResultCode 响应状态码"></a>ResultCode 响应状态码</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">enum</span> <span class="title">ResultCode</span> </span>{</span><br><span class="line"></span><br><span class="line"> SUCCESS(<span class="number">1000</span>, <span class="string">"操作成功"</span>),</span><br><span class="line"> FAILED(<span class="number">1001</span>, <span class="string">"操作失败"</span>),</span><br><span class="line"> NOT_LOGIN(<span class="number">1002</span>, <span class="string">"未登录"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">int</span> code;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> String describe;</span><br><span class="line"></span><br><span class="line"> ResultCode(<span class="keyword">int</span> code, String describe) {</span><br><span class="line"> <span class="keyword">this</span>.code = code;</span><br><span class="line"> <span class="keyword">this</span>.describe = describe;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getCode</span><span class="params">()</span> </span>{<span class="keyword">return</span> <span class="keyword">this</span>.code;}</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">getDescribe</span><span class="params">()</span> </span>{<span class="keyword">return</span> <span class="keyword">this</span>.describe;}</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="Mybatis-Plus-配置"><a href="#Mybatis-Plus-配置" class="headerlink" title="Mybatis-Plus 配置"></a>Mybatis-Plus 配置</h3><blockquote>
<p><strong>说明</strong>:下面要进行 Mybatis-Plus 的相关配置啦, 如果不了解 Mybatis-Plus 的小伙伴可以先阅读一下《<a href="/2021/04/18/MybatisPlus/" title="MyBatis-Plus">MyBatis-Plus</a>》这篇文章,如果实在不想阅读也没关系,只要按照本文的步骤来做,就不会出现问题哈 o(<em> ̄▽ ̄</em>)ブ</p>
</blockquote>
<p>在 SpringBoot 项目的启动类上添加扫描 mapper 包注解 <code>@MapperScan("com.azhen.mapper")</code>,</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@MapperScan("com.azhen.mapper")</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JwtApplication</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{ SpringApplication.run(JwtApplication.class, args); }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="application-yml-配置文件"><a href="#application-yml-配置文件" class="headerlink" title="application.yml 配置文件"></a>application.yml 配置文件</h4><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 数据库配置</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line"> <span class="attr">datasource:</span></span><br><span class="line"> <span class="attr">username:</span> <span class="string">root</span></span><br><span class="line"> <span class="attr">password:</span> <span class="string">root</span></span><br><span class="line"> <span class="attr">url:</span> <span class="string">jdbc:mysql://127.0.0.1:3306/jwt?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC</span></span><br><span class="line"> <span class="attr">driver-class-name:</span> <span class="string">com.mysql.cj.jdbc.Driver</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># mybatis-plus 配置</span></span><br><span class="line"><span class="attr">mybatis-plus:</span></span><br><span class="line"> <span class="attr">configuration:</span></span><br><span class="line"> <span class="attr">log-impl:</span> <span class="string">org.apache.ibatis.logging.stdout.StdOutImpl</span></span><br><span class="line"> <span class="attr">type-aliases-package:</span> <span class="string">com.azhen.entity</span></span><br><span class="line"> <span class="attr">mapper-locations:</span> <span class="string">com.azhen.mapper.xml/*.xml</span></span><br></pre></td></tr></table></figure>
<h4 id="CodeGenerator-代码生成器"><a href="#CodeGenerator-代码生成器" class="headerlink" title="CodeGenerator 代码生成器"></a>CodeGenerator 代码生成器</h4><p>在 test 中编写代码生成器代码(路径:<code>test/java/com/azhen</code>),需要根据自己的项目情况更改相关配置内容,编写完成后执行生成代码。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CodeGenerator</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> <span class="comment">// 构建一个代码生成器对象</span></span><br><span class="line"> AutoGenerator autoGenerator = <span class="keyword">new</span> AutoGenerator();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 1. 全局配置</span></span><br><span class="line"> GlobalConfig globalConfig = <span class="keyword">new</span> GlobalConfig();</span><br><span class="line"> String projectPath = System.getProperty(<span class="string">"user.dir"</span>);</span><br><span class="line"> <span class="comment">// 设置输出路径</span></span><br><span class="line"> globalConfig.setOutputDir(projectPath + <span class="string">"/src/main/java"</span>);</span><br><span class="line"> <span class="comment">// 设置作者</span></span><br><span class="line"> globalConfig.setAuthor(<span class="string">"Huang Yuchen"</span>);</span><br><span class="line"> <span class="comment">// 设置打开资源管理器</span></span><br><span class="line"> globalConfig.setOpen(<span class="keyword">false</span>);</span><br><span class="line"> <span class="comment">// 设置是否覆盖已存在的文件</span></span><br><span class="line"> globalConfig.setFileOverride(<span class="keyword">true</span>);</span><br><span class="line"> <span class="comment">// 设置服务接口名称</span></span><br><span class="line"> globalConfig.setServiceName(<span class="string">"%sService"</span>);</span><br><span class="line"> <span class="comment">// 设置服务接口实现类名称</span></span><br><span class="line"> globalConfig.setServiceImplName(<span class="string">"%sServiceImpl"</span>);</span><br><span class="line"> <span class="comment">// 设置主键生成策略</span></span><br><span class="line"> globalConfig.setIdType(IdType.ASSIGN_ID);</span><br><span class="line"> <span class="comment">// 设置日期类型</span></span><br><span class="line"> globalConfig.setDateType(DateType.ONLY_DATE);</span><br><span class="line"> <span class="comment">// 将全局配置添加到代码生成器对象中</span></span><br><span class="line"> autoGenerator.setGlobalConfig(globalConfig);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 2. 数据源配置</span></span><br><span class="line"> DataSourceConfig dataSourceConfig = <span class="keyword">new</span> DataSourceConfig();</span><br><span class="line"> dataSourceConfig.setDriverName(<span class="string">"com.mysql.cj.jdbc.Driver"</span>);</span><br><span class="line"> dataSourceConfig.setUrl(<span class="string">"jdbc:mysql://127.0.0.1:3306/jwt?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC"</span>);</span><br><span class="line"> dataSourceConfig.setDbType(DbType.MYSQL);</span><br><span class="line"> dataSourceConfig.setUsername(<span class="string">"root"</span>);</span><br><span class="line"> dataSourceConfig.setPassword(<span class="string">"root"</span>);</span><br><span class="line"> <span class="comment">// 将数据源配置添加到代码生成器对象中</span></span><br><span class="line"> autoGenerator.setDataSource(dataSourceConfig);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 3. 包配置</span></span><br><span class="line"> PackageConfig packageConfig = <span class="keyword">new</span> PackageConfig();</span><br><span class="line"> packageConfig.setModuleName(<span class="string">"azhen"</span>);</span><br><span class="line"> packageConfig.setParent(<span class="string">"com"</span>);</span><br><span class="line"> packageConfig.setEntity(<span class="string">"entity"</span>);</span><br><span class="line"> packageConfig.setMapper(<span class="string">"mapper"</span>);</span><br><span class="line"> packageConfig.setService(<span class="string">"service"</span>);</span><br><span class="line"> packageConfig.setController(<span class="string">"controller"</span>);</span><br><span class="line"> packageConfig.setServiceImpl(<span class="string">"service.impl"</span>);</span><br><span class="line"> <span class="comment">// 将包配置添加到代码生成器对象中</span></span><br><span class="line"> autoGenerator.setPackageInfo(packageConfig);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 4. 策略配置</span></span><br><span class="line"> StrategyConfig strategyConfig = <span class="keyword">new</span> StrategyConfig();</span><br><span class="line"> <span class="comment">// 设置映射的表名</span></span><br><span class="line"> strategyConfig.setInclude(<span class="string">"t_user"</span>);</span><br><span class="line"> strategyConfig.setTablePrefix(<span class="string">"t_"</span>);</span><br><span class="line"> <span class="comment">// 下划线转驼峰命名</span></span><br><span class="line"> strategyConfig.setNaming(NamingStrategy.underline_to_camel);</span><br><span class="line"> strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);</span><br><span class="line"> <span class="comment">// 设置是否开启lombok注解</span></span><br><span class="line"> strategyConfig.setEntityLombokModel(<span class="keyword">true</span>);</span><br><span class="line"> <span class="comment">// 生成实体类字段注解</span></span><br><span class="line"> strategyConfig.setEntityTableFieldAnnotationEnable(<span class="keyword">true</span>);</span><br><span class="line"> <span class="comment">// 开启RestController风格</span></span><br><span class="line"> strategyConfig.setRestControllerStyle(<span class="keyword">true</span>);</span><br><span class="line"> <span class="comment">// controller映射地址:驼峰转连字符</span></span><br><span class="line"> strategyConfig.setControllerMappingHyphenStyle(<span class="keyword">true</span>);</span><br><span class="line"> <span class="comment">// 将策略配置添加到代码生成器对象中</span></span><br><span class="line"> autoGenerator.setStrategy(strategyConfig);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 执行代码生成器对象</span></span><br><span class="line"> autoGenerator.execute();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="JWT-配置"><a href="#JWT-配置" class="headerlink" title="JWT 配置"></a>JWT 配置</h3><h4 id="JWTInterceptor-拦截器"><a href="#JWTInterceptor-拦截器" class="headerlink" title="JWTInterceptor 拦截器"></a>JWTInterceptor 拦截器</h4><p>为了做统一的 JWT 请求拦截,此处编写 JWT 拦截器代码(路径:<code>com.azhen.interceptors</code>),</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JWTInterceptor</span> <span class="keyword">implements</span> <span class="title">HandlerInterceptor</span> </span>{</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">preHandle</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object handler)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">// 获取请求头中的令牌</span></span><br><span class="line"> String token = request.getHeader(<span class="string">"token"</span>);</span><br><span class="line"> ResultEntity resultEntity;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> DecodedJWT verify = JWTUtils.verify(token);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> } <span class="keyword">catch</span> (TokenExpiredException e) {</span><br><span class="line"> resultEntity = ResultEntity.error(<span class="string">"登陆凭证已过期"</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (SignatureVerificationException e) {</span><br><span class="line"> resultEntity = ResultEntity.error(<span class="string">"无效签名"</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (AlgorithmMismatchException e) {</span><br><span class="line"> resultEntity = ResultEntity.error(<span class="string">"算法不一致"</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> resultEntity = ResultEntity.error(<span class="string">"签名认证失败"</span>);</span><br><span class="line"> }</span><br><span class="line"> String json = <span class="keyword">new</span> ObjectMapper().writeValueAsString(resultEntity);</span><br><span class="line"> response.setContentType(<span class="string">"application/json;charset=UTF-8"</span>);</span><br><span class="line"> response.getWriter().println(json);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="JWT-拦截器配置类"><a href="#JWT-拦截器配置类" class="headerlink" title="JWT 拦截器配置类"></a>JWT 拦截器配置类</h4><p>拦截器的相关配置(路径:<code>con.azhen.config</code>),</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">InterceptorConfig</span> <span class="keyword">implements</span> <span class="title">WebMvcConfigurer</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addInterceptors</span><span class="params">(InterceptorRegistry registry)</span> </span>{</span><br><span class="line"> registry.addInterceptor(<span class="keyword">new</span> JWTInterceptor())</span><br><span class="line"> <span class="comment">// 拦截的请求路径</span></span><br><span class="line"> .addPathPatterns(<span class="string">"/**"</span>)</span><br><span class="line"> <span class="comment">// 不拦截的请求路径</span></span><br><span class="line"> .excludePathPatterns(<span class="string">"/user/login"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="JWT-工具类"><a href="#JWT-工具类" class="headerlink" title="JWT 工具类"></a>JWT 工具类</h4><p>为了便于后续业务使用,我们将 JWT 常用的 API 封装成工具类(路径:<code>com.azhen.utils</code>),</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JWTUtils</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String SIGN = <span class="string">"!@61fwq7ds16w#&(qd6$^#6da4d6"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 生成token</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> claims</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">getToken</span><span class="params">(Map<String, String> claims)</span> </span>{</span><br><span class="line"> Calendar calendar = Calendar.getInstance();</span><br><span class="line"> <span class="comment">// 方便测试,默认30秒过期,可自行设置</span></span><br><span class="line"> calendar.add(Calendar.SECOND, <span class="number">30</span>);</span><br><span class="line"> JWTCreator.Builder builder = JWT.create();</span><br><span class="line"> <span class="comment">// 设置Claims</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> != claims) {</span><br><span class="line"> claims.forEach((k,v)-> builder.withClaim(k,v));</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 设置过期时间</span></span><br><span class="line"> builder.withExpiresAt(calendar.getTime());</span><br><span class="line"> <span class="comment">// 设置签名</span></span><br><span class="line"> String token = builder.sign(Algorithm.HMAC256(SIGN));</span><br><span class="line"> <span class="keyword">return</span> token;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 生成token</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> headerMap</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> claims</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">getToken</span><span class="params">(Map<String, Object> headerMap, Map<String, String> claims)</span> </span>{</span><br><span class="line"> Calendar calendar = Calendar.getInstance();</span><br><span class="line"> calendar.add(Calendar.SECOND, <span class="number">30</span>); <span class="comment">// 默认30秒过期</span></span><br><span class="line"> JWTCreator.Builder builder = JWT.create();</span><br><span class="line"> <span class="comment">// 设置header</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> != headerMap) {</span><br><span class="line"> builder.withHeader(headerMap);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 设置Claims</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> != claims) {</span><br><span class="line"> claims.forEach((k,v)->builder.withClaim(k,v));</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 设置过期时间</span></span><br><span class="line"> builder.withExpiresAt(calendar.getTime());</span><br><span class="line"> <span class="comment">// 设置签名</span></span><br><span class="line"> String token = builder.sign(Algorithm.HMAC256(SIGN));</span><br><span class="line"> <span class="keyword">return</span> token;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取token内容</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> token</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> DecodedJWT <span class="title">verify</span><span class="params">(String token)</span> </span>{</span><br><span class="line"> DecodedJWT verify = JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);</span><br><span class="line"> <span class="keyword">return</span> verify;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="逻辑代码"><a href="#逻辑代码" class="headerlink" title="逻辑代码"></a>逻辑代码</h3><h4 id="mapper-层"><a href="#mapper-层" class="headerlink" title="mapper 层"></a>mapper 层</h4><p>如果没有什么复杂逻辑的情况下,mapper 层不需要变动,记得加上 <code>@Repository</code> 注解即可,</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Repository</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UserMapper</span> <span class="keyword">extends</span> <span class="title">BaseMapper</span><<span class="title">User</span>> </span>{</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="seriver-层"><a href="#seriver-层" class="headerlink" title="seriver 层"></a>seriver 层</h4><p>UserService 用户服务接口添加登录接口,</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UserService</span> <span class="keyword">extends</span> <span class="title">IService</span><<span class="title">User</span>> </span>{</span><br><span class="line"> <span class="function">User <span class="title">login</span><span class="params">(User user)</span></span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>UserServiceImpl 用户服务接口实现类添加登录代码逻辑,</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserServiceImpl</span> <span class="keyword">extends</span> <span class="title">ServiceImpl</span><<span class="title">UserMapper</span>, <span class="title">User</span>> <span class="keyword">implements</span> <span class="title">UserService</span> </span>{</span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> UserMapper userMapper;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> User <span class="title">login</span><span class="params">(User user)</span> </span>{</span><br><span class="line"> QueryWrapper<User> queryWrapper = <span class="keyword">new</span> QueryWrapper<>();</span><br><span class="line"> queryWrapper.eq(<span class="string">"name"</span>, user.getName()).eq(<span class="string">"password"</span>, user.getPassword());</span><br><span class="line"> <span class="keyword">return</span> userMapper.selectOne(queryWrapper);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="controller-层"><a href="#controller-层" class="headerlink" title="controller 层"></a>controller 层</h4><p>UserController 用户控制器添加登录以及测试代码,登录代码用于生成 token 并返回给客户端,测试代码用于测试携带 token 请求的响应过程,</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping("/user")</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserController</span> </span>{</span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> UserService userService;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@PostMapping("/login")</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> ResultEntity <span class="title">login</span><span class="params">(<span class="meta">@RequestBody</span> User user)</span> </span>{</span><br><span class="line"> <span class="comment">// 登录逻辑</span></span><br><span class="line"> User result = userService.login(user);</span><br><span class="line"> <span class="keyword">if</span> (result == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> ResultEntity.error(<span class="string">"登陆失败"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 生成token令牌</span></span><br><span class="line"> Map<String, String> claims = <span class="keyword">new</span> ConcurrentHashMap<>();</span><br><span class="line"> claims.put(<span class="string">"id"</span>, String.valueOf(result.getId()));</span><br><span class="line"> claims.put(<span class="string">"username"</span>, result.getName());</span><br><span class="line"> String token = JWTUtils.getToken(claims);</span><br><span class="line"> Map<String, Object> data = <span class="keyword">new</span> ConcurrentHashMap<>();</span><br><span class="line"> data.put(<span class="string">"token"</span>, token);</span><br><span class="line"> <span class="keyword">return</span> ResultEntity.ok(<span class="string">"登录成功"</span>).setData(data);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@PostMapping("/test")</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> ResultEntity <span class="title">test</span><span class="params">(HttpServletRequest request)</span> </span>{</span><br><span class="line"> <span class="comment">// 获取请求头中的令牌,</span></span><br><span class="line"> String token = request.getHeader(<span class="string">"token"</span>);</span><br><span class="line"> DecodedJWT verify = JWTUtils.verify(token);</span><br><span class="line"> Map<String, Object> data = <span class="keyword">new</span> ConcurrentHashMap<>();</span><br><span class="line"> data.put(<span class="string">"username"</span>, verify.getClaim(<span class="string">"username"</span>).asString());</span><br><span class="line"> data.put(<span class="string">"userId"</span>, verify.getClaim(<span class="string">"id"</span>).asString());</span><br><span class="line"> <span class="keyword">return</span> ResultEntity.ok(ResultCode.SUCCESS.getDescribe()).setData(data);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<blockquote>
<p><strong>说明</strong>:<code>/user/test</code> 接口中的 <code>HttpServletRequest request</code> 参数是不必要,设置该参数的目的是为了获取 token 中的信息,例如:上述代码中获取了 token 中存储的用户 ID 和姓名信息,并将其返回给客户端。</p>
</blockquote>
<h3 id="启动项目"><a href="#启动项目" class="headerlink" title="启动项目"></a>启动项目</h3><p>启动项目,项目端口默认为 <code>8080</code>,端口可以在 <code>application.yml</code> 配置文件中配置,</p>
<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">server:</span></span><br><span class="line"> <span class="attr">port:</span> <span class="number">8888</span></span><br></pre></td></tr></table></figure>
<p>启动 postman 进行测试,</p>
<h4 id="登陆接口"><a href="#登陆接口" class="headerlink" title="登陆接口"></a>登陆接口</h4><p>接口相关设置如下图所示:</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/JWT/%E7%99%BB%E9%99%86%E6%8E%A5%E5%8F%A3.png" alt="登录接口"></p>
<ul>
<li>请求方式:<code>POST</code> </li>
<li>请求地址:<code>http://127.0.0.1:8888/user/login</code> </li>
<li>请求内容:</li>
</ul>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"><span class="attr">"name"</span>: <span class="string">"azhenYoo"</span>,</span><br><span class="line"><span class="attr">"password"</span>: <span class="string">"123456"</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>设置完毕后点击 【Send】,由于这个用户名和密码在数据库中是存在的(即用户存在)响应结果如下:</p>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"code"</span>: <span class="number">1000</span>,</span><br><span class="line"> <span class="attr">"message"</span>: <span class="string">"登录成功"</span>,</span><br><span class="line"> <span class="attr">"data"</span>: {</span><br><span class="line"> <span class="attr">"token"</span>: <span class="string">"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjYiLCJleHAiOjE2MTkwNjcwMzgsInVzZXJuYW1lIjoiYXpoZW5Zb28ifQ.Jnx5ZlJVbzpn-12f3zVSFBpZD8bV_vR5O7_iHXLRFRM"</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>其中 token 字段就是服务端生成的 token,客户端可以将其保存起来以便于下次请求时携带。如果查询的用户不存在,响应结果如下:</p>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"code"</span>: <span class="number">1001</span>,</span><br><span class="line"> <span class="attr">"message"</span>: <span class="string">"登陆失败"</span>,</span><br><span class="line"> <span class="attr">"data"</span>: <span class="literal">null</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="携带-token-请求接口"><a href="#携带-token-请求接口" class="headerlink" title="携带 token 请求接口"></a>携带 token 请求接口</h4><p>接口相关设置如下图所示:</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/JWT/%E6%90%BA%E5%B8%A6token%E8%AF%B7%E6%B1%82%E6%8E%A5%E5%8F%A3.png" alt="携带token请求接口"></p>
<p>在一般的开发中,token 信息不会放在参数中进行传递,而是放在 header 请求头中进行传递,</p>
<ul>
<li>请求方式:<code>POST</code> </li>
<li>请求地址:<code>http://127.0.0.1:8888/user/test</code> </li>
</ul>
<blockquote>
<p><strong>注意</strong> :此处 token 的设置为登录接口生成的 token,token 的有效期限为 30 秒(30 秒是为了测试方便,一般项目中常见的为 1 天或者 7 天),token 超时请求则会报错。</p>
</blockquote>
<p>设置完毕后点击 【Send】,响应结果如下:</p>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"code"</span>: <span class="number">1000</span>,</span><br><span class="line"> <span class="attr">"message"</span>: <span class="string">"操作成功"</span>,</span><br><span class="line"> <span class="attr">"data"</span>: {</span><br><span class="line"> <span class="attr">"userId"</span>: <span class="string">"6"</span>,</span><br><span class="line"> <span class="attr">"username"</span>: <span class="string">"azhenYoo"</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>如果在请求头中不设置 token 或者 token 的值为空进行请求,响应结果如下:</p>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"code"</span>: <span class="number">1001</span>,</span><br><span class="line"> <span class="attr">"message"</span>: <span class="string">"签名认证失败"</span>,</span><br><span class="line"> <span class="attr">"data"</span>: <span class="literal">null</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>如果设置错误 token 进行请求,响应结果如下:</p>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"code"</span>: <span class="number">1001</span>,</span><br><span class="line"> <span class="attr">"message"</span>: <span class="string">"无效签名"</span>,</span><br><span class="line"> <span class="attr">"data"</span>: <span class="literal">null</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>如果 token 过期进行请求,响应结果如下:</p>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"code"</span>: <span class="number">1001</span>,</span><br><span class="line"> <span class="attr">"message"</span>: <span class="string">"登陆凭证已过期"</span>,</span><br><span class="line"> <span class="attr">"data"</span>: <span class="literal">null</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
]]></content>
<categories>
<category>JWT</category>
</categories>
<tags>
<tag>JWT</tag>
<tag>token</tag>
</tags>
</entry>
<entry>
<title>MyBatis-Plus</title>
<url>/2021/04/18/MybatisPlus/</url>
<content><![CDATA[<img src="https://gitee.com/azhenyoo/img/raw/master/Mybatis-Plus/与Mybatis的关系.png" width=80% />
<p>MyBatis-Plus(opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。笔记基于官方文档书写,<a href="https://mp.baomidou.com/guide/">查阅官方文档请点击这里</a> 。<span id="more"></span></p>
<h2 id="特性"><a href="#特性" class="headerlink" title="特性"></a>特性</h2><ul>
<li><strong>无侵入</strong>:只做增强不做改变,引入它不会对现有工程产生影响</li>
<li><strong>损耗小</strong>:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作</li>
<li><strong>强大的 CRUD 操作</strong>:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求</li>
<li><strong>支持 Lambda 形式调用</strong>:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错</li>
<li><strong>支持主键自动生成</strong>:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题</li>
<li><strong>支持 ActiveRecord 模式</strong>:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作</li>
<li><strong>支持自定义全局通用操作</strong>:支持全局通用方法注入( Write once, use anywhere )</li>
<li><strong>内置代码生成器</strong>:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用</li>
<li><strong>内置分页插件</strong>:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询</li>
<li><strong>分页插件支持多种数据库</strong>:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库</li>
<li><strong>内置性能分析插件</strong>:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询</li>
<li><strong>内置全局拦截插件</strong>:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作</li>
</ul>
<h2 id="快速开始"><a href="#快速开始" class="headerlink" title="快速开始"></a>快速开始</h2><h3 id="创建数据库和表"><a href="#创建数据库和表" class="headerlink" title="创建数据库和表"></a>创建数据库和表</h3><p>创建数据库 <code>mybatis-plus</code> ,在其中创建一张 <code>user</code> 表,其表结构如下:</p>
<table>
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>age</th>
<th>email</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>Jone</td>
<td>18</td>
<td><a href="mailto:test1@baomidou.com">test1@baomidou.com</a></td>
</tr>
<tr>
<td>2</td>
<td>Jack</td>
<td>20</td>
<td><a href="mailto:test2@baomidou.com">test2@baomidou.com</a></td>
</tr>
<tr>
<td>3</td>
<td>Tom</td>
<td>28</td>
<td><a href="mailto:test3@baomidou.com">test3@baomidou.com</a></td>
</tr>
<tr>
<td>4</td>
<td>Sandy</td>
<td>21</td>
<td><a href="mailto:test4@baomidou.com">test4@baomidou.com</a></td>
</tr>
<tr>
<td>5</td>
<td>Billie</td>
<td>24</td>
<td><a href="mailto:test5@baomidou.com">test5@baomidou.com</a></td>
</tr>
</tbody></table>
<p>其对应的数据库表结构和数据如下:</p>
<figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- ----------------------------</span></span><br><span class="line"><span class="comment">-- Table structure for user</span></span><br><span class="line"><span class="comment">-- ----------------------------</span></span><br><span class="line"><span class="keyword">DROP</span> <span class="keyword">TABLE</span> IF <span class="keyword">EXISTS</span> <span class="keyword">user</span>;</span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> <span class="keyword">user</span></span><br><span class="line">(</span><br><span class="line"> id <span class="type">BIGINT</span>(<span class="number">20</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span> COMMENT <span class="string">'主键ID'</span>,</span><br><span class="line"> name <span class="type">VARCHAR</span>(<span class="number">30</span>) <span class="keyword">NULL</span> <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">'姓名'</span>,</span><br><span class="line"> age <span class="type">INT</span>(<span class="number">11</span>) <span class="keyword">NULL</span> <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">'年龄'</span>,</span><br><span class="line"> email <span class="type">VARCHAR</span>(<span class="number">50</span>) <span class="keyword">NULL</span> <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">'邮箱'</span>,</span><br><span class="line"> <span class="keyword">PRIMARY</span> KEY (id)</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- ----------------------------</span></span><br><span class="line"><span class="comment">-- Records of user</span></span><br><span class="line"><span class="comment">-- ----------------------------</span></span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> <span class="keyword">user</span> (id, name, age, email) <span class="keyword">VALUES</span></span><br><span class="line">(<span class="number">1</span>, <span class="string">'Jone'</span>, <span class="number">18</span>, <span class="string">'test1@baomidou.com'</span>),</span><br><span class="line">(<span class="number">2</span>, <span class="string">'Jack'</span>, <span class="number">20</span>, <span class="string">'test2@baomidou.com'</span>),</span><br><span class="line">(<span class="number">3</span>, <span class="string">'Tom'</span>, <span class="number">28</span>, <span class="string">'test3@baomidou.com'</span>),</span><br><span class="line">(<span class="number">4</span>, <span class="string">'Sandy'</span>, <span class="number">21</span>, <span class="string">'test4@baomidou.com'</span>),</span><br><span class="line">(<span class="number">5</span>, <span class="string">'Billie'</span>, <span class="number">24</span>, <span class="string">'test5@baomidou.com'</span>);</span><br></pre></td></tr></table></figure>
<blockquote>
<p>注意:在真实项目中,表结构中还应该有如下的字段:version(乐观锁)、deleted(逻辑删除)、gmt_create(创建时间)、gmt_modified(修改时间)</p>
</blockquote>
<h3 id="初始化一个项目"><a href="#初始化一个项目" class="headerlink" title="初始化一个项目"></a>初始化一个项目</h3><p>创建一个空的 Spring Boot 工程,创建时勾选上 <code>Spring Web</code>,创建完成后导入相关依赖,pom 文件如下,</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="meta"><?xml version="1.0" encoding="UTF-8"?></span></span><br><span class="line"><span class="tag"><<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">modelVersion</span>></span>4.0.0<span class="tag"></<span class="name">modelVersion</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">parent</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-parent<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>2.4.5<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">relativePath</span>/></span> <span class="comment"><!-- lookup parent from repository --></span></span><br><span class="line"> <span class="tag"></<span class="name">parent</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>com.azhen<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>mybatis-plus<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>0.0.1-SNAPSHOT<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">name</span>></span>mybatis-plus<span class="tag"></<span class="name">name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">description</span>></span>Mybatis-Plus Demo project for Spring Boot<span class="tag"></<span class="name">description</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">properties</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">java.version</span>></span>1.8<span class="tag"></<span class="name">java.version</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">properties</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependencies</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="comment"><!-- 数据库驱动 --></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>mysql<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>mysql-connector-java<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="comment"><!-- lombok --></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.projectlombok<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>lombok<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="comment"><!-- mybatis-plus --></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>com.baomidou<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>mybatis-plus-boot-starter<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>3.4.2<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-web<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-test<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">scope</span>></span>test<span class="tag"></<span class="name">scope</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependencies</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">build</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">plugins</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">plugin</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-maven-plugin<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">plugin</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">plugins</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">build</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"></<span class="name">project</span>></span></span><br></pre></td></tr></table></figure>
<blockquote>
<p>注意:尽量不要同时导入 mybatis 和 mybatis-plus 的依赖,可能会出现因为版本差异导致的问题!</p>
</blockquote>
<h3 id="编写配置文件"><a href="#编写配置文件" class="headerlink" title="编写配置文件"></a>编写配置文件</h3><p>在 <code>application.yml</code> 配置文件中添加 <code>mysql</code> 数据库的相关配置:</p>
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 数据库链接配置(DataSource Config)</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line"> <span class="attr">datasource:</span></span><br><span class="line"> <span class="attr">username:</span> <span class="string">root</span></span><br><span class="line"> <span class="attr">password:</span> <span class="string">root</span></span><br><span class="line"> <span class="attr">url:</span> <span class="string">jdbc:mysql://127.0.0.1:3306/mybatis-plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC</span></span><br><span class="line"> <span class="attr">driver-class-name:</span> <span class="string">com.mysql.cj.jdbc.Driver</span></span><br><span class="line"><span class="comment"># mysql 5 驱动 com.mysql.jdbc.Driver,但也可使用 8 的驱动 com.mysql.cj.jdbc.Driver,是向下兼容的</span></span><br><span class="line"><span class="comment"># mysql 8 驱动不同,新增时区配置 serverTimezone=UTC</span></span><br></pre></td></tr></table></figure>
<p>如果想要在控制台看到 SQL 语句的执行情况,还需要添加日志的相关配置:</p>
<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 配置日志(Log Config)</span></span><br><span class="line"><span class="attr">mybatis-plus:</span></span><br><span class="line"> <span class="attr">configuration:</span></span><br><span class="line"> <span class="attr">log-impl:</span> <span class="string">org.apache.ibatis.logging.stdout.StdOutImpl</span></span><br></pre></td></tr></table></figure>
<h3 id="编码"><a href="#编码" class="headerlink" title="编码"></a>编码</h3><p>编写实体类 <code>User.java</code>(此处使用了 Lombok 简化代码):</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@AllArgsConstructor</span></span><br><span class="line"><span class="meta">@NoArgsConstructor</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">User</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> Long id;</span><br><span class="line"> <span class="keyword">private</span> String name;</span><br><span class="line"> <span class="keyword">private</span> Integer age;</span><br><span class="line"> <span class="keyword">private</span> String email;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>编写 Mapper 类 <code>UserMapper.java</code> :</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Repository</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UserMapper</span> <span class="keyword">extends</span> <span class="title">BaseMapper</span><<span class="title">User</span>> </span>{</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 继承 BaseMapper 基础类,基础的 CRUD 操作就已经编程完成了</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在 Spring Boot 启动类中添加 <code>@MapperScan</code> 注解,扫描 Mapper 文件夹:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@MapperScan("com.azhen.mybatisplus.mapper")</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MybatisPlusApplication</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> SpringApplication.run(MybatisPlusApplication.class, args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="开始使用"><a href="#开始使用" class="headerlink" title="开始使用"></a>开始使用</h3><p>添加测试类,进行功能测试:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MybatisPlusApplicationTests</span> </span>{</span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> UserMapper userMapper;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">contextLoads</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// UserMapper 中的 selectList() 方法的参数为 MP 内置的条件封装器 Wrapper,所以不填写就是无任何条件</span></span><br><span class="line"> List<User> users = userMapper.selectList(<span class="keyword">null</span>);</span><br><span class="line"> users.forEach(System.out::println);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>控制台输出:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">User(id=1, name=Jone, age=18, email=test1@baomidou.com)</span><br><span class="line">User(id=2, name=Jack, age=20, email=test2@baomidou.com)</span><br><span class="line">User(id=3, name=Tom, age=28, email=test3@baomidou.com)</span><br><span class="line">User(id=4, name=Sandy, age=21, email=test4@baomidou.com)</span><br><span class="line">User(id=5, name=Billie, age=24, email=test5@baomidou.com)</span><br></pre></td></tr></table></figure>
<h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>通过以上几个简单的步骤,我们就实现了 User 表的 CRUD 功能,甚至连 XML 文件都不用编写!</p>
<p>从以上步骤中,我们可以看到集成 <code>MyBatis-Plus</code> 非常的简单,只需要引入 starter 工程,并配置 mapper 扫描路径即可。</p>
<h2 id="主键生成策略"><a href="#主键生成策略" class="headerlink" title="主键生成策略"></a>主键生成策略</h2><p>测试向数据库中插入一条用户记录:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testInsert</span><span class="params">()</span> </span>{</span><br><span class="line"> User user = <span class="keyword">new</span> User();</span><br><span class="line"> <span class="comment">// 链式编程:只需要在 User 实体类上添加 @Accessors(chain = true) 注解即可</span></span><br><span class="line"> user.setName(<span class="string">"azhenYoo"</span>).setAge(<span class="number">20</span>).setEmail(<span class="string">"azhen_study@163.com"</span>);</span><br><span class="line"> <span class="keyword">int</span> insert = userMapper.insert(user);</span><br><span class="line"> System.out.println(insert);</span><br><span class="line"> System.out.println(user);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>控制台输出:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">JDBC Connection [HikariProxyConnection@1679022055 wrapping com.mysql.cj.jdbc.ConnectionImpl@d16be4f] will not be managed by Spring</span><br><span class="line">==> Preparing: INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )</span><br><span class="line">==> Parameters: 1383786141299294209(Long), azhenYoo(String), 20(Integer), azhen_study@163.com(String)</span><br><span class="line"><== Updates: 1</span><br><span class="line"><span class="comment"># 打印影响行数</span></span><br><span class="line">1</span><br><span class="line"><span class="comment"># 打印用户信息</span></span><br><span class="line">User(id=1383786141299294209, name=azhenYoo, age=20, email=azhen_study@163.com)</span><br></pre></td></tr></table></figure>
<p>可以看到,user 实例的 id 字段的值被自动填充了,这就涉及到了主键生成策略。</p>
<p><a href="https://blog.csdn.net/cyl101816/article/details/107002852">分布式系统唯一ID生成方案</a>的详细内容可以点击链接进行查看,下边主要来介绍一下雪花算法:</p>
<h3 id="雪花算法"><a href="#雪花算法" class="headerlink" title="雪花算法"></a>雪花算法</h3><p>snowflake 是 Twitter 开源的分布式 ID 生成算法,结果是一个 long 型的 ID。其核心思想是:使用 41bit 作为毫秒数,10bit 作为机器的 ID(5 个 bit 是数据中心,5 个 bit 的机器 ID),12bit 作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是 0。可以保证几乎全球唯一!</p>
<h3 id="TableId-主键注解"><a href="#TableId-主键注解" class="headerlink" title="@TableId 主键注解"></a>@TableId 主键注解</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Documented</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="meta">@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> TableId {</span><br><span class="line"> <span class="function">String <span class="title">value</span><span class="params">()</span> <span class="keyword">default</span> ""</span>;</span><br><span class="line"> <span class="function">IdType <span class="title">type</span><span class="params">()</span> <span class="keyword">default</span> IdType.NONE</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<table>
<thead>
<tr>
<th align="left">属性</th>
<th align="left">类型</th>
<th align="left">默认值</th>
<th align="left">描述</th>
</tr>
</thead>
<tbody><tr>
<td align="left">value</td>
<td align="left">string</td>
<td align="left">“”</td>
<td align="left">主键字段名</td>
</tr>
<tr>
<td align="left">type</td>
<td align="left">Enum</td>
<td align="left">IdType.NONE</td>
<td align="left">主键类型</td>
</tr>
</tbody></table>
<h3 id="IdType"><a href="#IdType" class="headerlink" title="IdType"></a>IdType</h3><p>通过对 <code>User</code> 类中的 id 属性添加注解 <code>@TableId(type = IdType.xxx)</code> 的方式可以修改 id 的生成类型,IdType 的类型如下:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">enum</span> <span class="title">IdType</span> </span>{</span><br><span class="line"> AUTO(<span class="number">0</span>),</span><br><span class="line"> NONE(<span class="number">1</span>),</span><br><span class="line"> INPUT(<span class="number">2</span>),</span><br><span class="line"> ASSIGN_ID(<span class="number">3</span>),</span><br><span class="line"> ASSIGN_UUID(<span class="number">4</span>),</span><br><span class="line"> <span class="comment">/** <span class="doctag">@deprecated</span> */</span></span><br><span class="line"> <span class="meta">@Deprecated</span></span><br><span class="line"> ID_WORKER(<span class="number">3</span>),</span><br><span class="line"> <span class="comment">/** <span class="doctag">@deprecated</span> */</span></span><br><span class="line"> <span class="meta">@Deprecated</span></span><br><span class="line"> ID_WORKER_STR(<span class="number">3</span>),</span><br><span class="line"> <span class="comment">/** <span class="doctag">@deprecated</span> */</span></span><br><span class="line"> <span class="meta">@Deprecated</span></span><br><span class="line"> UUID(<span class="number">4</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">int</span> key;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="title">IdType</span><span class="params">(<span class="keyword">int</span> key)</span> </span>{ <span class="keyword">this</span>.key = key; }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getKey</span><span class="params">()</span> </span>{ <span class="keyword">return</span> <span class="keyword">this</span>.key; }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ul>
<li><strong>AUTO</strong> 数据库自增。如何才能做到主键自增?① 实体类的主键字段添加注解 @TableId(type = IdType.AUTO);② 数据库的主键字段必须设计成可以自增的</li>
<li><strong>NONE</strong> 无状态 </li>
<li><strong>INPUT</strong> 自行输入</li>
<li><strong>ASSIGN_ID</strong> 分布式全局唯一 ID 长整型类型,和 ID_WORKER 效果一样,ID_WORKER 现已弃用,ID_WORKER_STR 则为 ID_WORKER 的字符串表示,也已弃用</li>
<li><strong>ASSIGN_UUID</strong> 32 位 UUID 字符串,UUID 与 ASSIGN_UUID 效果一样,UUID 现已弃用</li>
</ul>
<h2 id="动态配置-SQL"><a href="#动态配置-SQL" class="headerlink" title="动态配置 SQL"></a>动态配置 SQL</h2><p>测试更新数据库中的用户记录:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testUpdate</span><span class="params">()</span></span>{</span><br><span class="line"> User user = <span class="keyword">new</span> User();</span><br><span class="line"> user.setId(<span class="number">6L</span>).setEmail(<span class="string">"azhen_work@126.com"</span>);</span><br><span class="line"> <span class="keyword">int</span> update = userMapper.updateById(user);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>控制台输出:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">JDBC Connection [HikariProxyConnection@1679022055 wrapping com.mysql.cj.jdbc.ConnectionImpl@d16be4f] will not be managed by Spring</span><br><span class="line">==> Preparing: UPDATE user SET email=? WHERE id=?</span><br><span class="line">==> Parameters: azhen_work@126.com(String), 6(Long)</span><br><span class="line"><== Updates: 1</span><br></pre></td></tr></table></figure>
<p>其中的 SQL 语句是 <code>UPDATE user SET email=? WHERE id=?</code>,尝试继续更新一条记录,代码如下:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testUpdate</span><span class="params">()</span></span>{</span><br><span class="line"> User user = <span class="keyword">new</span> User();</span><br><span class="line"> user.setId(<span class="number">6L</span>).setAge(<span class="number">28</span>).setEmail(<span class="string">"azhen_work@126.com"</span>);</span><br><span class="line"> <span class="keyword">int</span> update = userMapper.updateById(user);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>控制台输出:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">JDBC Connection [HikariProxyConnection@529766927 wrapping com.mysql.cj.jdbc.ConnectionImpl@45658133] will not be managed by Spring</span><br><span class="line">==> Preparing: UPDATE user SET age=?, email=? WHERE id=?</span><br><span class="line">==> Parameters: 28(Integer), azhen_work@126.com(String), 6(Long)</span><br><span class="line"><== Updates: 1</span><br></pre></td></tr></table></figure>
<p>其中的 SQL 语句是 <code>UPDATE user SET age=?, email=? WHERE id=?</code>。可以看出,所有的 sql 都是动态配置的!</p>
<h2 id="自动填充"><a href="#自动填充" class="headerlink" title="自动填充"></a>自动填充</h2><p>创建时间、修改时间的操作一般都是自动化的,我们不希望手动更新,以下是两种级别自动填充的方式。</p>
<h3 id="方式一:数据库级别"><a href="#方式一:数据库级别" class="headerlink" title="方式一:数据库级别"></a>方式一:数据库级别</h3><p>在表中新增字段 create_time 和 update_time 字段,</p>
<figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">`create_time` datetime <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span> COMMENT <span class="string">'创建时间'</span>,</span><br><span class="line">`update_time` datetime <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span> <span class="keyword">ON</span> UPDATE <span class="built_in">CURRENT_TIMESTAMP</span> COMMENT <span class="string">'更新时间'</span>,</span><br></pre></td></tr></table></figure>
<p>同步修改 <code>User</code> 实体类,</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">private</span> Date createTime;</span><br><span class="line"><span class="keyword">private</span> Date updateTime;</span><br></pre></td></tr></table></figure>
<p>测试插入一条记录并对其记录进行修改,数据库中的数据如下:</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/Mybatis-Plus/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%BA%A7%E5%88%AB%E8%87%AA%E5%8A%A8%E5%A1%AB%E5%85%85.png" alt="数据库级别自动填充"></p>
<blockquote>
<p>注意:这种方式在工作中是不建议使用,因为工作中一般是不允许修改数据库的!</p>
</blockquote>
<h3 id="方式二:代码级别"><a href="#方式二:代码级别" class="headerlink" title="方式二:代码级别"></a>方式二:代码级别</h3><p>删除数据库中关于时间字段的默认值、更新操作:</p>
<figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">`create_time` datetime <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">'创建时间'</span>,</span><br><span class="line">`update_time` datetime <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">'更新时间'</span>,</span><br></pre></td></tr></table></figure>
<h4 id="TableField-注解"><a href="#TableField-注解" class="headerlink" title="@TableField 注解"></a><strong>@TableField 注解</strong></h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Documented</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="meta">@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> TableField {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> <span class="function">FieldFill <span class="title">fill</span><span class="params">()</span> <span class="keyword">default</span> FieldFill.DEFAULT</span>; <span class="comment">// 默认为不自动填充</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>其中有一个 <code>fill()</code> 方法用来设置字段自动填充方式,FieldFill 字段自动填充方式有如下几种:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">enum</span> <span class="title">FieldFill</span> </span>{</span><br><span class="line"> DEFAULT, <span class="comment">// 默认,不自动填充</span></span><br><span class="line"> INSERT, <span class="comment">// 插入时</span></span><br><span class="line"> UPDATE, <span class="comment">// 更新时</span></span><br><span class="line"> INSERT_UPDATE; <span class="comment">// 插入和更新时</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="title">FieldFill</span><span class="params">()</span> </span>{</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在 User 实体类上增加 <code>@TabeField</code> 注解,</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@TableField(fill = FieldFill.INSERT)</span></span><br><span class="line"><span class="keyword">private</span> Date createTime;</span><br><span class="line"><span class="meta">@TableField(fill = FieldFill.INSERT_UPDATE)</span></span><br><span class="line"><span class="keyword">private</span> Date updateTime;</span><br></pre></td></tr></table></figure>
<p>编写处理器 <code>MyMetaObjectHandler</code> 来处理这个注解,</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="meta">@Component</span> <span class="comment">// 一定不要忘记将处理器加入到IOC容器中!</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyMetaObjectHandler</span> <span class="keyword">implements</span> <span class="title">MetaObjectHandler</span> </span>{</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 插入时的填充策略</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> metaObject</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">insertFill</span><span class="params">(MetaObject metaObject)</span> </span>{</span><br><span class="line"> <span class="comment">// setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject)</span></span><br><span class="line"> Date date = <span class="keyword">new</span> Date();</span><br><span class="line"> <span class="keyword">this</span>.setFieldValByName(<span class="string">"createTime"</span>, date, metaObject);</span><br><span class="line"> <span class="keyword">this</span>.setFieldValByName(<span class="string">"updateTime"</span>, date, metaObject);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 更新时的填充策略</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> metaObject</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">updateFill</span><span class="params">(MetaObject metaObject)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.setFieldValByName(<span class="string">"updateTime"</span>, <span class="keyword">new</span> Date(), metaObject);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>测试插入和更新,日期字段可以实现自动填充。</p>
<h2 id="乐观锁"><a href="#乐观锁" class="headerlink" title="乐观锁"></a>乐观锁</h2><blockquote>
<p>乐观锁:顾名思义十分乐观,它总是认为不会出现问题,无论干什么不去上锁!如果出现了问题,再次更新值测试<br>悲观锁:顾名思义十分悲观,他总是认为都会出现问题,无论干什么都要上锁!再去操作!</p>
</blockquote>
<p>当要更新一条记录的时候,希望这条记录没有被别人更新。乐观锁实现方式:</p>
<ul>
<li>取出记录时,获取当前 version</li>
<li>更新时,带上这个 version</li>
<li>执行更新时, set version = newVersion where version = oldVersion</li>
<li>如果 version 不对,就更新失败</li>
</ul>
<p>给数据库中的 user 表新增 <code>version</code> 字段,默认值设置为 1,<code>version int(10) DEFAULT '1' COMMENT '乐观锁'</code>;</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/Mybatis-Plus/%E4%B9%90%E8%A7%82%E9%94%81version%E8%AE%BE%E7%BD%AE.png" alt="乐观锁Version设置"></p>
<p>同步 User 实体类,添加 <code>version</code> 字段,并在该字段上加上 <code>@Version</code> 注解</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Version</span></span><br><span class="line"><span class="keyword">private</span> Integer version;</span><br></pre></td></tr></table></figure>
<p>添加 MybatisPlus 配置类,可以将扫描包的注解 <code>@MapperScan</code> 从启动器类转移到此配置类中</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableTransactionManagement</span></span><br><span class="line"><span class="meta">@MapperScan("com.azhen.mybatisplus.mapper")</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MybatisPlusConfig</span> </span>{</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 旧版:注册乐观锁插件</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> OptimisticLockerInterceptor <span class="title">optimisticLockerInterceptor</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> OptimisticLockerInterceptor();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 新版:注册乐观锁插件</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> MybatisPlusInterceptor <span class="title">mybatisPlusInterceptor</span><span class="params">()</span> </span>{</span><br><span class="line"> MybatisPlusInterceptor mybatisPlusInterceptor = <span class="keyword">new</span> MybatisPlusInterceptor();</span><br><span class="line"> mybatisPlusInterceptor.addInnerInterceptor(<span class="keyword">new</span> OptimisticLockerInnerInterceptor());</span><br><span class="line"> <span class="keyword">return</span> mybatisPlusInterceptor;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="测试乐观锁成功案例"><a href="#测试乐观锁成功案例" class="headerlink" title="测试乐观锁成功案例"></a>测试乐观锁成功案例</h3><p>执行如下更新代码,</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">optimisticLocker1</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// 1、查询用户信息</span></span><br><span class="line"> User user = userMapper.selectById(<span class="number">1383954037191245826L</span>);</span><br><span class="line"> <span class="comment">// 2、修改用户信息</span></span><br><span class="line"> user.setAge(<span class="number">58</span>).setEmail(<span class="string">"azhen_study@163.com"</span>);</span><br><span class="line"> <span class="comment">// 3、执行更新操作</span></span><br><span class="line"> <span class="keyword">int</span> update = userMapper.updateById(user);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>控制台输出:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">JDBC Connection [HikariProxyConnection@1573719263 wrapping com.mysql.cj.jdbc.ConnectionImpl@58253c57] will not be managed by Spring</span><br><span class="line">==> Preparing: UPDATE user SET name=?, age=?, email=?, version=?, create_time=?, update_time=? WHERE id=? AND version=?</span><br><span class="line">==> Parameters: azhenYoo(String), 58(Integer), azhen_study@163.com(String), 2(Integer), 2021-04-19 09:22:10.0(Timestamp), 2021-04-19 09:58:35.142(Timestamp), 1383954037191245826(Long), 1(Integer)</span><br><span class="line"><== Updates: 1</span><br></pre></td></tr></table></figure>
<p>可以看到,再进行更新操作成功,进行更新操作时带上了 version,更新后 version 变为 2。</p>
<h3 id="测试乐观锁失败案例"><a href="#测试乐观锁失败案例" class="headerlink" title="测试乐观锁失败案例"></a>测试乐观锁失败案例</h3><p>模拟多线程更新操作同一条记录(<em>只是模拟,并不是真正意义上的多线程</em>),执行如下更新代码,</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">optimisticLocker2</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// 线程1</span></span><br><span class="line"> User user = userMapper.selectById(<span class="number">1383954037191245826L</span>);</span><br><span class="line"> user.setAge(<span class="number">36</span>);</span><br><span class="line"> <span class="comment">// 线程2</span></span><br><span class="line"> User user2 = userMapper.selectById(<span class="number">1383954037191245826L</span>);</span><br><span class="line"> user2.setAge(<span class="number">69</span>);</span><br><span class="line"> <span class="comment">// 线程2抢先执行更新操作</span></span><br><span class="line"> userMapper.updateById(user2);</span><br><span class="line"> <span class="comment">// 线程1的更新操作</span></span><br><span class="line"> userMapper.updateById(user);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>控制台输出:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 线程 2 更新操作</span></span><br><span class="line">JDBC Connection [HikariProxyConnection@1573719263 wrapping com.mysql.cj.jdbc.ConnectionImpl@64c781a9] will not be managed by Spring</span><br><span class="line">==> Preparing: UPDATE user SET name=?, age=?, email=?, version=?, create_time=?, update_time=? WHERE id=? AND version=?</span><br><span class="line">==> Parameters: azhenYoo(String), 69(Integer), azhen_study@163.com(String), 3(Integer), 2021-04-19 09:22:10.0(Timestamp), 2021-04-19 10:08:26.875(Timestamp), 1383954037191245826(Long), 2(Integer)</span><br><span class="line"><== Updates: 1</span><br><span class="line"><span class="comment"># 线程 1 更新操作</span></span><br><span class="line">JDBC Connection [HikariProxyConnection@248705782 wrapping com.mysql.cj.jdbc.ConnectionImpl@64c781a9] will not be managed by Spring</span><br><span class="line">==> Preparing: UPDATE user SET name=?, age=?, email=?, version=?, create_time=?, update_time=? WHERE id=? AND version=?</span><br><span class="line">==> Parameters: azhenYoo(String), 36(Integer), azhen_study@163.com(String), 3(Integer), 2021-04-19 09:22:10.0(Timestamp), 2021-04-19 10:08:26.885(Timestamp), 1383954037191245826(Long), 2(Integer)</span><br><span class="line"><== Updates: 0</span><br></pre></td></tr></table></figure>
<p>可以看到,线程 1 的更新操作失败了!此时数据库中该记录的 version 值为 3。</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/Mybatis-Plus/%E6%B5%8B%E8%AF%95%E4%B9%90%E8%A7%82%E9%94%81%E5%A4%B1%E8%B4%A5%E7%BB%93%E6%9E%9C.png" alt="测试乐观锁失败结果"></p>
<h3 id="说明"><a href="#说明" class="headerlink" title="说明"></a>说明</h3><ul>
<li>支持的数据类型只有:<code>int</code>,<code>Integer</code>,<code>long</code>,<code>Long</code>,<code>Date</code>,<code>Timestamp</code>,<code>LocalDateTime</code></li>
<li>整数类型下 <code>newVersion = oldVersion + 1</code></li>
<li><code>newVersion</code> 会回写到 <code>entity</code> 中</li>
<li>仅支持 <code>updateById(id)</code> 与 <code>update(entity, wrapper)</code> 方法</li>
<li>在 update(entity, wrapper) 方法下,wrapper 不能复用!!!</li>
</ul>
<h2 id="查询操作"><a href="#查询操作" class="headerlink" title="查询操作"></a>查询操作</h2><h3 id="查询单条记录"><a href="#查询单条记录" class="headerlink" title="查询单条记录"></a>查询单条记录</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testSelectOne</span><span class="params">()</span> </span>{</span><br><span class="line"> User user = userMapper.selectById(<span class="number">1383954037191245826L</span>);</span><br><span class="line"> System.out.println(user);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>查询结果:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">User(id=1, name=Jone, age=18, email=test1@baomidou.com, version=1, createTime=Mon Apr 19 17:40:11 CST 2021, updateTime=Mon Apr 19 17:40:11 CST 2021)</span><br></pre></td></tr></table></figure>
<h3 id="批量查询记录"><a href="#批量查询记录" class="headerlink" title="批量查询记录"></a>批量查询记录</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testSelectList</span><span class="params">()</span> </span>{</span><br><span class="line"> List<User> users = userMapper.selectBatchIds(Arrays.asList(<span class="number">1L</span>, <span class="number">2L</span>, <span class="number">3L</span>));</span><br><span class="line"> users.forEach(System.out::println);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>查询结果:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">User(id=1, name=Jone, age=18, email=test1@baomidou.com, version=1, createTime=Mon Apr 19 17:40:11 CST 2021, updateTime=Mon Apr 19 17:40:11 CST 2021)</span><br><span class="line">User(id=2, name=Jack, age=20, email=test2@baomidou.com, version=1, createTime=Mon Apr 19 17:40:11 CST 2021, updateTime=Mon Apr 19 17:40:11 CST 2021)</span><br><span class="line">User(id=3, name=Tom, age=28, email=test3@baomidou.com, version=1, createTime=Mon Apr 19 17:40:11 CST 2021, updateTime=Mon Apr 19 17:40:11 CST 2021)</span><br></pre></td></tr></table></figure>
<h3 id="使用-map-的等值条件查询"><a href="#使用-map-的等值条件查询" class="headerlink" title="使用 map 的等值条件查询"></a>使用 map 的等值条件查询</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testSelectByMap</span><span class="params">()</span> </span>{</span><br><span class="line"> Map<String, Object> map = <span class="keyword">new</span> HashMap<>();</span><br><span class="line"> map.put(<span class="string">"age"</span>, <span class="number">28</span>);</span><br><span class="line"> map.put(<span class="string">"name"</span>, <span class="string">"azhenYoo"</span>);</span><br><span class="line"> List<User> users = userMapper.selectByMap(map);</span><br><span class="line"> users.forEach(System.out::println);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>查询结果:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">User(id=6, name=azhenYoo, age=28, email=azhen_work@126.com, version=1, createTime=Mon Apr 19 17:40:29 CST 2021, updateTime=Mon Apr 19 18:40:35 CST 2021)</span><br><span class="line">User(id=1383946077383720961, name=azhenYoo, age=28, email=azhen_work@126.com, version=1, createTime=Mon Apr 19 16:50:32 CST 2021, updateTime=Mon Apr 19 16:51:15 CST 2021)</span><br></pre></td></tr></table></figure>
<h3 id="分页查询插件"><a href="#分页查询插件" class="headerlink" title="分页查询插件"></a>分页查询插件</h3><p>配置分页拦截器组件,在乐观锁插件配置的组件中中添加分页插件</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> MybatisPlusInterceptor <span class="title">mybatisPlusInterceptor</span><span class="params">()</span> </span>{</span><br><span class="line"> MybatisPlusInterceptor mybatisPlusInterceptor = <span class="keyword">new</span> MybatisPlusInterceptor();</span><br><span class="line"> mybatisPlusInterceptor.addInnerInterceptor(<span class="keyword">new</span> OptimisticLockerInnerInterceptor());</span><br><span class="line"> mybatisPlusInterceptor.addInnerInterceptor(<span class="keyword">new</span> PaginationInnerInterceptor(DbType.MYSQL));</span><br><span class="line"> <span class="keyword">return</span> mybatisPlusInterceptor;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>分页查询编码</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testSelectPage</span><span class="params">()</span> </span>{</span><br><span class="line"> Page<User> userPage = <span class="keyword">new</span> Page<>(<span class="number">1</span>, <span class="number">2</span>);</span><br><span class="line"> Page<User> userPage1 = userMapper.selectPage(userPage, <span class="keyword">null</span>);</span><br><span class="line"> userPage.getRecords().forEach(System.out::println);</span><br><span class="line"> userPage1.getRecords().forEach(System.out::println);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<blockquote>
<p><strong>注意</strong>:userPage 已经是分页查询后的结果了,userPage1 是又经过 queryWrapper 筛选后的结果,这里的 queryWrapper 设置为了 null,因此查询结果 userPage 和 userPage1 的数据是相同。</p>
</blockquote>
<h2 id="条件构造器-Wrapper"><a href="#条件构造器-Wrapper" class="headerlink" title="条件构造器 Wrapper"></a>条件构造器 Wrapper</h2><p>条件构造器 Wrapper 是用于构造 SQL 的搜索条件,使用很便捷,但不支持以及不赞成在 RPC 调用中把 Wrapper 进行传输</p>
<ol>
<li>wrapper 很重</li>
<li>传输 wrapper 可以类比为你的 controller 用 map 接收值(开发一时爽,维护火葬场)</li>
<li>正确的 RPC 调用姿势是写一个 DTO 进行传输,被调用方再根据 DTO 执行相应的操作</li>
</ol>
<p>下面是 Wrapper 的常用函数说明:</p>
<h3 id="allEq"><a href="#allEq" class="headerlink" title="allEq"></a>allEq</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * @param param R 为数据库字段名,V 为字段值</span></span><br><span class="line"><span class="comment"> * @param null2IsNull 为 true 则在 map 的 value 为 null 时调用 isNull 方法,为 false 时则忽略 value 为 null 的</span></span><br><span class="line"><span class="comment"> * @param filter 过滤函数,是否允许字段传入比对条件中</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">allEq(Map<R, V> params)</span><br><span class="line">allEq(Map<R, V> params, <span class="keyword">boolean</span> null2IsNull)</span><br><span class="line">allEq(<span class="keyword">boolean</span> condition, Map<R, V> params, <span class="keyword">boolean</span> null2IsNull)</span><br><span class="line">allEq(BiPredicate<R, V> filter, Map<R, V> params)</span><br><span class="line">allEq(BiPredicate<R, V> filter, Map<R, V> params, <span class="keyword">boolean</span> null2IsNull)</span><br><span class="line">allEq(<span class="keyword">boolean</span> condition, BiPredicate<R, V> filter, Map<R, V> params, <span class="keyword">boolean</span> null2IsNull) </span><br></pre></td></tr></table></figure>
<ul>
<li>例 1:<code>allEq({id:1,name:"老王",age:null})</code>—><code>id = 1 and name = '老王' and age is null</code> </li>
<li>例 2:<code>allEq({id:1,name:"老王",age:null}, false)</code>—><code>id = 1 and name = '老王'</code> </li>
<li>例 3:<code>allEq((k,v) -> k.indexOf("a") >= 0, {id:1,name:"老王",age:null})</code>—><code>name = '老王' and age is null</code> </li>
<li>例 4:<code>allEq((k,v) -> k.indexOf("a") >= 0, {id:1,name:"老王",age:null}, false)</code>—><code>name = '老王'</code> </li>
</ul>
<h3 id="eq"><a href="#eq" class="headerlink" title="eq"></a>eq</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">eq(R column, Object val)</span><br><span class="line">eq(<span class="keyword">boolean</span> condition, R column, Object val)</span><br></pre></td></tr></table></figure>
<ul>
<li>例:``eq(“name”, “老王”)<code>---></code>name = ‘老王’` </li>
</ul>
<h3 id="ne"><a href="#ne" class="headerlink" title="ne"></a>ne</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">ne(R column, Object val)</span><br><span class="line">ne(<span class="keyword">boolean</span> condition, R column, Object val)</span><br></pre></td></tr></table></figure>
<ul>
<li>例:<code>ne("name", "老王")</code>—><code>name <> '老王'</code> </li>
</ul>
<h3 id="gt"><a href="#gt" class="headerlink" title="gt"></a>gt</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">gt(R column, Object val)</span><br><span class="line">gt(<span class="keyword">boolean</span> condition, R column, Object val)</span><br></pre></td></tr></table></figure>
<ul>
<li>例:<code>gt("age", 18)</code>—><code>age > 18</code> </li>
</ul>
<h3 id="ge"><a href="#ge" class="headerlink" title="ge"></a>ge</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">ge(R column, Object val)</span><br><span class="line">ge(<span class="keyword">boolean</span> condition, R column, Object val)</span><br></pre></td></tr></table></figure>
<ul>
<li>例:<code>ge("age", 18)</code>—><code>age >= 18</code> </li>
</ul>
<h3 id="lt"><a href="#lt" class="headerlink" title="lt"></a>lt</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">lt(R column, Object val)</span><br><span class="line">lt(<span class="keyword">boolean</span> condition, R column, Object val)</span><br></pre></td></tr></table></figure>
<ul>
<li>例:<code>lt("age", 18)</code>—><code>age < 18</code> </li>
</ul>
<h3 id="le"><a href="#le" class="headerlink" title="le"></a>le</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">le(R column, Object val)</span><br><span class="line">le(<span class="keyword">boolean</span> condition, R column, Object val)</span><br></pre></td></tr></table></figure>
<ul>
<li>例:<code>le("age", 18)</code>—><code>age <= 18</code> </li>
</ul>
<h3 id="between"><a href="#between" class="headerlink" title="between"></a>between</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">between(R column, Object val1, Object val2)</span><br><span class="line">between(<span class="keyword">boolean</span> condition, R column, Object val1, Object val2)</span><br></pre></td></tr></table></figure>
<ul>
<li>例:<code>between("age", 18, 30)</code>—><code>age between 18 and 30</code> </li>
</ul>
<h3 id="notBetween"><a href="#notBetween" class="headerlink" title="notBetween"></a>notBetween</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">notBetween(R column, Object val1, Object val2)</span><br><span class="line">notBetween(<span class="keyword">boolean</span> condition, R column, Object val1, Object val2)</span><br></pre></td></tr></table></figure>
<ul>
<li>例:<code>notBetween("age", 18, 30)</code>—><code>age not between 18 and 30</code> </li>
</ul>
<h3 id="like"><a href="#like" class="headerlink" title="like"></a>like</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">like(R column, Object val)</span><br><span class="line">like(<span class="keyword">boolean</span> condition, R column, Object val)</span><br></pre></td></tr></table></figure>
<ul>
<li>例:<code>like("name", "王")</code>—><code>name like '%王%'</code> </li>
</ul>
<h3 id="notLike"><a href="#notLike" class="headerlink" title="notLike"></a>notLike</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">notLike(R column, Object val)</span><br><span class="line">notLike(<span class="keyword">boolean</span> condition, R column, Object val)</span><br></pre></td></tr></table></figure>
<ul>
<li>例:<code>notLike("name", "王")</code>—><code>name not like '%王%'</code> </li>
</ul>
<h3 id="likeLeft"><a href="#likeLeft" class="headerlink" title="likeLeft"></a>likeLeft</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">likeLeft(R column, Object val)</span><br><span class="line">likeLeft(<span class="keyword">boolean</span> condition, R column, Object val)</span><br></pre></td></tr></table></figure>
<ul>
<li>例:<code>likeLeft("name", "王")</code>—><code>name like '%王'</code> </li>
</ul>
<h3 id="likeRight"><a href="#likeRight" class="headerlink" title="likeRight"></a>likeRight</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">likeRight(R column, Object val)</span><br><span class="line">likeRight(<span class="keyword">boolean</span> condition, R column, Object val)</span><br></pre></td></tr></table></figure>
<ul>
<li>例:<code>likeRight("name", "王")</code>—><code>name like '王%'</code> </li>
</ul>
<h3 id="isNull"><a href="#isNull" class="headerlink" title="isNull"></a>isNull</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">isNull(R column)</span><br><span class="line">isNull(<span class="keyword">boolean</span> condition, R column)</span><br></pre></td></tr></table></figure>
<ul>
<li>例:<code>isNull("name")</code>—><code>name is null</code> </li>
</ul>
<h3 id="isNotNull"><a href="#isNotNull" class="headerlink" title="isNotNull"></a>isNotNull</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">isNotNull(R column)</span><br><span class="line">isNotNull(<span class="keyword">boolean</span> condition, R column)</span><br></pre></td></tr></table></figure>
<ul>
<li>例:<code>isNotNull("name")</code>—><code>name is not null</code> </li>
</ul>
<h3 id="in"><a href="#in" class="headerlink" title="in"></a>in</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">in(R column, Collection<?> value)</span><br><span class="line">in(<span class="keyword">boolean</span> condition, R column, Collection<?> value)</span><br><span class="line">in(R column, Object... values)</span><br><span class="line">in(<span class="keyword">boolean</span> condition, R column, Object... values)</span><br></pre></td></tr></table></figure>
<ul>
<li>例 1:<code>in("age",{1,2,3})</code>—><code>age in (1,2,3)</code> </li>
<li>例 2:<code>in("age", 1, 2, 3)</code>—><code>age in (1,2,3)</code> </li>
</ul>
<h3 id="notIn"><a href="#notIn" class="headerlink" title="notIn"></a>notIn</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">notIn(R column, Collection<?> value)</span><br><span class="line">notIn(<span class="keyword">boolean</span> condition, R column, Collection<?> value)</span><br><span class="line">notIn(R column, Object... values)</span><br><span class="line">notIn(<span class="keyword">boolean</span> condition, R column, Object... values)</span><br></pre></td></tr></table></figure>
<ul>
<li><p>例 1:<code>notIn("age",{1,2,3})</code>—><code>age not in (1,2,3)</code> </p>
</li>
<li><p>例 2:<code>notIn("age", 1, 2, 3)</code>—><code>age not in (1,2,3)</code></p>
</li>
</ul>
<h3 id="inSql"><a href="#inSql" class="headerlink" title="inSql"></a>inSql</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">inSql(R column, String inValue)</span><br><span class="line">inSql(<span class="keyword">boolean</span> condition, R column, String inValue)</span><br></pre></td></tr></table></figure>
<ul>
<li>例 1:<code>inSql("age", "1,2,3,4,5,6")</code>—><code>age in (1,2,3,4,5,6)</code> </li>
<li>例 2:<code>inSql("id", "select id from table where id < 3")</code>—><code>id in (select id from table where id < 3)</code> </li>
</ul>
<h3 id="notInSql"><a href="#notInSql" class="headerlink" title="notInSql"></a>notInSql</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">notInSql(R column, String inValue)</span><br><span class="line">notInSql(<span class="keyword">boolean</span> condition, R column, String inValue)</span><br></pre></td></tr></table></figure>
<ul>
<li>例 1:``notInSql(“age”, “1,2,3,4,5,6”)<code>---></code>age not in (1,2,3,4,5,6)` </li>
<li>例 2:``notInSql(“id”, “select id from table where id < 3”)<code>---></code>id not in (select id from table where id < 3)` </li>
</ul>
<h3 id="groupBy"><a href="#groupBy" class="headerlink" title="groupBy"></a>groupBy</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">groupBy(R... columns)</span><br><span class="line">groupBy(<span class="keyword">boolean</span> condition, R... columns)</span><br></pre></td></tr></table></figure>
<ul>
<li>例:<code>groupBy("id", "name")</code>—><code>group by id,name</code> </li>
</ul>
<h3 id="orderByAsc"><a href="#orderByAsc" class="headerlink" title="orderByAsc"></a>orderByAsc</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderByAsc(R... columns)</span><br><span class="line">orderByAsc(<span class="keyword">boolean</span> condition, R... columns)</span><br></pre></td></tr></table></figure>
<ul>
<li>例:<code>orderByAsc("id", "name")</code>—><code>order by id ASC,name ASC</code> </li>
</ul>
<h3 id="orderByDesc"><a href="#orderByDesc" class="headerlink" title="orderByDesc"></a>orderByDesc</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderByDesc(R... columns)</span><br><span class="line">orderByDesc(<span class="keyword">boolean</span> condition, R... columns)</span><br></pre></td></tr></table></figure>
<ul>
<li>例:<code>orderByDesc("id", "name")</code>—><code>order by id DESC,name DESC</code> </li>
</ul>
<h3 id="orderBy"><a href="#orderBy" class="headerlink" title="orderBy"></a>orderBy</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderBy(<span class="keyword">boolean</span> condition, <span class="keyword">boolean</span> isAsc, R... columns)</span><br></pre></td></tr></table></figure>
<ul>
<li>例:<code>orderBy(true, true, "id", "name")</code>—><code>order by id ASC,name ASC</code> </li>
</ul>
<h3 id="having"><a href="#having" class="headerlink" title="having"></a>having</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">having(String sqlHaving, Object... params)</span><br><span class="line">having(<span class="keyword">boolean</span> condition, String sqlHaving, Object... params)</span><br></pre></td></tr></table></figure>
<ul>
<li>例 1:<code>having("sum(age) > 10")</code>—><code>having sum(age) > 10</code> </li>
<li>例 2:<code>having("sum(age) > {0}", 11)</code>—><code>having sum(age) > 11</code> </li>
</ul>
<h3 id="func"><a href="#func" class="headerlink" title="func"></a>func</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">func(Consumer<Children> consumer)</span><br><span class="line">func(<span class="keyword">boolean</span> condition, Consumer<Children> consumer)</span><br></pre></td></tr></table></figure>
<ul>
<li>func 方法(主要方便在出现 if…else 下调用不同方法能不断链)</li>
<li>例:<code>func(i -> if(true) {i.eq("id", 1)} else {i.ne("id", 1)})</code> </li>
</ul>
<h3 id="or"><a href="#or" class="headerlink" title="or"></a>or</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">or()</span><br><span class="line">or(<span class="keyword">boolean</span> condition)</span><br></pre></td></tr></table></figure>
<p><strong>拼接 OR</strong> - 主动调用 <code>or</code> 表示紧接着下一个<strong>方法</strong>不是用 <code>and</code> 连接,不调用 <code>or</code> 则默认为使用 <code>and</code> 连接!</p>
<ul>
<li>例:<code>eq("id",1).or().eq("name","老王")</code>—><code>id = 1 or name = '老王'</code> </li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">or(Consumer<Param> consumer)</span><br><span class="line">or(<span class="keyword">boolean</span> condition, Consumer<Param> consumer)</span><br></pre></td></tr></table></figure>
<ul>
<li>例:<code>or(i -> i.eq("name", "李白").ne("status", "活着"))</code>—><code>or (name = '李白' and status <> '活着')</code> </li>
</ul>
<h3 id="and"><a href="#and" class="headerlink" title="and"></a>and</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">and(Consumer<Param> consumer)</span><br><span class="line">and(<span class="keyword">boolean</span> condition, Consumer<Param> consumer)</span><br></pre></td></tr></table></figure>
<ul>
<li>例:<code>and(i -> i.eq("name", "李白").ne("status", "活着"))</code>—><code>and (name = '李白' and status <> '活着')</code> </li>
</ul>
<h3 id="nested"><a href="#nested" class="headerlink" title="nested"></a>nested</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">nested(Consumer<Param> consumer)</span><br><span class="line">nested(<span class="keyword">boolean</span> condition, Consumer<Param> consumer)</span><br></pre></td></tr></table></figure>
<ul>
<li>例:<code>nested(i -> i.eq("name", "李白").ne("status", "活着"))</code>—><code>(name = '李白' and status <> '活着')</code> </li>
</ul>
<h3 id="apply"><a href="#apply" class="headerlink" title="apply"></a>apply</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">apply(String applySql, Object... params)</span><br><span class="line">apply(<span class="keyword">boolean</span> condition, String applySql, Object... params)</span><br></pre></td></tr></table></figure>
<p><strong>拼接 SQL</strong> 该方法可用于数据库<strong>函数</strong>动态入参的 <code>params</code> 对应前面 <code>applySql</code> 内部的 <code>{index}</code> 部分,这样是不会有 SQL 注入风险的,反之会有!</p>
<ul>
<li>例 1:<code>apply("id = 1")</code>—><code>id = 1</code> </li>
<li>例 2:<code>apply("date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")</code>—><code>date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")</code> </li>
<li>例 3:<code>apply("date_format(dateColumn,'%Y-%m-%d') = {0}", "2008-08-08")</code>—><code>date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")</code> </li>
</ul>
<h3 id="last"><a href="#last" class="headerlink" title="last"></a>last</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">last(String lastSql)</span><br><span class="line">last(<span class="keyword">boolean</span> condition, String lastSql)</span><br></pre></td></tr></table></figure>
<p>无视优化规则直接拼接到 SQL 的最后,只能调用一次,多次调用以最后一次为准,有 SQL 注入的风险,请谨慎使用!</p>
<ul>
<li>例:<code>last("limit 1")</code> </li>
</ul>
<h3 id="exists"><a href="#exists" class="headerlink" title="exists"></a>exists</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">exists(String existsSql)</span><br><span class="line">exists(<span class="keyword">boolean</span> condition, String existsSql)</span><br></pre></td></tr></table></figure>
<ul>
<li>例:<code>exists("select id from table where age = 1")</code>—><code>exists (select id from table where age = 1)</code> </li>
</ul>
<h3 id="notExists"><a href="#notExists" class="headerlink" title="notExists"></a>notExists</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">notExists(String notExistsSql)</span><br><span class="line">notExists(<span class="keyword">boolean</span> condition, String notExistsSql)</span><br></pre></td></tr></table></figure>
<ul>
<li>例:<code>notExists("select id from table where age = 1")</code>—><code>not exists (select id from table where age = 1)</code> </li>
</ul>
<h2 id="逻辑删除"><a href="#逻辑删除" class="headerlink" title="逻辑删除"></a>逻辑删除</h2><ul>
<li>物理删除 :从数据库中直接移除</li>
<li>逻辑删除 :数据没有从数据库中移除,而是通过一个变量来让它失效!deleted = 0 -> deleted = 1</li>
</ul>
<blockquote>
<p><strong>场景</strong>:管理员可以查看被删除的记录!防止数据库的丢失,类似于回收站。</p>
</blockquote>
<p>在数据库中的 user 表中新增 <code>deleted</code> 逻辑删除字段,默认值为 0,</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/Mybatis-Plus/%E6%96%B0%E5%A2%9Edeleted%E9%80%BB%E8%BE%91%E5%88%A0%E9%99%A4%E5%AD%97%E6%AE%B5.png" alt="新增deleted逻辑删除字段"></p>
<p>同步 User 实体类,并给 deleted 属性添加 <code>@TableLogic</code> 注解,</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@TableLogic</span></span><br><span class="line"><span class="keyword">private</span> Integer deleted;</span><br></pre></td></tr></table></figure>
<p>在 <code>application.yml</code> 中进行相关配置</p>
<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">mybatis-plus:</span></span><br><span class="line"> <span class="attr">global-config:</span></span><br><span class="line"> <span class="attr">db-config:</span></span><br><span class="line"> <span class="attr">logic-delete-field:</span> <span class="string">deleted</span> <span class="comment"># 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)</span></span><br><span class="line"> <span class="attr">logic-delete-value:</span> <span class="number">1</span> <span class="comment"># 逻辑已删除值(默认为 1)</span></span><br><span class="line"> <span class="attr">logic-not-delete-value:</span> <span class="number">0</span> <span class="comment"># 逻辑未删除值(默认为 0)</span></span><br></pre></td></tr></table></figure>
<p>测试逻辑删除</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testLogicDelete</span><span class="params">()</span> </span>{</span><br><span class="line"> userMapper.deleteById(<span class="number">1L</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>执行代码,控制台输出:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 删除操作</span></span><br><span class="line">JDBC Connection [HikariProxyConnection@1042273835 wrapping com.mysql.cj.jdbc.ConnectionImpl@7112ce6] will not be managed by Spring</span><br><span class="line">==> Preparing: UPDATE user SET deleted=1 WHERE id=? AND deleted=0</span><br><span class="line">==> Parameters: 1(Long)</span><br><span class="line"><== Updates: 1</span><br><span class="line"><span class="comment"># 查询用户</span></span><br><span class="line">JDBC Connection [HikariProxyConnection@1246023616 wrapping com.mysql.cj.jdbc.ConnectionImpl@30ec7d21] will not be managed by Spring</span><br><span class="line">==> Preparing: SELECT id,name,age,email,version,create_time,update_time,deleted FROM user WHERE id=? AND deleted=0</span><br><span class="line">==> Parameters: 1(Long)</span><br><span class="line"><== Total: 0</span><br><span class="line"><span class="comment"># 打印用户信息</span></span><br><span class="line">null</span><br></pre></td></tr></table></figure>
<p>可以看到,虽然是删除操作,但走的是更新操作,并且在查询时会自动添加 <code>deleted=0</code> 的查询条件,无法查询到被逻辑删除的用户,下图为数据库中该用户的数据信息。</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/Mybatis-Plus/%E9%80%BB%E8%BE%91%E5%88%A0%E9%99%A4%E6%B5%8B%E8%AF%95%E7%BB%93%E6%9E%9C.png" alt="逻辑删除测试结果"></p>
<h2 id="代码生成器"><a href="#代码生成器" class="headerlink" title="代码生成器"></a>代码生成器</h2><p>AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。</p>
<h3 id="添加依赖"><a href="#添加依赖" class="headerlink" title="添加依赖"></a>添加依赖</h3><p>MyBatis-Plus 从 <code>3.0.3</code> 之后移除了代码生成器与模板引擎的默认依赖,需要手动添加相关依赖:</p>
<ul>
<li><p>添加代码生成器依赖</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>com.baomidou<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>mybatis-plus-generator<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>3.4.1<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure></li>
<li><p>添加模板引擎依赖,MyBatis-Plus 支持 Velocity(默认)、Freemarker、Beetl,用户可以选择自己熟悉的模板引擎,如果都不满足您的要求,可以采用自定义模板引擎</p>
<p>Velocity(默认):</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.apache.velocity<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>velocity-engine-core<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>2.3<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
<p>Freemarker:</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.freemarker<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>freemarker<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>2.3.31<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
<p>Beetl:</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>com.ibeetl<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>beetl<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>3.3.2.RELEASE<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure></li>
</ul>
<h3 id="编写代码生成器"><a href="#编写代码生成器" class="headerlink" title="编写代码生成器"></a>编写代码生成器</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CodeGenerator</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> <span class="comment">// 构建一个代码生成器对象</span></span><br><span class="line"> AutoGenerator autoGenerator = <span class="keyword">new</span> AutoGenerator();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 1. 全局配置</span></span><br><span class="line"> GlobalConfig globalConfig = <span class="keyword">new</span> GlobalConfig();</span><br><span class="line"> String projectPath = System.getProperty(<span class="string">"user.dir"</span>);</span><br><span class="line"> <span class="comment">// 设置输出路径</span></span><br><span class="line"> globalConfig.setOutputDir(projectPath + <span class="string">"/src/main/java"</span>);</span><br><span class="line"> <span class="comment">// 设置作者</span></span><br><span class="line"> globalConfig.setAuthor(<span class="string">"Huang Yuchen"</span>);</span><br><span class="line"> <span class="comment">// 设置打开资源管理器</span></span><br><span class="line"> globalConfig.setOpen(<span class="keyword">false</span>);</span><br><span class="line"> <span class="comment">// 设置是否覆盖已存在的文件</span></span><br><span class="line"> globalConfig.setFileOverride(<span class="keyword">true</span>);</span><br><span class="line"> <span class="comment">// 设置服务接口名称</span></span><br><span class="line"> globalConfig.setServiceName(<span class="string">"%sService"</span>);</span><br><span class="line"> <span class="comment">// 设置服务接口实现类名称</span></span><br><span class="line"> globalConfig.setServiceImplName(<span class="string">"%sServiceImpl"</span>);</span><br><span class="line"> <span class="comment">// 设置主键生成策略</span></span><br><span class="line"> globalConfig.setIdType(IdType.ASSIGN_ID);</span><br><span class="line"> <span class="comment">// 设置日期类型</span></span><br><span class="line"> globalConfig.setDateType(DateType.ONLY_DATE);</span><br><span class="line"> <span class="comment">// 设置swagger</span></span><br><span class="line"> globalConfig.setSwagger2(<span class="keyword">true</span>);</span><br><span class="line"> <span class="comment">// 将全局配置添加到代码生成器对象中</span></span><br><span class="line"> autoGenerator.setGlobalConfig(globalConfig);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 2. 数据源配置</span></span><br><span class="line"> DataSourceConfig dataSourceConfig = <span class="keyword">new</span> DataSourceConfig();</span><br><span class="line"> dataSourceConfig.setDriverName(<span class="string">"com.mysql.cj.jdbc.Driver"</span>);</span><br><span class="line"> dataSourceConfig.setUrl(<span class="string">"jdbc:mysql://127.0.0.1:3306/mybatis-plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC"</span>);</span><br><span class="line"> dataSourceConfig.setDbType(DbType.MYSQL);</span><br><span class="line"> dataSourceConfig.setUsername(<span class="string">"root"</span>);</span><br><span class="line"> dataSourceConfig.setPassword(<span class="string">"root"</span>);</span><br><span class="line"> <span class="comment">// 将数据源配置添加到代码生成器对象中</span></span><br><span class="line"> autoGenerator.setDataSource(dataSourceConfig);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 3. 包配置</span></span><br><span class="line"> PackageConfig packageConfig = <span class="keyword">new</span> PackageConfig();</span><br><span class="line"> packageConfig.setModuleName(<span class="string">"blog"</span>);</span><br><span class="line"> packageConfig.setParent(<span class="string">"com.azhen.mybatisplus"</span>);</span><br><span class="line"> packageConfig.setEntity(<span class="string">"entity"</span>);</span><br><span class="line"> packageConfig.setMapper(<span class="string">"mapper"</span>);</span><br><span class="line"> packageConfig.setService(<span class="string">"service"</span>);</span><br><span class="line"> packageConfig.setController(<span class="string">"controller"</span>);</span><br><span class="line"> packageConfig.setServiceImpl(<span class="string">"service.impl"</span>);</span><br><span class="line"> <span class="comment">// 将包配置添加到代码生成器对象中</span></span><br><span class="line"> autoGenerator.setPackageInfo(packageConfig);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 4. 策略配置</span></span><br><span class="line"> StrategyConfig strategyConfig = <span class="keyword">new</span> StrategyConfig();</span><br><span class="line"> <span class="comment">// 设置映射的表名</span></span><br><span class="line"> strategyConfig.setInclude(<span class="string">"user"</span>);</span><br><span class="line"> <span class="comment">// 下划线转驼峰命名</span></span><br><span class="line"> strategyConfig.setNaming(NamingStrategy.underline_to_camel);</span><br><span class="line"> strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);</span><br><span class="line"> <span class="comment">// 设置是否开启lombok注解</span></span><br><span class="line"> strategyConfig.setEntityLombokModel(<span class="keyword">true</span>);</span><br><span class="line"> <span class="comment">// 生成实体类字段注解</span></span><br><span class="line"> strategyConfig.setEntityTableFieldAnnotationEnable(<span class="keyword">true</span>);</span><br><span class="line"> <span class="comment">// 设置逻辑删除</span></span><br><span class="line"> strategyConfig.setLogicDeleteFieldName(<span class="string">"deleted"</span>);</span><br><span class="line"> <span class="comment">// 自动填充配置</span></span><br><span class="line"> TableFill createTime = <span class="keyword">new</span> TableFill(<span class="string">"create_time"</span>, FieldFill.INSERT);</span><br><span class="line"> TableFill updateTime = <span class="keyword">new</span> TableFill(<span class="string">"update_time"</span>, FieldFill.INSERT_UPDATE);</span><br><span class="line"> strategyConfig.setTableFillList(Arrays.asList(createTime, updateTime));</span><br><span class="line"> <span class="comment">// 乐观锁配置</span></span><br><span class="line"> strategyConfig.setVersionFieldName(<span class="string">"version"</span>);</span><br><span class="line"> <span class="comment">// 开启RestController风格</span></span><br><span class="line"> strategyConfig.setRestControllerStyle(<span class="keyword">true</span>);</span><br><span class="line"> <span class="comment">// controller映射地址:驼峰转连字符</span></span><br><span class="line"> strategyConfig.setControllerMappingHyphenStyle(<span class="keyword">true</span>);</span><br><span class="line"> <span class="comment">// 将策略配置添加到代码生成器对象中</span></span><br><span class="line"> autoGenerator.setStrategy(strategyConfig);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 执行代码生成器对象</span></span><br><span class="line"> autoGenerator.execute();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>为了代码的方便使用,可以将代码中的一些固定配置抽取出来放入配置文件中,这样以后使用时只需要修改配置文件即可,抽取配置的过程就不详述啦!</p>
]]></content>
<categories>
<category>开发框架</category>
<category>MyBatis-Plus</category>
</categories>
<tags>
<tag>Java</tag>
<tag>MyBatis-Plus</tag>
</tags>
</entry>
<entry>
<title>操作系统</title>
<url>/2021/04/18/Operation%20System/</url>
<content><![CDATA[<p>操作系统(operation system,简称 OS )是管理计算机硬件与软件资源的计算机程序。操作系统需要处理如管理与配置内存、决定系统资源供需的优先次序、控制输入设备与输出设备、操作网络与管理文件系统等基本事务。<span id="more"></span></p>
]]></content>
<categories>
<category>计算机基础</category>
<category>操作系统</category>
</categories>
<tags>
<tag>操作系统</tag>
<tag>OS</tag>
</tags>
</entry>
<entry>
<title>Hello Hexo</title>
<url>/2021/04/16/hexo/</url>
<content><![CDATA[<p>Welcome to <a href="https://hexo.io/">Hexo</a>! This is your very first post. Check <a href="https://hexo.io/docs/">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a href="https://hexo.io/docs/troubleshooting.html">troubleshooting</a> or you can ask me on <a href="https://github.com/hexojs/hexo/issues">GitHub</a>.</p>
<a href="/2021/04/18/MybatisPlus/" title="MyBatis-Plus">MyBatis-Plus</a>
<h2 id="Quick-Start"><a href="#Quick-Start" class="headerlink" title="Quick Start"></a>Quick Start</h2><h3 id="Create-a-new-post"><a href="#Create-a-new-post" class="headerlink" title="Create a new post"></a>Create a new post</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ hexo new <span class="string">"My New Post"</span></span><br></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/writing.html">Writing</a></p>
<h3 id="Run-server"><a href="#Run-server" class="headerlink" title="Run server"></a>Run server</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ hexo server</span><br></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/server.html">Server</a></p>
<h3 id="Generate-static-files"><a href="#Generate-static-files" class="headerlink" title="Generate static files"></a>Generate static files</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ hexo generate</span><br></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/generating.html">Generating</a></p>
<h3 id="Deploy-to-remote-sites"><a href="#Deploy-to-remote-sites" class="headerlink" title="Deploy to remote sites"></a>Deploy to remote sites</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ hexo deploy</span><br></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/one-command-deployment.html">Deployment</a></p>
]]></content>
<categories>
<category>Hexo</category>
</categories>
<tags>
<tag>Hexo</tag>
<tag>博客</tag>
</tags>
</entry>
<entry>
<title>计算机网络</title>
<url>/2020/11/14/Computer%20Network/</url>
<content><![CDATA[<p>计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。<span id="more"></span></p>
<h2 id="一、计算机网络概述"><a href="#一、计算机网络概述" class="headerlink" title="一、计算机网络概述"></a>一、计算机网络概述</h2><h3 id="1-1-计算机网络在当今社会的作用"><a href="#1-1-计算机网络在当今社会的作用" class="headerlink" title="1.1 计算机网络在当今社会的作用"></a>1.1 计算机网络在当今社会的作用</h3><p>在现代社会生产生活中,网络技术实现信息的互通和流动,高速完善的网络能使信息更快捷、准确的传输,发挥强大的作用。网络已成为信息社会的技术命脉和知识经济的发展基础。</p>
<h3 id="1-2-认识网络"><a href="#1-2-认识网络" class="headerlink" title="1.2 认识网络"></a>1.2 认识网络</h3><ul>
<li><strong>网络</strong> 使用集线器或者交换机将计算机连接起来构成一个网络,多个交换机连接的网络也只是一个网络</li>
<li><strong>互联网</strong> 使用路由器将多个网络连接起来形成互联网,即有路由器参与的网络才能称为互联网</li>
<li><strong>Internet/因特网</strong> 全球最大的互联网就是因特网</li>
</ul>
<p>因特网发展的三个阶段:</p>
<ol>
<li><p>单个网路的 ARPANET 向互联网发展 <span style="font-size: 13px">上世纪 60 年代到 80 年代中期</span></p>
<blockquote>
<p><strong>ARPANET</strong> 是 1969 年美国国防部创建的第一个分组交换网,1983 年 TCP/IP 协议成为 ARPANET 上的标准协议</p>
</blockquote>
</li>
<li><p>三级结构的因特网 <span style="font-size: 13px">上世纪 80 年代中期到 90 年代初</span></p>
</li>
<li><p>多层次 ISP 结构的因特网</p>
<blockquote>
<p><strong>ISP</strong> 互联网服务提供商(Internet Service Provider),简称 ISP,指的是面向公众提供下列信息服务的经营者:</p>
<ol>
<li>接入服务,即帮助用户接入 Internet;</li>
<li>导航服务,即帮助用户在 Internet 上找到所需要的信息;</li>
<li>信息服务,即建立数据服务系统,收集、加工、存储信息,定期维护更新,并通过网络向用户提供信息内容服务。</li>
</ol>
</blockquote>
</li>
</ol>
<h3 id="1-3-计算机网络通信"><a href="#1-3-计算机网络通信" class="headerlink" title="1.3 计算机网络通信"></a>1.3 计算机网络通信</h3><h4 id="1-3-1-计算机通信使用的协议"><a href="#1-3-1-计算机通信使用的协议" class="headerlink" title="1.3.1 计算机通信使用的协议"></a>1.3.1 计算机通信使用的协议</h4><p>计算机协议通信使用的协议包含三要素:语法、语义、同步。</p>
<ul>
<li><strong>语法</strong> 定义协议中每种报文的格式,哪些字段,字段是定长还是变长,如果是变长,字段分隔符是什么,都要在协议中定义。一个协议有可能需要多种报文定义,比如 ICMP 协议,定义了 ICMP 请求报文格式、ICMP 响应报文格式、ICMP 差错报告报文格式</li>
<li><strong>语义</strong> 客户端能够向服务端发送哪些请求(方法或命令),服务器有哪些响应(状态代码),每种状态代码代表什么意思</li>
<li><strong>同步</strong> 客户端和服务器命令互换顺序,比如 POP3 协议,需要先验证用户身份才能收邮件</li>
</ul>
<p>客户端程序能够向服务端程序发送哪些请求,也就是客户端能够向服务端发送哪些命令,这些命令发送的顺序,发送的请求有哪些字段分别代表什么意思,都需提前约定好。同理,服务端程序接收到客户端发来的请求,应该有什么相应,什么情况发送什么响应,发送的响应报文有哪些字段,分别代表什么意思,也需要提前约定好。</p>
<p>互联网中有很多常见的应用,很多家公司开发服务端程序,也有很多家公司开发客户端程序。为了使不同厂家开发的服务端程序和客户端程序能够通信,必须将这些应用程序通信协议的协议进行标准化。</p>
<p>生活中也存在种种协议,下图是一个租房协议,</p>
<img src="https://gitee.com/azhenyoo/img/raw/master/计算机网络/租房协议.jpg" alt="租房协议" style="zoom:50%;">
<ul>
<li>协议中包括甲方和乙方,签署协议的目的</li>
<li>协议条款列出了双方关心的事项、甲方乙方的责任、需要做什么、什么时候做、各种意外如何处理</li>
<li>协议最后还要有甲乙双方的签名,声明一式两份,意味着协议的双方对协议中的条款达成一致,都要遵守协议中的约定</li>
</ul>
<p>如果这份租房协议是全世界都公认的,就可以将租房协议简化、标准化后形成如下这张表格,该表格规定了要填写的内容,表格中的内容具体是哪些协议中的条款,甲乙双方都知道,就不用在这里填写了。表格如下:</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E6%8A%BD%E8%B1%A1%E5%8D%8F%E8%AE%AE.png" alt="抽象协议"></p>
<p>类比 IP 协议,其中有很多地方是差不多的。</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/IP%E5%8D%8F%E8%AE%AE.png" alt="IP协议"></p>
<h4 id="1-3-2-网络通信结构划分"><a href="#1-3-2-网络通信结构划分" class="headerlink" title="1.3.2 网络通信结构划分"></a>1.3.2 网络通信结构划分</h4><p>国际标准化组织规定了 OSI 参考模型,将计算机网络通讯结构划分成 7 层,分别为应用层、表示层、会话层、传输层、网络层、数据链路层以及物理层(巧计:从下往上 —— <u>武术网传会适应</u>);而TCP/IP 则将网络通信结构划分为 4 层,将 OSI 参考模型中的应用层、表示层、会话层合并为应用层,数据链路层和物理层合并为网络接口层。</p>
<ul>
<li>OSI 参考模型定义计算机通信每层的功能,不是协议</li>
<li>TCP/IP 协议是具体协议,实现了 OSI 参考模型规定的功能</li>
</ul>
<p>5 层参考模型则是计算机网络原理学习中使用的模型,这样分层可以带来的好处是每一层的更改不会影响到其他层,同时不同网络设备厂商可以生产出标准的网络设备进行网络通信。</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/OSI%E5%8F%82%E8%80%83%E6%A8%A1%E5%9E%8B.png" alt="OSI参考模型"></p>
<h4 id="1-3-3-OSI-参考模型"><a href="#1-3-3-OSI-参考模型" class="headerlink" title="1.3.3 OSI 参考模型"></a>1.3.3 OSI 参考模型</h4><ul>
<li><p><strong>应用层</strong> 根据互联网中需要通信的应用程序功能,定义客户端和服务端程序通信的规范,应用层向表示层发出请求。提供用户接口,特指能过够发起网络通信的应用程序,比如客户端程序,QQ、MSN、浏览器等,服务器程序有 Web 服务器、邮件服务器、流媒体服务器等</p>
</li>
<li><p><strong>表示层</strong> 使用何种编码方式。比如要传输的数据使用 ASCII 编码,Unicode 编码还是二进制文件,是否要加密和压缩。发送端和接收端程序必须使用相同的编码方式,才能正确显示,否则就会产生乱码</p>
</li>
<li><p><strong>会话层</strong> 通信的应用程序之间建立、维护和释放面向用户的连接时,通信的应用程序之间建立会话,需要传输层建立 1 个或多个连接,会话层会定义了如何开始、控制和结束一个会话,包括对多个双向消息的控制和管理,以便在只完成连续消息的一部分时间可以通知应用,从而使表示层看到的数据使连续的</p>
</li>
<li><p><strong>传输层</strong> 负责在通信的两个计算机之间建立连接,实现可靠的或不可靠的数据通信,能够实现发送端和接收端的丢包重传,流量控制,拥塞避免。<code>netstat -n</code> 可以查看计算机中传输层建立的连接,<code>netstat -nb</code> 可以查看建立连接的程序是什么</p>
</li>
<li><p><strong>网络层</strong> 路由器查看数据包目标 IP 地址,根据路由表为数据包选择最佳路径。路由表中的条目可以添加(静态路由)也可以动态生成(动态路由)</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 跟踪网络链路</span></span><br><span class="line"><span class="built_in">PS</span> C:\Users\azhen> tracert www.baidu.com</span><br><span class="line"></span><br><span class="line">通过最多 <span class="number">30</span> 个跃点跟踪</span><br><span class="line">到 www.a.shifen.com [<span class="number">36.152</span><span class="type">.44.96</span>] 的路由:</span><br><span class="line"></span><br><span class="line"> <span class="number">1</span> <span class="number">2</span> ms <span class="number">3</span> ms <span class="number">2</span> ms <span class="number">192.168</span>.<span class="number">0.1</span> [<span class="number">192.168</span><span class="type">.0.1</span>]</span><br><span class="line"> <span class="number">2</span> <span class="number">3</span> ms <span class="number">2</span> ms <span class="number">4</span> ms <span class="number">192.168</span>.<span class="number">1.1</span> [<span class="number">192.168</span><span class="type">.1.1</span>]</span><br><span class="line"> <span class="number">3</span> <span class="number">6</span> ms <span class="number">5</span> ms <span class="number">5</span> ms <span class="number">221.131</span>.<span class="number">196.205</span></span><br><span class="line"> <span class="number">4</span> <span class="number">11</span> ms <span class="number">13</span> ms <span class="number">13</span> ms <span class="number">112.17</span>.<span class="number">202.229</span></span><br><span class="line"> <span class="number">5</span> <span class="number">11</span> ms <span class="number">11</span> ms <span class="number">12</span> ms <span class="number">221.183</span>.<span class="number">77.61</span></span><br><span class="line"> <span class="number">6</span> * <span class="number">17</span> ms <span class="number">17</span> ms <span class="number">221.183</span>.<span class="number">42.129</span></span><br><span class="line"> <span class="number">7</span> <span class="number">17</span> ms <span class="number">18</span> ms <span class="number">17</span> ms <span class="number">221.183</span>.<span class="number">59.54</span></span><br><span class="line"> <span class="number">8</span> * * * 请求超时。</span><br><span class="line"> <span class="number">9</span> <span class="number">19</span> ms <span class="number">19</span> ms <span class="number">17</span> ms <span class="number">182.61</span>.<span class="number">216.72</span></span><br><span class="line"> <span class="number">10</span> * * * 请求超时。</span><br><span class="line"> <span class="number">11</span> <span class="number">19</span> ms <span class="number">16</span> ms <span class="number">29</span> ms <span class="number">36.152</span>.<span class="number">44.96</span></span><br><span class="line"></span><br><span class="line">跟踪完成。</span><br></pre></td></tr></table></figure></li>
<li><p><strong>数据链路层</strong> 不同的网络类型,发送数据的机制不同,数据链路层就是将数据包封装成能够在不同网络传输的帧。能够进行差错检查,但不纠错,检测出错误丢掉该帧</p>
</li>
<li><p><strong>物理层</strong> 该层规定了网络设备接口标准,电压标准。尽可能的通过频分复用、时分复用等技术在通信链路上更快的传输数据</p>
</li>
</ul>
<p>其中,应用层、表示层、会话层以及传输层是在通信主机上完成的功能,而网络层、数据链路层以及物理层是在网络设备上实现的功能。</p>
<h5 id="网络排错"><a href="#网络排错" class="headerlink" title="网络排错"></a>网络排错</h5><p>由于底层网络故障会造成高层网络的故障,因此一般需要从底层向高层逐层排错,</p>
<ul>
<li><strong>物理层故障</strong> 网线连接问题</li>
<li><strong>数据链路层故障</strong> 局域网内 MAC 地址冲突,计算机和换裸设备端口网速不一致,ADSL 欠费,将计算机连接到错误的 VLAN</li>
<li><strong>网络层故障</strong> 网络地址、子网掩码以及网关设置错误,路由器路由表错误</li>
<li><strong>表示层故障</strong> 乱码问题</li>
<li><strong>应用程故障</strong> 应用程序配置错误(比如浏览器指定了一个错误的代理服务器)</li>
</ul>
<h5 id="网络安全"><a href="#网络安全" class="headerlink" title="网络安全"></a>网络安全</h5><ul>
<li><strong>物理层安全</strong> 防止非法计算机接入网络,无线 AP</li>
<li><strong>数据链路层安全</strong> 划分 VLAN,交换机端口安全,ADSL 拨号的账号和密码,无线 AP 的密码机制</li>
<li><strong>网路层安全</strong> 在路由器上可以设置 ACL 控制数据包转发,在计算机上可以设置允许访问 IP 地址以及端口等设置网络安全</li>
<li><strong>应用层安全</strong> 开发出安全的应用程序</li>
</ul>
<h4 id="1-3-4-TCP-IP-体系结构"><a href="#1-3-4-TCP-IP-体系结构" class="headerlink" title="1.3.4 TCP/IP 体系结构"></a>1.3.4 TCP/IP 体系结构</h4><p><img src="https://gitee.com/azhenyoo/img/raw/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84.png" alt="体系结构"></p>
<p>TCP/IP 体系结构分为 4 层,从上至下分别为应用层、传输层、网络层以及网络接口层,下图是 TCP/IP 体系结构中相关的协议以及不同协议之间的关系。</p>
<p>体系结构就是包括哪些协议以及他们之间的关系,如果底层协议能够支持多种上层协议的封装,在底层协议必然要有一个字段用来指明封装的是哪种上层协议。否则接收端就不知道提交给上层的哪个协议。</p>
<p>TCP/IP 协议组中知名的应用层协议:</p>
<ul>
<li>超级文本传输协议 – HTTP,用于访问 Web 服务</li>
<li>安全的超级文本传输协议 – HTTPS,能够讲 HTTP 协议通信进行加密访问</li>
<li>简单邮件传输协议 – SMTP,用于发送电子邮件</li>
<li>邮局协议版本3 – POP3,用于接收电子邮件</li>
<li>域名解析协议 –- DNS,用于域名解析</li>
<li>文件传输协议 – FTP,用于在 Internet 上传和下载文件</li>
<li>简单文本传输协议 – TFTP,在客户机与服务器之间进行简单文本传输的协议</li>
<li>远程登陆 – telnet 协议,用于远程配置网络设备和 Linux 系统</li>
<li>动态主机配置协议 – DHCP,用于计算机自动请求 IP 地址</li>
</ul>
<h4 id="1-3-5-通信过程"><a href="#1-3-5-通信过程" class="headerlink" title="1.3.5 通信过程"></a>1.3.5 通信过程</h4><p><img src="https://gitee.com/azhenyoo/img/raw/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/TCP_IP%E9%80%9A%E8%AE%AF%E8%BF%87%E7%A8%8B.png" alt="TCP_IP通讯过程"></p>
<ul>
<li>数据在不同层的名称:<ul>
<li>应用层 :数据</li>
<li>传输层 :数据段</li>
<li>网络层 :数据包</li>
<li>数据链路层 :数据帧</li>
<li>物理层 :比特流</li>
</ul>
</li>
<li>发送端加上传输层首部、网络层首部、数据链路层首部的过程叫做<strong>封装</strong> </li>
<li>接收端收到后,去掉数据链路层首部、去掉网络层首部、去掉传输层首部的过程叫<strong>解封</strong> </li>
<li>目标 MAC 地址决定了数据帧下一跳由那个设备接收</li>
<li>目标 IP 地址决定了数据包最终到达哪个计算机</li>
<li>不同的网络数据链路层使用不同的协议,帧格式也不相同,路由器在不同网络转发数据包,需要将数据包重新封装</li>
<li>网络设备:<ul>
<li>集线器 :物理层设备,相当于网线</li>
<li>交换机 :数据链路层设备,可以进行数据帧的存储转发</li>
<li>路由器 :网络层设备</li>
</ul>
</li>
</ul>
<h3 id="1-4-计算机网络性能指标"><a href="#1-4-计算机网络性能指标" class="headerlink" title="1.4 计算机网络性能指标"></a>1.4 计算机网络性能指标</h3><h4 id="1-4-1-速率"><a href="#1-4-1-速率" class="headerlink" title="1.4.1 速率"></a>1.4.1 速率</h4><p>速率 :网络技术中的速率指的是每秒钟传输的比特数量,称为数据率或比特率,速率的单位为 b/s 或 bit/s,有时也写为 bps,即 bit per second。</p>
<p>现在人们习惯用更简洁但不严格的说法来描述速率,比如 10M 网速,而省略了单位中的 b/s。</p>
<h4 id="1-4-2-带宽"><a href="#1-4-2-带宽" class="headerlink" title="1.4.2 带宽"></a>1.4.2 带宽</h4><p>在计算机网络中,带宽用来表示网络通信线路传输数据的能力,即最高速率。</p>
<p>目前主流的笔记本电脑网卡能够支持 10M、100M、1000M 三个速率,并且可以指定网卡的带宽,默认是“自动侦探”。这意味着,将笔记本连接到 100M 接口交换机上,会自动协商成 100M 带宽;连接到 1000M 带宽也就是 1G 带宽的交换机,该网卡的带宽就会协商成 1G。</p>
<p>采用 ADSL 拨号上网,有 4M带宽、8M 带宽,这里说的带宽是可以访问 Internet 的最高带宽,但上网的带宽还要由电信运营商来控制。</p>
<h4 id="1-4-3-吞吐量"><a href="#1-4-3-吞吐量" class="headerlink" title="1.4.3 吞吐量"></a>1.4.3 吞吐量</h4><p>吞吐量:表示在单位时间内通过某个网络或接口的数据量,包括全部上传和下载的流量。</p>
<p>吞吐量受网络带宽或网络额定速率的限制,</p>
<ul>
<li>计算机的网卡如果连接交换机,网卡就可以在全双工模式,既能够同时接受和发送数据,如果网卡工作在 100M 全双工模式,就意味着网卡的最大吞吐量为 200Mb/s</li>
<li>计算机的网卡如果连接集线器,网卡就只能工作在半双工模式,即不能同时发送和接收数据,如果网卡工作在 100M 半双工模式,则网卡的最大吞吐量为 100Mb/s</li>
</ul>
<h4 id="1-4-4-时延"><a href="#1-4-4-时延" class="headerlink" title="1.4.4 时延"></a>1.4.4 时延</h4><p>时延:是指数据(一个数据包或 bit)从网络的一端传送到另一端所需的时间,有时也称为延迟或迟延。</p>
<h5 id="(一)发送时延"><a href="#(一)发送时延" class="headerlink" title="(一)发送时延"></a>(一)发送时延</h5><p>发送时延:主机或路由器发送数据帧所需的时间,也就是从发送数据帧的第一个比特开始,到该帧的最后一个比特发送完毕所需的时间。<br>$$<br>发送时延=\frac{数据帧长度(b)}{发送速率(b/s)}<br>$$<br>可以看到发送时延和数据帧的长度和发送速率有关,发送速率就是网卡的带宽,100M 带宽网卡就意味着 1 秒钟能够发送 $100\times 10^6$ 比特。</p>
<h5 id="(二)传播时延"><a href="#(二)传播时延" class="headerlink" title="(二)传播时延"></a>(二)传播时延</h5><p>传播时延:电磁波在信道中传播一定距离需要花费的时间。比如从计算机发送数据到路由器的传播时延,就是计算机发送完最后一比特到路由器接口接收完最后一比特所花费的时间。<br>$$<br>传播时延=\frac{信道长度(m)}{电磁波在信道上的传播速率(m/s)}<br>$$<br>电磁波在自由空间的传播速度是光速,即 $3.0\times 10^5$km/s。电磁波在网络中传播速度比在自由控件略低一些:在铜线电缆中传播速度约为 $2.3\times 10^5$km/s,在光纤中传播速度约为 $2.0\times10^5$km/s。</p>
<p>电磁波在指定介质的传播速率是固定的,从公式可以看出,信道长度固定了,传播时延也就固定了。网卡的不同带宽,改变的只是发送时延,而不是传播时延。</p>
<h5 id="(三)排队时延"><a href="#(三)排队时延" class="headerlink" title="(三)排队时延"></a>(三)排队时延</h5><p>分组在经过网络传输时,要经过许多路由器,但分组在进入路由器后要先在输入队列中排队等待处理。在路由器确定了转发接口后,还要在输出队列中排队等待转发,这就产生了排队时延。</p>
<p>排队时延的长短往往取决于网络当时的通信量,当网络的通信量很大时会发生队列溢出,使分组丢失,这相当于排队时延无穷大。</p>
<h5 id="(四)处理时延"><a href="#(四)处理时延" class="headerlink" title="(四)处理时延"></a>(四)处理时延</h5><p>路由器或主机在收到数据包时,要花费一定时间进行处理,例如分析数据包的首部】进行首部差错检验,查找路由表为数据包选定转发出口,这就产生了处理时延。</p>
<p>数据在网络中经历的总时延就是以上四种时延的总和。<br>$$<br>总时延=发送时延+传播时延+排队时延+处理时延<br>$$</p>
<h4 id="1-4-5-时延带宽积"><a href="#1-4-5-时延带宽积" class="headerlink" title="1.4.5 时延带宽积"></a>1.4.5 时延带宽积</h4><p>时延带宽积:把链路上的传播时延和带宽相乘,这个指标在计算以太网的最短帧非常有帮助。<br>$$<br>时延带宽积=传播时延\times带宽<br>$$<br>这个指标可以用来计算通信线路上有多少比特,例如 A 端到 B 端是 1km 的铜线路,电磁波在铜线中的传播速率为 $2.3\times10^5$km/s,在 1km 长的铜线传播时延大约为 $4.3\times10^{-6}$s,A 端网卡带宽为 10Mb\s,A 端发送数据时,问链路上有多少比特?只需要计算出 $4.3\times10^{-6}$s 的时间内 A 端网卡发送多少比特即可得出链路上有多少比特,这就是时延带宽积。</p>
<p>时延带宽积=$4.3\times10^{-6}$s $\times10\times10^6$b\s=43bit,进一步计算计算出每比特在铜线中的长度是 23m。</p>
<h4 id="1-4-6-往返时间"><a href="#1-4-6-往返时间" class="headerlink" title="1.4.6 往返时间"></a>1.4.6 往返时间</h4><p>在计算机网路中,往返时间 RTT(Round-Trip Time)也是一个重要的性能指标,它表示从发送端发送数据开始,到发送端接收数据到来自接收端的确认(发送端收到后立即发送确认),总共经历的时间,途径的路由器越多距离越长,往返时间也会越长。</p>
<p>通过情况下,企业内网之间计算机 ping 的往返时间小于 10ms,如果大于 10ms,就要安装抓包工具分析网络中的数据包是否有恶意的广播包,已找到发送广播包的计算机。</p>
<h4 id="1-4-7-利用率"><a href="#1-4-7-利用率" class="headerlink" title="1.4.7 利用率"></a>1.4.7 利用率</h4><p>利用率:指网络有百分之几的时间是被利用的(有数据通过),没有数据通过的网络利用率为 0。网络利用率越高,数据分组在路由器和交换机处理时就需要排队等待,因此时延也就越大。<br>$$<br>D=\cfrac{D_0}{1-U}<br>$$<br>其中 $U$ 是网络利用率,$D$ 表示网络当前时延,$D_0$ 表示网络空闲时的时延,下图为网络利用率和时延关系,</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E7%BD%91%E7%BB%9C%E5%88%A9%E7%94%A8%E7%8E%87%E5%92%8C%E6%97%B6%E5%BB%B6%E5%85%B3%E7%B3%BB.png" alt="网络利用率和时延关系"></p>
<p>当网络的利用率接近最大值 1 时,网络的时延就趋于无穷大。因此,一些拥有较大主干网的 ISP 通常控制他们的信道利用率不超过 50%。如果超过了就要准备扩容,以增大线路的带宽。</p>
<h3 id="1-5-网络的分类"><a href="#1-5-网络的分类" class="headerlink" title="1.5 网络的分类"></a>1.5 网络的分类</h3><h4 id="1-5-1-按网络的范围进行分类"><a href="#1-5-1-按网络的范围进行分类" class="headerlink" title="1.5.1 按网络的范围进行分类"></a>1.5.1 按网络的范围进行分类</h4><ul>
<li>局域网(Local Area Network,LAN):是一个在局部地理范围内(如一个学校、工厂和机关内),一般时方圆几千米以内,将各种计算机、外部设备和数据库等互相连接起来组成的计算机通信网。通常是单位自己采购设备组件局域网。</li>
<li>广域网(Wide Area Network,WAN):通常跨接很大的物理范围,所覆盖的范围从几十公里到几千公里,能连接多个城市或国家,或横跨几个洲并能提供远距通信,形成国际性的远程网络。通常情况下需要租用 ISP(Internet 服务提供商,比如电信、移动、联通公司)的线路,每年向 ISP 支付一定的费用购买带宽。</li>
<li>城域网(Metropolitan Area Network,MAN):作用范围一般是一个城市,可以跨几个街区甚至是整个城市,其作用距离约为 5~50km。城域网可以为一个和几个单位所拥有,但可以是一种公用设施,用来将多个局域网互连。目前很多城域网采用的是以太网技术,因此有时也将其并入局域网的范围进行讨论。</li>
<li>个人区域网(Personal Area Network,PAN):就是在个人工作的地方把属于个人使用的电子设备(如便携式电脑等)用无线技术连接起来的网络,因此也常称为无线个人区域网(Wireless PAN,WPAN),比如无线路由器组件的家庭网络,就是一个 PAN,其范围大约在几十米左右。</li>
</ul>
<h4 id="1-5-2-按网络的使用者进行分类"><a href="#1-5-2-按网络的使用者进行分类" class="headerlink" title="1.5.2 按网络的使用者进行分类"></a>1.5.2 按网络的使用者进行分类</h4><ul>
<li>公用网(public network)是指电信公司(国有或私有)出资建造的大型网络。“公用”的意思就是所有愿意按电信公司的规定缴纳费用的人都可以使用这种网络。因此公用网也可以成为公众网,因特网就是全球最大的公用网络。</li>
<li>专用网(private network)是某个部门为本单位的特殊业务工作需要而建造的网络,这种网络不向本单位以外的人提供服务。例如,军队、铁路、电力等系统均有本系统的网络。</li>
</ul>
<h2 id="二、物理层"><a href="#二、物理层" class="headerlink" title="二、物理层"></a>二、物理层</h2><h3 id="2-1-数据通信基础"><a href="#2-1-数据通信基础" class="headerlink" title="2.1 数据通信基础"></a>2.1 数据通信基础</h3><h4 id="2-1-1-通信接口特性"><a href="#2-1-1-通信接口特性" class="headerlink" title="2.1.1 通信接口特性"></a>2.1.1 通信接口特性</h4><p>物理层定义了与传输媒体的接口有关的一些特性。定义了这些接口的标准,各厂家生产的网络设备接口才能相互连接和通信。</p>
<ul>
<li><strong>机械特性</strong> 指明接口所用接线器的形状和尺寸,引脚数目和排列,固定的锁定装置等等,平时常见的各种规格的接插部件都有严格的标准化规定</li>
<li><strong>电器特性</strong> 指明在接口电缆的各条线上出现的电压范围。比如 -10V ~ +10V之间</li>
<li><strong>功能特性</strong> 指明某条线上出现的某一电平的电压表示何种意义</li>
<li><strong>过程特性</strong> 定义了在信号线上进行二进制比特流传输的一组操作过程,包括各信号线的工作顺序和时序,使得比特流传输得以完成</li>
</ul>
<h4 id="2-1-2-数据通信模型"><a href="#2-1-2-数据通信模型" class="headerlink" title="2.1.2 数据通信模型"></a>2.1.2 数据通信模型</h4><h5 id="(一)局域网通信模型"><a href="#(一)局域网通信模型" class="headerlink" title="(一)局域网通信模型"></a>(一)局域网通信模型</h5><p>使用集线器或交换机组建的局域网,计算机 A 和计算机 B 通信,计算机 A 将要传输的信息变成数字信号通过集线器或交换机发送给计算机 B,这个过程不需要对数字信号进行转换。</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E5%B1%80%E5%9F%9F%E7%BD%91%E9%80%9A%E4%BF%A1%E6%A8%A1%E5%9E%8B.png" alt="局域网通信模型"></p>
<h5 id="(二)广域网通信模型"><a href="#(二)广域网通信模型" class="headerlink" title="(二)广域网通信模型"></a>(二)广域网通信模型</h5><p>为了对计算机传输的数字信号进行长距离传输,需要把传输的数字信号转换成模拟信号(计算机通过 ADSL 接入 Internet,其中利用调制解调器进行数字信号和模拟信号的转换)或光信号(计算机通过光纤接入 Internet,其中利用光电转换器进行数字信号和光信号的转换)。</p>
<h4 id="2-1-3-数据通信的常用术语"><a href="#2-1-3-数据通信的常用术语" class="headerlink" title="2.1.3 数据通信的常用术语"></a>2.1.3 数据通信的常用术语</h4><ul>
<li>信息(message):通信的目的是传送信息,如文字、图像、视频和音频等都是信息</li>
<li>数据(data):信息在传输之前需要进行编码,编码后的信息就变成数据</li>
<li>信号(signal):数据在通信线路上传递需要变成电信号或光信号</li>
</ul>
<p> 下面以浏览器访问网站的过程来展现信息、数据和信号之间的关系,下图为信息、数据和信号关系图。</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E4%BF%A1%E6%81%AF%E3%80%81%E6%95%B0%E6%8D%AE%E5%92%8C%E4%BF%A1%E5%8F%B7%E5%85%B3%E7%B3%BB%E4%B8%BE%E4%BE%8B.png" alt="信息、数据和信号关系举例"></p>
<p>网页的内容就是要传送的信息,经过 M 字符集进行编码,变成二进制数据,网卡将数字信号转换成电信号或光信号在网络中传递,接收端网卡接收到电信号或光信号并将其转换成数据,在经过 M 字符集解码,得到信息。</p>
<h4 id="2-1-4-模拟信号和数字信号"><a href="#2-1-4-模拟信号和数字信号" class="headerlink" title="2.1.4 模拟信号和数字信号"></a>2.1.4 模拟信号和数字信号</h4><h5 id="(一)模拟信号-连续信号"><a href="#(一)模拟信号-连续信号" class="headerlink" title="(一)模拟信号/连续信号"></a>(一)模拟信号/连续信号</h5><p>模拟信号:指用连续变化的物理量所表达的信息,如温度、湿度、压力、长度、电流、电压等等,通常又把模拟信号称为连续信号,它在一定时间范围内可以有无限多个不同的取值。</p>
<p>模拟信号在传输过程中如果出现信号干扰,波形会发生形变,很难矫正。</p>
<h5 id="(二)数字信号-离散信号"><a href="#(二)数字信号-离散信号" class="headerlink" title="(二)数字信号/离散信号"></a>(二)数字信号/离散信号</h5><p>离散信号:代表信息的参数的取值是离散的。</p>
<p>在数字通信中常常用时间间隔相同的符号表示一个二进制数字,这样的时间间隔内的信号称为(二进制)<strong>码元</strong>,要求码元有 2 个波形;一个码元也可以表示两位二进制数,两位二进制取值有 00、01、10 和 11 四个取值,这就要求码元有 4 个波形;一个码元也可以表示三位二进制数,三位二进制数取值有 000、001、010、011、100、101、110 和 111 八种取值,这就要求码元有 8 个波形……可以看到,要想让一个码元承载更多信息就需要更多波形,一个波形就是一个码元。</p>
<p>数字信号在传输过程中由于信道本身的特性及噪声干扰会使得数字信号产生失真和信号衰减,为了消除这种波形失真和衰减,每隔一定的距离需要添加“再生中继器”,经过“再生中继器”的波形恢复到发送信号的波形。</p>
<p>模拟信号没办法消除噪声干扰造成的波形失真,所以现在的电视信号逐渐由数字信号替换掉以前的模拟信号。</p>
<h5 id="(三)模拟信号转换成数字信号"><a href="#(三)模拟信号转换成数字信号" class="headerlink" title="(三)模拟信号转换成数字信号"></a>(三)模拟信号转换成数字信号</h5><p>模拟信号和数字信号之间可以相互转换:模拟信号一般通过<strong>脉码调制</strong>方法量化为数字信号。模拟信号经过采样、对采样的值进行量化,对量化的采样进行数字化编码,将编码后的数据转化成数字信号发送。</p>
<p>电脑中的声音文件也是以数字信号的形式存储,需要将声音的模拟信号转换为数据进行存储。使用音乐软件下载音乐时,同一首歌会有超品音质、高品音质和流畅音质,不同品质音乐文件的大小不同,音质也不同。音乐的品质取决于采样频率和采样精度。如果采用 3 位编码,将模拟信号量化为 $2^3=8$ 个量级,数字信号只能近似表示模拟信号;而将模拟信号采用 5 位编码,模拟信号将量化为 $2^5=32$ 个数量级,采样频率提高了,这样数字信号可以更精确地表示模拟信号,编码后会产生更多的二进制数字,这就是为什么超品音质的 MP3 文件要比流畅音质的 MP3 文件更大,播放音质更加接近原声。通常语音信号采用 8 位编码,将模拟信号量化为 $2^8=256$ 个量级。</p>
<h3 id="2-2-信道和调制"><a href="#2-2-信道和调制" class="headerlink" title="2.2 信道和调制"></a>2.2 信道和调制</h3><h4 id="2-2-1-信道"><a href="#2-2-1-信道" class="headerlink" title="2.2.1 信道"></a>2.2.1 信道</h4><p>信道(Channel):信息传输的通道,即信息传输时所经过的一条通路,信道的一端是发送端,另一端是接收端,一条传输介质上可以有多条信道(多路复用)。与信号分类相对应,信道可以分为传输数字信号的数字信道和传输模拟信号的模拟信道。</p>
<p>按照信号传递方向和时间的关系,数据信道可以分为三种类型:单工通信、半双工通信和全双工通信。</p>
<ul>
<li><strong>单工通信</strong> 又称为单向通信,即信号只能向一个方向传播,任何时候都不能改变信号的传送方向。无线电广播或有线电视广播就是单工通信,信号只能是广播站发送,收音机接收</li>
<li><strong>半双工通信</strong> 又称双向交替通信,信号可以双向传送,但必须交替进行,一个时间只能向一个方向传。有时对讲机就是采用半双工通信,A端讲话 B 端听,B 端说话 A 端听,不同同时说和听</li>
<li><strong>全双工通信</strong> 又称双向同时通信,信号可以同时双向传送。比如我们用手机打电话,听和说可以同时进行</li>
</ul>
<h4 id="2-2-2-调制"><a href="#2-2-2-调制" class="headerlink" title="2.2.2 调制"></a>2.2.2 调制</h4><p>基带信号 :来自信源的信号,即基本频带信号。像计算机输出的代表各种文字或图像文件的数据信号都属于基带信号。</p>
<p>基带信号往往包含有较多的<u>低频成分</u>,甚至<u>直流成分</u>,而许多信道不能传输这种低频分量或直流分量,为解决这一问题,必须对基带信号进行调制。</p>
<p>调制可以分为两大类:</p>
<ul>
<li><strong>基带调制</strong> 仅仅对基带信号的波形进行变换,使它能够与信道特性相适应,变化后的信号仍然是基带信号。由于这种调制方式是把数字信号转换成另一种形式的数字信号,因此也可以称这个过程为<strong>编码</strong></li>
<li><strong>带通调制</strong> 使用载波进行调制,把基带信号的频率范围搬移到较高的频段以便在信道中传输,经过载波调制后的信号称为带同信号</li>
</ul>
<h5 id="(一)常见的编码方式"><a href="#(一)常见的编码方式" class="headerlink" title="(一)常见的编码方式"></a>(一)常见的编码方式</h5><ul>
<li><strong>不归零制</strong> :正电平代表 1,负电平代表 0。不归零制是编码效率最高的编码,但如果发送端发送连续的 0 或连续的 1,接收端不容易判断码元的边界</li>
<li><strong>归零制</strong> :码元中间信号回归到零电平,没传输完一位数据,信号返回到零电平,也就是说,信号线上会出现三种电平:正电平、负电平、零电平。因为每位传输之后都要归零,所以接收者只要在信号归零后采样即可,不需要单独的时钟信号,这样的信号也叫做自同步信号。归零制虽然省了时钟数据线,但还是有缺点的,因为在归零制编码中,大部分的数据带宽都用来传输“归零”而浪费掉了,一比特需要三个码元</li>
<li><strong>曼彻斯特编码</strong> :在曼彻斯特编码中,每一位的中间有一个跳变,位中间的跳变既做时钟信号,又做数据信号:从低到高跳变表示 1,从高到低跳变表示0,常用于局域网传输。曼彻斯特编码每位编码中有跳变,不存在直流分量,因此具有自同步能力和良好的抗干扰能力,但每一个码元都被调制成两个电平,所以数据传输速率只有调制速率的 1/2,使用曼彻斯特编码一比特需要两个码元</li>
<li><strong>差分曼彻斯特编码</strong> :在信号位开始时改变信号极性表示 0,在信号位开始时不改变信号极性表示 1。差分曼彻斯特编码比曼彻斯特编码的变化要少,因此更适合于传输高速的信息,被广泛应用于宽带高速网中。然而,由于每个时钟位都必须有一次变化,所以两种编码的效率仅可以达到 50% 左右,使用差分曼彻斯特编码一比特也需要两个码元</li>
</ul>
<p>下图为基带信号经过不同编码方式后产生的信号图,</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E5%9F%BA%E5%B8%A6%E4%BF%A1%E5%8F%B7%E7%BC%96%E7%A0%81.png" alt="基带信号编码"></p>
<h5 id="(二)常用带通调制方式"><a href="#(二)常用带通调制方式" class="headerlink" title="(二)常用带通调制方式"></a>(二)常用带通调制方式</h5><ul>
<li>调幅(AM):载波的振幅随基带数字信号而变化。例如,0 和 1分别对应于无载波或有载波输出</li>
<li>调频(FM):载波的频率随基带数字信号而变化。例如,0 和 1 分别对应于频率 f1 或 f2</li>
<li>调相(PM):载波的初始相位随基带数字信号而变化。例如,0 和 1 分别对应于相位 0 度或 180 度</li>
</ul>
<h4 id="2-2-3-信道的极限容量"><a href="#2-2-3-信道的极限容量" class="headerlink" title="2.2.3 信道的极限容量"></a>2.2.3 信道的极限容量</h4><p>影响信道上的数字信息传输速率的因素有两个:码元的传输速率和每个码元承载的比特信息量。码元的传输速率受信道能够通过的频率范围影响,每个码元承载的比特信息量则受信道信噪比的影响。</p>
<h5 id="(一)信道能够通过的频率范围"><a href="#(一)信道能够通过的频率范围" class="headerlink" title="(一)信道能够通过的频率范围"></a>(一)信道能够通过的频率范围</h5><p>在信道上传输的数字信号其实是使用多个频率的模拟信号进行多次谐波而成的方波。基波信号和更高频率的谐波叠加形成接近数字信号的波形,经过多次更高频率的波进行谐波,可以形成接近数字信号的波形。</p>
<p>具体的信道所能通过的模拟信号的频率范围是有限的,能够通过的最高频率减去最低频率,就是该信道的带宽。假定一条电话线允许频率范围从 300~3300Hz 的模拟信号能够通过,低于 300Hz 或高于 3300Hz 的模拟信号均不能通过,则该电话线的带宽为 3300-300=3000Hz。</p>
<p><strong>码间串扰</strong> 数字信号经过信道,数字信号中的高频分量(高频模拟信号)有可能不能通过信道或者衰减,接收端收到的波形前沿和后沿就变得不那么陡峭,码元之间所占用的时间界限也不在明显,而是前后都拖了“尾巴”。这样,在接收端收到的信号波形就失去了码元之间清晰的界限,这种现象叫做“码间串扰”,严重的码间串扰将使得本来分得很清楚的一串码元变得模糊而无法识别。</p>
<p>早在 1942 年,奈奎斯特就推导出了<strong>奈氏准则</strong>,他给出了在假定的理想条件下,为了避免码间串扰,码元的传输速率的上限值。<strong>在任何信道中,码元的传输速率是有上限的,否则就会出现码间串扰的问题,使接收端对码元的判决(即识别)成为不可能</strong>。如果信道的频带越宽,也就是能够通过的信号高频分量越多,那么就可以使用更高速率传递码元而不出现码间串扰。<br>$$<br>理想低通信道的最高码元传输速率=2W(Baud)<br>$$<br>$W$ 是理想低通信道的带宽,单位为 Hz。$Baud$ 是波特,是码元传输速率的单位。使用奈氏准则给出的公式,可以根据信道的带宽,计算出码元的最高传输速率。</p>
<h5 id="(二)信噪比"><a href="#(二)信噪比" class="headerlink" title="(二)信噪比"></a>(二)信噪比</h5><p>既然码元的传输速率有上限,如果打算让信道更快地传输信息,就需要让一个码元承载更多比特信息量。有二进制码元,一个码元代表一比特;八进制码元,一码元代表三比特;十六进制码元,一码元代表四比特。要是可以无限提高一元码元携带的信息量,信道传输数据的速率岂不是可以无限提高?其实信道传输信息的能力也是有上限的。</p>
<p>噪声存在于任何电子设备和通信信道中,由于噪声是随机产生的,它的瞬时值有时会很大。在电压范围一定的情况下,十六进制码元波形之间的差别要比八进制码元波形之间的差别小。在真实信道传输由于噪声干扰,码元波形差别太小的在接收端就不易清晰识别。</p>
<p>噪声的影响是相对的,如果信号相对较强,那么噪声的影响就相对较小。因此信噪比很重要,所谓信噪比就是信号的平均功率和噪声的平均功率之比,常记为 S/N。由于在实际使用中 S 与 N 的比值太大,故常取其分贝数(dB)作为度量单位,分贝与信噪比的关系为:<br>$$<br>分贝=10\log_{10}(S/N)(dB)<br>$$<br>在 1948 年,信息论的创始人香农推导出了著名的<strong>香农公式</strong>,信道的极限信息传输速率 $C$ 是<br>$$<br>C=W\log_{2}(1+S/N)(b/s)<br>$$<br>式中,$W$ 为信道的带宽(以 Hz 为单位);$S$ 为信道内所传信号的平均功率;$N$ 为信道内部的高斯噪声功率。香农公式表明,信道的带宽或信道中的噪声比越大,信息的极限传输速率就越高,信息传输速率是有上限的。<strong>香农公式的意义在于:只要信息传输速率低于信道的极限信息传输速率,就一定可以找到某种办法来实现无差别传输</strong>。</p>
<h3 id="2-3-传输媒体"><a href="#2-3-传输媒体" class="headerlink" title="2.3 传输媒体"></a>2.3 传输媒体</h3><p>传输媒体也成为传输介质或传输媒介,它就是数据传输系统中在发射器和接收器之间的物理通路。传输媒体可以分为两大类,即<strong>导向传输媒体</strong>和<strong>非导向传输媒体</strong>。</p>
<h4 id="2-3-1-导向传输媒体"><a href="#2-3-1-导向传输媒体" class="headerlink" title="2.3.1 导向传输媒体"></a>2.3.1 导向传输媒体</h4><h5 id="(一)双绞线"><a href="#(一)双绞线" class="headerlink" title="(一)双绞线"></a>(一)双绞线</h5><p>双绞线也成为双扭线,把两根互相绝缘的铜导线并排放在一起,然后用规则的方法绞合起来就构成了双绞线。用这种方式,<strong>不仅可以抵御一部分来自外界的电磁波干扰,也可以降低多对双绞线之间的干扰</strong>。</p>
<p>使用双绞线最多的地方就是电话系统,几乎所有的电话都是用双绞线连接到电话交换机,这段从用户电话机到交换机的双绞线称为用户线或用户环路。</p>
<p>模拟传输和数字传输都可以使用双绞线,其通信范围一般为几到十几公里,距离太长时就要加<strong>放大器</strong>以便将衰减了的信号放大到合适的数值(对于模拟传输),或者加上<strong>中继器</strong>以便将失真的数字信号进行整形(对于数字传输)。导线越粗,其通信距离就越远,导线的价格也越高。</p>
<p>现代计算机连接交换机使用的网线就是双绞线,其中有八根线,网线两头连接 RJ-45 连接头(俗称水晶头)。对于传输信号来说它们的作用分别是:1、2 用于发送,3、6 用于接收,4、5 和 7、8 时双向线;对与其连接的双绞线来说,为了减低相互干扰,标准要求 1、2 必须是绞缠的一对线,3、6 也必须是绞缠的一对线,4、5 相互交缠,7、8 相互绞缠。</p>
<p>八根线的接法标准分别为 TIA/EIA 568B 和TIA/EIA 568A,</p>
<ul>
<li><strong>TIA/EIA 568B</strong> :1、橙白,2、橙,3、绿白,4、蓝,5、蓝白,6、绿,7、棕白,8、棕</li>
<li><strong>TIA/EIA 568A</strong> :1、绿白,2、绿,3、橙白,4、蓝,5、蓝白,6、橙,7、棕白,8、棕</li>
</ul>
<p>网线的水晶头两端的线序如果都是 T568B 就称为直通线;如果网线一端的线序是 T568B,另一端是 T568A 就称为交叉线。</p>
<h5 id="(二)同轴电缆"><a href="#(二)同轴电缆" class="headerlink" title="(二)同轴电缆"></a>(二)同轴电缆</h5><p>同轴电缆由内导体铜质芯线(单股实心线或多股绞合线)、绝缘层、网状编制的外导体屏蔽层(也可以是单股的)以及保护塑料外层所组成。由于外导体屏蔽层的作用,同轴电缆具有很好的抗干扰特性,被广泛用于传输较高速率的数据,目前高质量的同轴电缆的带宽已接近 1GHz。</p>
<h5 id="(三)光缆"><a href="#(三)光缆" class="headerlink" title="(三)光缆"></a>(三)光缆</h5><p>光纤通信就是利用光导纤维(也就是光纤)传递光脉冲来进行通信。有光脉冲相当于 1,而没有光脉冲相当于0。由于可见光的频率非常高,约为 $10^8$MHz 的量级,因此一个光纤通信系统的传输带宽远远大于目前其他各种传输媒体的带宽。</p>
<p>光纤不仅具有通信容量非常大的特点,而且还具有其他的一些特性:</p>
<ul>
<li>传输损耗小,中继距离长,对远距离传输特别经济</li>
<li>康雷电和电磁干扰性能好,这在有大电流脉冲干扰的环境下尤为重要</li>
<li>无串音干扰,保密性好,也不容易被窃听和截取数据</li>
<li>体积小,重量轻</li>
</ul>
<h4 id="2-3-2-非导向传输媒体"><a href="#2-3-2-非导向传输媒体" class="headerlink" title="2.3.2 非导向传输媒体"></a>2.3.2 非导向传输媒体</h4><h5 id="(一)短波通信"><a href="#(一)短波通信" class="headerlink" title="(一)短波通信"></a>(一)短波通信</h5><p>无线传输可使用的频段很广,现在已经利用了好几个波段进行通信,紫外线和更高的波段目前还不能用于通信,下面列出了无线电波频段名称和频段范围。</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E6%97%A0%E7%BA%BF%E7%94%B5%E6%B3%A2%E9%A2%91%E6%AE%B5.png" alt="无线电波频段"></p>
<p>无线电广播、电视常用的无线电波的波段是:国内一般中波广播的波段大致为 550-1605kHz;短波广播的波段为 2-24MHz;调频广播的波段为 87-108MHz。</p>
<p>短波通信即高频通信,主要是靠电离层反射,电离层越高或电波进入电离层时于电离层的夹角越小,电波从入射点经电离层反射到地面的跨越距离越大,这就是短波可以进行远程通信的根本原因。而且电波返回地面时又可能被大地反射再次进入电离层,形成电离层的第二次、第三次反射,由于电离层对电波的反射作用,这就使本来直线传播的电波有可能到达地球的背面或者任何一个地方。电波经电离层一次反射称为“单跳”,单跳的跨越距离取决于电离层的高度。</p>
<p>但电离层的不稳定所产生的衰减现象和电离层反射所产生的多径效应使得短波通信的通信质量较差。</p>
<h5 id="(二)微波通信"><a href="#(二)微波通信" class="headerlink" title="(二)微波通信"></a>(二)微波通信</h5><p>微波通信在数据通信中占重要地位,微波的频率范围为 300MHz~300GHz(波长 1m~10cm),但主要是使用 2~40GHz 的频率范围。</p>
<p>微波在空间中主要是直线传播,由于微波会穿透电离层进入宇宙空间,因此它不像短波那样可以经电离层反射传播到地面上很远的地方。传统的微波通信主要有两种方式:<strong>地面微波接力通信</strong>和<strong>卫星通信</strong>。</p>
<p>微波接力通信为了实现远距离通信,必须在一条无线电通信信道的两个终端之间建立若干个中继站,中继站把前一站从来的信号经过放大后再放松到下一站,故称为“接力”。</p>
<p>微波接力通信可以传输电话、电报、图像、数据等信息,其主要特点是:</p>
<ul>
<li>微波波段频率很高,其频段范围也很宽,因此其通信信道的容量很大</li>
<li>因为工业干扰和天电干扰的主要频谱成分比微波频率低得多,因此对微波通信的危害比短波和米波通信小得多,因此微波传输质量较高</li>
<li>与相同容量和长度的电缆载波通信相比,微波接力通信建设投资少、见效快,易于跨越山区、江河</li>
</ul>
<p>微波接力也存在如下一些缺点:</p>
<ul>
<li>相邻站之间必须直视,不能有障碍物。有时一个天线发射出的信号也会分成几条略有差别的路径到达接收天线,因此造成失真</li>
<li>微波的传播有时会受到恶劣天气的影响</li>
<li>与电缆通信系统比较,微波通信的隐蔽性和保密性较差</li>
<li> 对大量中继站的使用和维护要耗费较多的人力和物力</li>
</ul>
<p>卫星通信是在地球站之间利用位于约 36000km 高空的人造地球同步卫星作为中继器的一种微波接力通信。卫星通信的最大特点就是通信距离远,且通信费用于通信距离无关。</p>
<p>卫星通信的主要优缺点大体上和地面微波通信差不多,卫星通信具有较大的传播时延,但这不等同于用卫星传送数据的时延较大,这是因为传送数据的总时延除了传播时延外,还有发送时延,处理时延和排队时延等部分。</p>
<h5 id="(三)无线局域网"><a href="#(三)无线局域网" class="headerlink" title="(三)无线局域网"></a>(三)无线局域网</h5><p>要使用某一段无线电频谱进行通信,通常必须得到本国政府有关无线电频谱管理机构的许可证,但是也有一些无线电频段是可以自由使用的(只要不干扰他人在这个频段中的通信),这正好满足计算机无线局域网的需求。</p>
<p>现在的无线局域网就使用 ISM 频段中的 2.4GHz 和 5.8GHz 频段。</p>
<h3 id="2-4-信道复用技术"><a href="#2-4-信道复用技术" class="headerlink" title="2.4 信道复用技术"></a>2.4 信道复用技术</h3><p>信道复用技术有频分复用、时分复用波分复用和码分复用。</p>
<h4 id="2-4-1-频分复用"><a href="#2-4-1-频分复用" class="headerlink" title="2.4.1 频分复用"></a>2.4.1 频分复用</h4><p><strong>频分复用</strong> (Frequency Division Multiplexing,FDM):最简单,适合模拟信号,用户分配到一定的频带后,在通信过程中自始自终都占用这个频带,可见频分复用的所有用户在同样的时间占用不同的带宽资源。</p>
<p>例如,$A_1→A2$ 信道使用频率 $f_1$ 调制载波,$B_1→B_2$ 信道使用频率 $f_2$ 调制载波,$C_1→C_2$ 使用频率 $f_3$ 调制载波,不同频率调制后的载波通过复用器将信号叠加后发送到信道;接收端的分用器将信号发送到三个滤波器,滤波器过滤出特定频率载波信号,在经过调解得到信源发送的模拟信号。</p>
<h4 id="2-4-2-时分复用"><a href="#2-4-2-时分复用" class="headerlink" title="2.4.2 时分复用"></a>2.4.2 时分复用</h4><p>数字信号的传输更多使用<strong>时分复用</strong>(Time Division Multiplexing,TDM)技术。时分服用采用同一物理连接的不同时段来传输不同的信号,将时间划分为一段段等长的时分复用帧(TDM 帧)。每一个时分复用的用户在每一个 TDM 帧中占用固定序号的资源。</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E6%97%B6%E5%88%86%E5%A4%8D%E7%94%A8.jpg" alt="时分复用"></p>
<p>可以看出,当某用户暂时无数据发送时,在时分复用帧中分配给该用户的时隙只能处于空闲状态,其他用户即使一直有数据要发送,也不能使用这些空闲的时隙,导致复用后的信道利用率不高。</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E6%97%B6%E5%88%86%E5%A4%8D%E7%94%A8%E6%9C%89%E6%B5%AA%E8%B4%B9.png" alt="时分复用有浪费"></p>
<p>统计时分复用(StatisticTDM,STDM)是一种改进的时分复用,它能明显地提高信道的利用效率。一个使用统计时分复用的集中器连接 4 个低速用户,然后将它们的数据集中起来通过高速线路发送到另一端。统计时分复用要求每个用户的数据需要添加地址信息或信道信息,接收端根据地址或信道信息分离出各个信道的数据。</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E7%BB%9F%E8%AE%A1%E6%97%B6%E5%88%86%E5%A4%8D%E7%94%A8.png" alt="统计时分复用"></p>
<h4 id="2-4-3-波分复用"><a href="#2-4-3-波分复用" class="headerlink" title="2.4.3 波分复用"></a>2.4.3 波分复用</h4><p><strong>波分复用</strong>(Wavelength Division Multiplexing,WDM)是将两种或多种不同波长的光载波信号(携带各种信息)在发送端经复用器(亦称合波器)汇合在一起,并耦合到光路的同一根光纤进行传输;在接收端,经复用器(亦称分波器或去复用器)将各种波长的光载波分离,然后由光接收机做进一步处理恢复原信号。这种在同一根光纤中同时传输两个或多个波长信号的技术,称为波分复用。</p>
<h4 id="2-4-4-码分复用"><a href="#2-4-4-码分复用" class="headerlink" title="2.4.4 码分复用"></a>2.4.4 码分复用</h4><p><strong>码分复用</strong>(Code Division Multiplexing,CDM)又称码分多址,是在扩频通信技术(数字技术的分支)基础上发展起来的的一种崭新而成熟的无线通信技术。CDM 于 FDM 和 TDM 不同,<strong>它既共享信道的频率,也共享时间</strong>,是一种真正的动态复用技术。</p>
<p>其原理是每比特时间被分成 m 个更短的时间片,称为码片,通常情况下,每比特有 64 或 128 个码片。每个站点被指定一个唯一的 m 位的代码(码片序列),当发送 1 时站点就发送码片序列,当发送 0 时就发送码片序列的反码。当两个或多个站点同时发送时,各路数据在信道中被线性相加。</p>
<p>为了从信道中分离出各路信号,要求<strong>每个站点分配的码片序列不仅必须各不相同,并且各个站点的码片序列要相互正交</strong>。假设向量 A 表示站 A 的码片向量,向量 B 表示其他任何站点的码片向量,则有:<br>$$<br>A\cdot B=\cfrac{1}{m}\sum^{m}_{i=1}A_iB_i=0<br>$$<br>任何一个码片向量和该码片向量自己的格式化内积都是1,一个码片向量和该码片反码向量的格式化内积是 -1。</p>
<blockquote>
<p><strong>题</strong> 假如基站发送了码片序列(0 0 -2 +2 0 -2 0 +2),A 手机的码片序列为(-1 -1 -1 +1 +1 -1 +1 +1),B 手机的码片序列为(-1 -1 +1 -1 +1 +1 +1 -1),C 手机的码片序列为(-1 +1 -1 +1 +1 +1 -1 -1),问这三个手机分别收到了什么信号?</p>
</blockquote>
<p><strong>解</strong> A、B、C 三个手机的码片序列和收到的码片序列做格式化内积,如果得数是 +1,说明收到的数字信号是 1;如果得数是 -1,说明收到的数字信号是 0;如果得数是 0,说明该手机没有收到信号,设基站的码片序列为向量 M,A、B、C 手机的码片序列分别为向量 A、B、C,则有,</p>
<p>$M\cdot A= \cfrac{1}{8}[0\times(-1)+0\times(-1)+(-2)\times(-1)+2\times1+0\times1+(-2)\times(-1)+0\times1+2\times1]=1 $,A 手机收到一位数字信号 1;</p>
<p>$M\cdot B=\cfrac{1}{8}[0\times(-1)+0\times(-1)+(-2)\times1+2\times(-1)+0\times1+(-2)\times1+0\times1+2\times(-1)]=-1$,B 手机收到一位数字信号 0;</p>
<p>$M\cdot C=\cfrac{1}{8}[0\times(-1)+0\times1+(-2)\times(-1)+2\times1+0\times1+(-2)\times1+0\times(-1)+2\times(-1)]=0$,C 手机没有收到数字信号。</p>
<h3 id="2-5-宽带接入技术"><a href="#2-5-宽带接入技术" class="headerlink" title="2.5 宽带接入技术"></a>2.5 宽带接入技术</h3><p>宽带接入技术主要包括:铜线宽带接入(电话线),HFC 技术(有线电视线路),光纤接入技术和移动互联网接入技术(3G 或 4G 技术)。</p>
<h4 id="2-5-1-铜线接入技术"><a href="#2-5-1-铜线接入技术" class="headerlink" title="2.5.1 铜线接入技术"></a>2.5.1 铜线接入技术</h4><p>传统的铜线接入技术即通过调制解调器拨号实现用户的接入,速率为 56kb/s,但是这种速率远远不能满足用户对宽带业务的需求。</p>
<p>铜线宽带接入技术也就是 xDSL 技术,xDSL 是数字用户线路的总称,包括 ADSL、RADSL、VDSL,SDSL、IDSL 和 HDSL 等,就是用数字技术对现有的模拟电话用户线进行改造,使它能够承载宽带业务。xDSL 技术把 0~4kHz 低端频谱留给传统电话使用,而把原来没有被利用的高频频谱留给用户上网使用。</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/xDSL%E7%AE%80%E4%BB%8B.png" alt="xDSL简介"></p>
<p>ADSL(Asymmetric Digital Subscriber Line,非对称数字用户线路,亦可称作非对称数字用户环路)属于 xDSL 技术中的一种,ADSL 考虑了访问 Internet 主要是获取网络资源,需要更多下载流量,较少的上行流量,因此 ADSL 上行和下行带宽设计为不对称的,上行指从用户到 ISP,而下行指从 ISP 到用户。</p>
<p>ASDL 在用户线的两端各安装一个 ADSL 调制解调器,我国目前采用的调制解调器的实现方案是离散多音调技术(DMT),多音调指“多载波”或“多子信道”。DMT 调制技术采用频分复用的方法,把 40kHz 以上一直到 1.1MHz 的高频频谱划分为许多子信道,其中 25 个子信道用于上行信道,而 249 个子信道用于下行信道,每个子信道占据 4kHz(严格将是 4.3kHz),并使用不同的载波(即不同的音调)进行数字调制。</p>
<h4 id="2-5-2-HFC-技术"><a href="#2-5-2-HFC-技术" class="headerlink" title="2.5.2 HFC 技术"></a>2.5.2 HFC 技术</h4><p>HFC 是 Hybrid Fiber Coax 的缩写,即光纤同轴 HFC 网(混合网)。HFC 网在是目前覆盖面很广的有线电视网(CATV)的基础上开发的一种居民宽带接入网,除可传送 CATV 外,还提供电话、数据和其他宽带交互型业务。</p>
<p>HFC 网把原有线电视网中的同轴电缆主干部分换为光纤,光纤从头端连接到光纤节点,在光纤节点光信号被转换为电信号,然后通过同轴电缆传送到每个家庭。</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/HFC%E7%BD%91%E7%BB%9C%E7%BB%93%E6%9E%84%E5%9B%BE.jpg" alt="HFC网络结构图"></p>
<h4 id="2-5-3-光纤接入技术"><a href="#2-5-3-光纤接入技术" class="headerlink" title="2.5.3 光纤接入技术"></a>2.5.3 光纤接入技术</h4><p>多种宽带光纤接入技术,称为 FTTx(Fiber-To-The-x)光纤接入,其中 x 代表不同的光纤接入点。根据光纤到用户的距离来分类,可分为光纤到小区(Fiber To The Zone,FTTZ)、光纤到路边(Fiber To The Curb,FTTC)、光纤到大楼(Fiber To The Building,FTTB)、光纤到户(Fiber To The Home,FTTH)以及光纤到桌面(Fiber To The Desk,FTTD)等。</p>
<h4 id="2-5-4-移动互联网接入"><a href="#2-5-4-移动互联网接入" class="headerlink" title="2.5.4 移动互联网接入"></a>2.5.4 移动互联网接入</h4><p>随着宽带无线接入技术和移动终端技术的飞速发展,人们迫切希望能够随时随地甚至在移动过程中都能方便得从互联网获取信息和服务,移动互联网应运而生并迅猛发展。移动互联网就是将移动通信和互联网二者结合为一体,4G 时代的开启以及移动终端设备的普及必将为移动互联网的发展注入巨大的能量。</p>
<p>4G 即第四代移动电话通信标准,指的是第四代移动通信技术,这种新网络可使电话用户以无线形式实现全方位虚拟连接。4G 最突出的特点之一,就是网络传输速率达到了前所未有的 100Mb/s,完全能够满足用户的上网需求。</p>
<p>4G 系统总的技术目标和特点可以概括为:</p>
<ul>
<li>系统具有更高的数据率、更好的业务质量(Qos)、更高的频谱利用率、更高的安全性、更高的智能性、更高的传输质量、更高的灵活性</li>
<li>4G 系统应能支持非对称性业务,并能支持多种业务</li>
<li>4G 系统应体现移动与无线接入网和 IP 网络不断融合的发展趋势</li>
</ul>
<h2 id="三、数据链路层"><a href="#三、数据链路层" class="headerlink" title="三、数据链路层"></a>三、数据链路层</h2><h3 id="3-1-数据链路层三个基本问题"><a href="#3-1-数据链路层三个基本问题" class="headerlink" title="3.1 数据链路层三个基本问题"></a>3.1 数据链路层三个基本问题</h3><h4 id="3-1-1-数据链路和帧"><a href="#3-1-1-数据链路和帧" class="headerlink" title="3.1.1 数据链路和帧"></a>3.1.1 数据链路和帧</h4><p><strong>链路</strong> 指从一个节点到相邻节点的一段物理线路(有线或无线),而中间没有任何其他的交换节点。</p>
<p>计算机通信的路径往往要经过许多这样的链路,链路只是一条路径的组成部分。如下图,计算机 A 到 计算机 B 要经过链路 1、链路 2、链路 3 和链路4。集线器不是交换节点,因此计算机 A 和路由器 1 之间是一条链路,而计算机 B 和路由器 2 之间使用了交换机,这就是两条链路——链路 3 和链路4。</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E9%93%BE%E8%B7%AF.png" alt="链路"></p>
<p><strong>数据链路</strong> 当需要在一条链路上传送数据时,除了必须有一条物理线路外,还必须有一些必要的通信协议来控制这些数据的传输。若把实现这些协议的硬件和软件加到链路上,就构成了数据链路。现在最常用的方法是使用网络适配器(既有硬件也有软件)来实现这些协议,一般适配器都包含了数据链路层和物理层。</p>
<p><strong>帧</strong> 数据链路层把网络层交下来的数据封装成帧发送到链路上,以及把接收到的帧中的数据取出并上交给网络层。</p>
<p>数据链路层在发送端把网络层交下来的 IP 数据报添加首部和尾部封装成帧并传送到链路中,接收端收到后检测帧在传输过程中是否产生差错,如果无差错将会把 IP 数据报上交给网络层,如果有差错则丢弃。</p>
<h4 id="3-1-2-数据链路层三个问题"><a href="#3-1-2-数据链路层三个问题" class="headerlink" title="3.1.2 数据链路层三个问题"></a>3.1.2 数据链路层三个问题</h4><p>数据链路层的协议有许多种,但有三个基本问题是共同的:封装成帧、透明传输和差错检测。</p>
<h5 id="(一)封装成帧"><a href="#(一)封装成帧" class="headerlink" title="(一)封装成帧"></a>(一)封装成帧</h5><p>封装成帧就是将网络层的 IP 数据包的前后分别添加首部和尾部,这样就构成了帧。不同数据链路层协议的帧的首部和尾部包含的信息有明确规定,帧的首部和尾部有帧开始符和帧结束符,称为<strong>帧定界符</strong>。接收端收到物理层传过来的数字信号读取到帧开始符一直到帧结束符,就认为接收到了一个完整的帧。</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E5%B0%81%E8%A3%85%E6%88%90%E5%B8%A7.png" alt="封装成帧"></p>
<p>帧定界符的作用更加明显。如果发送端在尚未发送完一个帧时突然出现故障,终端发送,接收端收到了只有帧开始符没有帧结束符,就认为是一个不完整的帧,必须丢弃。</p>
<p>为了提高数据链路层传输效率,应当使帧的数据部分尽可能大于首部和尾部的长度,但是每一种数据链路层协议都规定了所能传送<strong>帧的数据部分的长度的上限——即最大传输单元</strong>(Maximum Transfer Unit,MTU),以太网的 MTU 为 1500 个字节,MTU 指的是数据部分的长度。</p>
<h5 id="(二)透明传输"><a href="#(二)透明传输" class="headerlink" title="(二)透明传输"></a>(二)透明传输</h5><p>帧开始符和帧结束符最好选择不会出现在帧的数据部分的字符,通常电脑键盘能够输入的字符是 ASCII 字符代码表中的打印字符。在 ASCII 字符代码表中,还有非打印控制字符,其中有两个字符专门用来做定界符,代码 SOH(Start Of Header)作为帧开始定界符,对应的二进制编码为 0000 0001,代码 EOT(End Of Transmission)作为帧结束定界符,对应的二进制编码为 0000 0100。</p>
<p>如果传送的是文本文件组成的帧时(文本文件中的字符都是使用键盘输入的可打印字符),其数据部分显然不会出现 SOH 或 EOT 这样的帧定界符;而当数据部分时非 ASCII 字符代码表的文本文件时(比如二进制代码的计算机程序或图像等),如果数据中的某一段二进制代码正好和 SOH 或 EOT 帧定界符编码一样,接收端就会误认为这就是帧的边界就接收这个了帧,而后面的部分因为没有帧开始定界符而被认为是无效帧被丢弃。</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E6%95%B0%E6%8D%AE%E9%83%A8%E5%88%86%E5%87%BA%E7%8E%B0EOT.png" alt="数据部分出现EOT"></p>
<p>解决方案:<strong>字符填充法</strong>,在数据部分中的控制字符和特殊字符前插入一个非打印字符(代码是“ESC”,二进制编码为 0001 1011)作为转义字符,而真正的首部和尾部前不加。</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E5%AD%97%E7%AC%A6%E5%A1%AB%E5%85%85%E6%B3%95.png" alt="字符填充法"></p>
<p><strong>透明传输</strong> 发送节点在发送帧之前在原始数据中必要位置插入转义字符,接收节点收到后去掉转义字符,又得到原始数据,中间插入转义字符是要让传输的原始数据原封不动地发送到接收端,这个过程称为“透明传输”。</p>
<h5 id="(三)差错检测"><a href="#(三)差错检测" class="headerlink" title="(三)差错检测"></a>(三)差错检测</h5><p>传输差错的分类:</p>
<ul>
<li><strong>比特差错</strong> 比特在传输过程中可能会产生差错:1 可能会变成 0,0 也可能变成 1</li>
<li><strong>帧丢失</strong> 丢失某个帧</li>
<li><strong>帧重复</strong> 某个帧收到多次</li>
<li><strong>帧失序</strong> 后发送的帧反而先到达接收端</li>
</ul>
<p><strong>误码率</strong> 在一定时间内,传输错误的比特占所传输比特总数的比率称为误码率。误码率和信噪比有很大关系,如果设法提高信噪比,就可以使误码率降低。</p>
<p>但实际通信链路并非理想的,它不可能使误码率下降为零。因此,为了保证数据传输的可靠性,在计算计算机网络传输数据时,必须采用各种差错检测措施。要想让接收端能够判断帧在传输过程是否出现差错,需要在传输的帧中包含用于检测错误的信息,这部分信息就称为<strong>帧校验序列</strong>(Frame Check Sequence,FCS)。</p>
<p>目前在数据链路层广泛使用了<strong>循环冗余校验 CRC</strong> 的差错检测技术。CRC 运算就是在数据 M 的后面添加供差错检测用的 n 位冗余码,然后构成一个帧发送出去。算法:先规定一个生成多项式(共有 n 位),然后给被传输数据的末尾加上 n-1 个 0,用生成多项式除以(mod 2)加零后的传输数据,得到 n 位的余数,即为 FCS(帧校验序列)的值。</p>
<h3 id="3-2-点到点的数据链路"><a href="#3-2-点到点的数据链路" class="headerlink" title="3.2 点到点的数据链路"></a>3.2 点到点的数据链路</h3><p>点到点信道是指一条链路上就一个发送端和一个接收端的信道,通常用在广域网链路。比如两个路由器通过串口(广域网口)相连,或家庭用户使用调制解调器通过电话线拨号连接 ISP。</p>
<p>在通信线路质量较差的年代,在数据链路层使用可靠传输协议曾经是一种好办法。因此,能实现可靠传输的高级数据链路控制(High-level Data Link Control,HDLC)就成为了当时比较流行的数据链路层协议,但现在 HDLC 已很少使用了。对于点对点的链路,简单得多的点对点协议 PPP 则是目前使用最广泛的数据链路层。</p>
<h4 id="3-2-1-PPP-协议的特点"><a href="#3-2-1-PPP-协议的特点" class="headerlink" title="3.2.1 PPP 协议的特点"></a>3.2.1 PPP 协议的特点</h4><p>PPP 协议在 1994 年就成为了因特网的正式标准,具有以下特点:</p>
<ul>
<li><strong>简单</strong> PPP 协议不负责可靠传输、纠错和流量控制,也不需要给帧编号,接收端收到帧后,就进行 CRC 检验,如果 CRC 检验正确,就收下该帧,反之,则丢弃,其他什么也不做</li>
<li><strong>封装成帧</strong> PPP 协议必须规定特殊字符作为帧定界符(每种数据链路层协议都有特定的帧定界符),以便接收端能够从收到的比特流中准确地找出帧地开始和结束位置</li>
<li><strong>透明传输</strong> PPP 协议必须保证数据传输地透明性</li>
<li><strong>差错检测</strong> PPP 协议必须能够对接收端收到地帧进行检测,并立即丢弃有差错的帧</li>
<li><strong>支持多种网络层协议</strong> PPP 协议必须能够在同一条物理链路上同时支持网络层协议的运行,这就意味着 IP 数据包和 IPv6 数据包可以封装在 PPP 帧中继续传输</li>
<li><strong>检测连接状态</strong> PPP 协议必须具有一种机制能够及时自动检测出链路是否处于正常工作状态,当出现故障的链路隔了一段时间后又恢复正常工作时,就特别需要有这种及时检测功能</li>
<li><strong>最大传送单元</strong> PPP 协议必须对每一种类型的点对点链路设置最大传送单元的标准默认值,这样做是为了促进各种实体之间的互操作性。如果高层协议发送的分组过长并超过 MTU 的数值,PPP 就要丢弃这样的帧,并返回差错。需要强调的是,MTU 是数据链路层的帧可以载荷的数据部分的最大长度,而不是帧的总长度</li>
<li><strong>网络层地址协商</strong> PPP 协议必须提供一种机制使通信的两个网络层的实体能够通过协商知道或配置彼此的网络层地址。使用 ADSL 调制解调器拨号访问 Internet,ISP 会给拨号的计算机分配一个公网地址,这就是 PPP 协议的功能</li>
<li><strong>数据压缩协商</strong> PPP 协议必须提供一种方法来协商使用数据压缩算法,但 PPP 协议并不要求将数据压缩算法进行标准化</li>
</ul>
<h4 id="3-2-2-PPP-协议的组成"><a href="#3-2-2-PPP-协议的组成" class="headerlink" title="3.2.2 PPP 协议的组成"></a>3.2.2 PPP 协议的组成</h4><p>PPP 协议有三个组成部分:</p>
<ol>
<li><strong>高级数据链路控制协议(High-level Data Link Control,HDCL)</strong> 高级数据链路控制协议是将 IP 数据报封装到串行链路的方法。PPP 既支持异步链路(无奇偶校验的 8 比特数据),也支持面向比特的同步链路。IP 数据报在 PPP 帧中就是其信息部分,这个信息部分的长度受最大传送单元 MTU 的限制</li>
<li><strong>链路控制协议(Link Control Protocol,LCP)</strong> 用来建立、配置和测试数据链路连接,通信的双方可协商一些选项</li>
<li><strong>网络控制协议(Network Control Proyocol,NCP)</strong> 网络控制协议中的每个协议支持不同的网络层协议,如 IP、IPv6、DECnet,以及 AppleTalk 等</li>
</ol>
<p>PPP 帧的首部和尾部,首部有 5 个字节,其中 F 字段为帧开始定界符(0x7E),占一个字节,A 字段为地址字段,占一个字节,C 字段为控制字段,占 1 个字节。尾部有 3 个字节,其中 2 个字节是帧校验序列,1 字节是帧结束定界符(0x7E)。信息部分不超过 1500 字节。</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/PPP%E5%B8%A7%E6%A0%BC%E5%BC%8F.png" alt="PPP帧格式"></p>
<p>PPP 帧是面向字节的,所有的 PPP 帧的长度都是整数字节。</p>
<h4 id="3-2-3-PPP-帧填充方式"><a href="#3-2-3-PPP-帧填充方式" class="headerlink" title="3.2.3 PPP 帧填充方式"></a>3.2.3 PPP 帧填充方式</h4><p>当信息字段出现和帧开始定界符和帧结束定界符一样的比特(0x7E)组合时,就必须采取一些措施使这种形式上和标志字段一样的比特组合不出现在信息字段中。</p>
<h5 id="(一)异步传输使用字节填充"><a href="#(一)异步传输使用字节填充" class="headerlink" title="(一)异步传输使用字节填充"></a>(一)异步传输使用字节填充</h5><p>在异步传输的链路中,数据传输以字节为单位,PPP 帧的转义符定义为 0x7D,并使用字节填充,RFC1662 规定了如下所述的填充方法,</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/PPP%E5%B8%A7%E5%AD%97%E8%8A%82%E5%A1%AB%E5%85%85.png" alt="PPP帧字节填充"></p>
<ul>
<li>把信息字段中出现的每一个 0x7E 字节转变为 2 字节序列(0x7D,0x5E)</li>
<li>若信息字段中出现一个 0x7D 的字节(即出现了和转义字符一样的比特组合),则把 0x7D 转变为 2 字节序列(0x7D,0x5D)</li>
<li>若信息字段中出现一个 ASCII 码的控制字符(即数值小于 0x20 的字符),则在该字符前面要加一个 0x7D 字节,同时将该字符的编码加以改变。例如,出现 0x03 就要把它转变为 2 字节序列(0x7D,0x23)</li>
</ul>
<h5 id="(二)同步传输使用零比特填充"><a href="#(二)同步传输使用零比特填充" class="headerlink" title="(二)同步传输使用零比特填充"></a>(二)同步传输使用零比特填充</h5><p>在同步传输的链路中,数据传输以帧为单位,PPP 协议采用零比特填充法来实现透明传输。PPP 协议帧定界符 0x7E 写成二进制 0111 1110,也就是可以看到中间有连续的 6 个 1,只要想办法在数据部分不要出现连续的 6 个 1,就肯定不会出现这种定界符。</p>
<p>零比特填充的具体做法:在发送端,先扫描整个信息字段(通常使用硬件上实,但也可用软件实现,只是会慢些)。只要发现有连续 5 个 1,则立即填入一个 0。因此经过这种零比特填充后的数据,就可以保证在信息字段不会出现连续的 6 个 1;接收端在收到一个帧时,先找到标志字段 F 以确定一个帧的边界,接着再用硬件对其中的比特流进行扫描,每当发现连续 5 个 1 时,就把这连续 5 个 1 后的一个 0 删除,以还原成原来的信息比特流,这样就保证了透明传输。</p>
<h3 id="3-3-广播信道的数据链路"><a href="#3-3-广播信道的数据链路" class="headerlink" title="3.3 广播信道的数据链路"></a>3.3 广播信道的数据链路</h3><h4 id="3-3-1-广播信道的局域网"><a href="#3-3-1-广播信道的局域网" class="headerlink" title="3.3.1 广播信道的局域网"></a>3.3.1 广播信道的局域网</h4><p>最初的局域网使用同轴电缆进行组网,采用总线型拓扑,一个链路通过 T 型接口连接多个网络设备(网卡),链路上的两个计算机通信,比如计算机 A 给 计算机 B 发送一个帧,同轴电缆会把承载该帧的数字信号传送到所有终端,链路上的所有计算机都能收到(所以称为广播通信)。要在这样的一个广播信道实现点到点的通信,就需要给发送的帧添加源地址和目标地址,这就要求网络中的每个计算机的网卡有唯一的一个物理地址(即 MAC 地址),仅当帧的目标 MAC 地址和计算机的网卡 MAC 地址相同,网卡才接收该帧,对于不是发给自己的帧则丢弃。</p>
<p>广播信道中计算机发送数据的机会均等,但是链路上又不能同时传送多个计算机发送信号,因为会产生信号叠加相互干扰,因此每台计算机在发送之前要判断链路上是否有信号在传,开始发送后还要判断是否和其他正在链路上传过来的数字信号发生冲突。如果发送冲突,就要等一个随机时间再次尝试发送,这种机制就是带冲突检测的载波监听多路访问(CSMA/CD)。<strong>CSMA/CD 就是广播信道使用的数据链路层协议,使用该种协议的网络就是以太网</strong>。</p>
<p>以太网(Ethernet)是一种计算机局域网组网技术。IEEE 指定的 IEEE 802.3 标准给出了以太网的技术标准,即以太网的介质访问控制协议(CSMA/CD)及物理层技术规范(包括物理层的连线、电信号和介质访问层协议的内容)。最初的以太网只有 10Mbps 的吞吐量,称为标准以太网。</p>
<p>以太网可以使用粗同轴电缆、细同轴电缆、非屏蔽双绞线、屏蔽双绞线和光纤等多种传输介质。在 IEEE 802.3 标准中,为不同的传输介质制定了不同的物理层标准,标准中前面的数字表示传输速度,单位是“Mbps”。</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E4%BB%A5%E5%A4%AA%E7%BD%91%E6%A0%87%E5%87%86.png" alt="以太网标准"></p>
<h4 id="3-3-2-CSMA-CD-协议"><a href="#3-3-2-CSMA-CD-协议" class="headerlink" title="3.3.2 CSMA/CD 协议"></a>3.3.2 CSMA/CD 协议</h4><p><strong>多点接入</strong> 在总线型网络中很容易增加接入的计算机,这就是多点接入</p>
<p><strong>载波监听</strong> 在广播信道中的计算机发送数据的机会均等,但不能同时有两个计算机发送数据,因为总线上只要有一台九四u安吉发送数据,总线的传输资源就被占用。因此计算机在发送数据之前要先侦听总线是否有信号,只要检测到没有信号传输才能发送数据,这就是载波监听</p>
<p><strong>冲突检测</strong> 即便检测出总线上没有信号,开始发送数据后也有可能和迎面而来的信号在链路上发生碰撞,碰撞后的信号相互叠加,在总线上电压变化幅度将会增加,发送方检测到电压超过一定的门限值时,就认为发生冲突,这就是冲突检测</p>
<p>信号产生叠加就无法从中恢复出有用的信息,一旦发现总线上出现了碰撞,发送端就要立即停止发送,免得继续进行无效的发送,白白浪费网络资源,而是等待一个随机时间后再次发送。显然,在使用 CSMA/CD 协议时,一个站不可能同时进行发送和接收,因此使用 CSMA/CD 协议的以太网不可能进行全双工通信而只能进行半双工通信。</p>
<h5 id="(一)以太网最小帧"><a href="#(一)以太网最小帧" class="headerlink" title="(一)以太网最小帧"></a>(一)以太网最小帧</h5><p>以太网设计最大端到端长度为 5 千米,单程传播时延大约为 25.6μs,往返传播时延为 51.2μs,10Mb/s 标准以太网最小帧为:<br>$$<br>10Mb/s\times 51.2\mu s=10^7b/s\times51.2\times10^{-6}s=512bit<br>$$<br>512 比特也就是 64字节,这就意味着以太网发送数据帧时如果前 64 字节没有检测出冲突,后面发送的数据就一定不会发送冲突。换句话说,如果发生碰撞,就一定在前 64 字节之内。由于一旦检测出冲突就立即种终止发送,这时发送的数据一定小于 64 字节,因此但凡是长度小于 64 字节的帧都是由于冲突而异常终止的无效帧,只要收到了这种无效帧,就应当立即将其终止。</p>
<h5 id="(二)冲突解决办法——退避算法"><a href="#(二)冲突解决办法——退避算法" class="headerlink" title="(二)冲突解决办法——退避算法"></a>(二)冲突解决办法——退避算法</h5><p>总线上单程端到端传播时延称为 $\tau$,计算机要想知道发送的帧在链路上是否发生了碰撞必须等待 2$\tau$,2$\tau$ 称为争用期。</p>
<p>以太网使用<strong>截断二进制指数退避算法</strong>来解决碰撞问题,算法让发生碰撞的站在停止发送数据后,不是等信道变为空闲后就立即发生数据,而是推迟一个随机的时间,这样做是为了使重传时再次发生冲突的概率减小。具体的退避算法如下:</p>
<ol>
<li>确定基本退避时间,它就是争用期 2$\tau$</li>
<li>从离散的整数集合 $[0,1,\cdots,(2^k-1)]$ 中随机取一个数,记为 r。重传应推后的时间就是 r 倍的争用期。上面参数 k 的公式计算:$k=Min(重传次数, 10)$,可见当重传次数不超过 10 时,参数 k 等于重传次数;但当重传次数超过 10 时, k 就不再增大而一直等于 10</li>
<li>当重传次数达到 16 次仍不能成功时(这表明同时打算发送数据的站太多,以致连续发送冲突),则丢弃该帧,并向高层报告</li>
</ol>
<h4 id="3-3-3-以太网帧格式"><a href="#3-3-3-以太网帧格式" class="headerlink" title="3.3.3 以太网帧格式"></a>3.3.3 以太网帧格式</h4><p>常用的以太网 MAC 帧格式有两种标准,一种是 Ethernet V2 标准,另一种是 IEEE 802.3 标准,使用最多的是以太网 V2 标准的 MAC 帧格式。</p>
<p>Ethernet Ⅱ 帧比较简单,由五个字段组成。前两个字段分别为 6 字节长的目标 MAC 地址和源 MAC 地址,第三个字段是 2 字节的类型字段,用来标志上一层使用的是什么协议,以便把收到的 MAC 帧的数据上交给上一层协议,第四个字段是数据字段,其长度在 46~1500 字节之间(46 字节是这样得出的:最小长度 64 字节减去 18 字节的首部和尾部就得出数据字段的最小长度),最后一个字段是 4 字节的帧检验序列 FCS,使用 CRC 检验,下图为以太网帧结构。</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E4%BB%A5%E5%A4%AA%E7%BD%91%E5%B8%A7%E7%BB%93%E6%9E%84.png" alt="以太网帧结构"></p>
<p>当数据字段的长度小于 46 字节时,数据链路层就会在数据字段的后面加入一个整数字节的填充字段,以保证以太网的 MAC 帧长不小于 64 字节,接收端还必须能够将添加的字节去掉。在网络层首部有一个“总长度”字段,用来指明网络数据包的长度,根据网络层首部标注的数据包总长度,会去掉数据链路层提交的填充字节。</p>
<p>从上图可以看出,在传输媒体上实际传送的要比 MAC 帧多 8 字节,这是因为当一个站刚开始接收 MAC 帧时,由于适配器的时钟尚未与到达的比特流达成同步,因此 MAC 帧的最前面的若干位就无法接收,结果使整个 MAC 称为无用的帧。为了使接收端迅速实现位同步,从数据链路层向下传到物理层时还要在帧的前面插入 8 字节(由硬件生成),它由两个字段构成。第一个字段是 7 个字节的前同步码(1 和 0 的交替码),作用是使接收端的适配器在接收 MAC 帧时能够迅速调整其时钟频率,使之和发送端的始终同步,也就是“实现位同步”。第二个字段是帧开始定界符,定义为 10101011,它的前六位的作用和前同步码一样,最后两个连续的 1 是告诉接收端适配器:“MAC 帧的信息马上就要来了,请适配器注意接收”。MAC 帧的 FCS 字段的检测范围不包括前同步码和帧开始定界符。</p>
<p>在以太网上传数据时是以帧为单位传送的,以太网在传送帧时,各帧之间还必须有一定的间隙。因此,接收端只要找到帧开始定界符,其后面连续达到的比特流就都属于同一个 MAC 帧。可见以太网不需要使用帧结束定界符,也不需要使用字节插入来保证透明传输。</p>
<p>IEEE802.3 标准规定凡出现下列情况之一的即视为无效的 MAC 帧:</p>
<ul>
<li>帧的长度不是整数个字节</li>
<li>用收到的帧检测序列 FCS 查出差错</li>
<li>收到的 MAC 帧的数据字段的长度不在 46~1500 字节之间。考虑到 MAC 帧首部和尾部的长度共有 18 字节,因此可以得出有效的 MAC 帧长度应该在 64~1518 字节之间</li>
</ul>
<p>对于检查出无效的 MAC 帧就简单地丢弃,以太网并不负责重传丢弃的帧。</p>
<h4 id="3-3-4-以太网信道利用率"><a href="#3-3-4-以太网信道利用率" class="headerlink" title="3.3.4 以太网信道利用率"></a>3.3.4 以太网信道利用率</h4><p>利用率是指发送数据的时间占整个时间的比例,平均发送一帧所需的时间,经历了 $n$ 倍争用期 2$τ$,$T_0$ 为发送该帧所需的时间,$τ$ 为该帧的传播时延。信道的利用率为:<br>$$<br>S=\cfrac{T_0}{n\cdot2\tau+T_0+\tau}<br>$$<br>从公式可以看出,要想提高信道的利用率最好 $n$ 为 0,这就意味着以太网上各个计算机发送数据不会产生碰撞(这显然已经不是 CSMA/CD,而需要一种特殊的调度方法),并且能够非常有效地利用网络地传输资源,即总线一旦空闲就有一个站立即发送数据。以这种情况算出来地信道利用率是<strong>极限信道利用率</strong>。这样发送一帧占用地线路时间是 $T_0+\tau$,因此极限信道利用率为:<br>$$<br>S_{max}=\cfrac{T_0}{T_0+\tau}=\cfrac{1}{1+\cfrac{\tau}{T_0}}<br>$$<br>从以上公式可以看出,即便是以太网极限信道利用率也不能达到 100%。要想提高极限信道利用率就要降低公式中 $\cfrac{\tau}{T_0}$ 的比值,τ 值和以太网连线的长度有关,即 $τ$ 值要小,以太网网线的长度就不能太长。带宽一定的情况下 $T_0$ 和帧的长度有关,这就意味着,以太网的帧不能太短。</p>
<h4 id="3-3-5-网卡"><a href="#3-3-5-网卡" class="headerlink" title="3.3.5 网卡"></a>3.3.5 网卡</h4><p>计算机与外界局域网的连接是通过在主机箱内插入一块网络接口板(或者是在笔记本电脑中插入一块 PCMCIA 卡)。网络接口板又称通信适配器或网络适配器或网络接口卡,但是更多的人愿意使用更为简单的名称“网卡”。</p>
<p>网卡是工作在数据链路层和物理层的网络组件,是局域网中连接计算机和传输介质的接口,不仅能实现与局域网传输介质之间的物理连接和电信号匹配,还涉及帧的发送与接收、帧的封装和拆封、帧的差错校验、介质访问控制、数据的编码与解码以及数据缓存等功能。</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E7%BD%91%E5%8D%A1%E7%9A%84%E4%BD%9C%E7%94%A8.png" alt="网卡的作用"></p>
<p>网卡上面装有处理器和存储器(包括 RAM 和 ROM)。网卡和局域网之间的通信是通过电缆或双绞线以串行传输方式进行的。而网卡和计算机之间的通信则是通过计算机主板上的 I/O 总线以并行传输方式进行的。因此,网卡的一个重要功能就是要进行串行、并行转换。由于网络上的数据率和计算机总线上的数据率并不相同,因此在网卡中必须要装有对数据进行缓存的存储芯片。</p>
<p>适配器还要能够实现以太网协议(CSMA/CD),帧的封装和拆封功能,这些工作都是由网卡来实现的,计算机的 CPU 根本不关系这些事情。适配器接收和发送各种帧时不使用计算机的 CPU,这时 CPU 可以处理其他任务。当适配器收到有差错的帧时,就把这个帧丢弃而不必通知计算机。当适配器收到正确的帧时,它就使用中断来通知计算机并交付给协议栈中的网络层。</p>
<p>物理层功能实现网卡和网络的连接和数字信号同步,实现数据的编码即曼彻斯特编码。</p>
<h4 id="3-3-6-MAC-地址"><a href="#3-3-6-MAC-地址" class="headerlink" title="3.3.6 MAC 地址"></a>3.3.6 MAC 地址</h4><p>在广播信道实现点到点通信,需要网络中的每个网卡都有一个地址,这个地址称为物理地址或 MAC 地址。IEEE802 标准为局域网规定了一种 48 位的全球地址,这种 6 字节的 MAC 地址在生产时配置时就已被固化在网卡的 ROM 中,其中前 3 个字节是组织唯一标识符,是由生产局域网适配器的厂家向 IEEE 购买的地址块,后 3 个字节是扩展标识符,由厂家自行指派。</p>
<p>适配器有过滤功能,适配器从网络上每收到一个 MAC 帧就先用硬件检查 MAC 帧中的目的地址。如果是发往本站的帧则收下,然后再进行其他的处理;否则就将此帧丢弃,不再进行其他的处理。这样做不会浪费主机的处理机和内存资源。这里“发往本站的帧”包括一下三种帧:</p>
<ul>
<li>单播(unicast)帧(一对一),即收到的帧的 MAC 地址与本站的硬件地址相同</li>
<li>广播(broadcast)帧(一堆全体),即发送给本局域网上所有站点的帧</li>
<li>多播(multicast)帧(一对多),即发送给本局域网上一部分站点的帧</li>
</ul>
<h2 id="四、网络层"><a href="#四、网络层" class="headerlink" title="四、网络层"></a>四、网络层</h2><h3 id="4-1-IP-地址"><a href="#4-1-IP-地址" class="headerlink" title="4.1 IP 地址"></a>4.1 IP 地址</h3><h3 id="4-2-子网划分"><a href="#4-2-子网划分" class="headerlink" title="4.2 子网划分"></a>4.2 子网划分</h3><h3 id="4-3-静态路由"><a href="#4-3-静态路由" class="headerlink" title="4.3 静态路由"></a>4.3 静态路由</h3><h3 id="4-4-路由汇总"><a href="#4-4-路由汇总" class="headerlink" title="4.4 路由汇总"></a>4.4 路由汇总</h3><h3 id="4-5-RIP-协议"><a href="#4-5-RIP-协议" class="headerlink" title="4.5 RIP 协议"></a>4.5 RIP 协议</h3><h3 id="4-6-OSPF-协议"><a href="#4-6-OSPF-协议" class="headerlink" title="4.6 OSPF 协议"></a>4.6 OSPF 协议</h3><h3 id="4-7-网络层首部"><a href="#4-7-网络层首部" class="headerlink" title="4.7 网络层首部"></a>4.7 网络层首部</h3><h3 id="4-8-ICMP-协议"><a href="#4-8-ICMP-协议" class="headerlink" title="4.8 ICMP 协议"></a>4.8 ICMP 协议</h3><h3 id="4-9-ARP-协议"><a href="#4-9-ARP-协议" class="headerlink" title="4.9 ARP 协议"></a>4.9 ARP 协议</h3><h3 id="4-10-IGMP-协议"><a href="#4-10-IGMP-协议" class="headerlink" title="4.10 IGMP 协议"></a>4.10 IGMP 协议</h3><h2 id="五、传输层"><a href="#五、传输层" class="headerlink" title="五、传输层"></a>五、传输层</h2><h3 id="5-1-传输层的两大协议"><a href="#5-1-传输层的两大协议" class="headerlink" title="5.1 传输层的两大协议"></a>5.1 传输层的两大协议</h3><h4 id="5-1-1-用户数据报协议-UDP"><a href="#5-1-1-用户数据报协议-UDP" class="headerlink" title="5.1.1 用户数据报协议 UDP"></a>5.1.1 用户数据报协议 UDP</h4><h4 id="5-1-2-传输控制协议-TCP"><a href="#5-1-2-传输控制协议-TCP" class="headerlink" title="5.1.2 传输控制协议 TCP"></a>5.1.2 传输控制协议 TCP</h4><h3 id="5-2-可靠传输"><a href="#5-2-可靠传输" class="headerlink" title="5.2 可靠传输"></a>5.2 可靠传输</h3><h3 id="5-3-流量控制"><a href="#5-3-流量控制" class="headerlink" title="5.3 流量控制"></a>5.3 流量控制</h3><h3 id="5-4-拥塞控制"><a href="#5-4-拥塞控制" class="headerlink" title="5.4 拥塞控制"></a>5.4 拥塞控制</h3><h3 id="5-5-连接管理"><a href="#5-5-连接管理" class="headerlink" title="5.5 连接管理"></a>5.5 连接管理</h3><h2 id="六、应用层"><a href="#六、应用层" class="headerlink" title="六、应用层"></a>六、应用层</h2><p>应用层协议定义服务器和客户机之间如何交换信息、服务器和客户端之间能够进行哪些交互、命令和交互顺序,规定好信息的格式以及每个字段的意义。不同的应用实现的功能不一样,比如访问网站和收发电子邮件的应用的功能不一样,因此就需要有不同的应用层协议。具体来说,应用层协议应当定义:</p>
<ul>
<li>应用进程交换的报文类型,如请求报文和响应报文</li>
<li>各种报文类型的语法,如报文中各个字段及其详细描述</li>
<li>字段的语义,即包含在字段中的信息的含义</li>
<li>进程何时、如何发送报文,以及对报文进行响应的规则</li>
</ul>
<h3 id="6-1-域名系统-DNS"><a href="#6-1-域名系统-DNS" class="headerlink" title="6.1 域名系统 DNS"></a>6.1 域名系统 DNS</h3><h4 id="6-1-1-域名"><a href="#6-1-1-域名" class="headerlink" title="6.1.1 域名"></a>6.1.1 域名</h4><p>网络中的计算机之间通信,使用 IP 地址定位彼此。但对于使用计算机的人来说,数字形式的 IP 地址实在难以记忆,人们还是习惯使用具有一定意义的、好记的名称来访问某个服务器或网站,就比如我们一看到 <a href="http://www.baidu.com/">www.baidu.com</a> 就能知道是百度网站,这 baidu.com 就是百度网站<strong>域名</strong>。但整个 Internet 上网站和各种服务器数量众多,各个组织的服务器都需要一个名称,这就很容易重名。如何确保 Internet 上的服务器名称在整个 Internet 唯一呢?这就需要有域名管理认证机构进行统一管理。</p>
<p>如果你在互联网上有一组服务器,你需要先为其申请一个域名,也就是向管理认证机构注册一个域名。域名的注册遵循先申请先注册的原则,管理认证机构要确保每一个域名的注册都是独一无二、不可重复的。比如你现在想要申请一个 baidu.com 这个域名,管理认证机构肯定不能通过,因为已经被注册了。</p>
<p>企业或个人申请域名,通常要考虑以下因素:</p>
<ol>
<li>域名应该简明易记,便于输入</li>
<li>域名要有一定的内涵和意义</li>
</ol>
<h4 id="6-1-2-域名的结构"><a href="#6-1-2-域名的结构" class="headerlink" title="6.1.2 域名的结构"></a>6.1.2 域名的结构</h4><p>域名是全球唯一的,一个域名下可以有多个主机,那么“主机名+域名”肯定也是全球唯一的,“主机名+域名”称为<strong>完全陷定域名</strong>(FQDN,即 Fully Qualified Domain Name 的缩写,含义是完整的域名。例如,一台机器主机名 hostname 是 www,域名后缀是 baidu.com,那么该主机的 FQDN 应该是 <a href="http://www.baidu.com/">www.baidu.com</a> )。我们通常所说的网站的域名,严格来说指的就是完全限定域名。</p>
<p>为了方便记忆,我们常会使用一些约定俗成的名称来标识主机的作用,网站主机名为 <code>www</code>、博客主机名为 <code>blog</code>、论坛主机名为 <code>bbs</code>、发邮件的服务器名为 <code>smtp</code>、收邮件的服务器主机名为 <code>pop</code>,当然也可以不使用这些约定俗成的名字,比如网站的主机名称可以用 web。但需要注意的是,主机名和物理服务器并没有一一对应的关系,网站、博客、论坛三个网站可以在同一个服务器上,这里的主机名更多的是代表一个服务或是一个应用。</p>
<p>域名是分层的,所有的域名都是以英文的 “.” 开始的,是域名的根,根下面是顶级域名,顶级域名共有两种形式:国家代码顶级域名(简称国家顶级域名)和通用顶级域名。国家顶级域名由各个国家的互联网信息中心(NIC)管理,通用顶级域名则位于美国的全球域名最高管理机构 ICANN 负责管理。</p>
<ul>
<li><strong>国家顶级域名</strong> :又称国家代码顶级域名,指示国家区域,如 <code>.cn</code> 代表中国,<code>.us</code> 代表美国,<code>.fr</code> 代表法国,<code>.uk</code> 代表英国等等</li>
<li><strong>通过顶级域名</strong> :指示注册者的域名使用领域,他不带有国家特性。最常见的通用顶级域名有 7 个,即:<code>com</code>(公司企业)、<code>net</code>(网络服务机构)、<code>org</code>(非营利性的组织)、<code>int</code>(国际组织)、<code>edu</code>(美国专用的教育机构)、<code>gov</code>(美国的政府部门)、<code>mil</code>(美国的军事部门)</li>
</ul>
<p>在国家顶级域名下注册的二级域名均由该国家自定确定,我国把二级域名划分为“类别域名”和“行政区域名”两大类,</p>
<ul>
<li><strong>类别域名</strong> :类别域名共有 7 个,分别为:<code>ac</code>(科研机构)、<code>com</code>(工、商、金融等企业)、<code>edu</code>(中国的教育机构)、<code>mil</code>(中国的国防机构)、<code>net</code>(提供互联网服务的机构)、<code>org</code>(非营利性组织)、<code>gov</code>(中国的政府机构)</li>
<li><strong>行政区域名</strong> :行政区域名共 34 个,适用于我国的各省、自治区、直辖市。例如:<code>bj</code>(北京市)、<code>js</code>(江苏省)等等</li>
</ul>
<p>值得注意的是,我国修订的域名体系允许直接在 cn 的顶级域名下注册二级域名,比如,某公司 abe 以前要注册为 abe.com.cn,这个显然是三级域名,但现在可以注册为 abe.cn,变成二级域名。</p>
<p>企业或个人申请了域名之后,可以在该域名下添加多个主机名,也可以根据需要创建子域名,子域名下面亦可以有多个主机名。比如新浪网注册了域名 sina.com.cn,该域名下有多个主机名 www、smtp、pop,新浪新闻需要有单独的域名,于是在 sina.com.cn 域名下设置子域名 new.sina.com.cn,新闻又分为军事新闻、航空新闻、新浪天气等模块,分别使用 mil、sky 和 weather 作为栏目的主机名。</p>
<p>所有的域名都是以 “.” 结束,不过我们在使用时域名最后的 “.” 经常被省去。</p>
<h4 id="6-1-3-Internet-中的域名服务器"><a href="#6-1-3-Internet-中的域名服务器" class="headerlink" title="6.1.3 Internet 中的域名服务器"></a>6.1.3 Internet 中的域名服务器</h4><p>当我们通过域名访问网站或点击网页中的超链接跳转到其他网站时,计算机需要讲域名解析成 IP 地址才能访问这些网站。DNS 服务器负责域名解析,因此必须配置计算机使用哪些 DNS 服务器进行域名解析。计算机就配置了两个 DNS 服务器:一个首选的 DNS 服务器、一个备用的 DNS 服务器,配置两个 DNS 服务器可以实现容错。下面三个 DNS 服务器的地址都非常好记:<code>222.222.222.222</code> 是石家庄电信的 DNS 服务器,<code>114.114.114</code> 是江苏省南京市电信的 DNS 服务器,还有一个 <code>8.8.8.8</code> 是美国谷歌公司的 DNS 服务器。</p>
<p>全球有上亿个域名,假设全球由一个 DNS 服务器负责域名的解析,整个 Internet 每时每刻都有无数网民请求域名解析。这个 DNS 服务器需要多高的配置?该服务器联网的带宽需要多高才能满足要求?关键是,如果就一个 DNS 服务器的话,该服务器一旦垮掉,全球的域名解析都将失败。因此,要想在 Internet 中搭建一个健壮的、可扩展的域名解析体系架构,就要把域名解析任务分摊到多个 DNS 服务器上。</p>
<p>DNS 服务器上就一个根服务器,然后创建委派,每一个顶级域名指向一个负责的 NDS 服务器的 IP 地址,每一个 DNS 服务器都知道根 DNS 服务器的 IP 地址。如下图,</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/DNS%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%9A%84%E5%B1%82%E6%AC%A1.png" alt="DNS服务器的层次"></p>
<p>A 服务器是根域名服务器,不负责具体的域名解析,但根 DNS 服务器知道 B 服务器负责 net 域名解析、C 服务器负责 com 域名解析、D 服务器负责 org 域名解析。</p>
<p>根 DNS 知道顶级的 DNS 服务器,上级 DNS 委派下级 DNS,全部的 DNS 都知道根 DNS 服务器。基于这样的一种架构设计,客户端使用任何一个 DNS 服务器都能解析出全球的域名。</p>
<h4 id="6-1-4-域名的解析"><a href="#6-1-4-域名的解析" class="headerlink" title="6.1.4 域名的解析"></a>6.1.4 域名的解析</h4><p>一网络架构如图所示,</p>
<p><img src="https://gitee.com/azhenyoo/img/raw/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E5%9F%9F%E5%90%8D%E8%A7%A3%E6%9E%90.png" alt="域名解析"></p>
<p>客户端计算机 Client 的 DNS 指向 13.2.1.2,也就是指向了 B 服务器,现在 Client 向 DNS 发送一个域名解析请求数据包,要求解析 <a href="http://www.inhe.net/">www.inhe.net</a> 的 IP 地址,B 服务器正巧负责 inhe.net 域名解析,查询本地记录后直接返回给 Client 查询结果 221.192.141.115,DNS 服务器直接返回查询结果就是权威应答,这是一种情况。</p>
<p>现在看另一种情况,Client 向 B 服务器发送请求,解析 <a href="http://www.sohu.com/">www.sohu.com</a>. 域名的 IP 地址,解析过程如下:</p>
<ol>
<li>Clinet 向 DNS 服务器 13.2.1.2 发送域名解析请求</li>
<li>B 服务器只负责 net 域名解析,并不知道哪个 DNS 服务器负责 com 域名解析,但它知道根 DNS 服务器,于是讲域名解析的请求转发给根 DNS 服务器</li>
<li>根 DNS 服务器返回查询结果,告诉 B 服务器去查询 C 服务器</li>
<li>B 服务器将域名解析请求转发到 C 服务器</li>
<li>C 服务器虽然负责 com 域名解析,但 sohu.com 域名解析委派给了 E 服务器,C 服务器返回查询结果,告诉 B 服务器去查询 E 服务器</li>
<li>B 服务器将域名解析请求转发到 E 服务器</li>
<li>E 服务器上有 sohu.com 域名下的主机记录,将 <a href="http://www.sohu.com/">www.sohu.com</a> 的 IP 地址 220.181.90.14 返回给 B 服务器</li>
<li>B 服务器将费尽周折找到的结果缓存到本地,将解析到的 <a href="http://www.sohu.com/">www.sohu.com</a> 的 IP 地址 220.181.90.14 返回给 Client。这个查询结果是 B 服务器查询到的,因此不是权威应答。Client 缓存解析的结果。</li>
<li>如果此时另一个客户端 Client2(图中未画出)的 DNS 也指向 13.2.1.2,现在 Client2 也需要解析 <a href="http://www.sohu.com/">www.sohu.com</a> 的地址,将域名解析请求发送给 B 服务器</li>
<li>B 服务器刚刚缓存了 <a href="http://www.sohu.com/">www.sohu.com</a> 的查询结果,直接将查询缓存,即将 220.181.90.14 IP 地址返回给 Client2</li>
</ol>
<blockquote>
<p><strong>注意</strong> :可见 DNS 服务器的缓存功能能够减少向根 DNS 服务器转发查询次数、减少 Internet 上 DNS 查询报文的数量,缓存结果通常的有效期是 1 天时间,如果没有时间限制,当 <a href="http://www.sohu.com/">www.sohu.com</a> 的 IP 地址变化了,Client2 就不能查询到新的 IPD 地址了。</p>
</blockquote>
<h3 id="6-2-动态主机配置协议-DHCP"><a href="#6-2-动态主机配置协议-DHCP" class="headerlink" title="6.2 动态主机配置协议 DHCP"></a>6.2 动态主机配置协议 DHCP</h3><p>网络中计算机的 IP 地址、子网掩码、网关和 DNS 等设置可以人工指定,也可以设置成自动获得。设置成“自动获得”就需要使用 DHCP 协议从 DHCP 服务器请求 IP 地址。</p>
<h4 id="6-2-1-静态地址和动态地址的应用场景"><a href="#6-2-1-静态地址和动态地址的应用场景" class="headerlink" title="6.2.1 静态地址和动态地址的应用场景"></a>6.2.1 静态地址和动态地址的应用场景</h4><h4 id="6-2-2-DHCP-地址租约"><a href="#6-2-2-DHCP-地址租约" class="headerlink" title="6.2.2 DHCP 地址租约"></a>6.2.2 DHCP 地址租约</h4><h4 id="6-2-3-DHCP-地址租约生成过程"><a href="#6-2-3-DHCP-地址租约生成过程" class="headerlink" title="6.2.3 DHCP 地址租约生成过程"></a>6.2.3 DHCP 地址租约生成过程</h4><h4 id="6-2-4-DHCP-地址租约更新"><a href="#6-2-4-DHCP-地址租约更新" class="headerlink" title="6.2.4 DHCP 地址租约更新"></a>6.2.4 DHCP 地址租约更新</h4><h3 id="6-3-Telnet-协议"><a href="#6-3-Telnet-协议" class="headerlink" title="6.3 Telnet 协议"></a>6.3 Telnet 协议</h3><h3 id="6-4-远程桌面协议-RDP"><a href="#6-4-远程桌面协议-RDP" class="headerlink" title="6.4 远程桌面协议 RDP"></a>6.4 远程桌面协议 RDP</h3><h3 id="6-5-超文本传输协议-HTTP"><a href="#6-5-超文本传输协议-HTTP" class="headerlink" title="6.5 超文本传输协议 HTTP"></a>6.5 超文本传输协议 HTTP</h3><h3 id="6-6-文件传输协议-FTP"><a href="#6-6-文件传输协议-FTP" class="headerlink" title="6.6 文件传输协议 FTP"></a>6.6 文件传输协议 FTP</h3><h3 id="6-7-电子邮件"><a href="#6-7-电子邮件" class="headerlink" title="6.7 电子邮件"></a>6.7 电子邮件</h3>]]></content>
<categories>
<category>计算机基础</category>
<category>计算机网络</category>
</categories>
<tags>
<tag>计算机网络</tag>
</tags>
</entry>
<entry>
<title>Zookeeper</title>
<url>/2020/07/23/Zookeeper/</url>
<content><![CDATA[<p><a href="https://zookeeper.apache.org/">Zookeeper</a> 是 Apache 的一个分布式服务框架,是 Apache Hadoop 的一个子项目。官方文档上这么解释 Zookeeper,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。简单来说,Zookeeper = 文件系统 + 监听通知机制。<span id="more"></span></p>
<h2 id="一、Zookeeper-原理"><a href="#一、Zookeeper-原理" class="headerlink" title="一、Zookeeper 原理"></a>一、Zookeeper 原理</h2><h3 id="1-1-Zookeeper-的存储结构"><a href="#1-1-Zookeeper-的存储结构" class="headerlink" title="1.1 Zookeeper 的存储结构"></a>1.1 Zookeeper 的存储结构</h3><img src="https://gitee.com/azhenyoo/img/raw/master/Zookeeper/存储结构.png" alt="存储结构" style="zoom:100%;" />
<p>Zookeeper 底层是一套数据结构,这个存储结构是一个<strong>树形结构</strong>,其上的每一个节点,我们称之为“znode”。在 Zookeeper 中,znode 是一个跟 Unix 文件系统路径相似的节点,可以向节点<strong>存储数据</strong>或<strong>获取数据</strong>。Zookeeper 中的数据是按照“树”结构进行存储的,每一个 znode 默认能够存储 <strong>1MB</strong> 的数据,对于记录状态性质的数据来说,这足够了。</p>
<p> znode 节点可以分为 4 种不同的类型:</p>
<ul>
<li><strong>持久化目录节点</strong>(PERSISTENT) :客户端与 zookeeper 断开连接后,该节点依旧存在</li>
<li><strong>持久化顺序编号目录节点</strong>(PERSISTENT_SEQUENTIAL) :客户端与 zookeeper 断开连接后,该节点依旧存在,只是 Zookeeper 给该节点名称进行顺序编号</li>
<li><strong>临时目录节点</strong>(EPHEMERAL) :客户端与 zookeeper 断开连接后,该节点被删除</li>
<li><strong>临时顺序编号目录节点</strong>(EPHEMERAL_SEQUENTIAL) :客户端与 zookeeper 断开连接后,该节点被删除,只是 Zookeeper 给该节点名称进行顺序编号</li>
</ul>
<p>可以使用 zkCli 命令,登录到 Zookeeper 上,并通过 ls、create、delete、get、set 等命令操作这些 znode 节点。</p>
<h3 id="1-2-监听通知机制"><a href="#1-2-监听通知机制" class="headerlink" title="1.2 监听通知机制"></a>1.2 监听通知机制</h3><p>Zookeeper 是使用观察者设计模式来设计的。当客户端注册监听它关心的目录节点时,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)时,Zookeeper 会通知客户端。</p>
<h2 id="二、安装-Zookeeper"><a href="#二、安装-Zookeeper" class="headerlink" title="二、安装 Zookeeper"></a>二、安装 Zookeeper</h2><p>官方资源包可在 <a href="https://zookeeper.apache.org/releases.html">https://zookeeper.apache.org/releases.html</a> 站点中下载,最新发布版本为:3.6.1</p>
<h3 id="2-1-单机版"><a href="#2-1-单机版" class="headerlink" title="2.1 单机版"></a>2.1 单机版</h3><h4 id="2-1-1-上传文件"><a href="#2-1-1-上传文件" class="headerlink" title="2.1.1 上传文件"></a>2.1.1 上传文件</h4><p>上传 apache-zookeeper-3.6.1-bin.tar.gz 到 /root/temp 目录并解压,解压完成后将文件拷贝到 /usr/local 目录,并重命名为 zookeeper 目录。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[root@192 temp]# rz</span><br><span class="line"></span><br><span class="line">[root@192 temp]# tar -zxf apache-zookeeper-3.6.1-bin.tar.gz</span><br><span class="line">[root@192 temp]# cp apache-zookeeper-3.6.1-bin /usr/local/zookeeper -r</span><br></pre></td></tr></table></figure>
<h4 id="2-1-2-Zookeeper-的目录结构"><a href="#2-1-2-Zookeeper-的目录结构" class="headerlink" title="2.1.2 Zookeeper 的目录结构"></a>2.1.2 Zookeeper 的目录结构</h4><p>进入 <code>/usr/local/zookeeper</code> 目录</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[root@192 temp]# cd /usr/local/zookeeper</span><br><span class="line">[root@192 zookeeper]# ll</span><br><span class="line">总用量 36</span><br><span class="line">drwxr-xr-x. 2 root root 4096 7月 24 09:02 bin</span><br><span class="line">drwxr-xr-x. 2 root root 77 7月 24 09:02 conf</span><br><span class="line">drwxr-xr-x. 5 root root 4096 7月 24 09:02 docs</span><br><span class="line">drwxr-xr-x. 2 root root 4096 7月 24 09:02 lib</span><br><span class="line">-rw-r--r--. 1 root root 11358 7月 24 09:02 LICENSE.txt</span><br><span class="line">-rw-r--r--. 1 root root 432 7月 24 09:02 NOTICE.txt</span><br><span class="line">-rw-r--r--. 1 root root 1963 7月 24 09:02 README.md</span><br><span class="line">-rw-r--r--. 1 root root 3166 7月 24 09:02 README_packaging.md</span><br></pre></td></tr></table></figure>
<ul>
<li>bin :放置运行脚本和工具脚本</li>
<li>conf :zookeeper 默认读取配置的目录,里面会有默认的配置文件</li>
<li>docs :zookeeper 相关的文档</li>
<li>lib :zookeeper 核心的 jar</li>
<li>logs :zookeeper 日志,运行后才会生成</li>
</ul>
<h4 id="2-1-3-配置-Zookeeper"><a href="#2-1-3-配置-Zookeeper" class="headerlink" title="2.1.3 配置 Zookeeper"></a>2.1.3 配置 Zookeeper</h4><h5 id="创建数据缓存目录-data"><a href="#创建数据缓存目录-data" class="headerlink" title="创建数据缓存目录 data"></a>创建数据缓存目录 data</h5><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[root@192 zookeeper]# mkdir data</span><br><span class="line">[root@192 zookeeper]# cd data</span><br><span class="line">[root@192 data]# pwd</span><br><span class="line">/usr/local/zookeeper/data</span><br></pre></td></tr></table></figure>
<h5 id="创建-zoo-cfg-的配置文件"><a href="#创建-zoo-cfg-的配置文件" class="headerlink" title="创建 zoo.cfg 的配置文件"></a>创建 zoo.cfg 的配置文件</h5><p>Zookeeper 在启动时默认的去 conf 目录下查找一个名称为 <code>zoo.cfg</code> 的配置文件。但在 Zookeeper 应用目录的子目录 conf 中只有一个配置文件模板:zoo_sample.cfg 。因此需要拷贝一份配置文件,并命名为 zoo.cfg 。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[root@192 conf]# cp zoo_sample.cfg zoo.cfg</span><br></pre></td></tr></table></figure>
<h5 id="修改配置文件"><a href="#修改配置文件" class="headerlink" title="修改配置文件"></a>修改配置文件</h5><p>需要将数据缓存目录修改为 <code>/usr/local/zookeeper/data</code> 目录。</p>
<img src="https://gitee.com/azhenyoo/img/raw/master/Zookeeper/设置缓存目录.png" alt="设置缓存目录" style="zoom: 100%;" />
<h4 id="2-1-4-启动-Zookeeper"><a href="#2-1-4-启动-Zookeeper" class="headerlink" title="2.1.4 启动 Zookeeper"></a>2.1.4 启动 Zookeeper</h4><p>进入 Zookeeper 的 bin 目录,执行启动命令,</p>
<ul>
<li>默认加载配置文件 :<code>./zkServer.sh start</code>,默认会去 conf 目录下加载 zoo.cfg 配置文件</li>
<li>指定加载配置文件 :./zkServer.sh start 配置文件路径</li>
</ul>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[root@192 conf]# cd ../bin</span><br><span class="line">[root@192 bin]# ./zkServer.sh start</span><br><span class="line">ZooKeeper JMX enabled by default</span><br><span class="line">Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg</span><br><span class="line">Starting zookeeper ... STARTED</span><br></pre></td></tr></table></figure>
<p>启动后可以使用 <code>./zkServer.sh status</code> 命令来查看 Zookeeper 的状态</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[root@192 bin]# ./zkServer.sh status</span><br><span class="line">ZooKeeper JMX enabled by default</span><br><span class="line">Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg</span><br><span class="line">Client port found: 2181. Client address: localhost.</span><br><span class="line">Mode: standalone</span><br></pre></td></tr></table></figure>
<p>关闭 Zookeeper,需要使用 <code>./zkServer.sh stop</code> 命令</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[root@192 bin]# ./zkServer.sh stop</span><br><span class="line">ZooKeeper JMX enabled by default</span><br><span class="line">Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg</span><br><span class="line">Stopping zookeeper ... STOPPED</span><br></pre></td></tr></table></figure>
<h4 id="2-1-5-客户端连接-Zookeeper"><a href="#2-1-5-客户端连接-Zookeeper" class="headerlink" title="2.1.5 客户端连接 Zookeeper"></a>2.1.5 客户端连接 Zookeeper</h4><h5 id="连接方式一"><a href="#连接方式一" class="headerlink" title="连接方式一"></a>连接方式一</h5><p>bin/zkCli.sh 命令,默认连接地址为本机地址,默认连接端口为 2181</p>
<h5 id="连接方式二"><a href="#连接方式二" class="headerlink" title="连接方式二"></a>连接方式二</h5><p>bin/zkCli.sh -server ip:port 命令,连接指定 IP 地址与端口</p>
<h3 id="2-2-集群版"><a href="#2-2-集群版" class="headerlink" title="2.2 集群版"></a>2.2 集群版</h3><h4 id="2-2-1-Zookeeper-集群中的角色"><a href="#2-2-1-Zookeeper-集群中的角色" class="headerlink" title="2.2.1 Zookeeper 集群中的角色"></a>2.2.1 Zookeeper 集群中的角色</h4><p>Zookeeper 集群中的角色主要有以下三类:领导者、跟随者以及观察者。</p>
<ol>
<li>领导者(Leader) :领导者负责进行投票的发起和决议,更新系统状态</li>
<li>学习者(Learner)<ul>
<li>跟随者(Follower) :跟随者用于接收客户端请求并向客户端返回结果,在选主过程中参与投票</li>
<li>观察者(Observer) :观察者可以接受客户端连接,将写请求转发给领导者节点,但观察者不参加投票过程,只同步领导者的状态。观察者的目的是<strong>为了扩展系统,提高读取速度</strong> </li>
</ul>
</li>
<li>客户端(Client) :请求发起方</li>
</ol>
<h4 id="2-2-2-集群安装"><a href="#2-2-2-集群安装" class="headerlink" title="2.2.2 集群安装"></a>2.2.2 集群安装</h4><p>使用 3 个 Zookeeper 应用搭建一个伪集群。应用部署位置是:192.168.0.119。客户端监听端口分别为:2181、2182、2183。投票选举端口分别为 2881/3881、2882/3882、2883/3883。</p>
<h5 id="创建集群目录"><a href="#创建集群目录" class="headerlink" title="创建集群目录"></a>创建集群目录</h5><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[root@192 local]# mkdir zookeepercluster</span><br></pre></td></tr></table></figure>
<h5 id="拷贝-Zookeeper-文件"><a href="#拷贝-Zookeeper-文件" class="headerlink" title="拷贝 Zookeeper 文件"></a>拷贝 Zookeeper 文件</h5><ol>
<li>拷贝 zookeeper 文件到 /usr/local/zookeepercluster/ 目录下并重命名为 zookeeper01</li>
<li>在 zookeeper01 应用目录中创建 data 目录,用于缓存应用运行数据,</li>
<li>在 conf 目录中创建配置文件 zoo.cfg</li>
</ol>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[root@192 temp]# cp apache-zookeeper-3.6.1-bin /usr/local/zookeepercluster/ -r</span><br><span class="line">[root@192 temp]# cd /usr/local/zookeepercluster/</span><br><span class="line">[root@192 zookeepercluster]# ll</span><br><span class="line">总用量 0</span><br><span class="line">drwxr-xr-x. 6 root root 133 7月 24 10:08 apache-zookeeper-3.6.1-bin</span><br><span class="line">[root@192 zookeepercluster]# mv apache-zookeeper-3.6.1-bin/ zookeeper01</span><br><span class="line">[root@192 zookeepercluster]# cd zookeeper01/</span><br><span class="line">[root@192 zookeeper01]# mkdir data</span><br><span class="line">[root@192 zookeeper01]# cd conf</span><br><span class="line">[root@192 conf]# cp zoo_sample.cfg zoo.cfg</span><br></pre></td></tr></table></figure>
<h5 id="复制应用"><a href="#复制应用" class="headerlink" title="复制应用"></a>复制应用</h5><p>复制两份 Zookeeper 应用,用于模拟集群中的三个节点</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[root@192 zookeepercluster]# cp -r zookeeper01/ zookeeper02</span><br><span class="line">[root@192 zookeepercluster]# cp -r zookeeper01/ zookeeper03</span><br><span class="line">[root@192 zookeepercluster]# ll</span><br><span class="line">总用量 0</span><br><span class="line">drwxr-xr-x. 7 root root 145 7月 24 10:12 zookeeper01</span><br><span class="line">drwxr-xr-x. 7 root root 145 7月 24 10:17 zookeeper02</span><br><span class="line">drwxr-xr-x. 7 root root 145 7月 24 10:17 zookeeper03</span><br></pre></td></tr></table></figure>
<h4 id="2-2-3-集群配置"><a href="#2-2-3-集群配置" class="headerlink" title="2.2.3 集群配置"></a>2.2.3 集群配置</h4><h5 id="设置数据缓存路径"><a href="#设置数据缓存路径" class="headerlink" title="设置数据缓存路径"></a>设置数据缓存路径</h5><p>修改三个 Zookeeper 的配置文件,设置数据缓存路径</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[root@192 zookeepercluster]# vim zookeeper01/conf/zoo.cfg</span><br><span class="line">[root@192 zookeepercluster]# vim zookeeper02/conf/zoo.cfg</span><br><span class="line">[root@192 zookeepercluster]# vim zookeeper03/conf/zoo.cfg</span><br></pre></td></tr></table></figure>
<h5 id="提供应用唯一标识"><a href="#提供应用唯一标识" class="headerlink" title="提供应用唯一标识"></a>提供应用唯一标识</h5><p>在 Zookeeper 集群中,每个节点需要一个微以标识。这个唯一标识要求是自然数,且唯一标识的保存位置是:数据缓存目录的 myid 文件中。其中“数据缓存目录”为配置文件 zoo.cfg 中的配置参数。本环境中使用 1、2、3 作为每个节点的唯一标识。</p>
<p>可以使用 <code>echo 唯一标识 >> myid</code> 命令。echo 命令为回声命令,系统会将命令发送的数据返回。‘>>’为定位,代表系统回声数据指定发送到什么位置。此命令代表系统回声数据发送到 myid 文件中,如果没有文件则创建文件。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[root@192 zookeepercluster]# echo 1 >> zookeeper01/data/myid</span><br><span class="line">[root@192 zookeepercluster]# echo 2 >> zookeeper02/data/myid</span><br><span class="line">[root@192 zookeepercluster]# echo 3 >> zookeeper03/data/myid</span><br><span class="line">[root@192 zookeepercluster]# cat zookeeper01/data/myid</span><br><span class="line">1</span><br><span class="line">[root@192 zookeepercluster]# cat zookeeper02/data/myid</span><br><span class="line">2</span><br><span class="line">[root@192 zookeepercluster]# cat zookeeper03/data/myid</span><br><span class="line">3</span><br></pre></td></tr></table></figure>
<h5 id="设置监听客户端、投票选举端口"><a href="#设置监听客户端、投票选举端口" class="headerlink" title="设置监听客户端、投票选举端口"></a>设置监听客户端、投票选举端口</h5><ul>
<li>clientProt = 2181/2182/2183</li>
<li>server.1=192.168.0.119:2281:3381<br>server.2=192.168.0.119:2282:3382<br>server.3=192.168.0.119:2283:3383</li>
</ul>
<h4 id="2-2-4-启动-Zookeeper"><a href="#2-2-4-启动-Zookeeper" class="headerlink" title="2.2.4 启动 Zookeeper"></a>2.2.4 启动 Zookeeper</h4><h5 id="分别启动Zookeeper-应用"><a href="#分别启动Zookeeper-应用" class="headerlink" title="分别启动Zookeeper 应用"></a>分别启动Zookeeper 应用</h5><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[root@192 zookeepercluster]# zookeeper01/bin/zkServer.sh start</span><br><span class="line">ZooKeeper JMX enabled by default</span><br><span class="line">Using config: /usr/local/zookeepercluster/zookeeper01/bin/../conf/zoo.cfg</span><br><span class="line">Starting zookeeper ... STARTED</span><br><span class="line">[root@192 zookeepercluster]# zookeeper02/bin/zkServer.sh start</span><br><span class="line">ZooKeeper JMX enabled by default</span><br><span class="line">Using config: /usr/local/zookeepercluster/zookeeper02/bin/../conf/zoo.cfg</span><br><span class="line">Starting zookeeper ... STARTED</span><br><span class="line">[root@192 zookeepercluster]# zookeeper03/bin/zkServer.sh start</span><br><span class="line">ZooKeeper JMX enabled by default</span><br><span class="line">Using config: /usr/local/zookeepercluster/zookeeper03/bin/../conf/zoo.cfg</span><br><span class="line">Starting zookeeper ... STARTED</span><br></pre></td></tr></table></figure>
<h5 id="查看-Zookeeper-应用的状态"><a href="#查看-Zookeeper-应用的状态" class="headerlink" title="查看 Zookeeper 应用的状态"></a>查看 Zookeeper 应用的状态</h5><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[root@192 zookeepercluster]# zookeeper01/bin/zkServer.sh status</span><br><span class="line">ZooKeeper JMX enabled by default</span><br><span class="line">Using config: /usr/local/zookeepercluster/zookeeper01/bin/../conf/zoo.cfg</span><br><span class="line">Client port found: 2181. Client address: localhost.</span><br><span class="line">Mode: follower</span><br><span class="line">[root@192 zookeepercluster]# zookeeper02/bin/zkServer.sh status</span><br><span class="line">ZooKeeper JMX enabled by default</span><br><span class="line">Using config: /usr/local/zookeepercluster/zookeeper02/bin/../conf/zoo.cfg</span><br><span class="line">Client port found: 2182. Client address: localhost.</span><br><span class="line">Mode: follower</span><br><span class="line">[root@192 zookeepercluster]# zookeeper03/bin/zkServer.sh status</span><br><span class="line">ZooKeeper JMX enabled by default</span><br><span class="line">Using config: /usr/local/zookeepercluster/zookeeper03/bin/../conf/zoo.cfg</span><br><span class="line">Client port found: 2183. Client address: localhost.</span><br><span class="line">Mode: leader</span><br></pre></td></tr></table></figure>
<h4 id="2-2-5-编写集群服务脚本"><a href="#2-2-5-编写集群服务脚本" class="headerlink" title="2.2.5 编写集群服务脚本"></a>2.2.5 编写集群服务脚本</h4><h5 id="编写启动脚本"><a href="#编写启动脚本" class="headerlink" title="编写启动脚本"></a>编写启动脚本</h5><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[root@192 zookeepercluster]# vim start.sh</span><br></pre></td></tr></table></figure>
<img src="https://gitee.com/azhenyoo/img/raw/master/Zookeeper/编写启动解脚本.png" alt="编写启动解脚本" style="zoom: 100%;" />
<h5 id="编写关闭脚本"><a href="#编写关闭脚本" class="headerlink" title="编写关闭脚本"></a>编写关闭脚本</h5><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[root@192 zookeepercluster]# vim stop.sh</span><br></pre></td></tr></table></figure>
<img src="https://gitee.com/azhenyoo/img/raw/master/Zookeeper/编写关闭脚本.png" alt="编写关闭脚本" style="zoom: 100%;" />
<h5 id="编写重启脚本"><a href="#编写重启脚本" class="headerlink" title="编写重启脚本"></a>编写重启脚本</h5><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[root@192 zookeepercluster]# vim restart.sh</span><br></pre></td></tr></table></figure>
<img src="https://gitee.com/azhenyoo/img/raw/master/Zookeeper/编写重启脚本.png" alt="编写重启脚本" style="zoom: 100%;" />
<h5 id="给脚本授权"><a href="#给脚本授权" class="headerlink" title="给脚本授权"></a>给脚本授权</h5><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[root@192 zookeepercluster]# chmod 777 start.sh</span><br><span class="line">[root@192 zookeepercluster]# chmod 777 stop.sh</span><br><span class="line">[root@192 zookeepercluster]# chmod 777 restart.sh</span><br></pre></td></tr></table></figure>
<h4 id="2-2-6-连接-Zookeeper-集群"><a href="#2-2-6-连接-Zookeeper-集群" class="headerlink" title="2.2.6 连接 Zookeeper 集群"></a>2.2.6 连接 Zookeeper 集群</h4><p>可以使用任何节点中的客户端工具连接集群中的任何节点,例如:我们利用 zookeeper01 中的客户端工具连接 zookeeper03,是可以连接成功的。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[root@192 zookeepercluster]# zookeeper01/bin/zkCli.sh -server 192.168.0.119:2183</span><br></pre></td></tr></table></figure>
<img src="https://gitee.com/azhenyoo/img/raw/master/Zookeeper/连接Zookeeper集群.png" alt="连接Zookeeper集群" style="zoom: 100%;" />
<h2 id="三、Zookeeper-常用命令"><a href="#三、Zookeeper-常用命令" class="headerlink" title="三、Zookeeper 常用命令"></a>三、Zookeeper 常用命令</h2><h3 id="3-1-ls-命令"><a href="#3-1-ls-命令" class="headerlink" title="3.1 ls 命令"></a>3.1 ls 命令</h3><ul>
<li>语法 :<code>ls [-s] [-w] [-R] path</code> </li>
</ul>
<p>使用 ls 命令查看 zookeeper 中的内容。<strong>在 Zookeeper 控制端中,没有默认列表功能,必须指定要列表资源的位置</strong>。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[zk: 192.168.0.119:2183(CONNECTED) 0] ls /</span><br><span class="line">[zookeeper]</span><br></pre></td></tr></table></figure>
<h3 id="3-2-create-命令"><a href="#3-2-create-命令" class="headerlink" title="3.2 create 命令"></a>3.2 create 命令</h3><ul>
<li>语法 :<code>create [-s] [-e] [-c] [-t ttl] path [data] [acl]</code> </li>
</ul>
<p>使用 create 命令创建一个新的 Znode。创建节点,如:</p>
<ul>
<li>create /test 123 创建一个持久化节点 /test ,节点携带数据信息 123</li>
<li>create -e /test 123 创建一个临时节点 /test,携带数据为 123,临时节点只在当前会话生命周期中有效,会话结束节点自动删除</li>
<li>create -s /test 123 创建一个顺序节点 /test,携带数据为 123,创建的顺序节点由 Zookeeper 自动为节点增加后缀信息,如 -/test00000001 </li>
</ul>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[zk: 192.168.0.119:2183(CONNECTED) 4] create /zj azhen</span><br><span class="line">Created /zj</span><br><span class="line">[zk: 192.168.0.119:2183(CONNECTED) 5] ls /</span><br><span class="line">[zj, zookeeper]</span><br></pre></td></tr></table></figure>
<h3 id="3-3-get-命令"><a href="#3-3-get-命令" class="headerlink" title="3.3 get 命令"></a>3.3 get 命令</h3><ul>
<li>语法 :<code>get [-s] /path</code> </li>
</ul>
<p>get 命令获取 znode 中的数据,-s 查看 znode 详细信息。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[zk: 192.168.0.119:2183(CONNECTED) 6] get -s /zj</span><br><span class="line">azhen</span><br><span class="line">cZxid = 0x600000002</span><br><span class="line">ctime = Fri Jul 24 12:49:13 CST 2020</span><br><span class="line">mZxid = 0x600000002</span><br><span class="line">mtime = Fri Jul 24 12:49:13 CST 2020</span><br><span class="line">pZxid = 0x600000002</span><br><span class="line">cversion = 0</span><br><span class="line">dataVersion = 0</span><br><span class="line">aclVersion = 0</span><br><span class="line">ephemeralOwner = 0x0</span><br><span class="line">dataLength = 5</span><br><span class="line">numChildren = 0</span><br></pre></td></tr></table></figure>
<ul>
<li>azhen :存放的数据</li>
<li>cZxid :创建时 zxid(znode 每次改变时递增的事务 id)</li>
<li>ctime :创建时间戳</li>
<li>mZxid :最近一个更近的 zxid</li>
<li>mtime :最近一次更新额时间戳</li>
<li>pZxid :子节点的 zxid,若没有子节点,则返回当前节点的 zxid</li>
<li>cversion :子节点更新次数</li>
<li>dataVersion :节点数据更新次数</li>
<li>aclVersion :节点 ACL(授权信息)的更新次数</li>
<li>ephemeralOwner :如果该节点为 ehpemeral 临时节点,ephemeralOwner 值表示与该节点绑定的 session id。如果该节点不是 ehpemeral 临时节点,ephemeralOwner 值为0</li>
<li>dataLength :节点数据的字节数</li>
<li>numChildren :子节点数量</li>
</ul>
<h3 id="3-4-set-命令"><a href="#3-4-set-命令" class="headerlink" title="3.4 set 命令"></a>3.4 set 命令</h3><ul>
<li>语法 :set -path [data]</li>
</ul>
<p>set 命令添加或修改 znode 中的值。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[zk: 192.168.0.119:2183(CONNECTED) 7] set /zj chengyan</span><br><span class="line">[zk: 192.168.0.119:2183(CONNECTED) 8] get /zj</span><br><span class="line">chengyan</span><br></pre></td></tr></table></figure>
<h3 id="3-5-delete-命令"><a href="#3-5-delete-命令" class="headerlink" title="3.5 delete 命令"></a>3.5 delete 命令</h3><ul>
<li>语法 :delete /path</li>
</ul>
<p>delete 命令用于删除 znode 节点。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[zk: 192.168.0.119:2183(CONNECTED) 9] delete /zj</span><br><span class="line">[zk: 192.168.0.119:2183(CONNECTED) 10] ls /</span><br><span class="line">[zookeeper]</span><br></pre></td></tr></table></figure>
<h2 id="四、使用-Java-API-操作-Zookeeper"><a href="#四、使用-Java-API-操作-Zookeeper" class="headerlink" title="四、使用 Java API 操作 Zookeeper"></a>四、使用 Java API 操作 Zookeeper</h2><h3 id="4-1-创建项目"><a href="#4-1-创建项目" class="headerlink" title="4.1 创建项目"></a>4.1 创建项目</h3><h4 id="4-1-1-新建-Maven-项目"><a href="#4-1-1-新建-Maven-项目" class="headerlink" title="4.1.1 新建 Maven 项目"></a>4.1.1 新建 Maven 项目</h4><p>使用 IDEA 创建一个 Maven 工程</p>
<h4 id="4-1-2-修改-pom-文件"><a href="#4-1-2-修改-pom-文件" class="headerlink" title="4.1.2 修改 pom 文件"></a>4.1.2 修改 pom 文件</h4><p>添加 zookeeper 坐标依赖,依赖的版本需要和安装 zookeeper 的版本一致</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="comment"><!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper --></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.apache.zookeeper<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>zookeeper<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>3.6.1<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
<h3 id="4-2-创建连接并添加节点"><a href="#4-2-创建连接并添加节点" class="headerlink" title="4.2 创建连接并添加节点"></a>4.2 创建连接并添加节点</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ZnodeDemo</span> <span class="keyword">implements</span> <span class="title">Watcher</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> KeeperException, InterruptedException, IOException </span>{</span><br><span class="line"> <span class="comment">// 创建连接zookeeper对象</span></span><br><span class="line"> ZooKeeper zooKeeper = <span class="keyword">new</span> ZooKeeper(<span class="string">"192.168.0.119:2181,192.168.0.119:2182,192.168.0.119:2183"</span>, <span class="number">15000</span>, <span class="keyword">new</span> ZnodeDemo());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 事件通知回调</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> watchedEvent</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">process</span><span class="params">(WatchedEvent watchedEvent)</span> </span>{</span><br><span class="line"> <span class="comment">// 获取连接事件</span></span><br><span class="line"> <span class="keyword">if</span>(watchedEvent.getState() == Event.KeeperState.SyncConnected) {</span><br><span class="line"> System.out.println(<span class="string">"连接成功!"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="4-3-znode-的数据操作"><a href="#4-3-znode-的数据操作" class="headerlink" title="4.3 znode 的数据操作"></a>4.3 znode 的数据操作</h3><h4 id="4-3-1-创建-znode"><a href="#4-3-1-创建-znode" class="headerlink" title="4.3.1 创建 znode"></a>4.3.1 创建 znode</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 创建 ZNode</span></span><br><span class="line">String path = zooKeeper.create(<span class="string">"/zj"</span>, <span class="string">"浙江"</span>.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);</span><br><span class="line">System.out.println(path);</span><br></pre></td></tr></table></figure>
<h4 id="4-3-2-获取指定节点的数据"><a href="#4-3-2-获取指定节点的数据" class="headerlink" title="4.3.2 获取指定节点的数据"></a>4.3.2 获取指定节点的数据</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 获取指定节点数据</span></span><br><span class="line"><span class="keyword">byte</span>[] data = zooKeeper.getData(<span class="string">"/zj"</span>, <span class="keyword">new</span> ZnodeDemo(), <span class="keyword">new</span> Stat());</span><br><span class="line">System.out.println(<span class="keyword">new</span> String(data)); <span class="comment">// azhen</span></span><br><span class="line">System.out.println(String.valueOf(data)); <span class="comment">// [B@782830e</span></span><br></pre></td></tr></table></figure>
<h4 id="4-3-3-获取所有子节点中的数据"><a href="#4-3-3-获取所有子节点中的数据" class="headerlink" title="4.3.3 获取所有子节点中的数据"></a>4.3.3 获取所有子节点中的数据</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 获取指定节点子节点中的数据</span></span><br><span class="line">List<String> children = zooKeeper.getChildren(<span class="string">"/zj"</span>, <span class="keyword">new</span> ZnodeDemo());</span><br><span class="line"><span class="keyword">for</span> (String child : children) {</span><br><span class="line"> <span class="keyword">byte</span>[] data = zooKeeper.getData(<span class="string">"/zj/"</span> + child, <span class="keyword">new</span> ZnodeDemo(), <span class="keyword">new</span> Stat());</span><br><span class="line"> System.out.println(<span class="keyword">new</span> String(data));</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="4-3-4-设置-znode-中的值"><a href="#4-3-4-设置-znode-中的值" class="headerlink" title="4.3.4 设置 znode 中的值"></a>4.3.4 设置 znode 中的值</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Stat stat = zooKeeper.setData(<span class="string">"/zj"</span>, <span class="string">"浙江省"</span>.getBytes(), -<span class="number">1</span>);</span><br><span class="line">System.out.println(stat);</span><br></pre></td></tr></table></figure>
<h4 id="4-3-5-删除-znode"><a href="#4-3-5-删除-znode" class="headerlink" title="4.3.5 删除 znode"></a>4.3.5 删除 znode</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">zooKeeper.delete(<span class="string">"/zj/shaoxing"</span>, -<span class="number">1</span>);</span><br></pre></td></tr></table></figure>
<h2 id="五、基于-RMI-实现远程方法调用"><a href="#五、基于-RMI-实现远程方法调用" class="headerlink" title="五、基于 RMI 实现远程方法调用"></a>五、基于 RMI 实现远程方法调用</h2><h3 id="5-1-RMI-简介"><a href="#5-1-RMI-简介" class="headerlink" title="5.1 RMI 简介"></a>5.1 RMI 简介</h3><p>RMI(Remote Method Invocation,远程方法调用) 是从 JDK1.2 推出的功能,它可以实现在一个 Java 应用中可以像调用本地方法一样调用另一个服务器中 Java 应用(JVM) 中的内容。</p>
<p>RMI 是 Java 语言的远程调用,无法实现跨语言。</p>
<img src="https://gitee.com/azhenyoo/img/raw/master/Zookeeper/RMI.jpg" alt="RMI" style="zoom:100%;" />
<p>Registry(注册表) 是放置所有服务器对象的命名空间。每次服务端创建一个对象时,它都会使用 <code>bind()</code> 或 <code>rebind()</code> 方法注册该对象。这些是使用称为绑定名称的唯一名称注册的。</p>
<p>要调用远程对象,客户端需要该对象的引用,即使用 <code>lookup()</code> 方法通过服务器端绑定的名称从注册表中获取对象。</p>
<h3 id="5-2-RMI-常用-API"><a href="#5-2-RMI-常用-API" class="headerlink" title="5.2 RMI 常用 API"></a>5.2 RMI 常用 API</h3><h4 id="5-2-1-Remote-接口"><a href="#5-2-1-Remote-接口" class="headerlink" title="5.2.1 Remote 接口"></a>5.2.1 Remote 接口</h4><p>java.rmi.Remote,Remote 定义了此接口为远程调用接口。如果接口被外部调用,需要继承此接口。</p>
<h4 id="5-2-2-RemoteException-类"><a href="#5-2-2-RemoteException-类" class="headerlink" title="5.2.2 RemoteException 类"></a>5.2.2 RemoteException 类</h4><p>java.rmi.RemoteException,继承了 Remote 接口的接口,如果方法是允许被远程调用的,需要抛出此异常。</p>
<h4 id="5-2-3-UnicastRemoteObject-类"><a href="#5-2-3-UnicastRemoteObject-类" class="headerlink" title="5.2.3 UnicastRemoteObject 类"></a>5.2.3 UnicastRemoteObject 类</h4><p>java.rmi.server.UnicastRemoteObject,此类实现了 Remote 接口和 Serializable 接口,自定义接口实现类除了实现自定义接口还需要继承此类。</p>
<h4 id="5-2-4-LocateRegistry-类"><a href="#5-2-4-LocateRegistry-类" class="headerlink" title="5.2.4 LocateRegistry 类"></a>5.2.4 LocateRegistry 类</h4><p>java.rmi.registry.LocateRegistry,可以通过 LocateRegistry 在本机上创建 Registry,通过特定的端口就可以访问这个 Registry。</p>
<h4 id="5-2-5-Naming-类"><a href="#5-2-5-Naming-类" class="headerlink" title="5.2.5 Naming 类"></a>5.2.5 Naming 类</h4><p>java.rmi.Naming,Naming 定义了发布内容可访问 RMI 名称,也是通过 Naming 获取到指定的远程方法。</p>
<h3 id="5-3-创建-server-端"><a href="#5-3-创建-server-端" class="headerlink" title="5.3 创建 server 端"></a>5.3 创建 server 端</h3><h4 id="5-3-1-创建项目"><a href="#5-3-1-创建项目" class="headerlink" title="5.3.1 创建项目"></a>5.3.1 创建项目</h4><p>新建空项目 RMI-demo01,然后在空项目中新建 Maven 模块 rmi-server。</p>
<h4 id="5-3-2-创建目录结构"><a href="#5-3-2-创建目录结构" class="headerlink" title="5.3.2 创建目录结构"></a>5.3.2 创建目录结构</h4><img src="https://gitee.com/azhenyoo/img/raw/master/Zookeeper/RMI-创建目录结构.png" alt="RMI-创建目录结构" style="zoom: 100%;" />
<h4 id="5-3-3-DemoService"><a href="#5-3-3-DemoService" class="headerlink" title="5.3.3 DemoService"></a>5.3.3 DemoService</h4><p>定义允许远程调用的接口 DemoService.java,该接口必须实现 <code>Remote</code> 接口,并且允许被远程调用的方法必须要抛出 <code>RemoteException</code> 异常。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">DemoService</span> <span class="keyword">extends</span> <span class="title">Remote</span> </span>{</span><br><span class="line"> <span class="function">String <span class="title">demo</span><span class="params">(String str)</span> <span class="keyword">throws</span> RemoteException</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="5-3-4-DemoServiceImpl"><a href="#5-3-4-DemoServiceImpl" class="headerlink" title="5.3.4 DemoServiceImpl"></a>5.3.4 DemoServiceImpl</h4><p>定义允许远程调用接口实现现类 DemoServiceImpl.java,该类必须要继承 <code>UnicastRemoteObject</code> 类,需要添加构造方法并将方法的修饰符修改为 <code>public</code>。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DemoServiceImpl</span> <span class="keyword">extends</span> <span class="title">UnicastRemoteObject</span> <span class="keyword">implements</span> <span class="title">DemoService</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">DemoServiceImpl</span><span class="params">()</span> <span class="keyword">throws</span> RemoteException </span>{}</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">demo</span><span class="params">(String str)</span> <span class="keyword">throws</span> RemoteException </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Hello RMI, "</span> + str;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="5-3-5-DemoServer"><a href="#5-3-5-DemoServer" class="headerlink" title="5.3.5 DemoServer"></a>5.3.5 DemoServer</h4><p>定义服务端的启动的主方法类 DemoServer.java。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DemoServer</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> RemoteException, AlreadyBoundException, MalformedURLException </span>{</span><br><span class="line"> <span class="comment">// 将对象实例化</span></span><br><span class="line"> DemoService demoService = <span class="keyword">new</span> DemoServiceImpl();</span><br><span class="line"> <span class="comment">// 创建本地注册表</span></span><br><span class="line"> LocateRegistry.createRegistry(<span class="number">8888</span>);</span><br><span class="line"> <span class="comment">// 将对象绑定到注册表</span></span><br><span class="line"> Naming.bind(<span class="string">"rmi://localhost:8888/demoService"</span>, demoService);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="5-4-创建-client-端"><a href="#5-4-创建-client-端" class="headerlink" title="5.4 创建 client 端"></a>5.4 创建 client 端</h3><h4 id="5-4-1-创建项目"><a href="#5-4-1-创建项目" class="headerlink" title="5.4.1 创建项目"></a>5.4.1 创建项目</h4><p>在 RMI-demo01 项目中新建 Maven 模块 rmi-client。</p>
<h4 id="5-4-2-复制-DemoService"><a href="#5-4-2-复制-DemoService" class="headerlink" title="5.4.2 复制 DemoService"></a>5.4.2 复制 DemoService</h4><p>将服务端的 DemoService.java 接口复制到客户端中。</p>
<img src="https://gitee.com/azhenyoo/img/raw/master/Zookeeper/DemoService.png" alt="DemoService" style="zoom: 100%;" />
<h4 id="5-4-3-DemoClient"><a href="#5-4-3-DemoClient" class="headerlink" title="5.4.3 DemoClient"></a>5.4.3 DemoClient</h4><p>在 <code>com.azhen</code> 包下创建客户端主方法类 DemoClient.java。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DemoClient</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> RemoteException, NotBoundException, MalformedURLException </span>{</span><br><span class="line"> DemoService service = (DemoService) Naming.lookup(<span class="string">"rmi://localhost:8888/demoService"</span>);</span><br><span class="line"> String string = service.demo(<span class="string">"server!"</span>);</span><br><span class="line"> System.out.println(string);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="六、使用-Zookeeper-作为注册中心实现-RPC"><a href="#六、使用-Zookeeper-作为注册中心实现-RPC" class="headerlink" title="六、使用 Zookeeper 作为注册中心实现 RPC"></a>六、使用 Zookeeper 作为注册中心实现 RPC</h2><h3 id="6-1-创建-server-端"><a href="#6-1-创建-server-端" class="headerlink" title="6.1 创建 server 端"></a>6.1 创建 server 端</h3><h4 id="6-1-1-创建项目"><a href="#6-1-1-创建项目" class="headerlink" title="6.1.1 创建项目"></a>6.1.1 创建项目</h4><p>新建空项目 rpc-demo01,然后在空项目中新建 Maven 模块 rpc-server。</p>
<h4 id="6-1-2-修改-pom-文件"><a href="#6-1-2-修改-pom-文件" class="headerlink" title="6.1.2 修改 pom 文件"></a>6.1.2 修改 pom 文件</h4><p>添加 zookeeper 的坐标依赖。</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.apache.zookeeper<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>zookeeper<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>3.6.1<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
<h4 id="6-1-3-创建目录结构"><a href="#6-1-3-创建目录结构" class="headerlink" title="6.1.3 创建目录结构"></a>6.1.3 创建目录结构</h4><img src="https://gitee.com/azhenyoo/img/raw/master/Zookeeper/RPC-创建服务端目录结构.png" alt="RPC-创建服务端目录结构" style="zoom: 100%;" />
<h4 id="6-1-4-编码"><a href="#6-1-4-编码" class="headerlink" title="6.1.4 编码"></a>6.1.4 编码</h4><h5 id="UserService"><a href="#UserService" class="headerlink" title="UserService"></a>UserService</h5><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UserService</span> <span class="keyword">extends</span> <span class="title">Remote</span> </span>{</span><br><span class="line"> <span class="function">String <span class="title">findUsers</span><span class="params">(String str)</span> <span class="keyword">throws</span> RemoteException</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h5 id="UserServiceImpl"><a href="#UserServiceImpl" class="headerlink" title="UserServiceImpl"></a>UserServiceImpl</h5><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserServiceImpl</span> <span class="keyword">extends</span> <span class="title">UnicastRemoteObject</span> <span class="keyword">implements</span> <span class="title">UserService</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">UserServiceImpl</span><span class="params">()</span> <span class="keyword">throws</span> RemoteException </span>{ }</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">findUsers</span><span class="params">(String str)</span> <span class="keyword">throws</span> RemoteException </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Hello Zookeeper, "</span> + str;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h5 id="ZookeeperServer"><a href="#ZookeeperServer" class="headerlink" title="ZookeeperServer"></a>ZookeeperServer</h5><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ZookeeperServer</span> <span class="keyword">implements</span> <span class="title">Watcher</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException, AlreadyBoundException, KeeperException, InterruptedException </span>{</span><br><span class="line"> UserService userService = <span class="keyword">new</span> UserServiceImpl();</span><br><span class="line"> LocateRegistry.createRegistry(<span class="number">8888</span>);</span><br><span class="line"> String url = <span class="string">"rmi://localhost:8888/userService"</span>;</span><br><span class="line"> Naming.bind(url, userService);</span><br><span class="line"> <span class="comment">// 创建集群url</span></span><br><span class="line"> String zookeeperUrl = <span class="string">"192.168.0.119:2181,192.168.0.119:2182,192.168.0.119:2183"</span>;</span><br><span class="line"> <span class="comment">// 将url信息放到zookeeper的节点中</span></span><br><span class="line"> ZooKeeper zooKeeper = <span class="keyword">new</span> ZooKeeper(zookeeperUrl, <span class="number">20000</span>, <span class="keyword">new</span> ZookeeperServer());</span><br><span class="line"> zooKeeper.create(<span class="string">"/rpc/user"</span>, url.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);</span><br><span class="line"> System.out.println(<span class="string">"服务发布成功, /rpc/user["</span>+ url + <span class="string">"]"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">process</span><span class="params">(WatchedEvent watchedEvent)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span>(watchedEvent.getState() == Event.KeeperState.SyncConnected) {</span><br><span class="line"> System.out.println(<span class="string">"连接成功"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="6-1-5-测试服务端"><a href="#6-1-5-测试服务端" class="headerlink" title="6.1.5 测试服务端"></a>6.1.5 测试服务端</h4><img src="https://gitee.com/azhenyoo/img/raw/master/Zookeeper/测试服务端.png" alt="测试服务端" style="zoom: 100%;" />
<h3 id="6-2-创建-client-端"><a href="#6-2-创建-client-端" class="headerlink" title="6.2 创建 client 端"></a>6.2 创建 client 端</h3><h4 id="6-2-1-创建项目"><a href="#6-2-1-创建项目" class="headerlink" title="6.2.1 创建项目"></a>6.2.1 创建项目</h4><p>在 rpc-demo01 项目中新建 Maven 模块 rpc-client。</p>
<h4 id="6-2-2-修改-pom-文件"><a href="#6-2-2-修改-pom-文件" class="headerlink" title="6.2.2 修改 pom 文件"></a>6.2.2 修改 pom 文件</h4><p>添加 zookeeper 的坐标依赖。</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.apache.zookeeper<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>zookeeper<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>3.6.1<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
<h4 id="6-2-3-创建目录结构"><a href="#6-2-3-创建目录结构" class="headerlink" title="6.2.3 创建目录结构"></a>6.2.3 创建目录结构</h4><img src="https://gitee.com/azhenyoo/img/raw/master/Zookeeper/RPC-创建客户端目录结构.png" alt="RPC-创建客户端目录结构" style="zoom: 100%;" />
<h4 id="6-2-4-编码"><a href="#6-2-4-编码" class="headerlink" title="6.2.4 编码"></a>6.2.4 编码</h4><h5 id="UserService-1"><a href="#UserService-1" class="headerlink" title="UserService"></a>UserService</h5><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UserService</span> </span>{</span><br><span class="line"> <span class="function">String <span class="title">findUsers</span><span class="params">(String str)</span></span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h5 id="ZookeeperClient"><a href="#ZookeeperClient" class="headerlink" title="ZookeeperClient"></a>ZookeeperClient</h5><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ZookeeperClient</span> <span class="keyword">implements</span> <span class="title">Watcher</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException, KeeperException, InterruptedException, NotBoundException </span>{</span><br><span class="line"> String zookeeperUrl = <span class="string">"192.168.0.119:2181,192.168.0.119:2182,192.168.0.119:2183"</span>;</span><br><span class="line"> ZooKeeper zooKeeper = <span class="keyword">new</span> ZooKeeper(zookeeperUrl, <span class="number">20000</span>, <span class="keyword">new</span> ZookeeperClient());</span><br><span class="line"> <span class="keyword">byte</span>[] data = zooKeeper.getData(<span class="string">"/rpc/user"</span>, <span class="keyword">new</span> ZookeeperClient(), <span class="keyword">new</span> Stat());</span><br><span class="line"> String url = <span class="keyword">new</span> String(data);</span><br><span class="line"> UserService userService = (UserService) Naming.lookup(url);</span><br><span class="line"> System.out.println(userService.findUsers(<span class="string">"azhen"</span>));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">process</span><span class="params">(WatchedEvent watchedEvent)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span>(watchedEvent.getState() == Event.KeeperState.SyncConnected) {</span><br><span class="line"> System.out.println(<span class="string">"客户端连接成功"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="6-2-5-测试客户端"><a href="#6-2-5-测试客户端" class="headerlink" title="6.2.5 测试客户端"></a>6.2.5 测试客户端</h4><img src="https://gitee.com/azhenyoo/img/raw/master/Zookeeper/测试客户端.png" alt="测试客户端" style="zoom: 100%;" />
]]></content>
<categories>
<category>开发框架</category>
<category>Zookeeper</category>
</categories>
<tags>
<tag>Java</tag>
<tag>中间件</tag>
<tag>Zookeeper</tag>
</tags>
</entry>
<entry>
<title>Git</title>
<url>/2020/05/25/Git/</url>
<content><![CDATA[<p><a href="https://git-scm.com/">Git</a> 是一个免费的、开源的分布式版本控制系统,可以快速高效地处理从小型到大型地项目。<span id="more"></span></p>
<p><strong>其他常见地版本控制工具</strong>:SVN、CVS 等。</p>
<p><strong>版本公工具的作用</strong>:</p>
<ul>
<li><strong>协同开发</strong> :多人并行不悖的修改服务器端的同一个文件</li>
<li><strong>数据备份</strong> :不仅保存目录和文件的当前状态,还能够保存每一个提交过的历史状态</li>
<li><strong>版本管理</strong> :在保存每一个版本的文件信息的时候要做到不保存重要数据,以节约存储空间,提高运行效率。在这方面 SVN 采用的增量式管理的方式,而 Git 采取文件系统快照的方式</li>
<li><strong>权限控制</strong> :对团队中参与开发的人员进行权限控制,对团队外开发者贡献的代码进行审核</li>
<li><strong>历史记录</strong> :查看修改人、修改事件、修改内容、日志信息。将本地文件恢复到某一个历史状态</li>
<li><strong>分支管理</strong> :允许开发团队在工作过程中多条生产线同时推进,进一步提高效率</li>
</ul>
<h2 id="一、Git-本地操作"><a href="#一、Git-本地操作" class="headerlink" title="一、Git 本地操作"></a>一、Git 本地操作</h2><h3 id="1-1-安装及初始化本地仓库"><a href="#1-1-安装及初始化本地仓库" class="headerlink" title="1.1 安装及初始化本地仓库"></a>1.1 安装及初始化本地仓库</h3><p>安装 git 十分简单,这里就不详细介绍了。安装完以后,就可以在本地计算机中创建仓库。</p>
<h4 id="1-1-1-新建本地仓库"><a href="#1-1-1-新建本地仓库" class="headerlink" title="1.1.1 新建本地仓库"></a>1.1.1 新建本地仓库</h4><p>在本地磁盘 D 盘下新建文件夹 Git 作为本地仓库,进入文件夹后右键选择【Git Bash Here】,会显示如下命令窗口</p>
<img src="https://gitee.com/azhenyoo/img/raw/master/Git/打开Git_Bash.png" alt="打开Git_Bash" style="zoom: 67%;" />
<h4 id="1-1-2-Git-命令"><a href="#1-1-2-Git-命令" class="headerlink" title="1.1.2 Git 命令"></a>1.1.2 Git 命令</h4><h5 id="查看-Git-版本"><a href="#查看-Git-版本" class="headerlink" title="查看 Git 版本"></a>查看 Git 版本</h5><p>使用 <code>git --version</code> 命令就可以查看安装的 Git 的版本信息</p>
<img src="https://gitee.com/azhenyoo/img/raw/master/Git/查看Git版本.png" alt="查看Git版本" style="zoom: 80%;" />
<p>可以看到,我在本地计算机中安装的 Git 版本为 2.26.2</p>
<h5 id="配置基本信息"><a href="#配置基本信息" class="headerlink" title="配置基本信息"></a>配置基本信息</h5><p>使用 <code>git config --global user.name “azhen”</code> 命令配置用户名</p>
<p>使用 <code>git config --global user.email “azhen_study@163.com”</code> 命令配置邮箱</p>
<h5 id="初始化本地仓库"><a href="#初始化本地仓库" class="headerlink" title="初始化本地仓库"></a>初始化本地仓库</h5><p>使用 <code>git init</code> 命令初始化 git 的本地仓库</p>
<img src="https://gitee.com/azhenyoo/img/raw/master/Git/初始化本地仓库.png" alt="初始化本地仓库" style="zoom:80%;" />
<h3 id="1-2-存储流程"><a href="#1-2-存储流程" class="headerlink" title="1.2 存储流程"></a>1.2 存储流程</h3><p>存储流程:代码工作区 → 执行【git add】→ 暂存区(临时存储)→ 执行【git commit】→ 本地库(历史版本),工作区、暂存区和本地仓库,逻辑上是本地计算机。</p>
<ol>
<li>当我们新建一个文件时,文件位于工作区,处于已修改(modified) 状态,表明文件以进行了修改,但还没有提交保存;</li>
<li>通过命令 <code>git add</code> 将其添加到暂存区,文件是已暂存(staged) 状态,表明把已修改的文件放到了下次提交时要保存的清单中;</li>
<li>通过命令 <code>git commit</code> 将文件放入本地仓库,文件为已提交(commited) 状态,表明该文件已经被安全地保存到本地数据库中,到这一步可以说是成功生成了一个新的版本。</li>
</ol>
<h4 id="1-2-1-添加文件"><a href="#1-2-1-添加文件" class="headerlink" title="1.2.1 添加文件"></a>1.2.1 添加文件</h4><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git add test.txt</span></span><br></pre></td></tr></table></figure>
<h4 id="1-2-2-文件提交"><a href="#1-2-2-文件提交" class="headerlink" title="1.2.2 文件提交"></a>1.2.2 文件提交</h4><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git commit -m <span class="string">"test.txt modified by azhen"</span></span></span><br><span class="line">[master (root-commit) d8f3565] test.txt modified by azhen</span><br><span class="line"> 1 file changed, 0 insertions(+), 0 deletions(-)</span><br><span class="line"> create mode 100644 test.txt</span><br></pre></td></tr></table></figure>
<h4 id="1-2-3-查看文件状态"><a href="#1-2-3-查看文件状态" class="headerlink" title="1.2.3 查看文件状态"></a>1.2.3 查看文件状态</h4><h5 id="没有文件需要提交"><a href="#没有文件需要提交" class="headerlink" title="没有文件需要提交"></a>没有文件需要提交</h5><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git status</span></span><br><span class="line">On branch master</span><br><span class="line">nothing to commit, working tree clean</span><br></pre></td></tr></table></figure>
<h5 id="有文件需要提交"><a href="#有文件需要提交" class="headerlink" title="有文件需要提交"></a>有文件需要提交</h5><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git status</span></span><br><span class="line">On branch master</span><br><span class="line">Changes not staged for commit:</span><br><span class="line"> (use "git add <file>..." to update what will be committed)</span><br><span class="line"> (use "git restore <file>..." to discard changes in working directory)</span><br><span class="line"> modified: test.txt</span><br><span class="line"></span><br><span class="line">no changes added to commit (use "git add" and/or "git commit -a")</span><br></pre></td></tr></table></figure>
<h3 id="1-3-文件对比和查看日志"><a href="#1-3-文件对比和查看日志" class="headerlink" title="1.3 文件对比和查看日志"></a>1.3 文件对比和查看日志</h3><h4 id="1-3-1-文件对比指令"><a href="#1-3-1-文件对比指令" class="headerlink" title="1.3.1 文件对比指令"></a>1.3.1 文件对比指令</h4><p><code>git diff</code> 指令可以查看当前文件和仓库中地的文件有什么区别</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git diff test.txt</span></span><br><span class="line">diff --git a/test.txt b/test.txt</span><br><span class="line">index 2226df5..12ff2bb 100644</span><br><span class="line">--- a/test.txt</span><br><span class="line">+++ b/test.txt</span><br><span class="line">@@ -1,4 +1,5 @@</span><br><span class="line"> public class Test{</span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line">+ System.out.println("Hello World");</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">\ No newline at end of file</span><br></pre></td></tr></table></figure>
<h4 id="1-3-2-日志查询"><a href="#1-3-2-日志查询" class="headerlink" title="1.3.2 日志查询"></a>1.3.2 日志查询</h4><p><code>git log</code> 命令可以进行日志查询,以时间的倒叙可以查看修改数据信息</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git <span class="built_in">log</span></span></span><br><span class="line">commit eb9009f85db705061a6b69c79707b2a65f7b8b87 (HEAD -> master)</span><br><span class="line">Author: azhen <azhen_study@163.com></span><br><span class="line">Date: Mon Jul 27 08:42:44 2020 +0800</span><br><span class="line"></span><br><span class="line"> commit 3</span><br><span class="line"></span><br><span class="line">commit 0907a82150750143a34a49f95d6ead4efc5654de</span><br><span class="line">Author: azhen <azhen_study@163.com></span><br><span class="line">Date: Sun Jul 26 14:42:00 2020 +0800</span><br><span class="line"></span><br><span class="line"> test.txt second modified by azhen</span><br><span class="line"></span><br><span class="line">commit d8f3565d86ac49cbb0bb4a39341559029d9a9444</span><br><span class="line">Author: azhen <azhen_study@163.com></span><br><span class="line">Date: Sun Jul 26 14:36:10 2020 +0800</span><br><span class="line"></span><br><span class="line"> test.txt modified by azhen</span><br></pre></td></tr></table></figure>
<p>此外,还可以使用 <code>git log --pretty=oneline</code> 命令简化日志输出</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git <span class="built_in">log</span> --pretty=oneline</span></span><br><span class="line">eb9009f85db705061a6b69c79707b2a65f7b8b87 (HEAD -> master) commit 3</span><br><span class="line">0907a82150750143a34a49f95d6ead4efc5654de test.txt second modified by azhen</span><br><span class="line">d8f3565d86ac49cbb0bb4a39341559029d9a9444 test.txt modified by azhen</span><br></pre></td></tr></table></figure>
<p>还可以使用引用日志查看命令 <code>git reflog</code> 查看文件的变更信息,此命令主要用于版本的切换</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git reflog</span></span><br><span class="line">eb9009f (HEAD -> master) HEAD@{0}: commit: commit 3</span><br><span class="line">0907a82 HEAD@{1}: commit: test.txt second modified by azhen</span><br><span class="line">d8f3565 HEAD@{2}: commit (initial): test.txt modified by azhen</span><br></pre></td></tr></table></figure>
<p><code>git reset --hard</code> 命令用于文件版本的切换,比如切回 0907a82 的版本</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git reset --hard 0907a82</span></span><br><span class="line">HEAD is now at 0907a82 test.txt second modified by azhen</span><br><span class="line"></span><br><span class="line"><span class="meta">$</span><span class="bash"> git reflog</span></span><br><span class="line">0907a82 (HEAD -> master) HEAD@{0}: reset: moving to 0907a82</span><br><span class="line">eb9009f HEAD@{1}: commit: commit 3</span><br><span class="line">0907a82 (HEAD -> master) HEAD@{2}: commit: test.txt second modified by azhen</span><br><span class="line">d8f3565 HEAD@{3}: commit (initial): test.txt modified by azhen</span><br></pre></td></tr></table></figure>
<h3 id="1-4-删除文件与恢复文件"><a href="#1-4-删除文件与恢复文件" class="headerlink" title="1.4 删除文件与恢复文件"></a>1.4 删除文件与恢复文件</h3><h4 id="1-4-1-删除文件"><a href="#1-4-1-删除文件" class="headerlink" title="1.4.1 删除文件"></a>1.4.1 删除文件</h4><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 删除文件</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> rm test.txt</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 对比文件</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> git diff</span></span><br><span class="line">diff --git a/test.txt b/test.txt</span><br><span class="line">deleted file mode 100644</span><br><span class="line">index 2226df5..0000000</span><br><span class="line">--- a/test.txt</span><br><span class="line">+++ /dev/null</span><br><span class="line">@@ -1,4 +0,0 @@</span><br><span class="line">-public class Test{</span><br><span class="line">- public static void main(String[] args) {</span><br><span class="line">- }</span><br><span class="line">-}</span><br><span class="line">\ No newline at end of file</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 添加文件进入git缓存</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> git add test.txt</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 提交文件</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> git commit -m <span class="string">"delete text.txt"</span></span></span><br><span class="line">[master f62be66] delete text.txt</span><br><span class="line"> 1 file changed, 4 deletions(-)</span><br><span class="line"> delete mode 100644 test.txt</span><br><span class="line"> </span><br><span class="line"><span class="meta"> #</span><span class="bash"> 查看引用日志</span></span><br><span class="line"><span class="meta"> $</span><span class="bash"> git reflog</span></span><br><span class="line">f62be66 (HEAD -> master) HEAD@{0}: commit: delete text.txt</span><br><span class="line">0907a82 HEAD@{1}: reset: moving to 0907a82</span><br><span class="line">eb9009f HEAD@{2}: commit: commit 3</span><br><span class="line">0907a82 HEAD@{3}: commit: test.txt second modified by azhen</span><br><span class="line">d8f3565 HEAD@{4}: commit (initial): test.txt modified by azhen</span><br></pre></td></tr></table></figure>
<h4 id="1-4-2-恢复文件"><a href="#1-4-2-恢复文件" class="headerlink" title="1.4.2 恢复文件"></a>1.4.2 恢复文件</h4><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 切换版本号恢复文件</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> git reset --hard 0907a82</span></span><br><span class="line">HEAD is now at 0907a82 test.txt second modified by azhen</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 查看恢复文件的内容</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> cat test.txt</span></span><br><span class="line">public class Test{</span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="1-4-3-另一种恢复方式"><a href="#1-4-3-另一种恢复方式" class="headerlink" title="1.4.3 另一种恢复方式"></a>1.4.3 另一种恢复方式</h4><p>当删除文件没有进行提交操作的时候,可以通过 <code>git checkout -- test.txt</code> 命令从 git 版本库中读取一个文件恢复</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 删除文件</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> rm test.txt</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 查看当前目录下的文件</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> ls</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 恢复文件</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> git checkout -- test.txt</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 查看当前目录下的文件</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> ls</span></span><br><span class="line">test.txt</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 查看恢复文件内容</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> cat test.txt</span></span><br><span class="line">public class Test{</span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="1-5-分支管理"><a href="#1-5-分支管理" class="headerlink" title="1.5 分支管理"></a>1.5 分支管理</h3><p>在版本回退那里,已经知道每次提交,git 都会把他们串成一条时间线,这条时间线就是一个分支。截至到目前,只有一条时间线,在 git 里,这个分支叫主分支,及 master。切换分支即可理解为切换时间线。</p>
<p>在版本控制过程中,使用多条时间线同时推进多个任务。每条线成为一个分支。</p>
<h4 id="1-5-1-创建分支"><a href="#1-5-1-创建分支" class="headerlink" title="1.5.1 创建分支"></a>1.5.1 创建分支</h4><p><code>*</code> 代表为当前所在的分支</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 创建分支</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> git branch b1</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 查看分支信息</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> git branch -v</span></span><br><span class="line"> b1 0907a82 test.txt second modified by azhen</span><br><span class="line">* master 0907a82 test.txt second modified by azhen</span><br></pre></td></tr></table></figure>
<h4 id="1-5-2-切换分支"><a href="#1-5-2-切换分支" class="headerlink" title="1.5.2 切换分支"></a>1.5.2 切换分支</h4><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git checkout b1</span></span><br><span class="line">Switched to branch 'b1'</span><br></pre></td></tr></table></figure>
<h4 id="1-5-3-合并分支"><a href="#1-5-3-合并分支" class="headerlink" title="1.5.3 合并分支"></a>1.5.3 合并分支</h4><p>在 b1 分支上修改文件后提交</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git branch -v</span></span><br><span class="line">* b1 560c0f8 text.txt b1 commit 1</span><br><span class="line"> master 0907a82 test.txt second modified by azhen</span><br></pre></td></tr></table></figure>
<p>切换到主干分支</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git checkout master</span></span><br><span class="line">Switched to branch 'master'</span><br></pre></td></tr></table></figure>
<p>将 b1 分支合并到主干分支上</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git merge b1</span></span><br><span class="line">Updating 0907a82..560c0f8</span><br><span class="line">Fast-forward</span><br><span class="line"> test.txt | 3 ++-</span><br><span class="line"> 1 file changed, 2 insertions(+), 1 deletion(-)</span><br></pre></td></tr></table></figure>
<p>查看分支版本信息</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git branch -v</span></span><br><span class="line"> b1 560c0f8 text.txt b1 commit 1</span><br><span class="line">* master 560c0f8 text.txt b1 commit 1</span><br></pre></td></tr></table></figure>
<h5 id="1-5-4-删除分支"><a href="#1-5-4-删除分支" class="headerlink" title="1.5.4 删除分支"></a>1.5.4 删除分支</h5><p>删除分支</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git branch -d b1</span></span><br><span class="line">Deleted branch b1 (was 560c0f8).</span><br></pre></td></tr></table></figure>
<p>查看分支版本信息</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git branch -v</span></span><br><span class="line">* master 560c0f8 text.txt b1 commit 1</span><br></pre></td></tr></table></figure>
<h2 id="二、Git-远程操作"><a href="#二、Git-远程操作" class="headerlink" title="二、Git 远程操作"></a>二、Git 远程操作</h2><h3 id="2-1-在码云注册账号"><a href="#2-1-在码云注册账号" class="headerlink" title="2.1 在码云注册账号"></a>2.1 在码云注册账号</h3><p><a href="https://gitee.com/">码云网址</a></p>
<h3 id="2-2-创建-SSH-key"><a href="#2-2-创建-SSH-key" class="headerlink" title="2.2 创建 SSH key"></a>2.2 创建 SSH key</h3><p>因为数据保存在远程服务器,服务器需要对你的身份进行识别,SSH key 可以让你的电脑和码云之间建立安全的加密连接。</p>
<h4 id="2-1-1-创建本地的公钥和私钥"><a href="#2-1-1-创建本地的公钥和私钥" class="headerlink" title="2.1.1 创建本地的公钥和私钥"></a>2.1.1 创建本地的公钥和私钥</h4><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> ssh-keygen -t rsa -C <span class="string">"azhen_study@163.com"</span></span></span><br><span class="line">Generating public/private rsa key pair.</span><br><span class="line">Enter file in which to save the key (/c/Users/ChengYan/.ssh/id_rsa):</span><br><span class="line">Created directory '/c/Users/ChengYan/.ssh'.</span><br><span class="line">Enter passphrase (empty for no passphrase):</span><br><span class="line">Enter same passphrase again:</span><br><span class="line">Your identification has been saved in /c/Users/ChengYan/.ssh/id_rsa</span><br><span class="line">Your public key has been saved in /c/Users/ChengYan/.ssh/id_rsa.pub</span><br><span class="line">The key fingerprint is:</span><br><span class="line">SHA256:NBJ+QviTqoQbI+GKKyBTnzGA45iVE3ExaF92GSMZp28 azhen_study@163.com</span><br><span class="line">The key's randomart image is:</span><br><span class="line">+---[RSA 3072]----+</span><br><span class="line">| .o=+o=o+o |</span><br><span class="line">|o B..++=o. |</span><br><span class="line">|o= + +=o+ |</span><br><span class="line">|+.. + +* . |</span><br><span class="line">|.+ . = .E |</span><br><span class="line">|O.. + . |</span><br><span class="line">|** . |</span><br><span class="line">|= . |</span><br><span class="line">|o. |</span><br><span class="line">+----[SHA256]-----+</span><br></pre></td></tr></table></figure>
<h4 id="2-2-2-在码云上添加公钥"><a href="#2-2-2-在码云上添加公钥" class="headerlink" title="2.2.2 在码云上添加公钥"></a>2.2.2 在码云上添加公钥</h4><p>登录码云,在 SSH 公钥文本框里粘贴 id_rsa.pub 文件的内容</p>
<img src="https://gitee.com/azhenyoo/img/raw/master/Git/设置Gitee的SSH.png" alt="设置Gitee的SSH" style="zoom:80%;" />
<h4 id="2-2-3-验证密钥是否添加成功"><a href="#2-2-3-验证密钥是否添加成功" class="headerlink" title="2.2.3 验证密钥是否添加成功"></a>2.2.3 验证密钥是否添加成功</h4><p>执行命令 <code>ssh -T git@oschina.net</code> ,输入 yes 即可完成认证</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> ssh -T git@oschina.net</span></span><br><span class="line">The authenticity of host 'oschina.net (212.64.62.183)' can't be established.</span><br><span class="line">ECDSA key fingerprint is SHA256:FQGC9Kn/eye1W8icdBgrQp+KkGYoFgbVr17bmjey0Wc.</span><br><span class="line">Are you sure you want to continue connecting (yes/no/[fingerprint])? yes</span><br><span class="line">Warning: Permanently added 'oschina.net,212.64.62.183' (ECDSA) to the list of known hosts.</span><br><span class="line">Hi 竹秋! You've successfully authenticated, but GITEE.COM does not provide shell access.</span><br></pre></td></tr></table></figure>
<h3 id="2-3-创建远程仓库"><a href="#2-3-创建远程仓库" class="headerlink" title="2.3 创建远程仓库"></a>2.3 创建远程仓库</h3><img src="https://gitee.com/azhenyoo/img/raw/master/Git/创建仓库.png" alt="创建仓库" style="zoom:80%;" />
<h3 id="2-4-推送与拉取"><a href="#2-4-推送与拉取" class="headerlink" title="2.4 推送与拉取"></a>2.4 推送与拉取</h3><h4 id="2-4-1-克隆文件"><a href="#2-4-1-克隆文件" class="headerlink" title="2.4.1 克隆文件"></a>2.4.1 克隆文件</h4><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git <span class="built_in">clone</span> https://gitee.com/azhenyoo/git-demo.git</span></span><br><span class="line">Cloning into 'git-demo'...</span><br><span class="line">remote: Enumerating objects: 4, done.</span><br><span class="line">remote: Counting objects: 100% (4/4), done.</span><br><span class="line">remote: Compressing objects: 100% (4/4), done.</span><br><span class="line">remote: Total 4 (delta 0), reused 0 (delta 0), pack-reused 0</span><br><span class="line">Receiving objects: 100% (4/4), done.</span><br></pre></td></tr></table></figure>
<h4 id="2-4-2-推动文件"><a href="#2-4-2-推动文件" class="headerlink" title="2.4.2 推动文件"></a>2.4.2 推动文件</h4><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git push https://gitee.com/azhenyoo/git-demo.git master</span></span><br><span class="line">Enumerating objects: 7, done.</span><br><span class="line">Counting objects: 100% (7/7), done.</span><br><span class="line">Delta compression using up to 8 threads</span><br><span class="line">Compressing objects: 100% (5/5), done.</span><br><span class="line">Writing objects: 100% (6/6), 624 bytes | 312.00 KiB/s, done.</span><br><span class="line">Total 6 (delta 1), reused 0 (delta 0), pack-reused 0</span><br><span class="line">remote: Powered by GITEE.COM [GNK-5.0]</span><br><span class="line">To https://gitee.com/azhenyoo/git-demo.git</span><br><span class="line"> f71628e..716f908 master -> master</span><br></pre></td></tr></table></figure>
<h4 id="2-4-3-拉取文件"><a href="#2-4-3-拉取文件" class="headerlink" title="2.4.3 拉取文件"></a>2.4.3 拉取文件</h4><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git pull https://gitee.com/azhenyoo/git-demo.git</span></span><br><span class="line">remote: Enumerating objects: 4, done.</span><br><span class="line">remote: Counting objects: 100% (4/4), done.</span><br><span class="line">remote: Compressing objects: 100% (3/3), done.</span><br><span class="line">remote: Total 3 (delta 1), reused 0 (delta 0), pack-reused 0</span><br><span class="line">Unpacking objects: 100% (3/3), 494 bytes | 29.00 KiB/s, done.</span><br><span class="line">From https://gitee.com/azhenyoo/git-demo</span><br><span class="line"> * branch HEAD -> FETCH_HEAD</span><br><span class="line">Updating 716f908..21df7ea</span><br><span class="line">Fast-forward</span><br><span class="line"> spring-dubbo.xml | 10 ++++++++++</span><br><span class="line"> 1 file changed, 10 insertions(+)</span><br><span class="line"> create mode 100644 spring-dubbo.xml</span><br></pre></td></tr></table></figure>
<h3 id="2-5-远程仓库地址别名"><a href="#2-5-远程仓库地址别名" class="headerlink" title="2.5 远程仓库地址别名"></a>2.5 远程仓库地址别名</h3><h4 id="2-5-1-查看远程地址信息"><a href="#2-5-1-查看远程地址信息" class="headerlink" title="2.5.1 查看远程地址信息"></a>2.5.1 查看远程地址信息</h4><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git remote -v</span></span><br><span class="line">origin https://gitee.com/azhenyoo/git-demo.git (fetch)</span><br><span class="line">origin https://gitee.com/azhenyoo/git-demo.git (push)</span><br></pre></td></tr></table></figure>
<h4 id="2-5-2-添加远程地址别名"><a href="#2-5-2-添加远程地址别名" class="headerlink" title="2.5.2 添加远程地址别名"></a>2.5.2 添加远程地址别名</h4><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 添加远程地址别名</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> git remote add git-demo https://gitee.com/azhenyoo/git-demo.git</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 查看远程地址信息</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> git remote -v</span></span><br><span class="line">git-demo https://gitee.com/azhenyoo/git-demo.git (fetch)</span><br><span class="line">git-demo https://gitee.com/azhenyoo/git-demo.git (push)</span><br><span class="line">origin https://gitee.com/azhenyoo/git-demo.git (fetch)</span><br><span class="line">origin https://gitee.com/azhenyoo/git-demo.git (push)</span><br></pre></td></tr></table></figure>
<h4 id="2-5-3-删除远程地址别名"><a href="#2-5-3-删除远程地址别名" class="headerlink" title="2.5.3 删除远程地址别名"></a>2.5.3 删除远程地址别名</h4><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 删除远程地址别名</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> git remote remove git-demo</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 查看远程地址信息</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> git remote -v</span></span><br><span class="line">origin https://gitee.com/azhenyoo/git-demo.git (fetch)</span><br><span class="line">origin https://gitee.com/azhenyoo/git-demo.git (push)</span><br></pre></td></tr></table></figure>
]]></content>
<categories>
<category>版本控制工具</category>
<category>Git</category>
</categories>
<tags>
<tag>Git</tag>
</tags>
</entry>
<entry>
<title>JavaSE 基础</title>
<url>/2019/06/25/JavaSE/</url>
<content><![CDATA[<blockquote class="blockquote-center">
<i class="fa fa-quote-left"></i>
<p>Write Once, Run Anywhere<br>——<strong>Java</strong></p>
<i class="fa fa-quote-right"></i>
</blockquote><span id="more"></span>
<h2 id="Java-简介"><a href="#Java-简介" class="headerlink" title="Java 简介"></a>Java 简介</h2><p>Java 是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 面向对象程序设计语言和 Java 平台的总称。由 James Gosling和同事们共同研发,并在 1995 年正式推出。</p>
<p>后来 Sun 公司被 Oracle (甲骨文)公司收购,Java 也随之成为 Oracle 公司的产品。</p>
<p>Java分为三个体系:</p>
<ul>
<li>JavaSE(J2SE) - Java2 Platform Standard Edition,java平台标准版</li>
<li>JavaEE(J2EE) - Java 2 Platform Enterprise Edition,java平台企业版</li>
<li>JavaME(J2ME) - Java 2 Platform Micro Edition,java平台微型版</li>
</ul>
<p>2005 年 6 月,JavaOne 大会召开,SUN 公司公开 Java SE 6。此时,Java 的各种版本已经更名,以取消其中的数字 “2”:J2EE 更名为 Java EE,J2SE 更名为Java SE,J2ME 更名为 Java ME。</p>
]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>编程语言</tag>
</tags>
</entry>
</search>
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。