自定义博客皮肤VIP专享

*博客头图:

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

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

博客底图:

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

栏目图:

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

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

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

原创 【深入理解SpringCloud微服务】Spring-Security作用与原理解析

Spring Security是一个提供认证和授权功能的框架,基于Servlet过滤器链实现。其核心流程包括:UsernamePasswordAuthenticationFilter处理登录认证,AuthenticationManager调用AuthenticationProvider进行身份验证,最终通过FilterSecurityInterceptor完成权限校验。框架支持基于RBAC模型的权限控制,通过@PreAuthorize注解实现细粒度授权。与JWT集成时,需自定义登录接口生成Token,并通过

2025-12-24 20:16:12 1028

原创 【深入理解SpringCloud微服务】Gateway源码解析

本文深入解析了Spring Cloud Gateway的核心原理与源码实现。Gateway基于Spring WebFlux框架,主要由HandleMapping和WebHandler两大组件构成:HandleMapping负责路由匹配,WebHandler处理过滤器链。源码分析重点包括DispatcherHandler处理请求的流程、RoutePredicateHandlerMapping的路由匹配机制,以及FilteringWebHandler如何组合全局和路由过滤器形成处理链。

2025-12-24 20:13:33 959

原创 【深入理解SpringCloud微服务】Gateway简介与模拟Gateway手写一个微服务网关

微服务网关Gateway简介与实现 Gateway作为微服务架构的统一入口,主要功能包括路由转发、负载均衡、认证鉴权、安全防护等。其核心原理基于Spring-WebFlux,通过路由(Route)、断言(Predicate)和过滤器(Filter)三个核心概念实现请求处理流程。本文还介绍了如何模拟Gateway实现一个简易微服务网关,包括架构设计和关键组件实现,如GatewayHandlerMapping负责路由匹配和过滤器链组装,以及三个核心过滤器(负载均衡、路由转发和响应写入)的处理流程。

2025-12-21 18:58:53 1447

原创 【深入理解SpringCloud微服务】Seata对于TCC模式下对于空回滚、幂等、悬挂等问题的处理

Seata在TCC模式下通过事务控制表解决了空回滚、幂等和悬挂问题。对于空回滚,通过检查表中是否存在对应记录来决定是否执行cancel操作;针对幂等问题,使用status字段标记操作状态,确保confirm/cancel只执行一次;处理悬挂问题时,新增suspended状态,在cancel操作后标记事务状态,避免后续try操作执行。这些机制通过xid、branch_id和status字段的协同工作,保证了TCC模式下的数据一致性和可靠性。

2025-12-21 18:54:13 981

原创 【深入理解SpringCloud微服务】Seata(AT模式)源码解析——@GlobalLock注解原理

本文解析了Seata框架中@GlobalLock注解的工作原理。该注解用于确保本地事务与全局事务并发操作同一数据时的数据一致性,防止脏读和回滚失败问题。通过分析两个典型场景(并发更新和脏读),文章展示了@GlobalLock如何通过全局锁机制协调事务执行。源码层面,GlobalTransactionalInterceptor拦截器处理@GlobalLock标记的方法,通过GlobalLockTemplate绑定全局锁标志到当前线程,在执行SQL前检查TC(事务协调器)中的全局锁状态。对于"sele

2025-12-21 18:52:24 1071

原创 【深入理解SpringCloud微服务】Seata(AT模式)源码解析——全局事务的回滚

本文分析了Seata AT模式下全局事务回滚的源码流程。当业务逻辑抛出异常时,TransactionalTemplate会捕获异常并判断是否匹配@GlobalTransaction的rollbackFor属性。若匹配则通过DefaultGlobalTransaction发起回滚请求,经过重试机制后,最终由DefaultTransactionManager通过Netty向TC发送GlobalRollbackRequest。TC端DefaultCoordinator接收请求后,查询并修改全局事务状态为Rollb

2025-12-19 19:44:26 908

原创 【深入理解SpringCloud微服务】Seata(AT模式)源码解析——全局事务的提交

本文分析了Seata AT模式下全局事务提交的源码流程。当业务逻辑执行成功后,TM会调用DefaultGlobalTransaction的commit()方法发起全局事务提交。该方法通过Netty向TC(事务协调器)发送GlobalCommitRequest请求。TC接收到请求后,DefaultCore会查询对应的GlobalSession,将其状态改为AsyncCommitting并释放全局锁。不同于同步提交模式,AT模式下事务提交是异步完成的,由定时任务后续处理状态为AsyncCommitting的Gl

2025-12-19 19:43:06 908

原创 【深入理解SpringCloud微服务】Seata(AT模式)源码解析——全局事务的事务传播机制

本文解析了Seata AT模式中全局事务传播机制的实现原理。核心在于通过xid传递实现事务传播:调用方通过RestTemplate拦截器SeataRestTemplateInterceptor将xid添加到HTTP请求头;被调用方通过SpringMVC拦截器TransactionPropagationInterceptor从请求头获取xid并绑定到ThreadLocal。xid存储于RootContext(ThreadLocal实现),SQL执行时会检查当前线程是否绑定xid来决定是否加入全局事务。文章详细

2025-12-16 12:32:41 1018

原创 【深入理解SpringCloud微服务】Seata(AT模式)源码解析——分支事务处理

Seata AT模式通过代理JDBC对象实现分支事务处理。DataSourceProxy生成ConnectionProxy,后者再生成PreparedStatementProxy。SQL执行时,ExecuteTemplate根据SQL类型选择对应Executor执行。若存在全局事务XID,会绑定到ConnectionProxy,并生成前后镜像数据用于构建undo log。对于自动提交的连接,会关闭自动提交并重试执行。核心流程包括:代理JDBC对象、判断事务上下文、选择执行器、生成数据快照以及构建undo日志

2025-12-16 12:30:32 735

原创 【深入理解SpringCloud微服务】Seata(AT模式)源码解析——开启全局事务

本文深入分析了Seata AT模式下开启全局事务的源码实现。当方法被@GlobalTransactional注解修饰时,GlobalTransactionScanner会生成AOP代理对象,调用时进入GlobalTransactionalInterceptor拦截器,最终通过TransactionalTemplate执行事务处理。核心流程包括:DefaultGlobalTransaction请求TC开启全局事务并绑定xid到ThreadLocal;DefaultTransactionManager通过Net

2025-12-13 14:41:48 1029 2

原创 【深入理解SpringCloud微服务】Seata(AT模式)源码解析——@GlobalTransactional注解与@globalLock生效的原理

本文解析了Seata AT模式的核心流程和源码实现。AT模式通过两阶段提交实现分布式事务:一阶段生成前后镜像并注册分支事务,二阶段根据全局事务状态提交或回滚。源码层面,Seata基于Spring AOP实现,通过GlobalTransactionScanner扫描@GlobalTransactional注解,生成代理对象并注入GlobalTransactionalInterceptor拦截器。该拦截器负责开启全局事务、执行目标方法和提交/回滚事务。执行SQL时通过代理链获取数据镜像生成undo log,并通

2025-12-13 14:40:43 914 1

原创 【深入理解SpringCloud微服务】Seata简介与四种事务模式

Seata是阿里开源的分布式事务组件,包含TM、RM、TC三种角色,通过@GlobalTransactional和@GlobalLock实现事务控制,支持AT(自动回滚)、TCC(需自定义提交/回滚方法)、SAGA(长事务补偿模式)、XA(两阶段提交)四种事务模式,适用于不同分布式场景。

2025-05-10 10:37:53 970

原创 【深入理解SpringCloud微服务】手写实现一个微服务分布式事务组件

本文介绍了一种基于两阶段提交协议的手写分布式事务组件实现方案。通过AOP切面在调用链起点服务注册全局事务,生成唯一XID并透传至下游服务,各服务通过代理数据源拦截SQL操作,在事务提交时注册分支事务并暂存数据库连接。服务端协调器记录全局事务与参与者映射关系,最终根据业务执行结果通知所有参与者提交或回滚。关键实现包括:通过ThreadLocal传递XID、RestTemplate拦截器透传事务ID、Connection代理延迟提交、连接持有器管理物理连接等,构建了完整的分布式事务生命周期管理机制。

2025-05-10 10:30:44 1391

原创 【深入理解SpringCloud微服务】Sentinel规则持久化实战

第二种办法是修改Sentinel控制台的源码,Sentinel控制台不和Sentinel客户端直接通讯,而是把在控制台上新增或修改的规则保存到配置中心,由配置中心通知Sentinel客户端。原因是因为Sentinel控制台发布到nacos的格式是按照ParamFlowRuleEntity的格式推的,而Sentinel从nacos拉取到热点参数规则时,是解析成ParamFlowRule的。这种模式是最简单的,不需要做任何改造,但是一旦Sentinel客户端重启,所有的规则都会丢失。

2025-01-25 20:03:26 1238

原创 【深入理解SpringCloud微服务】Sentinel源码解析——DegradeSlot熔断规则

如果到了可以重试的时间,那么调用fromOpenToHalfOpen方法尝试切换为半开状态,这里有可能会切换失败,因为有可能有多个线程并发尝试切换,只有成功切换的那一个线程被放行,其他的都会被拦截掉;当断路器处于打开状态一段时间后,就会变成半开状态,此时断路器允许一次请求调用,如果这次请求调用成功,那么断路器切换为闭合状态,否则重新变成打开状态。如果我们设置的熔断策略是“慢调用比例”,那么断路器会利用滑动时间窗统计总调用数和慢调用数,如果本次调用超过了设置的最大响应时长,那么就记一次慢调用。

2025-01-25 20:01:32 1456

原创 【深入理解SpringCloud微服务】Sentinel源码解析——FlowSlot流控规则

在前面的文章,我们对Sentinel的原理进行了分析,Sentinel底层使用了责任链模式,这个责任链就是ProcessorSlotChain对象,链中的每个节点都是一个ProcessorSlot,每个ProcessorSlot对应一个规则的处理。然后我们又对Sentinel的整体流程进行了源码分析,我们分析了Sentinel的ProcessorSlotChain对象默认的构成:但是我们没有对每个slot进行深入的分析,本篇文章就对它们进行深入的了解。

2024-12-28 20:14:37 1537

原创 【深入理解SpringCloud微服务】Sentinel源码解析——SentinelWebInterceptor拦截器与@SentinelResource注解原理

invokeResourceWithSentinel方法中的两个catch块是值得我们注意的,它会寻找@SentinelResource注解是否有指定降级处理方法,如果有的话会执行降级处理,这也是SentinelWebInterceptor拦截器不具备的一个能力。使得@SentinelResource注解注解生效的是SentinelResourceAspect切面类,它通过AOP对@SentinelResource注解修饰的方法进行增强,织入Sentinel进行规则校验的模板代码。

2024-12-28 20:12:29 872

原创 【深入理解SpringCloud微服务】Sentinel硬编码模式源码解析

方法时,Sentinel就会以责任链模式的方式逐一调用chain中的slot去进行规则校验,一旦某个slot校验不通过,那么就终止,所有slot都检验通过才算校验通过,最后entry方法会返回一个Entry对象。业务逻辑执行完毕后,我们需要调用Entry的exit()方法,Entry对象默认是CtEntry类型,那么调用的就是CtEntry的exit()方法。)方法进行规则校验。可以看到fireEntry()就是用于调用下一个slot的transformEntry方法触发该slot的执行的。

2024-11-24 11:37:16 1278

原创 【深入理解SpringCloud微服务】Sentinel功能详解

上一篇文章《Sentinel实战与原理剖析》我们没有对Sentinel的功能进行讲解,本篇文件将对它们进行详细的解析。

2024-11-24 11:34:42 1570

原创 【深入理解SpringCloud微服务】Sentinel实战与原理剖析

如果是引入的spring-cloud-starter-alibaba-sentinel依赖,那就不需要配置Sentinel切面类了,spring-cloud-starter-alibaba-sentinel已经通过自动装配的方式帮我们配置好了。使用拦截器方式,就不需要声明@SentinelResource注解了,如果通过控制台配置规则的话,甚至连上面FlowRuleInit的流控规则配置都可以不需要,只需要引入依赖jar包,然后配置一个拦截器即可。剩下的就是在控制台配置规则。

2024-10-26 11:27:13 1006

原创 【深入理解SpringCloud微服务】Hystrix作用与原理剖析

前面三篇文章,我们已经了解了微服务的熔断、限流、降级,并且也手写了一个熔断限流框架。从本篇文章开始,我们了解一下相关的开源框架Hystrix与Sentinel,本篇文章先解析Hystrix,后面的文章解析Sentinel。Hystrix其实已经算是一个过期了的技术,说实话不学也没什么问题,毕竟现在都有Sentinel了,但是我们简单了解一下,有助于与Sentinel作对比。

2024-10-26 11:25:28 1248

原创 【深入理解SpringCloud微服务】手写实现断路器算法

假如时间往后走了5秒,此时时间是2024-04-05 20:09:25,时间戳是1712318965000,那么currentTimeSeconds = 1712318965000 / 1000 = 1712318965,然后timeIndex = 1712318965 % 5 = 0,得到的时间窗格数组下标又是0,然后times[timeIndex]是1712318960,不等于1712318965,那么得知时间窗格已过期。我们的BasicBreaker实现的是滑动时间窗统计失败数的断路器。

2024-10-06 09:06:27 1244 1

原创 【深入理解SpringCloud微服务】手写实现各种限流算法——固定时间窗、滑动时间窗、令牌桶算法、漏桶算法

我们在上一篇文章手写实现了一个熔断限流微服务框架,但是限流算法和断路器的具体实现没有进行详细分析,今天是对上一篇文章的补充。当然由于本篇文章专注于限流和断路器算法,因此没有看过上一篇文章也可以学习本篇文章。

2024-10-06 09:03:59 1406

原创 【图解秒杀系列】秒杀技术点——隔离、熔断、限流、降级

一个容器有它独立的与外界隔离的环境与资源,包括CPU、内存等,如果容器中的服务出问题,比如占用资源过高,也不会影响到别的容器中的服务。秒杀系统的隔离是指单独部署一套秒杀服务,与常规的服务区分开,这样即使秒杀服务出了问题,原先的服务也不会受影响。比如服务A的接口调用服务B的接口发生异常或超时,那么服务A的接口进行降级逻辑,调用本地的一个方法,返回默认的数据。接口级别的降级是指,原有的逻辑无法正常执行,转而执行的一段备用的有损的逻辑。被流控的请求,可以报错返回错误信息,也可以走降级处理的逻辑。

2024-09-29 09:04:29 1365

原创 【深入理解SpringCloud微服务】了解微服务的熔断、限流、降级,手写实现一个微服务熔断限流器

我们通过SpringAOP扫描被@Protected注解修饰的方法,给它们生成代理对象,并且通过SpringAOP的@Around注解定义了环绕增强逻辑,当请求被@Protected注解修饰的接口方法接收时,就会进入这里的增强逻辑,增强逻辑做的自然就是熔断、限流、降级等这些事情。当调用一个接口方法时,如果被流控,或者断路器处于打开状态,或者执行目标方法时超时或抛出异常,并且接口设置了降级回调,那么执行对应Fallback实现类的降级处理方法。限流也叫流控,也就是流量控制,作用是限制流入服务接口的流量大小。

2024-09-21 08:24:04 1970 1

原创 【深入理解SpringCloud微服务】深入理解nacos配置中心(六)——spring-cloud-context关于配置刷新的公共逻辑

我们在上一篇文章《客户端监听配置变更并刷新的源码分析》中最后说到,nacos客户端监听到配置变更通知后,会发布一个RefreshEvent事件,触发spring-cloud-context关于配置刷新的公共逻辑。但是由于它不是nacos实现的逻辑,我们没有对这段逻辑进行分析,本篇文章将会分析spring-cloud-context关于配置刷新的公共逻辑。

2024-09-21 08:19:51 1389

原创 【深入理解SpringCloud微服务】深入理解nacos配置中心(五)——客户端监听配置变更并刷新的源码分析

listener.receiveConfigInfo(contentTmp)里面会调用到innerReceive方法,就进入了在NacosContextRefresher的registerNacosListener方法创建的监听器实现的innerReceive方法。...@Override...// 发布一个RefreshEvent事件...});...这里的监听器的innerReceive方法被触发,发布一个RefreshEvent事件。

2024-09-14 09:34:33 1744

原创 【深入理解SpringCloud微服务】深入理解nacos配置中心(四)——配置新增或修改源码分析

dump配置内容到磁盘文件是通过DumpService进行的,DumpService先把新增或修改的配置dump到磁盘文件,然后根据文件内容算出一个MD5值,再拿到算出的MD5值与缓存中该配置文件对应的MD5值进行比较,如果两MD5值不一致,说明修改后的配置内容与修改前不一致,也就是发生了配置变更,需要更新MD5值并通知客户端。ConfigCacheService的dump方法在服务端启动的时候也会调用,这个我们在上一篇文章《服务端启动与获取配置源码分析》已经分析过,这里就不再分析了。

2024-09-14 09:31:06 720

原创 【深入理解SpringCloud微服务】深入理解nacos配置中心(三)——服务端启动与获取配置源码分析

ConfigCacheService的updateMd5方法首先从从一个ConcurrentHashMap中根据groupKey获取CacheItem,然后判断如果CacheItem等于空或者CacheItem中的md5值与刚算出的md5值不匹配则进入分支,由于是刚启动,因此CacheItem肯定等于空,所以会进入if分支。由于已经查询MySQL然后dump到磁盘成配置文件了,因此当接收到获取配置的RPC请求时,就不需要再去查询MySQL,而是读取磁盘的配置文件即可。

2024-09-07 10:12:47 1312

原创 【深入理解SpringCloud微服务】深入理解nacos配置中心(二)——客户端启动源码分析

NacosPropertySourceLocator的loadNacosDataIfPresent()调用loadNacosPropertySource方法获取配置信息,返回NacosPropertySource,然后把NacosPropertySource放入CompositePropertySource。然后循环遍历每一个PropertySourceLocator,调用locate方法,返回的PropertySource对象放入CompositePropertySource中。

2024-09-07 10:11:39 1123

原创 【深入理解SpringCloud微服务】深入理解nacos配置中心(一)——宏观理解nacos配置中心原理

nacos配置中心的作用就是在微服务架构中负责集中管理各个微服务工程的配置文件,原先散落到各个微服务本地的配置文件,现在都集中存储到nacos上,这样更方便管理和维护。nacos配置中心客户端以jar包的形式被各微服务引入,然后微服务启动的时候,会通过nacos配置中心客户端请求nacos服务端拉取该微服务对应的配置,然后把拉取到的配置加载到Environment中。nacos有一个控制台界面,可以在上面修改配置信息。

2024-08-31 11:12:59 1273

原创 【深入理解SpringCloud微服务】深入理解微服务配置中心原理,并手写一个微服务配置中心

这里我们可以再回顾一下服务端的LongPollingService和客户端的LongPollingClient的关系。通过websocket建立了长连接建立连接后,会回调监听器RefreshListener发送对应的environment和serviceName,LongPollingService接收到后会保存其与Session的对应关系。

2024-08-31 11:10:45 1999 1

原创 【图解秒杀系列】秒杀技术点——缓存预热,库存预扣 & 异步下单,超时未支付库存回填

由于热点数据没有加载到缓存中,当秒杀开始时,大量请求由于缓存缺失,都涌向了数据库,此时数据库就会面临很大的压力。但是有时候用户超时了也没有进行支付,此时除了要告知用户比如“超时未支付,秒杀失败”等的消息外,还要把扣减的库存回填回去,如果缓存中还有售罄标志,那么还要把该标志修改为false,此时秒杀按钮再次从灰变成亮。“检查redis中的库存”,“以及扣减redis中的库存两个动作”,必须保证原子性,因此这里可以使用lua脚本的方式来处理。lua脚本在redis中执行可以保证脚本中的多个动作具有原子性。

2024-08-24 10:11:10 1846 3

原创 【图解秒杀系列】秒杀技术点——多级缓存、分层过滤

但是,直接用HashMap做本地缓存有一个缺点,就是对于一些冷数据的删除是不方便的,如果不做删除的话该HashMap占用的空间会越来越大,因此我们还要自己维护数据的访问频率或者最近的访问时间,以便于冷数据的删除。分层过滤是秒杀系统中高效处理高并发请求的策略,分层过滤采用“漏斗”式设计,通过在不同层次上逐步过滤无效请求,确保只有有效请求能够到达系统后端,从而减轻系统压力,提高系统的处理能力和响应速度。一般而言,我们会把本地缓存的过期时间设置的短一点,而分布式缓存的过期时间设置的较长一点。

2024-08-24 10:09:13 1333

原创 【深入理解SpringCloud微服务】Spring-Cloud-OpenFeign源码解析(下)——LoadBalancerFeignClient详解

由于LoadBalancerFeignClient里面使用到了RxJava ,因此我们要先了解一下RxJava ,RxJava 是一个在Java平台上实现响应式编程的库,用于处理异步数据流。我们看一下LoadBalancerFeignClient里面用到的RxJava的相关方法,其他的RxJava方法我们就不研究了,因为重点是LoadBalancerFeignClient的源码,而不是学习RxJava。// 创建一个负载均衡命令对象try {

2024-08-18 11:18:18 1379

原创 【深入理解SpringCloud微服务】Spring-Cloud-OpenFeign源码解析(上)

Feign对Ribbon和各种http客户端工具类(如OKHttp、HttpClient、HttpURLConnection等)进行了封装,使得开发者无需再手动调用RestTemplate发起http请求,而是以方法调用的方式向远处服务发起请求。当使用Feign之后,开发者要做的就是在接口以及接口方法上使用Feign的注解修饰,Feign就会扫描并给这些接口生成一个代理对象,当我们调用接口的方法时,实际上就是调用代理对象,底层就会通过Ribbon进行负载均衡,然后使用http客户端发起http请求。

2024-08-18 11:16:18 1169

原创 【图解秒杀系列】秒杀技术点——秒杀按钮点亮、削峰

因此在生成的这个静态页面中,会加入一个特殊JavaScript,该JavaScript携带了秒杀是否开始的标识,该JavaScript不会被浏览器缓存,每次刷新页面都会请求一次,由于该JavaScript文件的体积很小,因此每次刷新页面都请求一次也不会对服务器造成什么压力。增加了答题或者验证码机制后,每个用户答题或输入验证码的速度都是不一样的,有的快有的慢,这就有效的把不同用户下单的请求在时间上错开了,很好的把一瞬间的请求均摊到一个相对较长的时间范围内,使得服务承受的并发压力大大降低。

2024-08-17 09:31:45 1431

原创 【图解秒杀系列】秒杀技术点——静态化

静态化就是指通过某种静态化技术,将原本需要动态渲染生成的HTML页面固定下来变成一个静态页面文件,后续请求该页面都直接返回该静态页面。首先要有模板和数据,然后根据给定的模板和数据,通过模板引擎,就能生成对应的静态HTML文件。生成的静态HTML页面,可以推到Nginx上缓存到Nginx本地。当用户请求访问对应的页面时,Nginx直接返回缓存在本地的静态页面,这样响应速度就大大提升。在秒杀场景中,商品详情页就可以进行静态化处理,提升商品详情页的访问速度。

2024-08-17 09:28:43 1318

原创 【深入理解SpringCloud微服务】深入理解微服务中的远程调用,并手写一个微服务RPC框架

我们可以定义一个注解(比如OpenFeign的@FeignClient注解),注解需要指定服务名(表示这个接口是请求哪个微服务的),然后通过Spring的ClassPathBeanDefinitionScanner扫描我们自定义的注解修饰的接口,然后生成BeanDefinition,注册到Spring容器中。用于修饰启动类,表示启动微服务RPC功能。获取到服务名后,然后要读取接口方法上的注解,解析注解上的url,从注解解析出来的url前面拼接上修饰接口的自定义注解的服务名,就形成了完成的请求地址。

2024-08-10 09:56:02 1203

原创 【深入理解SpringCloud微服务】Ribbon源码解析

RibbonLoadBalancerClient首先调用getLoadBalancer(serviceId),根据服务名(serviceId就是服务名),取得对应的ILoadBalancer。RestTemplate发起的http请求会被LoadBalancerInterceptor的intercept方法拦截,LoadBalancerInterceptor的intercept方法从url中取出域名部分作为服务名serviceName,然后调用LoadBalancerClient的execute方法。

2024-08-10 09:54:40 1303

空空如也

空空如也

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

TA关注的人

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