分析golong—Web后端的三大层次 | 青训营

分析golong—Web后端的三大层次 | 青训营

业务描述如下:

查询自己(一个用户)的评论。数据格式为json

我们猜想:每一个用户存在一个或多个评论,必然在每个评论中数据格式必须包含自己的唯一id,而数据在cup上以map集合的方式,key:用户id,value:评论数据

类似Java的mvc三层架构

回顾一下Javaweb经典三层架构:

  1. dao 数据持久层
  2. service 业务逻辑层
  3. controller 视图层

1. 数据持久层 repository

在此之前先看一下数据的存在形式,我们以文本存储代替数据库存储

1.1. post(评论)
{"id":1,"parent_id":1,"content":"小姐姐快来1","create_time":1650437616}
{"id":2,"parent_id":1,"content":"小姐姐快来2","create_time":1650437617}
{"id":3,"parent_id":1,"content":"小姐姐快来3","create_time":1650437618}
{"id":4,"parent_id":1,"content":"小姐姐快来4","create_time":1650437619}
{"id":5,"parent_id":1,"content":"小姐姐快来5","create_time":1650437620}
{"id":6,"parent_id":2,"content":"小哥哥快来1","create_time":1650437621}
{"id":7,"parent_id":2,"content":"小哥哥快来2","create_time":1650437622}
{"id":8,"parent_id":2,"content":"小哥哥快来3","create_time":1650437623}
{"id":9,"parent_id":2,"content":"小哥哥快来4","create_time":1650437624}
{"id":10,"parent_id":2,"content":"小哥哥快来5","create_time":1650437625}
1.2. topic(用户或话题)看自己理解
{"id":1,"title":"青训营来啦!","content":"小姐姐,快到碗里来~","create_time":1650437625}
{"id":2,"title":"青训营来啦!","content":"小哥哥,快到碗里来~","create_time":1650437640}
看这里:
对上述数据格式进行存储定义,注意:go中以struct代替Java中的类class

type Post struct {  
id int64 `json:"id"`  
ParentId int64 `json:"parent_id"`  
Content string `json:"content"`  
CreateTime int64 `json:"create_time"`  
}
//----注意:两个结构体存在自己的.go文件中,为的是代码可读性
type Post struct {  
id int64 `json:"id"`  
ParentId int64 `json:"parent_id"`  
Content string `json:"content"`  
CreateTime int64 `json:"create_time"`  
}

我们在后续的查询要求必须只有一次查询,在这里我们创造dao,这里的dao作用只是为了限制查询次数,这里使用空结构体,作用在下面会说的

type PostDao struct {  
}
type TopicDao struct {  
}

限制次数可以使用package sync下once.go中的Do方法

var (  
postDao *PostDao  
PostOnce sync.Once  
)  
  
func NewPostDaoInstance() *PostDao {  
PostOnce.Do(  
func() {  
postDao = &PostDao{}  
})  
return postDao  
}  
func (*PostDao) QueryPostsByParentId(parentId int64) []*Post {  
return postIndexMap[parentId]  
}
var (  
topicDao *TopicDao  
topicOnce sync.Once  
)  
  
func NewTopicDaoInstance() *TopicDao {  
topicOnce.Do(  
func() {  
topicDao = &TopicDao{}  
})  
return topicDao  
}  
func (*TopicDao) QueryTopicById(id int64) *Topic {  
return topicIndexMap[id]  
}

欧克,直接回到主方法,之前,讲一下上面为什么使用空的结构体。上述代码New*函数返回的是空的结构体,使用sync.Once中的Do方法,保证每次都是使用最开始创建的结构体。这也是和Java不同的区别,使用的是指针,传递的是地址。OK!讲到这里我想你们也有自己的思路了,在Java中做到这一点也非常简单,我们可以实现单例模式。上我们回顾一下Java的单例模式,使用私有属性执行自身,提供共有的函数给这个私有属性赋值。话不多说直接上代码:

public class Singleton { 
// 1.防止外部直接 new 对象破坏单例模式
private Singleton() {} 
// 2.通过私有变量保存单例对象【添加了 volatile 修饰】 
private static volatile Singleton instance = null; 
// 3.提供公共获取单例对象的方法 
public static Singleton getInstance() { 
    if (instance == null) {
        // 第 1 次效验 
        synchronized (Singleton.class) { 
            if (instance == null) { 
            // 第 2 次效验 
            instance = new Singleton(); 
            } 
       } 
    } 
    return instance; 
} 
}

2. 主函数

func main() {  
if err := Init("./src/data/"); err != nil {  
os.Exit(-1)  
}  
r := gin.Default()  
r.GET("/community/page/get/:id", func(c *gin.Context) {  
topicId := c.Param("id")  
data := cotroller.QueryPageInfo(topicId)  
c.JSON(200, data)  
})  
err := r.Run()  
if err != nil {  
return  
}  
}  
  
func Init(filePath string) error {  
if err := repository.Init(filePath); err != nil {  
return err  
}  
return nil  
}

ok,一目了然,主函数首先调用持久层的Init方法,进行数据持久化,即,对数据进行装载
上述也讲过两种数据在内存中的存在形式

var (  
topicIndexMap map[int64]*Topic  
postIndexMap map[int64][]*Post  
)

ok 介绍Init方法

func Init(filePath string) error {  
if err := initTopicIndexMap(filePath); err != nil {  
return err  
}  
if err := initPostIndexMap(filePath); err != nil {  
return err  
}  
return nil  
}

Init方法中调用了两个方法initTopicIndexMap(param string),initPostIndexMap(param string)

func initTopicIndexMap(filePath string) error {  
open, err := os.Open(filePath + "topic")  
if err != nil {  
return err  
}  
scanner := bufio.NewScanner(open)  
topicTmpMap := make(map[int64]*Topic)  
for scanner.Scan() {  
text := scanner.Text()  
var topic Topic  
if err := json.Unmarshal([]byte(text), &topic); err != nil {  
return err  
}  
topicTmpMap[topic.Id] = &topic  
}  
topicIndexMap = topicTmpMap  
return nil  
}  
//==========================================================
func initPostIndexMap(filePath string) error {  
open, err := os.Open(filePath + "post")  
if err != nil {  
return err  
}  
scanner := bufio.NewScanner(open)  
postTmpMap := make(map[int64][]*Post)  
for scanner.Scan() {  
text := scanner.Text()  
//json化post  
var post Post  
if err := json.Unmarshal([]byte(text), &post); err != nil {  
return err  
}  
//初始化postIndexMap  
posts, ok := postTmpMap[post.ParentId]  
//获取结果第一次为false  
if !ok {  
//真正更新postIndexMap  
postTmpMap[post.ParentId] = []*Post{&post}  
continue  
}  
posts = append(posts, &post) //将json化结果进行拼接  
postTmpMap[post.ParentId] = posts  
}  
postIndexMap = postTmpMap  
return nil  
}

json.Unmarshal([]byte(text), &topic):将一个json格式的数据反序列化为topic

request := DictRequest{TransType: "en2zh", Source: word}  
buf, err := json.Marshal(request)  
if err != nil {  
log.Fatal(err)  
}  
var data = bytes.NewBuffer(buf)

ok,回到主函数,在数据初始化成功后调用视图层

3. controller视图层

  
type PageData struct {  
Code int64 `json:"code"`  
Msg string `json:"msg"`  
Data interface{} `json:"data"`  
}  
  
func QueryPageInfo(topicIdStr string) *PageData {  
topicId, err := strconv.ParseInt(topicIdStr, 10, 64)  
//错误存在返回
if err != nil {  
return &PageData{  
Code: -1,  
Msg: err.Error(),  
}  
} 
//无错误,进行调用service层进行数据存储
pageInfo, err := service.QueryPageInfo(topicId)  
//调用时候存在错误,返回错误
if err != nil {  
return &PageData{  
Code: -1,  
Msg: err.Error(),  
}  
}  
//无错误,返回数据
return &PageData{  
Code: 0,  
Msg: "success",  
Data: pageInfo,  
}  
  
}

代码我就不一一介绍
controller调用service层

4. service业务逻辑层

type PageInfo struct {  
Topic *repository.Topic  
PostList []*repository.Post  
}

对外接口,下面是一个查询观点的函数,见名知意

func QueryPageInfo(topicId int64) (*PageInfo, error) {  
return NewQueryPageInfoFlow(topicId).Do()  
}

ok!, 代码时候调用另一个函数。这里我也有一点不太理解,语法。。。
该代码定义了一个名为Do()的方法,它属于QueryPageInfoFlow结构体。该方法不接受任何参数,并返回两个值:指向PageInfo对象的指针和一个错误。

type QueryPageInfoFlow struct {  
topicId int64  
pageInfo *PageInfo  
  
topic *repository.Topic  
posts []*repository.Post  
}  
  //==============
func NewQueryPageInfoFlow(topId int64) *QueryPageInfoFlow {  
return &QueryPageInfoFlow{  
topicId: topId,  
}  
}  
  //==============
func (f *QueryPageInfoFlow) Do() (*PageInfo, error) {  
if err := f.checkParam(); err != nil {  
return nil, err  
}  
if err := f.prepareInfo(); err != nil {  
return nil, err  
}  
if err := f.packPageInfo(); err != nil {  
return nil, err  
}  
return f.pageInfo, nil  
}

func (f *QueryPageInfoFlow) Do() (*PageInfo, error){} *QueryPageInfoFlow 是一个指针类型,指向*QueryPageInfoFlow类型的值。 可以被*QueryPageInfoFlow类型的变量调用

func (f *QueryPageInfoFlow) checkParam() error {  
if f.topicId <= 0 {  
return errors.New("topic id must be larger than 0")  
}  
return nil  
}
func (f *QueryPageInfoFlow) prepareInfo() error {  
//获取topic信息  
var wg sync.WaitGroup  
wg.Add(2)  
go func() {  
defer wg.Done()  
topic := repository.NewTopicDaoInstance().QueryTopicById(f.topicId)  
f.topic = topic  
}()  
//获取post列表  
go func() {  
defer wg.Done()  
posts := repository.NewPostDaoInstance().QueryPostsByParentId(f.topicId)  
f.posts = posts  
}()  
wg.Wait()  
return nil  
}
func (f *QueryPageInfoFlow) packPageInfo() error {  
f.pageInfo = &PageInfo{  
Topic: f.topic,  
PostList: f.posts,  
}  
return nil  
}

欧克,欧克,到这里了,下面我介绍一下golang特性,在后面文章中也会对其特性在具体案例中进行解释,喜欢可以点点关注!!!
Go语言是一种开源的静态类型编程语言,具有以下特性:

  1. 简洁易读:Go语言的语法设计简洁清晰,注重可读性,减少了冗余的语法元素,使得代码更易于理解和维护。
  2. 高效编译:Go语言的编译速度非常快,可以快速生成可执行文件,加快开发和部署的速度。
  3. 并发支持:Go语言内置了轻量级的协程(goroutine)和通信机制(channel),使得并发编程更加简单和高效。通过goroutine和channel,可以实现并发任务的编排和通信,提高程序的性能和响应能力。
  4. 内存安全:Go语言通过内置的垃圾回收机制(garbage collector)和严格的内存访问规则,保证了程序的内存安全性,减少了常见的内存错误,如空指针引用和内存泄漏。
  5. 静态类型和类型推断:Go语言是静态类型语言,变量需要在编译时声明类型。但是,Go语言也支持类型推断,可以根据变量的赋值自动推导出变量的类型,简化了类型声明的过程。
  6. 内置并发模型:Go语言提供了内置的并发模型,包括goroutine和channel,使得编写并发代码变得更加简单和直观。同时,标准库中还提供了丰富的并发相关的包和工具,如sync、atomic、context等,方便开发者进行并发编程。
  7. 内置网络编程支持:Go语言标准库中提供了强大的网络编程支持,包括HTTP、TCP、UDP等协议的封装和实现,使得开发网络应用变得更加便捷和高效。
  8. 跨平台支持:Go语言的编译器可以生成不同平台的可执行文件,因此可以轻松地在不同操作系统和硬件架构上部署和运行Go程序。
  9. 强调工程化:Go语言鼓励使用模块化和规范化的代码组织方式,提供了包管理工具(go mod)和标准的代码风格指南,便于团队协作和项目维护。
  10. 丰富的标准库:Go语言的标准库提供了许多常用的功能和工具,包括字符串处理、文件操作、加密解密、正则表达式、测试框架等,开发者可以直接使用这些库来加速开发过程。

这些特性使得Go语言成为一种适用于构建高效可靠并发的应用程序的编程语言。

全部评论

相关推荐

无情咸鱼王的秋招日记之薛定谔的Offer:好拒信,偷了,希望有机会用到
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务