mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-26 05:06:42 +08:00
467 lines
23 KiB
HTML
467 lines
23 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>24 消息队列——发布订阅模式.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="/专栏/Redis 核心原理与实战/01 Redis 是如何执行的.md.html">01 Redis 是如何执行的</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/02 Redis 快速搭建与使用.md.html">02 Redis 快速搭建与使用</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/03 Redis 持久化——RDB.md.html">03 Redis 持久化——RDB</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/04 Redis 持久化——AOF.md.html">04 Redis 持久化——AOF</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/05 Redis 持久化——混合持久化.md.html">05 Redis 持久化——混合持久化</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/06 字符串使用与内部实现原理.md.html">06 字符串使用与内部实现原理</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/07 附录:更多字符串操作命令.md.html">07 附录:更多字符串操作命令</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/08 字典使用与内部实现原理.md.html">08 字典使用与内部实现原理</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/09 附录:更多字典操作命令.md.html">09 附录:更多字典操作命令</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/10 列表使用与内部实现原理.md.html">10 列表使用与内部实现原理</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/11 附录:更多列表操作命令.md.html">11 附录:更多列表操作命令</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/12 集合使用与内部实现原理.md.html">12 集合使用与内部实现原理</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/13 附录:更多集合操作命令.md.html">13 附录:更多集合操作命令</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/14 有序集合使用与内部实现原理.md.html">14 有序集合使用与内部实现原理</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/15 附录:更多有序集合操作命令.md.html">15 附录:更多有序集合操作命令</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/16 Redis 事务深入解析.md.html">16 Redis 事务深入解析</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/17 Redis 键值过期操作.md.html">17 Redis 键值过期操作</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/18 Redis 过期策略与源码分析.md.html">18 Redis 过期策略与源码分析</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/19 Redis 管道技术——Pipeline.md.html">19 Redis 管道技术——Pipeline</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/20 查询附近的人——GEO.md.html">20 查询附近的人——GEO</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/21 游标迭代器(过滤器)——Scan.md.html">21 游标迭代器(过滤器)——Scan</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/22 优秀的基数统计算法——HyperLogLog.md.html">22 优秀的基数统计算法——HyperLogLog</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/23 内存淘汰机制与算法.md.html">23 内存淘汰机制与算法</a>
|
||
</li>
|
||
<li>
|
||
<a class="current-tab" href="/专栏/Redis 核心原理与实战/24 消息队列——发布订阅模式.md.html">24 消息队列——发布订阅模式</a>
|
||
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/25 消息队列的其他实现方式.md.html">25 消息队列的其他实现方式</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/26 消息队列终极解决方案——Stream(上).md.html">26 消息队列终极解决方案——Stream(上)</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/27 消息队列终极解决方案——Stream(下).md.html">27 消息队列终极解决方案——Stream(下)</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/28 实战:分布式锁详解与代码.md.html">28 实战:分布式锁详解与代码</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/29 实战:布隆过滤器安装与使用及原理分析.md.html">29 实战:布隆过滤器安装与使用及原理分析</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/30 完整案例:实现延迟队列的两种方法.md.html">30 完整案例:实现延迟队列的两种方法</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/31 实战:定时任务案例.md.html">31 实战:定时任务案例</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/32 实战:RediSearch 高性能的全文搜索引擎.md.html">32 实战:RediSearch 高性能的全文搜索引擎</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/33 实战:Redis 性能测试.md.html">33 实战:Redis 性能测试</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/34 实战:Redis 慢查询.md.html">34 实战:Redis 慢查询</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/35 实战:Redis 性能优化方案.md.html">35 实战:Redis 性能优化方案</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/36 实战:Redis 主从同步.md.html">36 实战:Redis 主从同步</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/37 实战:Redis哨兵模式(上).md.html">37 实战:Redis哨兵模式(上)</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/38 实战:Redis 哨兵模式(下).md.html">38 实战:Redis 哨兵模式(下)</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/39 实战:Redis 集群模式(上).md.html">39 实战:Redis 集群模式(上)</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/40 实战:Redis 集群模式(下).md.html">40 实战:Redis 集群模式(下)</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/41 案例:Redis 问题汇总和相关解决方案.md.html">41 案例:Redis 问题汇总和相关解决方案</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/42 技能学习指南.md.html">42 技能学习指南</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/Redis 核心原理与实战/43 加餐:Redis 的可视化管理工具.md.html">43 加餐:Redis 的可视化管理工具</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>24 消息队列——发布订阅模式</h1>
|
||
<p>在 Redis 中提供了专门的类型:Publisher(发布者)和 Subscriber(订阅者)来实现消息队列。</p>
|
||
<p>在文章开始之前,先来介绍消息队列中有几个基础概念,以便大家更好的理解本文的内容。</p>
|
||
<p>首先,发布消息的叫做发布方或发布者,也就是消息的生产者,而接收消息的叫做消息的订阅方或订阅者,也就是消费者,用来处理生产者发布的消息。</p>
|
||
<p><img src="assets/33a25110-6905-11ea-a947-1f4a9107200f" alt="生产者-消费者基础概念-2.png" /></p>
|
||
<p>除了发布和和订阅者,在消息队列中还有一个重要的概念:channel 意为频道或通道,可以理解为某个消息队列的名称,首先消费者先要订阅某个 channel,然后当生产者把消息发送到这个 channel 中时,消费者就可以正常接收到消息了,如下图所示:</p>
|
||
<p><img src="assets/643cf190-6905-11ea-9247-611fe26c272c" alt="频道channel.png" /></p>
|
||
<h3>普通订阅与发布</h3>
|
||
<p>消息队列有两个重要的角色,一个是发送者,另一个就是订阅者,对应的命令如下:</p>
|
||
<ul>
|
||
<li>发布消息:publish channel "message"</li>
|
||
<li>订阅消息:subscribe channel</li>
|
||
</ul>
|
||
<p>下面我们来看具体的命令实现。</p>
|
||
<h4><strong>订阅消息</strong></h4>
|
||
<pre><code class="language-shell">127.0.0.1:6379> subscribe channel #订阅消息channel
|
||
Reading messages...
|
||
1) "subscribe"
|
||
2) "channel"
|
||
3) (integer) 1
|
||
</code></pre>
|
||
<p>相关语法:</p>
|
||
<pre><code>subscribe channel [channel ...]
|
||
</code></pre>
|
||
<p>此命令支持订阅一个或多个频道的命令,也就是说一个订阅者可以订阅多个频道。例如,某个客户端订阅了两个频道 channel 和 channel2,当两个发布者分别推送消息后,订阅者的信息输出如下:</p>
|
||
<pre><code class="language-shell">127.0.0.1:6379> subscribe channel channel2 #订阅 channel 和 channel2
|
||
Reading messages... (press Ctrl-C to quit)
|
||
1) "subscribe"
|
||
2) "channel"
|
||
3) (integer) 1
|
||
1) "subscribe"
|
||
2) "channel2"
|
||
3) (integer) 2
|
||
1) "message"
|
||
2) "channel" # 收到 channel 消息
|
||
3) "message 1."
|
||
1) "message"
|
||
2) "channel2" # 收到 channel2 消息
|
||
3) "message 2."
|
||
</code></pre>
|
||
<p>可以看出此订阅者可以收到来自两个频道的消息推送。</p>
|
||
<h4><strong>发送消息</strong></h4>
|
||
<pre><code class="language-shell">127.0.0.1:6379> publish channel "hello,redis." #发布消息
|
||
(integer) 1
|
||
</code></pre>
|
||
<p>相关语法:</p>
|
||
<pre><code>publish channel message
|
||
</code></pre>
|
||
<p>最后的返回值表示成功发送给几个订阅方,1 表示成功发给了一个订阅者,这个数字可以是 0~n,这是由订阅者的数量决定的。</p>
|
||
<p>例如,当有两个订阅者时,推送的结果为 2,如下代码所示。</p>
|
||
<p>订阅者一:</p>
|
||
<pre><code class="language-shell">127.0.0.1:6379> subscribe channel
|
||
Reading messages... (press Ctrl-C to quit)
|
||
1) "subscribe"
|
||
2) "channel"
|
||
3) (integer) 1
|
||
</code></pre>
|
||
<p>订阅者二:</p>
|
||
<pre><code class="language-shell">127.0.0.1:6379> subscribe channel
|
||
Reading messages... (press Ctrl-C to quit)
|
||
1) "subscribe"
|
||
2) "channel"
|
||
3) (integer) 1
|
||
</code></pre>
|
||
<p>发送消息:</p>
|
||
<pre><code class="language-shell">127.0.0.1:6379> publish channel "message"
|
||
(integer) 2
|
||
</code></pre>
|
||
<p>可以看出,此消息已成功发给两个订阅者,结果也变成 2 了。</p>
|
||
<h3>主题订阅</h3>
|
||
<p>上面介绍了普通的订阅与发布模式,但如果我要订阅某一个类型的消息就不适用了,例如我要订阅日志类的消息队列,它们的命名都是 logXXX,这个时候就需要使用 Redis 提供的另一个功能 Pattern Subscribe 主题订阅,这种方式可以使用 <code>*</code> 来匹配多个频道,如下图所示:</p>
|
||
<p><img src="assets/c071d520-6905-11ea-bfcb-156eb66fb883" alt="主题订阅2.png" /></p>
|
||
<p>主题模式的具体实现代码如下,订阅者:</p>
|
||
<pre><code class="language-shell">127.0.0.1:6379> psubscribe log_* #主题订阅 log_*
|
||
1) "psubscribe"
|
||
2) "log_*"
|
||
3) (integer) 1
|
||
1) "pmessage"
|
||
2) "log_*"
|
||
3) "log_user" #接收到频道 log_user 的消息推送
|
||
4) "user message."
|
||
1) "pmessage"
|
||
2) "log_*"
|
||
3) "log_sys" #接收到频道 log_sys 的消息推送
|
||
4) "sys message."
|
||
1) "pmessage"
|
||
2) "log_*"
|
||
3) "log_db" #接收到频道 log_db 的消息推送
|
||
4) "db message"
|
||
</code></pre>
|
||
<p>从上面的运行结果,可以看出使用命令 <code>psubscribe log_*</code> 可以接收到所有频道包含 log_XXX 的消息。</p>
|
||
<p>相关语法:</p>
|
||
<pre><code>psubscribe pattern [pattern ...]
|
||
</code></pre>
|
||
<p>生产者的代码如下:</p>
|
||
<pre><code class="language-shell">127.0.0.1:6379> publish log_user "user message."
|
||
(integer) 1
|
||
127.0.0.1:6379> publish log_sys "sys message."
|
||
(integer) 1
|
||
127.0.0.1:6379> publish log_db "db message"
|
||
(integer) 1
|
||
</code></pre>
|
||
<h3>代码实战</h3>
|
||
<p>下面我们使用 Jedis 实现普通的发布订阅模式和主题订阅的功能。</p>
|
||
<h4><strong>普通模式</strong></h4>
|
||
<p>消费者代码如下:</p>
|
||
<pre><code class="language-java">/**
|
||
* 消费者
|
||
*/
|
||
public static void consumer() {
|
||
Jedis jedis = new Jedis("127.0.0.1", 6379);
|
||
// 接收并处理消息
|
||
jedis.subscribe(new JedisPubSub() {
|
||
@Override
|
||
public void onMessage(String channel, String message) {
|
||
// 接收消息,业务处理
|
||
System.out.println("频道 " + channel + " 收到消息:" + message);
|
||
}
|
||
}, "channel");
|
||
}
|
||
</code></pre>
|
||
<p>生产者代码如下:</p>
|
||
<pre><code class="language-java">/**
|
||
* 生产者
|
||
*/
|
||
public static void producer() {
|
||
Jedis jedis = new Jedis("127.0.0.1", 6379);
|
||
// 推送消息
|
||
jedis.publish("channel", "Hello, channel.");
|
||
}
|
||
</code></pre>
|
||
<p>发布者和订阅者模式运行:</p>
|
||
<pre><code class="language-java">public static void main(String[] args) throws InterruptedException {
|
||
// 创建一个新线程作为消费者
|
||
new Thread(() -> consumer()).start();
|
||
// 暂停 0.5s 等待消费者初始化
|
||
Thread.sleep(500);
|
||
// 生产者发送消息
|
||
producer();
|
||
}
|
||
</code></pre>
|
||
<p>以上代码运行结果如下:</p>
|
||
<pre><code>频道 channel 收到消息:Hello, channel.
|
||
</code></pre>
|
||
<h4><strong>主题订阅模式</strong></h4>
|
||
<p>主题订阅模式的生产者的代码是一样,只有消费者的代码是不同的,如下所示:</p>
|
||
<pre><code class="language-java">/**
|
||
* 主题订阅
|
||
*/
|
||
public static void pConsumer() {
|
||
Jedis jedis = new Jedis("127.0.0.1", 6379);
|
||
// 主题订阅
|
||
jedis.psubscribe(new JedisPubSub() {
|
||
@Override
|
||
public void onPMessage(String pattern, String channel, String message) {
|
||
// 接收消息,业务处理
|
||
System.out.println(pattern + " 主题 | 频道 " + channel + " 收到消息:" + message);
|
||
}
|
||
}, "channel*");
|
||
}
|
||
</code></pre>
|
||
<p>主题模式运行代码如下:</p>
|
||
<pre><code class="language-java">public static void main(String[] args) throws InterruptedException {
|
||
// 主题订阅
|
||
new Thread(() -> pConsumer()).start();
|
||
// 暂停 0.5s 等待消费者初始化
|
||
Thread.sleep(500);
|
||
// 生产者发送消息
|
||
producer();
|
||
}
|
||
</code></pre>
|
||
<p>以上代码运行结果如下:</p>
|
||
<pre><code>channel* 主题 | 频道 channel 收到消息:Hello, channel.
|
||
</code></pre>
|
||
<h3>注意事项</h3>
|
||
<p>发布订阅模式存在以下两个缺点:</p>
|
||
<ol>
|
||
<li>无法持久化保存消息,如果 Redis 服务器宕机或重启,那么所有的消息将会丢失;</li>
|
||
<li>发布订阅模式是“发后既忘”的工作模式,如果有订阅者离线重连之后不能消费之前的历史消息。</li>
|
||
</ol>
|
||
<p>然而这些缺点在 Redis 5.0 添加了 Stream 类型之后会被彻底的解决。</p>
|
||
<p>除了以上缺点外,发布订阅模式还有另一个需要注意问题:当消费端有一定的消息积压时,也就是生产者发送的消息,消费者消费不过来时,如果超过 32M 或者是 60s 内持续保持在 8M 以上,消费端会被强行断开,这个参数是在配置文件中设置的,默认值是 <code>client-output-buffer-limit pubsub 32mb 8mb 60</code>。</p>
|
||
<h3>小结</h3>
|
||
<p>本文介绍了消息队列的几个名词,生产者、消费者对应的就是消息的发送者和接收者,也介绍了发布订阅模式的三个命令:</p>
|
||
<ul>
|
||
<li>subscribe channel 普通订阅</li>
|
||
<li>publish channel message 消息推送</li>
|
||
<li>psubscribe pattern 主题订阅</li>
|
||
</ul>
|
||
<p>使用它们之后就可以完成单个频道和多个频道的消息收发,但发送与订阅模式也有一些缺点,比如“发后既忘”和不能持久化等问题,然而这些问题会等到 Stream 类型的出现而得到解决,关于更多 Stream 的内容后面文章会详细介绍。</p>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div style="float: left">
|
||
<a href="/专栏/Redis 核心原理与实战/23 内存淘汰机制与算法.md.html">上一页</a>
|
||
</div>
|
||
<div style="float: right">
|
||
<a href="/专栏/Redis 核心原理与实战/25 消息队列的其他实现方式.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":"709973e3ec403d60","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>
|