Go语言学习(二)

embedded/2024/12/23 5:42:27/

一、Go项目结构抽象

├── bin                      # 存放项目过程中生成的可执行文件
├── script                   # 存放项目中需要使用的脚本文件,shell脚本,或者项目使用的功能性脚本
├── document                 # 项目的说明文档
│   ├── README.md
├── schema                   # 自定义的,可以用于存放定义的请求结构体,常量等
│   ├── actions
│   │   ├── actions.go
│   ├── common
│   │   ├── common.go
├── Dockerfile               # docker文件
├── conf_online              # 线上的配置文件,同理还有测试配置,线下配置等
│   ├── port.conf            # 配置端口等
│   ├── auth.toml            # 配置用户认证等SK,AK等内容
│   ├── logit                # 配置日志,如日志文件名,大小,日志等级,日志格式等,可以针对不同的Server配置不同的内容,Redis,MySQL,HTTPServer等各自使用不同的配置文件
│   ├── app.toml             # 此项目的配置,项目名称,端口,发布模式
│   ├── const.toml           # 全局常量配置
│   ├── server.toml          # 读写超时,监听端口
│   ├── servicer             # 针对不同的服务,配置相关内容,如Redis和MySQL连接配置
│   ├── derive               # 驱动配置
│   ├── bos.toml             # 使用BOS存储的相关配置AK,SK,Domain等信息
├── data                     # 项目需要使用的数据存放目录
│   ├── static_dict
│   ├── dynamic_dict
│   ├── var
├── go.mod                   # mod文件
├── go.env                   # Go环境变量
├── vendor                   # 使用的第三方库
├── library                  # 自己写的库文件
│   ├── resource             # 错误类型,返回定义
│   ├── utils                # 其他工具
│   ├── mysql                # 操作MySQL相关
│   ├── bos                  # 操作BOS相关工具
│   ├── redisclient          # 客户端测试相关
│   ├── types                # 类型定义
├── conf                     # 同上
│   ├── port.conf
│   ├── auth.toml
│   ├── logit
│   ├── app.toml
│   ├── const.toml
│   ├── server.toml
│   ├── servicer
│   ├── afsshell.conf
│   ├── bos.toml
├── Makefile
├── main.go                  # 主入口文件
├── go.sum
├── webroot
│   ├── README.md
├── log                      # 项目的日志输出文件,可以按日志输出的服务和类型进行分类
│   ├── panic
│   ├── service
├── servers                  # 服务端
│   ├── adminserver
│   │   ├── admin.go
│   │   ├── controller
│   ├── httpserver
│   │   ├── router.go        # 路由配置,添加对应的controller
│   │   ├── server.go        # 创建一个server
│   │   ├── controller       # 进行参数校验,调用service
│   │   │   ├── hello.go
│   ├── start.go             # 启动服务
├── bootstrap                
│   ├── shutdown.go          
│   ├── bootstrap.go         # 启动前的各种初始化操作,初始化日志,Redis,MySQL
├── model
│   ├── service              # 调用Dao层进行工作
│   ├── dao                  # 和数据库层直接进行操作
├── README.md

路由管理将请求路径映射到指定的控制器的指定方法中,控制器中只做简单的任务,主要还是调用业务层的方法来完成主要逻辑,定义实体时,可一并设置实体属性的赋值方法以及创建实体的方法。如果有一些实体使用的常量可以单独在一个包中定义出来,定义实体之后,就需要定义和实体相关的方法并放入到一个接口中,该接口所拥有的方法在具体的业务层使用,业务层只需要引入该接口的实例即可。

二、命令使用

go build -tags "tag1"新增标签
go version -m ${buldfile} | tail -n 5查看版本控制信息
go version -m ${buildfile} | grep tag1查看标签信息
go mod vendor读取mod文件,下载第三方代码到vendor文件夹中

go build -mod=vendor

go test -mod=vendor

指定使用vendor中的依赖包
go work没有go work命令之前,本地只能存放一个版本的第三方包,如果需要使用不同的包版本,就需要在go.mod中增加replace映射,使用go work可以轻松的管理各个包之间的依赖关系,可以通过-workfile=off关闭工作区

三、常见错误

(1)拷贝错误,目的切片没有开辟空间

package mainimport "fmt"func main() {src := []int{0, 1, 2}var dst []intcopy(dst, src)fmt.Println(dst)
}
// 正确代码
func main() {src := []int{0, 1, 2}dst := make([]int, len(src)) // 分配长度与 src 相同的切片copy(dst, src)fmt.Println(dst)
}

(2)在range中循环使用指针元素的影响

package mainimport "fmt"func main() {items := []int{10, 20, 30}var pointers []*intfor _, item := range items {pointers = append(pointers, &item)}for _, p := range pointers {fmt.Println(*p)}
}
// 输出
30
30
30

两种解决方法,在循环内部再创建一个临时变量

func main() {items := []int{10, 20, 30}var pointers []*intfor _, item := range items {t := itempointers = append(pointers, &t)}for _, p := range pointers {fmt.Println(*p)}
}
// 输出 10 20 30

或者直接使用真实元素的地址

func main() {items := []int{10, 20, 30}var pointers []*intfor i := range items {pointers = append(pointers, &items[i])}for _, p := range pointers {fmt.Println(*p)}
}
// 输出 10 20 30

或者在Go 1.21中,使用环境变量GOEXPERIMENT=loopvar能够控制每次循环都会创建新的循环变量。

四、Dao层参考结构

type (Entity struct {ID     int64 `gorm:"column:id;type:bigint(20) unsigned;primary_key;AUTO_INCREMENT;comment:自增主键" json:"id"`}QueryCond struct {EntityQueryOptions mysql.QueryOptions}DaoObj struct {GormDB *gorm.DB}
)

为了方便接口和代码分离,可以专门定义一个接口文件,用来定义Dao层需要实现的各种方法,比如定义一个api.go文件,在该文件中定义API接口。

type OpenAPI interface {Modify(ctx context.Context, e *Entity) (error)
}    

定义OpenAPI之后,为了约束某个Dao层对象完全实现所定义的API 接口,可以使用类型赋值的方式进行检测。

var (_ OpenAPI = (*DaoObj)(nil)
)

 这段代码的含义如下:将nil值转换为*DaoObj类型,nil代表*DaoObj类型的零值,转换为*DaoObj类型之后,赋值给OpenAPI类型的_,这里使用_作为变量名,是因为这里只需要做类型检查,而不需要保存这个变量,如果*DaoObj类型没有完全实现OpenAPI里面定义的方法,这里赋值就会报错,方便进行错误提示和检查。

在service层调用dao层服务时,根据DaoObj进行相关操作,如果Dao有多个,即有多个数据表Model时,可以将多个DaoModel集中到一个Service对象中,定义新的SvrModel类型,

type (SvrModel struct {DaoModel1 *daoModel1.DaoObj1DaoModel2 *daoModel2.DaoObj2DaoModel3 *daoModel3.DaoObj3}
)

定义一个方法构造SvrModel指针类型的对象,对DaoModel1,DaoModel2,DaoModel3进行赋值,因此Dao层也需要方法能够构造DaoObj2,DaoObj2,DaoObj3对指针类型。

另外,在事物中,尽量不要放查询操作,这种耗时的操作应该尽量放在开启事物之前,使用协程进行并行查询。其次,Dao层之间不要互相引用,每个Dao层只完成自己对应表的操作,如果需要多个表操作,放在service层中进行。

其他常量或者结构体,可以单独定义在schema(模式、架构)中,举一个例子如下:

type ListReq struct {Page     uint   `form:"page,default=1" json:"page,omitempty"`
}
  • form:"page,default=1"

    • form:这部分指明这个字段在解析来自表单(Form)数据时的行为。
    • page:这告诉解析器,当从表单数据中读取时,应该将对应的表单值映射到这个 Page 字段。
    • default=1:这表示如果在表单数据中没有找到 page,则默认将 Page 字段的值设置为 1
  • json:"page,omitempty"

    • json:这部分指定了在进行 JSON 序列化或反序列化时如何处理这个字段。
  • page:这指出在 JSON 数据中对应的键是 page
  • omitempty:这是一个选项,表示如果字段 Page 的值是零值(对于 uint 类型,零值是 0),则在生成的 JSON 数据中省略这个字段。这有助于减少 JSON 数据的大小,特别是在数据传输或存储时。

五、定义API层

每个业务模块建立一个文件夹,比如业务模块A和B分别用来完成针对业务模块A和B的任务,在每个业务模块中,定义三个go文件,一个go文件用来专门定义API接口和常用的请求返回结构体,大概代码样式:

package atask
import "context"type (APIer interface {Add(ctx context.Context, record string) errorDelete(ctx context.Context, id int) errorUpdate(ctx context.Context, id int, record string) errorQuery(ctx context.Context, id int) (queryRes, error)}queryRes struct {ID int64 `json:"id"`Record string `json:"msg"`}
)

另一个go文件中,负责提供创建对象的方法,比如提供New方法,可以返回一个结构体对象,要求这个结构体对象完成APIer的接口,一般在定义个结构体,用该结构体接收数据,将该结构体作为数据部分封装到另外一个结构体中,提供创建该结构体的方法。要让该结构体有API功能,需要将该结构体对象复制为API类型结构体。

type (API struct {APIer}Opts struct {Sk string `yaml:"sk"`AK string `yaml:"ak"`}objAPI struct {opts *Opts}
)func NewOptions(ctx context.Context) (opts *Opts, error) {}
func NewobjAPI(opts *Opts) APIer {return &objAPI{opts: opts,}
}func New(o *Opts) (APIer, error) {return &API{NewobjAPI(o)}, nil
}

第三个go文件则负责实现具体的API接口,

func (objapi *objAPI) Add(...)...
func (objapi *objAPI) Delete(...)...
func (objapi *objAPI) Update(...)...
func (objapi *objAPI) Query(...)...


http://www.ppmy.cn/embedded/147988.html

相关文章

《开启微服务之旅:Spring Boot 从入门到实践》(三)

自动配置原理 配置文件到底能写什么?怎么写?自动配置原理; https://docs.spring.io/spring-boot/docs/1.5.9.RELEASE/reference/htmlsingle/#common-application-properties 自动配置原理 SpringBoot启动的时候加载主配置类,开启…

Python 从入门到实战46(Matplotlib绘制常用表)

我们的目标是:通过这一套资料学习下来,可以熟练掌握python基础,然后结合经典实例、实践相结合,使我们完全掌握python,并做到独立完成项目开发的能力。 上篇文章我们学习了pandas数据操作的相关基础知识。今天学习一下M…

Nginx界的天花板-Oracle 中间件OHS 11g服务器环境搭建

环境信息 服务器基本信息 如下表,本次安装总共使用2台服务器,具体信息如下: 服务器IP DNS F5配置 OHS1 172.xx.xx.xx ohs01.xxxxxx.com ohs.xxxxxx.com OHS2 172.xx.xx.xx ohs02.xxxxxx.com 服务器用户角色信息均为:…

Java 集合框架中的 List、ArrayList 和 泛型 实例

— Java 集合框架中的 List、ArrayList 和 泛型 在 Java 中,集合框架提供了许多不同类型的集合类,用于存储和操作对象。List 和 ArrayList 是最常用的两种集合类型,而泛型(Generics)则是 Java 中的一项重要特性&…

Python实现应用最小二乘法融合SVM-LSTM回归模型电力负荷预测项目实战

说明:这是一个机器学习实战项目(附带数据代码文档视频讲解),如需数据代码文档视频讲解可以直接到文章最后关注获取。 1.项目背景 随着全球能源需求的不断增长,电力系统的稳定性和效率变得至关重要。准确的电力负荷预测…

融合机器学习算法:用VotingClassifier实现分类多模型的投票集成

背景 VotingClassifier是一种集成学习方法,它将多个不同的机器学习模型组合起来,通过投票的方式对分类任务做出决策,从而提高整体模型的预测性能和鲁棒性 VotingClassifier的核心机制 VotingClassifier提供两种主要的投票策略硬投票和软投…

设计模式学习[13]---抽象工厂模式+简单工厂+工厂方法模式回顾

文章目录 前言1.原理阐述1.1 说明11.2 说明2 2.举例 总结 前言 之前写过一些工厂的相关内容,详情见这两篇:简单工厂与工厂方法 这篇博客主要讲抽象工厂模式。 1.原理阐述 1.1 说明1 抽象工厂模式:提供一个创建一系列相关或相互依赖对象的…

Neo4j插入数据逐级提升速度4倍又4倍

语雀版:https://www.yuque.com/xw76/back/dtukgqfkfwg1d6yo 目录 背景介绍初始方案Node()创建事务批量提交记录Node是否存在生成Cypher语句执行数据库参数优化切换成85k个三元组测试建索引(很显著!!!)MATCH…