├── .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 |
--------------------------------------------------------------------------------