Blog
Blog
PHODAL
PS:文章仅为个人观点 —— 本文的内容基于我这几年在开源世界的观察得出的结论,并非调查所得到的结果。 上周,我在 GitHub 上发布了 Ledge 知识平台,我以一种“重量级”的方式来运行这个开源项目。换句话来说,以正确的方式运行起了这个项目。因为我知道怎么运作一个开源项目,加上一些外部的原因,我开始思考个人开源和组织开源的一些困境。 开始之前,让我讲个笑话和无奈: 组织开源的四大笑话是:一次性开源、按揭开源、KPI 驱动式开源、社区是什么?(分别代表了国内的几加大公司的开源做法) 不好意思,你们是对开源有什么误解吗? ## 什么是开源 事实上,我们经常混淆了两个概念,那就是开源软件和开源这个行为。 > 开源软件是源代码可以任意获取的计算机软件,任何人都能查看、修改和分发他们认为合适的代码。开源软件依托同行评审和社区生产,皆以分散、协作的方式开发。 —— 红帽官网 换句话来说,你选择一个协议,将你写的代码公开发布,这叫开源一个软件。但是,它并不叫你搞开源。开源源于开源软件,但是它现在已经成为超越软件生产的运行和工作方式。 > 开源源于开源软件生产的运行和工作方式,它是一种基于去中心化、自组织式的软件开发模式运作的工作方式。它以社区作为根基,通过开放、透明、协作几项原则开展的活动。 ## 开源不是公开代码 在那本开源的《GitHub 漫游指南》里,我一直在讲述如何在 GitHub 上开发一个 “成功” 的开源项目。因为开源不仅仅只是说源代码的开源,还包含了设计文档、产品的内容等等,还要以开源的方式来运作。以 opensource.com 对于开源方式的解释来说,需要这么五个维度: 1. 透明度。 2. 协作。 3. 尽早发布、持续发布。快速建立原型,发布第一个版本,并且不断地快速地迭代。 4. 精英制度。根据提出的最佳方案做决定的方式 5. 社区。形成社区,提升社区参与度,转化为社区目标。 也因此,如果只是公开源码的话,那是走到开源的第一步,刚来到开源的起跑线上而已,还没参与到这个游戏中去。 ## 一个开源项目是一个产品 作为一个资深的开源运动参与者,我有一个这样的体会:运营一个开源项目,就好像创业一样。我们需要采用《黑客与画家》作者 Paul Graham 所说的创业公式: 1. 搭建原型 2. 上线运营 3. 收集反馈 4. 调整产品 5. 成长壮大 所以,开源就像是一场小型创业,需要进行竞品分析,需要制定合理的策略。当然了,如果你的东西绝无仅有,那就无需如此。而除了分析市场,针对于开发人员,还要考虑: 1. 作为投资人(技术投资),他们能获得什么?提升技术?找个好工作? 2. 作为潜在的用户,从哪里知道这个项目? 3. 作为贡献者,如何提供不同级别的贡献计划? 4. …… 你并不一定非得去考虑这些问题,只要在持续完善的过程中,这些问题的答案就会浮现出来。只是呢,在你开始之前想好,可能会事半功倍。 ## 开源的重点在于生态建设 对于个人来说,开源的目的可能是找个好工作、为以后找个好工作……;对于一家组织来说,他们考虑开源可能有多种多样的目的: 1. 降低开发、维护成本。由社区来帮助寻找 bug,提出一些观点。 2. 技术影响力招聘 3. 建立技术壁垒。 4. 营造生态。 5. …… 一个好的开源作品,需要连接到上下游,即影响开发者,又影响使用者。慢慢地,它个作品就会成为一个影响行业的存在。尽管会不断有其它的项目冒出来,但是由于稳固的生态建设,将巩固组织在该领域的影响力。 ## 组织需要制定开源策略 从开头的大部分四大难题:一次性开源、按揭开源、KPI 驱动式开源、社区是什么?。我们就会发现:国内大公司的开源策略都是错的。 他们可能,今年发布 Phodal UI,明后发布 Phodal Compiler,后年发布 Phodal OS。然后,中间靠各种公关稿,完成在社区的宣传。 应该是这样的,今年发布 Phodal UI 1.0,年中发布 Phodal UI 2.0,明年发布 Phodal UI 3.0 和 Phodal Compiler 1.0,明年年中 Phodal UI 4.0 + Phodal Compiler 2.0。过程中,需要依赖于布道师来进行闭环: 1. 维护开发者关系 2. 在社区进行宣传 3. 对社区进行支持、收集社区反馈 4. 建立连接内部的通道 5. 促进内部进行改进。 这些组织需要建立一个具备可持续性的开源策略: 1. 明确其带来的业务价值(如人才引进 、生态等) 2. 专职的开发人员进行开源支持 3. 开放式的开源团队组织结构 4. 合理、适当地长期 KPI 考核机制 5. 政策和流程支持。如专项鼓励奖金 6. 明确地专利和知识产权机制 ## 开源到开放式组织 领导力变化,当我们在组织中开发一个软件应用时是以职权影响力为核心构建的;而开源方式,则是以非职权影响力构建的。 社区的每个人都可以提出自己的意见,你可以 say No,但是每个人都可以提出意见。就这一点来说,对于大部分的国内公司来说是一种挑战,大部分的领导希望听到统一的声音 —— 论组织内多样性的重要。 简单来说,大家想来就可以来,想走就可以走。所以,开源的一个难点就在于:如何吸引到人来参与开发。 尽管大部分项目都是围绕个人、团队的中心化开放式组织,如 linus 之于 Linux。但是,开源还可能变成一个中心化的组织,如 Node.js 的 IO.js 出走事件。根据开源协议,人们可以很容易派生出一个新的项目。 ## 结论 开源,就是生态。

> 文档代码化,将文档以类代码的领域特定语言的方式编写,并借鉴软件开发的方式(如源码管理、部署)进行管理。它可以借助于特定的工具进行编辑、预览、查看,又或者是通过专属的系统部署到服务器上。面向非技术人员的文档代码化的一种常见架构模式是:编辑-发布-开发分离』,

最近,我在设计、开发、维护一个基于『文档代码化』思想的平台。因为丰富的 markdown 经验和文档化系统的设计经验,我在这个系统中实施了很多过去的一些想法。系统工作得很好,但是代码却显得一片混乱。而,我突然觉得这是一件好事。 ## 最佳实践很浪费时间 对于敏捷开发来说,我只采纳了持续集成和持续部署的思想,即提交代码便发布到 GitHub Pages。但是,这也浪费了我很多的时间,而且我觉得没有必要,因为我已经有一个本地可以部署的脚本。我只需要在本地运行一下 ``deploy``,那么就会在本地构建,并部署到服务器上。然而,为了最佳实践的理念,我还是花了半天的时间,研究了一下 GitHub Action,然后让它实现自动部署。 系统的 UI 采用的于 Angular 框架,因为我懒得搭建脚手架,而且我还有一些先前的代码可以复用。所以,我 copy / paste 大量的代码,这些代码大部分都是没有测试覆盖的。是的,你很少看到我的开源项目是没有测试覆盖的 —— 毕竟写单元测试也是要花时间的。过去,我们统计过一个相关的数据,在我们日常的开发中,我们差不多有 1/5 的时间花在了单元测试。所以,一周下来,我差不多一天的时间在写测试这件事上。 ## 一个问题,三种方法实现 如开头所说,整个系统的核心是一个基于 markdown 的多功能渲染引擎。这部分的组件可以让你用 markdown: 1. 画出条形图、雷达图、思维导图 2. 画出甘特图 3. 画出特定的四象限 4. 调用 Web Components 5. …… 而为了实现这个功能,一共有三套不同的机制,当然了对于写 markdown 的人来说,它们是无感知的。这三种方法分别有: 1. 创建占位符,渲染完成后,替换占位符 2. 直接生成最后要渲染的 HTML 3. 生成一个 ID,在渲染的过程中,根据 ID 替换元素。 所以,整个过程就相当于,是解决一个问题有三个方法,然后我都用了这三种方法。 ## 起初,我只想创建个原型 起初,我只是想创建一个简单的系统,它只是一个简单的原型。而后,随着越来越多想法的产出,我创建了一个足够复杂的系统。所以,我起初设计的一系列要素都失效了。 我还有一堆糟糕的 SCSS 要管理,因为第一个版本设计的 CSS 体系,无法适用于新的架构。整个系统围绕在 markdown render 上,而这个 render 有大量的样式,就好像早期的多核 CPU 架构,只有一个 CPU 在工作,毕竟有大量的工作。 随着系统越来越复杂,我开始需要一个文档系统来管理这个文档系统,以便告诉我自己:系统里有什么功能? ## 一片混乱,真的很爽 是的,现在,现在虽然看上去界面很美观,功能也很强大。就好多是我们看到的其它软件系统一样,但是内部真的是一片混乱。如果你习惯了这样的系统的代码,那么你可能觉得这不是一个问题。 但是,我习惯了在项目中引用各种最佳实践。看了这样一个系统,觉得非常的爽 —— 大部分系统就是这样的:**同样的时间压力之下,我做得也就那样**。虽然,我的手速可能比大部分人还快,实现的功能可能更多。但是,这些都是无关紧要的。 如果没有充足的时间改善,我们的系统都变得一片混乱。没有符合未来变化的设计,更何况你可能没有时间设计。 ## 好了,是时候重构 所以,经历了一系列的过程之后,我决定挑个合适的时候重建这个系统。 1. 开始重写之前的 markdown converter。 2. 将 markdown render 所有相关的组件提取为框架。 3. 重写系统。 于是,我们就出现了第二个系统,它设计良好。它的灵感都来自于我们在做第一个系统中的感受。如果没有来自于我们第一个系统的经验,我们无法设计出第二个系统。 ## 经验:快速验证概念,创造业务价值 事实上,我们在市面上看到的大部分系统,都是以如此的方式演进的: - 第一个系统,赚了钱,创造了价值,但是缺少各种最佳实践,生存周期短。 - 第二个系统,设计良好,包含了各种实践,生存周期变长,但是慢慢变得臃肿 而我们的第二个系统很快将变成一个臃肿而缓慢的系统。 > 『第三个系统』由那些为 『第二个系统』 所累的人们创建。 —— 《Linux/Unix设计思想》 没有新系统,哪来的 KPI? 感谢您的支持,祝您生活愉快!

过去的几年里,我一直在打造各式各样的编程相关的工具。这些工具有的是用于指导软件开发工作,有的是用来进行编程学习,还有的纯粹是为了提升技术而写的。在我写了越来越多的工具,接触了越来越多的工具思路之后。 我便想写一篇文章,用于记录一下过程中发生的一些变化。

过去的一个月里,因为种种不可告人的原因,我开始建设一个 DevOps 知识平台。因为,现有的笔记系统都太 TM 难用了。所以,我要写一个,我要造一个轮子。在这一个轮子里,它可以通过 markdown 编写内容,并渲染出各种绚丽的效果。哦,不对,这部分的内容偏题了。 在这个知识平台里, 它包含了这么一些内容: - **DevOps 工具元素周期表**。帮助您进行数字化时代的 DevOps 工具选型。 - **DevOps 设计工具**。帮助您设计组织内的 DevOps 流程,涵盖了流程、人、工具、制品等等。 - **案例学习**。从社区的知识库中,我们总结了传统企业走向 DevOps 的经验,并浓缩到易于使用的内容和材料中。 - **最佳实践**。我们从海量的 DevOps 内容中,提炼出了一系列的最佳实践,以更好地帮助企业进行 DevOps 实践。 - **模式与原则**。基于我们的实践,我们提炼了位于它背后的模式与原则,帮助个人和组织更好地了解 DevOps 文化。 - **操作手册**。只凭实践与原则,无法让中小型 IT 团队进行 DevOps 转型,所以我们准备了详实的操作手册,以帮助您一步步前进。 - **度量**。KPI - 度量、度量 - KPI、KPI - 度量,帮助您更好地度量 DevOps 转型情况。 - **报告**。我们尝试从丰富地 DevOps 报告中,提炼出有用的实践和工具。 - **Mobile DevOps**。我们相信移动应用的 DevOps 改进,才是大多数公司的挑战。 - **工具**。工具,工具,工具是最好的生产力,工具比人的记忆力更加可靠。 起先,我是想做一些 DevOps 工具,比如说适合于中国国情的『DevOps 元素周期表』。顺带一说,这个工具不是我首创的,我只是用更好的架构实现了一遍。。如此一来,对于大部分开发人员来说,它们就可以从这个表中,组合出适合于自己组织的分子(毕竟周期表上都是原子)。几天之后,我就有了这个工具,根据整个研发体系的每一个过程,你可以从中挑选出适合你的要素。 为了凑满上面的元素,我不得不找一个又一个大公司的案例,看看他们到底是用什么技术栈。所以,我七拼八凑得差不多了,顺便一想,既然我有这么多大公司的案例,为什么不抽象一下这些案例呢。 于是,我们从互联网的各个地方(来源见内容中标明的出处),帮你抽取了各大公司的案例: - 腾讯 - 小米 - 招商银行 - 美团 - …… 在这些案例,背后往往包含、隐藏了各种各样的价值取向。所以,进一步地,我想去提取这些模式,所以就包含了: - 流畅度模式 - 度量体系设计 - 学习型组织构建 - …… 画完这些大包之后,随后,我们就可以进入 DevOps 的设计和实施阶段。我们要找到那些最好的实践:编程、团队、文化、能力、测试等等。 太多,不写。 当然了,为了在组织中实施 DevOps,我们还需要一本操作手册,来帮助你一步步构建 DevOps 体系。 从度量,到实践,到工程化,再到流程打通,顺势而来,一步下实践。 光有手册是不行的,我们还把各种各样的工具做了上去,除了工具的名称,还包含: - 工具的准备事项 - 工具的操作步骤 - 工具的示例 - 该工具的在线工具使用 还有更多的功能在开发中。 也欢迎加入我们的开发队伍,更多的案例将帮助每个人更好地成长。 GitHub:https://github.com/phodal/ledge/ 在线使用:https://devops.phodal.com/

软件开发是一项复杂的集体活动,它涉及到一系列的行为和艺术,如项目管理、流程管控、知识转化、程序员心理学(狗头)等。从个体出发时,这些都是一些无关紧要的因素。作为一个 “螺丝钉”,我们所关心的是:如何去解决问题?当然了,我们把组织视为一个个体时,我们也只关心:如何去解决问题?

PS:本文所针对的场景,都是复杂业务场景下的 Web 应用。简单的 Web 应用不适合复杂的架构模式,它为带来巨大的成本。 在接触了领域驱动设计的概念,其中关于核心域的想法让人颇为激动。而在微服务架构中,核心域是一个或者多个服务的域,而位于核心域的核心则是领域模型。简单地来说,对于一个系统来说,它的核心的核心的 ”核心“ 就是:领域模型。 过去,当我们谈及领域模型的时候,我们往往将其置于 Web 后端的领域。当我 GET 了一些基本的理念之后,便尝试整合到前端架构中。去年,我开始了 在项目上的第一次尝试,并在 GitHub 上创建了 [clean-frontend](https://github.com/phodal/clean-frontend) 项目,它以 Angular 作为示例,介绍了如何开发一个整洁前端架构的前端应用。 ## 引子 1:模型一致化 我习惯于在公司的项目中引入成熟的框架,从几年前的 Angular.js 到近几年的 Angular,完整的框架能为企业带来更少的维护成本,从而降低软件开发成本。几年前,我们引入了 Angular 之后,便开始大量地 TypeScript 项目实践。作为一个支持静态类型的语言,它非常适合于开发大型应用、企业应用,在这一点上你可以从 React、Vue 等框架建议使用 TypeScript 看到一种趋势。 采纳了 TypeScript 之后,当前端从后端获取到数据之后,那么它需要将 JSON 数据映射到对应的 interface。如此一来,前端的模型也就有了对应的领域模型。而这个数据模型和后端的数据模型应该是完成一致的,但是实际上,它往往是落后的。后端的 API 发生的变更之后,才需要前端同步去修改模型。 而当我作为一个前端的 Tech Lead 来考虑这个问题的时候,我首先想到的是:读取后端的 Java 代码,然后生成对应的前端模型。在没有造 Chapi 的轮子之前,通过 TypeScript Compiler 转换获得对应的类型,是我觉得比较靠谱的方案。在有了 Chapi 之后,我觉得它们都是小问题了。 不过,然后我一想这样做的意义并不大,还不如:套用后端的契约测试,造一个前端的自动化契约测试,即:[mest](http://github.com/phodal/mest)。它通过 API 返回数据和 TypeScript 的 Interface 来完成对于契约的测试。一个简单的测试数据如下: ```csv url,interface https://phodal.github.io/mest-test/error.json,mock/IError.ts ``` 通过 HTTP 请求获取对应的测试数据,再将其与本地的模型进行比较。 不过呢,我们还要使用 JavaScript 语言重写部分 Java 代码,那么我们为什么不要 JavaScript / TypeScript 重写一切呢? ## 引子 2:JavaScript / TypeScript 重写一切 遗憾的是,使用 JavaScript 编写后端应用(BFF、胶水层除外),在大部分的大公司是比较难的。内部的生态链和运维影响了技术决策,使用 C++ 不香吗,使用 JavaScript 会存在无人在背后支持。 ### 纯 Node.js 后端应用 在过去的几年里,我建议:小公司的后端应用**不要**使用 JavaScript / TypeScript 编写,因为运维、监控、APM 等生态尚不够完善。小公司应该优先投资于业务领域,在基础设施的投入见效比较短,除非能控制好开发人员的流动性。 不过,不管怎样,已经有大量的小公司因为人力成本的原因,已经使用上了 Node.js 来开发后端应用。 ### Serverless 应用 而随着 JavaScript 系生态的完善,基础设施已经不是会成为小公司的负担,我便觉得这是一个好的时机。不过,我指的是采用 Serverlesss 架构,而非自建 JavaScript 系生态。Serverless 不仅帮助小企业解决了基础设施的问题,还能为小企业降低软件运维成本。一旦企业做大之后,也可以自建采用 OpenFaas 等开源方案解决 Serverless 的供应商锁定问题。 不论是 Node.js 后端应用还是 Serverless 应用,因为使用的是同一种语言,我们可以轻松地在前后端之间共享代码,可以是 Git submodule、NPM 包、远程等等的方式。 ## 引子 3:纯编译到 JavaScript 市面上有各种各样支持 compile to js 的语言、框架,诸如于 Kotlin.js、Scala.js、Python.js 等等。 对于小前端的应用来说,这种架构非常的不错。它相当于是渐进式的系统架构方案,当前采用了主流的前端框架,而非传统的后端渲染机制,并统一了技术栈,降低了组织内部的学习成本。不过,它带来额外的调试因素,毕竟每多一层封装,系统的复杂度就需要 * 2。 而为了让框架的使用者支持不同的框架 React、Angular、Vue,这个框架还需要提供这些框架的 bind 或者是 wrapper,以提升框架使用者的幸福感。 但是,我们的挑战依然是复杂的前端应用,以及它难以消除交互的复杂度。 ## 引子 4:共享领域模型/模式库 开始之前,我不得不强调一,领域模型是一种包含数据和行为的抽血模型。 编译到 JavaScript 是一种轻前端的方案,而对于重前端的项目来说,它们完成可以采用一种新的模式:共享领域模型。即将领域模型作为模式库,提供给前后端一起使用。即,我们只需要编译所需要的部分。 这在我使用了 Kotlin 的多平台技术(multiplatform)重写了 Chapi 的 domain 层之后,我意识到了这是一个非常迷人的方案。我即使用在前端代码中使用 Chapi 的领域模型,我还需要在后端的代码中使用这套模型。原先,我需要手动翻译一行行代码,现在我并不需要这样的一个步骤。只需要在 pom.xml、build.gradle 或者是 package.json 中引入依赖及其对应的版本即可。 在引入了这部分的代码之后,我们再关注于 UI 交互部分即可。 ## 引子 5:领域模型编译到 WASM 考虑到并非所有的语言都能支持 compile to JavaScript,一种颇为有效的方式就是使用 WASM。 > WebAssembly 或称 wasm 是一个实验性的低端编程语言,应用于浏览器内的客户端。WebAssembly 将让开发者能运用自己熟悉的编程语言编译,再藉虚拟机引擎在浏览器内运行。 —— [维基百科](https://zh.wikipedia.org/zh-cn/WebAssembly) 我尝试将使用 Go 编写的 Coca 编译成 WASM,但是遇到一系列的问题(我已经忘了),体积似乎是个问题,所以我尝试使用 Rust 去构建另外一种可能性。Rust 官方提供了 Rust Webpack Template,因此我可以将其集成到我现有的前端应用中。只是呢,似乎还没有人会使用 Rust 去编写后端应用。 但是 WASM 提供了一种更友好的方式,即我们不需要重写现有的代码,而是只需要添加一些代码,便可以将现有的后端模型代码提到给前端使用。并且,与混淆后的 JavaScript 相比,它看上去更加安全 —— 学习成本更高一些。 ## 引子 6:ComponentLess 在研究 Serverless 和微前端的期间,我突然有了一点想法,我对于客户端领域的 Serverless 式架构有了一些基本的构想,叫:ComponentLess。尽管有了一些基础的理念,但是还缺乏一个真实可用的 Demo,所以我并没有定义出什么是 ComponentLess。 起先,我以为无代码编程是一个 ComponentLess 方向,但是一研究发现并不是。无代码编程倾向于可视化编程,而 ComponentLess 倾向于使用 DSL 编程。就这一点来说,我便偏向于使用 Web Components + WAM 技术来构建新的前端架构。 ### ComponentLess 从单体应用转向微服务架构的一大特质是,组件(非单指 UI 组件,可以视为服务)由函数调用转向了 HTTP 调用。而 Serverless 进一步地将微服务的服务级别 HTTP 调用,细化为函数级别的 HTTP 调用。 对于前端领域来说,也是如此。微前端将单体应用拆分一个个的独立运行前端应用,我们可以随意地组合这些应用。进一步地,结合诸如于 Web Component 这样的组件级方案,便可以将拆分细分为到 UI 组件的粒度。我们可以使用 (HTML Imports 已遗弃)script 标签从远程导入: ``` ``` 因此,在未来,不论是前端开发人员,还是开发人员,都可以通过集成组件的方式来开发应用。也就是说,我们只需要关注于编写核心业务代码即可,剩下的部分可以通过一些特殊的方式来实现。 ### DSL 抽象化代码 ComponentLess + Serverless 是**代码即基础设施**开始的一个标志。当代码开始作为基础设施的一部分时,代码便需要以某种方式才能组合到一起。在 Serverless Framework 中,开发人员通过配置接入服务端所使用的基础信息。而 YAML 配置本身也是 DSL 的一种,缺乏灵活度,但是使用非常简单。 事实上,这是两个选择: - 配置 + 编程语言。 上手容易,迁移难 - DSL。 上手复杂,易于迁移 但是,无论如何我还是如何 DSL,它听上去有着更丰富的 KPI。至于如何抽象化基础设计代码,可以参考《[云研发:研发即代码](https://github.com/phodal/cloud-dev)》一文。 ### 模型复用 不论是 ComponentLess 还是 Serverless,它们都是由函数、UI 组件变为一个独立可运行的单元。为了与别人交互,它需要包含输入和输出。而输入和输出本身是需要一个数据模型作为支撑的,以此才能完成整个系统的稳定性。 而这个话题已经回到我们开头所讨论的内容里。 ## 前后端分离将死? 在所有的引子里, 我们已经准备了所有的论据,所以只需要: - 使用可以跨前后端的语言,构建领域模型 - 将后端服务、前端设施细化为更小的组件 - 设计 DSL 将领域模型转换到特定平台的代码 你就可以杀死前后端分离,就是这么简单。 前后端分离将死,不是现在,但是可能在五年后开始。 你说呢? ## 其它 架构,没有终点。

去年年底,在公司大佬的带领下,我们结合架构守护的需要,对代码进行了简单的建模。在过去的几个月里,我一直工作在相关的事项上,不断地优化、改进相关的模型:

说来惭愧,我并没有理由来再写这样一篇文章,因为答案大家都知道,只是呢,大家仍然都好奇 —— 或许大家是想找个合适的借口,以自我安慰;而不是成为努力的一个方向。

跨平台不是一个新的话题,它已经被讨论了几十年了。在最近的一些尝试,让我对跨平台有了一些新的想法。在想法真正落地之前,我梳理了一下不同跨平台方案的一些特征,便有了它的几种模式。

Feeds

RSS / Atom

最近文章

存档

2026 (2 个月)
2025 (12 个月)
2024 (12 个月)
2023 (12 个月)
2022 (12 个月)
2021 (12 个月)
2020 (12 个月)
2019 (12 个月)
2018 (12 个月)
2017 (12 个月)
2016 (12 个月)
2015 (12 个月)
2014 (12 个月)
2013 (9 个月)
2012 (3 个月)
2011 (1 月)
2010 (1 月)
1991 (1 月)

分类

标签

作者