Hello 小伙伴们,今日给我们带来了一份Go言语建立后端事务体系的教程,restful风格哦,既然是简略的事务体系,那么必要的功用就少不了增删改查,也就是传说中的CRUD,当然比较Spring Boot而言,Go言语写后端事务体系不是那么的流行,可是比照一下我们也很容易能发现,Go言语建立的Web后端体系的优势:
- (1)内存占用更少
- (2)发动速度更快
- (3)代码愈加简练
OK,下面我们开端正文,首先看下完成后的成品吧:

1 事务&技能归纳
1.1 事务功用
实体(NumInfo) :
- Id:主键
- Name :称号
- InfoKey :Key
- InfoNum :Num
事务功用:
- 依据Key将Num加1
- 依据Key查找
- 依据ID查找
- 增加一个
- 依据ID删去一个
- 检查悉数
- 依据ID修正一个
1.2 技能点
框架:
- Gin
- Viper
- GORM
数据库:
- MySQL
前端:
- JQuery
- layUI
2 接口标准
## 依据Key将Num加1 http://localhost:9888/add/{key} ### req: http://localhost:9888/add/zs ### resp: ```json { "code": "0", "msg": "true", "count": "0", "data":"true" } ``` ## 依据Key查找 http://localhost:9888/findByKey/{key} ### req: http://localhost:9888/findByKey/zs ### resp: ```json { "code": "0", "msg": "true", "count": "0", "data":{ "id":"1" "name":"zs", "info_key":"zs", "info_num":"12" } } ``` ## 依据ID查找 http://localhost:9888/findById/{id} ### req: http://localhost:9888/findById/1 ### resp: ```json { "code": "0", "msg": "true", "count": "0", "data":{ "id":"1" "name":"zs", "info_key":"zs", "info_num":"12" } } ``` ## 增加一个 http://localhost:9888/saveInfo ### req: http://localhost:9888/saveInfo ```json { "name":"ww", "info_key":"ww" } ``` ### resp: ```json { "code": "0", "msg": "true", "count": "0", "data":"true" } ``` ## 依据ID删去一个 http://localhost:9888/deleteInfo/{id} ### req: http://localhost:9888/deleteInfo/1 ### resp: ```json { "code": "0", "msg": "true", "count": "0", "data":"true" } ``` ## 检查悉数 http://localhost:9888/getAll ### req: http://localhost:9888/getAll ### resp: ```json { "code": "0", "msg": "true", "count": "0", "data":[{ "id":"1" "name":"zs", "info_key":"zs", "info_num":"12" },{ "id":"2" "name":"ls", "info_key":"ls", "info_num":"12" }] } ``` ## 依据ID修正一个 http://localhost:9888/update ### req: http://localhost:9888/update { "id":"1" "name":"zs", "info_key":"zs", "info_num":"12" } ### resp: ```json { "code": "0", "msg": "true", "count": "0", "data":"true" } ```
3 后端代码
整体结构:

3.1 DAO层
接口
package dao import ( "context" "count_num/pkg/entity" ) type CountNumDAO interface { //增加一个 AddNumInfo(ctx context.Context, info entity.NumInfo) bool //依据Key获取一个 GetNumInfoByKey(ctx context.Context, url string) entity.NumInfo //检查悉数 FindAllNumInfo(ctx context.Context) []entity.NumInfo //依据Key修正 UpdateNumInfoByKey(ctx context.Context, info entity.NumInfo) bool //删去一个 DeleteNumInfoById(ctx context.Context, id int64) bool //依据ID获取一个 GetNumInfoById(ctx context.Context, id int64) entity.NumInfo //依据ID修正 UpdateNumInfoById(ctx context.Context, info entity.NumInfo) bool }
实现
package impl import ( "context" "count_num/pkg/config" "count_num/pkg/entity" "gorm.io/gorm" ) type CountNumDAOImpl struct { db *gorm.DB } func NewCountNumDAOImpl() *CountNumDAOImpl { return &CountNumDAOImpl{db: config.DB} } func (impl CountNumDAOImpl) AddNumInfo(ctx context.Context, info entity.NumInfo) bool { var in entity.NumInfo impl.db.First(&in, "info_key", info.InfoKey) if in.InfoKey == info.InfoKey { //去重 return false } impl.db.Save(&info) //要运用指针 return true } func (impl CountNumDAOImpl) GetNumInfoByKey(ctx context.Context, key string) entity.NumInfo { var info entity.NumInfo impl.db.First(&info, "info_key", key) return info } func (impl CountNumDAOImpl) FindAllNumInfo(ctx context.Context) []entity.NumInfo { var infos []entity.NumInfo impl.db.Find(&infos) return infos } func (impl CountNumDAOImpl) UpdateNumInfoByKey(ctx context.Context, info entity.NumInfo) bool { impl.db.Model(&entity.NumInfo{}).Where("info_key = ?", info.InfoKey).Update("info_num", info.InfoNum) return true } func (impl CountNumDAOImpl) DeleteNumInfoById(ctx context.Context, id int64) bool { impl.db.Delete(&entity.NumInfo{}, id) return true } func (impl CountNumDAOImpl) GetNumInfoById(ctx context.Context, id int64) entity.NumInfo { var info entity.NumInfo impl.db.First(&info, "id", id) return info } func (impl CountNumDAOImpl) UpdateNumInfoById(ctx context.Context, info entity.NumInfo) bool { impl.db.Model(&entity.NumInfo{}).Where("id", info.Id).Updates(entity.NumInfo{Name: info.Name, InfoKey: info.InfoKey, InfoNum: info.InfoNum}) return true }
3.2 Web层
package controller import ( "bytes" "count_num/pkg/dao/impl" "count_num/pkg/entity" "encoding/json" "github.com/gin-gonic/gin" "github.com/spf13/cast" "io/ioutil" "strconv" ) type NumInfoControllerImpl struct { dao *impl.CountNumDAOImpl } type NumInfoController interface { AddNumByKey(c *gin.Context) FindNumByKey(c *gin.Context) SaveNumInfo(c *gin.Context) DeleteById(c *gin.Context) FindAll(c *gin.Context) FindNumById(c *gin.Context) Update(context *gin.Context) } func NewNumInfoControllerImpl() *NumInfoControllerImpl { return &NumInfoControllerImpl{dao: impl.NewCountNumDAOImpl()} } func (impl NumInfoControllerImpl) AddNumByKey(c *gin.Context) { key := c.Param("key") numInfo := impl.dao.GetNumInfoByKey(c, key) numInfo.InfoNum = numInfo.InfoNum + 1 isOk := impl.dao.UpdateNumInfoByKey(c, numInfo) c.JSON(200, map[string]interface{}{"code": 0, "msg": "", "count": 0, "data": isOk}) } func (impl NumInfoControllerImpl) FindNumByKey(c *gin.Context) { key := c.Param("key") numInfo := impl.dao.GetNumInfoByKey(c, key) c.JSON(200, map[string]interface{}{"code": 0, "msg": "", "count": 0, "data": numInfo}) } func (impl NumInfoControllerImpl) SaveNumInfo(c *gin.Context) { body := c.Request.Body bytes, err := ioutil.ReadAll(body) info := entity.NumInfo{} json.Unmarshal(bytes, &info) if err != nil { panic(err) } isOk := impl.dao.AddNumInfo(c, info) c.JSON(200, map[string]interface{}{"code": 0, "msg": "", "count": 0, "data": isOk}) } func (impl NumInfoControllerImpl) DeleteById(c *gin.Context) { id := c.Param("id") i, _ := strconv.Atoi(id) isOk := impl.dao.DeleteNumInfoById(c, int64(i)) c.JSON(200, map[string]interface{}{"code": 0, "msg": "", "count": 0, "data": isOk}) } func (impl NumInfoControllerImpl) FindAll(c *gin.Context) { numInfos := impl.dao.FindAllNumInfo(c) c.JSON(200, map[string]interface{}{"code": 0, "msg": "", "count": len(numInfos), "data": numInfos}) } func (impl NumInfoControllerImpl) FindNumById(c *gin.Context) { id := c.Param("id") i, _ := strconv.Atoi(id) numInfo := impl.dao.GetNumInfoById(c, int64(i)) c.JSON(200, map[string]interface{}{"code": 0, "msg": "", "count": 0, "data": numInfo}) } func (impl NumInfoControllerImpl) Update(c *gin.Context) { body := c.Request.Body jsonBytes, err := ioutil.ReadAll(body) d := json.NewDecoder(bytes.NewReader(jsonBytes)) d.UseNumber() m := make(map[string]string) d.Decode(&m) if err != nil { panic(err) } info := entity.NumInfo{ Id: cast.ToInt64(m["id"]), Name: m["name"], InfoKey: m["info_key"], InfoNum: cast.ToInt64(m["info_num"]), } isOk := impl.dao.UpdateNumInfoById(c, info) c.JSON(200, map[string]interface{}{"code": 0, "msg": "", "count": 0, "data": isOk}) }
拦截器装备
package interceptor import ( "github.com/gin-gonic/gin" "log" ) // HttpInterceptor 自定义拦截器 func HttpInterceptor() gin.HandlerFunc { return func(c *gin.Context) { // 设置 example 变量 c.Set("example", "12345") // 恳求前 log.Print("--------------拦截器-------------") //定义错误,停止并返回该JSON //c.AbortWithStatusJSON(500, "error") //requestURI := c.Request.RequestURI //fmt.Println(requestURI) //通过恳求 c.Next() } }
router
package web import ( "count_num/pkg/config" "count_num/pkg/web/controller" "count_num/pkg/web/interceptor" "github.com/gin-gonic/gin" ) func RunHttp() { r := gin.Default() //增加拦截器 r.Use(interceptor.HttpInterceptor()) //处理跨域 r.Use(config.CorsConfig()) //路由组 appInfoGroup := r.Group("/") { appInfoGroup.POST("/add/:key", controller.NewNumInfoControllerImpl().AddNumByKey) appInfoGroup.GET("/findByKey/:key", controller.NewNumInfoControllerImpl().FindNumByKey) appInfoGroup.GET("/findById/:id", controller.NewNumInfoControllerImpl().FindNumById) appInfoGroup.POST("/saveInfo", controller.NewNumInfoControllerImpl().SaveNumInfo) appInfoGroup.POST("/deleteInfo/:id", controller.NewNumInfoControllerImpl().DeleteById) appInfoGroup.GET("/getAll", controller.NewNumInfoControllerImpl().FindAll) appInfoGroup.POST("/update", controller.NewNumInfoControllerImpl().Update) } r.Run("127.0.0.1:" + config.PORT) }
3.3 装备和实体结构体
package config import ( "fmt" _ "github.com/go-sql-driver/mysql" "github.com/spf13/viper" "gorm.io/driver/mysql" "gorm.io/gorm" ) var DB *gorm.DB func init() { var err error viper.SetConfigName("app") viper.SetConfigType("properties") viper.AddConfigPath("./") err = viper.ReadInConfig() if err != nil { panic(fmt.Errorf("Fatal error config file: %w n", err)) } if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); ok { fmt.Println("No file ...") } else { fmt.Println("Find file but have err ...") } } PORT = viper.GetString("server.port") url := viper.GetString("db.url") db := viper.GetString("db.databases") username := viper.GetString("db.username") password := viper.GetString("db.password") dsn := username + ":" + password + "@tcp(" + url + ")/" + db + "?charset=utf8mb4&parseTime=True&loc=Local" DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { panic(err) } }
跨域问题
package config import ( "github.com/gin-gonic/gin" "net/http" ) var PORT string func CorsConfig() gin.HandlerFunc { return func(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") // 可将将 * 替换为指定的域名 c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE") c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization") c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type") c.Header("Access-Control-Allow-Credentials", "true") c.Writer.Header().Set("Access-Control-Max-Age", "86400") if c.Request.Method == http.MethodOptions { c.AbortWithStatus(200) } else { c.Next() } } }
实体
package entity type NumInfo struct { Id int64 `json:"id"` Name string `json:"name"` InfoKey string `json:"info_key"` InfoNum int64 `json:"info_num"` } func (stu NumInfo) TableName() string { return "num_info" }
3.4 发动主函数
package main import "count_num/pkg/web" func main() { web.RunHttp() }
3.5 装备文件和SQL
server.port=9888 db.driver=mysql db.url=127.0.0.1:3306 db.databases=test db.username=root db.password=12345
SQL文件
CREATE TABLE `num_info` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `info_key` varchar(255) NOT NULL, `info_num` int(11) DEFAULT NULL, PRIMARY KEY (`id`, `info_key`) ) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8;
4 前端代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>后台管理</title>
<link rel="stylesheet" href="https://juejin.im/post/layui/css/layui.css">
</head>
<body>
<div>
<button class="layui-btn layui-btn-normal" id="add_btn" lay-filter="add_btn">增加一个</button>
<table id="demo" lay-filter="test"></table>
</div>
<script src="https://juejin.im/post/layui/layui.js"></script>
<script src="https://juejin.im/post/layui/jquery.min.js"></script>
<script>
var base_url = 'http://localhost:9888'
layui.use('element', function () {
var element = layui.element;
});
layui.use('table', function () {
var table = layui.table;
//第一个实例
table.render({
elem: '#demo'
, height: 500
, url: base_url + '/getAll' //数据接口
, page: false //开启分页
, cols: [[ //表头
{field: 'id', title: 'ID', width: '10%'}
, {field: 'name', title: '称号', width: '30%'}
, {field: 'info_key', title: '标记', sort: true, width: '20%'}
, {field: 'info_num', title: '次数', width: '20%', sort: true}
, {title: '操作', width: '20%', toolbar: '#barDemo'}
]]
});
//工具条事情
table.on('tool(test)', function (obj) {
var data = obj.data;
var layEvent = obj.event;
var tr = obj.tr;
if ('edit' == layEvent) {
layer.open({
title: '修正',
type: 1,
content: '<form class="layui-form">' +
' <div class="layui-form-item">n' +
' <label class="layui-form-label">称号</label>n' +
' <div class="layui-input-inline">n' +
' <input type="text" class="layui-input" id="u_name" value="' + data.name + '">n' +
' </div>n' +
' </div>' +
' <div class="layui-form-item">n' +
' <label class="layui-form-label">标记</label>n' +
' <div class="layui-input-inline">n' +
' <input type="text" class="layui-input" id="u_key" value="' + data.info_key + '">n' +
' </div>n' +
' </div>' +
' <div class="layui-form-item">n' +
' <label class="layui-form-label">次数</label>n' +
' <div class="layui-input-inline">n' +
' <input type="text" class="layui-input" id="u_num" value="' + data.info_num + '">n' +
' </div>n' +
' </div>' +
'<div class="layui-form-item">n' +
' <div class="layui-input-block">n' +
' <button class="layui-btn" type="button" onclick="update()">确定修正</button>n' +
' <button type="reset" class="layui-btn layui-btn-primary">重置</button>n' +
' </div>n' +
' </div>' +
' <input id="u_id" value="' + data.id + '">' +
'</form>'
});
} else if ('del' == layEvent) {
layer.confirm('确定删去吗?', function (index) {
//点击确认时执行
$.ajax({
url: base_url + '/deleteInfo/'+data.id,
type: 'POST',
success: function (r) {
if (r.data) {
location.reload();
}
}
})
layer.close(index);
});
}
});
});
$('#add_btn').on('click', function () {
layer.open({
title: '增加',
type: 1,
content: '<form class="layui-form">' +
' <div class="layui-form-item">n' +
' <label class="layui-form-label">称号</label>n' +
' <div class="layui-input-inline">n' +
' <input type="text" class="layui-input" id="s_name">n' +
' </div>n' +
' </div>' +
' <div class="layui-form-item">n' +
' <label class="layui-form-label">标记</label>n' +
' <div class="layui-input-inline">n' +
' <input type="text" class="layui-input" id="s_key">n' +
' </div>n' +
' </div>' +
'<div class="layui-form-item">n' +
' <div class="layui-input-block">n' +
' <button class="layui-btn" onclick="save()" type="button">当即提交</button>n' +
' <button type="reset" class="layui-btn layui-btn-primary">重置</button>n' +
' </div>n' +
' </div>' +
'</form>'
});
});
</script>
<script>
function save() {
var name = $("#s_name").val();
var key = $("#s_key").val();
$.ajax({
url: base_url + '/saveInfo',
type: 'POST',
data: JSON.stringify({"name": name, "info_key": key}),
contentType: 'application/json',
success: function (r) {
if (r.data) {
location.reload();
}
}
})
}
function update() {
var id = $("#u_id").val();
var name = $("#u_name").val();
var key = $("#u_key").val();
var num = $("#u_num").val();
$.ajax({
url: base_url + '/update',
type: 'POST',
data: JSON.stringify({"id": id, "name": name, "info_key": key, "info_num": num}),
contentType: 'application/json',
success: function (r) {
if (r.data) {
location.reload();
}
}
})
}
</script>
<script type="text/html" id="barDemo">
<a class="layui-btn layui-btn-xs" lay-event="edit">修正</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删去</a>
</script>
</body>
</html>
5 遇见的问题及排查方法
5.1 GORM的运用问题
5.1.1 自定义表名
func (stu NumInfo) TableName() string { return "num_info" }
5.1.2 主键自增
impl.db.Save(&info) //要运用指针
5.2 跨域问题
func CorsConfig() gin.HandlerFunc { return func(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") // 可将将 * 替换为指定的域名 c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE") c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization") c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type") c.Header("Access-Control-Allow-Credentials", "true") c.Writer.Header().Set("Access-Control-Max-Age", "86400") if c.Request.Method == http.MethodOptions { c.AbortWithStatus(200) } else { c.Next() } } }
在Gin中处理跨域问题
func RunHttp() { r := gin.Default() ...... //处理跨域 r.Use(config.CorsConfig()) ...... r.Run("127.0.0.1:" + config.PORT) }
5.3 JSON字段转int64失效问题
func (impl NumInfoControllerImpl) Update(c *gin.Context) { body := c.Request.Body jsonBytes, err := ioutil.ReadAll(body) //先将JSON转为Map d := json.NewDecoder(bytes.NewReader(jsonBytes)) d.UseNumber() m := make(map[string]string) d.Decode(&m) if err != nil { panic(err) } //再将Map转为实体 info := entity.NumInfo{ Id: cast.ToInt64(m["id"]), Name: m["name"], InfoKey: m["info_key"], InfoNum: cast.ToInt64(m["info_num"]), } isOk := impl.dao.UpdateNumInfoById(c, info) c.JSON(200, map[string]interface{}{"code": 0, "msg": "", "count": 0, "data": isOk}) }
6 总结
好了,今日的分享就到这里,虽然学习了很长时刻的Go言语,可是建立这样较为完好的事务体系的时机不是许多,过程中也遇到了几个问题,可是都利用官方文档或搜索引擎独立的处理了。
当然目前的后端事务体系只支撑restful风格的Http恳求,假如后续有时刻的话还会增加相同功用的rpc接口来做扩展,相关的GitHub地址分享给我们,假如有哪些地方需求改进和优化,还我们请多多指教!
源码获取方法:关注公众号[ 扯编程的淡 ],回复:0615
我正在参加技能社区创作者签约计划招募活动,点击链接报名投稿。
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
评论(0)