由于整個博客是前后端分離的夸溶,并且接口規(guī)范主要使用的是 graphql(對于前端的界面不會有什么侵入性)。所以我這先把服務(wù)端的接口給實現(xiàn)了凶硅。
一缝裁、實體類實現(xiàn)
在做服務(wù)端——數(shù)據(jù)庫表結(jié)構(gòu)設(shè)計時,已經(jīng)實現(xiàn)完了足绅。
二捷绑、實體查詢韩脑、刪除、創(chuàng)建的數(shù)據(jù)庫訪問代碼實現(xiàn)
由于所有維度數(shù)據(jù)訪問接口約定為:全部支持分頁胎食。因此服務(wù)端無須做訪問單條數(shù)據(jù)的代碼實現(xiàn)扰才。(即使是單條數(shù)據(jù)允懂,也返回一個列表)(也是為了簡化考慮)厕怜。
1. 創(chuàng)建
函數(shù)實現(xiàn):
/*
*
- @Author qijing
- @Description 創(chuàng)建實體
- @Date 2022-12-22 22:42
- @Param
- @return
*
*/
func CreateEntity(detail interface{}) (err error) {
err = DB.Debug().Create(detail).Error
return err
}
測試用例:
func TestCreateEntity(t *testing.T) {
type args struct {
detail User
}
tests := []struct {
name string
args args
wantI interface{}
wantErr bool
}{
// TODO: Add test cases.
{
name: "test1",
args: args{detail: User{
Account: "test2",
Password: "test1",
}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := CreateEntity(&tt.args.detail)
if err != nil {
t.Errorf("CreateEntity() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}
測試結(jié)果:
- 傳進(jìn)來的參數(shù)為結(jié)構(gòu)體的引用
- 該方法可以實現(xiàn)所有實體的創(chuàng)建,是一個抽象構(gòu)建
2. 刪除
函數(shù)實現(xiàn):
/*
*
- @Author qijing
- @Description 刪除實體蕾总,通過主鍵
- @Date 2022-12-23 13:53
- @Param
- @return
*
*/
func DeleteEntity(detail interface{}) (err error) {
err = DB.Debug().Unscoped().Delete(detail).Error
return err
}
- 傳進(jìn)來的參數(shù)為結(jié)構(gòu)體的引用
- 該方法可以實現(xiàn)所有實體的刪除(結(jié)構(gòu)體必須包含主鍵)粥航,是一個抽象構(gòu)建
測試用例:
func TestDeleteEntity(t *testing.T) {
err := DeleteEntity(&User{Base: Base{ID: "532dce46-82aa-11ed-9202-b025aa395669"}})
if err != nil {
t.Error(err)
}
}
測試結(jié)果:
3. 查詢
查詢比較麻煩,這里先說下和查詢相關(guān)的需求:
- 支持關(guān)鍵詞檢索全博客網(wǎng)站的維度(比如說檢索全博客的文章生百,摘錄的好詞句)
- 支持關(guān)鍵詞檢索當(dāng)個賬號內(nèi)部所有的文章(比如說從賬號a寫的文章里檢索出關(guān)鍵詞帶“七鏡”的文章)
- 支持分頁
- 支持排序
- 支持按列檢索(比如說递雀,查找 column1=value1 and column2=value2 的表里的所有數(shù)據(jù))
a. 數(shù)據(jù)庫表結(jié)構(gòu)約定
- 所有表與表之間的關(guān)系都采用多對多(ManyToMany)
- 所有表與表之間連結(jié)的中間表名都采用“__” 雙下劃線連接。比如 user__comment 表示是user表和comment表的中間表
-
所有表與表之間連結(jié)的中間表名雙下劃線左邊的表名是主表蚀浆,右邊的表明是客表缀程。即客表的實體由主表的實體創(chuàng)建。比如賬號表是主表市俊,評論表是客表杨凑,因為賬號創(chuàng)建評論。
b. 入?yún)⒔Y(jié)構(gòu)設(shè)計
表結(jié)構(gòu)的約定是為了方便邏輯呈現(xiàn)摆昧,減少代碼量撩满。入?yún)⒔Y(jié)構(gòu)設(shè)計是為了實現(xiàn)具體的查詢需求。具體入?yún)⒔Y(jié)構(gòu)設(shè)計如下:
type CondGetDetails struct {
PageAfter int `json:"pageAfter"` // 支持分頁
PageSize int `json:"pageSize"` // 支持分頁
OrderBy string `json:"orderBy"` // 支持排序
SortBy string `json:"sortBy"` // 支持排序
Detail interface{} `json:"detail"` // 支持按列檢索
Keyword Keyword `json:"keyword"` // 支持關(guān)鍵字檢索 // 20221222
TableName string `json:"table_name"` // 支持關(guān)鍵字檢索 绅你,主實體名 // 20221222
JoinTables []CondJoinObject `json:"joinTables"` // 支持賬號內(nèi)維度檢索 Join Table
}
type Keyword map[string]string // 統(tǒng)一規(guī)定關(guān)鍵詞結(jié)構(gòu)
type CondJoinObject struct {
TableName string `json:"table_name"` // 需要join 表名
IsMain bool `json:"is_main"` // 主表是靠左的
IsInner bool `json:"is_inner"` // 是否 inner join
IsLeft bool `json:"is_left"` // 是否 left join
IsRight bool `json:"is_right"` // 是否 right join
Keyword Keyword `json:"keyword"` // 要 join 的表里的關(guān)鍵詞 like 查詢
}
c. 代碼實現(xiàn)
函數(shù)實現(xiàn):
/*
*
- @Author qijing
- @Description get entities
- @Date 2022-12-22 22:42
- @Param
- @return
*
*/
func GetEntities(cond CondGetDetails) (db *gorm.DB, err error) {
detail := cond.Detail.(map[string]interface{})
var tx *gorm.DB
if cond.Keyword != nil {
for k, v := range cond.Keyword {
delete(detail, k)
tx = DB.Where(k+" like @"+k, sql.NamedArg{
Name: k,
Value: "%" + v + "%",
}).Where(detail)
}
} else {
tx = DB.Where(detail)
}
for _, joinTable := range cond.JoinTables {
mainTableSplit := cond.TableName
main := cond.TableName
sub := joinTable.TableName
subTableSplit := joinTable.TableName
if joinTable.IsMain {
mainTableSplit = joinTable.TableName
subTableSplit = cond.TableName
main = cond.TableName
sub = joinTable.TableName
}
if joinTable.IsInner {
tx.Joins("LEFT JOIN " + mainTableSplit + "__" + subTableSplit + " on " + main + ".id = " + mainTableSplit + "__" + subTableSplit + "." + main + "_id")
likeSplit := ""
for k, v := range joinTable.Keyword {
likeSplitSplit := " and " + sub + "." + k + " like '%" + v + "%'"
likeSplit += likeSplitSplit
}
tx.Joins("INNER JOIN " + sub + " on " + sub + ".id = " + mainTableSplit + "__" + subTableSplit + "." + sub + "_id" + likeSplit)
} else {
return nil, errors.New("unimplemented join except for `INNER JOIN`")
}
}
tx = tx.Debug().Preload(clause.Associations).Limit(cond.PageSize).Offset(cond.PageAfter).Order(cond.OrderBy + " " + cond.SortBy)
return tx, err
}
測試用例:
func TestGetEntities2(t *testing.T) {
type args struct {
cond CondGetDetails
}
tests := []struct {
name string
args args
}{
// TODO: Add test cases.
{
name: "test2",
args: args{
cond: CondGetDetails{
PageAfter: 0,
PageSize: 10,
OrderBy: "created_at",
SortBy: "desc",
Detail: map[string]interface{}{},
Keyword: map[string]string{"content": "文件名中加上"},
TableName: "dimension_reading",
JoinTables: []CondJoinObject{
{
TableName: "user",
IsMain: true,
IsInner: true,
IsLeft: false,
IsRight: false,
Keyword: map[string]string{
"account": "min",
},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotDb, err := GetEntities(tt.args.cond)
if err != nil {
t.Error(err)
}
var dim []DimensionReading
var count int64
if err := gotDb.Debug().Find(&dim).Count(&count).Error; err != nil {
t.Error(err)
}
t.Log(dim)
t.Log(len(dim))
t.Log(count)
})
}
}
測試結(jié)果:
4. 修改
函數(shù)實現(xiàn):
/*
*
- @Author qijing
- @Description update entity
- @Date 2022-12-23 18:21
- @Param
- @return
*
*/
func UpdateEntity(detail map[string]interface{}, entity interface{}) (i interface{}, err error) {
id, idOk := detail["id"].(string)
if !idOk || id == "" {
return nil, errors.New("unknown entity.id")
}
if err != nil {
return nil, err
}
err = DB.Transaction(func(tx *gorm.DB) error {
result := DB.Debug().Model(entity).Where("id = @id", sql.Named("id", id)).Omit("id", "created_at", "created").Updates(&detail)
if result.RowsAffected <= 0 {
return errors.New("更新0條數(shù)據(jù)")
}
return nil
})
if err != nil {
return nil, err
}
return nil, err
}
測試用例:
func TestUpdateEntity(t *testing.T) {
type args struct {
detail map[string]interface{}
entity interface{}
}
tests := []struct {
name string
args args
}{
// TODO: Add test cases.
{
name: "test1",
args: args{
detail: map[string]interface{}{
"id": "3778e25e-82ac-11ed-8162-b025aa395669",
"account": "七鏡",
},
entity: User{},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := UpdateEntity(tt.args.detail, tt.args.entity)
if err != nil {
t.Error(err)
}
})
}
}
測試結(jié)果:
OK伺帘,至此,所有的數(shù)據(jù)庫訪問基本已經(jīng)完成忌锯。整個數(shù)據(jù)庫訪問的實現(xiàn)伪嫁,體現(xiàn)了約定的價值。以上4個函數(shù)偶垮,實現(xiàn)了所有實體的數(shù)據(jù)庫訪問操作礼殊,無需再為每一個實體單獨實現(xiàn)數(shù)據(jù)庫訪問函數(shù)了。