mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-17 08:46:40 +08:00
493 lines
24 KiB
HTML
493 lines
24 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>20 协作开发:模块化管理为什么能够提升研发效能?.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="/专栏/22 讲通关 Go 语言-完/00 开篇词 Go 为开发者的需求设计,带你实现高效工作.md.html">00 开篇词 Go 为开发者的需求设计,带你实现高效工作.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/22 讲通关 Go 语言-完/01 基础入门:编写你的第一个 Go 语言程序.md.html">01 基础入门:编写你的第一个 Go 语言程序.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/22 讲通关 Go 语言-完/02 数据类型:你必须掌握的数据类型有哪些?.md.html">02 数据类型:你必须掌握的数据类型有哪些?.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/22 讲通关 Go 语言-完/03 控制结构:if、for、switch 逻辑语句的那些事儿.md.html">03 控制结构:if、for、switch 逻辑语句的那些事儿.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/22 讲通关 Go 语言-完/04 集合类型:如何正确使用 array、slice 和 map?.md.html">04 集合类型:如何正确使用 array、slice 和 map?.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/22 讲通关 Go 语言-完/05 函数和方法:Go 语言中的函数和方法到底有什么不同?.md.html">05 函数和方法:Go 语言中的函数和方法到底有什么不同?.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/22 讲通关 Go 语言-完/06 struct 和 interface:结构体与接口都实现了哪些功能?.md.html">06 struct 和 interface:结构体与接口都实现了哪些功能?.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/22 讲通关 Go 语言-完/07 错误处理:如何通过 error、deferred、panic 等处理错误?.md.html">07 错误处理:如何通过 error、deferred、panic 等处理错误?.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/22 讲通关 Go 语言-完/08 并发基础:Goroutines 和 Channels 的声明与使用.md.html">08 并发基础:Goroutines 和 Channels 的声明与使用.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/22 讲通关 Go 语言-完/09 同步原语:sync 包让你对并发控制得心应手.md.html">09 同步原语:sync 包让你对并发控制得心应手.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/22 讲通关 Go 语言-完/10 Context:你必须掌握的多线程并发控制神器.md.html">10 Context:你必须掌握的多线程并发控制神器.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/22 讲通关 Go 语言-完/11 并发模式:Go 语言中即学即用的高效并发模式.md.html">11 并发模式:Go 语言中即学即用的高效并发模式.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/22 讲通关 Go 语言-完/12 指针详解:在什么情况下应该使用指针?.md.html">12 指针详解:在什么情况下应该使用指针?.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/22 讲通关 Go 语言-完/13 参数传递:值、引用及指针之间的区别?.md.html">13 参数传递:值、引用及指针之间的区别?.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/22 讲通关 Go 语言-完/14 内存分配:new 还是 make?什么情况下该用谁?.md.html">14 内存分配:new 还是 make?什么情况下该用谁?.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/22 讲通关 Go 语言-完/15 运行时反射:字符串和结构体之间如何转换?.md.html">15 运行时反射:字符串和结构体之间如何转换?.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/22 讲通关 Go 语言-完/16 非类型安全:让你既爱又恨的 unsafe.md.html">16 非类型安全:让你既爱又恨的 unsafe.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/22 讲通关 Go 语言-完/17 SliceHeader:slice 如何高效处理数据?.md.html">17 SliceHeader:slice 如何高效处理数据?.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/22 讲通关 Go 语言-完/18 质量保证:Go 语言如何通过测试保证质量?.md.html">18 质量保证:Go 语言如何通过测试保证质量?.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/22 讲通关 Go 语言-完/19 性能优化:Go 语言如何进行代码检查和优化?.md.html">19 性能优化:Go 语言如何进行代码检查和优化?.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
<a class="current-tab" href="/专栏/22 讲通关 Go 语言-完/20 协作开发:模块化管理为什么能够提升研发效能?.md.html">20 协作开发:模块化管理为什么能够提升研发效能?.md.html</a>
|
||
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/22 讲通关 Go 语言-完/21 网络编程:Go 语言如何玩转 RESTful API 服务?.md.html">21 网络编程:Go 语言如何玩转 RESTful API 服务?.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/22 讲通关 Go 语言-完/22 网络编程:Go 语言如何通过 RPC 实现跨平台服务?.md.html">22 网络编程:Go 语言如何通过 RPC 实现跨平台服务?.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/22 讲通关 Go 语言-完/23 结束语 你的 Go 语言成长之路.md.html">23 结束语 你的 Go 语言成长之路.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>20 协作开发:模块化管理为什么能够提升研发效能?</h1>
|
||
<p>任何业务,都是从简单向复杂演进的。而在业务演进的过程中,技术是从单体向多模块、多服务演进的。技术的这种演进方式的核心目的是<strong>复用代码、提高效率</strong>,这一讲,我会为你介绍 Go 语言是如何通过模块化的管理,提升开发效率的。</p>
|
||
<h3>Go 语言中的包</h3>
|
||
<h4>什么是包</h4>
|
||
<p>在业务非常简单的时候,你甚至可以把代码写到一个 Go 文件中。但随着业务逐渐复杂,你会发现,如果代码都放在一个 Go 文件中,会变得难以维护,这时候你就需要抽取代码,把相同业务的代码放在一个目录中。在 Go 语言中,这个目录叫作包。</p>
|
||
<p>在 Go 语言中,一个包是通过<strong>package 关键字定义</strong>的,最常见的就是<strong>main 包</strong>,它的定义如下所示:</p>
|
||
<pre><code>package main
|
||
</code></pre>
|
||
<p>此外,前面章节演示示例经常使用到的 fmt 包,也是通过 package 关键字声明的。</p>
|
||
<p>一个包就是一个<strong>独立的空间</strong>,你可以在这个包里<strong>定义函数</strong>、<strong>结构体</strong>等。这时,我们认为这些函数、结构体是属于这个包的。</p>
|
||
<h4>使用包</h4>
|
||
<p>如果你想使用一个包里的函数或者结构体,就需要先<strong>导入这个包</strong>,才能使用,比如常用的 fmt包,代码示例如下所示。</p>
|
||
<pre><code>package main
|
||
|
||
import "fmt"
|
||
|
||
func main() {
|
||
|
||
fmt.Println("先导入fmt包,才能使用")
|
||
|
||
}
|
||
</code></pre>
|
||
<p>要导入一个包,需要使用 import 关键字;如果需要同时导入多个包,则可以使用小括号,示例代码如下所示。</p>
|
||
<pre><code>import (
|
||
|
||
"fmt"
|
||
|
||
"os"
|
||
|
||
)
|
||
</code></pre>
|
||
<p>从以上示例可以看到,该示例导入了 fmt 和 os 这两个包,使用了小括号,每一行写了一个要导入的包。</p>
|
||
<h4>作用域</h4>
|
||
<p>讲到了包之间的导入和使用,就不得不提作用域这个概念,因为只有满足作用域的函数才可以被调用。</p>
|
||
<ul>
|
||
<li>在<strong>Java 语言</strong>中,通过 public、private 这些修饰符修饰一个类的作用域;</li>
|
||
<li>但是在<strong>Go 语言</strong>中,并没有这样的作用域修饰符,它是通过首字母是否大写来区分的,这同时也体现了 Go 语言的简洁。</li>
|
||
</ul>
|
||
<p>如上述示例中 fmt 包中的<strong>Println 函数</strong>:</p>
|
||
<ul>
|
||
<li>它的首字母就是大写的 P,所以该函数才可以在 main 包中使用;</li>
|
||
<li>如果 Println 函数的首字母是小写的 p,那么它只能在 fmt 包中被使用,不能跨包使用。</li>
|
||
</ul>
|
||
<p>这里我为你总结下 Go 语言的作用域:</p>
|
||
<ul>
|
||
<li>Go 语言中,所有的定义,比如函数、变量、结构体等,如果首字母是大写,那么就可以被其他包使用;</li>
|
||
<li>反之,如果首字母是小写的,就只能在同一个包内使用。</li>
|
||
</ul>
|
||
<h4>自定义包</h4>
|
||
<p>你也可以自定义自己的包,通过包的方式把相同业务、相同职责的代码放在一起。比如你有一个 util 包,用于存放一些常用的工具函数,项目结构如下所示:</p>
|
||
<pre><code>ch20
|
||
|
||
├── main.go
|
||
|
||
└── util
|
||
|
||
└── string.go
|
||
</code></pre>
|
||
<p>在 Go 语言中,一个包对应一个文件夹,上面的项目结构示例也验证了这一点。在这个示例中,有一个 util 文件夹,它里面有一个 string.go 文件,这个 Go 语言文件就属于 util 包,它的包定义如下所示:</p>
|
||
<p><em>ch20/util/string.go</em></p>
|
||
<pre><code>package util
|
||
</code></pre>
|
||
<p>可以看到,Go 语言中的包是代码的一种<strong>组织形式</strong>,通过包把相同业务或者相同职责的代码放在一起。通过包对代码进行归类,便于代码维护以及被其他包调用,提高团队协作效率。</p>
|
||
<h4>init 函数</h4>
|
||
<p>除了 main 这个特殊的函数外,Go 语言还有一个特殊的函数——init,通过它可以<strong>实现包级别的一些初始化操作</strong>。</p>
|
||
<p>init 函数没有返回值,也没有参数,它<strong>先于 main 函数执行</strong>,代码如下所示:</p>
|
||
<pre><code>func init() {
|
||
|
||
fmt.Println("init in main.go ")
|
||
|
||
}
|
||
</code></pre>
|
||
<p>一个包中可以有多个 init 函数,但是它们的执行顺序并不确定,所以如果你定义了多个 init 函数的话,要确保它们是<strong>相互独立</strong>的,一定<strong>不要有顺序上的依赖</strong>。</p>
|
||
<p>那么 init 函数作用是什么呢? 其实就是在导入一个包时,可以对这个包做一些必要的初始化操作,比如数据库连接和一些数据的检查,确保我们可以正确地使用这个包。</p>
|
||
<h3>Go 语言中的模块</h3>
|
||
<p>如果包是比较低级的代码组织形式的话,那么模块就是更高级别的,在 Go 语言中,一个模块可以包含很多个包,所以模块是相关的包的集合。</p>
|
||
<p>在 Go 语言中:</p>
|
||
<ul>
|
||
<li>一个模块通常是<strong>一个项目</strong>,比如这个专栏实例中使用的 gotour 项目;</li>
|
||
<li>也可以是<strong>一个框</strong>架,比如常用的 Web 框架 gin。</li>
|
||
</ul>
|
||
<h4>go mod</h4>
|
||
<p>Go 语言为我们提供了 go mod 命令来创建一个模块(项目),比如要创建一个 gotour 模块,你可以通过如下命令实现:</p>
|
||
<pre><code>➜ go mod init gotour
|
||
|
||
go: creating new go.mod: module gotour
|
||
</code></pre>
|
||
<p>运行这一命令后,你会看到已经创建好一个名字为 gotour 的文件夹,里面有一个 go.mod 文件,它里面的内容如下所示:</p>
|
||
<pre><code>module gotour
|
||
|
||
go 1.15
|
||
</code></pre>
|
||
<ul>
|
||
<li>第一句是该项目的<strong>模块名</strong>,也就是 gotour;</li>
|
||
<li>第二句表示要编译该模块至少需要<strong>Go 1.15 版本的 SDK</strong>。</li>
|
||
</ul>
|
||
<blockquote>
|
||
<p>小提示:模块名最好是以自己的域名开头,比如 flysnow.org/gotour,这样就可以很大程度上保证模块名的唯一,不至于和其他模块重名。</p>
|
||
</blockquote>
|
||
<h4>使用第三方模块</h4>
|
||
<p>模块化为什么可以提高开发效率?最重要的原因就是<strong>复用了现有的模块</strong>,Go 语言也不例外。比如你可以把项目中的公共代码抽取为一个模块,这样就可以供其他项目使用,不用再重复开发;同理,在 Github 上也有很多开源的 Go 语言项目,它们都是一个个独立的模块,也可以被我们直接使用,提高我们的开发效率,比如 Web 框架 <a href="https://github.com/gin-gonic/gin">gin-gonic/gin</a>。</p>
|
||
<p>众所周知,在使用第三方模块之前,需要先设置下 Go 代理,也就是 GOPROXY,这样我们就可以获取到第三方模块了。</p>
|
||
<p>在这里我推荐 goproxy.io 这个代理,非常好用,速度也很快。要使用这个代理,需要进行如下代码设置:</p>
|
||
<pre><code>go env -w GO111MODULE=on
|
||
|
||
go env -w GOPROXY=https://goproxy.io,direct
|
||
</code></pre>
|
||
<p>打开终端,输入这一命令回车即可设置成功。</p>
|
||
<p>在实际的项目开发中,除了第三方模块外,还有我们<strong>自己开发的模块</strong>,放在了公司的 GitLab上,这时候就要把公司 Git 代码库的域名排除在 Go PROXY 之外,为此 Go 语言提供了GOPRIVATE 这个环境变量帮助我们达到目的。通过如下命令即可设置 GOPRIVATE:</p>
|
||
<pre><code># 设置不走 proxy 的私有仓库,多个用逗号相隔(可选)
|
||
|
||
go env -w GOPRIVATE=*.corp.example.com
|
||
</code></pre>
|
||
<p>以上域名只是一个示例,实际使用时你要改成自己<strong>公司私有仓库的域名</strong>。</p>
|
||
<p>一切都准备好就可以使用第三方的模块了,假设我们要使用 Gin 这个 Web 框架,首先需要安装它,通过如下命令即可安装 Gin 这个 Web 框架:</p>
|
||
<pre><code>go get -u github.com/gin-gonic/gin
|
||
</code></pre>
|
||
<p>安装成功后,就可以像 Go 语言的标准包一样,通过 import 命令导入你的代码中使用它,代码如下所示:</p>
|
||
<pre><code>package main
|
||
|
||
import (
|
||
|
||
"fmt"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
|
||
)
|
||
|
||
func main() {
|
||
|
||
fmt.Println("先导入fmt包,才能使用")
|
||
|
||
r := gin.Default()
|
||
|
||
r.Run()
|
||
|
||
}
|
||
</code></pre>
|
||
<p>以上代码现在还<strong>无法编译通过</strong>,因为还没有同步 Gin 这个模块的依赖,也就是没有把它添加到go.mod 文件中。通过如下命令可以添加缺失的模块:</p>
|
||
<pre><code>go mod tidy
|
||
</code></pre>
|
||
<p>运行这一命令,就可以把缺失的模块添加进来,同时它也可以移除不再需要的模块。这时你再查看 go.mod 文件,会发现内容已经变成了这样:</p>
|
||
<pre><code>module gotour
|
||
|
||
go 1.15
|
||
|
||
require (
|
||
|
||
github.com/gin-gonic/gin v1.6.3
|
||
|
||
github.com/golang/protobuf v1.4.2 // indirect
|
||
|
||
github.com/google/go-cmp v0.5.2 // indirect
|
||
|
||
github.com/kr/text v0.2.0 // indirect
|
||
|
||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||
|
||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||
|
||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||
|
||
github.com/stretchr/testify v1.6.1 // indirect
|
||
|
||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634 // indirect
|
||
|
||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||
|
||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||
|
||
gopkg.in/yaml.v2 v2.3.0 // indirect
|
||
|
||
)
|
||
</code></pre>
|
||
<p>所以我们不用手动去修改 go.mod 文件,通过 Go 语言的工具链比如 go mod tidy 命令,就可以帮助我们自动地维护、自动地添加或者修改 go.mod 的内容。</p>
|
||
<h3>总结</h3>
|
||
<p>在 Go 语言中,包是同一目录中,编译在一起的源文件的集合。包里面含有函数、类型、变量和常量,不同包之间的调用,必须要首字母大写才可以。</p>
|
||
<p>而模块又是相关的包的集合,它里面包含了很多为了实现该模块的包,并且还可以通过模块的方式,把已经完成的模块提供给其他项目(模块)使用,达到了代码复用、研发效率提高的目的。</p>
|
||
<p>所以对于你的项目(模块)来说,它具有<strong>模块 ➡ 包 ➡ 函数类型</strong>这样三层结构,同一个模块中,可以通过包组织代码,达到代码复用的目的;在不同模块中,就需要通过模块的引入,达到这个目的。</p>
|
||
<p>编程界有个谚语:不要重复<strong>造轮子</strong>,使用现成的轮子,可以提高开发效率,降低 Bug 率。Go 语言提供的模块、包这些能力,就可以很好地让我们使用现有的轮子,在多人协作开发中,更好地提高工作效率。</p>
|
||
<p>最后,为你留个作业:基于模块化拆分你所做的项目,提取一些公共的模块,以供更多项目使用。相信这样你们的开发效率会大大提升的。</p>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div style="float: left">
|
||
<a href="/专栏/22 讲通关 Go 语言-完/19 性能优化:Go 语言如何进行代码检查和优化?.md.html">上一页</a>
|
||
</div>
|
||
<div style="float: right">
|
||
<a href="/专栏/22 讲通关 Go 语言-完/21 网络编程:Go 语言如何玩转 RESTful API 服务?.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":"70996dd1fde83d60","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>
|