探索flutter框架开发的app在移动应用市场的潜力与挑战
920
2022-11-26
从0开始,用Go语言搭建一个简单的后端业务系统
Hello 小伙伴们,今天给大家带来了一份Go语言搭建后端业务系统的教程,restful风格哦,既然是简单的业务系统,那么必要的功能就少不了增删改查,也就是传说中的CRUD,当然相比Spring Boot而言,Go语言写后端业务系统不是那么的流行,但是对比一下我们也很容易能发现,Go语言搭建的Web后端系统的优势:
(1)内存占用更少(2)启动速度更快(3)代码更加简洁
1 业务&技术概括
1.1 业务功能
实体(NumInfo):
Id:主键Name :名称InfoKey :KeyInfoNum :Num
业务功能:
根据Key将Num加1根据Key查找根据ID查找添加一个根据ID删除一个查看全部根据ID修改一个
1.2 技术点
框架:
GinViperGORM
数据库:
MySQL
前端:
JQuerylayUI
2 接口规范
## 根据Key将Num加1req:resp:```json{ "code": "0", "msg": "true", "count": "0", "data":"true"}```## 根据Key查找req:resp:```json{ "code": "0", "msg": "true", "count": "0", "data":{ "id":"1" "name":"zs", "info_key":"zs", "info_num":"12" }}```## 根据ID查找req:resp:```json{ "code": "0", "msg": "true", "count": "0", "data":{ "id":"1" "name":"zs", "info_key":"zs", "info_num":"12" }}```## 添加一个req: "name":"ww", "info_key":"ww"}```### resp:```json{ "code": "0", "msg": "true", "count": "0", "data":"true"}```## 根据ID删除一个req:resp:```json{ "code": "0", "msg": "true", "count": "0", "data":"true"}```## 查看全部req: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修改一个req: "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 daoimport ( "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 implimport ( "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 controllerimport ( "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 interceptorimport ( "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 webimport ( "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 configimport ( "fmt" _ "github.com/go-sql-driver/mysql" "github.com/spf13/viper" "gorm.io/driver/mysql" "gorm.io/gorm")var DB *gorm.DBfunc 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 configimport ( "github.com/gin-gonic/gin" "net/PORT stringfunc 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 == { c.AbortWithStatus(200) } else { c.Next() } }}
实体
package entitytype 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 mainimport "count_num/pkg/web"func main() { web.RunHttp()}
3.5 配置文件和SQL
server.port=9888db.driver=mysqldb.url=127.0.0.1:3306db.databases=testdb.username=rootdb.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 前端代码
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 == { 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地址分享给大家,如果有哪些地方需要改良和优化,还大家请多多指教!
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~