自定义博客皮肤VIP专享

*博客头图:

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

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

博客底图:

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

栏目图:

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

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

-+

长三月的游戏开发

资深游戏开发者,专注于Java游戏服务端技术

  • 博客(97)
  • 收藏
  • 关注

原创 游戏服务器开发指南(序)

一方面,作为一个十多年经验的游戏开发者,很希望把自己掌握的知识和经验系统化;在当前浮躁的社会背景下,技术大概率是无法做一辈子的,但是深耕一个领域,尽可能延长自己的职业生涯,增强不可替代性,无疑是有益的。系列标题是《游戏服务器开发指南》,它也揭示了本系列的写作主题:汇集游戏服务器开发领域一些有价值的经验法则,这些经验法则来自与业务实际紧密相关的细分话题,结合本人多年经验和实践,确保分析深入浅出。在我多年的游戏服务器开发生涯中,学习过大量网络编程和数据存储方面的知识,也看过不少游戏服务器相关的书籍。

2023-03-14 09:52:50 338

原创 近年GDC服务器分享合集(四): 《火箭联盟》:为免费游玩而进行的扩展

上线之后的巅峰在线人数多于改版前的5倍,但是少于压力测试的最高值,因此还在允许的承载范围内,原先的5倍预估还算比较准确。在线人数稳定后也仍然维持在3倍的高位,更多的在线对服务器带来了更大的承载压力,因此后续版本迭代也在进行持续的压力测试。早早开始规划。不要羞于寻求外界帮助,但也不要过度规划。要做好压力测试很难,但是非常重要。有序安排好重要的特性开发,依据中间的时间节点按时完成。像流量限制一样实现多种功能的控制。

2023-09-03 13:22:33 653

原创 一文弄懂游戏服务器的云原生化

如今,云原生技术在游戏行业的应用越来越广泛。笔者对此有浓厚兴趣,特地集中学习了一段时间。这篇文章既是记录学习过程的个人笔记,也是游戏服务器云原生的入门引导文章。读完这篇文章,可以对云原生在游戏行业的应用现状、游戏服云原生化的重难点及主流解决方案有初步了解。

2023-07-11 22:12:21 339

原创 游戏服务器开发指南(八):合理应对异常

JDK中也有对fail-fast思想的运用,例如ArrayList是一个非线程安全的容器,如果在使用for-each遍历元素的同时又修改了它的结构,那么会第一时间抛出ConcurrentModificationException,快速而干净地失败,而不是冒着在未来不确定的时间出现不确定行为的风险。所以,对于这种影响全局的严重问题,不如在停服重启的时候就彻底解决好,解决了再对玩家开放服务器。合理地处理异常,能够将故障对玩家的影响降到最低水平,快速从错误中恢复,并提供充足的信息,避免以后再出现类似的问题。

2023-06-10 11:32:44 731

原创 近年GDC服务器分享合集(三): 《Sky光·遇》实现百万在线:一种云原生的扩容方法

分享者推荐的实现是使用Saga,它具有稍弱的一致性,但是非阻塞而且执行速度快,能保证最终的数据一致性。Saga是一种补偿事务,它的运行逻辑是:将分布式事务看作一组事务组成的事务链,链中的每一个正向事务操作,都对应一个可逆的事务操作。:十二要素应用,又名12-Factor,它定义了一个优雅的、适应云环境的互联网应用在设计过程中,需要遵循的一些基本原则。最终,《Sky光·遇》团队实现了支撑100万在线的目标。:简称Postgres,一种常见的关系型数据库,具有丰富的数据类型和强大的索引,在国外应用比国内更多。

2023-06-04 15:08:25 661

原创 游戏服务器开发指南(七):资源流通保证先减后增的顺序

更重要的是,数据在事后是可恢复的,只需要检查报错日志和接口日志,就能计算出玩家错误扣除的金币数额,再给玩家补上,而不像刷资源那样只能使用回档的方式来暴力解决。如果刷资源的人数众多,而且刷到的资源又被转换成了其他资源不好追溯,那么不得已只能使用最后的保留手段——回档。解决办法是调整逻辑顺序,改为先减后增的顺序,即先行扣除要捐的钱,再向公会agent请求捐款,如果捐款失败,那么再把钱返还给玩家。以上代码是按照先增后减的顺序,即先向公会agent请求,让公会得到这笔钱,然后如果捐款成功,那么再扣除自己的钱。

2023-05-27 18:25:20 238

原创 近年GDC服务器分享合集(二): 《太空工程师》中基于预测物理的多人游戏

另一类是服务端有一个完整的物理世界,运动状态以服务端每帧的计算结果为准,客户端只发送玩家操作给服务端,然后使用服务端的推送结果来修正当前的运动状态。将宇航员和飞船绑定后,宇航员的运动状态更新改为相对飞船的相对运动。(Relative Position Updates),具体来说是先定义物体之间的层次关系,再对子级物体做相对于它的父级物体的运动插值,即本地空间中的运动插值。这样造成的结果是:客户端上宇航员成功进入船舱,而服务端会碰壁,稍后服务端把碰壁消息推送给客户端,客户端据此修正,又将宇航员移出船舱。

2023-05-25 09:53:59 569

原创 游戏服务器开发指南(六):条件判断永远放在状态变更前

试想,如果把对商品剩余数量的判断移到consumeMoney之后,那么可能会发生消耗了金币商品却没有到账的情况,这肯定是无法接受的。而游戏服务器的业务又与互联网或者其他领域不同,有着自己的特点。这条原则意思是为接口编写处理逻辑时,先罗列所有的条件判断,如果所有的判断通过,那么才进行状态变更(如更新DB或者修改内存状态),否则返回接口失败的信息给前端。建议的改进写法是修改原有的drawLottery方法,将其改造成抽1次和抽10次都可以调用的通用方法,这样不仅保证了代码的复用性,还不会带来原写法的上述缺点。

2023-05-14 19:36:48 413

原创 近年GDC服务器分享合集(一):在《黑道圣徒》的boss工厂中创建可扩展和吸引人的UGC

黑道圣徒》是一个经典的犯罪题材RPG游戏IP,玩家可以扮演黑道与其他人进行畅快淋漓的枪战。这次演讲的内容来自最新发售的《黑道圣徒》重启版。在这个版本中,新增了Boss Factory的功能,玩家可以自定义角色的容貌和道具,并允许在线上与他人分享。演讲人是一位印度哥们,印度口音不重,发音清晰标准。他自述从产品经理转到开发,拥有10多年的跨行业服务器经验,以及4年多的游戏服务器经验。

2023-05-08 22:14:39 196

原创 用Java实现Actor模型(模仿Skynet)

Actor模型是一种常见的并发模型,与最常见的并发模型——共享内存(同步锁)不同,它将程序分为许多独立的计算单元——Actor,每个Actor独立管理自己的资源,不同Actor之间通过消息传递来交互。它的好处是全异步执行,不会造成线程阻塞,从而提升CPU使用率,另外由于线程之间是异步交互,所以也不用考虑加锁和线程同步的问题。Actor模型在业界有许多应用,例如游戏服务器框架Skynet、编程语言Erlang。因为历史原因,Java下的Actor模型应用较少,知名的只有基于Scala的Akka。

2023-05-03 13:53:53 1480 5

原创 游戏服务器开发指南(五):避免死锁

定时锁可以设置一个超时时间,当超过这个时间还未获得锁时不会继续阻塞等待,而是返回失败状态,程序员可以根据返回状态的不同,决定是继续执行同步块内部的代码,还是回滚当前操作。一种特殊的情况是是不带超时时间的定时锁,在这种情况下,尝试获得锁会立即返回成功或失败,失败后可以间隔一小段时间重试获取锁,直至超时操作失败,这种情况下的锁称为轮询锁。形成死锁后,不仅线程无法再被使用,而且线程持有的资源(锁)也无法再被其他线程获取,这会导致程序的全部或部分功能无法正常运行,对于游戏服务器来说需要重启才能恢复。

2023-04-30 22:54:21 113

原创 游戏服务器开发指南(四):降低同步的开销

为了保证房间状态的正确性,我们需要保证进入房间和退出房间的操作为原子操作,例如进入房间至少应包括:判断是否能进入,将玩家id加入到房间中的玩家集合,当玩家人数满5人时自动开启战斗等,这一系列操作应该组合为一个原子操作不可分割。游戏模式是开房间战斗,玩家可以自由进入或退出战斗房间,当房间人数满5人时自动开启战斗,当房间人数为0时房间自动解散,玩家进入或退出房间都会向房间中的其他人推送进入或退出的消息,玩家有每日可战斗次数,进入房间时消耗1次次数,退出房间时增加1次次数,该次数会计入DB。

2023-04-19 23:16:51 394

原创 游戏服务器开发指南(三):设计高效的线程模型

这样设计的问题是,由于战斗计算是CPU密集型的计算任务,当房间数量增多到一定程度时会达到单核性能瓶颈,无法在原定的一帧时间内执行完所有的房间计算,导致掉帧的情况出现。同样典型的场景还有MMO中的大地图,由于整个地图运算量巨大,通常会考虑使用多线程分担计算量,拆分角度通常是按地图区域,每个区域分配一个线程,或者是按业务类型,每种独立的业务逻辑分配一个线程。例如,恰好玩家分配到线程1上的数量较多,而且来自这部分玩家的请求数也较多,就会造成线程1繁忙,而其他线程空闲的情况。一周一次的系列分享又与大家见面了。

2023-04-09 23:36:45 709 2

原创 5天带你读完《Effective Java》(五)

在没有考虑默认序列化形式是否合适之前,不要接受它。如果对象的物理表示与其逻辑内容相同,则默认的序列化形式可能是合适的。/*** @serial/*** @serial/*** @serial即使你认为默认的序列化形式是合适的,你通常也必须提供readObject方法来确保不变性和安全性。Entry next;将导出的 API 永久地绑定到当前的内部实现。占用过多的空间。消耗过多的时间。可能导致堆栈溢出。

2023-04-07 23:30:01 880

原创 游戏服务器开发指南(二):优化包体传输

大家好!我是长三月,一位在游戏行业工作多年的老程序员,专注于分享服务器开发相关的文章。这个系列本来计划每周更新一篇。本周因工作事情比较多,拖到周日才更新,让大家久等了。这次的主题是优化包体传输,是网络通信类别下的第二篇。在游戏服务器的部署环境中,机房的网络带宽都是有限制的。如果通信传输的数据总量太大,会挤占带宽甚至达到带宽上限,影响正常消息发送。另外,如果包体太大,在弱网环境下的通信质量会变差,更容易发生丢包重传和延迟,而且大包对于截包和查找问题也不方便。

2023-04-02 23:32:13 302

原创 游戏服务器开发指南(目录)

本目录用于游戏服务器开发指南系列,分门别类存放文章的链接地址,方便读者快速索引。目录按不同的主题组织,如网络通信、数据存储等,每个主题会包含若干篇文章。个人计划是平均每周写一篇,写作基于平时工作中产生的灵感,因此不一定会按照主题的排列顺序来。

2023-03-23 09:46:06 169

原创 游戏服务器开发指南(一):设置合适的Socket选项

上周写完系列序言,得到不少读者朋友的关注,这也给了我额外的动力。写东西就是这样,都希望获得更多的关注,如果写出来没什么人看,那就无异于闭门造车、自娱自乐。欢迎朋友们在文后留言,我也会根据大家的反馈适时调整写作的内容和方式。我在这里整理了一个系列目录,用于分门别类存放文章的链接地址,方便读者快速索引。目录按不同的主题组织,如网络通信、数据存储等,每个主题会包含若干篇文章。个人计划是平均每周写一篇,写作基于平时工作中产生的灵感,因此不一定会按照主题的排列顺序来。

2023-03-23 09:41:53 505 1

原创 手把手教你写一个极简版Jedis

Jedis是Redis官方推荐的Jedis客户端,在业界使用非常广泛。通过阅读Jedis源码,可以掌握如何设计和实现一个轻量级、带连接池的Redis客户端。我常常说,学习源码最好的方法就是以自己的思考重新写一遍,这样有助于把学到的知识固化成实际经验。本文会带着你从零开始设计一个极简版的Jedis,从支持最简单的GET/SET命令出发,逐步添加缓冲区、多种命令、断线重连、连接池等多种功能。本文的源代码放在Github上(mini-jedis),感兴趣的读者可以自行取阅。

2023-03-10 23:44:56 431

原创 5天带你读完《Effective Java》(四)

Effective Java》是Java开发领域无可争议的经典之作,连Java之父James Gosling都说: “如果说我需要一本Java编程的书,那就是它了”。它为Java程序员提供了90个富有价值的编程准则,适合对Java开发有一定经验想要继续深入的程序员。本系列文章便是这本著作的精华浓缩,通过阅读,读者可以在5天时间内快速掌握书中要点。为了方便读者理解,笔者用通俗易懂的语言对全书做了重新阐述,避免了如翻译版本中生硬难懂的直译,同时对原作讲得不够详细的地方做了进一步解释和引证。

2023-03-04 22:31:19 866

原创 5天带你读完《Effective Java》(三)

标记接口是一种不包含任何方法声明的接口,它只是标记它的实现类具有某种特性。如Serializable 接口,实现此接口的类可以写入ObjectOutputStream(被序列化)。如果要定义类型,那么使用标记接口优于使用标记注解。标记接口定义的类型由标记类的实例实现;标记注解不会。前者在编译时捕获错误,后者在运行时才能捕捉错误。可以更精确地定位。标记注解可以应用于任何类或接口,而标记接口限定于它的实现类。标记注解的优点是:它们可以是其他注解功能的一部分。

2023-02-25 18:55:24 752

原创 手把手教你写一个极简版Netty

Netty如今是使用最广泛的网络通信框架,许多人对此有强烈的学习需求。但是Netty本身代码量大、概念众多,单纯看代码学习容易一头雾水,看了后面忘了前面。这个问题的破解之道,就是以自己的思考重新写一遍,这是掌握一个复杂设计的最好方法。本文会带着你从零开始设计一个极简版的Netty,为你演示如何从最原始的BIO进化到NIO,再一步步添加Netty的Reactor线程模型和任务处理的。本文的源代码放在Github上,有兴趣的读者可以自己取阅。

2023-01-24 22:38:38 1304

原创 5天带你读完《Effective Java》(二)

接口只应该用来定义类型,不要用来导出常量。想要导出常量,可以把它们放在相关的类中,如Integer类中的MAX_VALUE;或者定义一个XXXConstants类来存放一组相关的常量。

2022-12-17 15:42:50 478

原创 5天带你读完《Effective Java》(一)

Effective Java》是Java开发领域无可争议的经典之作,连Java之父James Gosling都说:“如果说我需要一本Java编程的书,那就是它了”。它为Java程序员提供了90个富有价值的编程准则,适合对Java开发有一定经验想要继续深入的程序员。本系列文章便是这本著作的精华浓缩,通过阅读,读者可以在5天时间内快速掌握书中要点。为了方便读者理解,笔者用通俗易懂的语言对全书做了重新阐述,避免了如翻译版本中生硬难懂的直译,同时对原作讲得不够详细的地方做了进一步解释和引证。

2022-12-13 22:09:40 795

原创 跟着实例学Go语言(四)

本教程全面涵盖了Go语言基础的各个方面。一共80个例子,每个例子对应一个语言特性点,非常适合新人快速上手。教程代码示例来自go by example,文字部分来自本人自己的理解。本文是教程系列的第四部分,共计20个例子、约1.5万字。Go提供了Base64编解码的功能。Base64是将二进制数据转换成可读字符串的编码方式。例如我们用记事本打开jpg文件,会看到一串乱码,这个就是Base64转换后的字符串。Base64提供了64种不同的字符,6位编码表示一个字符。62. Reading File

2022-12-06 10:01:47 369

原创 跟着实例学Go语言(三)

本教程全面涵盖了Go语言基础的各个方面。一共80个例子,每个例子对应一个语言特性点,非常适合新人快速上手。教程代码示例来自go by example,文字部分来自本人自己的理解。本文是教程系列的第三部分,共计20个例子、约1.5万字。Go中处理同步的另一方式是使用互斥锁,这也是许多其他语言会采用的方式,保证加锁的代码块为原子操作。42. Stateful Goroutines比起互斥锁,Go更推荐使用通道通信的方式来实现goroutine之间的同步。以下是一个使用channel通信来实现并发

2022-12-05 09:56:34 311

原创 跟着实例学Go语言(二)

本教程全面涵盖了Go语言基础的各个方面。一共80个例子,每个例子对应一个语言特性点,非常适合新人快速上手。教程代码示例来自go by example,文字部分来自本人自己的理解。本文是教程系列的第二部分,共计20个例子、约1.2万字。下面的例子展示了用interface关键字定义接口。接口是一组方法签名的集合,用于实现多态。Go不像其他语言那样需要通过extend关键字显式指定类型的继承关系。如果一个struct类型实现了interface中定义的所有方法,那么就认为这个struct类型属于interfa

2022-12-04 20:39:51 416

原创 跟着实例学Go语言(一)

本教程全面涵盖了Go语言基础的各个方面。一共80个例子,每个例子对应一个语言特性点,非常适合新人快速上手。教程代码示例来自go by example,文字部分来自本人自己的理解。本文是教程系列的第一部分,共计20个例子、10233字。下面的例子演示了如何打印经典的“Hello world”语句,以及运行和编译go代码的方法。2. Values下面的代码演示了打印不同类型变量的方法。3. Variables下面的例子演示了定义和初始化变量的方法。Go的编译器可以做类型推导,无需显示指定类型。

2022-12-04 12:08:49 558

原创 ArrayList 和 LinkedList 之间应该怎么选择

虽然ArrayList会扩容到1.5倍,但是LinkedList内部为每个存储元素封装了Node对象,除了Node对象头部,还要保存前后Node的地址和当前存储元素的地址,实际上LinkedList占用内存要远大于ArrayList。另外在空间上,ArrayDeque底层使用的循环数组也是优于LinkedList使用的Node链表。我写了它,但是我从没用过”。相信很多人在面试时都被问过这个问题,然后一般回答:ArrayList在指定下标访问时快,LinkedList在插入/删除元素时快。

2022-11-30 14:32:08 704

原创 Netty对JDK做了哪些定制和优化

Netty在JDK的基础上做了部分定制和优化,使之更适合高并发、网络通信的场景。

2022-11-29 09:40:41 319

原创 Java游戏服务器中定时任务的四种实现方式

工作中研究过Java技术栈下几种常见的定时器实现,在此做个总结:在游戏服务器中几乎不会使用它,因为有明显的缺点:首先,它是单线程执行的,如果某个任务执行太长,可能会影响后续任务的准时执行。其次,游戏中经常有修改系统时间测活动的需求,这种场景不适合。因为它底层是在schedule task的时候就算好了wait的时间,无法在系统时间修改后自动调整。最主流的实现方式,也是Netty等框架实现定时任务的方式。基本思路是:开单独线程不断循环,每次sleep固定时间片,然后依次遍历任务队列中的任务,若到时间了就扔到业

2022-11-28 09:50:34 1049

原创 Netty 5新Buffer API详解

Netty 5的第一个alpha版本于2022/5/17发布。不同于老早发布但后面长期封存的版本,这次的新Netty 5改动相对3到4的升级来说没有那么大,侧重点放在了更安全好用的Buffer API和其他一些API的优化上。本文介绍的内容正是这次新版本的重头戏——新的Buffer API。

2022-11-03 07:18:36 246

原创 Java热更新失败常见原因总结

热更新是Java开发者经常需要考虑的一个问题,无论是游戏还是互联网应用,都需要尽量做到运行时代码修复,以避免重启给用户体验带来的负面影响。目前主流的热更新方案是基于Java的Attach和Instrumentation API。热更新时需要满足不改变方法签名或者类的字段。在普通情况下我们比较容易通过diff看出是否有上述改动,但是在一些特殊情况下失败原因却藏得很深。本文就是通过总结这些特殊情况,避免大家踩坑。本文介绍了几种常见的Java热更新失败情况,对这些情况的理解和掌握有助于读者避免踩坑。

2022-10-17 21:10:41 2103

原创 Redis使用心得

1.Redis适合做什么,不适合做什么?2. 架构选型3. 是否需要代理4. 持久化方式选择5. 避免大key出现6. 批量操作请用pipeline7. Redis版本选择

2021-05-09 23:48:01 217 1

原创 如何有效阅读一本技术书

俗话说,知己知彼方能百战不殆。我们先来分析下读书这种形式的优缺点。相比阅读博客、搜索等轻量级的学习方式而言,读书的主要缺点是:效率低、目的不明确。一本技术书为了保证专业性,往往填充大量细节,同时为了兼顾不同层次背景的读者,希望面面俱到,进一步增加了篇幅。读这样的书,如同在沙滩上拾贝壳,读了半天,发现真正消化的知识不多,剩下的大部分是无用的,或是背景不够理解不了的。例如这本《深入理解Java虚拟机》,第一章用很大篇幅在讲Java虚拟机的发展历史。对于普通读者而言,除非是专门做JVM研发的,否则根本没

2021-04-30 22:02:29 357 4

原创 从一条阿里编码规范谈线程池的使用

最近在看阿里Java编程规范,有一条引起了我的注意:【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。说明:Executors 返回的线程池对象的弊端如下:FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为Intege...

2020-03-25 17:13:33 1188

原创 记一次服务端定时器问题的解决

最近遇到了一次服务端定时器性能瓶颈的问题,把解决过程在此记录一下,作为备忘。首先是前一段线上项目连续导人,平均在线千人左右,随之服务器慢接口(>100ms)越来越多,一天能达到五位数。观察内存占用情况,不高,没有频繁old gc的情况。观察cpu占用率,达70%-90%。这个数据是比较高的,比一般同等人数在线的游戏高出不少。这说明出现这种情况不太正常,并不是单纯在线高需要分服务器的问题,可...

2020-03-05 11:48:01 371 1

原创 如何使用UWA做游戏性能优化

UWA是现在常用的Unity手游性能分析工具,好处是比Unity自带的工具或Xcode更详细和直观。最近公司的项目也在使用,将使用经验在此做个分享。先看下UWA的产品线:GOT Local/Online线上测评线下深度优化三者之间的定位参见下图:简单的说:如果想要快速定位问题、快速拿到分析结果,那就选择GOT。GOT分Local版和Online版,前者可以通过在本地搭建服务器查...

2019-06-23 11:15:59 3474

原创 如何进行一场高质量的游戏技术面试(原则篇)

最近项目组在大量招人,我作为项目组的技术主管参与了各种技术职位的招聘,在此将一些心得总结一下。打算做成一个系列,分不同岗位论述:后端(Java)和前端(Unity, Cocos, H5)。这篇作为总纲,先谈一下大的招聘原则。首先是要明确招聘的岗位需求。如果是招聘资深的工程师,更多是看项目经验和性能优化相关;如果是招初级的,更多是看基础知识的掌握,侧重算法和数据结构等。切不可一味地追求问所谓高端...

2019-05-29 10:14:17 724

原创 如何进行一场高质量的游戏技术面试(实战篇)

接着上一篇写,上一篇是讲理论,那这一篇则是讲实战。这里针对不同的工种,列出一份我在实际面试过程中会用到的考核大纲,算是抛砖引玉。Java(服务端)项目经验:业务模型,总体架构,自己负责的模块Java语言:GC,多线程,性能分析(Jstack、Jmap),容器类,NIO,内存分配方式,类加载,传值传引用,内部类数据库:索引,分页查找,排序,行级锁、表级锁算法:LeetCode简单和中级难...

2019-05-28 22:46:50 983

原创 【bat】批量修改文件名脚本

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入欢迎使用Ma...

2019-01-31 23:58:06 884 1

空空如也

空空如也

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

TA关注的人

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