# 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 } ```