183 lines
4.8 KiB
Go
183 lines
4.8 KiB
Go
package dtmsvr
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"github.com/sirupsen/logrus"
|
||
"github.com/yedf/dtm/common"
|
||
"gorm.io/gorm"
|
||
"gorm.io/gorm/clause"
|
||
)
|
||
|
||
type TransGlobal struct {
|
||
common.ModelBase
|
||
Gid string `json:"gid"`
|
||
TransType string `json:"trans_type"`
|
||
Data string `json:"data"`
|
||
Status string `json:"status"`
|
||
QueryPrepared string `json:"query_prepared"`
|
||
CommitTime *time.Time
|
||
FinishTime *time.Time
|
||
RollbackTime *time.Time
|
||
}
|
||
|
||
func (*TransGlobal) TableName() string {
|
||
return "trans_global"
|
||
}
|
||
|
||
type TransProcessor interface {
|
||
GenBranches() []TransBranch
|
||
ProcessOnce(db *common.DB, branches []TransBranch)
|
||
ExecBranch(db *common.DB, branch *TransBranch)
|
||
}
|
||
|
||
func (t *TransGlobal) touch(db *common.DB) *gorm.DB {
|
||
writeTransLog(t.Gid, "touch trans", "", "", "")
|
||
return db.Model(&TransGlobal{}).Where("gid=?", t.Gid).Update("gid", t.Gid) // 更新update_time,避免被定时任务再次
|
||
}
|
||
|
||
func (t *TransGlobal) changeStatus(db *common.DB, status string) *gorm.DB {
|
||
writeTransLog(t.Gid, "change status", status, "", "")
|
||
updates := M{
|
||
"status": status,
|
||
}
|
||
if status == "succeed" {
|
||
updates["finish_time"] = time.Now()
|
||
} else if status == "failed" {
|
||
updates["rollback_time"] = time.Now()
|
||
}
|
||
dbr := db.Must().Model(t).Where("status=?", t.Status).Updates(updates)
|
||
checkAffected(dbr)
|
||
t.Status = status
|
||
return dbr
|
||
}
|
||
|
||
type TransBranch struct {
|
||
common.ModelBase
|
||
Gid string
|
||
Url string
|
||
Data string
|
||
Branch string
|
||
BranchType string
|
||
Status string
|
||
FinishTime *time.Time
|
||
RollbackTime *time.Time
|
||
}
|
||
|
||
func (*TransBranch) TableName() string {
|
||
return "trans_branch"
|
||
}
|
||
|
||
func (t *TransBranch) changeStatus(db *common.DB, status string) *gorm.DB {
|
||
writeTransLog(t.Gid, "branch change", status, t.Branch, "")
|
||
dbr := db.Must().Model(t).Where("status=?", t.Status).Updates(M{
|
||
"status": status,
|
||
"finish_time": time.Now(),
|
||
})
|
||
checkAffected(dbr)
|
||
t.Status = status
|
||
return dbr
|
||
}
|
||
|
||
func checkAffected(db1 *gorm.DB) {
|
||
if db1.RowsAffected == 0 {
|
||
panic(fmt.Errorf("duplicate updating"))
|
||
}
|
||
}
|
||
|
||
type processorCreator func(*TransGlobal) TransProcessor
|
||
|
||
var processorFac = map[string]processorCreator{}
|
||
|
||
func registorProcessorCreator(transType string, creator processorCreator) {
|
||
processorFac[transType] = creator
|
||
}
|
||
|
||
func (trans *TransGlobal) getProcessor() TransProcessor {
|
||
return processorFac[trans.TransType](trans)
|
||
}
|
||
|
||
func (t *TransGlobal) MayQueryPrepared(db *common.DB) {
|
||
if t.Status != "prepared" {
|
||
return
|
||
}
|
||
resp, err := common.RestyClient.R().SetQueryParam("gid", t.Gid).Get(t.QueryPrepared)
|
||
e2p(err)
|
||
body := resp.String()
|
||
if strings.Contains(body, "FAIL") {
|
||
preparedExpire := time.Now().Add(time.Duration(-config.PreparedExpire) * time.Second)
|
||
logrus.Printf("create time: %s prepared expire: %s ", t.CreateTime.Local(), preparedExpire.Local())
|
||
status := common.If(t.CreateTime.Before(preparedExpire), "canceled", "prepared").(string)
|
||
if status != t.Status {
|
||
t.changeStatus(db, status)
|
||
} else {
|
||
t.touch(db)
|
||
}
|
||
} else if strings.Contains(body, "SUCCESS") {
|
||
t.changeStatus(db, "committed")
|
||
}
|
||
}
|
||
|
||
func (trans *TransGlobal) Process(db *common.DB) {
|
||
defer handlePanic()
|
||
defer func() {
|
||
if TransProcessedTestChan != nil {
|
||
TransProcessedTestChan <- trans.Gid
|
||
}
|
||
}()
|
||
branches := []TransBranch{}
|
||
db.Must().Where("gid=?", trans.Gid).Order("id asc").Find(&branches)
|
||
trans.getProcessor().ProcessOnce(db, branches)
|
||
}
|
||
|
||
func (t *TransGlobal) SaveNew(db *common.DB) {
|
||
err := db.Transaction(func(db1 *gorm.DB) error {
|
||
db := &common.DB{DB: db1}
|
||
|
||
writeTransLog(t.Gid, "create trans", t.Status, "", t.Data)
|
||
dbr := db.Must().Clauses(clause.OnConflict{
|
||
DoNothing: true,
|
||
}).Create(t)
|
||
if dbr.RowsAffected == 0 && t.Status == "committed" { // 如果数据库已经存放了prepared的事务,则修改状态
|
||
dbr = db.Must().Model(&TransGlobal{}).Where("gid=? and status=?", t.Gid, "prepared").Update("status", t.Status)
|
||
}
|
||
if dbr.RowsAffected == 0 { // 未保存任何数据,直接返回
|
||
return nil
|
||
}
|
||
// 保存所有的分支
|
||
branches := t.getProcessor().GenBranches()
|
||
if len(branches) > 0 {
|
||
writeTransLog(t.Gid, "save branches", t.Status, "", common.MustMarshalString(branches))
|
||
db.Must().Clauses(clause.OnConflict{
|
||
DoNothing: true,
|
||
}).Create(&branches)
|
||
}
|
||
return nil
|
||
})
|
||
e2p(err)
|
||
}
|
||
|
||
func TransFromContext(c *gin.Context) *TransGlobal {
|
||
data := M{}
|
||
b, err := c.GetRawData()
|
||
e2p(err)
|
||
common.MustUnmarshal(b, &data)
|
||
logrus.Printf("creating trans in prepare")
|
||
if data["steps"] != nil {
|
||
data["data"] = common.MustMarshalString(data["steps"])
|
||
}
|
||
m := TransGlobal{}
|
||
common.MustRemarshal(data, &m)
|
||
return &m
|
||
}
|
||
|
||
func TransFromDb(db *common.DB, gid string) *TransGlobal {
|
||
m := TransGlobal{}
|
||
dbr := db.Must().Model(&m).Where("gid=?", gid).First(&m)
|
||
e2p(dbr.Error)
|
||
return &m
|
||
}
|