golang操作Redis&Mysql&RabbitMQ方法

golang操作Redis&Mysql&RabbitMQ:

Reids

安装导入

go get github.com/garyburd/redigo/redis

import "github.com/garyburd/redigo/redis"

使用

连接

import "github.com/garyburd/redigo/redis"

func main() {

c, err := redis.Dial("tcp", "localhost:6379")

if err != nil {

fmt.Println("conn redis failed, err:", err)

return

}

defer c.Close()

}

set & get

_, err = c.Do("Set", "name", "nick")

if err != nil {

fmt.Println(err)

return

}

r, err := redis.String(c.Do("Get", "name"))

if err != nil {

fmt.Println(err)

return

}

fmt.Println(r)

mset & mget

批量设置

_, err = c.Do("MSet", "name", "nick", "age", "18")

if err != nil {

fmt.Println("MSet error: ", err)

return

}

r2, err := redis.Strings(c.Do("MGet", "name", "age"))

if err != nil {

fmt.Println("MGet error: ", err)

return

}

fmt.Println(r2)

hset & hget

hash操作

_, err = c.Do("HSet", "names", "nick", "suoning")

if err != nil {

fmt.Println("hset error: ", err)

return

}

r, err = redis.String(c.Do("HGet", "names", "nick"))

if err != nil {

fmt.Println("hget error: ", err)

return

}

fmt.Println(r)

expire

设置过期时间

_, err = c.Do("expire", "names", 5)

if err != nil {

fmt.Println("expire error: ", err)

return

}

lpush & lpop & llen

队列

// 队列

_, err = c.Do("lpush", "Queue", "nick", "dawn", 9)

if err != nil {

fmt.Println("lpush error: ", err)

return

}

for {

r, err = redis.String(c.Do("lpop", "Queue"))

if err != nil {

fmt.Println("lpop error: ", err)

break

}

fmt.Println(r)

}

r3, err := redis.Int(c.Do("llen", "Queue"))

if err != nil {

fmt.Println("llen error: ", err)

return

}

连接池

各参数的解释如下:

MaxIdle:最大的空闲连接数,表示即使没有redis连接时依然可以保持N个空闲的连接,而不被清除,随时处于待命状态。

MaxActive:最大的激活连接数,表示同时最多有N个连接

IdleTimeout:最大的空闲连接等待时间,超过此时间后,空闲连接将被关闭

pool := &redis.Pool{

MaxIdle:     16,

MaxActive:   1024,

IdleTimeout: 300,

Dial: func() (redis.Conn, error) {

return redis.Dial("tcp", "localhost:6379")

},

}

连接池例子:

package main

import (

"fmt"

"github.com/garyburd/redigo/redis"

)

var pool *redis.Pool

func init() {

pool = &redis.Pool{

MaxIdle:     16,

MaxActive:   1024,

IdleTimeout: 300,

Dial: func() (redis.Conn, error) {

return redis.Dial("tcp", "localhost:6379")

},

}

}

func main() {

c := pool.Get()

defer c.Close()

_, err := c.Do("Set", "name", "nick")

if err != nil {

fmt.Println(err)

return

}

r, err := redis.String(c.Do("Get", "name"))

if err != nil {

fmt.Println(err)

return

}

fmt.Println(r)

}

管道操作

请求/响应服务可以实现持续处理新请求,客户端可以发送多个命令到服务器而无需等待响应,最后在一次读取多个响应。

使用Send(),Flush(),Receive()方法支持管道化操作

Send向连接的输出缓冲中写入命令。

Flush将连接的输出缓冲清空并写入服务器端。

Recevie按照FIFO顺序依次读取服务器的响应。

func main() {

c, err := redis.Dial("tcp", "localhost:6379")

if err != nil {

fmt.Println("conn redis failed, err:", err)

return

}

defer c.Close()

c.Send("SET", "name1", "sss1")

c.Send("SET", "name2", "sss2")

c.Flush()

v, err := c.Receive()

fmt.Printf("v:%v,err:%v\n", v, err)

v, err = c.Receive()

fmt.Printf("v:%v,err:%v\n", v, err)

v, err = c.Receive()    // 夯住,一直等待

fmt.Printf("v:%v,err:%v\n", v, err)

}

Mysql

安装导入

go get "github.com/go-sql-driver/mysql"

go get "github.com/jmoiron/sqlx"

import (

_ "github.com/go-sql-driver/mysql"

"github.com/jmoiron/sqlx"

)

连接

import (

_ "github.com/go-sql-driver/mysql"

"github.com/jmoiron/sqlx"

)

var Db *sqlx.DB

func init() {

database, err := sqlx.Open("mysql", "root:@tcp(127.0.0.1:3306)/test")

if err != nil {

fmt.Println("open mysql failed,", err)

return

}

Db = database

}

建表

CREATE TABLE `person` (

`user_id` int(128) DEFAULT NULL,

`username` varchar(255) DEFAULT NULL,

`sex` varchar(16) DEFAULT NULL,

`email` varchar(128) DEFAULT NULL

) ENGINE=InnoDB DEFAULT CHARSET=utf8

(insert)

package main

import (

"fmt"

_ "github.com/go-sql-driver/mysql"

"github.com/jmoiron/sqlx"

)

type Person struct {

UserId   int    `db:"user_id"`

Username string `db:"username"`

Sex      string `db:"sex"`

Email    string `db:"email"`

}

var Db *sqlx.DB

func init() {

database, err := sqlx.Open("mysql", "root:@tcp(127.0.0.1:3306)/test")

if err != nil {

fmt.Println("open mysql failed,", err)

return

}

Db = database

}

func main() {

r, err := Db.Exec("insert into person(username, sex, email)values(?, ?, ?)", "suoning", "man", "suoning@net263.com")

if err != nil {

fmt.Println("exec failed, ", err)

return

}

id, err := r.LastInsertId()

if err != nil {

fmt.Println("exec failed, ", err)

return

}

fmt.Println("insert succ:", id)

}

(update)

package main

import (

"fmt"

_ "github.com/go-sql-driver/mysql"

"github.com/jmoiron/sqlx"

)

type Person struct {

UserId   int    `db:"user_id"`

Username string `db:"username"`

Sex      string `db:"sex"`

Email    string `db:"email"`

}

var Db *sqlx.DB

func init() {

database, err := sqlx.Open("mysql", "root:@tcp(127.0.0.1:3306)/test")

if err != nil {

fmt.Println("open mysql failed,", err)

return

}

Db = database

}

func main() {

_, err := Db.Exec("update person set user_id=? where username=?", 20170808, "suoning")

if err != nil {

fmt.Println("exec failed, ", err)

return

}

}

(select)

package main

import (

"fmt"

_ "github.com/go-sql-driver/mysql"

"github.com/jmoiron/sqlx"

)

type Person struct {

UserId   int    `db:"user_id"`

Username string `db:"username"`

Sex      string `db:"sex"`

Email    string `db:"email"`

}

type Place struct {

Country string `db:"country"`

City    string `db:"city"`

TelCode int    `db:"telcode"`

}

var Db *sqlx.DB

func init() {

database, err := sqlx.Open("mysql", "root:@tcp(127.0.0.1:3306)/test")

if err != nil {

fmt.Println("open mysql failed,", err)

return

}

Db = database

}

func main() {

var person []Person

err := Db.Select(&person, "select user_id, username, sex, email from person where user_id=?", 1)

if err != nil {

fmt.Println("exec failed, ", err)

return

}

fmt.Println("select succ:", person)

people := []Person{}

Db.Select(&people, "SELECT * FROM person ORDER BY user_id ASC")

fmt.Println(people)

jason, john := people[0], people[1]

fmt.Printf("%#v\n%#v", jason, john)

}

(delete)

package main

import (

"fmt"

_ "github.com/go-sql-driver/mysql"

"github.com/jmoiron/sqlx"

)

type Person struct {

UserId   int    `db:"user_id"`

Username string `db:"username"`

Sex      string `db:"sex"`

Email    string `db:"email"`

}

var Db *sqlx.DB

func init() {

database, err := sqlx.Open("mysql", "root:@tcp(127.0.0.1:3306)/test")

if err != nil {

fmt.Println("open mysql failed,", err)

return

}

Db = database

}

func main() {

_, err := Db.Exec("delete from person where username=? limit 1", "suoning")

if err != nil {

fmt.Println("exec failed, ", err)

return

}

fmt.Println("delete succ")

}

事务

package main

import (

"github.com/astaxie/beego/logs"

_ "github.com/go-sql-driver/mysql"

"github.com/jmoiron/sqlx"

)

var Db *sqlx.DB

func init() {

database, err := sqlx.Open("mysql", "root:@tcp(127.0.0.1:3306)/test")

if err != nil {

logs.Error("open mysql failed,", err)

return

}

Db = database

}

func main()  {

conn, err := Db.Begin()

if err != nil {

logs.Warn("DB.Begin failed, err:%v", err)

return

}

defer func() {

if err != nil {

conn.Rollback()

return

}

conn.Commit()

}()

// do something

}

RabbitMQ

安装

go get "github.com/streadway/amqp"

普通模式

生产者:

package main

import (    "fmt"

"log"

"os"

"strings"

"github.com/streadway/amqp"

"time")/*默认点对点模式*/func failOnError(err error, msg string) {    if err != nil {

log.Fatalf("%s: %s", msg, err)

panic(fmt.Sprintf("%s: %s", msg, err))

}

}

func main() {    // 连接

conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")

failOnError(err, "Failed to connect to RabbitMQ")

defer conn.Close()    // 打开一个并发服务器通道来处理消息

ch, err := conn.Channel()

failOnError(err, "Failed to open a channel")

defer ch.Close()    // 申明一个队列

q, err := ch.QueueDeclare(        "task_queue", // name

true,         // durable  持久性的,如果事前已经声明了该队列,不能重复声明

false,        // delete when unused

false,        // exclusive 如果是真,连接一断开,队列删除

false,        // no-wait

nil,          // arguments    )

failOnError(err, "Failed to declare a queue")

body := bodyFrom(os.Args)    // 发布

err = ch.Publish(        "",     // exchange 默认模式,exchange为空

q.Name,           // routing key 默认模式路由到同名队列,即是task_queue

false,  // mandatory

false,

amqp.Publishing{            // 持久性的发布,因为队列被声明为持久的,发布消息必须加上这个(可能不用),但消息还是可能会丢,如消息到缓存但MQ挂了来不及持久化。            DeliveryMode: amqp.Persistent,

ContentType:  "text/plain",

Body:         []byte(body),

})

failOnError(err, "Failed to publish a message")

log.Printf(" [x] Sent %s", body)

}

func bodyFrom(args []string) string {    var s string

if (len(args) < 2) || os.Args[1] == "" {

s = fmt.Sprintf("%s-%v","hello", time.Now())

} else {

s = strings.Join(args[1:], " ")

}    return s

}

消费者:

package main

import (

"bytes"

"fmt"

"github.com/streadway/amqp"

"log"

"time"

)

/*

默认点对点模式

工作方,多个,拿发布方的消息

*/

func failOnError(err error, msg string) {

if err != nil {

log.Fatalf("%s: %s", msg, err)

panic(fmt.Sprintf("%s: %s", msg, err))

}

}

func main() {

conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")

failOnError(err, "Failed to connect to RabbitMQ")

defer conn.Close()

ch, err := conn.Channel()

failOnError(err, "Failed to open a channel")

defer ch.Close()

// 指定队列!

q, err := ch.QueueDeclare(

"task_queue", // name

true,         // durable

false,        // delete when unused

false,        // exclusive

false,        // no-wait

nil,          // arguments

)

failOnError(err, "Failed to declare a queue")

// Fair dispatch 预取,每个工作方每次拿一个消息,确认后才拿下一次,缓解压力

err = ch.Qos(

1,     // prefetch count

0,     // prefetch size

false, // global

)

failOnError(err, "Failed to set QoS")

// 消费根据队列名

msgs, err := ch.Consume(

q.Name, // queue

"",     // consumer

false,  // auto-ack   设置为真自动确认消息

false,  // exclusive

false,  // no-local

false,  // no-wait

nil,    // args

)

failOnError(err, "Failed to register a consumer")

forever := make(chan bool)

go func() {

for d := range msgs {

log.Printf("Received a message: %s", d.Body)

dot_count := bytes.Count(d.Body, []byte("."))

t := time.Duration(dot_count)

time.Sleep(t * time.Second)

log.Printf("Done")

// 确认消息被收到!!如果为真的,那么同在一个channel,在该消息之前未确认的消息都会确认,适合批量处理

// 真时场景:每十条消息确认一次,类似

d.Ack(false)

}

}()

log.Printf(" [*] Waiting for messages. To exit press CTRL+C")

<-forever

}

订阅模式

订阅 生产者:

package main

import (

"fmt"

"github.com/streadway/amqp"

"log"

"os"

"strings"

"time"

)

/*

广播模式

发布方

*/

func failOnError(err error, msg string) {

if err != nil {

log.Fatalf("%s: %s", msg, err)

panic(fmt.Sprintf("%s: %s", msg, err))

}

}

func main() {

conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")

failOnError(err, "Failed to connect to RabbitMQ")

defer conn.Close()

ch, err := conn.Channel()

failOnError(err, "Failed to open a channel")

defer ch.Close()

// 默认模式有默认交换机,广播自己定义一个交换机,交换机可与队列进行绑定

err = ch.ExchangeDeclare(

"logs",   // name

"fanout", // type 广播模式

true,     // durable

false,    // auto-deleted

false,    // internal

false,    // no-wait

nil,      // arguments

)

failOnError(err, "Failed to declare an exchange")

body := bodyFrom(os.Args)

// 发布

err = ch.Publish(

"logs", // exchange 消息发送到交换机,这个时候没队列绑定交换机,消息会丢弃

"",     // routing key  广播模式不需要这个,它会把所有消息路由到绑定的所有队列

false,  // mandatory

false,  // immediate

amqp.Publishing{

ContentType: "text/plain",

Body:        []byte(body),

})

failOnError(err, "Failed to publish a message")

log.Printf(" [x] Sent %s", body)

}

func bodyFrom(args []string) string {

var s string

if (len(args) < 2) || os.Args[1] == "" {

s = fmt.Sprintf("%s-%v","hello", time.Now())

} else {

s = strings.Join(args[1:], " ")

}

return s

}

订阅 消费者:

package main

import (

"fmt"

"github.com/streadway/amqp"

"log"

)

/*

广播模式

订阅方

*/

func failOnError(err error, msg string) {

if err != nil {

log.Fatalf("%s: %s", msg, err)

panic(fmt.Sprintf("%s: %s", msg, err))

}

}

func main() {

conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")

failOnError(err, "Failed to connect to RabbitMQ")

defer conn.Close()

ch, err := conn.Channel()

failOnError(err, "Failed to open a channel")

defer ch.Close()

// 同样要申明交换机

err = ch.ExchangeDeclare(

"logs",   // name

"fanout", // type

true,     // durable

false,    // auto-deleted

false,    // internal

false,    // no-wait

nil,      // arguments

)

failOnError(err, "Failed to declare an exchange")

// 新建队列,这个队列没名字,随机生成一个名字

q, err := ch.QueueDeclare(

"",    // name

false, // durable

false, // delete when usused

true,  // exclusive  表示连接一断开,这个队列自动删除

false, // no-wait

nil,   // arguments

)

failOnError(err, "Failed to declare a queue")

// 队列和交换机绑定,即是队列订阅了发到这个交换机的消息

err = ch.QueueBind(

q.Name, // queue name  队列的名字

"",     // routing key  广播模式不需要这个

"logs", // exchange  交换机名字

false,

nil)

failOnError(err, "Failed to bind a queue")

// 开始消费消息,可开多个订阅方,因为队列是临时生成的,所有每个订阅方都能收到同样的消息

msgs, err := ch.Consume(

q.Name, // queue  队列名字

"",     // consumer

true,   // auto-ack  自动确认

false,  // exclusive

false,  // no-local

false,  // no-wait

nil,    // args

)

failOnError(err, "Failed to register a consumer")

forever := make(chan bool)

go func() {

for d := range msgs {

log.Printf(" [x] %s", d.Body)

}

}()

log.Printf(" [*] Waiting for logs. To exit press CTRL+C")

<-forever

}

RPC模式

RPC 应答方:

package main

import (

"fmt"

"log"

"strconv"

"github.com/streadway/amqp"

)

/*

RPC模式

应答方

*/

func failOnError(err error, msg string) {

if err != nil {

log.Fatalf("%s: %s", msg, err)

panic(fmt.Sprintf("%s: %s", msg, err))

}

}

func fib(n int) int {

if n == 0 {

return 0

} else if n == 1 {

return 1

} else {

return fib(n-1) + fib(n-2)

}

}

func main() {

conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")

failOnError(err, "Failed to connect to RabbitMQ")

defer conn.Close()

ch, err := conn.Channel()

failOnError(err, "Failed to open a channel")

defer ch.Close()

q, err := ch.QueueDeclare(

"rpc_queue", // name

false,       // durable

false,       // delete when usused

false,       // exclusive

false,       // no-wait

nil,         // arguments

)

failOnError(err, "Failed to declare a queue")

// 公平分发 没有这个则round-robbin

err = ch.Qos(

1,     // prefetch count

0,     // prefetch size

false, // global

)

failOnError(err, "Failed to set QoS")

// 消费,等待请求

msgs, err := ch.Consume(

q.Name, // queue

"",     // consumer

false,  // auto-ack

false,  // exclusive

false,  // no-local

false,  // no-wait

nil,    // args

)

failOnError(err, "Failed to register a consumer")

forever := make(chan bool)

go func() {

//请求来了

for d := range msgs {

n, err := strconv.Atoi(string(d.Body))

failOnError(err, "Failed to convert body to integer")

log.Printf(" [.] fib(%d)", n)

// 计算

response := fib(n)

// 回答

err = ch.Publish(

"",        // exchange

d.ReplyTo, // routing key

false,     // mandatory

false,     // immediate

amqp.Publishing{

ContentType:   "text/plain",

CorrelationId: d.CorrelationId,  //序列号

Body:          []byte(strconv.Itoa(response)),

})

failOnError(err, "Failed to publish a message")

// 确认回答完毕

d.Ack(false)

}

}()

log.Printf(" [*] Awaiting RPC requests")

<-forever

}

RPC 请求方:

package main

import (

"fmt"

"log"

"math/rand"

"os"

"strconv"

"strings"

"time"

"github.com/streadway/amqp"

)

/*

RPC模式

请求方

*/

func failOnError(err error, msg string) {

if err != nil {

log.Fatalf("%s: %s", msg, err)

panic(fmt.Sprintf("%s: %s", msg, err))

}

}

func randomString(l int) string {

bytes := make([]byte, l)

for i := 0; i < l; i++ {

bytes[i] = byte(randInt(65, 90))

}

return string(bytes)

}

func randInt(min int, max int) int {

return min + rand.Intn(max-min)

}

func fibonacciRPC(n int) (res int, err error) {

conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")

failOnError(err, "Failed to connect to RabbitMQ")

defer conn.Close()

ch, err := conn.Channel()

failOnError(err, "Failed to open a channel")

defer ch.Close()

// 队列声明

q, err := ch.QueueDeclare(

"",    // name

false, // durable

false, // delete when usused

true,  // exclusive 为真即连接断开就删除

false, // noWait

nil,   // arguments

)

failOnError(err, "Failed to declare a queue")

msgs, err := ch.Consume(

q.Name, // queue

"",     // consumer

true,   // auto-ack

false,  // exclusive   这个为真,服务器会认为这是该队列唯一的消费者

false,  // no-local

false,  // no-wait

nil,    // args

)

failOnError(err, "Failed to register a consumer")

corrId := randomString(32)

err = ch.Publish(

"",          // exchange

"rpc_queue", // routing key

false,       // mandatory

false,       // immediate

amqp.Publishing{

ContentType:   "text/plain",

CorrelationId: corrId,

ReplyTo:       q.Name,

Body:          []byte(strconv.Itoa(n)),

})

failOnError(err, "Failed to publish a message")

for d := range msgs {

if corrId == d.CorrelationId {

res, err = strconv.Atoi(string(d.Body))

failOnError(err, "Failed to convert body to integer")

break

}

}

return

}

func main() {

rand.Seed(time.Now().UTC().UnixNano())

n := bodyFrom(os.Args)

log.Printf(" [x] Requesting fib(%d)", n)

res, err := fibonacciRPC(n)

failOnError(err, "Failed to handle RPC request")

log.Printf(" [.] Got %d", res)

}

func bodyFrom(args []string) int {

var s string

if (len(args) < 2) || os.Args[1] == "" {

s = "30"

} else {

s = strings.Join(args[1:], " ")

}

n, err := strconv.Atoi(s)

failOnError(err, "Failed to convert arg to integer")

return n

}

本文来自php中文网的golang栏目:https://www.php.cn/be/go/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容