go17/vblog/README.md

258 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Web全栈开发(Vblog)
## 软件设计
### 需求
管理markdown个文字的一个网站作者后台发布文章访客前台浏览查看文章
### 流程
![](./docs/flow.drawio)
### 产品原型
https://gitee.com/infraboard/go-course/blob/master/new.md#%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1
![](./docs/page.png)
### 架构(BS)和概要设计
![](./docs/arch.png)
### 业务的详细设计
直接使用Go的接口 来定义业务
```go
// 业务域
type Service interface {
UserService
InnterService
}
// 1. 外部
type UserService interface {
// 颁发令牌 登录
IssueToken(context.Context, *IssueTokenRequest) (*Token, error)
// 撤销令牌 退出
RevolkToken(context.Context, *RevolkTokenRequest) (*Token, error)
}
type RevolkTokenRequest struct {
// 访问令牌
AccessToken string `json:"access_token"`
// 刷新令牌, 构成一对避免AccessToken 泄露,用户可以直接 revolk
RefreshToken string `json:"refresh_token"`
}
type IssueTokenRequest struct {
Username string `json:"username"`
Password string `json:"password"`
// 记住我, Token可能1天过期, 过去时间调整为7天
RememberMe bool `json:"remember_me"`
}
// 内部
type InnterService interface {
// 令牌校验
ValidateToken(context.Context, *ValidateTokenRequest) (*Token, error)
}
type ValidateTokenRequest struct {
// 访问令牌
AccessToken string `json:"access_token"`
}
```
数据库的设计伴随接口设计已经完成
1. 如何基于Vscode 构造单元测试的配置
```json
{
"go.testEnvFile": "${workspaceFolder}/etc/test.env",
}
```
添加工作目录环境变量
```
WORKSPACE_DIR="/Users/xxxx/Projects/go-course/go17/vblog"
```
### 业务模块的实现
TDD (Test Drive Development)
+ 用户模块
```go
// 我要测试的对象是什么?, 这个服务的具体实现
// Service的具体实现现在还没实现
// $2a$10$yHVSVuyIpTrQxwiuZUwSMuaJFsnd4YBd6hgA.31xNzuyTu4voD/QW
// $2a$10$fe0lsMhM15i4cjHmWudroOOIIBR27Nb7vwrigwK.9PhWdFld44Yze
// $2a$10$RoR0qK37vfc7pddPV0mpU.nN15Lv8745A40MkCJLe47Q00Ag83Qru
// https://gitee.com/infraboard/go-course/blob/master/day09/go-hash.md#bcrypt
func TestRegistry(t *testing.T) {
req := user.NewRegistryRequest()
req.Username = "test02"
req.Password = "123456"
ins, err := impl.UserService.Registry(ctx, req)
if err != nil {
t.Fatal(err)
}
t.Log(ins)
}
func TestDescribeUser(t *testing.T) {
ins, err := impl.UserService.DescribeUser(ctx, &user.DescribeUserRequest{
user.DESCRIBE_BY_USERNAME, "admin",
})
if err != nil {
t.Fatal(err)
}
//
// if ins.Password = in.Password
t.Log(ins.CheckPassword("1234567"))
}
```
```go
var UserService user.Service = &UserServiceImpl{}
// 定义一个struct, 用于实现 UserService就是刚才定义的接口
// 怎么才能判断这个结构体没有实现这个接口
type UserServiceImpl struct {
}
// DescribeUser implements user.Service.
func (u *UserServiceImpl) DescribeUser(ctx context.Context, in *user.DescribeUserRequest) (*user.User, error) {
query := datasource.DBFromCtx(ctx)
switch in.DescribeBy {
case user.DESCRIBE_BY_ID:
query = query.Where("id = ?", in.Value)
case user.DESCRIBE_BY_USERNAME:
query = query.Where("username = ?", in.Value)
}
ins := &user.User{}
if err := query.Take(ins).Error; err != nil {
return nil, err
}
return ins, nil
}
// Registry implements user.Service.
func (u *UserServiceImpl) Registry(ctx context.Context, in *user.RegistryRequest) (*user.User, error) {
ins, err := user.New(in)
if err != nil {
return nil, err
}
// 明文密码保持到数据库,是不安全
// 对称加密/非对称, 解密
// 消息摘要, 无法还原
// 怎么知道用户的密码 比对hash 123 -> (xxx)
// md5 sha1/256/512, hmac, ...
// 结果固定
hashPass, err := bcrypt.GenerateFromPassword([]byte(in.Password), bcrypt.DefaultCost)
if err != nil {
return nil, err
}
ins.Password = string(hashPass)
if err := datasource.DBFromCtx(ctx).Create(ins).Error; err != nil {
return nil, err
}
// context.WithValue()
// 无事务的模式
// datasource.DB().Transaction(func(tx *gorm.DB) error {
// ctx := datasource.WithTransactionCtx(ctx)
// // 1.
// svcA.Call(ctx)
// // 2.
// svcB.Call(ctx)
// // 3.
// svcC.Call(ctx)
// })
return ins, nil
}
```
### API接口层
Gin
```go
package api
import (
"github.com/gin-gonic/gin"
"github.com/infraboard/mcube/v2/http/gin/response"
"gitlab.com/go-course-project/go17/vblog/apps/token"
"gitlab.com/go-course-project/go17/vblog/apps/token/impl"
)
func NewTokenApiHandler() *TokenApiHandler {
return &TokenApiHandler{
token: impl.TokenService,
}
}
type TokenApiHandler struct {
// 业务控制器
token token.UserService
}
// 提供注册功能, 提供一个Group
// book := server.Group("/api/tokens")
func (h *TokenApiHandler) Registry(r *gin.Engine) {
router := r.Group("/api/tokens")
router.POST("/issue", h.IssueToken)
router.POST("/revolk", h.RevolkToken)
}
func (h *TokenApiHandler) IssueToken(ctx *gin.Context) {
in := token.NewIssueTokenRequest("", "")
if err := ctx.BindJSON(in); err != nil {
response.Failed(ctx, err)
return
}
ins, err := h.token.IssueToken(ctx.Request.Context(), in)
if err != nil {
response.Failed(ctx, err)
return
}
response.Success(ctx, ins)
}
func (h *TokenApiHandler) RevolkToken(ctx *gin.Context) {
in := &token.RevolkTokenRequest{}
if err := ctx.BindJSON(in); err != nil {
response.Failed(ctx, err)
return
}
ins, err := h.token.RevolkToken(ctx, in)
if err != nil {
response.Failed(ctx, err)
return
}
response.Success(ctx, ins)
}
```
### 组装程序
怎么做开发显得专业
### 中间件鉴权
### ioc优化