mirror of https://github.com/dtm-labs/dtm.git
11 changed files with 397 additions and 0 deletions
@ -0,0 +1,32 @@ |
|||
package common |
|||
|
|||
import ( |
|||
"github.com/bwmarrin/snowflake" |
|||
) |
|||
|
|||
var gNode *snowflake.Node = nil |
|||
|
|||
func GenGid() string { |
|||
return gNode.Generate().Base58() |
|||
} |
|||
|
|||
func init() { |
|||
node, err := snowflake.NewNode(1) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
gNode = node |
|||
} |
|||
|
|||
func PanicIfError(err error) { |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
} |
|||
|
|||
func If(condition bool, trueObj interface{}, falseObj interface{}) interface{} { |
|||
if condition { |
|||
return trueObj |
|||
} |
|||
return falseObj |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
package dtm |
|||
|
|||
import ( |
|||
"fmt" |
|||
|
|||
"github.com/gin-gonic/gin" |
|||
"github.com/go-resty/resty/v2" |
|||
) |
|||
|
|||
var client *resty.Client = resty.New() |
|||
|
|||
type Saga struct { |
|||
Server string |
|||
Gid string |
|||
Steps []SagaStep |
|||
TransQuery string |
|||
} |
|||
type SagaStep struct { |
|||
Action string |
|||
Compensate string |
|||
PostData interface{} |
|||
} |
|||
|
|||
func (s *Saga) Add(action string, compensate string, postData interface{}) error { |
|||
step := SagaStep{ |
|||
Action: action, |
|||
Compensate: compensate, |
|||
} |
|||
step.PostData = postData |
|||
s.Steps = append(s.Steps, step) |
|||
return nil |
|||
} |
|||
|
|||
func (s *Saga) Prepare(url string) error { |
|||
s.TransQuery = url |
|||
resp, err := client.R().SetBody(gin.H{ |
|||
"Gid": s.Gid, |
|||
"TransQuery": s.TransQuery, |
|||
"Steps": s.Steps, |
|||
}).Post(fmt.Sprintf("%s/prepare", s.Server)) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
if resp.StatusCode() != 200 { |
|||
return fmt.Errorf("prepare failed: %v", resp.Body()) |
|||
} |
|||
return nil |
|||
} |
|||
|
|||
func (s *Saga) Commit() error { |
|||
resp, err := client.R().SetBody(gin.H{}).Post(fmt.Sprintf("%s/commit", s.Server)) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
if resp.StatusCode() != 200 { |
|||
return fmt.Errorf("commit failed: %v", resp.Body()) |
|||
} |
|||
return nil |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
package dtmsvr |
|||
|
|||
import ( |
|||
"path/filepath" |
|||
"runtime" |
|||
|
|||
"github.com/sirupsen/logrus" |
|||
"github.com/spf13/viper" |
|||
) |
|||
|
|||
type Config struct { |
|||
Server string `json:"server"` |
|||
Rabbitmq RabbitmqConfig `json:"rabbitmq"` |
|||
} |
|||
|
|||
var ServerConfig Config = Config{} |
|||
|
|||
func LoadConfig() { |
|||
_, file, _, _ := runtime.Caller(0) |
|||
viper.SetConfigFile(filepath.Dir(file) + "/dtmsvr.yml") |
|||
if err := viper.ReadInConfig(); err != nil { |
|||
panic(err) |
|||
} |
|||
if err := viper.Unmarshal(&ServerConfig); err != nil { |
|||
panic(err) |
|||
} |
|||
logrus.Printf("config is: %v", ServerConfig) |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
package dtmsvr |
|||
|
|||
func ConsumeHalfMsg() { |
|||
|
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
package dtmsvr |
|||
|
|||
func ConsumeMsg() { |
|||
|
|||
} |
|||
@ -0,0 +1,173 @@ |
|||
package dtmsvr |
|||
|
|||
import ( |
|||
"encoding/json" |
|||
"fmt" |
|||
"sync" |
|||
|
|||
"github.com/sirupsen/logrus" |
|||
"github.com/streadway/amqp" |
|||
"github.com/yedf/dtm/common" |
|||
) |
|||
|
|||
type Rabbitmq struct { |
|||
Config RabbitmqConfig |
|||
ChannelPool *sync.Pool |
|||
} |
|||
|
|||
type RabbitmqConfig struct { |
|||
Host string |
|||
Username string |
|||
Password string |
|||
Vhost string |
|||
Exchange string |
|||
KeyPrepared string |
|||
KeyCommited string |
|||
QueuePrepared string |
|||
QueueCommited string |
|||
} |
|||
|
|||
type RabbitmqChannel struct { |
|||
Confirms chan amqp.Confirmation |
|||
Channel *amqp.Channel |
|||
} |
|||
|
|||
type RabbitmqConst string |
|||
|
|||
const ( |
|||
RabbitmqConstPrepared RabbitmqConst = "dtm_prepared" |
|||
RabbitmqConstCommited RabbitmqConst = "dtm_commited" |
|||
) |
|||
|
|||
func RabbitmqNew(conf *RabbitmqConfig) *Rabbitmq { |
|||
return &Rabbitmq{ |
|||
Config: *conf, |
|||
ChannelPool: &sync.Pool{ |
|||
New: func() interface{} { |
|||
channel := newChannel(conf) |
|||
err := channel.Confirm(false) |
|||
common.PanicIfError(err) |
|||
confirms := channel.NotifyPublish(make(chan amqp.Confirmation, 2)) |
|||
return &RabbitmqChannel{ |
|||
Channel: channel, |
|||
Confirms: confirms, |
|||
} |
|||
}, |
|||
}, |
|||
} |
|||
} |
|||
|
|||
func newChannel(conf *RabbitmqConfig) *amqp.Channel { |
|||
uri := fmt.Sprintf("amqp://%s:%s@%s/%s", conf.Username, conf.Password, conf.Host, conf.Vhost) |
|||
logrus.Printf("connecting rabbitmq: %s", uri) |
|||
conn, err := amqp.Dial(uri) |
|||
common.PanicIfError(err) |
|||
channel, err := conn.Channel() |
|||
common.PanicIfError(err) |
|||
err = channel.ExchangeDeclare( |
|||
conf.Exchange, // exchange name
|
|||
"direct", // exchange type
|
|||
true, // durable
|
|||
false, // autoDelete
|
|||
false, // internal
|
|||
false, // noWait
|
|||
nil, // args
|
|||
) |
|||
common.PanicIfError(err) |
|||
return channel |
|||
} |
|||
|
|||
func (r *Rabbitmq) SendAndConfirm(key RabbitmqConst, data map[string]interface{}) error { |
|||
body, err := json.Marshal(data) |
|||
common.PanicIfError(err) |
|||
channel := r.ChannelPool.Get().(*RabbitmqChannel) |
|||
err = channel.Channel.Publish( |
|||
r.Config.Exchange, |
|||
common.If(key == RabbitmqConstPrepared, r.Config.KeyPrepared, r.Config.KeyCommited).(string), |
|||
true, |
|||
false, |
|||
amqp.Publishing{ |
|||
ContentType: "application/json", |
|||
DeliveryMode: amqp.Persistent, |
|||
Body: body, |
|||
}, |
|||
) |
|||
common.PanicIfError(err) |
|||
confirm := <-channel.Confirms |
|||
r.ChannelPool.Put(channel) |
|||
logrus.Printf("confirmed %t for %s", confirm.Ack, data["gid"]) |
|||
if !confirm.Ack { |
|||
return fmt.Errorf("confirm not ok for %s", data["gid"]) |
|||
} |
|||
return nil |
|||
} |
|||
|
|||
type RabbitmqQueue struct { |
|||
Channel *amqp.Channel |
|||
Conn *amqp.Connection |
|||
Deliveries <-chan amqp.Delivery |
|||
} |
|||
|
|||
func (q *RabbitmqQueue) Close() { |
|||
q.Channel.Close() |
|||
// q.Conn.Close()
|
|||
} |
|||
|
|||
func (q *RabbitmqQueue) WaitAndHandle(handler func(data map[string]interface{})) { |
|||
for { |
|||
q.WaitAndHandleOne(handler) |
|||
} |
|||
} |
|||
func (q *RabbitmqQueue) WaitAndHandleOne(handler func(data map[string]interface{})) { |
|||
logrus.Printf("reading message") |
|||
msg := <-q.Deliveries |
|||
data := map[string]interface{}{} |
|||
err := json.Unmarshal(msg.Body, &data) |
|||
logrus.Printf("handling one message: %v", data) |
|||
common.PanicIfError(err) |
|||
handler(data) |
|||
err = msg.Ack(false) |
|||
common.PanicIfError(err) |
|||
logrus.Printf("acked msg: %d", msg.DeliveryTag) |
|||
} |
|||
|
|||
func (r *Rabbitmq) QueueNew(queueType RabbitmqConst) *RabbitmqQueue { |
|||
channel := newChannel(&r.Config) |
|||
queue, err := channel.QueueDeclare( |
|||
common.If(queueType == RabbitmqConstPrepared, r.Config.QueuePrepared, r.Config.QueueCommited).(string), // name of the queue
|
|||
true, // durable
|
|||
false, // delete when unused
|
|||
false, // exclusive
|
|||
false, // noWait
|
|||
nil, // arguments
|
|||
) |
|||
common.PanicIfError(err) |
|||
logrus.Printf("declared Queue (%q %d messages, %d consumers), binding to Exchange", |
|||
queue.Name, queue.Messages, queue.Consumers) |
|||
err = channel.QueueBind( |
|||
queue.Name, // name of the queue
|
|||
common.If(queueType == RabbitmqConstPrepared, r.Config.KeyPrepared, r.Config.KeyCommited).(string), // bindingKey
|
|||
r.Config.Exchange, // sourceExchange
|
|||
false, // noWait
|
|||
nil, // arguments
|
|||
) |
|||
common.PanicIfError(err) |
|||
deliveries, err := channel.Consume( |
|||
queue.Name, // name
|
|||
"simple-consumer", // consumerTag,
|
|||
false, // noAck
|
|||
false, // exclusive
|
|||
false, // noLocal
|
|||
false, // noWait
|
|||
nil, // arguments
|
|||
) |
|||
common.PanicIfError(err) |
|||
return &RabbitmqQueue{ |
|||
Channel: channel, |
|||
Deliveries: deliveries, |
|||
} |
|||
} |
|||
|
|||
func (r *Rabbitmq) HandleMsg(data interface{}) { |
|||
|
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
package dtmsvr |
|||
|
|||
import ( |
|||
"testing" |
|||
|
|||
"github.com/gin-gonic/gin" |
|||
"github.com/magiconair/properties/assert" |
|||
"github.com/sirupsen/logrus" |
|||
"github.com/yedf/dtm/common" |
|||
) |
|||
|
|||
func init() { |
|||
LoadConfig() |
|||
} |
|||
|
|||
func TestRabbitConfig(t *testing.T) { |
|||
assert.Matches(t, ServerConfig.Rabbitmq.KeyCommited, "key_committed") |
|||
} |
|||
|
|||
func TestRabbitmq1Msg(t *testing.T) { |
|||
rb := RabbitmqNew(&ServerConfig.Rabbitmq) |
|||
err := rb.SendAndConfirm(RabbitmqConstPrepared, gin.H{ |
|||
"gid": common.GenGid(), |
|||
}) |
|||
assert.Equal(t, nil, err) |
|||
queue := rb.QueueNew(RabbitmqConstPrepared) |
|||
queue.WaitAndHandle(func(data map[string]interface{}) { |
|||
logrus.Printf("processed msg: %v in queue1", data) |
|||
}) |
|||
assert.Equal(t, 0, 1) |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
package dtmsvr |
|||
|
|||
import "github.com/gin-gonic/gin" |
|||
|
|||
func AddRoute(engine *gin.Engine) { |
|||
route := engine.Group("/api/dmtsvr") |
|||
route.POST("/prepare", Prepare) |
|||
route.POST("/commit", Commit) |
|||
} |
|||
|
|||
func Prepare(c *gin.Context) { |
|||
data := gin.H{} |
|||
err := c.BindJSON(&data) |
|||
if err == nil { |
|||
return |
|||
} |
|||
|
|||
} |
|||
|
|||
func Commit(c *gin.Context) { |
|||
|
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
package dtmsvr |
|||
|
|||
import ( |
|||
"github.com/gin-gonic/gin" |
|||
"github.com/sirupsen/logrus" |
|||
) |
|||
|
|||
func main() { |
|||
logrus.Printf("start tc") |
|||
gin.SetMode(gin.ReleaseMode) |
|||
app := gin.Default() |
|||
AddRoute(app) |
|||
go ConsumeHalfMsg() |
|||
go ConsumeMsg() |
|||
app.Run() |
|||
} |
|||
@ -0,0 +1,4 @@ |
|||
package examples |
|||
|
|||
const TcServer = "http://localhost:8080/api/dtmsvr" |
|||
const Busi = "http://localhost:8081/api/busi" |
|||
@ -0,0 +1,22 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"github.com/gin-gonic/gin" |
|||
"github.com/sirupsen/logrus" |
|||
"github.com/yedf/dtm/common" |
|||
"github.com/yedf/dtm/dtmsvr" |
|||
) |
|||
|
|||
func main() { |
|||
logrus.SetFormatter(&logrus.JSONFormatter{}) |
|||
dtmsvr.LoadConfig() |
|||
rb := dtmsvr.RabbitmqNew(&dtmsvr.ServerConfig.Rabbitmq) |
|||
err := rb.SendAndConfirm(dtmsvr.RabbitmqConstPrepared, gin.H{ |
|||
"gid": common.GenGid(), |
|||
}) |
|||
common.PanicIfError(err) |
|||
queue := rb.QueueNew(dtmsvr.RabbitmqConstPrepared) |
|||
queue.WaitAndHandle(func(data map[string]interface{}) { |
|||
logrus.Printf("processed msg: %v in queue1", data) |
|||
}) |
|||
} |
|||
Loading…
Reference in new issue