├── .idea ├── .gitignore ├── modules.xml ├── shop-seckill.iml └── vcs.xml ├── README.md ├── conf └── app.conf ├── controllers ├── common.go └── order.go ├── go.mod ├── go.sum ├── lastupdate.tmp ├── main.go ├── models ├── order.go ├── orderItem.go └── productSku.go ├── mq └── seckill │ ├── main.go │ └── seckill ├── routers ├── commentsRouter_controllers.go └── router.go ├── seckill.sql ├── services ├── mq │ └── Mq.go └── redis │ └── Redis.go ├── shop-seckill └── tests └── default_test.go /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # 默认忽略的文件 2 | /shelf/ 3 | /workspace.xml 4 | # 数据源本地存储已忽略文件 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # 基于编辑器的 HTTP 客户端请求 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/shop-seckill.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | beego 秒杀 2 | =============== 3 | 4 | ## 项目环境 5 | 6 | * golang语言框架 beego1.12.1 7 | * 消息队列中间件 RabbitMQ 8 | * 秒杀商品缓存:Redis 9 | * 秒杀接口压测工具:JMeter 10 | 11 | 12 | 13 | ## 业务梳理 14 | ##### 1. 预热缓存数据,把秒杀商品的 库存和秒杀时间 存入缓存中。 15 | ##### 2. 秒杀接口提交地址字段address 和 商品sku sku_id,并进行数据校验 16 | ##### 3. 在缓存里校验秒杀商品是否存在、是否有库存、是否在秒杀时间内 17 | ##### 4. 在缓存里判断用户是否已经秒杀过 18 | ##### 5. 把用户id、sku_id、address写入RabbitMQ的消息队列 19 | ##### 6:处理消息使用RabbitMQ的work工作模式,一个生成者多个消费者,用于处理在短时间的HTTP请求中无法处理的复杂任务。 20 | ##### 7:处理消息,RabbitMQ消息持久化和重复消息处理 21 | ##### 8:校验商品库存 高并发下 redis还在减库存, 这一步再利用redis原子性校验库存,避免库存不足走到数据库操作耗性能。 22 | ##### 9:MySQL事务秒杀下单:减库存(乐观锁,防止库存超卖)、订单表、订单子表。 23 | ##### 10:更新redis缓存:秒杀商品库存缓存、用户下单缓存 24 | 25 | 26 | ## JMeter压测 27 | ##### 1:安装JMeter进行压测 28 | ##### 2:在进行php-fpm进程数调优、mysql连接数调优、开启opcache, 同等配置情况下还是远远及go版下的秒杀 29 | 30 | -------------------------------------------------------------------------------- /conf/app.conf: -------------------------------------------------------------------------------- 1 | appname = shop-seckill 2 | httpport = 8082 3 | runmode = dev 4 | autorender = false 5 | copyrequestbody = true 6 | EnableDocs = true 7 | sqlconn = 8 | 9 | 10 | [dev] 11 | defaultdb = root:root@tcp(127.0.0.1:3306)/seckill?charset=utf8 12 | redisdb = 127.0.0.1:6379 13 | mqhost = amqp://guest:guest@127.0.0.1:5672/ 14 | 15 | [prod] 16 | defaultdb = root:zhjaxxxxxx@tcp(xxx.xxx.xxx.xxx:3306)/fyouku?charset=utf8 17 | redisdb = 127.0.0.1:6379 18 | mqhost = amqp://guest:guest@127.0.0.1:5672/ -------------------------------------------------------------------------------- /controllers/common.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | 7 | "github.com/astaxie/beego" 8 | ) 9 | 10 | type CommonController struct { 11 | beego.Controller 12 | } 13 | 14 | type JsonStruct struct { 15 | Code int `json:"code"` 16 | Msg interface{} `json:"msg"` 17 | Items interface{} `json:"items"` 18 | Count int64 `json:"count"` 19 | } 20 | 21 | func ReturnSuccess(code int, msg interface{}, items interface{}, count int64) (json *JsonStruct) { 22 | json = &JsonStruct{Code: code, Msg: msg, Items: items, Count: count} 23 | return 24 | } 25 | 26 | func ReturnError(code int, msg interface{}) (json *JsonStruct) { 27 | json = &JsonStruct{Code: code, Msg: msg} 28 | return 29 | } 30 | 31 | 32 | func DateFormat(times int64) string { 33 | videoTime := time.Unix(times, 0) 34 | return videoTime.Format("2006-01-02") 35 | } 36 | 37 | 38 | func RandString(length int) string { 39 | var source = rand.NewSource(time.Now().UnixNano()) 40 | 41 | const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 42 | b := make([]byte, length) 43 | for i := range b { 44 | b[i] = charset[source.Int63()%int64(len(charset))] 45 | } 46 | return string(b) 47 | } -------------------------------------------------------------------------------- /controllers/order.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/astaxie/beego" 7 | "github.com/garyburd/redigo/redis" 8 | "math/rand" 9 | "shop-seckill/models" 10 | "shop-seckill/services/mq" 11 | redisClient "shop-seckill/services/redis" 12 | "strconv" 13 | "time" 14 | ) 15 | 16 | // Operations about Users 17 | type OrderController struct { 18 | beego.Controller 19 | } 20 | 21 | 22 | //@router /order/seckill [*] 23 | func (this *OrderController) Seckill() { 24 | skuId, _ := this.GetInt("sku_id") 25 | address := this.GetString("address") 26 | 27 | if skuId == 0 { 28 | this.Data["json"] = ReturnError(4001, "sku id不能为空") 29 | this.ServeJSON() 30 | return 31 | } 32 | if address == "" { 33 | this.Data["json"] = ReturnError(4002, "收货地址不能为空") 34 | this.ServeJSON() 35 | return 36 | } 37 | 38 | redisConn := redisClient.PoolConnect() 39 | defer redisConn.Close() 40 | 41 | //1:在缓存中校验库存 42 | stockKey := "go_stock:" + strconv.Itoa(skuId) 43 | stock, err := redis.Int(redisConn.Do("get", stockKey)) 44 | if err != nil { 45 | this.Data["json"] = ReturnError(4003, "该商品不存在") 46 | this.ServeJSON() 47 | return 48 | } 49 | if stock < 1 { 50 | this.Data["json"] = ReturnError(4005, "该商品库存不足") 51 | this.ServeJSON() 52 | return 53 | } 54 | 55 | //2:在缓存中检验秒杀是否开始 56 | xt := ExpireTime{} 57 | expire, err := redis.String(redisConn.Do("get", "go_expire:1")) 58 | if err != nil { 59 | this.Data["json"] = ReturnError(4006, "商品秒杀时间缓存不存在") 60 | this.ServeJSON() 61 | return 62 | 63 | } else { 64 | json.Unmarshal([]byte(expire), &xt) 65 | start := xt.Start 66 | end := xt.End 67 | 68 | local, _ := time.LoadLocation("Local") 69 | startTime, _ := time.ParseInLocation("2006-01-02 15:04:05", start, local) 70 | endTime, _ := time.ParseInLocation("2006-01-02 15:04:05", end, local) 71 | now := time.Now() 72 | if startTime.After(now) { 73 | this.Data["json"] = ReturnError(4007, "秒杀还未开始") 74 | this.ServeJSON() 75 | return 76 | } 77 | if endTime.Before(now) { 78 | this.Data["json"] = ReturnError(4007, "秒杀还未开始") 79 | this.ServeJSON() 80 | return 81 | } 82 | 83 | } 84 | 85 | //3:在缓存中检验该用户是否秒杀过 86 | userId := rand.Intn(99) + 1 87 | usrOrderKey := "go_user_order_" + strconv.Itoa(skuId) + ":" + strconv.Itoa(userId) 88 | order, err := redis.String(redisConn.Do("get", usrOrderKey)) 89 | if err == nil { 90 | this.Data["json"] = ReturnError(4008, "该用户已经下过单"+order) 91 | this.ServeJSON() 92 | return 93 | } 94 | 95 | //4:秒杀入队 96 | msg := &MsgData{} 97 | msg.TaskName = "seckill_order" 98 | msg.UserId = userId 99 | msg.SkuId = skuId 100 | msg.Address = address 101 | msg.Time = time.Now().Format("2006-01-02 15:04:05") 102 | msgStr, _ := json.Marshal(msg) 103 | 104 | //go func() { 105 | mq.Publish("", "go_seckill", string(msgStr)) 106 | //}() 107 | 108 | this.Data["json"] = ReturnSuccess(2000, "秒杀中", nil, 0) 109 | this.ServeJSON() 110 | return 111 | } 112 | 113 | type MsgData struct { 114 | TaskName string `json:"task_name"` 115 | UserId int `json:"user_id"` 116 | SkuId int `json:"sku_id"` 117 | Address string `json:"address"` 118 | Time string `json:"time"` 119 | } 120 | 121 | type ExpireTime struct { 122 | Start string `json:"start"` 123 | End string `json:"end"` 124 | } 125 | 126 | // @router /cache/set [*] 127 | func (this *OrderController) Set() { 128 | redisConn := redisClient.PoolConnect() 129 | defer redisConn.Close() 130 | redisConn.Do("set", "go_stock:1", 10) 131 | 132 | xt := &ExpireTime{} 133 | xt.Start = "2021-01-15 00:00:00" 134 | xt.End = "2021-02-15 00:00:00" 135 | data, _ := json.Marshal(xt) 136 | redisConn.Do("set", "go_expire:1", string(data)) 137 | 138 | 139 | this.Ctx.WriteString("设置缓存成功") 140 | } 141 | 142 | // @router /cache/get [*] 143 | func (this *OrderController) Get() { 144 | redisConn := redisClient.PoolConnect() 145 | defer redisConn.Close() 146 | 147 | xt := ExpireTime{} 148 | expire, err := redis.String(redisConn.Do("get", "go_expire:1")) 149 | if err != nil { 150 | this.Ctx.WriteString("商品秒杀时间缓存不存在") 151 | } else { 152 | 153 | err := json.Unmarshal([]byte(expire), &xt) 154 | fmt.Println(err) 155 | fmt.Println(xt.Start) 156 | fmt.Println(xt.End) 157 | 158 | } 159 | 160 | stock, err := redis.String(redisConn.Do("get", "go_stock:1")) 161 | if err != nil { 162 | this.Ctx.WriteString("商品sku 库存缓存key 不存在") 163 | } else { 164 | this.Ctx.WriteString(stock) 165 | } 166 | 167 | timeStr := time.Now().Format("2006-01-02 15:04:05") 168 | fmt.Println(timeStr) 169 | 170 | skuInfo, err := models.GetSkuInfo(1) 171 | if err == nil { 172 | fmt.Println(skuInfo.Stock) 173 | } else { 174 | fmt.Println("没有sku") 175 | fmt.Println(err) 176 | } 177 | 178 | //res, err := models.UpdateStock(1) 179 | //if err == nil { 180 | // fmt.Println("mysql row affected nums: ", res) 181 | // 182 | //} else { 183 | // fmt.Println("更新库存失败") 184 | // fmt.Println(err) 185 | //} 186 | // 187 | //id,err := models.SaveOrder("golang3567891qwqw2",2, "广州") 188 | //if err == nil{ 189 | // err := models.SaveItem(id,1) 190 | // if err != nil{ 191 | // fmt.Println(err) 192 | // } 193 | //}else{ 194 | // fmt.Println(err) 195 | //} 196 | 197 | 198 | } 199 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module shop-seckill 2 | 3 | go 1.15 4 | 5 | require github.com/astaxie/beego v1.12.1 6 | 7 | require ( 8 | github.com/garyburd/redigo v1.6.2 9 | github.com/go-sql-driver/mysql v1.4.1 10 | github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect 11 | github.com/smartystreets/goconvey v1.6.4 12 | github.com/streadway/amqp v1.0.0 13 | google.golang.org/appengine v1.6.7 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= 2 | github.com/OwnLocal/goes v1.0.0/go.mod h1:8rIFjBGTue3lCU0wplczcUgt9Gxgrkkrw7etMIcn8TM= 3 | github.com/astaxie/beego v1.12.1 h1:dfpuoxpzLVgclveAXe4PyNKqkzgm5zF4tgF2B3kkM2I= 4 | github.com/astaxie/beego v1.12.1/go.mod h1:kPBWpSANNbSdIqOc8SUL9h+1oyBMZhROeYsXQDbidWQ= 5 | github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ= 6 | github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU= 7 | github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= 8 | github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE= 9 | github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= 10 | github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U= 11 | github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c= 12 | github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs= 13 | github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY= 14 | github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= 15 | github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk= 16 | github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= 17 | github.com/garyburd/redigo v1.6.2 h1:yE/pwKCrbLpLpQICzYTeZ7JsTA/C53wFTJHaEtRqniM= 18 | github.com/garyburd/redigo v1.6.2/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= 19 | github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= 20 | github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= 21 | github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 22 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 23 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 24 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 25 | github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= 26 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 27 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 28 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 29 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 30 | github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= 31 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 32 | github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= 33 | github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 34 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 35 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 36 | github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 h1:DAYUYH5869yV94zvCES9F51oYtN5oGlwjxJJz7ZCnik= 37 | github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg= 38 | github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= 39 | github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg= 40 | github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA= 41 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= 42 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 43 | github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= 44 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 45 | github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE= 46 | github.com/streadway/amqp v1.0.0 h1:kuuDrUJFZL1QYL9hUNuCxNObNzB0bV/ZG5jV3RWAQgo= 47 | github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= 48 | github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= 49 | github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc= 50 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 51 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= 52 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 53 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 54 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 55 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 56 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 57 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= 58 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 59 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 60 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 61 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 62 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 63 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 64 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 65 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 66 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 67 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 68 | golang.org/x/tools v0.0.0-20200117065230-39095c1d176c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 69 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 70 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= 71 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 72 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 73 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 74 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 75 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 76 | -------------------------------------------------------------------------------- /lastupdate.tmp: -------------------------------------------------------------------------------- 1 | {"/Users/wangqun/go/src/shop-seckill/controllers":1610978207470366919} -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/astaxie/beego/orm" 5 | _ "shop-seckill/routers" 6 | 7 | "github.com/astaxie/beego" 8 | _ "github.com/go-sql-driver/mysql" 9 | ) 10 | 11 | func main() { 12 | defaultdb := beego.AppConfig.String("defaultdb") 13 | orm.RegisterDriver("mysql", orm.DRMySQL) 14 | orm.RegisterDataBase("default", "mysql", defaultdb, 30, 30) 15 | 16 | if beego.BConfig.RunMode == "dev" { 17 | beego.BConfig.WebConfig.DirectoryIndex = true 18 | beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger" 19 | } 20 | beego.Run() 21 | } 22 | -------------------------------------------------------------------------------- /models/order.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/astaxie/beego/orm" 5 | "time" 6 | ) 7 | 8 | type Orders struct{ 9 | Id int 10 | No string 11 | UserId int 12 | Address string 13 | TotalAmount float32 14 | CreatedAt string 15 | } 16 | 17 | 18 | 19 | func init(){ 20 | orm.RegisterModel(new(Orders)) 21 | } 22 | 23 | func SaveOrder( no string, userId int, address string) (int64, error){ 24 | var ( 25 | err error 26 | order Orders 27 | ) 28 | o := orm.NewOrm() 29 | order.No = no 30 | order.Address = address 31 | order.UserId = userId 32 | order.TotalAmount= 1.0 33 | order.CreatedAt = time.Now().Format("2006-01-02 15:04:05") 34 | 35 | id,err := o.Insert(&order) 36 | 37 | return id,err 38 | } 39 | 40 | -------------------------------------------------------------------------------- /models/orderItem.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/astaxie/beego/orm" 5 | ) 6 | 7 | type OrderItems struct{ 8 | Id int 9 | OrderId int64 10 | ProductId int 11 | ProductSkuId int 12 | Amount int 13 | Price float32 14 | } 15 | 16 | 17 | 18 | func init(){ 19 | orm.RegisterModel(new(OrderItems)) 20 | } 21 | 22 | func SaveItem( orderId int64, skuId int) error{ 23 | var ( 24 | err error 25 | item OrderItems 26 | ) 27 | o := orm.NewOrm() 28 | item.OrderId = orderId 29 | item.ProductId = 1 30 | item.ProductSkuId = skuId 31 | item.Amount = 1 32 | item.Price = 1.0 33 | _,err = o.Insert(&item) 34 | 35 | return err 36 | } 37 | 38 | -------------------------------------------------------------------------------- /models/productSku.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/astaxie/beego/orm" 5 | ) 6 | 7 | type ProductSku struct{ 8 | Id int 9 | Title string 10 | Stock int 11 | } 12 | 13 | type SkuInfo struct{ 14 | Id int `json:"id"` 15 | Stock int `json:"stock"` 16 | } 17 | 18 | func init(){ 19 | orm.RegisterModel(new(ProductSku)) 20 | } 21 | 22 | func GetSkuInfo(skuId int) (SkuInfo, error){ 23 | o := orm.NewOrm() 24 | var sku SkuInfo 25 | err := o.Raw("select id,stock from product_skus where id=? limit 1", skuId).QueryRow(&sku) 26 | return sku,err 27 | } 28 | 29 | func UpdateStock(skuId int) (int64, error){ 30 | var ( 31 | err error 32 | ) 33 | o := orm.NewOrm() 34 | res,err := o.Raw("update product_skus set stock = stock - 1 where stock >= 1 and id = ?", skuId).Exec() 35 | if err == nil { 36 | num, _ := res.RowsAffected() 37 | return num,err 38 | }else{ 39 | return 0,err 40 | } 41 | } -------------------------------------------------------------------------------- /mq/seckill/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/astaxie/beego" 7 | "github.com/astaxie/beego/orm" 8 | "github.com/garyburd/redigo/redis" 9 | _ "github.com/go-sql-driver/mysql" 10 | "shop-seckill/controllers" 11 | "shop-seckill/services/mq" 12 | redisClient "shop-seckill/services/redis" 13 | "strconv" 14 | "time" 15 | ) 16 | 17 | 18 | func main(){ 19 | beego.LoadAppConfig("ini", "../../conf/app.conf") 20 | defaultdb := beego.AppConfig.String("defaultdb") 21 | orm.RegisterDriver("mysql", orm.DRMySQL) 22 | orm.RegisterDataBase("default", "mysql", defaultdb, 30, 30) 23 | mq.Consumer("", "go_seckill", callback) 24 | } 25 | 26 | func callback(s string){ 27 | type Data struct{ 28 | TaskName string `json:"task_name"` 29 | UserId int `json:"user_id"` 30 | SkuId int `json:"sku_id"` 31 | Address string `json:"address"` 32 | Time string `json:"time"` 33 | } 34 | var data Data 35 | err := json.Unmarshal([]byte(s), &data) 36 | 37 | if err == nil{ 38 | 39 | redisConn := redisClient.PoolConnect() 40 | defer redisConn.Close() 41 | 42 | //校验库存 43 | stockKey := "go_stock:" + strconv.Itoa(data.SkuId) 44 | 45 | stock, _ := redis.Int(redisConn.Do("get", stockKey)) 46 | if stock < 1{ 47 | fmt.Println("库存不足") 48 | return 49 | } 50 | //skuInfo,err := models.GetSkuInfo(data.SkuId) 51 | //if err == nil{ 52 | // fmt.Println(skuInfo.Stock) 53 | // if skuInfo.Stock < 1 { 54 | // redisConn.Do("del", "go_stock:"+strconv.Itoa(data.SkuId)) 55 | // fmt.Println("库存不足") 56 | // return 57 | // } 58 | // 59 | //}else{ 60 | // fmt.Println("没有sku"+strconv.Itoa(data.SkuId)) 61 | // return 62 | //} 63 | 64 | 65 | no := controllers.RandString(21) 66 | now := time.Now().Format("2006-01-02 15:04:05") 67 | 68 | o := orm.NewOrm() 69 | o.Begin() 70 | 71 | res1,err1 := o.Raw("update product_skus set stock = stock - 1 where stock >= 1 and id = ?", data.SkuId).Exec() 72 | updateRow,_ := res1.RowsAffected() 73 | 74 | res2,err2 := o.Raw("INSERT INTO orders (`no`, `user_id`, `address`, `total_amount`,`created_at`) VALUES (?, ?, ?, ?,?)", 75 | no, data.UserId, data.Address, 1.0, now).Exec() 76 | orderId,err4 := res2.LastInsertId() 77 | 78 | _,err3 := o.Raw("INSERT INTO order_items (`price`, `product_id`, `product_sku_id`, `amount`,`order_id`) VALUES (?, ?, ?, ?,?)", 79 | 1.0, 1, data.SkuId, 1, orderId).Exec() 80 | 81 | 82 | if(err1 != nil || updateRow<1 || err2 != nil || err4 != nil || err3 !=nil ){ 83 | fmt.Println("事务回滚") 84 | o.Rollback() 85 | }else{ 86 | o.Commit() 87 | //redis库存更新 88 | stockKey := "go_stock:" + strconv.Itoa(data.SkuId) 89 | redisConn.Do("decr", stockKey) 90 | 91 | //下单成功用户存入缓存 92 | usrOrderKey := "go_user_order_" + strconv.Itoa(data.SkuId) + ":" + strconv.Itoa(data.UserId) 93 | redisConn.Do("set", usrOrderKey, orderId) 94 | } 95 | 96 | } 97 | fmt.Printf("msg is :%s\n", s) 98 | } 99 | type Orders struct{ 100 | No string 101 | UserId int 102 | Address string 103 | TotalAmount float32 104 | CreatedAt string 105 | } 106 | 107 | type OrderItems struct{ 108 | OrderId int64 109 | ProductId int 110 | ProductSkuId int 111 | Amount int 112 | Price float32 113 | } -------------------------------------------------------------------------------- /mq/seckill/seckill: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KingWQ/seckill-beego/59b31512f147367dc472e6b5b284ad25af389f52/mq/seckill/seckill -------------------------------------------------------------------------------- /routers/commentsRouter_controllers.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | "github.com/astaxie/beego/context/param" 6 | ) 7 | 8 | func init() { 9 | 10 | beego.GlobalControllerRouter["shop-seckill/controllers:OrderController"] = append(beego.GlobalControllerRouter["shop-seckill/controllers:OrderController"], 11 | beego.ControllerComments{ 12 | Method: "Get", 13 | Router: `/cache/get`, 14 | AllowHTTPMethods: []string{"*"}, 15 | MethodParams: param.Make(), 16 | Filters: nil, 17 | Params: nil}) 18 | 19 | beego.GlobalControllerRouter["shop-seckill/controllers:OrderController"] = append(beego.GlobalControllerRouter["shop-seckill/controllers:OrderController"], 20 | beego.ControllerComments{ 21 | Method: "Set", 22 | Router: `/cache/set`, 23 | AllowHTTPMethods: []string{"*"}, 24 | MethodParams: param.Make(), 25 | Filters: nil, 26 | Params: nil}) 27 | 28 | beego.GlobalControllerRouter["shop-seckill/controllers:OrderController"] = append(beego.GlobalControllerRouter["shop-seckill/controllers:OrderController"], 29 | beego.ControllerComments{ 30 | Method: "Seckill", 31 | Router: `/order/seckill`, 32 | AllowHTTPMethods: []string{"*"}, 33 | MethodParams: param.Make(), 34 | Filters: nil, 35 | Params: nil}) 36 | 37 | } 38 | -------------------------------------------------------------------------------- /routers/router.go: -------------------------------------------------------------------------------- 1 | // @APIVersion 1.0.0 2 | // @Title beego Test API 3 | // @Description beego has a very cool tools to autogenerate documents for your API 4 | // @Contact astaxie@gmail.com 5 | // @TermsOfServiceUrl http://beego.me/ 6 | // @License Apache 2.0 7 | // @LicenseUrl http://www.apache.org/licenses/LICENSE-2.0.html 8 | package routers 9 | 10 | import ( 11 | "github.com/astaxie/beego" 12 | "shop-seckill/controllers" 13 | ) 14 | 15 | func init() { 16 | beego.Include(&controllers.OrderController{}) 17 | 18 | } 19 | -------------------------------------------------------------------------------- /seckill.sql: -------------------------------------------------------------------------------- 1 | # ************************************************************ 2 | # Sequel Pro SQL dump 3 | # Version 4541 4 | # 5 | # http://www.sequelpro.com/ 6 | # https://github.com/sequelpro/sequelpro 7 | # 8 | # Host: 127.0.0.1 (MySQL 5.7.29) 9 | # Database: seckill 10 | # Generation Time: 2021-01-21 08:23:40 +0000 11 | # ************************************************************ 12 | 13 | 14 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 15 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 16 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 17 | /*!40101 SET NAMES utf8 */; 18 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 19 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 20 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 21 | 22 | 23 | # Dump of table order_items 24 | # ------------------------------------------------------------ 25 | 26 | DROP TABLE IF EXISTS `order_items`; 27 | 28 | CREATE TABLE `order_items` ( 29 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 30 | `order_id` bigint(20) unsigned NOT NULL, 31 | `product_id` bigint(20) unsigned NOT NULL, 32 | `product_sku_id` bigint(20) unsigned NOT NULL, 33 | `amount` int(10) unsigned NOT NULL, 34 | `price` decimal(10,2) NOT NULL, 35 | `rating` int(10) unsigned DEFAULT NULL, 36 | `review` text COLLATE utf8mb4_unicode_ci, 37 | `reviewed_at` timestamp NULL DEFAULT NULL, 38 | PRIMARY KEY (`id`) 39 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 40 | 41 | 42 | 43 | # Dump of table orders 44 | # ------------------------------------------------------------ 45 | 46 | DROP TABLE IF EXISTS `orders`; 47 | 48 | CREATE TABLE `orders` ( 49 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 50 | `no` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, 51 | `user_id` bigint(20) unsigned NOT NULL, 52 | `address` text COLLATE utf8mb4_unicode_ci NOT NULL, 53 | `total_amount` decimal(10,2) NOT NULL COMMENT '订单总金额', 54 | `remark` text COLLATE utf8mb4_unicode_ci, 55 | `paid_at` datetime DEFAULT NULL COMMENT '支付时间', 56 | `payment_method` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '支付方式', 57 | `payment_no` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '支付平台订单号', 58 | `refund_status` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending' COMMENT '退款状态', 59 | `refund_no` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '退款单号', 60 | `closed` tinyint(1) NOT NULL DEFAULT '0' COMMENT '订单是否已关闭', 61 | `reviewed` tinyint(1) NOT NULL DEFAULT '0' COMMENT '订单是否已评价', 62 | `ship_status` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending' COMMENT '物流状态', 63 | `ship_data` text COLLATE utf8mb4_unicode_ci COMMENT '物流数据', 64 | `extra` text COLLATE utf8mb4_unicode_ci COMMENT '其他额外数据', 65 | `created_at` timestamp NULL DEFAULT NULL, 66 | `updated_at` timestamp NULL DEFAULT NULL, 67 | PRIMARY KEY (`id`), 68 | UNIQUE KEY `orders_no_unique` (`no`), 69 | UNIQUE KEY `orders_refund_no_unique` (`refund_no`) 70 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 71 | 72 | 73 | 74 | # Dump of table product_skus 75 | # ------------------------------------------------------------ 76 | 77 | DROP TABLE IF EXISTS `product_skus`; 78 | 79 | CREATE TABLE `product_skus` ( 80 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 81 | `title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, 82 | `description` text COLLATE utf8mb4_unicode_ci NOT NULL, 83 | `price` decimal(10,2) NOT NULL, 84 | `stock` int(10) unsigned NOT NULL, 85 | `product_id` bigint(20) unsigned NOT NULL, 86 | `created_at` timestamp NULL DEFAULT NULL, 87 | `updated_at` timestamp NULL DEFAULT NULL, 88 | PRIMARY KEY (`id`) 89 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 90 | 91 | LOCK TABLES `product_skus` WRITE; 92 | /*!40000 ALTER TABLE `product_skus` DISABLE KEYS */; 93 | 94 | INSERT INTO `product_skus` (`id`, `title`, `description`, `price`, `stock`, `product_id`, `created_at`, `updated_at`) 95 | VALUES 96 | (1,'iphone12','256G',1.00,0,1,NULL,NULL); 97 | 98 | /*!40000 ALTER TABLE `product_skus` ENABLE KEYS */; 99 | UNLOCK TABLES; 100 | 101 | 102 | # Dump of table products 103 | # ------------------------------------------------------------ 104 | 105 | DROP TABLE IF EXISTS `products`; 106 | 107 | CREATE TABLE `products` ( 108 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 109 | `title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, 110 | `description` text COLLATE utf8mb4_unicode_ci NOT NULL, 111 | `image` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, 112 | `on_sale` tinyint(1) NOT NULL DEFAULT '1', 113 | `rating` double(8,2) NOT NULL DEFAULT '5.00', 114 | `sold_count` int(10) unsigned NOT NULL DEFAULT '0', 115 | `review_count` int(10) unsigned NOT NULL DEFAULT '0', 116 | `price` decimal(10,2) NOT NULL, 117 | `created_at` timestamp NULL DEFAULT NULL, 118 | `updated_at` timestamp NULL DEFAULT NULL, 119 | PRIMARY KEY (`id`) 120 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 121 | 122 | 123 | 124 | # Dump of table seckill_products 125 | # ------------------------------------------------------------ 126 | 127 | DROP TABLE IF EXISTS `seckill_products`; 128 | 129 | CREATE TABLE `seckill_products` ( 130 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 131 | `product_id` bigint(20) unsigned NOT NULL, 132 | `product_sku_id` bigint(20) unsigned NOT NULL, 133 | `start_at` datetime NOT NULL, 134 | `end_at` datetime NOT NULL, 135 | PRIMARY KEY (`id`) 136 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 137 | 138 | LOCK TABLES `seckill_products` WRITE; 139 | /*!40000 ALTER TABLE `seckill_products` DISABLE KEYS */; 140 | 141 | INSERT INTO `seckill_products` (`id`, `product_id`, `product_sku_id`, `start_at`, `end_at`) 142 | VALUES 143 | (3,1,1,'2021-01-15 00:00:00','2021-01-17 00:00:00'); 144 | 145 | /*!40000 ALTER TABLE `seckill_products` ENABLE KEYS */; 146 | UNLOCK TABLES; 147 | 148 | 149 | 150 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; 151 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; 152 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; 153 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 154 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 155 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 156 | -------------------------------------------------------------------------------- /services/mq/Mq.go: -------------------------------------------------------------------------------- 1 | package mq 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/streadway/amqp" 7 | ) 8 | 9 | type Callback func(msg string) 10 | 11 | func Connect() (*amqp.Connection,error){ 12 | conn,err := amqp.Dial("amqp://guest:guest@127.0.0.1:5672/") 13 | return conn,err 14 | } 15 | 16 | //简单模式和工作模式 发送端 17 | func Publish(exchange string, queueName string, body string) error{ 18 | //1:建立连接 19 | conn,err := Connect() 20 | if err != nil { 21 | return err 22 | } 23 | defer conn.Close() 24 | 25 | //2:创建通道 26 | channel, err := conn.Channel() 27 | if err != nil { 28 | return err 29 | } 30 | defer channel.Close() 31 | 32 | //3:创建队列 33 | q,err := channel.QueueDeclare(queueName,true,false,false,false,nil) 34 | if err != nil { 35 | return err 36 | } 37 | 38 | //4:发送消息 39 | err = channel.Publish(exchange, q.Name, false, false, amqp.Publishing{ 40 | DeliveryMode: amqp.Persistent, 41 | ContentType: "text/plain", 42 | Body: []byte(body), 43 | }) 44 | return err 45 | } 46 | //简单模式和工作模式 接收端 47 | func Consumer(exchange string, queueName string, callback Callback){ 48 | //1:建立连接 49 | conn,err := Connect() 50 | defer conn.Close() 51 | if err != nil { 52 | fmt.Println(err) 53 | return 54 | } 55 | 56 | //2:创建通道 57 | channel,err := conn.Channel() 58 | defer channel.Close() 59 | if err != nil{ 60 | fmt.Println(err) 61 | return 62 | } 63 | 64 | //3:创建队列 65 | q,err := channel.QueueDeclare(queueName, true, false, false, false, nil) 66 | if err != nil { 67 | fmt.Println(err) 68 | return 69 | } 70 | 71 | 72 | //4:从队列中获取数据 第二参数是路由 第三个参数自动应答 73 | msgs,err := channel.Consume(q.Name, "", false, false, false, false, nil) 74 | if err != nil { 75 | fmt.Println(err) 76 | return 77 | } 78 | 79 | //5:进行消息处理 80 | forever := make(chan bool) 81 | go func(){ 82 | for d := range msgs{ 83 | s := BytesToString(&(d.Body)) 84 | callback(*s) 85 | //回调函数执行完 代表业务处理完消息进行手动应答 86 | d.Ack(false) 87 | } 88 | }() 89 | fmt.Println("Waiting for messages") 90 | <-forever 91 | } 92 | 93 | func BytesToString(b *[]byte) *string { 94 | s := bytes.NewBuffer(*b) 95 | r := s.String() 96 | return &r 97 | } 98 | 99 | //订阅模式-路由模式-主题模式 100 | func PublishEx(exchange string, types string, routingKey string, body string) error { 101 | //1:建立连接 102 | conn,err := Connect() 103 | defer conn.Close() 104 | if err != nil { 105 | return err 106 | } 107 | 108 | //2:创建通道 109 | channel, err := conn.Channel() 110 | defer channel.Close() 111 | if err != nil{ 112 | return err 113 | } 114 | 115 | //3:创建交换机 116 | err = channel.ExchangeDeclare(exchange, types,true, false, false,false,nil) 117 | if err != nil{ 118 | return err 119 | } 120 | 121 | //4:发送消息 122 | err = channel.Publish(exchange, routingKey, false, false, amqp.Publishing{ 123 | DeliveryMode: amqp.Persistent, 124 | ContentType: "text/plain", 125 | Body: []byte(body), 126 | }) 127 | 128 | return err 129 | } 130 | func ConsumerEx(exchange string, types string, routingKey string, callback Callback){ 131 | //1:建立连接 132 | conn,err := Connect() 133 | defer conn.Close() 134 | if err != nil{ 135 | fmt.Println(err) 136 | return 137 | } 138 | 139 | //2:创建通道 140 | channel,err := conn.Channel() 141 | defer channel.Close() 142 | if err != nil{ 143 | fmt.Println(err) 144 | return 145 | } 146 | 147 | //3:创建交换机 148 | err = channel.ExchangeDeclare(exchange,types,true,false, false, false, nil) 149 | if err != nil{ 150 | fmt.Println(err) 151 | return 152 | } 153 | 154 | //4:创建队列 155 | q,err := channel.QueueDeclare("", false, false, true, false, nil) 156 | if err != nil{ 157 | fmt.Println(err) 158 | return 159 | } 160 | 161 | //5:绑定 162 | err = channel.QueueBind(q.Name, routingKey, exchange, false, nil) 163 | if err != nil{ 164 | fmt.Println(err) 165 | return 166 | } 167 | 168 | //6:从队列中获取数据 第二参数是路由 第三个参数自动应答 169 | msgs,err := channel.Consume(q.Name, "", false, false, false, false, nil) 170 | if err != nil { 171 | fmt.Println(err) 172 | return 173 | } 174 | 175 | //7:进行消息处理 176 | forever := make(chan bool) 177 | go func(){ 178 | for d := range msgs{ 179 | s := BytesToString(&(d.Body)) 180 | callback(*s) 181 | //回调函数执行完 代表业务处理完消息进行手动应答 182 | d.Ack(false) 183 | } 184 | }() 185 | fmt.Println("Waiting for messages\n") 186 | <-forever 187 | 188 | } 189 | 190 | //死信队列 191 | func PublishDlx(exchangeA string, body string) error { 192 | //1:建立连接 193 | conn,err := Connect() 194 | if err != nil{ 195 | return err 196 | } 197 | defer conn.Close() 198 | 199 | //2:创建通道 200 | channel,err := conn.Channel() 201 | if err != nil{ 202 | return err 203 | } 204 | defer channel.Close() 205 | 206 | //3:消息发送到A交换机 207 | err = channel.Publish(exchangeA, "", false, false, amqp.Publishing{ 208 | DeliveryMode: amqp.Persistent, 209 | ContentType: "text/plain", 210 | Body:[]byte(body), 211 | }) 212 | return err 213 | } 214 | func ConsumerDlx(exchangeA string, queueAName string, exchangeB string, queueBName string, ttl int, callback Callback){ 215 | //1:建立连接 216 | conn,err := Connect() 217 | if err != nil{ 218 | fmt.Println(err) 219 | return 220 | } 221 | defer conn.Close() 222 | 223 | //2:创建通道 224 | channel,err := conn.Channel() 225 | if err != nil{ 226 | fmt.Println(err) 227 | return 228 | } 229 | defer channel.Close() 230 | 231 | //3: 创建A交换机-创建A队列-A交换机和A队列绑定 232 | err = channel.ExchangeDeclare(exchangeA, "fanout", true, false, false, false, nil) 233 | if err != nil{ 234 | fmt.Println(err) 235 | return 236 | } 237 | //3.1 创建一个queue,指定消息过期时间,并且绑定过期以后发送到那个交换机 238 | queueA,err := channel.QueueDeclare(queueAName, true, false, false, false, amqp.Table{ 239 | // 当消息过期时把消息发送到 exchangeB 240 | "x-dead-letter-exchange": exchangeB, 241 | "x-message-ttl": ttl, 242 | //"x-dead-letter-queue" : queueBName, 243 | //"x-dead-letter-routing-key" : 244 | }) 245 | if err != nil{ 246 | fmt.Println(err) 247 | return 248 | } 249 | 250 | err = channel.QueueBind(queueA.Name, "", exchangeA, false, nil) 251 | if err != nil{ 252 | fmt.Println(err) 253 | return 254 | } 255 | 256 | //4: 创建B交换机-创建B队列-B交换机和B队列绑定 257 | err = channel.ExchangeDeclare(exchangeB, "fanout", true, false, false, false, nil) 258 | if err != nil{ 259 | fmt.Println(err) 260 | return 261 | } 262 | 263 | queueB,err := channel.QueueDeclare(queueBName, true, false, false, false, nil) 264 | if err != nil{ 265 | fmt.Println(err) 266 | return 267 | } 268 | 269 | err = channel.QueueBind(queueB.Name, "", exchangeB, false, nil) 270 | if err != nil{ 271 | fmt.Println(err) 272 | return 273 | } 274 | 275 | //5:从队列中获取数据 第二参数是路由 第三个参数自动应答 276 | msgs, err := channel.Consume(queueB.Name, "", false, false, false, false, nil) 277 | if err != nil { 278 | fmt.Println(err) 279 | return 280 | } 281 | 282 | //6:进行消息处理 283 | forever := make(chan bool) 284 | go func(){ 285 | for d := range msgs{ 286 | s := BytesToString(&(d.Body)) 287 | callback(*s) 288 | //回调函数执行完 代表业务处理完消息进行手动应答 289 | d.Ack(false) 290 | } 291 | }() 292 | fmt.Printf(" [*] Waiting for messages. To exit press CTRL+C\n") 293 | <-forever 294 | } 295 | -------------------------------------------------------------------------------- /services/redis/Redis.go: -------------------------------------------------------------------------------- 1 | package redisClient 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | "github.com/garyburd/redigo/redis" 6 | "time" 7 | ) 8 | 9 | //直接连接 10 | func Connect() redis.Conn{ 11 | pool,_ := redis.Dial("tcp", beego.AppConfig.String("redisdb")) 12 | return pool 13 | } 14 | 15 | //连接池连接 16 | func PoolConnect() redis.Conn{ 17 | //建立连接池 18 | pool := &redis.Pool{ 19 | MaxIdle: 5000, //最大空闲连接数 20 | MaxActive: 10000, //最大连接数 21 | IdleTimeout: 180 * time.Second, //空闲连接超时时间 22 | Wait: true, //超过最大连接数时,是等待还是报错 23 | Dial: func() (redis.Conn, error) { //建立链接 24 | c, err := redis.Dial("tcp", beego.AppConfig.String("redisdb")) 25 | if err != nil { 26 | return nil, err 27 | } 28 | // 选择db 29 | //c.Do("SELECT", '') 30 | return c, nil 31 | }, 32 | } 33 | return pool.Get() 34 | } 35 | -------------------------------------------------------------------------------- /shop-seckill: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KingWQ/seckill-beego/59b31512f147367dc472e6b5b284ad25af389f52/shop-seckill -------------------------------------------------------------------------------- /tests/default_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | "runtime" 8 | "path/filepath" 9 | _ "shop-seckill/routers" 10 | 11 | "github.com/astaxie/beego" 12 | . "github.com/smartystreets/goconvey/convey" 13 | ) 14 | 15 | func init() { 16 | _, file, _, _ := runtime.Caller(0) 17 | apppath, _ := filepath.Abs(filepath.Dir(filepath.Join(file, ".." + string(filepath.Separator)))) 18 | beego.TestBeegoInit(apppath) 19 | } 20 | 21 | // TestGet is a sample to run an endpoint test 22 | func TestGet(t *testing.T) { 23 | r, _ := http.NewRequest("GET", "/v1/object", nil) 24 | w := httptest.NewRecorder() 25 | beego.BeeApp.Handlers.ServeHTTP(w, r) 26 | 27 | beego.Trace("testing", "TestGet", "Code[%d]\n%s", w.Code, w.Body.String()) 28 | 29 | Convey("Subject: Test Station Endpoint\n", t, func() { 30 | Convey("Status Code Should Be 200", func() { 31 | So(w.Code, ShouldEqual, 200) 32 | }) 33 | Convey("The Result Should Not Be Empty", func() { 34 | So(w.Body.Len(), ShouldBeGreaterThan, 0) 35 | }) 36 | }) 37 | } 38 | 39 | --------------------------------------------------------------------------------