Go ORM 干啥的?

胖sir :接著,給你一個餡餅兒

兵長 : 來嘞!!
一篇來自ORM的整理筆記...

1 什么是ORM?為什么要?ORM?

什么是ORM ,即Object-Relationl Mapping,它的作?是在關系型數據庫和對象之間作?個映射,

這樣,我們在具體的 操作數據庫的時候,就不需要再去和復雜的SQL語句打交道,只要像平時操作對象?樣操作它就可以了 。

ORM解決的主要問題是對象關系的映射。域模型和關系模型分別是建?在概念模型的基礎上的。

  • 域模型是?向對 象的
  • 關系模型是?向關系的

?般情況下,?個持久化類和?個表對應,類的每個實例對應表中的?條記錄,

類的每個屬性對應表的每個字段。

ORM技術特點:

  • 提?了開發效率。

    由于ORM可以?動對Entity對象與數據庫中的Table進?字段與屬性的映射,所以我們實際 可能已經不需要?個專?的、龐?的數據訪問層。

  • ORM提供了對數據庫的映射,不?sql直接編碼,能夠像操作對象?樣從數據庫獲取數據。

ORM的缺點

ORM的缺點是會犧牲程序的執?效率和會固定思維模式。

從系統結構上來看,采?ORM的系統?般都是多層系統,系統的層次多了,效率就會降低。ORM是?種完全的 ?向對象的做法,??向對象的做法也會對性能產??定的影響。

2 ORM操作數據

package main

import (
    "fmt"
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
)

// UserInfo 用戶信息
//orm會默認根據結構體創建table , orm采用的是linux命名方式  即小寫加下劃線,且會在名字后面加 s
//會創建user_infos 表
type UserInfo struct {
    gorm.Model
    ID     uint
    Name   string
    Gender string
    Hobby  string
}

//  truncate table 表名
//  數據庫需要提前創建好  例如mygorm
//  parseTime是查詢結果是否?動解析為時間
//  loc是MySQL的時區設置
//  charset是編碼方式
func main() {
    fmt.Println("try open mysql connection....")
    db, err := gorm.Open("mysql", "root:123456@(localhost:3306)/mygorm?charset=utf8mb4&parseTime=True&loc=Local")
    if err != nil {
        panic(err)
    }
    fmt.Println("successful")
    defer db.Close()
    // 自動遷移
    //若該表不存在則創建該表,若該表存在且結構體發生變化則更新表結構
    db.AutoMigrate(&UserInfo{})
    u1 := UserInfo{gorm.Model{}, 1, "xiaozhu", "man", "playing"}
    // 創建記錄
    result := db.Create(&u1)
    fmt.Println("result:", result.RowsAffected)
    // 查詢
    var u = new(UserInfo)
    //查詢一條記錄
    db.First(u)
    fmt.Printf("First: %#v\n", u)
    //按照條件查詢
    var uu UserInfo
    db.Find(&uu, "name=?", "xiaozhu")
    fmt.Printf("Find: %#v\n", uu)
    // 更新
    db.Model(&uu).Update("hobby", "sing")
    // 刪除 , 此處刪除記錄,是不會將數據表中的數據刪除掉,而是deleted_at 會更新刪除時間
    db.Delete(&uu)
}

  • 使用gorm必須要先創建好數據庫
  • gorm會自動創建數據表,且表結構可以動態變化
  • gorm創建的表命名方式為 代碼中結構體命名的轉換, 例如 結構體命名為UserInfo,則table會命名為user_infos
  • gorm修改表結構非常的容易
  • gorm是完全面向對象的思想

3 模型定義

模型是標準的 struct,由 Go 的基本數據類型、實現了 ScannerValuer 接口的自定義類型及其指針或別名組成

例如:

type User struct {
  ID           uint
  Name         string
  Email        *string
  Age          uint8
  Birthday     *time.Time
  MemberNumber sql.NullString
  ActivatedAt  sql.NullTime
  CreatedAt    time.Time
  UpdatedAt    time.Time
}

自定義模型

遵循 GORM 已有的約定,可以減少您的配置和代碼量。

type User struct {
   gorm.Model   // 內嵌
   Name         string
   Age          sql.NullInt64
   Birthday     *time.Time
   Email        string  `gorm:"type:varchar(100);uniqueIndex"`
   Role         string  `gorm:"size:255"`        // 設置字段大小為255
   MemberNumber *string `gorm:"unique;not null"` // 設置會員號(member number)唯一并且不為空
   Num          int     `gorm:"AUTO_INCREMENT"`  // 設置 num 為自增類型
   Address      string  `gorm:"index:addr"`      // 給address字段創建名為addr的索引
   IgnoreMe     int     `gorm:"-"`               // 忽略本字段
}

字段標簽

聲明 model 時,tag 是可選的,GORM 支持以下 tag: tag 名大小寫不敏感,但建議使用 camelCase 風格

標簽名 說明
column 指定 db 列名
type 列數據類型,推薦使用兼容性好的通用類型,例如:所有數據庫都支持 bool、int、uint、float、string、time、bytes 并且可以和其他標簽一起使用,例如:not nullsize, autoIncrement… 像 varbinary(8) 這樣指定數據庫數據類型也是支持的。在使用指定數據庫數據類型時,它需要是完整的數據庫數據類型,如:MEDIUMINT UNSIGNED not NULL AUTO_INSTREMENT
size 指定列大小,例如:size:256
primaryKey 指定列為主鍵
unique 指定列為唯一
default 指定列的默認值
precision 指定列的精度
scale 指定列大小
not null 指定列為 NOT NULL
autoIncrement 指定列為自動增長
autoIncrementIncrement 自動步長,控制連續記錄之間的間隔
embedded 嵌套字段
embeddedPrefix 嵌入字段的列名前綴
autoCreateTime 創建時追蹤當前時間,對于 int 字段,它會追蹤秒級時間戳,您可以使用 nano/milli 來追蹤納秒、毫秒時間戳,例如:autoCreateTime:nano
autoUpdateTime 創建/更新時追蹤當前時間,對于 int 字段,它會追蹤秒級時間戳,您可以使用 nano/milli 來追蹤納秒、毫秒時間戳,例如:autoUpdateTime:milli
index 根據參數創建索引,多個字段使用相同的名稱則創建復合索引,查看 索引 獲取詳情
uniqueIndex index 相同,但創建的是唯一索引
check 創建檢查約束,例如 check:age > 13,查看 約束 獲取詳情
<- 設置字段寫入的權限, <-:create 只創建、<-:update 只更新、<-:false 無寫入權限、<- 創建和更新權限
-> 設置字段讀的權限,->:false 無讀權限
- 忽略該字段,- 無讀寫權限
comment 遷移時為字段添加注釋

關聯標簽

標簽 描述
foreignKey 指定當前模型的列作為連接表的外鍵
references 指定引用表的列名,其將被映射為連接表外鍵
polymorphic 指定多態類型,比如模型名
polymorphicValue 指定多態值、默認表名
many2many 指定連接表表名
joinForeignKey 指定連接表的外鍵列名,其將被映射到當前表
joinReferences 指定連接表的外鍵列名,其將被映射到引用表
constraint 關系約束,例如:OnUpdateOnDelete

4 主鍵、表名、列名的約定

主鍵(Primary Key)

GORM 默認會使?名為ID的字段作為表的主鍵。

 type User struct { 

 ID string // 名為`ID`的字段會默認作為表的主鍵 

 Name string 

 }
// 使?`AnimalID`作為主鍵 

type Animal struct { 

AnimalID int64 `gorm:"primary_key"` 

Name string 

Age int64 

} 

表名(Table Name)

表名默認就是結構體名稱的復數,例如:

type User struct {} // 默認表名是 `users` 

// 將 User 的表名設置為 `profiles` 

func (User) TableName() string { 

    return "profiles" 

} 

func (u User) TableName() string { 

    if u.Role == "admin" { 

        return "admin_users" 

    } else { 

        return "users" 

    } 

} 

// 禁?默認表名的復數形式,如果置為 true,則 `User` 的默認表名是 `user` 

db.SingularTable(true) 

也可以通過 Table() 指定表名:

// 使?User結構體創建名為`deleted_users`的表 

db.Table("deleted_users").CreateTable(&User{}) 

var deleted_users []User 

db.Table("deleted_users").Find(&deleted_users) 

// SELECT * FROM deleted_users; 

db.Table("deleted_users").Where("name = ?", "jinzhu").Delete() 

// DELETE FROM deleted_users WHERE name = 'jinzhu'; 

GORM還?持更改默認表名稱規則:

gorm.DefaultTableNameHandler = func (db *gorm.DB, defaultTableName string) string { 
     return "prefix_" + defaultTableName;
} 

列名(Column Name)

列名由字段名稱進?下劃線分割來?成

type User struct { 

ID uint // column name is `id` 

Name string // column name is `name` 

Birthday time.Time // column name is `birthday` 

CreatedAt time.Time // column name is `created_at` 

} 

可以使?結構體tag指定列名:

type Animal struct { 

AnimalId int64 `gorm:"column:beast_id"` // set column name to `beast_id` 

Birthday time.Time `gorm:"column:day_of_the_beast"` // set co lumn name to `day_of_the_beast` 

Age int64 `gorm:"column:age_of_the_beast"` // set column name to `age_of_the_beast` 

} 

時間戳跟蹤

CreatedAt

如果模型有 CreatedAt 字段,該字段的值將會是初次創建記錄的時間。

db.Create(&user) // `CreatedAt`將會是當前時間 

// 可以使?`Update`?法來改變`CreateAt`的值 

db.Model(&user).Update("CreatedAt", time.Now()) 

UpdatedAt

如果模型有 UpdatedAt 字段,該字段的值將會是每次更新記錄的時間。

db.Save(&user) // `UpdatedAt`將會是當前時間 

db.Model(&user).Update("name", "jinzhu") // `UpdatedAt`將會是當前時間 

DeletedAt

如果模型有 DeletedAt 字段,調? Delete 刪除該記錄時,將會設置 DeletedAt 字段為當前時間,?

不是直接將記錄從數據庫中刪除。

5 CURD

創建記錄

user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}

result := db.Create(&user) // 通過數據的指針來創建

user.ID             // 返回插入數據的主鍵
result.Error        // 返回 error
result.RowsAffected // 返回插入記錄的條數

批量插入

要有效地插入大量記錄,請將一個 slice 傳遞給 Create 方法。 將切片數據傳遞給 Create 方法,GORM 將生成一個單一的 SQL 語句來插入所有數據,并回填主鍵的值,鉤子方法也會被調用。

var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
db.Create(&users)

for _, user := range users {
  user.ID // 1,2,3
}

使用 CreateInBatches 創建時,你還可以指定創建的數量,例如:

var users = []User{{name: "jinzhu_1"}, ...., {Name: "jinzhu_10000"}}

// 數量為 100
db.CreateInBatches(users, 100)

默認值

您可以通過標簽 default 為字段定義默認值,如:

type User struct {
  ID   int64
  Name string `gorm:"default:galeone"`
  Age  int64  `gorm:"default:18"`
}

插入記錄到數據庫時,默認值 會被用于 填充值為 零值 的字段

查詢

檢索單個對象

GORM 提供了 FirstTakeLast 方法,以便從數據庫中檢索單個對象。當查詢數據庫時它添加了 LIMIT 1 條件,且沒有找到記錄時,它會返回 ErrRecordNotFound 錯誤

// 獲取第一條記錄(主鍵升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;

// 獲取一條記錄,沒有指定排序字段
db.Take(&user)
// SELECT * FROM users LIMIT 1;

// 獲取最后一條記錄(主鍵降序)
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;

result := db.First(&user)
result.RowsAffected // 返回找到的記錄數
result.Error        // returns error

// 檢查 ErrRecordNotFound 錯誤
errors.Is(result.Error, gorm.ErrRecordNotFound)

如果你想避免ErrRecordNotFound錯誤,你可以使用Find,比如db.Limit(1).Find(&user)Find方法可以接受struct和slice的數據。

First, Last方法將按主鍵排序查找第一/最后一條記錄,只有在用struct查詢或提供model value時才有效,如果當前model沒有定義主鍵,將按第一個字段排序,例如:

var user User
var users []User

// 可以
db.First(&user)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1

// 可以
result := map[string]interface{}{}
db.Model(&User{}).First(&result)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1

// 不行
result := map[string]interface{}{}
db.Table("users").First(&result)

// 但可以配合 Take 使用
result := map[string]interface{}{}
db.Table("users").Take(&result)

// 根據第一個字段排序
type Language struct {
  Code string
  Name string
}
db.First(&Language{})
// SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1

用主鍵檢索

如果主鍵是數值類型,也可以通過 內聯條件 傳入主鍵來檢索對象。如果主鍵是 string 類型,要小心避免 SQL 注入,查看 安全 獲取詳情

db.First(&user, 10)
// SELECT * FROM users WHERE id = 10;

db.First(&user, "10")
// SELECT * FROM users WHERE id = 10;

db.Find(&users, []int{1,2,3})
// SELECT * FROM users WHERE id IN (1,2,3);

如果主鍵是像 uuid 這樣的字符串,您需要這要寫:

db.First(&user, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a")
// SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a";

檢索全部對象

// 獲取全部記錄
result := db.Find(&users)
// SELECT * FROM users;

result.RowsAffected // 返回找到的記錄數,相當于 `len(users)`
result.Error        // returns error

條件

String 條件
// 獲取第一條匹配的記錄
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;

// 獲取全部匹配的記錄
db.Where("name <> ?", "jinzhu").Find(&users)
// SELECT * FROM users WHERE name <> 'jinzhu';

// IN
db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');

// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)
// SELECT * FROM users WHERE name LIKE '%jin%';

// AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;

// Time
db.Where("updated_at > ?", lastWeek).Find(&users)
// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';

// BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
// SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';

Struct & Map 條件

// Struct
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;

// Map
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;

// 主鍵切片條件
db.Where([]int64{20, 21, 22}).Find(&users)
// SELECT * FROM users WHERE id IN (20, 21, 22);

注意 當使用結構作為條件查詢時,GORM 只會查詢非零值字段。這意味著如果您的字段值為 0''false 或其他 零值,該字段不會被用于構建查詢條件,例如:

db.Where(&User{Name: "jinzhu", Age: 0}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu";

你可以使用 map 來構建查詢條件,它會使用所有的值,例如:

db.Where(map[string]interface{}{"Name": "jinzhu", "Age": 0}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 0;

或查看 指定結構體查詢字段 獲取詳情

指定結構體查詢字段

當使用結構體進行查詢時,你可以使用它的字段名或其 dbname 列名作為參數來指定查詢的字段,例如:

db.Where(&User{Name: "jinzhu"}, "name", "Age").Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 0;

db.Where(&User{Name: "jinzhu"}, "Age").Find(&users)
// SELECT * FROM users WHERE age = 0;

內聯條件

用法與 Where 類似

// SELECT * FROM users WHERE id = 23;
// 根據主鍵獲取記錄,如果是非整型主鍵
db.First(&user, "id = ?", "string_primary_key")
// SELECT * FROM users WHERE id = 'string_primary_key';

// Plain SQL
db.Find(&user, "name = ?", "jinzhu")
// SELECT * FROM users WHERE name = "jinzhu";

db.Find(&users, "name <> ? AND age > ?", "jinzhu", 20)
// SELECT * FROM users WHERE name <> "jinzhu" AND age > 20;

// Struct
db.Find(&users, User{Age: 20})
// SELECT * FROM users WHERE age = 20;

// Map
db.Find(&users, map[string]interface{}{"age": 20})
// SELECT * FROM users WHERE age = 20;

Not 條件

構建 NOT 條件,用法與 Where 類似

db.Not("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE NOT name = "jinzhu" ORDER BY id LIMIT 1;

// Not In
db.Not(map[string]interface{}{"name": []string{"jinzhu", "jinzhu 2"}}).Find(&users)
// SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");

// Struct
db.Not(User{Name: "jinzhu", Age: 18}).First(&user)
// SELECT * FROM users WHERE name <> "jinzhu" AND age <> 18 ORDER BY id LIMIT 1;

// 不在主鍵切片中的記錄
db.Not([]int64{1,2,3}).First(&user)
// SELECT * FROM users WHERE id NOT IN (1,2,3) ORDER BY id LIMIT 1;

Or 條件

db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';

// Struct
db.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2", Age: 18}).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);

// Map
db.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "jinzhu 2", "age": 18}).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);

您還可以查看高級查詢中的 分組條件,它被用于編寫復雜 SQL

選擇特定字段

選擇您想從數據庫中檢索的字段,默認情況下會選擇全部字段

db.Select("name", "age").Find(&users)
// SELECT name, age FROM users;

db.Select([]string{"name", "age"}).Find(&users)
// SELECT name, age FROM users;

db.Table("users").Select("COALESCE(age,?)", 42).Rows()
// SELECT COALESCE(age,'42') FROM users;

還可以看一看 智能選擇字段

Order

指定從數據庫檢索記錄時的排序方式

db.Order("age desc, name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;

// 多個 order
db.Order("age desc").Order("name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;

db.Clauses(clause.OrderBy{
  Expression: clause.Expr{SQL: "FIELD(id,?)", Vars: []interface{}{[]int{1, 2, 3}}, WithoutParentheses: true},
}).Find(&User{})
// SELECT * FROM users ORDER BY FIELD(id,1,2,3)

Limit & Offset

Limit 指定獲取記錄的最大數量 Offset 指定在開始返回記錄之前要跳過的記錄數量

db.Limit(3).Find(&users)
// SELECT * FROM users LIMIT 3;

// 通過 -1 消除 Limit 條件
db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
// SELECT * FROM users LIMIT 10; (users1)
// SELECT * FROM users; (users2)

db.Offset(3).Find(&users)
// SELECT * FROM users OFFSET 3;

db.Limit(10).Offset(5).Find(&users)
// SELECT * FROM users OFFSET 5 LIMIT 10;

// 通過 -1 消除 Offset 條件
db.Offset(10).Find(&users1).Offset(-1).Find(&users2)
// SELECT * FROM users OFFSET 10; (users1)
// SELECT * FROM users; (users2)

查看 Pagination 學習如何寫一個分頁器

Group & Having

type result struct {
  Date  time.Time
  Total int
}

db.Model(&User{}).Select("name, sum(age) as total").Where("name LIKE ?", "group%").Group("name").First(&result)
// SELECT name, sum(age) as total FROM `users` WHERE name LIKE "group%" GROUP BY `name`


db.Model(&User{}).Select("name, sum(age) as total").Group("name").Having("name = ?", "group").Find(&result)
// SELECT name, sum(age) as total FROM `users` GROUP BY `name` HAVING name = "group"

rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Rows()
for rows.Next() {
  ...
}

rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Rows()
for rows.Next() {
  ...
}

type Result struct {
  Date  time.Time
  Total int64
}
db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Scan(&results)

Distinct

從模型中選擇不相同的值

db.Distinct("name", "age").Order("name, age desc").Find(&results)

Distinct 也可以配合 Pluck, Count 使用

Joins

指定 Joins 條件

type result struct {
  Name  string
  Email string
}
db.Model(&User{}).Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&result{})
// SELECT users.name, emails.email FROM `users` left join emails on emails.user_id = users.id

rows, err := db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Rows()
for rows.Next() {
  ...
}

db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&results)

// 帶參數的多表連接
db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "jinzhu@example.org").Joins("JOIN credit_cards ON credit_cards.user_id = users.id").Where("credit_cards.number = ?", "411111111111").Find(&user)

Joins 預加載

您可以使用 Joins 實現單條 SQL 預加載關聯記錄,例如:

db.Joins("Company").Find(&users)
// SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name` FROM `users` LEFT JOIN `companies` AS `Company` ON `users`.`company_id` = `Company`.`id`;

Scan

Scan 結果至 struct,用法與 Find 類似

type Result struct {
  Name string
  Age  int
}

var result Result
db.Table("users").Select("name", "age").Where("name = ?", "Antonio").Scan(&result)

// 原生 SQL
db.Raw("SELECT name, age FROM users WHERE name = ?", "Antonio").Scan(&result)

處理錯誤

GORM 的錯誤處理與常見的 Go 代碼不同,因為 GORM 提供的是鏈式 API。

如果遇到任何錯誤,GORM 會設置 *gorm.DBError 字段,您需要像這樣檢查它:

if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
  // 處理錯誤...
}

或者

if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil {
  // 處理錯誤...
}

ErrRecordNotFound

FirstLastTake 方法找不到記錄時,GORM 會返回 ErrRecordNotFound 錯誤。如果發生了多個錯誤,你可以通過 errors.Is 判斷錯誤是否為 ErrRecordNotFound,例如:

// 檢查錯誤是否為 RecordNotFound
err := db.First(&user, 100).Error
errors.Is(err, gorm.ErrRecordNotFound)

技術是開放的,我們的心態,更應是開放的。擁抱變化,向陽而生,努力向前行。

更多高級用法和細節 可以查看 中文GROM網站

作者:小魔童哪吒

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,835評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,676評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,730評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,118評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,873評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,266評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,330評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,482評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,036評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,846評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,025評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,575評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,279評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,684評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,953評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,751評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,016評論 2 375