当前位置: 首页 > news >正文

HTML5系列(5)-- SVG 集成详解

前端技术探索系列:HTML5 SVG 集成详解 🎨

开篇寄语 👋

前端开发者们,

在前五篇文章中,我们探讨了 HTML5 的多个特性。今天,让我们深入了解 SVG 的魅力,看看如何创建可缩放的矢量图形。

一、SVG 基础入门 ✨

1. SVG 语法基础

<!-- 内联 SVG -->
<svg width="200" height="200" viewBox="0 0 200 200"><!-- 基础图形 --><circle cx="100" cy="100" r="50" fill="blue" /><rect x="50" y="50" width="100" height="100" fill="red" opacity="0.5" /><path d="M10 10 H 90 V 90 H 10 Z" fill="none" stroke="black" />
</svg>

2. 基础图形绘制

<svg width="400" height="400"><!-- 矩形 --><rectx="10" y="10"width="100" height="50"fill="blue"stroke="black"stroke-width="2"rx="10" ry="10"/><!-- 圆形 --><circlecx="200" cy="50"r="40"fill="red"stroke="darkred"stroke-width="2"/><!-- 椭圆 --><ellipsecx="300" cy="50"rx="60" ry="30"fill="green"/><!-- 线条 --><linex1="10" y1="100"x2="100" y2="150"stroke="purple"stroke-width="2"/><!-- 多边形 --><polygonpoints="200,100 250,150 150,150"fill="orange"/>
</svg>

3. 复用与组织

<!-- 定义可重用元素 -->
<svg><defs><!-- 渐变定义 --><linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="0%" style="stop-color:rgb(255,255,0);" /><stop offset="100%" style="stop-color:rgb(255,0,0);" /></linearGradient><!-- 符号定义 --><symbol id="star" viewBox="0 0 32 32"><path d="M16 0 L20 12 L32 12 L22 20 L26 32 L16 24 L6 32 L10 20 L0 12 L12 12 Z" /></symbol></defs><!-- 使用定义的元素 --><rect width="200" height="100" fill="url(#gradient)" /><use href="#star" x="50" y="50" width="32" height="32" fill="gold" />
</svg>

二、SVG 动画技术 🎬

1. SMIL 动画

<svg width="300" height="300"><circle cx="150" cy="150" r="50" fill="blue"><!-- 位置动画 --><animateTransformattributeName="transform"type="translate"values="0,0; 50,0; 0,0"dur="2s"repeatCount="indefinite"/><!-- 颜色动画 --><animateattributeName="fill"values="blue;red;blue"dur="3s"repeatCount="indefinite"/><!-- 路径动画 --><animateMotionpath="M 0 0 H 100 V 100 H 0 Z"dur="4s"repeatCount="indefinite"/></circle>
</svg>

2. CSS 动画

<style>
.rotating-star {animation: rotate 3s linear infinite;transform-origin: center;
}@keyframes rotate {from { transform: rotate(0deg); }to { transform: rotate(360deg); }
}.pulsing-circle {animation: pulse 2s ease-in-out infinite;
}@keyframes pulse {0% { transform: scale(1); }50% { transform: scale(1.2); }100% { transform: scale(1); }
}
</style><svg width="200" height="200"><use href="#star" class="rotating-star" x="50" y="50" width="100" height="100" /><circle class="pulsing-circle" cx="100" cy="100" r="50" fill="purple" />
</svg>

3. JavaScript 动画

class SVGAnimator {constructor(element) {this.element = element;this.animations = new Map();}// 添加动画animate(properties, duration, easing = 'linear') {const startTime = performance.now();const initialValues = {};// 获取初始值for (const prop in properties) {initialValues[prop] = parseFloat(this.element.getAttribute(prop));}const animation = {startTime,duration,initialValues,targetValues: properties,easing};this.animations.set(animation, true);this.startAnimation();}// 动画循环startAnimation() {if (this.animationFrame) return;const animate = (currentTime) => {let hasRunning = false;this.animations.forEach((isRunning, animation) => {if (!isRunning) return;const elapsed = currentTime - animation.startTime;const progress = Math.min(elapsed / animation.duration, 1);if (progress < 1) {hasRunning = true;this.updateProperties(animation, progress);} else {this.animations.delete(animation);}});if (hasRunning) {this.animationFrame = requestAnimationFrame(animate);} else {this.animationFrame = null;}};this.animationFrame = requestAnimationFrame(animate);}// 更新属性updateProperties(animation, progress) {for (const prop in animation.targetValues) {const initial = animation.initialValues[prop];const target = animation.targetValues[prop];const current = initial + (target - initial) * progress;this.element.setAttribute(prop, current);}}
}

三、SVG 交互实现 🖱️

1. 事件处理

class SVGInteraction {constructor(svg) {this.svg = svg;this.elements = new Map();this.setupEvents();}setupEvents() {this.svg.addEventListener('click', this.handleClick.bind(this));this.svg.addEventListener('mousemove', this.handleMouseMove.bind(this));}// 获取 SVG 坐标getSVGPoint(event) {const point = this.svg.createSVGPoint();point.x = event.clientX;point.y = event.clientY;return point.matrixTransform(this.svg.getScreenCTM().inverse());}// 添加可交互元素addInteractiveElement(element, handlers) {this.elements.set(element, handlers);element.addEventListener('mouseenter', () => {if (handlers.hover) {handlers.hover(element, true);}});element.addEventListener('mouseleave', () => {if (handlers.hover) {handlers.hover(element, false);}});}handleClick(event) {const point = this.getSVGPoint(event);this.elements.forEach((handlers, element) => {if (this.isPointInElement(point, element) && handlers.click) {handlers.click(element);}});}handleMouseMove(event) {const point = this.getSVGPoint(event);this.elements.forEach((handlers, element) => {if (this.isPointInElement(point, element) && handlers.move) {handlers.move(element, point);}});}isPointInElement(point, element) {const bbox = element.getBBox();return (point.x >= bbox.x &&point.x <= bbox.x + bbox.width &&point.y >= bbox.y &&point.y <= bbox.y + bbox.height);}
}

2. 滤镜效果

<svg width="400" height="400"><defs><!-- 高斯模糊 --><filter id="blur"><feGaussianBlur stdDeviation="2" /></filter><!-- 阴影效果 --><filter id="shadow"><feDropShadow dx="2" dy="2" stdDeviation="2" /></filter><!-- 发光效果 --><filter id="glow"><feGaussianBlur stdDeviation="2" result="coloredBlur" /><feMerge><feMergeNode in="coloredBlur" /><feMergeNode in="SourceGraphic" /></feMerge></filter></defs><!-- 使用滤镜 --><circle cx="100" cy="100" r="50" fill="purple" filter="url(#glow)" />
</svg>

四、实践项目:动态 LOGO 生成器 🎯

class LogoGenerator {constructor(container) {this.container = container;this.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');this.setup();}setup() {this.svg.setAttribute('width', '300');this.svg.setAttribute('height', '300');this.container.appendChild(this.svg);// 定义滤镜和渐变this.defineFilters();this.defineGradients();}defineFilters() {const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');// 添加发光效果const glowFilter = `<filter id="logo-glow"><feGaussianBlur stdDeviation="2" result="blur" /><feFlood flood-color="rgba(0,0,255,0.3)" result="color" /><feComposite in="color" in2="blur" operator="in" /><feMerge><feMergeNode /><feMergeNode in="SourceGraphic" /></feMerge></filter>`;defs.innerHTML = glowFilter;this.svg.appendChild(defs);}defineGradients() {const defs = this.svg.querySelector('defs');// 添加渐变const gradient = `<linearGradient id="logo-gradient" x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="0%" style="stop-color:#4CAF50" /><stop offset="100%" style="stop-color:#2196F3" /></linearGradient>`;defs.innerHTML += gradient;}// 生成 LogogenerateLogo(text, options = {}) {const {fontSize = 48,fontFamily = 'Arial',color = 'url(#logo-gradient)',useGlow = true} = options;// 清空现有内容while (this.svg.lastChild) {this.svg.removeChild(this.svg.lastChild);}// 重新添加 defsthis.defineFilters();this.defineGradients();// 创建文本元素const textElement = document.createElementNS('http://www.w3.org/2000/svg', 'text');textElement.textContent = text;textElement.setAttribute('x', '150');textElement.setAttribute('y', '150');textElement.setAttribute('text-anchor', 'middle');textElement.setAttribute('dominant-baseline', 'middle');textElement.setAttribute('font-size', fontSize);textElement.setAttribute('font-family', fontFamily);textElement.setAttribute('fill', color);if (useGlow) {textElement.setAttribute('filter', 'url(#logo-glow)');}// 添加动画const animation = document.createElementNS('http://www.w3.org/2000/svg', 'animateTransform');animation.setAttribute('attributeName', 'transform');animation.setAttribute('type', 'scale');animation.setAttribute('values', '1;1.1;1');animation.setAttribute('dur', '2s');animation.setAttribute('repeatCount', 'indefinite');textElement.appendChild(animation);this.svg.appendChild(textElement);return this;}// 导出 SVGexport() {const serializer = new XMLSerializer();return serializer.serializeToString(this.svg);}// 导出 PNGasync exportPNG() {return new Promise((resolve, reject) => {const image = new Image();image.onload = () => {const canvas = document.createElement('canvas');canvas.width = 300;canvas.height = 300;const ctx = canvas.getContext('2d');ctx.drawImage(image, 0, 0);resolve(canvas.toDataURL('image/png'));};image.onerror = reject;const svgBlob = new Blob([this.export()], { type: 'image/svg+xml' });image.src = URL.createObjectURL(svgBlob);});}
}// 使用示例
const logoGen = new LogoGenerator(document.getElementById('logo-container'));
logoGen.generateLogo('LOGO', {fontSize: 64,useGlow: true
});

性能优化建议 🚀

  1. SVG 优化

    • 使用 <use> 复用元素
    • 压缩 SVG 代码
    • 避免不必要的精度
  2. 动画优化

    • 使用 CSS 动画代替 SMIL
    • 使用 transform 代替位置属性
    • 避免频繁的 DOM 操作
  3. 交互优化

    • 使用事件委托
    • 优化碰撞检测
    • 使用 requestAnimationFrame

浏览器兼容性 🌐

特性ChromeFirefoxSafariEdge
基础 SVG
SMIL
CSS 动画
滤镜

实用工具推荐 🛠️

  1. SVG 编辑器

    • Inkscape
    • Adobe Illustrator
    • Figma
  2. SVG 优化工具

    • SVGO
    • SVG OMG
    • SVG Optimizer
  3. JavaScript 库

    • Snap.svg
    • SVG.js
    • GreenSock

总结 🎯

SVG 为我们提供了强大的矢量图形能力:

  • 可缩放性 📏
  • 动画效果 🎬
  • 交互能力 🖱️
  • 滤镜效果 🎨

如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇

终身学习,共同成长。

咱们下一期见

💻

相关文章:

HTML5系列(5)-- SVG 集成详解

前端技术探索系列&#xff1a;HTML5 SVG 集成详解 &#x1f3a8; 开篇寄语 &#x1f44b; 前端开发者们&#xff0c; 在前五篇文章中&#xff0c;我们探讨了 HTML5 的多个特性。今天&#xff0c;让我们深入了解 SVG 的魅力&#xff0c;看看如何创建可缩放的矢量图形。 一、…...

深度学习常见数据集处理方法

1、数据集格式转换&#xff08;json转txt&#xff09; import json import os 任务&#xff1a;实例分割&#xff0c;labelme的json文件, 转txt文件 Ultralytics YOLO format <class-index> <x1> <y1> <x2> <y2> ... <xn> <yn> # 类…...

1180 - 【入门】数字出现次数

题目描述 有50个数&#xff08;0-19&#xff09;&#xff0c;求这50个数中相同数字出现的最多次数为几次&#xff1f; 输入 50个数字 输出 1个数字&#xff08;即相同数字出现的最多次数&#xff09; 样例 输入 复制 1 10 2 0 15 8 12 7 0 3 15 0 15 18 16 7 17 16 9 …...

C++20: 像Python一样split字符串

概要 Python 的字符串天生支持 split( ) 操作&#xff0c;支持单个字符或字符串作为分隔符。 C 在这方面显得很笨拙&#xff0c;但是在 C20 下经过一番尝试&#xff0c;还是能够提供类似的简洁调用。 Python 代码 s 0,11,336,23,370nums s.split(,) for n in nums:print(n…...

Unity3D UI 嵌套滚动视图

Unity3D 解决 UI 嵌套滚动视图滑动问题。 嵌套滚动视图 滑动问题 在游戏开发中&#xff0c;我们常常会遇到一种情况&#xff0c;在一个滚动视图列表中&#xff0c;每个 item 还包含了一个内嵌的滚动视图。 这样&#xff0c;当我们在滑动外层的滚动视图时&#xff0c;如果点…...

你还没有将 Siri 接入GPT对话功能吗?

由于各种原因&#xff0c;国内ios用户目前无缘自带 AI 功能&#xff0c;但是这并不代表国内 ios 无法接入 AI 功能&#xff0c;接下来手把手带你为iPhone siri 接入 gpt 对话功能。 siri 接入 chatGPT 暂时还无法下载 ChatGPT app&#xff0c;或者没有账号的读者可以直接跳到…...

_C#_串口助手_字符串拼接缺失问题(未知原理)

最近使用WPF开发串口助手时&#xff0c;遇到一个很奇怪的问题&#xff0c;无论是主线程、异步还是多线程&#xff0c;当串口接收速度达到0.016s一次以上&#xff0c;就会发生字符串缺失问题并且很卡。而0.016s就一切如常&#xff0c;仿佛0.015s与0.016s是天堑之隔。 同一份代码…...

浅析大数据时代下的网络安全

一、大数据时代下网络安全的现状 在全球化进程不断深入发展的情况下&#xff0c;互联网行业发展速度也更加迅猛&#xff0c;人们对网络信息的需求量不断增加&#xff0c;所以目前已经进入了大数据时代。 随着计算机技术的不断发展&#xff0c;我国互联网网络规模、网民数量、…...

Mysql数据库基础篇笔记

目录 sql语句 DDL——数据库定义语言&#xff08;定义库&#xff0c;表&#xff0c;字段&#xff09; 数据库操作&#xff1a; 表操作&#xff1a; DML 增删改语句 DQL 语法编写顺序&#xff1a; 条件查询 DCL 用户管理&#xff1a; 权限管理&#xff1a; 函数 常见字符串内置函…...

rabbitmq原理及命令

目录 一、RabbitMQ原理1、交换机&#xff08;Exchange&#xff09;fanoutdirecttopicheaders&#xff08;很少用到&#xff09; 2、队列Queue3、Virtual Hosts4、基础对象 二、RabbitMQ的一些基本操作:1、用户管理2、用户角色3、vhost4、开启web管理接口5、批量删除队列 一、Ra…...

React进阶面试题(四)

React 的 reconciliation&#xff08;协调&#xff09;算法 Reconciliation是React的diff算法&#xff0c;用于比较更新前后的虚拟DOM树差异&#xff0c;从而使用最小的代价将原始DOM按照新的状态、属性进行更新。其目的是找出两棵树的差异&#xff0c;原生方式直接比较复杂度…...

24/12/1 算法笔记<强化学习> 创建Maze交互

我们今天制作一个栅格的游戏。 我们直接上代码教学。 1.载入库和查找相应的函数版本 import numpy as np import time import sysif sys.version_info.major 2:import Tkinter as tk else:import tkinter as tk 2.设置长宽和单元格大小 UNIT 40 MAZE_H 4 MAZE_W 4 3.初始…...

Linux驱动开发(10):I2C子系统–mpu6050驱动实验

本章我们以板载MPU6050为例讲解i2c驱动程序的编写&#xff0c;本章主要分为五部分内容。 第一部分&#xff0c;i2c基本知识&#xff0c;回忆i2c物理总线和基本通信协议。 第二部分&#xff0c;linux下的i2c驱动框架。 第三部分&#xff0c;i2c总线驱动代码拆解。 第四部分&a…...

《装甲车内气体检测“神器”:上海松柏 K-5S 电化学传感器模组详解》

《装甲车内气体检测“神器”:上海松柏 K-5S 电化学传感器模组详解》 一、引言二、K-5S 电化学传感器模组概述&#xff08;一&#xff09;产品简介&#xff08;二&#xff09;产品特点&#xff08;三&#xff09;产品适用场景 三、电化学传感器原理及优点&#xff08;一&#xf…...

如何将多个JS文件打包成一个JS文件?

文章目录 前言SDK 打包安装 webpack创建 webpack.config.js编译命令行遇到的坑点前言 上一篇已经记录了如何开发一个小游戏聚合SDK,既然是SDK,最终都是给外部人员使用的。调研了一下市面上的前端SDK,最终都是编译成一个 js 文件。我猜理由大概是 js 文件之间的调用都是需要…...

100个python经典面试题详解(新版)

应老粉要求,每晚加餐一个最新面试题 包括Python面试中常见的问题,涵盖列表、元组、字符串插值、比较操作符、装饰器、类与对象、函数调用方式、数据结构操作、序列化、数据处理函数等多个方面。 旨在帮助数据科学家和软件工程师准备面试或提升Python技能。 7、Python面试题…...

C#初阶概念理解

​​​​​​​ 梳理了一些本人在学习C#时的一些生疏点&#xff0c;同时也加深自己的印象。 堆&栈 堆用来存储程序运行时产生的变量&#xff0c;当程序结束时释放&#xff1b; 栈用来存储程序运行时&#xff0c;调用方法产生的临时变量&#xff0c;方法运行完成后就会释放…...

node.js基础学习-url模块-url地址处理(二)

前言 前面我们创建了一个HTTP服务器&#xff0c;如果只是简单的http://localhost:3000/about这种链接我们是可以处理的&#xff0c;但是实际运用中一般链接都会带参数&#xff0c;这样的话如果我们只是简单的判断链接来分配数据&#xff0c;就会报404找不到链接。为了解决这个问…...

算法与数据结构(1)

一&#xff1a;数据结构概论 数据结构分为初阶数据结构&#xff08;主要由C语言实现&#xff09;和高阶数据结构&#xff08;由C实现&#xff09; 初阶数据结构当中&#xff0c;我们会学到顺序表、链表、栈和队列、二叉树、常见排序算法等内容。 高阶数据结构当中&#xff0…...

FTP介绍与配置

前言&#xff1a; FTP是用来传送文件的协议。使用FTP实现远程文件传输的同时&#xff0c;还可以保证数据传输的可靠性和高效性。 介绍 FTP的应用 在企业网络中部署一台FTP服务器&#xff0c;将网络设备配置为FTP客户端&#xff0c;则可以使用FTP来备份或更新VRP文件和配置文件…...

SQL面试题——抖音SQL面试题 最近一笔有效订单

最近一笔有效订单 题目背景如下,现有订单表order,包含订单ID,订单时间,下单用户,当前订单是否有效 +---------+----------------------+----------+-----------+ | ord_id | ord_time | user_id | is_valid | +---------+----------------------+--------…...

【线程】Java多线程代码案例(1)

【线程】Java多线程代码案例&#xff08;1&#xff09; 一、“单例模式” 的实现1.1“饿汉模式”1.2 “懒汉模式”1.3 线程安全问题 二、“阻塞队列”的实现2.1阻塞队列2.2生产者消费者模型2.3 阻塞队列的实现2.4 再谈生产者消费者模型 一、“单例模式” 的实现 “单例模式”即…...

go使用mysql实现增删改查操作

1、安装MySQL驱动 go get -u github.com/go-sql-driver/mysql2、go连接MySQL import ("database/sql""log"_ "github.com/go-sql-driver/mysql" // 导入 mysql 驱动 )type Users struct {ID intName stringEmail string }var db *sql.DBfu…...

【Rust】unsafe rust入门

这篇文章简单介绍下unsafe rust的几个要点 1. 解引用裸指针 裸指针其实就是C或者说C的指针&#xff0c;与C的指针不同的是&#xff0c;Rust的裸指针还是要分为可变和不可变&#xff0c;*const T 和 *mut T&#xff1a; 基于引用创建裸指针 let mut num 5;let r1 &num …...

dpwwn02靶场

靶机下载地址&#xff1a;https://download.vulnhub.com/dpwwn/dpwwn-02.zip 信息收集 ip add 查看kali Linux虚拟机的IP为&#xff1a;10.10.10.128 https://vulnhub.com/entry/dpwwn-2,343/中查看靶机的信息&#xff0c;IP固定为10.10.10.10 所以kali Linux添加仅主机网卡…...

K8S疑难概念理解——Pod,应该以哪种Kind来部署应用,为什么不直接Pod这种kind?

文章目录 一、Pod概念深度理解&#xff0c;为什么一般不直接以kindPod资源类型来部署应用?二、究竟应该以哪种资源类型来部署应用 一、Pod概念深度理解&#xff0c;为什么一般不直接以kindPod资源类型来部署应用? Pod是Kubernetes中的最小部署单元&#xff0c;可以包含一个或…...

LabVIEW进行仪器串行通信与模拟信号采集的比较

在现代测试、测量和控制系统中&#xff0c;设备通常采用两种主要方式与计算机进行交互&#xff1a;一种是通过数字通信接口&#xff08;如RS-232、RS-485、GPIB等&#xff09;&#xff0c;另一种是通过模拟信号&#xff08;电压、电流&#xff09;进行数据输出。每种方式具有其…...

D81【 python 接口自动化学习】- python基础之HTTP

day81 requests请求session用法 学习日期&#xff1a;20241127 学习目标&#xff1a;http定义及实战 -- requests请求session用法 学习笔记&#xff1a; requests请求session用法 import requests# 创建一个会话 reqrequests.session() url "http://sellshop.5istud…...

白鹿 Hands-on:消除冷启动——基于 Amazon Lambda SnapStart 轻松打造 Serverless Web 应用(二)

文章目录 前言一、前文回顾二、在 Lambda 上运行2.1、查看 Amazon SAM template2.2、编译和部署到 Amazon Lambda2.3、功能测试与验证 三、对比 Snapstart 效果四、资源清理五、实验总结总结 前言 在这个环节中&#xff0c;我们将延续《白鹿 Hands-on&#xff1a;消除冷启动——…...

ROC曲线

文章目录 前言一、ROC的应用&#xff1f;二、使用方式1. 数据准备2.绘图可视化 前言 在差异分析中&#xff0c;ROC曲线可以用来评估不同组之间的分类性能差异。差异分析旨在比较不同组之间的特征差异&#xff0c;例如在基因表达研究中比较不同基因在不同条件或组织中的表达水平…...

wordpress 不同ip/有哪些网络营销公司

总览 #include <linux/module.h> int query_module(const char *name, int which,void *buf, size_t bufsize, size _t *ret); 描述 query_module请求和可加载模块有关的来自内核的信息.信息的细致的特性和格式依赖于 which参数,一些函数要求name参数来命名当前被加载的模…...

企业网站怎么搭建/logo设计

我接触Linux已经挺久了&#xff0c;十分喜爱Linux命令行的操作模式&#xff0c;无需鼠标就能够实现众多工作&#xff0c;然而现在的众多工作还是离不开桌面环境&#xff0c;固然众多Linux发行版都已经给Linux预备好了桌面环境&#xff0c;大部分常用的软件也已经有了Linux版本&…...

龙岗中心城网站建设/厦门人才网招聘

Oracle全文索引相信大家都有一定的了解&#xff0c;下面就教您如何建立oracle全文索引&#xff0c;如果您对oracle全文索引方面感兴趣的话&#xff0c;不妨一看。步骤一 检查和设置数据库角色首先检查数据库中是否有CTXSYS用户和CTXAPP脚色。如果没有这个用户和角色&#xff0…...

招投标信息查询平台/seo排名点击报价

转载于:https://www.cnblogs.com/6DAN_HUST/archive/2012/08/19/2646715.html...

网站建设xyhlrj/聚合搜索引擎入口

我们集成支付宝支付 也就需要提供两个接口出来.一个是给前端 作用是拿到请求参数加签返回给前端 前端拿到我们返回的数据.进行调起支付就行了,第二个接口是提供给支付宝的服务器,支付成功或者失败的时候.支付宝的服务器会回调我们的这个接口.异步通知我们支付结果 服务端sdk地址…...

做淘宝差不多的网站吗/网络营销公司网络推广

文章目录背景设计思路具体实现项目代码结构模块说明定义一个幂等注解定义一个AOP切面定义抽象唯一id接口定义一个自动配置类使用项目源码地址不足关于我背景 目前项目中RocketMQ应用场景还是比较多的&#xff0c;但是线上经常因为一些异常原因导致生产者重新投递消息导致消费者…...