阿里云「飞天发布时刻」2024来啦!新产品、新特性、新能力、新方案,等你来探~ 了解详情
写点什么

使用 Apache Shindig 为客户应用程序加入社区内容

  • 2010-02-26
  • 本文字数:8859 字

    阅读完需:约 29 分钟

社区网络是目前网络上最热门的事情之一,所以毫不奇怪,许多 Web 应用程序试图加上社区网络内容来吸引更多的访问者。尽管 API 正在标准化 [1] ,但大多数现有的社区网站依然提供专有 API,或者对 OpenSocial 的不同版本提供支持, 而这正是许多开发人员所面临的挑战。结果是目前 OpenSocial 有多版本的的 API 和大量的开源代码支持他们 [2] 。在这篇文章中我将讨论 Apache Shindig [3] 如何实现 OpenSocial,如何减少其中的一些问题。

我首先讨论 OpenSocial 的标准和 Shingig 架构,然后说明它们如何为一个应用程序添加社区网络内容。最后,将以 OpenSocial Gypsii 社区网络 [4] 为例,讲解启用 Shindig 的方法。

OpenSocial APIs

OpenSocial 所定义的规范 [5]

“OpenSocial 是一组用于构建在网络上运行的社交应用程序的 API。OpenSocial 的目标是通过提供一个可在很多不同的环境中使用的通用 API ,让更多的应用程序被更多的用户访问。开发人员可以使用标准的 javascript 和 html 创建应用程序,运行在那些已经应用 OpenSocial 的社交网站中。这些网站作为 OpenSocial 的容器,允许开发者访问他们的社区信息;反过来,又为他们的用户收到了大量的应用程序。

OpenSocail API 暴露了在一个 [社区网络] 容器中获取用户以及他们的好友信息的方式。”

社区应用程序围绕人、活动和关系展开。人是社区网络软件和 OpenSocial API 的基本组成部分。Person 对象提供对用户信息的访问接口,它包含了用户的基本信息或其他扩展的信息,如“最喜爱的电视节目”、“我所期望的工作”以及“最喜欢的地方”。OpenSocial API 提供了用于查看和更改个人资料的支持。另一个重要的用户信息是他们在社交图中的连接。为了能够共享信息和体验朋友动态变化,OpenSocial 支持用户的朋友概念,并提供检索用户朋友信息的 API。当应用程序想进行交互或显示“朋友的朋友”的数据,OpenSocial 允许增加一个你所做操作的可选过滤器来支持扩展查询,如用户可以通过朋友的距离来排序。容器可以有选择地支持“朋友的朋友”查询,“朋友的朋友的朋友”的查询,等等。 OpenSocial 还允许朋友分享他们最近一直在做的信息 - 这些被称为活动。OpenSocial 的公开活动流是一个用户所采取的行动的集合。活动模板允许应用程序开发人员在消息中为应用程序或用户数据块定义占位符。这种数据层和显示层的分离允许多个活动合并成活动包:活动综合包,让用户知道他们朋友消息,而不必辛苦地搜集大量消息。社区互动的另一个重要方面是能够读取,张贴和删除用户之间在网络中的消息。 OpenSocial 定义的消息类型,包括公共消息(如文件的评论)和私人消息(仅限于某些个人和团体)。消息归纳,其中包括一个唯一的 ID,名称和信息数量大小。

目前的规范定义了两种版本的 API──JavaScript [6] 和 REST [7]

OpenSocial 的 JavaScript 规范是提供一个小工具 Gadget [8] 设计来支持 OpenSocial 的功能。架构如图 1:

图 1 使用 JavaScript OpenSocial API 典型架构

在这种情况下,一个用户应用程序被作为一个 Gadgets 的集合,Gadget 自己用 OpenSoical API 可以获取社交信息。因此, JavaScript API 与 Gadget API 的紧密结合, 并非总是适合不是基于 Gadget 的实现。

相反的,OpenSocail REST API 不直接与 Gadgets 关联,允许任何客戶端应用程序来查看和发布用户的 OpenSocial 数据。我们将用这些 API 来实现 OpenSocial 应用。

Apache Shindig

Apache Shindig 是 Gadget API 和 OpenSocial API 的开源实现。下面将简单回顾一下什么是 Apache Shindig;Apache Shindig 是一个 OpenSocial 的容器,帮助您通过提供代码托管、代理请求和处理 REST 和 RPC 请求来快速启用 OpenSocial。

图 2 Apache Shindig [9]

对于 OpenSocial,Shindig 同时实现 JavaScript 和 REST 的 API。 Shindig OpenSocial 实现 [10] 总体结构如图 3:

图 3 Shindig 实现 Open Social 的整体结构图

从图 3 中我们可以看到 Shindig 实现包括两个主要部分:客户端的 JavaScript 容器和 Shindig 服务器。 服务器是一个 WAR 文件,它可以运行在任何 Servlet 容器中 - 我们使用 Tomcat。正如上面所定义的,因为我们正在直接用 REST OpenSocial API,所以在这里我们不会讨论客户端 JavaScript 容器。

服务器执行包含两个监听器:JSON RPC Servlet 和数据服务(REST)Servlet。 每个 Servlet 被它自己的处理程序支持。 因此有两个处理器:RPC 和 REST 的处理器。这两种处理器共享同一基础类库,包括日期转换器(这里没有显示)和一系列 OpenSocial 的处理器:

  • 人处理器──种支持用户管理操作的处理器,如检索有关用户信息和他们的关系信息。
  • 活动处理器──种支持检索和存储一个用户活动信息的处理器。
  • 数据处理器──种支持存储和检索应用数据的处理器。应用数据是一项信息,如一个偏好设置,由社区网络对于某个给定的应用根据一个用户的行为来存储。
  • 信息处理器──种用于社会网络中发送信息和检索信息的处理器。

Shindig 服务实现基于依赖注入框架 Guice [11] 。它使用的是绑定类(在 web.xml 文件中配置),所定义的服务被每一个处理器调用:即每个处理程序使用相应的服务。这种架构极其易于使用自定义实现覆盖 Shindig 发布服务一部分的实现,如实现与现有社区网络应用程序的集成。

Gypsii

Gypsii [12] 是一个位置感知的社区网络。为实现共同的社区网络, 除了传统的用户归属,它还允许其参与者存储和共享地点与位置信息,包括地址,地理坐标,可选的图片和说明。 GyPSii 中一个特殊的地点是当前用户的位置。它也支持 POI(兴趣点)和广告。

GyPSii 提供了一套 API:GyPSii OenExperience API(OEx) [13] 、XML-RPC API,还提供了访问 GyPSii 所有功能的方法。 GyPSii XML-RPC 的变种是由 XML - RPC 规范 [14] 创造的,但已适应 GyPSii 的需要。该协议定义使用 XML 双方沟通时一个客户端服务器的通信模式。 Gypsii 为开发人员提供了 XML-RPC 协议和应用了该协议的 JAVA 客户端实现的文档。

实现

整体实现架构

实现的整体架构如图 4。实现一个 Gypsii 的适配器,通过自定义 OpenSocial 容器替换 Shindig 提供的默认 OpenSocial 容器的方式,使得现有的 Shindig 支持 OpenSocial REST。 请注意,这个自定义提供者可以连接到任何其他社区网络供应商,如 Facebook,MySpace 或 Ning。

图 4 整体解决方案框架

这种方法的好处是有效地将我们的应用程序与特定的社区网络的实现 / 集成方法屏蔽开来。客户端 API 由 Shindig 提供和内部控制。因此,我们的应用程序不依赖于具体的技术和社区网络供应商公开的 API。这些细节被封装在一个特定的网络适配器中。

安全

与多个社区网络供应商集成的复杂问题之一是安全 / 证书支持,范围涉及从一个登录用户名称 / 密码到 OpenID [15] ,再到 OAuth [16] 。我们的实现是基于一个简单的身份转换,如图 5 所示。

图 5 身份转换

例如,用户 ID 123468@gypsii 意味着 NAVTEQ 用户以 ID123468 访问 Gypsii。

用户请求具有 NAVTEQ’s 用户 ID 的 Shindig:令牌。网络要求一种特定的社会网络适配器将令牌转换成证书。基于用户存储的转换包含转换所需的信息:用户名 / 密码,OpenID,特定的键或其他任何一个特定的转换要求。这允许我们使用 Navteq 公司的用户 ID 作为 OpenSocial REST API 的标准化参数。它还允许多个供应商之间的社区网络的请求,基于以下命名约定简单实现:

OpenSocialUID = NAVTEQUID@Network

例如用户 ID 123468 @ GyPSii 意思是 ID 123468 的 NAVTEQ 用户访问 Gypsii。这允许构建一个非常简单的网络适配器(图 4),社区网络适配器根据所提供的用户名选择线路。

替换默认的社区服务

在依赖注入支持下,替换默认的社区网络服务就相当简单,主要包括以下主要步骤:

  • 实现自定义 OpenSocial 服务。自定义服务要么作为一个单独的类(清单 1)实现,要么为一组类共同实现 Shindig 所定义的 API。
复制代码
<span color="#800040"><strong>public</strong></span> <span color="#800040"><strong>class</strong></span> CustomOpenSocialService <strong><span color="#800040">implements</span></strong> ActivityService,
AppDataService, PersonService, MessageService {
…………………………………..
}

清单 1 客户自定义的 OpenSocial 服务

  • 创建绑定类。一旦自定义的 OpenSocial 服务到位(我们假定一个:清单 1), 一个绑定类必须实现(清单 2)并告诉 Shindig 调用它处理请求。
复制代码
<span color="#800040"><strong>public class</strong></span> NAVTEQBind <strong><span color="#800040">extends</span></strong> SocialApiGuiceModule {
………………
bind(ActivityService.class).to(CustomOpenSocialService.<strong><span color="#800040">class</span></strong>);
bind(AppDataService.class).to(CustomOpenSocialService.<strong><span color="#800040">class</span></strong>);
bind(PersonService.class).to(CustomOpenSocialService.<strong><span color="#800040">class</span></strong>);
bind(MessageService.class).to(CustomOpenSocialService.<strong><span color="#800040">class</span></strong>);
………………
}

清单 2 绑定自定义 OpenSocial 服务

  • 用绑定类配置 Shindig。最后,这个绑定类必须知道 Shindig 的实现。通过一个配置文件 Web.xml 改变来处理(清单 3). 这个配置文件是通过org.apache.shindig.common.servlet.GuiceServletContextListener类调用 Shindig 应用程序和设置正确的绑定。
复制代码
<span color="#008080"><context-param><br></br></context-param></span><span color="#008080"><context-param><br></br> <param-name></span>guice-modules<span color="#008040"></param-name></span>
<span color="#008080"><param-value><br></br></span>
<u>org</u>.<u>apache</u>.<u>shindig</u>.common.PropertiesModule:com.<u>navteq</u>.<u>opensocial</u>.bind.NAVTEQBind
<span color="#008080"> </param-value><br></br> </context-param></span>

清单 3 Web.config 变化

从浏览器中访问 OpenSocial REST API

我们需要从浏览器中访问 OpenSocial REST APIs。这里的问题是现在浏览器都具有同源策略限制 [17]

“同源策略是指阻止代码获得或者更改从另一个域名下获得的文件或者信息。也就是说我们的请求 URL 域必须和当前网站的域相同。这基本上意味着,Web 浏览器将不同域的内容隔离以阻止它们彼此对终端用户的操作。”

这里有几种克服限制的常用方法:

  • 具有 GET/POST 数据的网页,通过服务器端发送请求,服务器充当一个到达第三方服务器代理。虽然使用广泛,但是这个方法却不够灵活,无法伸缩,并增加了处理请求的延迟(负载大时会更明显)。
  • 在页面中使用框架元素创建新区域来访问任何第三方内容。没有返回数据的 POSTs 方式运行的很好,框架间由于有限的访问很少应用 GET 方式。但是试图下载文件时也会受到同源政策限制。
  • 实现 GET 返回 JSONP(JSON with Padding):在一个函数调用中包装的 JSON 数据,数据在一个方法调用中返回。当脚本加载时执行。因为同源策略不会阻止动态插入的代码,不会测试脚本是否与 Web 页面来自相同的域。

由 Shindig 提供的 OpenSocial API 是利用 GET 从社区网络中获取信息和 POST 更新信息。为了支持 GET 方式,我们充分利用 JSONP,而为了支持 POST 方式,我们使用框架技术。

为了实现服务器上 JSONP 支持,我们使用了开源 JSONP 过滤器 [18] ,不要求对 Shindig 的方案变动支持。在 Shindig 的 Web.xml 中定义过滤器用法(清单 4):

复制代码
<span color="#008080"><filter><br></br> <display-name><span color="#000000"><u>jsonp</u></span></display-name><br></br> <filter-name><span color="#000000"><u>jsonp</u></span></filter-name><br></br> <filter-class><span color="#000000"><u>org</u>.<u>jsonp</u>.JsonpFilter</span></filter-class><br></br> <init-param><br></br> <param-name><span color="#000000"><u>jsonp</u></span></param-name><br></br> <param-value><span color="#000000">jsonpCallback</span></param-value><br></br> </init-param><br></br> <init-param><br></br> <param-name><span color="#000000"><u>json</u>-<u>mime</u>-types</span></param-name><br></br> <param-value><span color="#000000">application/<u>json</u></span></param-value><br></br> </init-param><br></br> </filter><br></br> <filter-mapping><br></br> <filter-name><span color="#000000"><u>jsonp</u></span></filter-name><br></br> <url-pattern><span color="#000000">*</span></url-pattern><br></br> </filter-mapping></span>

清单 4 配置 JSONP 过滤器

我们还必须稍微修改 Shindig 代码使之能够支持 Post 方式。Shindig 发布版不允许内容类型为application/x-www-form-urlencoded,因为它与 OAuth body signing 冲突。因为我们没有使用 OAuth 和application/x-www-form-urlencoded的内容类型的 POST 浏览器,我们必须修改类org.apache.shindig.protocol.ContentTypes(清单 5)和org.apache.shindig.protocol.DataServiceServlet(清单 6)允许application/x-www-form-urlencoded

复制代码
………………….
<strong><span color="#800040">public static void</span></strong> checkContentTypes(Set<String> allowedContentTypes,
String contentType, <strong><span color="#800040">boolean</span></strong> disallowUnknownContentTypes) <strong><span color="#800040">throws</span></strong> InvalidContentTypeException {
<strong><span color="#800040">if</span></strong> (StringUtils.isEmpty(contentType)) {
<strong><span color="#800040">if</span></strong> (disallowUnknownContentTypes) {
<span color="#800040"><strong>throw</strong> <strong>new</strong></span> InvalidContentTypeException(
<span color="#0000ff">"No Content-Type specified. One of "<br></br></span>
+ StringUtils.join(allowedContentTypes, ", ") + " is required");
} <strong><span color="#800040">else</span></strong> {
<span color="#008080">// No content type specified, we can fail in other ways later.<br></br></span>
<strong><span color="#800040">return</span></strong>;
}
}
contentType = ContentTypes.extractMimePart(contentType);
<span color="#008080">//BL. comented out to support <br></br> // if (ContentTypes.FORBIDDEN_CONTENT_TYPES.contains(contentType)) {<br></br> // throw new InvalidContentTypeException(<br></br> // "Cannot use disallowed Content-Type " + contentType);<br></br> //</span> }
..........................................<string></string>

清单 5 修改 ContentTypes 类

复制代码
……………
<strong><span color="#800040">public static final</span></strong> Set<String> <span color="#0000ff">ALLOWED_CONTENT_TYPES</span> =
<strong><span color="#800040">new</span></strong> ImmutableSet.Builder<String>().addAll(ContentTypes.<span color="#0000ff">ALLOWED_JSON_CONTENT_TYPES)</span>
.addAll(ContentTypes.<span color="#0000ff">ALLOWED_XML_CONTENT_TYPES</span>)
.addAll(ContentTypes.<span color="#0000ff">FORBIDDEN_CONTENT_TYPES</span>)
.addAll(ContentTypes.<span color="#0000ff">ALLOWED_ATOM_CONTENT_TYPES</span>).build();
……………………<string><string></string></string>

清单 6 修改 DataServiceServlet 类

另一个问题是,浏览器 POST 方式提交名称 / 值。虽然,Shindig 支持 POST 名称 / 值,它也支持由 POST body 直接处理的数据。作为一个执行增加了一个额外的名称 / 值参数的主体内容。问题是,在浏览器提交的情况下,Body 是空的而其参数的内容被覆盖。 org.apache.shindig.protocol.DefaultHandlerRegistry类(清单 7)更改修复了这个问题。

复制代码
……………..
<strong>public</strong> Future<?> execute(Map<String, String[]> parameters, Reader body,
{1}
SecurityToken token, BeanConverter converter) {
<strong>try</strong> {
<span color="#008080">// bind the body contents if available<br></br></span>
<strong>if</strong> (body != <strong>null</strong>) {
String bString = IOUtils.toString(body);
<strong>if</strong>(bString.length() > 0)
parameters.put(<span color="#0000ff">operation</span>.bodyParam(), <strong>new</strong> String[]{bString});
}
RequestItem item = <span color="#0000ff">methodCaller</span>.getRestRequestItem(parameters, token, converter,
<span color="#0000ff">beanJsonConverter</span>);
<span color="#0000ff">listener</span>.executing(item);
<strong>return</strong> <span color="#0000ff">methodCaller</span>.call(<span color="#0000ff">handlerProvider</span>.get(), item);
………………………………………..

清单 7 禁止覆盖空消息体

最后,当 POST 方式不会返回任何数据,Shindig 实现将空数据转换成一个空的 JSON 对象,返回的类型为“application/json”。这将导致浏览器尝试主动与用户会话。org.apache.shindig.protocol.DataServiceServlet(清单 7)解决了这一问题:

复制代码
org.apache.shindig.protocol.DataServiceServlet (Listing 7) solves this problem:
servletResponse.setContentType(converter.getContentType());
<strong><span color="#800040">if</span></strong> (responseItem.getErrorCode() >= 200 && responseItem.getErrorCode() < 400) {
Object response = responseItem.getResponse();
<span color="#008080">// TODO: ugliness resulting from not using RestfulItem<br></br></span>
<strong>if</strong> (!(response <strong><span color="#800040">instanceof</span></strong> DataCollection) && !(response <strong><span color="#800040">instanceof</span></strong> RestfulCollection)) {
<span color="#008080">//BL - modified to not return fake responce</span>
<strong><span color="#800040">if</span></strong>(response <strong><span color="#800040">instanceof</span></strong> Map){
<strong><span color="#800040">if</span></strong>(((Map)response).isEmpty()){
servletResponse.setContentType("<span color="#0000ff">text/plain</span>");
<strong><span color="#800040">return</span></strong>;
}
}
response = ImmutableMap.of("entry", response);
}

清单 8 禁用空响应

示例应用程序

在这篇文章中,我们描述 OpenSococial 的实现,用于构造 GeoSpatial OpenSocial mashup,允许用户和他的朋友把他们最喜欢的地方放在地图上(图 6)

图 6 地图上用户和他的朋友

它还允许将用户信息和他最爱的地方以及社区网络信息(图 7)发送给另一个朋友。

图 7 社区网络信息

这种简单的实现只是社区网络和地理空间数据之间许多潜在协同效应之一。基于以上所描述的结合两者的方法,能实现许多其他有趣的混搭网站。

结论

开源社区 API 的扩展使它很难开发出应用程序支持多种现有的社区网络。在本文中提出的一个解决方案,通过第三方提供的标准 API“隐藏”前端实现差异来克服这些问题。 Apache Shindig,提供 API 和“插件”架构,大大简化了整个实现。

查看英文原文 Bringing in Social Content to Custom Applications with Apache Shindig


[1] http://www.opensocial.org/

[2] http://www.thegoodnamesweregone.com/post/83-open-source-web-api-frameworks.aspx

[3] http://incubator.apache.org/shindig/

[4] http://www.gypsii.com/

[5] http://www.opensocial.org/Technical-Resources/opensocial-spec-v09/OpenSocial-Specification.html

[6] http://wiki.opensocial.org/index.php?title=JavaScript_API_Reference

[7] http://wiki.opensocial.org/index.php?title=OpenSocial_REST_Developer%27s_Guide

[8] http://www.opensocial.org/Technical-Resources/opensocial-spec-v09/Gadgets-API-Specification.html

[9] Source : http://incubator.apache.org/shindig/

[10] Rajdeep Dua. Architectural Overview of Shindig , an OpenSocial Reference Implementation. http://chrisschalk.com/shindig_docs/rajdeep/shindig-overview/onjava-shindig-overview-tidy.html and Rajdeep Dua. Overview of REST Implementation in Shindig - Java Version. http://sites.google.com/site/opensocialarticles/Home/shindig-rest-java

[11] http://code.google.com/p/google-guice/

[12] http://www.gypsii.com/home.cgi

[13] http://developer.gypsii.com/docs.cgi?op=view&id=api.html

[14] http://www.xmlrpc.com/

[15] http://openid.net/

[16] http://oauth.net/

[17] Seda Özses, Salih Ergül Cross-domain communications with JSONP, Part 1: Combine JSONP and jQuery to quickly build powerful mashups. http://www.ibm.com/developerworks/library/wa-aj-jsonp1/

[18] http://code.google.com/p/jsonp-java/

感谢曹云飞对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家加入到 InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。

2010-02-26 00:054724

评论

发布
暂无评论
发现更多内容

微信朋友圈广告项目全国招商 全媒体平台矩阵 终身售后扶持

互联网广告践行者

世界读书日 | 开发者必读书单重磅来袭,华为云DTSE专家天团力荐

华为云开发者联盟

华为云 华为云开发者联盟 企业号2024年4月PK榜 世界读书日 开发者必读书单

lerna-lite 轻量化 monorepo 管理利器

小鑫同学

金三银四 面试准备 数据结构面试题,编程与算法经典面试题

测试人

面试 软件测试 求职

怎么做腾讯微信朋友圈广告代理业务?微信朋友圈广告项目加盟商利润咋样?

互联网创业说

✅MySQL的脏读、幻读、不可重复度是什么

派大星

E-MapReduce极客挑战赛季军方案

阿里云天池

阿里云

GLM国产大模型训练加速:高效性能与成本优化的实践

百度开发者中心

人工智能 深度学习 大模型

第二届阿里巴巴大数据智能云上编程大赛亚军比赛攻略_北方的郎队

阿里云天池

阿里云

数据洞察创新挑战赛之智能运维赛参赛攻略--皮卡丘的皮卡

阿里云天池

阿里云

快刀斩乱麻,DevOps让代码评审也自动起来

禅道项目管理

DevOps 软件测试 软件开发 代码规范 代码评审

出海不出局 | 小游戏引爆高线市场,新竞争态势下的应用出海攻略

融云 RongCloud

HashData获得华为鲲鹏Validated认证 信创版图持续壮大

酷克数据HashData

刘强东“分身”直播首秀带火 AI 数字人直播,青否数字人厂商备受关注!

青否数字人

数字人

Prompt工程师压箱底绝活——Prompt的基本组成部分、格式化输出与应用构建

百度Geek说

企业号 4 月 PK 榜 Prompt

除了Pura70系列,华为最近还有这件事值得关注!

华为云PaaS服务小智

华为云 鲲鹏

虚拟现实VR展厅演示

-亦世凡华、

JavaScript Vue three

面试不会算法和数据结构,经典面试题讲解来了!

霍格沃兹测试开发学社

利用jd.item_get API获取商品信息,打造可读性强的商品介绍

技术冰糖葫芦

API 接口 API 文档 pinduoduo API

使用php获取时间今天、明天、昨天时间戳的详解

百度搜索:蓝易云

php 云计算 Linux 运维 云服务器

捷途山海T2:卓越性能、丰富配置,树立混动越野新标杆

Geek_2d6073

教你如何进行Prometheus 分片自动缩放

华为云开发者联盟

开发 Prometheus 华为云 华为云开发者联盟 企业号2024年4月PK榜

你的debug包在Android 14变卡了吗?|得物技术

得物技术

android 性能优化 debug 堆栈 企业号 4 月 PK 榜

LLaMA 2语言大模型的微调策略:LoRA与全参数选择

百度开发者中心

人工智能 大模型 llama2

青否数字人直播带货源码有哪些功能?

青否数字人

数字人

仓储管理解决方案:混合低代码与定制开发,实现灵活性与效率的完美结合

天津汇柏科技有限公司

低代码开发 软件开发定制 仓储系统

字节面试:如何解决MQ消息积压问题?

王磊

Java 面试题

做全媒体广告代理商怎么发展 腾讯微信广告代理怎么做利润高?

微点全媒体微信推广渠道

Databend 开源周报第 141 期

Databend

Weekly

什么是GPTs?如何创建GPTs?详细图文教程!

蓉蓉

ChatGPT GPT-4 GPTs

文心中国行走进成都!4月24日一起把握大模型时代的产业新机遇

飞桨PaddlePaddle

百度 BAIDU 百度飞桨 文心大模型 文心中国行

使用Apache Shindig为客户应用程序加入社区内容_Java_Boris Lublinsky_InfoQ精选文章