文档章节

开发Ubuntu Touch可用的Scope界面-快速入门

openthings
 openthings
发布于 2015/04/17 09:29
字数 5218
阅读 92
收藏 0

    Scope是一组数据的定制视图,可使用定制的布局、显示和品牌创建选项。从RSS新闻推送到天气数据和搜索引擎结果,Scope的灵活性使您能够使用其余OS提供简单、明确且一致的体验。Scope也可与系统范围内的帐户集成(电子邮件、社交网络…),将您的内容分为多个类别并在各个类别 中进行集合(例如,“shopping”Scope集合了多个商店Scope的结果)。

    在本教程中,您将了解如何使用Ubuntu SDK编写SouldCloud的C++Scope。在本示例中,只需要非常少的C++知识,将其根据暴露JSON API其他服务来调整也非常简单。

    注意:本教程也适用于Ubuntu 14.04及更高版本。如果您希望使用Scope布局工具,则至少要使用Ubuntu 14.10。

SDK设置

SDK提供适用于多种不同应用程序类型的多种模板。C++Scope有自己的模板,这也是我们将使用的模板。单击“New Project”按钮来创建新Scope项目。系统将要求您填入一些值来生成该项目。

如果您需要获得更多有关SDK入门指南的帮助,请查看SDK设置文章

注意:即使您要使用平台的安全策略,您还需要了解有关Scope的另一件事:如果您在某个时刻需要使用网络,您将无法访问用户数据。这是一项合理的隐私政策,以避免在未得到明确许可的情况下提取用户数据。

测试您的Scope

在本教程的任一点,您都可按下SDK侧栏上的Play按钮来测试您手机上或仿真器上的Scope。等待几秒,项目的生成并上传到设备后,项目应会自动打开。

关键源文件

您可通过运行以下项目获得本教程的源代码

$ bzr branch lp:~davidc3/ubuntu-sdk-tutorials/scope-tutorial-soundcloud-qjson

生成的项目包含相当多的文件,我们将讨论其中最重要的一些文件。需要注意的一点是:模板已提供了一个正在使用的Scope:使用openweathermap.org的天气Scope。我们将对其进行更改,使其从SoundCloud中提取结果。

manifest.json

其能容将由生成系统用于生成点击数据包,您能够在Ubuntu Store内安装和发布该点击数据包。大多数情况下,您可保留从开发人员环境中提取的默认值。

<scope>.apparmor

您的Scope使用的安全策略组。我们的示例中为无,因为我们使用的“ubuntu-scope-network”模板已经许可网络调用。了解更多安全策略组

data/<appid>.ini

连接到文件

一个非常重要的文件,将允许您自定义和推广您的Scope(图标、背景图像、颜色…)。我们稍后将看到相关情况。

include/api/config.h

连接到文件

我们的HTTP配置:用户代理和基础API URL。让我们更改SoundCloud API URL的apiroot,完成首个更改。

15

std::string apiroot { "https://api.soundcloud.com" };

其他URL参数稍后将通过net-cpp库添加。

include/api/client.h, /scope/scope.h, /scope/query.h, /scope/preview.h

连接至文件夹

我们的C++标头的其余部分。如下所示,更改client.h标头,以匹配SoundCloud API的数据结构。您可保留标头的其余部分不变。

这是我的Client类现在的外观。您可通过将教程文件的内容粘贴到您自己的文件中进行尝试:

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95

class Client {

     public :

 

     /**

     * Our Artist object.

     */

     struct Artist {

         unsigned int id;

         std::string username;

         std::string avatar_url;

     };

 

     /**

     * Track info, including the artist.

     */

     struct Track {

         unsigned int id;

         std::string title;

         std::string uri;

         std::string artwork_url;

         std::string stream_url;

         std::string description;

         std::string genre;

         Artist artist;

     };

 

     /**

     * A list of Track objects.

     */

     typedef std::deque<Track> TrackList;

 

     /**

     * Track results.

     */

     struct TrackRes {

         TrackList tracks;

     };

 

     Client(Config::Ptr config);

 

     virtual ~Client() = default ;

 

     /**

      * Get the track list for a query

      */

     virtual TrackRes tracks( const std::string &query);

 

     /**

      * Cancel any pending queries (this method can be called from a different thread)

      */

     virtual void cancel();

 

     virtual Config::Ptr config();

 

protected :

     void get( const core::net::Uri::Path &path,

              const core::net::Uri::QueryParameters &parameters,

              QJsonDocument &root);

     /**

      * Progress callback that allows the query to cancel pending HTTP requests.

      */

     core::net::http::Request::Progress::Next progress_report(

             const core::net::http::Request::Progress& progress);

 

     /**

      * Hang onto the configuration information

      */

     Config::Ptr config_;

 

     /**

      * Thread-safe cancelled flag

      */

     std::atomic< bool > cancelled_;

};

src/api/client.cpp

连接到文件

我们的API客户端。它提供Scope代码和HTTP API访问之间的隔离。其唯一的作用是检索SoundCloud中的数据。

src/scope/scope.cpp

连接到文件

该文件定义类型类unity::scopes::ScopeBase,该类型类提供客户端用于与Scope互动的输入点API。

  • 它实行启动和停止方法。很多Scope都保持这些方法处于不修改的状态,本示例也一样。

  • 它还实行两个关键方法:搜索和预览。这些方法一般不需要修改,本示例也未修改。但是,如下所述,它们调用每个Scope需要实行的关键方法。

注意:您可能会发现在相应的标头文件:include/scope/scope.h中检查ScopeBase类声明(其API)很有用。该标头文件是了解C++类的绝佳方法,因为它们的API无需其他任何实行代码即可声明,理解非常容易。

提示:如果您希望深入了解各种特定类,请在本教程期间查看Unity 8 Scope API参考文件

src/scope/query.cpp

连接到文件

我们在该位置发送查询到API客户端,传输返回的结果到结果卡,声明将托管这些卡及其布局的类别。

该文件定义一个类型类unity::scopes::SearchQueryBase

该类从客户端提供的查询字符串生成搜索结果,并将其返回作为对客户端的回复:

  • 接收来自客户端的查询字符串

  • 接收来自客户端的回复对象

  • 发送查询到API客户端

  • 创建搜索结果类别(对于有不同布局的示例:grid/carousel)

  • 将每个搜索结果与其类别结合(创建CategorisedResult对象)

  • 推送分类结果到回复对象中,由客户端进行显示

在运行方法中完成了大量的编码规则,我们在此处只需完成最少的变动。

检查相应标头文件:include/scope/query.h中的SearchQueryBase类声明(其API)。

src/scope/preview.cpp

连接到文件

该关键文件定义一个类型类unity::scopes::PreviewQueryBase

该类定义预览阶段每个搜索结果使用的小工具和布局。它:

  • 定义预览中使用的小工具

  • 每个结果中针对数据字段的Maps小工具字段

  • 定义有不同列数的布局——取决于显示大小的不同,仅由客户端在显示时间了解。

  • 分配小工具到每个布局的各列

  • 接收回复对象,推送由客户端使用的小工具和布局到对象上

检查相应标头文件:include/scope/preview.h中的SearchPreviewBase类声明(其API)。

对于预览小工具列表和文档,请参阅本页

当我们深入了解我们的示例Scope并详细说明一些代码,从查询开始。

查询字符串

src/scope/query.cpp中,您可轻松看到Scope的哪个位置在接收用户查询。在Scope打开后,该查询为空白,您将为本示例提供一些数据。这是呈现特色类容或最新/流行项目的好机会。

在这里,我刚刚触发了一个有关字符串“blur cover”的搜索,该搜索将推送至API客户端,因为SoundCloud为自己的歌曲设置了美观的封面。您可能希望看到更明确的解释,但就本示例来看,让我们假设这是我们用户的一个好起点。修改Query::run方法,使其如此处所示,或者只需将教程文件的内容粘贴到您自己的方法中:

60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

void Query::run(sc::SearchReplyProxy const & reply) {

     try {

         // Start by getting information about the query

         const sc::CannedQuery &query(sc::SearchQueryBase::query());

 

         // Trim the query string of whitespace

         string query_string = alg::trim_copy(query.query_string());

 

         Client::TrackRes trackslist;

         if (query_string.empty()) {

             // If the string is empty, provide a specific one

             trackslist = client_.tracks( "blur cover" );

         } else {

             // otherwise, use the query string

             trackslist = client_.tracks(query_string);

         }

(...)

生成搜索结果

让我们移至api/client.cpp,获取来自SoundCloud的一些结果…

net-cpp是一个我们将用于查询API的简单联网库。但是,您可以用替换他,并使用其他任何满足您目的的联网库。模板已提供一个使用net- cpp处理HTTP标题和错误的get方法,解析回复并返回一个JSON对象,该操作很方便,将执行大多数JSON API的工作。只需粘贴教程文件中的内容到自己的方法中,或执行以下步骤,即可尝试该方法。

基础URL来自我们的配置标头,我们只需添加我们的路径和参数其余部分即可:

60
61

get( { "tracks.json" }, { { "client_id" , "apigee" }, { "q" , query } }, root);

注释client_id:如果您希望分发SoundCloudScope,您将需要在SoundCloud Developers中注册自己的API键(免费,只需花费 5分钟)。在上述例子中,我使用示例键。

然后,我们需要迭代根JSON中显示的每个结果,然后提取我们需要的结果。以下是我们的完整方法:

54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95

Client::TrackRes Client::tracks( const string& query) {

     QJsonDocument root;

 

     // Build a URI and get the contents.

     // The fist parameter forms the path part of the URI.

     // The second parameter forms the CGI parameters.

     get( { "tracks.json" }, { { "client_id" , "apigee" }, { "q" , query } }, root);

 

     // My “list of tracks” object (as seen in the corresponding header file)

     TrackRes result;

 

     QVariantList variant = root.toVariant().toList();

     for ( const QVariant &i : variant) {

         QVariantMap item = i.toMap();

         QVariantMap user = item[ "user" ].toMap();

         string art;

         // If the track artwork is empty, we use the artist picture

         if (item[ "artwork_url" ].toString().toStdString() == "" ) {

             art = user[ "avatar_url" ].toString().toStdString();

         } else {

             art = item[ "artwork_url" ].toString().toStdString();

         }

         cout << item[ "title" ].toString().toStdString();

         // We add each result to our list

         result.tracks.emplace_back(

             Track {

                 item[ "id" ].toUInt(), item[ "title" ].toString().toStdString(),

                 item[ "uri" ].toString().toStdString(), art,

                 item[ "stream_url" ].toString().toStdString(),

                 item[ "description" ].toString().toStdString(),

                 item[ "genre" ].toString().toStdString(),

                 Artist {

                     user[ "id" ].toUInt(),

                     user[ "username" ].toString().toStdString(),

                     user[ "avatar_url" ].toString().toStdString()

                 }

             }

         );

     }

     return result;

}

就是这样了。我们已经获得所需的数据,下面将开始了解如何按照我们喜欢的方式显示这些数据。

类别渲染器

每个结果都需要在一个类别内显示。对于UI,一个类别可为一列结果提供一个标头标题和一个具体的布局,布局说明结果的放置方式和外观。通过粘贴教程文件的内容到自己的方法中,或执行以下步骤,尝试这一操作。

CategoryRenderer通过JSON对象创建。这些渲染器作为原始字符串创建。JSON对象有两个涉及直接兴趣的字段:模板和组件。

修改src/scope/query.cpp上的类别,使其类似与以下类别:

30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

const static string TRACKS_TEMPLATE =

     R"(

         {

             "schema-version" : 1,

             "template" : {

                 "category-layout" : "grid" ,

                 "card-layout" : "horizontal" ,

                 "card-size" : "large"

             },

             "components" : {

                 "title" : "title" ,

                 "art" : {

                     "field" : "art"

                 },

                 "subtitle" : "artist"

             }

         }

     )";

这将显示简单的结构列表,它是很多Scope中使用的类别样式,与很多不同的内容类型都能兼容。您可查看unity::scopes::CategoryRenderer doc中的所有选项。

现在,在try{}部分/Query::run方法中,我们可以在回复对象行登记我们的类别:

77
78
79
80
81
82

// Register a category for tracks

auto tracks_cat = reply->register_category( "tracks" , "" , "" ,

     sc::CategoryRenderer(TRACKS_TEMPLATE));

// register_category(arbitrary category id, header title, header icon, template)

// In this case, since this is the only category used by our scope,

// it doesn’t need to display a header title, we leave it as a blank string.

结果

要使这个SoundCloudScope有用,我们希望每个结果至少都拥有:

  • URI:曲目页面的链接(必要)

  • 类别:如上所示,它决定了结果在UI中的显示位置和方式(必要)

  • 标头:曲目的名称

  • 艺术家:品牌/艺术家的名称

  • 视觉:专辑/曲目封面

确保您已在类别模板组件中定义的每个字段都在结果中显示,即使这些字段为空。无效结果将自动弃置。

还是在src/scope/query.cpp中,在try{}部分/我们的Query::run方法中,我们需要迭代我们的曲目列表,为每个曲目创建一个unity::scope::CategorisedResult。将教程文件的类容粘贴到您自己的方法中,或复制以下行:

85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106

for ( const auto &;track : trackslist.tracks) {

 

     // Use the tracks category

     sc::CategorisedResult res(tracks_cat);

 

     // We must have a URI

     res.set_uri(track.uri);

 

     // Our result also needs a track title

     res.set_title(track.title);

 

     // Set the rest of the attributes, art, artist, etc.

     res.set_art(track.artwork_url);

     res[ "artist" ] = track.artist.username;

     res[ "stream" ] = track.stream_url;

 

     // Push the result

     if (!reply->push(res)) {

         // If we fail to push, it means the query has been cancelled.

         return ;

     }

}

如您所见,您可为某些字段使用特定方法(set_art、set_uri…),也可添加自定义字段(artist、stream、duration…)。

预览

该预览需要生成小工具,并连接其字段到CategorisedResult中的数据字段。

它还将生成处理不同显示环境的布局。想法是仅由客户端了解布局上下文。客户端在思考布局上下文时要考虑可用的列数。对于有不同列数的布局,Scope定义哪些列用于放入小工具。

首先,让我们来了解一下小工具。

预览小工具

以下是一组预定义的预览小工具。每个小工具都有一个您用于创建的输入字段。每个小工具类型也有其他的字段,具体情况因小工具类型的不同而变。

您可看到此处提供的预览小工具类型和字段列表。

本示例使用三种类型的预览小工具:

  • 标头:有一个标题和一个副标题字段

  • 图像:有用于检索艺术形式的源字段

  • 操作:用户单击预览时,用于提供按钮文本“Open”和已打开的URI

此处示范我们的示例如何创建名为w_header的标头小工具(在src/scope/preview.cpp的Preview::run方法中):

40

sc::PreviewWidget w_header( "headerId" , "header" );

  • 首个参数为一个随意的ID。我们使用这些ID分配小工具到不同的布局,稍后将展示这一操作。

  • 第二个参数是预览小工具类型,预定义类型组中的一个类型。

在创建小工具后,小工具字段中将填入由客户端处理的CategorisedResult中的数据。我们的w_header小工具的标准字段:标题和副标题已填充。

有两种可用的方法用于在小工具字段中填入数据:

  • add_attribute_value(FIELD, VALUE):您可使用该方法将您手边的数据填充到小工具字段中

  • add_attribute_mapping(FIELD, CR_FIELD):使用该方法填充待处理的CategorisedResult中的数据到小工具字段中。

在我们的示例中,小工具数据通过当前CategorisedResult提取,add_attribute_mapping也使用该方法。

首先,当我们将w_header小工具的标题字段(第一个参数)映射到当前CategorisedResult的标题字段(第二个参数):

42

w_header.add_attribute_mapping( "title" , "title" );

接下来的示例会更有趣,因为我们将从CategorisedResult(不属于CategoryRenderer)字段填充小工具字段。该字段为 艺术家。对于之前的每个结果,我们已经直接在CategorisedResult中添加艺术家键和值。因此,本示例说明当数据未在结果阶段显示并定制到 Scope时如何在预览中显示数据:

43

w_header.add_attribute_mapping( "subtitle" , "artist" );

回看查询,即创建CategorisedResults的位置,我们再次了解艺术家数据如何在CategorisedResult提供:

84

res[ "artist" ] = track.artist.username;

因此,每个CategorisedResult都有一个“艺术家”字段,该字段由搜索结果填充。在此预览阶段,我们将艺术家数据推送到w_header小工具预定义的副标题字段。

教程文件的内容可粘贴到自己的方法中,以试用这些小工具。

以下是我们的变更结果:

39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

// Define the header section

sc::PreviewWidget w_header( "headerId" , "header" );

 

// It has title and a subtitle properties

w_header.add_attribute_mapping( "title" , "title" );

w_header.add_attribute_mapping( "subtitle" , "artist" );

 

// Define the image section

sc::PreviewWidget w_art( "imageId" , "image" );

 

// It has a single source property, mapped to the result's art property

w_art.add_attribute_mapping( "source" , "art" );

 

// Define the actions section

sc::PreviewWidget w_actions( "actionsId" , "actions" );

 

// Actions are built using tuples with an id, a label and a URI

sc::VariantBuilder builder;

builder.add_tuple({

     { "id" , sc::Variant( "open" )},

     { "label" , sc::Variant( "Open" )},

     { "uri" , result[ "uri" ]}

});

w_actions.add_attribute_value( "actions" , builder.end());

现在,它们可与回复对象一同推送到客户端:

61

reply->push( { w_art, w_header, w_actions });

小工具已创建、填充和推送。但是,客户端也需要了解放置小工具的位置,以及如何在不同的上下文中以美观的方式安排小工具,例如,一个窄屏和一个宽屏,让我们一起查看布局。

生成布局

我们的示例定义了两个布局:一个有一列,另一个有两列。这些布局如下所示进行声明:

27

sc::ColumnLayout layout1col(1), layout2col(2);

提示:查看ColumnLayout文档(此处)。

我们不必具体了解如何客户端如何使用这些布局。但是,一般的预期是,单列布局与窄屏情况相配(比如素描模式),双列布局可能与宽屏情况相配(比如景观模式)。

现在,如您在教程文件src/scope/preview.cpp中所见,我们需要定义三个小工具在每个布局中的放置位置。

自然情况下,在单列布局中,所有小工具必须放入单列中:

30

layout1col.add_column( { "imageId" , "headerId" , "actionsId" });

在双列布局中,我们决定添加图像到第一列,标头和操作添加到第二列:

33
34

layout2col.add_column( { "imageId" });

layout2col.add_column( { "headerId" , "actionsId" });

现在,我们需要在回复对象中注册布局,方法如下所示:

37

reply->;register_layout({layout1col, layout2col});

自定义和推广

默认情况下,您的Scope将如下所示:

很多显示选项都可在data/<appid>.ini中进行更改。以下是我为推广该Scope的最佳做法,大多数选项都是自明式选项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

[ScopeConfig]

DisplayName = SoundCloud

Description = This is a SoundCloud scope doing SoundCloud things

Art = screenshot.png

Author = Firstname Lastname

Icon = icon.png

 

[Appearance]

PageHeader.Logo = logo.png

PageHeader.background = color:///#FFFFFF

PageHeader.ForegroundColor = #F8500F

BackgroundColor = #FFFFFF

PageHeader.DividerColor = #F8500F

PreviewButtonColor = #F8500F

我也找到了这个SoundCloud徽标来替换模板中提供的徽标。下载它,将其保存为data/logo.png

如果您调整类别布局和颜色,您可得到差异非常大的样式。左侧的布局是使用上述代码片段生成的:

请查看所有可用的自定义选项并尝试让您的Scope美观起来!

这就是了,我们的SoundCloudScope完成了。您可按下SDK侧栏的Start按钮启动该Scope,在编辑器的底部查看是否所有内容都已编写完成且正确启动,然后试用您的新Scope!

概述

  • 我们已看到如何创建可查询web API的Scope

  • 查询结果将通过一个独特的渲染器放入一个类别

  • 客户端显示搜索结果

  • 对于预览阶段,使用了四个预定义的小工具类型

  • 多个布局已创建,在布局中小工具采用不同的布置方法,以在数个外观设置中得到美观的显示效果

  • 一些仅与此Scope相配的自定义数据(例如艺术家)在预览和结果中显示

进一步了解

Scope是强大的工具,可帮助用户访问信息和选中的内容。Ubuntu已提供大量默认的Scope,但我们总是可以创建更多的Scope!

新API的收藏夹源(书籍、电影等)转换到Scope中为ProgrammableWeb API目录,但还有其他多种不同的源。请随意实践不同的布局和卡,以包含不同类型的数据!

发布Scope与发布其他应用程序完全一致,请查看我们的发布指南,以用数分钟的时间在店内发布您的Scope。


本文转载自:https://developer.ubuntu.com/zh-cn/scopes/tutorials/write-a-json-scope-in-cpp/

openthings
粉丝 310
博文 1116
码字总数 644109
作品 1
东城
架构师
私信 提问
Ubuntu SDK开发工具链的安装和使用

Ubuntu系列现在已经发展为云、服务器、桌面、平板、手机通吃的全功能OS了,这里介绍了Ubuntu SDK如何为其全面的应用开发提供支持,包括Ubuntu SDK的安装以及Scope、QML和JavaScript集成应用等...

openthings
2015/04/07
0
0
十月份新出炉的snap应用

十月份新出炉的snap应用 IMCN 2017年12月1日暂无评论 阅读 373 次 安装这个snap之后,为了确保启用所有MuseScore功能,需要使用以下命令手动连接一些安全接口: 投稿作者 作者网站 0 0 为您推...

IMCN
2017/12/01
0
0
漂亮且稳定!Ubuntu Touch 正式版发布

Canonical日前发布了Ubuntu Touch系统的首个RTM正式版,Ubuntu粉丝现在就可以到官网下载,安装到手机或平板机上进行体验。 首款Ubuntu Touch手机将在12月发布,来自我国厂商魅族。 Ubuntu T...

oschina
2014/09/20
21.4K
71
如何成为一个Ubuntu开发者?

近年来,Canonical的Ubuntu在中国频频亮相,产品横跨智能手机界,个人电脑系统和企业云技术服务,未来更是瞄准了智能物联,可以预见一个平台适用于多个终端不再遥远。 2013年,Canonical公司...

ubuntu_fan
2015/11/03
380
1
Ubuntu手机迎来OTA-11更新:Wifi Display

Ubuntu手机今天迎来第十一个重要更新:OTA-11,这次更新主要的亮点为Wifi Display(屏幕无线投射),借助Wifi Display的功能用户可以体验无线投射屏幕加桌面融合(convergence)的巨大便利。...

UbuntuCN
2016/06/07
3.8K
9

没有更多内容

加载失败,请刷新页面

加载更多

聊聊Elasticsearch的MonitorService

序 本文主要研究一下Elasticsearch的MonitorService MonitorService elasticsearch-7.0.1/server/src/main/java/org/elasticsearch/monitor/MonitorService.java public class MonitorServic......

go4it
27分钟前
1
0
二、Docker

1、Docker - The TLDR(Too Long,Don't Read,Linxu 终端工具 ) Docker是在Linux和Windows上运行的软件。它创建、管理和编排容器。该软件以开源方式开发,在Github上作为Moby开源项目的一部分。...

倪伟伟
40分钟前
2
0
Python猫荐书系列之七:Python入门书籍有哪些?

本文原创并首发于公众号【Python猫】,未经授权,请勿转载。 原文地址:https://mp.weixin.qq.com/s/ArN-6mLPzPT8Zoq0Na_tsg 最近,猫哥的 Python 技术学习群里进来了几位比较特殊的同学:一...

豌豆花下猫
今天
5
0
Guava RateLimiter限流源码解析和实例应用

在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流 缓存 缓存的目的是提升系统访问速度和增大系统处理容量 降级 降级是当服务出现问题或者影响到核心流程时,需要暂时屏蔽掉,待高...

算法之名
今天
13
0
国产达梦数据库与MySQL的区别

背景 由于项目上的需要,把项目实现国产化,把底层的MySQL数据库替换为国产的达梦数据库,花了一周的时间研究了国产的数据库-达梦数据库,它和MySQL有一定的区别,SQL的写法也有一些区别。 ...

TSMYK
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部