前言
项目的的源代码来源于博主jun_chiang.通过对博主代码的学习,我对课程内容有了更全面的了解.感谢博主jun_chiang!
1编写结构体
根据课上给的例子编写结构体
代码文件放在文件夹entity中,代码内容如下
package entity
type Topic struct {
Id uint64
Title string
Content string
CreateTime time.Now().Unix()
}
package entity
type Post struct {
Id uint64
ParentId uint64
Content string
CreateTime time.Now().Unix()
}
因为id是数字,并且大于1,所以id类型为无符号整数,title和content都是字符串,create_time是时间戳. 在之后的代码中还会用到结构体pageinfo,用于存储返回查询结果.
package entity
// 页面信息结构体
type PageInfo struct {
Topic *Topic
PostList []*Post
}
2模拟编写数据库
我新建了文件夹repository,文件夹里有两个文件,一个文件存储json数据,另一个文件是.go文件,用来读取json数据,模拟数据库.
package repository
import (
"encoding/json"
"io"
"os"
"test/entity"
)
// 为类型定义别名 如果没有 ‘=’ 就是定义新的类型
type Topic = entity.Topic
type Post = entity.Post
// 存储数据的变量
var (
// 定义话题索引
TopicIndexMap map[uint]*Topic = make(map[uint]*Topic, 5)
// 定义并初始化话题回复索引
PostIndexMap map[uint][]*Post = make(map[uint][]*entity.Post, 5)
)
// 从文件初始话题索引
func InitTopicIndexMap() error {
// 读取json文件
jsonFile, err := os.Open("repository/initial_data.json")
if err != nil {
return err
}
// 延迟关闭文件流
defer jsonFile.Close()
// 读取全部文件
var topics []Topic
byteValue, err := io.ReadAll(jsonFile)
if err != nil {
return err
}
// json反序列化
err = json.Unmarshal(byteValue, &topics)
if err != nil {
return err
}
for i := range topics {
topic := topics[i]
// 以ID为key,把对象指针放到Map里面去
TopicIndexMap[uint(topic.Id)] = &topic
}
return nil
}
根据课程4.6的内容,我使用两个map来存储数据,把结构体的id作为key,结构体指针作为value,这样根据id查找我们需要的结构体对象的时间复杂度为O(1).
函数InitTopicIndexMap读取json文件中的内容并进行反序列化,然后将json文件中的所有内容存入map中.
3实现dao层
在课程中,没有定义dao层,通过截图来看,应该是将dao层内容写入了repository中. 我在这里模仿博主jun_chiang的写法.我先创建dao文件夹,文件夹中有文件topic_dao.go,topic_dao_impl.go. 这是topic_dao.go文件的源码
package dao
import "test/entity"
// TopicDao定义了查询Topic的方式
type TopicDao interface {
// 根据Topic的ID查询Topi对象
QueryTopicById(id uint) (*entity.Topic, error)
}
这是topic_dao_impl.go文件的源码
package memory
import (
"sync"
"test/entity"
"test/repository"
)
// 定义结构体
type TopicDaoImpl struct {
}
var (
// 声明结构体变量
topicDaoImpl *TopicDaoImpl
initialOnce sync.Once
)
// 获取TopicDaoImpl的全局唯一实例
func NewTopicDaoImplInstance() *TopicDaoImpl {
// 单例模式
initialOnce.Do(func() {
// 为结构体变量赋值
topicDaoImpl = &TopicDaoImpl{}
})
// 返回已经赋值的topicDaoImpl结构体对象指针
return topicDaoImpl
}
// 实现Topic_dao的方法
func (*TopicDaoImpl) QueryTopicById(id uint) (*entity.Topic, error) {
return repository.TopicIndexMap[id], nil
}
这里采用了单例模式,我不太清楚这样做的优势是什么,博主也没有讲.
4实现service层
service层要实现查询topic的功能,所以就只有一个文件query_page_info.go,因为这个demo只有一个功能.
这一层的功能主要就是控制业务的流程,分为三个部分
1 参数校验
2 准备数据
3 组装实体
这三个部分对应三个函数,分别是checkParam(),prepareInfo(),packPageInfo(); 这是query_page_info的源码
package service
import (
"errors"
"strconv"
"sync"
"test/dao"
"test/dao/memory"
"test/entity"
)
type QueryPageInfoFlow struct {
topicIdStr string
topicId uint
PageInfo *entity.PageInfo
topic *entity.Topic
postList []*entity.Post
}
// 通过TopicId查询页面信息的方法
func QueryPageInfo(topicIdStr string) (*entity.PageInfo, error) {
return NewQueryPageInfoFlow(topicIdStr).Do()
}
// 工厂函数,返回查询流程结构体的指针,用于启动流程
func NewQueryPageInfoFlow(topicIdStr string) *QueryPageInfoFlow {
return &QueryPageInfoFlow{
topicIdStr: topicIdStr,
}
}
// 查询页面信息的整个流程
func (f *QueryPageInfoFlow) Do() (*entity.PageInfo, error) {
// 参数校验
if err := f.checkParam(); err != nil {
return nil, err
}
// 准备数据
if err := f.prepareInfo(); err != nil {
return nil, err
}
// 组装实体
if err := f.packageInfo(); err != nil {
return nil, err
}
return f.PageInfo, nil
}
// 参数校验
func (f *QueryPageInfoFlow) checkParam() error {
topicId, err := strconv.ParseUint(f.topicIdStr, 0, 64)
if err != nil {
return errors.New("parse topicIdStr to uint64 failed")
}
if topicId <= 0 {
return errors.New("topic id must larger than 0")
}
f.topicId = uint(topicId)
return nil
}
// 获取Topic的信息以及对应的Post列表的信息
func (f *QueryPageInfoFlow) prepareInfo() error {
var wg sync.WaitGroup
wg.Add(2)
var topicErr, postErr error
// 获取Topic的信息
go func() {
defer wg.Done()
var topicDao dao.TopicDao = memory.NewTopicDaoImplInstance()
topic, err := topicDao.QueryTopicById(f.topicId)
if err != nil {
topicErr = err
return
}
f.topic = topic
}()
// 获取Post列表的信息
go func() {
defer wg.Done()
// 利用接口实现多态
var postDao dao.PostDao = memory.NewPostDaoImplInstance()
postList, err := postDao.QueryPostListByTopicId(f.topicId)
if err != nil {
postErr = err
return
}
f.postList = postList
}()
wg.Wait()
if topicErr != nil {
return topicErr
}
if postErr != nil {
return postErr
}
return nil
}
// 包装数据
func (f *QueryPageInfoFlow) packageInfo() error {
f.PageInfo = &entity.PageInfo{
Topic: f.topic,
PostList: f.postList,
}
return nil
}
checkParam()实现校验参数功能,实现的逻辑是判断topicid是否大于0,如果不大于0将报错.
prepareInfo()实现准备数据功能,实现的逻辑是通过dao层的函数QueryTopicById()获取id=topicid的topic,通过函数QueryPostListByTopicId()获取post.
packageInfo()实现组装实体功能,实现的逻辑是将查询到的topic和postList存入pageinfo中.
5实现controller
controller层包含两个文件common.go和query_page_info.go,前一个文件用来定义一个统一返回对象.另一个文件用来实现返回查询结果. 这是common.go的源码
package controller
// 任意类型的数据
type any = interface{}
// 返回给视图的结果的包装器
type ResultWrapper struct {
/*
2000 代表成功
4000 代表失败
*/
Code int32
Msg string
Data any
}
// 返回错误响应对象的快捷方法
func ServerFailed(msg string) *ResultWrapper {
wrapper := ResultWrapper{
Code: 4000,
Msg: msg,
Data: nil,
}
return &wrapper
}
// 返回正确响应对象的快捷方法
// data是要返回给前端的数据
func ServerSuccess(data any) *ResultWrapper {
wrapper := ResultWrapper{
Code: 2000,
Msg: "success",
Data: data,
}
return &wrapper
}
返回数据包含三个部分,Code字段负责返回状态码,用于标识当前业务的状态是成功还是失败,成功返回2000,失败则返回4000.Msg字段负责返回业务的提示语,如果成功返回success,如果失败则返回err.Error().err是service.QueryPageInfo(topicIdStr)返回的错误.Data字段是service层查询到的数据。
这是query_page_info.go的源码
package controller
import (
"net/http"
"test/service"
"github.com/gin-gonic/gin"
)
func QueryPageInfo(c *gin.Context) {
// 获取URL链接中的ID
topicIdStr := c.Param("topicId")
data, err := service.QueryPageInfo(topicIdStr)
if err != nil {
c.JSON(http.StatusOK, ServerFailed(err.Error()))
return
}
c.JSON(http.StatusOK, ServerSuccess(data))
}
代码的实现逻辑是将 topicIdStr作为参数传给函数service.QueryPageInfo(topicIdStr) ,然后函数将查询到的数据赋值给data.然后发送json数据.
router
在课程中router有四大功能分别是:初始化数据索引,初始化引擎配置,构建路由,启动服务.在这里分成了两部分,router.go文件只实现启动服务功能,其他三大功能由main.go文件实现.
router的功能是启动服务.
源码如下
package main
import (
"github.com/gin-gonic/gin"
"test/controller"
)
func initRouter(r *gin.Engine) {
apiRouter := r.Group("demo1")
apiRouter.GET("/queryPageInfo/:topicId", controller.QueryPageInfo)
}
main
main文件实现的功能是初始化数据索引,初始化引擎配置,构建路由这三个功能.
package main
import (
"fmt"
"test/repository"
"github.com/gin-gonic/gin"
)
func main() {
err := repository.InitTopicIndexMap()
if err != nil {
fmt.Println("数据库初始化出错:", err.Error())
}
r := gin.Default()
initRouter(r)
r.Run(":8081")
}
运行结果
项目运行后,可以看到8081端口已经开启了监听
输入命令后可以看到请求topic成功.
返回信息如下.
PS E:\Users\Dell\GolandProjects\test> curl http://localhost:8081/demo1/queryPageInfo/1
StatusCode : 200
StatusDescription : OK
Content : {"Code":2000,"Msg":"success","Data":{"Topic":{"Id":1,"Title":"青训营项目实战","Content":"一起来挑战自己","CreateTime":"2023-07-27 15:06:07"},"PostList":null}}
RawContent : HTTP/1.1 200 OK
Content-Length: 172
Content-Type: application/json; charset=utf-8
Date: Tue, 01 Aug 2023 04:36:06 GMT
{"Code":2000,"Msg":"success","Data":{"Topic":{"Id":1,"Title":"青训营项目实战","Cont...
Forms : {} Headers : {[Content-Length, 172], [Content-Type, application/json; charset=utf-8], [Date, Tue, 01 Aug 2023 04:36:06 GMT]} Images : {} InputFields : {} Links : {} ParsedHtml : mshtml.HTMLDocumentClass RawContentLength : 172