iis服务器助手广告广告
返回顶部
首页 > 资讯 > 数据库 >NoSQL数据建模的示例分析
  • 667
分享到

NoSQL数据建模的示例分析

2024-04-02 19:04:59 667人浏览 薄情痞子
摘要

这篇文章将为大家详细讲解有关NoSQL数据建模的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。NoSQL:一种新的思维方式?当开发人员谈论非关系或 Nosql 数

这篇文章将为大家详细讲解有关NoSQL数据建模的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。

NoSQL:一种新的思维方式?

开发人员谈论非关系或 Nosql 数据库时,经常提到的第一件事是他们需要改变思维方式。我认为,那实际上取决于您的初始数据建模方法。如果您习惯通过首先建模数据库结构(即首先确定表及其关联关系)来设计应用程序,那么使用一个无模式数据存储(比如 Bigtable)来进行数据建模则需要您重新思考您的做事方式。但是,如果您从域模型开始设计您的应用程序,那么 Bigtable 的无模式结构将看起来更自然。

非关系数据存储没有联接表或主键,甚至没有外键这个概念(尽管这两种类型的键以一种更松散的形式出现)。因此,如果您尝试将关系建模作为一个 NoSQL 数据库中的数据建模的基础,那么您可能最后以失败告终。从域模型开始将使事情变得简单;实际上,我已经发现,域模型下的无模式结构的灵活性正在重新焕发生机。

从关系数据模型迁移到无模式数据模型的相对复杂程度取决于您的方法:即您从基于关系的设计开始还是从基于域的设计开始。当您迁移到 CouchDB 或 Bigtable 这样的数据库时,您 的确会丧失 Hibernate(至少现在)这样的成熟的持久存储平台的顺畅感觉。另一方面,您却拥有能够亲自构建它的 “绿地效果”。在此过程中,您将深入了解无模式数据存储。

实体和关系

无模式数据存储赋予您首先使用对象来设计域模型的灵活性(Grails 这样的较新的框架自动支持这种灵活性)。您的下一步工作是将您的域映射到底层数据存储,这在使用 Google App Engine 时再简单不过了。

在文章 “Java 开发 2.0:针对 Google App Engine 的 Gaelyk” 中,我介绍了 Gaelyk —— 一个基于 Groovy 的框架,该框架有利于使用 Google 的底层数据存储。那篇文章的主要部分关注如何利用 Google 的 Entity对象。下面的示例(来自那篇文章)将展示对象实体如何在 Gaelyk 中工作。

清单1. 使用 Entity 的对象持久存储

def ticket = new Entity("ticket")   ticket.officer = params.officer   ticket.license = params.plate   ticket.issuseDate = offensedate   ticket.location = params.location   ticket.notes = params.notes   ticket.offense = params.offense

这种对象持久存储方法很有效,但容易看出,如果您频繁使用票据实体 —例如,如果您正在各种 servlet 中创建(或查找)它们,那么这种方法将变得令人厌烦。使用一个公共 servlet(或 Groovlet)来为您处理这些任务将消除其中一些负担。一种更自然的选择——我将稍后展示——将是建模一个 Ticket对象。

返回比赛

我不会重复 Gaelyk 简介中的那个票据示例,相反,为保持新鲜感,我将在本文中使用一个赛跑主题,并构建一个应用程序来展示即将讨论的技术。

如图 1 中的 “多对多” 图表所示,一个 Race拥有多个 Runner,一个 Runner可以属于多个 Race。

图1. 比赛和参赛者

NoSQL数据建模的示例分析

如果我要使用一个关系表结构来设计这个关系,至少需要 3 个表:第 3 表将是链接一个 “多对多” 关系的联接表。所幸我不必局限于关系数据模型。相反,我将使用 Gaelyk(和 Groovy 代码)将这个 “多对多” 关系映射到 Google 针对 Google App Engine 的 Bigtable 抽象。事实上,Gaelyk 允许将 Entity当作 Map,这使得映射过程相当简单。

无模式数据存储的好处之一是无须事先知道所有事情,也就是说,与使用关系数据库架构相比,可以更轻松地适应变化。(注意,我并非暗示不能更改架构;我只是说,可以更轻松地适应变化。)我不打算定义我的域对象上的属性 —我将其推迟到 Groovy 的动态特性(实际上,这个特性允许创建针对 Google 的 Entity对象的域对象代理)。相反,我将把我的时间花费在确定如何查找对象并处理关系上。这是 NoSQL 和各种利用无模式数据存储的框架还没有内置的功能。

Model 基类

我将首先创建一个基类,用于容纳 Entity对象的一个实例。然后,我将允许一些子类拥有一些动态属性,这些动态属性将通过 Groovy 的方便的 setProperty方法添加到对应的 Entity实例。setProperty针对对象中实际上不存在的任何属性设置程序调用。(如果这听起来耸人听闻,不用担心,您看到它的实际运行后就会明白。)

清单2展示了位于我的示例应用程序的一个 Model实例的第一个 stab:

清单2. 一个简单的 Model 基类

package com.b50.nosql     import com.google.appengine.api.datastore.DatastoreServiceFactory    import com.google.appengine.api.datastore.Entity     abstract class Model {     def entity    static def datastore = DatastoreServiceFactory.datastoreService     public Model(){     super()    }     public Model(params){     this.@entity = new Entity(this.getClass().simpleName)     params.each{ key, val ->      this.setProperty key, val     }    }     def getProperty(String name) {     if(name.equals("id")){      return entity.key.id     }else{      return entity."${name}"   }    }     void setProperty(String name, value) {     entity."${name}" = value    }     def save(){     this.entity.save()    }     }

注意抽象类如何定义一个构造函数,该函数接收属性的一个 Map ——我总是可以稍后添加更多构造函数,稍后我就会这么做。这个设置对于 WEB 框架十分方便,这些框架通常采用从表单提交的参数。Gaelyk 和 Grails 将这样的参数巧妙地封装到一个称为 params的对象中。这个构造函数迭代这个 Map并针对每个 “键 / 值” 对调用 setProperty方法。

检查一下 setProperty方法就会发现 “键” 设置为底层 entity的属性名称,而对应的 “值” 是该 entity的值。

Groovy 技巧

如前所述,Groovy 的动态特性允许我通过 get和 set Property方法捕获对不存在的属性的方法调用。这样,清单 2 中的 Model的子类不必定义它们自己的属性 —它们只是将对一个属性的所有调用委托给这个底层 entity对象。

清单 2 中的代码执行了一些特定于 Groovy 的操作,值得一提。首先,可以通过在一个属性前面附加一个 @来绕过该属性的访问器方法。我必须对构造函数中的 entity对象引用执行上述操作,否则我将调用 setProperty方法。很明显,在这个关头调用 setProperty将打破这种模式,因为 setProperty方法中的 entity变量将是 null。

其次,构造函数中的调用 this.getClass().simpleName将设置 entity的 “种类” —— simpleName属性将生成一个不带包前缀的子类名称(注意,simpleName的确是对 getSimpleName的调用,但 Groovy 允许我不通过对应的 JavaBeans 式的方法调用来尝试访问一个属性)。

最后,如果对 id属性(即,对象的键)进行一个调用,getProperty方法很智能,能够询问底层 key以获取它的 id。在 Google App Engine 中,entities的 key属性将自动生成。

Race 子类

定义 Race子类很简单,如清单 3 所示:

清单3. 一个 Race 子类

package com.b50.nosql    class Race extends Model {   public Race(params){    super(params)   }   }

当一个子类使用一列参数(即一个包含多个 “键 / 值” 对的 Map)实例化时,一个对应的 entity将在内存中创建。要持久存储它,只需调用 save方法。

清单4. 创建一个 Race 实例并将其保存到 GAE 的数据存储

import com.b50.nosql.Runner     def iparams = [:]                                    def fORMatter = new SimpleDateFormat("MM/dd/yyyy")    def rdate = formatter.parse("04/17/2010")                    iparams["name"] = "Charlottesville Marathon"  iparams["date"] = rdate    iparams["distance"] = 26.2 as double     def race = new Race(iparams)    race.save()

清单4 是一个 Groovlet,其中,一个 Map(称为 iparams)创建为带有 3 个属性 ——一次比赛的名称、日期和距离。(注意,在 Groovy 中,一个空白 Map通过 [:]创建。)Race的一个新实例被创建,然后通过 save方法存储到底层数据存储。

可以通过 Google App Engine 控制台来查看底层数据存储,确保我的数据的确在那里,如图 2 所示:

图2. 查看新创建的Race

NoSQL数据建模的示例分析

查找程序方法生成持久存储的实体

现在我已经存储了一个 Entity,拥有查找它的能力将有所帮助。接下来,我可以添加一个 “查找程序” 方法。在本例中,我将把这个 “查找程序” 方法创建为一个类方法(static)并且允许通过名称查找这些 Race(即基于 name属性搜索)。稍后,总是可以通过其他属性添加其他查找程序。

我还打算对我的查找程序采用一个惯例,即指定:任何名称中不带单词 all的查找程序都企图找到 一个实例。名称中包含单词 all的查找程序(如 findAllByName)能够返回一个实例 Collection或 List。清单 5 展示了 findByName查找程序:

清单5. 一个基于 Entity 名称搜索的简单查找程序

static def findByName(name){    def query = new Query(Race.class.simpleName)    query.addFilter("name", Query.FilterOperator.EQUAL, name)    def preparedQuery = this.datastore.prepare(query)    if(preparedQuery.countEntities() > 1){     return new Race(preparedQuery.asList(withLimit(1))[0])    }else{     return new Race(preparedQuery.asSingleEntity())    }    }

这个简单的查找程序使用 Google App Engine 的 Query和 PreparedQuery类型来查找一个类型为 “Race” 的实体,其名称(完全)等同于传入的名称。如果有超过一个 Race符合这个标准,查找程序将返回一个列表的第一项,这是分页限制 1(withLimit(1))所指定的。

对应的 findAllByName与上述方法类似,但添加了一个参数,指定 您想要的实体个数,如清单 6 所示:

清单 6. 通过名称找到全部实体

static def findAllByName(name, pagination=10){   def query = new Query(Race.class.getSimpleName())   query.addFilter("name", Query.FilterOperator.EQUAL, name)   def preparedQuery = this.datastore.prepare(query)   def entities = preparedQuery.asList(withLimit(pagination as int))   return entities.collect { new Race(it as Entity) }   }

与前面定义的查找程序类似,findAllByName通过名称找到 Race实例,但是它返回 所有 Race。顺便说一下,Groovy 的 collect方法非常灵活:它允许删除创建 Race实例的对应的循环。注意,Groovy 还支持方法参数的默认值;这样,如果我没有传入第 2 个值,pagination将拥有值 10。

清单7. 查找程序的实际运行

def nrace = Race.findByName("Charlottesville Marathon")    assert nrace.distance == 26.2     def races = Race.findAllByName("Charlottesville Marathon")    assert races.class == ArrayList.class

清单 7中的查找程序按照既定的方式运行:findByName返回一个实例,而 findAllByName返回一个 Collection(假定有多个 “Charlottesville Marathon”)。

“参赛者” 对象没有太多不同

现在我已能够创建并找到 Race的实例,现在可以创建一个快速的 Runner对象了。这个过程与创建初始的 Race实例一样简单,只需如清单 8 所示扩展 Model:

清单 8. 创建一个参赛者很简单

package com.b50.nosql    class Runner extends Model{   public Runner(params){    super(params)   }   }

看看 清单 8,我感觉自己几乎完成工作了。但是,我还需创建参赛者和比赛之间的链接。当然,我将把它建模为一个 “多对多” 关系,因为我希望我的参赛者可以参加多项比赛。

没有架构的域建模

Google App Engine 在 Bigtable 上面的抽象不是一个面向对象的抽象;即,我不能原样存储关系,但可以共享键。因此,为建模多个 Race和多个 Runner之间的关系,我将在每个 Race实例中存储一列 Runner键,并在每个 Runner实例中存储一列 Race键。

我必须对我的键共享机制添加一点逻辑,但是,因为我希望生成的 API 比较自然 —我不想询问一个 Race以获取一列 Runner键,因此我想要一列 Runner。幸运的是,这并不难实现。

在清单 9 中,我已经添加了两个方法到 Race实例。但一个 Runner实例被传递到 addRunner方法时,它的对应 id被添加到底层 entity的 runners属性中驻留的 id的 Collection。如果有一个现成的 runners的 collection,则新的 Runner实例键将添加到它;否则,将创建一个新的 Collection,且这个 Runner的键(实体上的 id属性)将添加到它。

清单9. 添加并检索参赛者

def addRunner(runner){    if(this.@entity.runners){     this.@entity.runners << runner.id    }else{     this.@entity.runners = [runner.id]    }    }     def getRunners(){    return this.@entity.runners.collect {     new Runner( this.getEntity(Runner.class.simpleName, it) )    }    }

当清单 9 中的 getRunners方法调用时,一个 Runner实例集合将从底层的 id集合创建。这样,一个新方法(getEntity)将在 Model类中创建,如清单 10 所示:

清单10. 从一个id 创建一个实体

def getEntity(entityType, id){   def key = KeyFactory.createKey(entityType, id)            return this.@datastore.get(key)   }

getEntity方法使用 Google 的 KeyFactory类来创建底层键,它可以用于查找数据存储中的一个单独实体。

最后,定义一个新的构造函数来接受一个实体类型,如清单 11 所示:

清单11. 一个新添加的构造函数

public Model(Entity entity){    this.@entity = entity    }

如清单 9、10和 11、以及 图 1的对象模型所示,我可以将一个 Runner添加到任一 Race,也可以从任一Race获取一列 Runner实例。在清单 12 中,我在这个等式的 Runner方上创建了一个类似的联系。清单 12 展示了 Runner类的新方法。

清单12. 参赛者及其比赛

def addRace(race){    if(this.@entity.races){     this.@entity.races << race.id    }else{     this.@entity.races = [race.id]    }    }     def getRaces(){    return this.@entity.races.collect {     new Race( this.getEntity(Race.class.simpleName, it) )    }    }

这样,我就使用一个无模式数据存储创建了两个域对象。

通过一些参赛者完成这个比赛

此前我所做的是创建一个 Runner实例并将其添加到一个 Race。如果我希望这个关系是双向的,如图1中我的对象模型所示,那么我也可以添加一些 Race实例到一些Runner,如清单 13 所示:

清单 13. 参加多个比赛的多个参赛者

def runner = new Runner([fname:"Chris", lname:"Smith", date:34])    runner.save()     race.addRunner(runner)    race.save()     runner.addRace(race)    runner.save()

将一个新的 Runner添加到 race并添加对Race的save的调用后,这个数据存储已使用一列ID 更新,如图 3 中的屏幕快照所示:

图3. 查看一项比赛中的多个参赛者的新属性

NoSQL数据建模的示例分析

通过仔细检查Google App Engine 中的数据,可以看到,一个Race实体现在拥有了一个Runners 的list,如图 4 所示。

图4. 查看新的参赛者列表

NoSQL数据建模的示例分析

同样,在将一个 Race添加到一个新创建的 Runner实例之前,这个属性并不存在,如图 5 所示。

5. 一个没有比赛的参赛者

NoSQL数据建模的示例分析

但是,将一个 Race关联到一个 Runner后,数据存储将添加新的 races ids 的 list。

图6. 一个参加比赛的参赛者

NoSQL数据建模的示例分析

无模式数据存储的灵活性正在刷新 —属性按照需要自动添加到底层存储。作为开发人员,我无须更新或更改架构,更谈不上部署架构了!

NoSQL 的利弊

当然,无模式数据建模也有利有弊。回顾上面的比赛应用程序,它的一个优势是非常灵活。如果我决定将一个新属性(比如 SSN)添加到一个 Runner,我不必进行大幅更改 —事实上,如果我将该属性包含在构造函数的参数中,那么它就会自动添加。对那些没有使用一个 SSN 创建的旧实例而言,发生了什么事情?什么也没发生!它们拥有一个值为 null的字段。

另一方面,我已经明确表明要牺牲一致性和完整性来换取效率。这个应用程序的当前数据架构没有向我施加任何限制 —理论上我可以为同一个对象创建无限个实例。在 Google App Engine 引擎的键处理机制下,它们都有惟一的键,但其他属性都是一致的。更糟糕的是,级联删除不存在,因此如果我使用相同的技术来建模一个 “一对多” 关系并删除父节点,那么我得到一些无效的子节点。当然,我可以实现自己的完整性检查 —但关键是,我必须亲自动手(就像完成其他任务一样)。

使用无模式数据存储需要严明的纪律。如果我创建各种类型的 Races —有些有名称,有些没有,有些有 date属性,而另一些有 race_date属性 —那么我只是在搬起石头砸自己(或使用我的代码的人)的脚。

当然,也有可能联合使用 JDO、JPA 和 Google App Engine。在多个项目上使用过关系模型和无模式模型后,我可以说 Gaelyk 的低级 API 最灵活,使用最方便。使用 Gaelyk 的另一个好处是能够深入了解 Bigtable 和一般的无模式数据存储。

关于“NoSQL数据建模的示例分析”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。

您可能感兴趣的文档:

--结束END--

本文标题: NoSQL数据建模的示例分析

本文链接: https://www.lsjlt.com/news/71625.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • NoSQL数据建模的示例分析
    这篇文章将为大家详细讲解有关NoSQL数据建模的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。NoSQL:一种新的思维方式?当开发人员谈论非关系或 NoSQL 数...
    99+
    2024-04-02
  • 关系数据库和nosql的示例分析
    小编给大家分享一下关系数据库和nosql的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!NoSQL概念随着web2.0的...
    99+
    2024-04-02
  • sql与各个nosql数据库使用场景的示例分析
    这篇文章主要介绍了sql与各个nosql数据库使用场景的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。sql为主干为什么我这样理解:...
    99+
    2024-04-02
  • VS建模调用的示例分析
    这篇文章主要为大家展示了“VS建模调用的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“VS建模调用的示例分析”这篇文章吧。VS建模是主线程消息队列相关的,发送给主窗体的消息才能发送到界面...
    99+
    2023-06-17
  • Python数据结构创建的示例分析
    本篇文章为大家展示了Python数据结构创建的示例分析,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。 列表list:变量赋值方式:shoplist = ['apple', '...
    99+
    2023-06-17
  • Java建造者模式的示例分析
    这篇文章主要介绍“Java建造者模式的示例分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Java建造者模式的示例分析”文章能帮助大家解决问题。定义建造者模式(Builder Pattern),又...
    99+
    2023-06-29
  • JSON数据的示例分析
    这篇文章将为大家详细讲解有关JSON数据的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。 JSON 数据详解一、json值的类型1.简单值: 可以表示字...
    99+
    2024-04-02
  • SpringBoot中web模版数据渲染展示的示例分析
    这期内容当中小编将会给大家带来有关SpringBoot中web模版数据渲染展示的示例分析,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。模板引擎SpringBoot是通过模版引擎进行页面结果渲染的,官方提供...
    99+
    2023-06-29
  • vue模拟后台数据调试的示例分析
    这篇文章给大家分享的是有关vue模拟后台数据调试的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。首先创建一个本地json文件,放在项目中如下{  "r...
    99+
    2024-04-02
  • Vue模拟实现数据驱动的示例分析
    这篇文章主要为大家展示了“Vue模拟实现数据驱动的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Vue模拟实现数据驱动的示例分析”这篇文章吧。一、前言之...
    99+
    2024-04-02
  • redis学习之NoSQL数据四大分类的对比示例
    这篇文章主要介绍了redis学习之NoSQL数据四大分类的对比示例,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。KV键值对新浪:Berkel...
    99+
    2024-04-02
  • MySQL数据库的示例分析
    这篇文章给大家分享的是有关MySQL数据库的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。一、数据库概要数据库(Database)是存储与管理数据的软件系统,就像一个存入...
    99+
    2024-04-02
  • VB.NET数据集的示例分析
    这篇文章将为大家详细讲解有关VB.NET数据集的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。1.基本概念VB.NET数据集是一种离线了的缓存存储数据,它的结构和数据库一样,具有表格、行、列的一种...
    99+
    2023-06-17
  • Apache Avro数据的示例分析
    这篇文章主要介绍Apache Avro数据的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!随着互联网高速的发展,云计算、大数据、人工智能AI、物联网等前沿技术已然成为当今时代主流的高新技术,诸如电商网站、人脸...
    99+
    2023-06-29
  • LINQ数据源的示例分析
    这篇文章给大家分享的是有关LINQ数据源的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。LINQ数据源在上一个示例中,由于数据源是数组,因此它隐式支持泛型 IEnumerable<(Of <(...
    99+
    2023-06-17
  • python数学建模实例分析
    SciPy 学习''' SciPy 包含的模块有最优化、线性代数、积分、插值、特殊函数、快速傅里叶变换、 信号处理和图像处理、常微分方程求解和其他科学与工程中常用的计算。 ''' # 安装sc...
    99+
    2023-05-14
    Python
  • NoSQL数据建模技术有什么用
    小编给大家分享一下NoSQL数据建模技术有什么用,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!NoSQL数据建模技术NoSQL数...
    99+
    2024-04-02
  • Entity Framework 4.0自关联建模的示例分析
    这篇文章主要介绍了Entity Framework 4.0自关联建模的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。针对这个表建立自关联,详细截图如下Children...
    99+
    2023-06-17
  • 数据库中数据模型的实例分析
    数据库中数据模型的实例分析,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。数据模型可以说软件开发中最重要的部分,因为影响着我们的思考方式、解题思...
    99+
    2024-04-02
  • python模块的示例分析
    小编给大家分享一下python模块的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!Python的优点有哪些1、简单易用,与C/C++、Java、C# 等传...
    99+
    2023-06-14
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作