mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-17 08:46:40 +08:00
679 lines
22 KiB
HTML
679 lines
22 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
|
||
|
||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
|
||
<head>
|
||
|
||
<head>
|
||
|
||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
||
|
||
<link rel="icon" href="/static/favicon.png">
|
||
|
||
<title>11 解决技术改造困局的钥匙:整洁架构.md.html</title>
|
||
|
||
<!-- Spectre.css framework -->
|
||
|
||
<link rel="stylesheet" href="/static/index.css">
|
||
|
||
<!-- theme css & js -->
|
||
|
||
<meta name="generator" content="Hexo 4.2.0">
|
||
|
||
</head>
|
||
|
||
|
||
|
||
<body>
|
||
|
||
|
||
|
||
<div class="book-container">
|
||
|
||
<div class="book-sidebar">
|
||
|
||
<div class="book-brand">
|
||
|
||
<a href="/">
|
||
|
||
<img src="/static/favicon.png">
|
||
|
||
<span>技术文章摘抄</span>
|
||
|
||
</a>
|
||
|
||
</div>
|
||
|
||
<div class="book-menu uncollapsible">
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="/" class="current-tab">首页</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="../">上一级</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/DDD 微服务落地实战/00 开篇词 让我们把 DDD 的思想真正落地.md.html">00 开篇词 让我们把 DDD 的思想真正落地.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/DDD 微服务落地实战/01 DDD :杜绝软件退化的利器.md.html">01 DDD :杜绝软件退化的利器.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/DDD 微服务落地实战/02 以电商支付功能为例演练 DDD.md.html">02 以电商支付功能为例演练 DDD.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/DDD 微服务落地实战/03 DDD 是如何落地到数据库设计的?.md.html">03 DDD 是如何落地到数据库设计的?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/DDD 微服务落地实战/04 领域模型是如何指导程序设计的?.md.html">04 领域模型是如何指导程序设计的?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/DDD 微服务落地实战/05 聚合、仓库与工厂:傻傻分不清楚.md.html">05 聚合、仓库与工厂:傻傻分不清楚.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/DDD 微服务落地实战/06 限界上下文:冲破微服务设计困局的利器.md.html">06 限界上下文:冲破微服务设计困局的利器.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/DDD 微服务落地实战/07 在线订餐场景中是如何开事件风暴会议的?.md.html">07 在线订餐场景中是如何开事件风暴会议的?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/DDD 微服务落地实战/08 DDD 是如何解决微服务拆分难题的?.md.html">08 DDD 是如何解决微服务拆分难题的?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/DDD 微服务落地实战/09 DDD 是如何落地微服务设计实现的?.md.html">09 DDD 是如何落地微服务设计实现的?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/DDD 微服务落地实战/10 微服务落地的技术实践.md.html">10 微服务落地的技术实践.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
<a class="current-tab" href="/专栏/DDD 微服务落地实战/11 解决技术改造困局的钥匙:整洁架构.md.html">11 解决技术改造困局的钥匙:整洁架构.md.html</a>
|
||
|
||
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/DDD 微服务落地实战/12 如何设计支持快速交付的技术中台战略?.md.html">12 如何设计支持快速交付的技术中台战略?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/DDD 微服务落地实战/13 如何实现支持快速交付的技术中台设计?.md.html">13 如何实现支持快速交付的技术中台设计?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/DDD 微服务落地实战/14 如何设计支持 DDD 的技术中台?.md.html">14 如何设计支持 DDD 的技术中台?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/DDD 微服务落地实战/15 如何设计支持微服务的技术中台?.md.html">15 如何设计支持微服务的技术中台?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/DDD 微服务落地实战/16 基于 DDD 的代码设计演示(含 DDD 的技术中台设计).md.html">16 基于 DDD 的代码设计演示(含 DDD 的技术中台设计).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/DDD 微服务落地实战/17 基于 DDD 的微服务设计演示(含支持微服务的 DDD 技术中台设计).md.html">17 基于 DDD 的微服务设计演示(含支持微服务的 DDD 技术中台设计).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/DDD 微服务落地实战/18 基于事件溯源的设计开发.md.html">18 基于事件溯源的设计开发.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
|
||
|
||
<div class="sidebar-toggle-inner"></div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<script>
|
||
|
||
function add_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.add('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
function remove_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.remove('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
function sidebar_toggle() {
|
||
|
||
let sidebar_toggle = document.querySelector('.sidebar-toggle')
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let content = document.querySelector('.off-canvas-content')
|
||
|
||
if (sidebar_toggle.classList.contains('extend')) { // show
|
||
|
||
sidebar_toggle.classList.remove('extend')
|
||
|
||
sidebar.classList.remove('hide')
|
||
|
||
content.classList.remove('extend')
|
||
|
||
} else { // hide
|
||
|
||
sidebar_toggle.classList.add('extend')
|
||
|
||
sidebar.classList.add('hide')
|
||
|
||
content.classList.add('extend')
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
function open_sidebar() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.add('show')
|
||
|
||
overlay.classList.add('show')
|
||
|
||
}
|
||
|
||
function hide_canvas() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.remove('show')
|
||
|
||
overlay.classList.remove('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
</script>
|
||
|
||
|
||
|
||
<div class="off-canvas-content">
|
||
|
||
<div class="columns">
|
||
|
||
<div class="column col-12 col-lg-12">
|
||
|
||
<div class="book-navbar">
|
||
|
||
<!-- For Responsive Layout -->
|
||
|
||
<header class="navbar">
|
||
|
||
<section class="navbar-section">
|
||
|
||
<a onclick="open_sidebar()">
|
||
|
||
<i class="icon icon-menu"></i>
|
||
|
||
</a>
|
||
|
||
</section>
|
||
|
||
</header>
|
||
|
||
</div>
|
||
|
||
<div class="book-content" style="max-width: 960px; margin: 0 auto;
|
||
|
||
overflow-x: auto;
|
||
|
||
overflow-y: hidden;">
|
||
|
||
<div class="book-post">
|
||
|
||
<p id="tip" align="center"></p>
|
||
|
||
<div><h1>11 解决技术改造困局的钥匙:整洁架构</h1>
|
||
|
||
<p>有个“大师与太空”的梗是这样说的:大师通常都站得很高很高、高瞻远瞩,站得有多高呢?都是站在太空里的,所以很多人追随大师容易缺氧。因此,我们学习大师的理论,就要脚踏实地、踏踏实实地将其落地到项目应用中,重新落地到“地面”上,这些理论才是有用的,落地 DDD 同样是这样。</p>
|
||
|
||
<p>如何落地 DDD 呢?除了在项目中实践 DDD,领域建模,按照 DDD 的思想设计开发以外,还需要一个支持 DDD 与微服务的技术中台。在 DDD 实现过程中,这个技术中台应当能够封装那些烦琐的聚合操作、仓库与工厂的设计,以及相关的各种技术。有了这个技术中台的支持,开发团队就可以把更多的精力放到对用户业务的理解,对业务痛点的理解,快速开发用户满意的功能并快速交付,而不再受限于那些烦琐的技术细节,从而降本增效。这样,不仅编写代码减少了,技术门槛降低了,还使得日后的变更更加容易,技术更迭也更加方便。</p>
|
||
|
||
<p>那么,如何设计这样一个技术中台呢?首先应当从现有系统的设计痛点开始分析。</p>
|
||
|
||
<h3>底层技术的更迭</h3>
|
||
|
||
<p>这些年,随着互联网、大数据、人工智能等新技术层出不穷,整个 IT 产业的技术架构也在快速迭代。</p>
|
||
|
||
<ul>
|
||
|
||
<li>过去,我们说“架构是软件系统中最稳定不变的部分”;</li>
|
||
|
||
<li>而现在,我们说“好的架构源于不停地演变”。</li>
|
||
|
||
</ul>
|
||
|
||
<p>因此,如今的架构设计需要思考如何让底层的架构更易于技术更迭、易于架构调整,以应对不断演进的新技术、新框架,从而获得行业竞争的技术优势。</p>
|
||
|
||
<p>然而,在实际项目中,特别是很多运行了七八年、十多年的老项目,要做一次技术升级,那叫一个费劲,就像脱一层皮那么痛苦。为什么技术升级那么费劲呢?究其原因,是在系统设计开发时,大量的业务代码依赖于底层的技术框架,形成了耦合。</p>
|
||
|
||
<p>譬如,过去采用 hibernate 进行数据持久化,每个模块的 DAO 都要继承自 HibernateDaoSupport。这样,所有的 DAO 都与 Hibernate 形成了一种依赖。当系统架构由 Hibernate2 升级成 Hibernate3,甚至升级成 MyBatis,就不是改换一个 jar 包那么简单了。</p>
|
||
|
||
<p>技术框架一换,底层的类、接口、包名都变了,就意味着上层的所有模块的 DAO 都需要改,改完了还要测试。这样的技术升级成本极高,风险极大,需要我们认真去思考解决方案。</p>
|
||
|
||
<p><img src="assets/CgpVE1_cU7qANfJ9AAGBoHYkAH4499.png" alt="图片1.png" /></p>
|
||
|
||
<p>总之,老系统技术架构升级成本极高的根源,在于<strong>业务代码与底层技术框架的耦合</strong>。因此,解决思路就是对它们进行解耦。如何解耦呢?就是在上层业务代码与底层技术框架之间建立“接口层”。</p>
|
||
|
||
<p><img src="assets/Cip5yF_cU9WAcI51AAG19Z4AMUk696.png" alt="图片2.png" /></p>
|
||
|
||
<p><img src="assets/CgqCHl_bMT-AGb0pAAEhTDqU53U764.png" alt="DDD 11--金句.png" /></p>
|
||
|
||
<p>如何在业务代码与底层框架之间建立“接口层”呢?如上图所示,上层业务代码在进行持久化时,各个模块的 DAO 不再去调用底层框架,而是对接口层的 DaoSupport 进行调用。DaoSupport 接口是我们自己设计的,它应当满足上层的所有业务需求,比如各种类型的 insert、 update、delete、get、load、find,并让这个接口保持稳定。上层业务代码的设计实现都依赖于 DaoSupport 接口,只要它稳定了,上层业务代码就稳定了。</p>
|
||
|
||
<p>接着,在 DaoSupport 接口的基础上编写<strong>实现类</strong>,由实现类去调用底层技术框架,实现真正的持久化。</p>
|
||
|
||
<ul>
|
||
|
||
<li>起初使用 Hibernate2 作为底层框架,所以为 Hibernate2 编写了一个实现类。</li>
|
||
|
||
<li>当 Hibernate2 升级成 Hibernate3 时,为 Hibernate3 写一个实现类。</li>
|
||
|
||
<li>当底层框架要升级成MyBatis 时,再为 MyBatis 写一个实现类。</li>
|
||
|
||
</ul>
|
||
|
||
<p>这样的设计,当系统进行技术架构升级时,其影响就不再扩展到业务层代码,而仅仅局限于调整接口层的实现类,技术升级的成本将得到大幅度的降低。</p>
|
||
|
||
<h3>整洁架构的设计</h3>
|
||
|
||
<p><img src="assets/CgpVE1_cU_SARXH3AADks5ecaDI652.png" alt="图片3.png" /></p>
|
||
|
||
<p>通过前面对问题的分析与接口层的设计,可以得出一个非常重要的结论:如何既能轻松地实现技术架构演化,又能保证开发团队的快速交付呢,关键的思路是将业务代码与技术框架解耦。如上图所示,在系统分层时,基于领域驱动的设计,将业务代码都整合在业务领域层中去实现。这里的业务领域层包括了 BUS 层中的 Service,以及与它们相关的业务实体与值对象。</p>
|
||
|
||
<p>业务领域层设计的实质,就是将<strong>领域模型通过贫血模型与充血模型的设计</strong>,<strong>最终落实到对代码的设计</strong>。在此基础上,通过分层将业务领域层与其他各个层次的技术框架进行解耦,这就是“整洁架构”的核心设计思路。</p>
|
||
|
||
<p><img src="assets/CgpVE1_ceLqAPVyTAARGrJWSmWs085.png" alt="图片1.png" /></p>
|
||
|
||
<p>整洁架构(The Clean Architecture)是 Robot C. Martin 在《架构整洁之道》中提出来的架构设计思想。如上图所示,它以圆环的形式把系统分成了几个不同的层次,因此又称为“洋葱头架构(The Onion Architecture)”。在整洁架构的中心是业务实体(黄色部分)与业务应用(红色部分),<strong>业务实体</strong>就是那些<strong>核心业务逻辑</strong>,而<strong>业务应用</strong>就是<strong>面向用户的那些服务</strong>(Service)。它们合起来组成了业务领域层,也就是通过领域模型形成的业务代码的实现。</p>
|
||
|
||
<p>整洁架构的最外层是各种技术框架,包括:</p>
|
||
|
||
<ul>
|
||
|
||
<li>与用户 UI 的交互;</li>
|
||
|
||
<li>客户端与服务器的网络交互;</li>
|
||
|
||
<li>与硬件设备和数据库的交互;</li>
|
||
|
||
<li>与其他外部系统的交互。</li>
|
||
|
||
</ul>
|
||
|
||
<p>整洁架构的精华在于其中间的<strong>适配器层</strong>,它通过适配器将核心的业务代码,与外围的技术框架进行解耦。因此,如何设计适配层,让业务代码与技术框架解耦,让业务开发团队与技术架构团队各自独立地工作,成了整洁架构落地的核心。</p>
|
||
|
||
<p><img src="assets/CgpVE1_Yge6AEg27AAOLJ2FjYts902.png" alt="Drawing 4.png" /></p>
|
||
|
||
<p>整洁架构设计的细化图,图片来自《软件架构编年史》</p>
|
||
|
||
<p>如图,进一步细化整洁架构,将其划分为 2 个部分:<strong>主动适配器</strong>与<strong>被动适配器</strong>。</p>
|
||
|
||
<ul>
|
||
|
||
<li>主动适配器,又称为“北向适配器”,就是由前端用户以不同的形式发起业务请求,然后交由应用层去接收请求,交由领域层去处理业务。用户可以用浏览器、客户端、移动 App、微信端、物联网专用设备等各种不同形式发起请求。然而,通过北向适配器,最后以同样的形式调用应用层。</li>
|
||
|
||
<li>被动适配器,又称为“南向适配器”,就是在业务领域层完成各种业务处理以后,以某种形式持久化存储最终的结果数据。最终的数据可以存储到关系型数据库、NoSQL 数据库、NewSQL 数据库、Redis 缓存中,或者以消息队列的形式发送给其他应用系统。但不论采用什么形式,业务领域层只有一套,但持久化存储可以有各种不同形式。<strong>南向适配器将业务逻辑与存储技术解耦</strong>。</li>
|
||
|
||
</ul>
|
||
|
||
<h3>整洁架构的落地</h3>
|
||
|
||
<p><img src="assets/Cip5yF_bMWWAYtJwAAEgzkERkwU966.png" alt="image" /></p>
|
||
|
||
<p>按照整洁架构的思想如何落地架构设计呢?如上图所示,在这个架构中,将适配器层通过数据接入层、数据访问层与接口层等几个部分的设计,实现与业务的解耦。</p>
|
||
|
||
<p>首先,用户可以用浏览器、客户端、移动 App、微信端、物联网专用设备等不同的前端形式,<strong>多渠道地接入到系统中</strong>,不同的渠道的接入形式是不同的。通过数据接入层进行解耦,然后以同样的方式去调用上层业务代码,就能将前端的多渠道接入,与后台的业务逻辑实现了解耦。这样,前端不管怎么变,有多少种渠道形式,后台业务只需要编写一套,维护成本将大幅度降低。</p>
|
||
|
||
<p>接着,<strong>通过数据访问层将业务逻辑与数据库解耦</strong>。前面说了,在未来三五年时间里,我们又将经历一轮大数据转型。转型成大数据以后,数据存储的设计可能不再仅限于关系型数据库与 3NF的思路设计,而是通过 JSON、增加冗余、设计宽表等设计思路,将其存储到 NoSQL 数据库中,设计思想将发生巨大的转变。但无论怎么转变,都只是存储形式的转变,不变的是<strong>业务逻辑层中的业务实体</strong>。因此,通过数据访问层的解耦,今后系统向大数据转型的时候,业务逻辑层不需要做任何修改,只需要重新编写数据访问层的实现,就可以转型成大数据技术。转型成本将大大降低,转型将更加容易。</p>
|
||
|
||
<p>最后,就是<strong>底层的技术架构</strong>。现在我们谈架构,越来越多地是在谈架构演化,也就是底层技术架构要不断地随着市场和技术的更迭而更迭。但是,话虽如此,很多系统的技术架构更迭,是一个非常痛苦的过程。为什么呢?究其原因,是软件在设计时,将太多业务代码与底层框架耦合,底层框架一旦变更,就会导致大量业务代码的变更,各个业务模块的都要更迭,导致架构调整的成本巨大、风险高昂。</p>
|
||
|
||
<p>既然这里的问题是耦合,解决的思路就是解耦。在平台建设的过程中,除了通过技术选型将各种技术整合到系统中以外,还应通过封装,在其上建立接口层。通过接口层的封装,封装许多技术的实现,以更加简便的接口开放给上层的业务开发人员。这样,既可以降低业务开发的技术门槛,让他们更加专注于业务,提高开发速度,又让业务代码与技术框架解耦。有了这种解耦,就使得未来可以用更低的成本技术更迭,加速技术架构演进,跟上这个快速变化的时代。</p>
|
||
|
||
<h3>总结</h3>
|
||
|
||
<p>整洁架构的中心是基于 DDD 的业务实现,即那些通过领域模型指导设计与开发的 Service、Entity 与 Value Object。整洁架构的最外层是各种硬件、设备与技术框架。而整洁架构最核心的思想,是通过适配器层,将业务实现与技术框架解耦,这也是 DDD 落地到架构设计的最佳实践。</p>
|
||
|
||
<p>因此,支持 DDD 与微服务的技术中台,就是基于整洁架构的思想,将 DDD 底层的那些烦琐的聚合操作、仓库与工厂的设计,与微服务的技术框架,以及整洁架构中的适配器,统统封装在技术中台中。有了这个技术中台,就能让上层的业务开发人员,更加轻松地运用 DDD 的思想,更加快捷地更迭与交付用户需求,从而在激烈的市场竞争中获得优势。</p>
|
||
|
||
<p>下一讲将进一步讲解这样的技术中台是如何设计的。</p>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div>
|
||
|
||
<div style="float: left">
|
||
|
||
<a href="/专栏/DDD 微服务落地实战/10 微服务落地的技术实践.md.html">上一页</a>
|
||
|
||
</div>
|
||
|
||
<div style="float: right">
|
||
|
||
<a href="/专栏/DDD 微服务落地实战/12 如何设计支持快速交付的技术中台战略?.md.html">下一页</a>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
|
||
|
||
</div>
|
||
|
||
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70996ec26cff3d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
|
||
|
||
</body>
|
||
|
||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||
|
||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
|
||
|
||
<script>
|
||
|
||
window.dataLayer = window.dataLayer || [];
|
||
|
||
|
||
|
||
function gtag() {
|
||
|
||
dataLayer.push(arguments);
|
||
|
||
}
|
||
|
||
|
||
|
||
gtag('js', new Date());
|
||
|
||
gtag('config', 'G-NPSEEVD756');
|
||
|
||
var path = window.location.pathname
|
||
|
||
var cookie = getCookie("lastPath");
|
||
|
||
console.log(path)
|
||
|
||
if (path.replace("/", "") === "") {
|
||
|
||
if (cookie.replace("/", "") !== "") {
|
||
|
||
console.log(cookie)
|
||
|
||
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
setCookie("lastPath", path)
|
||
|
||
}
|
||
|
||
|
||
|
||
function setCookie(cname, cvalue) {
|
||
|
||
var d = new Date();
|
||
|
||
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
|
||
|
||
var expires = "expires=" + d.toGMTString();
|
||
|
||
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
|
||
|
||
}
|
||
|
||
|
||
|
||
function getCookie(cname) {
|
||
|
||
var name = cname + "=";
|
||
|
||
var ca = document.cookie.split(';');
|
||
|
||
for (var i = 0; i < ca.length; i++) {
|
||
|
||
var c = ca[i].trim();
|
||
|
||
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
|
||
|
||
}
|
||
|
||
return "";
|
||
|
||
}
|
||
|
||
|
||
|
||
</script>
|
||
|
||
|
||
|
||
</html>
|
||
|