mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-10-07 18:46:41 +08:00
314 lines
26 KiB
HTML
314 lines
26 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>09 学习开源代码该如何入手?.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="/专栏/消息队列高手课/00 开篇词 优秀的程序员,你的技术栈中不能只有“增删改查”.md.html">00 开篇词 优秀的程序员,你的技术栈中不能只有“增删改查”</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/00 预习 怎样更好地学习这门课?.md.html">00 预习 怎样更好地学习这门课?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/01 为什么需要消息队列?.md.html">01 为什么需要消息队列?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/02 该如何选择消息队列?.md.html">02 该如何选择消息队列?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/03 消息模型:主题和队列有什么区别?.md.html">03 消息模型:主题和队列有什么区别?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/04 如何利用事务消息实现分布式事务?.md.html">04 如何利用事务消息实现分布式事务?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/05 如何确保消息不会丢失.md.html">05 如何确保消息不会丢失</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/06 如何处理消费过程中的重复消息?.md.html">06 如何处理消费过程中的重复消息?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/07 消息积压了该如何处理?.md.html">07 消息积压了该如何处理?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/08 答疑解惑(一) 网关如何接收服务端的秒杀结果?.md.html">08 答疑解惑(一) 网关如何接收服务端的秒杀结果?</a>
|
||
</li>
|
||
<li>
|
||
<a class="current-tab" href="/专栏/消息队列高手课/09 学习开源代码该如何入手?.md.html">09 学习开源代码该如何入手?</a>
|
||
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/10 如何使用异步设计提升系统性能?.md.html">10 如何使用异步设计提升系统性能?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/11 如何实现高性能的异步网络传输?.md.html">11 如何实现高性能的异步网络传输?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/12 序列化与反序列化:如何通过网络传输结构化的数据?.md.html">12 序列化与反序列化:如何通过网络传输结构化的数据?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/13 传输协议:应用程序之间对话的语言.md.html">13 传输协议:应用程序之间对话的语言</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/14 内存管理:如何避免内存溢出和频繁的垃圾回收?.md.html">14 内存管理:如何避免内存溢出和频繁的垃圾回收?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/15 Kafka如何实现高性能IO?.md.html">15 Kafka如何实现高性能IO?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/16 缓存策略:如何使用缓存来减少磁盘IO?.md.html">16 缓存策略:如何使用缓存来减少磁盘IO?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/17 如何正确使用锁保护共享数据,协调异步线程?.md.html">17 如何正确使用锁保护共享数据,协调异步线程?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/18 如何用硬件同步原语(CAS)替代锁?.md.html">18 如何用硬件同步原语(CAS)替代锁?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/19 数据压缩:时间换空间的游戏.md.html">19 数据压缩:时间换空间的游戏</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/20 RocketMQ Producer源码分析:消息生产的实现过程.md.html">20 RocketMQ Producer源码分析:消息生产的实现过程</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/21 Kafka Consumer源码分析:消息消费的实现过程.md.html">21 Kafka Consumer源码分析:消息消费的实现过程</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/22 Kafka和RocketMQ的消息复制实现的差异点在哪?.md.html">22 Kafka和RocketMQ的消息复制实现的差异点在哪?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/23 RocketMQ客户端如何在集群中找到正确的节点?.md.html">23 RocketMQ客户端如何在集群中找到正确的节点?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/24 Kafka的协调服务ZooKeeper:实现分布式系统的“瑞士军刀”.md.html">24 Kafka的协调服务ZooKeeper:实现分布式系统的“瑞士军刀”</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/25 RocketMQ与Kafka中如何实现事务?.md.html">25 RocketMQ与Kafka中如何实现事务?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/26 MQTT协议:如何支持海量的在线IoT设备.md.html">26 MQTT协议:如何支持海量的在线IoT设备</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/27 Pulsar的存储计算分离设计:全新的消息队列设计思路.md.html">27 Pulsar的存储计算分离设计:全新的消息队列设计思路</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/28 答疑解惑(二):我的100元哪儿去了?.md.html">28 答疑解惑(二):我的100元哪儿去了?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/29 流计算与消息(一):通过Flink理解流计算的原理.md.html">29 流计算与消息(一):通过Flink理解流计算的原理</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/30 流计算与消息(二):在流计算中使用Kafka链接计算任务.md.html">30 流计算与消息(二):在流计算中使用Kafka链接计算任务</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/31 动手实现一个简单的RPC框架(一):原理和程序的结构.md.html">31 动手实现一个简单的RPC框架(一):原理和程序的结构</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/32 动手实现一个简单的RPC框架(二):通信与序列化.md.html">32 动手实现一个简单的RPC框架(二):通信与序列化</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/33 动手实现一个简单的RPC框架(三):客户端.md.html">33 动手实现一个简单的RPC框架(三):客户端</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/34 动手实现一个简单的RPC框架(四):服务端.md.html">34 动手实现一个简单的RPC框架(四):服务端</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/35 答疑解惑(三):主流消息队列都是如何存储消息的?.md.html">35 答疑解惑(三):主流消息队列都是如何存储消息的?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/加餐 JMQ的Broker是如何异步处理消息的?.md.html">加餐 JMQ的Broker是如何异步处理消息的?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/消息队列高手课/结束语 程序员如何构建知识体系?.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>09 学习开源代码该如何入手?</h1>
|
||
<p>你好,我是李玥。对于很多开源软件来说,如果我们把它作为我们业务系统的重要组成部分之一,真正地用于生产,仅仅知道如何使用是远远不够的,你必须掌握它的实现原理和很多细节,这样才能找到最佳的使用姿势,当你的系统出现问题时,你才有可能基于它的实现原理,再根据一些现象来排查问题原因。</p>
|
||
<p>掌握这些开源软件的最佳方式就是去学习它的源代码。很多同学跟我说:“我也很想去看一些开源软件的代码,也尝试去看过,但是面对上千个源码文件,几十万行代码,完全不知道从哪儿入手啊。”</p>
|
||
<p>这节课我们就针对这个情况来聊一聊,学习开源软件的代码该如何入手。</p>
|
||
<p>有一点我提前说明一下,对于这节课里面涉及到的一些名词,我会直接使用英文,主要目的是方便你直接对应到那些开源软件英文官网上的标题。</p>
|
||
<h2>通过文档来了解开源项目</h2>
|
||
<p>学习源代码应该从哪儿入手呢?<strong>最佳的方式就是先看它的文档。</strong></p>
|
||
<p>通过看文档,你可以快速地掌握这个软件整体的结构,它有哪些功能特性,它涉及到的关键技术、实现原理和它的生态系统等等。在掌握了这些之后,你对它有个整体的了解,然后再去看它的源代码,就不会再有那种盲人摸象找不到头绪的感觉了。</p>
|
||
<p>首先强调一点是,你必须去看这些开源软件官方网站上的文档,尽量不要去网上搜一些翻译的中文文档。为什么呢?</p>
|
||
<p>因为这些开源软件,特别是一些社区活跃的软件,它的迭代是很快的,即使是自带官方中文翻译的项目,它的中文文档很多都会落后于英文版,你能看到的中文版本很多时候都已经过时了。那非官方的翻译,问题可能就不止是过时的问题了,可能还会出现一些错漏的地方。所以,最好还是直接来看官方的英文文档。</p>
|
||
<p>如果说你的英文阅读水平确实有限,直接阅读英文文档有困难或者看得非常慢,怎么办?你还是要按照我接下来告诉你的方法去看它的英文官网,即使阅读大段的技术文章有困难,网站的标题你总能看懂吧?找到你需要阅读的文章后,你可以在网上搜一下对应的中文版本,先看一遍中文版,然后再对着英文原版过一遍,弥补中文版可能过时或翻译不准确的问题。</p>
|
||
<p>开源社区经过这么多年的发展,它已经形成一个相对比较成熟的文化。每个开源软件,代码如何管理、社区成员如何沟通、如何协作这些都已经形成了一个比较固定的套路。大多数开源软件,它的官网和技术文档也是有一个相对比较固定的结构的。</p>
|
||
<p>接下来我们以<a href="https://kafka.apache.org/">Kafka 的官网</a>为例子,来说下怎么来看它的文档。</p>
|
||
<p>如果说你对这个项目完全不了解,没用过这个软件,你首先需要看的文档是<a href="https://kafka.apache.org/documentation/#quickstart">Quick Start</a>,按照 Quick Start 中的指导快速把它的环境搭起来,把它运行起来,这样你会对这个项目有个感性认识,也便于你在后续深入学习的时候“跑”一些例子。</p>
|
||
<p>然后你需要找一下它的<a href="https://kafka.apache.org/documentation/#introduction">Introduction</a>,一般里面会有项目的基本介绍。这里面很重要的一点是,你需要找到这个项目用到的一些基本概念或者名词的介绍文档,在 Kafka 的文档中,这些内容就在 Introduction 里面,比如 Topic、Producer、 Consumer、Partition 这些概念在 Kafka 中代表的含义。</p>
|
||
<p>有些开源项目会单独有一个 Basic Concepts 文档来讲这些基础概念。这个文档非常重要,因为这些开源社区的开发者都有个很不好的爱好:发明概念。很多开源项目都会自己创造一些名词或者概念,了解这些基本概念才有可能看懂它项目的其他文档。</p>
|
||
<p>对项目有个基本的了解之后呢,接下来你可以看一下它的使用场景、功能特性以及相关的生态系统的介绍。在 Kafka 中功能相关的内容在<a href="https://kafka.apache.org/documentation/#uses">Use cases</a>和<a href="https://cwiki.apache.org/confluence/display/KAFKA/Ecosystem">EcoSystem</a>两篇文章中,有些项目中会有类似名为 Features 的文档介绍功能和特性。</p>
|
||
<p>其中项目的生态系统,也就是 EcoSystem,一般会介绍它这个项目适用的一些典型的使用场景,在某个场景下适合与哪些其他的系统一起来配合使用等。如果说你的系统不是特别特殊或者说冷门的话,你大概率可以在 EcoSystem 里面找到和你类似的场景,可以少走很多的弯路。</p>
|
||
<p>你在读完上面这些文档之后,对这个项目的整体应该会有一个比较全面的了解了,比如说:</p>
|
||
<ul>
|
||
<li>这个项目是干什么的?</li>
|
||
<li>能解决哪些问题?</li>
|
||
<li>适合在哪些场景使用?</li>
|
||
<li>有哪些功能?</li>
|
||
<li>如何使用?</li>
|
||
</ul>
|
||
<p>对这些问题有一个初步的答案之后,接下来你就可以去深入学习它的实现原理了。这是不是意味着,你可以立即去看它的源码呢?这样做或许可行,但并不是最好的方法。</p>
|
||
<p>你知道大部分开源项目都是怎么诞生的吗?一般来说是这样的:某个大学或者大厂的科学家,某天脑海里突然出现了一个改变世界的想法,科学家们会基于这个想法做一些深入的研究,然后写了一篇论文在某个学术期刊或者会议上发表。论文发表后在业内获得很多的赞,这时候就轮到像 Google、Facebook 这样的大厂出手了:这个论文很有价值,不如我们把它实现出来吧?一个开源项目就这样诞生了。</p>
|
||
<p>所以,对于这样的开源项目,它背后的这篇论文就是整个项目的灵魂,你如果能把这篇论文看完并且理解透了,这个项目的实现原理也就清楚了。</p>
|
||
<p>对于 Kafka 来说,它的灵魂是这篇博文:<a href="https://engineering.linkedin.com/distributed-systems/log-what-every-software-engineer-should-know-about-real-time-datas-unifying">The Log: What every software engineer should know about real-time data’s unifying abstraction</a>,对应的中文译稿在这里:《<a href="http://www.kancloud.cn/kancloud/log-real-time-datas-unifying/58708">日志:每个软件工程师都应该知道的有关实时数据的统一抽象</a>》。</p>
|
||
<p>这篇博文被评为<a href="https://bryanpendleton.blogspot.hk/2014/01/the-log-epic-software-engineering.html">程序员史诗般必读文章</a>,无论你是不是想了解 Kafka 的实现原理,我都强烈推荐你好好读一下上面这篇博文。</p>
|
||
<p>学习完项目灵魂,就可以开始阅读源码了。</p>
|
||
<h2>用以点带面的方式来阅读源码</h2>
|
||
<p>需要注意的是,你在读源码的时候,千万不要上来就找 main 方法这样泛泛地去看,为什么?你可以想一下,一篇文章,它是一个线性结构,你从前往后读就行了。一本书呢?如果我们看目录的话,可以认为是个树状结构,但大多数的书的内容还是按照线性结构来组织的,你可以从前往后读,也可以通过目录跳着读。</p>
|
||
<p>那程序的源代码是什么结构?那是一个网状结构,关系错综复杂,所以这种结构是非常不适合人类去阅读的。你如果是泛泛去读源代码,很容易迷失在这个代码织成的网里面。那怎么办?</p>
|
||
<p>我推荐大家阅读源码的方式是,**带着问题去读源码,最好是带着问题的答案去读源码。**你每次读源码之前,确定一个具体的问题,比如:</p>
|
||
<ul>
|
||
<li>RocketMQ 的消息是怎么写到文件里的?</li>
|
||
<li>Kafka 的 Coordinator 是怎么维护消费位置的?</li>
|
||
</ul>
|
||
<p>类似这种非常细粒度的问题,粒度细到每个问题的答案就是一两个流程就可以回答,这样就可以了。如果说你就想学习一下源代码,或者说提不出这些问题怎么办呢?答案还是,<strong>看文档。</strong></p>
|
||
<p>确定问题后,先不要着急看源代码,而是应该先找一下是否有对应的实现文档,一般来说,核心功能都会有专门的文档来说明它的实现原理,比如在 Kafka 的文档中,<a href="https://kafka.apache.org/documentation/#design">DESIGN</a>和<a href="https://kafka.apache.org/documentation/#implementation">IMPLEMENTATION</a>两个章节中,介绍了 Kafka 很多功能的实现原理和细节。一些更细节的非核心的功能不一定有专门的文档来说明,但是我们可以去找一找是否有对应的 Improvement Proposal。(Kafka 的所有 Improvement Proposals 在<a href="https://cwiki.apache.org/confluence/display/KAFKA/Kafka+Improvement+Proposals">这里</a>。)</p>
|
||
<p>这个 Improvement Proposal 是什么呢?你可以认为它是描述一个新功能的文档,一般开源项目需要增加一个新的功能或者特性的时候,都会创建一个 Improvement Proposal,一般标题都是"xIP- 新功能名称",其中 IP 就是 Improvement Proposal 的缩写,x 一般就是这个开源项目的名称的首字母,比如 Kafka 中 Improvement Proposal 的标题就都是以 KIP 来开头。</p>
|
||
<p>每个 Improvement Proposal 都是有固定格式的,一般要说明为什么需要增加这个功能,会对系统产生那些影响和改变,还有我们最关心的设计和实现原理的简述。</p>
|
||
<p>你读完讲解实现的文档再去看源代码,也就是我刚刚说的,不只是带着问题去读,而是带着答案去读源码。这样你在读源码的时候,不仅仅是更容易理解源代码,还可以把更多的精力放在一些实现细节上,这样阅读源码的效果会更好。</p>
|
||
<p>使用这种以问题为阅读单元的方式来读源代码,你每次只要花很短的时间,阅读很少的一部分源码,就能解决一个问题,得到一些收获。这种方式其实是通过一个一个的问题,在网状的源代码中,每次去读几个点组成的那一两条线。随着你通过阅读源码了解的问题越来越多,你对项目源码的理解也会越来越全面和深入。</p>
|
||
<h2>小结</h2>
|
||
<p>如果你想了解一个开源项目,学习它的代码,最佳的切入点就是去读它的官方文档,这些文档里面,最重要的灵魂就是项目背后的那篇论文,它一般是这个开源项目的理论基础。</p>
|
||
<p>在阅读源码的时候呢,最佳的方式是带着问题去阅读,最好是带着问题的答案去读,这样难度低、周期短、收获快。不要想着一定要从总体上去全面掌握一个项目的所有源代码,也没有必要。</p>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div style="float: left">
|
||
<a href="/专栏/消息队列高手课/08 答疑解惑(一) 网关如何接收服务端的秒杀结果?.md.html">上一页</a>
|
||
</div>
|
||
<div style="float: right">
|
||
<a href="/专栏/消息队列高手课/10 如何使用异步设计提升系统性能?.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":"709979570daf3cfa","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>
|