微博客
前端后端分离的博客系统, 是一个单体应用
产品
用户定位:
产品的原型
架构
技术架构
业务逻辑代码组织方式
首先使用go mod init 初始化一个Go项目工程, vscode 打开这个工程(在这个工程下面由go.mod文件)
业务代码组织风格:
- MVC 分层架构: 比较传统的代码组织风格
- 微服务渐进架构: 微服务的孵化,业务开展之前, 尽量少的划分服务(2 ~5个服务)
- DDD 分区架构(Domain Driven Develop): 域(领域): 一个业务单元(商品管理/订单管理), 理解为一个业务分区
基于DDD和TDD的功能分区架构: Blog业务实现流程
代码开发的2种方式:
- 从上往下 进行设计, 顶层设计
- 从下往上 做业务的具体实现 (推荐)
RESTful接口设计
// 文章管理 API, 同时设计业务接口(需要暴露成HTTP RESTFUL)
业务接口定义
HTTP 接口 只是把业务接口的能通过HTTP协议对外进行暴露
// POST
// action: /vblog/api/v1/blogs/list
// action: /vblog/api/v1/blogs/get?
// action: /vblog/api/v1/blogs/create?
// Jquery
GET /vblog/api/v1/blogs 获取博客列表
POST /vblog/api/v1/blogs 创建博客 参数通过Body
GET /vblog/api/v1/blogs/:id 获取单个文章
PUT /vblog/api/v1/blogs/:id 获取单个文章
DELETE /vblog/api/v1/blogs/:id 删除单个文章
数据库的设计
文章: Blog
文章的元数据:
用户传入的数据
- 标题
- 作者
- 发布时间
- 内容(Markdown)
- 标签(map)
补充数据库的建库,建表
准备一个MySQL数据库: 使用docker安装一个mysql
docker run mysql
项目开发
项目骨架做定义(流程是从下到上的)
-
业务处理模块: 每个业务一个模块: apps
-
项目的配置管理: pkg conf 项目的运行参数, 通过配置传递给程序: conf
-
项目的文档: docs
-
项目的接口: protocol 协议服务器, 监听对应的端口(http server/grpc server), 处理用户的连接
-
项目CLI: cmd 项目的cli工具(cobra), ./vblog start/init
-
项目入口文件: main.go, 项目所有的包,类, 在main.go 进行程序组装
-
程序日志处理: logurs/zap/zerolog, 项目里面我们使用zerolog, 程序里面负责统一打印日志的组件: logger
-
程序的配置文件: 加载配置文件目录
- 文件格式的配置: config.toml
- 环境变量的配置: config.env
- 程序的样例配置: config.example.toml
- 单元测试配置: unit_test.env(vscode 使用的)
-
.gitignore: 哪些文件忽略不提交, config.toml
v1.0版本 问题
业务开发流程:
- 写业务模块, 业务模块并没有被加载到框架中去
- 加载业务逻辑, 收到添加业务逻辑的初始化
// 加载业务逻辑实现
r := gin.Default()
// blog 业务模块
blogService := &impl.Impl{}
err := blogService.Init()
cobra.CheckErr(err)
apiHandler := api.NewHandler(blogService)
apiHandler.Registry(r)
// 20多个业务模块
...
...
- 注意里面不能集中在写业务功能上, 不希望在apps模块之外还要做业务代码的添加
- 对象的依赖问题, 一些其他的负责的业务, 可能会已很多业务对象来开发上层业务
v2.0版本
核心采用Ioc来进行重构
实现ioc
-
- 先写业务实现
-
- 再写接口实现
-
- 实例注册到ioc
-
- 程序启动的时候, 注册所有的实例: apps/registry.go,
注册业务对象
import (
// 注册api handler
_ "gitee.com/go-course/go11/vblog/apps/blog/api"
// 注册 blog 业务具体实现
_ "gitee.com/go-course/go11/vblog/apps/blog/impl"
)
接口测试
请求 POST
curl --location 'http://127.0.0.1:8010/vblog/api/v1/blogs' \
--header 'Authorization: bearer xFonlTKhEtFqnUvhGiGv5fiL' \
--header 'Content-Type: application/json' \
--data '{
"title": "api test",
"author": "oldyu",
"content": "go项目教学"
}'
请求的返回
{
"id": 23,
"created_at": 1685168742,
"updated_at": 1685168742,
"pulished_at": 0,
"title": "api test",
"author": "oldyu",
"content": "go项目教学",
"tags": {},
"status": 0
}
如果控制枚举类型的序列化: "status": 0
- "status": "草稿/Draft", 0 ---> 草稿
objcect(内存中的数据结构) --> json(字符串), 对于枚举类型,我们我们来控制如何暂时, 如何自定义JSON序列化
// Marshaler is the interface implemented by types that
// // can marshal themselves into valid JSON.
// type Marshaler interface {
// MarshalJSON() ([]byte, error)
// }
// 你自己定义当前类型的JSON输出, 一定要是一个合法的JSON
// "status": "xxx", "xxx"
func (s STATUS) MarshalJSON() ([]byte, error) {
switch s {
case STATUS_DRAFT:
// 草稿 "草稿"
return []byte(`"草稿"`), nil
case STATUS_PUBLISHED:
return []byte(`"已发布"`), nil
}
return []byte(fmt.Sprintf("%d", s)), nil
}
GET: 查询文章列表
curl --location 'http://127.0.0.1:8010/vblog/api/v1/blogs?keywords=%E9%A1%B9%E7%9B%AE%E6%95%99%E5%AD%A6' \
--header 'Authorization: bearer xFonlTKhEtFqnUvhGiGv5fiL'
{
"total": 1,
"items": [
{
"id": 23,
"created_at": 1685168742,
"updated_at": 1685168742,
"pulished_at": 0,
"title": "api test",
"author": "oldyu",
"content": "go项目教学",
"tags": {},
"status": "草稿"
}
]
}
认证功能开发
验证认证接口:
curl --location 'http://localhost:8010/vblog/api/v1/tokens' \
--header 'Authorization: bearer xFonlTKhEtFqnUvhGiGv5fiL' \
--header 'Content-Type: application/json' \
--header 'Cookie: token=chotfbp3n7pgk12r9iog' \
--data '{
"username": "admin",
"passwrod": "123456"
}'
程序的Debug
- 单元测试: 调试某个函数
- 程序Debug: 程序以Debug启动