自定义博客皮肤VIP专享

*博客头图:

格式为PNG、JPG,宽度*高度大于1920*100像素,不超过2MB,主视觉建议放在右侧,请参照线上博客头图

请上传大于1920*100像素的图片!

博客底图:

图片格式为PNG、JPG,不超过1MB,可上下左右平铺至整个背景

栏目图:

图片格式为PNG、JPG,图片宽度*高度为300*38像素,不超过0.5MB

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

-+
  • 博客(154)
  • 收藏
  • 关注

原创 【ElasticSearch】ES架构、倒排索引、深分页问题

在Elasticsearch中进行分页查询通常使用from和size参数。当我们对Elasticsearch发起一个带有分页参数的查询(如使用from和size参数)时,ES需要遍历所有匹配的文档直到达到指定的起始点(from),然后返回从这一点开始的size个文档。"query": {ES的检索机制决定了,当进行分页查询时,Elasticsearch需要先找到并处理所有位于当前页之前的记录。

2025-10-14 10:23:07 594

原创 【注册中心】Nacos

Nacos是一个基于云原生架构的动态服务发现、配置管理和服务治理平台。支持多种编程语言和多种部署方式,并且与Spring Cloud等主流的微服务框架深度集成。配置管理:可以将应用程序的配置信息存储在Nacos的配置中心,通过Nacos实现动态配置管理和灰度发布,从而实现应用程序的动态调整和部署。服务发现及注册:可以将服务注册到Nacos注册中心,并通过Nacos实现服务的自动发现和负载均衡,从而实现服务的高可用和弹性伸缩。

2025-10-14 10:16:12 576

原创 【RabbitMQ】架构原理、消息丢失、重复消费、顺序消费、事务消息

想要保证发送者一定能把消息发送给RabbitMQ,一种是通过confirm机制,另外一种就是通过事务机制。RabbitMQ的事务机制,允许生产者将一组操作打包成一个原子事务单元,要么全部执行成功,要么全部失败。事务提供了一种确保消息完整性的方法,但需要谨慎使用,因为它们对性能有一定的影响。因为事务机制是同步的,提交一个事务之后会阻塞在那儿,但是 confirm机制是异步的,发送一个消息之后就可以发送下一个消息,RabbitMQ 接收了之后会异步回调confirm接口通知这个消息接收到了。

2025-10-14 09:55:47 856

原创 【RocketMQ】架构原理、消息丢失、重复消费、顺序消费、事务消息

首先在生产者端,消息的发送分为同步、异步两种和单向发送(单向发送不保证成功,不建议使用),在同步发送消息的情况下,消息的发送会同步阻塞等待Broker返回结果,在Broker确认收到消息之后,生产者才会拿到SendResult。那就可能会导致没有人知道这个消息失败了,就导致不会重试了。不管是因为什么情况了,是真的消费失败了,还是出现了异常了,还是明明消费成功了,但是你错误的返回了失败等等情况,只要你给RocketMQ返回的是 RECONSUME_LATER ,那么消息就会重投,有重投就会有重复消费。

2025-10-14 09:55:32 877

原创 【Kafka】架构原理、消息丢失、重复消费、顺序消费、事务消息

当我们使用Kafka发送消息时,一般有两种方式,分别是同步发送(producer.send(msg).get() )及异步发送(producer.send(msg, callback))。同步发送的时候,可以在发送消息后,通过get方法等待消息结果:producer.send(record).get();,这种情况能够准确的拿到消息最终的发送结果,要么是成功,要么是失败。而异步发送,是采用了callback的方式进行回调的,可以大大的提升消息的吞吐量,也可以根据回调来判断消息是否发送成功。

2025-10-12 16:43:03 946

原创 【Redis】用Redis实现分布式锁、乐观锁

RedLock是Redis的作者提出的一个多节点分布式锁算法,旨在解决使用单节点Redis分布式锁可能存在的单点故障问题。Redis的单点故障问题:1、在使用单节点Redis实现分布式锁时,如果这个Redis实例挂掉,那么所有使用这个实例的客户端都会出现无法获取锁的情况。

2025-10-12 16:41:51 814

原创 【Dubbo】Rpc与HTTP的区别、Dubbo调用过程

RPC 是Remote Procedure Call的缩写,译为远程过程调用。要想实现RPC通常需要包含传输协议和序列化协议的实现。而熟知的HTTP,他的中文名叫超文本传输协议,所以他就是一种传输协议。所以,我们可以认为RPC和HTTP并不是同一个维度的两个概念。只不过他们都是可以作为远程调用的,所以经常拿来对比。RPC的具体实现上,可以像HTTP一样,基于TCP协议来实现,也可以直接基于HTTP协议实现。RPC主要用于公司内部服务之间的互相调用,所以他性能消耗低,传输效率高,服务治理方便。

2025-09-25 11:27:03 1118

原创 【Redis】热Key/大Key问题、缓存击穿、缓存穿透、缓存雪崩、缓存与数据库一致性问题

当使用Redis作为存储时,如果发生一些特殊情况,比如明星官宣的突发事件,世界杯等重大活动,双十一的活动秒杀等等,就会出现特别大的流量,并且会导致某些热词、商品等被频繁的查询和访问。如果在同一个时间点上,Redis中的同一个key被大量访问,就会导致流量过于集中,使得很多物理资源无法支撑,如网络带宽、物理存储空间、数据库连接等。这也是为什么某某明星官宣之后,微博上面就会出现宕机的情况。有时候这种宕机发生后,其他功能都是可以使用的,只是和这个热点有关的内容会无法访问,这其实就和热点数据有关系了。

2025-09-25 10:18:55 1194

原创 【Redis】Redis为什么这么快、Redis数据类型

Sorted Set 能支持范围查询,这是因为它的核心数据结构设计采用了跳表,而它又能O(1)的复杂度获取元素权重,这是因为它同时采用了哈希表进行索引。dict *dict;} zset;以上是zset的数据结构,其中包含了两个成员,分别是哈希表dict和跳表zsl。dict存储 member->score 之间的映射关系,所以 ZSCORE 的时间复杂度为 O(1)。skiplist 是一个「有序链表 + 多层索引」的结构,查询元素的复杂度是 O(logN),所以他的查询效率很高。

2025-09-25 10:16:53 729

原创 【Redis】集群模式、持久化机制、事务机制、过期策略

Redis有三种主要的集群模式,用于在分布式环境中实现高可用性和数据复制。这些集群模式分别是:主从复制(Master-Slave Replication)、哨兵模式(Sentinel)和Redis Cluster模式。Redis 的 Pipeline 机制是一种用于优化网络延迟的技术,主要用于在单个请求/响应周期内执行多个命令。在没有 Pipeline 的情况下,每执行一个 Redis 命令,客户端都需要等待服务器响应之后才能发送下一个命令。这种往返通信尤其在网络延迟较高的环境中会显著影响性能。

2025-09-25 10:15:48 934

原创 【分布式】分布式ID生成方案、接口幂等、一致性哈希

哈希算法大家都不陌生,经常被用在负载均衡、分库分表等场景中,比如说我们在做分库分表的时候,最开始我们根据业务预估,把数据库分成了128张表,这时候要插入或者查询一条记录的时候,我们就会先把分表键,如buyer_id进行hash运算,然后再对128取模,得到0-127之间的数字,这样就可以唯一定位到一个分表。但是随着业务得突飞猛进,128张表,已经不够用了,这时候就需要重新分表,比如增加一张新的表。这时候如果采用hash或者取模的方式,就会导致128+1张表的数据都需要重新分配,成本巨高。

2025-09-23 10:57:36 1173

原创 【分布式】分布式事务方案:两阶段、TCC、SEATA

所谓一致性,是指数据在多个副本之间是否能够保持一致的特性。在聊一致性的时候,其实要搞清楚一致性模型。(概念挺多,但是没办法,这玩意它本身就是理论。想结合代码、示例都做不到,甚至想着画个图都不知道该如何下手)分布式系统中的一致性模型是一组管理分布式系统行为的规则。它决定了在分布式系统中如何访问和更新数据,以及如何将这些更新提供给客户端。面对网络延迟和局部故障等分布式计算难题,分布式系统的一致性模型对保证系统的一致性和可靠性起着关键作用。

2025-09-23 10:56:26 1124

原创 【微服务】微服务如何拆分、如何解决循环依赖

微服务(Microservices)是一种软件架构风格,用于构建复杂的应用程序。它将一个大型的应用程序拆分成一组小型、独立的服务单元,每个服务单元都可以独立部署、运行和扩展。每个微服务都专注于执行一个特定的业务功能,并通过轻量级的通信机制(如HTTP、RPC、MQ等)来相互调用。微服务的目的是有效的拆分应用,实现敏捷开发和部署。比如把一个原来中心化的商城服务,拆分成产品服务、订单服务及用户服务,每一个服务把功能内聚,系统间通过远程调用解耦。实现高内聚低耦合。并且每一个服务都可以独立的提供子服务。

2025-09-20 16:13:18 976

原创 【Tomcat】基础总结:类加载机制

Tomcat的类加载机制,在默认情况下,是先把当前要加载的类委托给BootstrapClassLoader尝试加载,为了避免JRE中的核心类被我们应用自己的类给覆盖(如String等),Bootstrap如果无法加载,那么就由WebAppClassLoader尝试加载,如果无法加载,那么再委托通过双亲委派的方式向上委派给Common、System等类加载进行加载,即顺序为:Bootstrap->WebApp->System->Common。启动类加载器是JVM的一部分,负责加载JVM运行所需的基础类。

2025-09-20 15:47:09 789

原创 【Mybatis】基础总结:缓存机制、如何实现分页

在Hibernate当中,开发者只需要定义好数据库的表字段和Java DO的映射关系和规则即可,Hibernate会开放出来接口自动去处理数据库表的CURD,并按照规定好的规则映射到DO对象中,这个过程中操作者是完全不需要感知Sql逻辑的。二级缓存的使用需要注意缓存的更新和失效机制,以及并发操作的问题。在同一个会话中,Mybatis会将执行过的SQL语句的结果缓存到内存中,下次再执行相同的SQL语句时,会先查看缓存中是否存在该结果,如果存在则直接返回缓存中的结果,不再执行SQL语句。增加开发者的开发效率。

2025-09-20 15:46:13 730

原创 【Mysql】深分页问题、页分裂问题、加密/解密、执行计划

MySQL的数据是存储在磁盘上面的(Memory引擎除外),但是如果每次数据的查询和修改都直接和磁盘交互的话,性能是很差的。于是,为了提升读写性能,Innodb引擎就引入了一个中间层,就是buffer pool。buffer是在内存上的一块连续空间,他主要的用途就是用来缓存数据页的,每个数据页的大小是16KB。

2025-09-20 15:45:38 905

原创 【Mysql】count(1)/count(*) /count(列名) 的区别、自增主键原理、exists/in的区别

那么,不管是我们自己定义的自增主键,还是row_id的这个主键,都是一个固定类型的,一般都是bigint unsigned,那么既然有固定类型,就有取值范围。但是,MySQL在使用的时候,还有一种做法,那就是读写分离,即在主库上操作写,在从读库上进行读,这时候主库和从库都是对外提供服务的。当然,前提是单库的情况下,如果你说的是分库分表的情况下,那就另当别论了。日常使用的MySQL,虽然也是有多个节点的,但是大多数情况下都是主备的,即只有一个主节点对外提供服务,当主节点挂了之后,备节点顶上来。

2025-09-20 15:44:19 672

原创 【Mysql】事务隔离级别、索引原理、/redolog/undolog/binlog区别、主从复制原理

【数据库三大范式和反范式】范式()是数据库设计时遵循的一种规范,不同的规范遵循不同的范式。范式可以避免数据冗余,减少数据库的空间,减轻维护数据完整性的麻烦。属性不可分割。即每个属性都是不可分割的原子项。联合主键不能存在部分依赖。满足第一范式,并且不存在部分依赖,即非主属性必须完全依赖于主属性。(主属性即主键,完全依赖是针对于联合主键的情况,非主键列不能只依赖于联合主键的一部分)。非主键字段不能存在传递依赖。满足第二范式,并且不能存在传递依赖。

2025-09-20 15:36:01 1082

原创 【Spring】SpringBoot原理

SpringApplication.run(Application.class, args)方法的实现细节如下:一个是new SpringApplication的初始化过程,一个是SpringApplication.run的启动过程。这个过程确保了在应用上下文被创建和启动之前,所有关键的设置都已就绪,包括环境设置、初始化器和监听器的配置,以及主应用类的识别。这一步,是Spring启动的核心步骤了,这一步骤包括了实例化所有的 Bean、设置它们之间的依赖关系以及执行其他的初始化任务。

2025-09-20 15:32:27 605

原创 【Spring】Spring Event事件驱动

在上面的示例中,UserRegistrationEvent 事件在用户注册成功后发布,然后 UserRegistrationEventListener 中的 handleUserRegistrationEvent 方法在事务成功提交后触发,发送欢迎邮件。Spring框架中的事件机制建立在观察者模式的基础上,允许应用程序中的组件注册监听器来监听特定类型的事件,并在事件发生时执行相应的操作。事件监听器将在事务尚未提交时执行,这意味着它可以在事务内部进行回滚操作,如果事件监听器抛出异常,将导致事务回滚。

2025-09-20 15:31:32 725

原创 【SpringCloud】SpringCloud组件

Spring Cloud是基于Spring Boot的分布式系统开发工具,它提供了一系列开箱即用的、针对分布式系统开发的特性和组件,用于帮助开发人员快速构建和管理云原生应用程序。Spring Cloud的主要目标是解决分布式系统中的常见问题,例如服务发现、负载均衡、配置管理、断路器、消息总线等。Eureka 的缓存机制设计主要目的是提高服务发现的效率和减少服务注册中心的压力,尤其是在面对大规模的服务注册和发现请求时。这种多层缓存设计帮助 Eureka 提供快速的响应能力。

2025-09-15 09:40:11 1014

原创 【Spring】Spring核心功能的理解

相同点:对于下面的代码来说,如果是Spring容器的话,两个注解的功能基本是等价的,他们都可以将bean注入到对应的field中。不同点:1、Autowired是Spring提供的自动注入注解,只有Spring容器会支持,如果做容器迁移,是需要修改代码的。Resource是JDK官方提供的自动注入注解(JSR-250)。它等于说是一个标准或者约定,所有的IOC容器都会支持这个注解。假如系统容器从Spring迁移到其他IOC容器中,是不需要修改代码的。2、Autowired在获取bean的时候,先是

2025-09-15 09:19:05 861

原创 【Spring】Spring事务传播机制

有的时候,你排查了很久,发现都没问题,但是还是不生效,然后找别人来帮你看,他上来就看了一下你用的@Transactional,发现并不是Spring中的,而是其他什么地方的,比如 javax.transaction.Transactional ,这样也会导致事务失效。private方法,只会在当前对象中的其他方法中调用,也就是会进行对象的自调用,这种情况是用this调用的,并不会走到代理对象,而@Transactional是基于动态代理实现的,所以代理会失效。2、在事务中有远程调用,就会拉长整个事务。

2025-09-15 09:18:50 956

原创 【Spring】Spring MVC

MVC模式(Model-View-Controller)是一种软件设计模式,它将应用程序分为三个部分:模型、视图和控制器。这个模式的目的是将应用程序的表示(视图)与处理(控制器)分开,以及将应用程序的数据和业务逻辑(模型)与表示和处理分开。2)将RequestMappingInfo 和 HandlerMethod(处理url的方法名) 作为kv保存在mappingLookup缓存中。1)将请求路径 和 RequestMappingInfo 作为KV保存在urlLookup缓存中。

2025-09-15 09:18:38 925

原创 【Spring】Spring Bean的生命周期

1、在创建对象时,先实例化bean,然后再初始化bean,会调用applyBeanPostProcessorsAfterInitialization方法。2、在applyBeanPostProcessorsAfterInitialization方法中,先遍历每一个后置处理器,然后调用后置处理器的postProcessAfterInitialization方法。

2025-09-15 09:18:25 821

原创 【JVM】类的生命周期

下图中展示的类加载器之间的这种层次关系,称为类加载器的双亲委派模型(Parents Delegation Model)。双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系一般不会以继承(Inheritance)的关系来实现,而是都使用组合(Composition)关系来复用父加载器的代码。

2025-09-09 09:27:51 598

原创 【JVM】Java对象结构

当我们在Java代码中,使用new创建一个对象的时候,JVM会创建一个instanceOopDesc对象,这个对象中包含了对象头、实例数据以及对齐填充。对象头信息是与对象自身定义的数据无关的额外存储成本,考虑到虚拟机的空间效率,Mark Word被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息,它会根据对象的状态复用自己的存储空间。由于虚拟机要求对象的起始地址必须是8字节的倍数(在某些平台上要求更大),因此可能需要在对象的实例数据末尾添加额外的字节来对齐。

2025-09-09 09:27:29 741

原创 【JVM】垃圾回收器

总结一下就是,G1会把Java的堆分为多个大小相等的Region(每个Region的大小为1M-32M),他在年轻代回收的时候采用标记-复制算法,而在老年代回收的时候,采用的是标记-整理算法,这两种算法都可以避免内存碎片的产生。G1 和 CMS相比,他们都是基于三色标记法实现的,替代了原有的传统的可达性分析(三色标记也是可达性分析的一种,只不过特殊一点),可以大大的降低STW的时长。总之,G1是一个先进的垃圾收集器,它可以提高系统的吞吐量,降低停顿的频率,并且可以有效管理大型堆。

2025-09-09 09:27:10 720

原创 【JVM】垃圾回收算法

JVM的跨代引用问题是指在Java堆内存的不同代之间存在引用关系,导致对象在不同代之间的引用被称为跨代引用。比如:新生代到老年代的引用,老年代到新生代的引用等。跨代引用会有什么问题呢?假如,我们现在JVM的堆是上面这种情况,那么在进行一次MinorGC(YoungGC)的时候,会从GC Root出发,然后进行可达性分析,假如当前正在进行一次Young GC,如果他发现一个对象处于老年代,那么JVM就会中断这条路径。

2025-09-09 09:26:48 611

原创 【JVM】堆内存分代和GC触发条件

Safe Point(安全点)是JVM中的一个关键概念。官方的解释是(https://openjdk.org/groups/hotspot/docs/HotSpotGlossary.html):安全点,简单点说就是代码执行过程中的一些特殊位置,当线程执行到这个位置的时候,可以被认为处于“安全状态”,如果有需要,可以在这里暂停,在这里暂停是安全的!这些安全点通常出现在不会改变共享数据状态的位置,例如在方法调用、循环迭代和异常抛出的地方。

2025-09-09 09:26:26 573

原创 【JVM】运行时内存区域

堆外内存是指将数据存储在堆以外的内存中,主要是指将一些直接内存分配在堆以外,以提高应用程序的性能或节省堆内存空间。堆外内存可以用于分配容量较大的缓冲区,比如文件缓冲区等等。Java 中的直接内存是通过使用 java.nio 包中的 DirectByteBuffer 类来实现的。DirectByteBuffer 类可以用来分配本地内存,并允许在 Java 和本地内存之间交换数据。使用 DirectByteBuffer 可以减少从 Java 堆和本地堆之间进行复制和读写的开销,从而提升程序的性能。

2025-09-09 09:25:58 938

原创 【JVM】JIT优化技术

TLAB是虚拟机在堆内存的eden划分出来的一块专用空间,是线程专属的。在虚拟机的TLAB功能启动的情况下,在线程初始化时,虚拟机会为每个线程分配一块TLAB空间,只给当前线程使用,这样每个线程都单独拥有一个空间,如果需要分配内存,就在自己的空间上分配,这样就不存在竞争的情况,可以大大提升分配效率。所以说,因为有了TLAB技术,堆内存并不是完完全全的线程共享,其eden区域中还是有一部分空间是分配给线程独享的。这里值得注意的是,

2025-09-09 09:25:04 999

原创 【Java并发】ThreadLocal

ThreadLocal是java.lang下面的一个类,是用来解决java多线程程序中并发问题的一种途径;通过为每一个线程创建一份共享变量的副本来保证各个线程之间的变量的访问和修改互相不影响;ThreadLocal存放的值是线程内共享的,线程间互斥的,主要用于线程内共享一些数据,避免通过参数来传递,这样处理后,能够优雅的解决一些实际问题。

2025-08-27 09:57:28 651

原创 【Java并发】synchronized原理

在未释放之前,其他线程是无法再次获得锁的,所以,通过monitorenter和monitorexit指令,可以保证被synchronized修饰的代码在同一时间只能被一个线程访问,在锁未释放之前,无法被其他线程访问到。如果该对象已经被锁住,线程就会进入阻塞状态,直到锁被释放。但是,偏向锁的局限是当只有一个线程反复进入同步代码块时他才能快速获得,但是当有其他线程尝试获取锁的时候,就需要等到 safe point 时,再将偏向锁撤销为无锁的状态或者升级为轻量级锁,而这个过程其实是会消耗一定的性能的。

2025-08-27 09:49:07 1057

原创 【Java并发】AQS原理

AQS的各种实现类中,要么是基于独占模式实现的, 要么是基于共享模式实现的。在独占模式中,状态通常表示是否被锁定(0表示未锁定,1表示锁定)。在共享模式中,状态可以表示可用的资源数量。当需要保证某个资源或一段代码在同一时间内只能被一个线程访问时,独占模式是最合适的选择。如我们经常用的ReentrantLock和ReadWriteLock中的写锁。当资源或数据主要被多个线程读取,而写操作相对较少时,共享模式能够提高并发性能。

2025-08-27 09:47:22 849

原创 【Java并发】线程状态

在Java中,共有四种方式可以创建线程,分别是(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。(2)创建Thread子类的实例,即创建了线程对象。(3)调用线程对象的start()方法来启动该线程。1.2 实现Runnable接口创建线程(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。(2)创建 Runnable实现类的实例,并依此实例作为Thre

2025-08-27 09:44:57 406

原创 【Java并发】CAS

CAS是一项乐观锁技术,是Compare And Swap的简称,顾名思义就是先比较再替换。CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。在进行并发修改的时候,会先比较A和V中取出的值是否相等,如果相等,则会把值替换成B,否则就不做任何操作。当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

2025-08-27 09:35:28 565

原创 【Java集合类】HashMap实现原理

ConcurrentHashMap将哈希表分成多个段,每个段拥有一个独立的锁,这样可以在多个线程同时访问哈希表时,只需要锁住需要操作的那个段,而不是整个哈希表,从而提高了并发性能。简单点说,就是为了把高位的特征和低位的特征组合起来,降低哈希冲突的概率,也就是说,尽量做到任何一位的变化都能对最终得到的结果产生影响。,即将哈希表分成多个段,每个段拥有一个独立的锁。当上面的操作完结后,HashMap会检测两个链表的长度,当元素小于等于6的时候,就会执行取消树化的操作,否则就会将新生成的链表重新树化。

2025-08-13 13:32:16 684

原创 【Java集合类】集合排序的方式有哪些?

Java.util包中的List接口继承了Collection接口,用来存放对象集合,所以对这些对象进行排序的时候,要么让对象类自己实现同类对象的比较,要么借助比较器进行比较排序。举例:学生实体类,包含姓名和年龄属性,比较时先按姓名升序排序,如果姓名相同则按年龄升序排序。

2025-08-11 08:46:49 268

原创 【Java】面试题

因为BigDecimal的equals方法和compareTo并不一样,equals方法会比较两部分内容,分别是值(value)和标度(scale),而对于0.1和0.10这两个数字,他们的值虽然一样,但是精度是不一样的,所以在使用equals比较的时候会返回false。浮点数不一定能精确地表示小数,double是不精确的。这时候,对于那种可能经常使用的字符串,使用intern进行定义,每次JVM运行到这段代码的时候,就会直接把常量池中该字面值的引用返回,这样就可以减少大量字符串对象的创建了。

2025-08-11 08:45:21 602

空空如也

空空如也

TA创建的收藏夹 TA关注的收藏夹

TA关注的人

提示
确定要删除当前文章?
取消 删除