Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

Cloud Development Getting Started with Cloud Databases


May 22, 2021 Mini Program Cloud Development Study Guide


Table of contents


Any large application or service must use a high-performance data storage solution for accuracy (ACID, AtomicIty, Consistency, Isolation, Persistence Durability, Expanded Understanding), fast and reliable storage and retrieval of users' account information, merchandise and commodity transaction information, product data, information articles, and more, and cloud development brings its own high performance, Highly available, expandable, and secure databases.

The basics of a cloud database

In the operation of the database, we have to have a certain understanding of the database database, collectioncollection, record doc and field field, first of all, remember these corresponding English words, when you want to operate a record doc field content, just like delivery courier, first to find out which database it is in, in which collection, in which record, a level of search. Operational databases are usually databases, collections, records, fields to add, delete, change, check, when you understand these, the operation database will not be confused.

The corresponding understanding of the cloud database with Excel and MySQL

We can combine Excel and MySQL (it doesn't matter if we haven't been exposed to MySQL before, just look at the corresponding line with Excel) to understand the database for cloud development.

Cloud database MySQL database Excel file
Database database Database database The workbook
Collection collection Table table The worksheet
Field field The data column column Each column of the data table
Record record/doc Record row The data table divides each row except the first row

The creation of the collection and the data type

Let's now create a collection of books (the equivalent of creating an Excel table) to store information about books in the library, such as this one:

The title of the book, title JavaScript Authoritative Guide (Version 6)
Author author David Flanagan
Standard book number isbn 9787111376613
Publishing information publicInfo Press press Machinery Industry Press
Year of publication year 2012

Open the database tab for the cloud development console, create a new collection book, select the collection, add a record to the book (similar to filling in the first line of Excel fields and one of the information records about the book), and then add fields:

  • Field Name: Title, Type: String, Value: JavaScript Authoritative Guide (Version 6)
  • Field Name: author, Type: String, Value: David Flanagan
  • Field name: isbn, type: string, value: 9787111376613
  • Field name: publishInfo, type: object
  • Then we add the field press below the publisInfo (level 2) with the value: Machinery Industry Press;

Permission control and security rules for the database

After the database is created, we need to set the permission settings for the database in the permission settings tab of the cloud development console-database-collection. T he permissions of the database are divided into the small terminal and the service side (cloud function, cloud development console). T he service side has read and write permissions for all data, so the permissions here are set only to set the user's permissions on the database on the small terminal. Permission control is divided into simple permission control and custom permissions (i.e. security rules), and it is recommended that developers replace simple permission control with security rules.

Technical documentation: Permission control

To use custom permissions (i.e., security rules) to completely replace simple permission control, we need to understand the meaning of 4 simple permission control, and how security rules should replace them one by one, that is, when we configure the permissions of the collection, we no longer choose simple permission control, but the unified selection of custom permissions, fill in the corresponding jason rules.

Security rules allow for more flexibility and clarity in the ability to customize read and write permissions for the front-end database, and by configuring security rules, developers can fine-tune the readread and writewrite permissions for all records in the collection. Among them, the write permission can also be subdivided into creative new, update update, delete delete and other permissions, but also support comparison, logical operators for more granular permission configuration.

All users can read, only the creator can read and write: such as the user's posts, comments, articles, the creator here refers to the small terminal users, that is, the collection of storage UGC (user-generated content) to be set to this permission;

  1. {
  2. "read": true,
  3. "write": "doc._openid == auth.openid"
  4. }

Only the creator can read and write: such as private photo albums, users' personal information, orders, that is, only users read and write their own, other people can not read and write data collection;

  1. {
  2. "read": "doc._openid == auth.openid",
  3. "write": "doc._openid == auth.openid"
  4. }

All people can read: such as information articles, product information, product data, etc. you want everyone to see, but can not modify the content;

  1. {
  2. "read": true,
  3. "write": false
  4. }

All users can not read and write: such as background use of unexposed data, only you can see and modify the data;

  1. {
  2. "read": false,
  3. "write": false
  4. }

The small terminal API has strict call permission control, for example, in the small terminal A user is not able to modify B user's data, there is no such permission, in the small terminal can only modify non-sensitive and only for a single user's data;

If the data in the database collection is obtained by import, the permission for this collection defaults to "creator-only readable and writeable", a permission that can be called on the service side (cloud function), but an empty array may be returned on the small terminal Oh, so be sure to remember to modify the permissions as appropriate.

  1. 小程序端与云函数的服务端无论是在权限方面、API的写法上(有时看起来一样,但是写法不一样),还是在异步处理上(比如服务端不再使用successfailcomplete回调,而是返回Promise对象),都存在非常多的差异,这一点要分清楚。

Get a glimpse of the full picture of the data query

Query the records in the collectioncollection

The records in query collectioncollection are the most important knowledge of cloud development database operation, in the previous section we have imported the data of China's urban economic data china.csv into the collection china, and have set the permissions of the collection to be "all readable, only the creator can read and write" (or use security rules), and then we will use this as an example and combine the Excel version of China's urban economy line to explain the database query. In the Excel version of China's urban economic line and the cloud development console china collection, we can see the names of 332 cities in China city, provincial provance, urban area city_area, built-up area builtup_area, household registration population reg_pop, resident population resident_pop, GDP data.

Query the top 10 cities with GDP of more than 300 billion yuan and ask that the _id field not be displayed, showing the name of the city, the province and GDP, and in descending order of GDP.

Use the developer tool to create a new chinadata page, and then enter .js code in the onLoad lifecycle function of the index. The data in the operation collection involves very complex knowledge points, and the following cases are relatively complete, making it easy for everyone to have a holistic understanding:

  1. const db = wx.cloud.database() //获取数据库的引用
  2. const _ = db.command //获取数据库查询及更新指令
  3. db.collection("china") //获取集合china的引用
  4. .where({ //查询的条件指令where
  5. gdp: _.gt(3000) //查询筛选条件,gt表示字段需大于指定值。
  6. })
  7. .field({ //显示哪些字段
  8. _id:false, //默认显示_id,这个隐藏
  9. city: true,
  10. province: true,
  11. gdp:true
  12. })
  13. .orderBy('gdp', 'desc') //排序方式,降序排列
  14. .skip(0) //跳过多少个记录(常用于分页),0表示这里不跳过
  15. .limit(10) //限制显示多少条记录,这里为10
  16. .get() //获取根据查询条件筛选后的集合数据
  17. .then(res => {
  18. console.log(res.data)
  19. })
  20. .catch(err => {
  21. console.error(err)
  22. })
  1. 大家可以留意一下数据查询的链式写法, wx.cloud.database().collection('数据库名').where().get().then().catch(),前半部分是数据查询时对对象的引用和方法的调用;后半部分是Promise对象的方法,Promise对象是get的返回值。写的时候为了让结构更加清晰,我们做了换行处理,写在同一行也是可以的。

5 ways to build query criteria

In the case above, there are five ways to build query criteria: Collection.where (), Collection.field (), Collection.orderBy (), Collection.skip (), Collection.limit (), which can be disassembled separately, such as using only where or only field, limit, You can also draw a few combinations from these five, and you can write multiple identical methods in a query, such as orderBy, where you can write the same multiple times.

It's worth noting, however, that the results of these five method-ordered queries can sometimes vary (for example, if the order is repeatedly upset by the order), and query performance can vary. U sually skip is best left behind and don't let skip skip a lot of data. T he skip(.limit)) and limit(.skip() effects are equivalent. T he five methods for building query criteria are collection-based references to College, where, for example, it cannot be wx.cloud.database().where(), nor can it be wx.cloud.database ("china") .doc.where(), only wx.cloud.database ().collection (). china"). where(), that is, can only be used to query records in the collection.

  • Instruction query conditions where, note that later we will introduce the command query instructions such as filter field is greater than / less than / not equal to a value of the comparison instructions, while meeting the logic of multiple filter criteria, and fuzzy query is written in where;

Technical documentation: Collection.where

  • Specify which fields are returned, and only the values of the city, province, gdp three fields need to be returned or not returned when querying only true|false (or 1|-1):

Technical documentation: Collection.field

  • Data sorterBy, sorted by the syntax below, which is the sorting condition, where the field name is not restricted by field (not in the field, not displayed, but still works): orderBy ('field name', 'sort method'). S orting only supports desc descending, asc ascending, if the values in the field when the numbers are in size, if the letters are in order, do not support the sorting of Chinese. S orting supports sorting by multiple fields, just call orderBy multiple times, and sort multiple fields in order of orderBy calls. If you need to sort nested fields, you can use point notation, such as the books above, which sort from old to new based on the year of publication, and can be written as orderBy ('publicInfo.year', 'asc').

Technical documentation: Collection.orderBy

  • Paged shows skip, skip is often used with limit for page, such as a list of goods a page only shows 20 items, page 1 shows the entire data of 0 to 20, then page 2 we can use skip (20) skip (20) can skip the first page of 20 data, page 3 skip 40 data, page N is skip (n-1) x 20 data:

Technical documentation: Collection.skip

  • Limit the maximum number of limit, the maximum number of collection data queries limit in the small terminal maximum number of 20, in the service side of 100, such as limit (30) in the small terminal or only show 20 pieces of data, more data needs us to combine ply skip and javascript programming processing.

Technical documentation: Collection.limit

  • Although there is a limit to the number of results displayed by the small program query data, such as 100 on the service side, the sorting is still based on the data of the entire collection, not just for the 100 data.

Match the query

Each of the incoming objects, which forms a filter, has multiple slt;key, value, and indicates that these conditions need to be met at the same time, and is related to, if necessary, or if necessary, command.or

Query instruction Command

When instructions are used for queries, they are written in where, mainly to compare the values of the fields and to logically filter judgments. The database API provides a variety of query instructions, large or smaller, that are exposed to db.command objects.

Directive Command can be divided into query instructions and update instructions, the use of the two are very different, query instructions for db.collection where condition filtering, and update instructions for db.collection.doc of the update request field update, the difference between the two will be repeated later.

Compare operators with logical operators

Let's put together a table of the comparison operators and logical operators of the query instructions, and attach the corresponding technical documentation to facilitate a clear and holistic understanding of them. The comparison of query instructions

The comparison of query instructions
gt Greater than lt Less than
eq Equals neq Not equal to
Lte Less than or equal to gte Is greater than or equal to
in In the array nin Not in the array
The logic of the query instruction
and Conditions and or Conditions or
not The condition is not nor Not at all

The way the query instruction is written

The instruction command is based on a database database reference, and we take the full writing of a larger than gt on the small terminal (in the case of a larger than 3000):

  1. wx.cloud.database().command.gt(3000)

For simplicity, we usually assign wx.cloud.database() to a variable, such as db, db.command, and eventually to .gt (3000). By declaring variables and assigning values layer by layer, the writing of instructions is greatly simplified, which can be followed in other instructions.

Rich usage equals instruction Command.eq

Compared to other comparison instructions equal to eq and not equal to the use of the neq operator is very rich, it can make numerical comparisons, we query a field such as GDP equal to a value of 175.28 billion cities:

  1. .where({
  2. gdp: _.eq(17502.8),
  3. })

It can also match strings, such as when we query a field such as city to match a string completely, such as Shenzhen:

  1. .where({
  2. city: _.eq("深圳"),
  3. })
  1. 注意:在查询时,gdp: _.eq(17502.8)的效果等同于gdp:17502.8,而city: _.eq(“深圳”)等同于city:”深圳”,虽然两种方式查询的结果都是一致的,但是它们的原理不同,前者用的是等于指令,后者用的是传递对象值。

eq can also be used for field values such as arrays and objects, which we'll cover later in the chapter.

The logical instructions within the field

Find out about cities in Guangdong province with GDP of more than 300 billion and less than 1 trillion yuan. In Guangdong Province, that is, let the value of the field profile equal to "Guangdong", and the GDP requirement is that the GDP field meets both greater than 300 billion and less than 1 trillion, then you need to use and (conditions and, that is, meaning):

  1. .where({
  2. province:_.eq("广东"),
  3. gdp:_.gt(3000).and(_.lt(10000))
  4. })

Logical instructions across fields

The two conditions in the above case, province: .eq ("Guangdong") and gdp: .gt (3000). and (.lt(10000)) have a cross-field condition and the relationship between and (i.e., and) how to implement the cross-field condition or or?

Check out the top 20 major cities with GDP of more than 300 billion yuan and resident population of more than 5 million or urban areas of more than 300 square kilometers. The resident population and the area of the built-up area here only need to meet one of the conditions, which involves the conditions or or (note the format of the following code):

  1. .where(
  2. {
  3. gdp: _.gt(3000),
  4. resident_pop:_.gt(500),
  5. },
  6. _.or([{
  7. builtup_area: _.gt(300)}
  8. ]),
  9. )

Note the above three conditions, gdp: .gt (3000) and resident pop: .gt (500) is logical, and the relationship with buildup area: .gt(300)) is logical or. There is an array within the .or (condition one, condition two), and condition one and condition two constitute a logical relationship.

The positive query db. Regexp

Regular expressions have the flexibility to effectively match strings and can be used to check if a string contains some sort of sub-string, such as the word "technology" in "CloudBase Technical Bootcamp." C loud database query supports UTC-8 format, can be fuzzy query in Chinese and English. The positive query is also written in the condition filter for the where field.

Technical documentation: Database.RegExp

A fuzzy query for a field string

We can query a field with a positive query, such as a city city name, that contains a string such as a "state" city:

  1. .where({
  2. city: db.RegExp({
  3. regexp: '州',
  4. options: 'i',
  5. })
  6. })

Note that the city here is the field, db. T he regexp in RegExp() is a regular expression, while the options are flags, and i is the value of the flag, which means that the letters are not case sensitive. O f course, we can also use JavaScript's native writing or call the constructor of the RegExp object directly inside. For example, the above case can also be written as:

  1. //JavaScript原生正则写法
  2. .where({
  3. city:/州/i
  4. })
  5. //JavaScript调用RegExp对象的构造函数写法
  6. .where({
  7. city: new db.RegExp({
  8. regexp: "州",
  9. options: 'i',
  10. })
  11. })

Regular expressions for database queries also support template strings, for example, we can declare const cityname with "state" and then wrap the cityname variable with a template string:

  1. city: db.RegExp({
  2. regexp:`${cityname}`,
  3. options: 'i',
  4. })

Get started with simple regular expressions

The use of regular expressions is very complicated, knowledge of regular expressions can go to search for more details.

Technical documentation: Regular expressions

It is important to note that when database queries, you should avoid overusing regular expressions for complex matches, especially in scenarios where user access triggers more, and it is generally best to have a response time of less than 500ms for data queries, whether small or cloud-based.

New records and statistical records are added on the small terminal

Earlier we've covered the query method get for collection data requests, in addition to get queries, the methods requested are added, remove delete, update overwrite/update, count statistics, and watch listening, all of which are based on database collection references to Collegection, and then let's show you how to build on the number of new records and statistical records in College.

References to the database collection are queried for multiple records, which means that we can add, delete, change, check and other operations on N records, but at present it is not supported to do multiple records on the small terminal update and remove, only on the cloud function side of such operations.

Statistical recordscollection.count

Count the number of collection records or the number of result records corresponding to the statistical query statement. The performance of the small terminal and the cloud function side will be as follows: the small terminal: note that in the case of collection permission settings, a user can only count the number of records that they have read permissions on the cloud function side: because it belongs to the management side, it can count the number of records in the collection.

Technical documentation: Collection.count().

  1. const db = wx.cloud.database()
  2. const _ = db.command
  3. db.collection("china")
  4. .where({
  5. gdp: _.gt(3000)
  6. })
  7. .count().then(res => {
  8. console.log(res.total)
  9. })

field, orderBy, skip, limit are not valid for count, only where will affect the results of count, count will only return the number of records, will not return the query data.

New recordCollection.add

In the previous, we imported the data from the zhihu_daily into the collection of the data, and then we'll zhihu_daily new records.

Technical documentation: Collection.add

Use the developer tool to create a new zhihudaily page, and then enter the following code in zhihudaily.wxml to create a new button that binds the event handler to addDaily:

  1. <button bindtap="addDaily">新增日报数据</button>

Then enter the following code in the zhihudaily.js, call College.add in the event handler addDaily, add a record to the collection zhihu_daily, automatically generate _id from the background if the incoming record object does not have a _id field, and if _id is specified, it cannot conflict with the existing record.

  1. addDaily(){
  2. db.collection('zhihu_daily').add({
  3. data: {
  4. _id:"daily9718005",
  5. title: "元素,生生不息的宇宙诸子",
  6. images: [
  7. "https://pic4.zhimg.com/v2-3c5d866701650615f50ff4016b2f521b.jpg"
  8. ],
  9. id: 9718005,
  10. url: "https://daily.zhihu.com/story/9718005",
  11. image: "https://pic2.zhimg.com/v2-c6a33965175cf81a1b6e2d0af633490d.jpg",
  12. share_url: "http://daily.zhihu.com/story/9718005",
  13. body:"<p><strong><strong>谨以此文,纪念元素周期表发布 150 周年。</strong></strong></p>\r\n<p>地球,世界,和生活在这里的芸芸众生从何而来,这是每个人都曾有意无意思考过的问题。</p>\r\n<p>科幻小说家道格拉斯·亚当斯给了一个无厘头的答案,42;宗教也给出了诸神创世的虚构场景;</p>\r\n<p>最为恢弘的画面,则是由科学给出的,另一个意义上的<strong>生死轮回,一场属于元素的生死轮回</strong></p>"
  14. }
  15. })
  16. .then(res => {
  17. console.log(res)
  18. })
  19. .catch(console.error)
  20. }

Click on button for the new daily data and you'll see that the console-printed res object contains the new record_id the daily9718005 we set up for ourselves. Open the database tab for the cloud development console, open zhihu_daily collection, turn to the last page, and you'll see our new records.

_openid and collection permissions

Note that, different from the imported data, new records on the small terminal automatically add a _openid field whose value is equal to the user openid, and the value of the _openid is not allowed to be modified. A condition is automatically added when we change the permissions of the collection to be read-and-write-only, or read-only for everyone, read-to-write for only creators, and query or update records on the small terminal.

  1. .where({
  2. _openid:"当前用户的openid"
  3. })

So that's why, even though there's data in the collection, because of this condition, as long as there's no _openid or openid mismatch in the record, the record can't be queried.

Collection request method considerations

  1. getupdatecountremoveadd等都是请求,在小程序端可以有callbackpromise两种写法,但是在云函数端只能用promise,不能用callback。为了方便,建议大家统一使用promise的写法,也就是thencatch
  2. getupdatecountremoveadd请求不能在一个数据库引用里同时存在。比如不能又是get(),又是count()的,不能这么写:
  1. db.collection('china').where({
  2. _openid: 'xxx',
  3. }).get().count().add()

Records within the cloud function-side operations collection

The database is called on the cloud function side

We've covered how to call databases on the cloud function side in the Cloud Development Capabilities section, and the same is true here. C reate a new cloud function, chinadata, and then enter the following code at exports.main sync (event, context), note that const db s cloud.database(), wx. cloud.database(), database references on the cloud function side are different from the smaller terminals:

  1. const db = cloud.database()
  2. const _ = db.command
  3. return await db.collection("china")
  4. .where({
  5. gdp: _.gt(3000)
  6. })
  7. .field({
  8. _id: false,
  9. city: true,
  10. province: true,
  11. gdp: true
  12. })
  13. .orderBy('gdp', 'desc')
  14. .skip(0)
  15. .limit(10)
  16. .get()
  1. try/catch async错误处理
  2. async 函数中只要一个 await 出现 reject 状态,则后面的 await 都不会被执行。如果有多个 await 则可以将其都放在 try/catch 中。

Then right-click the chinadata cloud function root to open in the terminal, enter npm install, and then upload and deploy all files.

As we learned earlier, calling cloud functions can be done using on-premises debugging, cloud testing, and we can also call cloud functions on the small terminal to return data from the cloud function to the smaller terminal. Use the developer tool to enter the following code in chinadata.wxml, which is our universal click button to trigger the event handler:

  1. <button bindtap="callChinaData">调用chinadata云函数</button>

Then call the cloud function in the event handler, enter the getChinaData event .js in the chinadata event handler in the chinadata function:

  1. getChinaData() {
  2. wx.cloud.callFunction({
  3. name: 'chinadata',
  4. success: res => {
  5. console.log("云函数返回的数据",res.result.data)
  6. },
  7. fail: err => {
  8. console.error('云函数调用失败:', err)
  9. }
  10. })
  11. },

Clicking on the button to call the chinadata cloud function in the emulator will allow you to see the results of the query returned by the cloud function in the console, and you can render the results of the query to the small program page by settingData, which is not covered here.

Delete multiple data records

Based on the reference Torction of the database collection, we can first match the where statement query to multiple records of the relevant condition, and then call College.remove() to delete it. F ive query methods, skip and limit are not supported, field, orderBy are meaningless, only where conditions can be used to filter records. Once the data is deleted, it cannot be retrieved.

Technical documentation: Collection.remove().

We can modify the code in the previously built chinadata cloud function exports.main s async (event, context) to the following, i.e. delete all data from the province of province for Guangdong:

  1. return await db.collection('china')
  2. .where({
  3. province:"广东"
  4. })
  5. .remove()

Clicking on the button to call the chinadata cloud function in the emulator allows you to see the object returned by the cloud function in the console, which contains stats: s removed: 22, i.e. 22 data deleted.

Update multiple records collection.update

We can change the code in the previously built chinadata cloud function exports.main s async (event, context) to the following, that is, first query the record of the province of provance for Hubei, and update the record with a field English province name pro-en:

  1. return await db.collection('china')
  2. .where({
  3. province:"湖北"
  4. })
  5. .update({
  6. data: {
  7. "pro-en": "Hubei"
  8. },
  9. })

It should be noted here that the pro-en field was not previously available, through College.update not only to play an update role, but also to add the field in bulk and assign values, that is, when the record has the same field updated, not added;

If you want to add a _openid to the imported data, you can't do it with a cloud function, because the cloud function doesn't have a user's login state. Before we can add openid to a record, we need to call a cloud function on a small program, such as login to return openid, and then pass the value of openid to the chinadata cloud function.

Manipulate the field value of a single record doc

Earlier, we've looked at 5 methods for building query conditions based on collection references, as well as some request methods, and let's look at four request methods for referring to Document based on collection records: get a single record data Document.get (), delete a single record Document.remove (), update a single record Document.update(), R eplace updating a single record, Document.set(). In the same way as Action-based, the former addition and deletion check can be bulk-based, while Document-based is an operation of a single record.

Records in the query collection collection are often used to get lists of articles, information, goods, products, and so on, while field values in querying individual record docs are often used for details in those lists. If you need to add or censor field values for a record in development, it is recommended that you use the program to make a rule-based build when you create a record in order to make it easier for the program to find the corresponding record based on_id _id.

Query the field value of a single record doc

Each record in the collection has an _id field to uniquely flag a record, and _id's data format can be 2000 number numbers or string strings. T his _id can be customized, and a very long string is automatically generated when the imported or written record is not customized. The field field value of the query record doc is based _id value.

Technical documentation: Get a single record data Document.get().

For example, if we query the data of an article in a daily newspaper (that is, one of the records.js), we use the developer tool zhihudaily page's onLoad lifecycle function to enter the following code (db don't repeat the declaration):

  1. db.collection('zhihu_daily').doc("daily9718006")
  2. .get()
  3. .then(res => {
  4. console.log('单个记录的值',res.data)
  5. })
  6. .catch(err => {
  7. console.error(err)
  8. })
  9. },

If the data of the collection is imported, then _id is automatically generated, the automatically generated _id is the string, so doc uses single quotes (double quotes are also available Oh), if you customize the _id is the number type, such as the custom _id for 20191125, the query is doc (20191125), this is just the basic knowledge.

Delete a single record

Technical documentation: Delete a single record Document.remove().

  1. removeDaily(){
  2. db.collection('zhihu_daily').doc("daily9718006")
  3. .remove()
  4. .then(console.log)
  5. .catch(console.error)
  6. }

Update a single record

Technical documentation: Update a single record Document.update().

  1. updateDaily(){
  2. db.collection('zhihu_daily').doc("daily9718006")
  3. .update({
  4. data:{
  5. title: "【知乎日报】元素,生生不息的宇宙诸子",
  6. }
  7. })
  8. },

Replace the update record

Technical documentation: Replace and update a single record Document.set().

  1. setDaily(){
  2. db.collection('zhihu_daily').doc("daily9718006")
  3. .set({
  4. data: {
  5. "title": "为什么狗会如此亲近人类?",
  6. "images": [
  7. "https://pic4.zhimg.com/v2-4cab2fbf4fe9d487910a6f2c54ab3ed3.jpg"
  8. ],
  9. "id": 9717547,
  10. "url": "https://daily.zhihu.com/story/9717547",
  11. "image": "https://pic4.zhimg.com/v2-60f220ee6c5bf035d0eaf2dd4736342b.jpg",
  12. "share_url": "http://daily.zhihu.com/story/9717547",
  13. "body": "<p>让狗从凶猛的野兽变成忠实的爱宠,涉及了宏观与微观上的两层故事:我们如何在宏观上驯养了它们,以及这些驯养在生理层面究竟意味着什么。</p>\r\n<p><img class=\"content-image\" src=\"http://pic1.zhimg.com/70/v2-4147c4b02bf97e95d8a9f00727d4c184_b.jpg\" alt=\"\"></p>\r\n<p>狗是灰狼(Canis lupus)被人类驯养后形成的亚种,至少可以追溯到 1 万多年以前,是人类成功驯化的第一种动物。在这漫长的岁月里,人类的定向选择强烈改变了这个驯化亚种的基因频率,使它呈现出极高的多样性,尤其体现在生理形态上。</p>"
  14. }
  15. })
  16. }