2024-12-01 09:52:50 +08:00
|
|
|
|
# Web全栈开发(Vblog)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 软件设计
|
|
|
|
|
|
|
|
|
|
### 需求
|
|
|
|
|
|
|
|
|
|
管理markdown个文字的一个网站,作者后台发布文章,访客前台浏览查看文章
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 流程
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
### 产品原型
|
|
|
|
|
|
|
|
|
|
https://gitee.com/infraboard/go-course/blob/master/new.md#%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1
|
|
|
|
|
|
2024-12-01 09:54:26 +08:00
|
|
|
|

|
|
|
|
|
|
2024-12-01 11:06:09 +08:00
|
|
|
|
### 架构(BS)和概要设计
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
### 业务的详细设计
|
|
|
|
|
|
2024-12-01 17:17:51 +08:00
|
|
|
|
直接使用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"
|
2024-12-01 18:04:02 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 业务模块的实现
|
|
|
|
|
|
2024-12-08 10:10:37 +08:00
|
|
|
|
TDD (Test Drive Development)
|
|
|
|
|
|
2024-12-08 11:07:03 +08:00
|
|
|
|
+ 用户模块
|
|
|
|
|
|
|
|
|
|
```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
|
|
|
|
|
}
|
2024-12-08 16:20:18 +08:00
|
|
|
|
```
|
|
|
|
|
|
2024-12-08 17:00:22 +08:00
|
|
|
|
### API接口层
|
2024-12-08 16:20:18 +08:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
2024-12-08 17:00:22 +08:00
|
|
|
|
```
|
|
|
|
|
|
2024-12-08 17:31:55 +08:00
|
|
|
|
### 组装程序(v1)
|
2024-12-08 17:00:22 +08:00
|
|
|
|
|
|
|
|
|
怎么做开发显得专业
|
|
|
|
|
|
2024-12-08 17:31:55 +08:00
|
|
|
|
```go
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"log"
|
|
|
|
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
"github.com/infraboard/mcube/v2/ioc/config/http"
|
|
|
|
|
blogApi "gitlab.com/go-course-project/go17/vblog/apps/blog/api"
|
|
|
|
|
tokenApi "gitlab.com/go-course-project/go17/vblog/apps/token/api"
|
|
|
|
|
"gitlab.com/go-course-project/go17/vblog/config"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
config.LoadConfig()
|
|
|
|
|
|
|
|
|
|
// gin Engine, 它包装了http server
|
|
|
|
|
server := gin.Default()
|
|
|
|
|
|
|
|
|
|
// 注册业务模块的路有
|
|
|
|
|
tokenApi.NewTokenApiHandler().Registry(server)
|
|
|
|
|
blogApi.NewBlogApiHandler().Registry(server)
|
|
|
|
|
|
|
|
|
|
// 服务器启动
|
|
|
|
|
if err := server.Run(http.Get().Addr()); err != nil {
|
|
|
|
|
log.Println(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
功能分层架构(MVC): Book Api
|
|
|
|
|
业务分区架构(DDD): Vblog
|
|
|
|
|
|
|
|
|
|
### 测试验证
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
curl --location 'http://127.0.0.1:8080/vblog/api/v1/blogs' \
|
|
|
|
|
--header 'Content-Type: application/json' \
|
|
|
|
|
--data '{
|
|
|
|
|
"title": "POSTMAN测试01",
|
|
|
|
|
"author": "will",
|
|
|
|
|
"content": "post man 测试",
|
|
|
|
|
"summary": "Go全栈项目",
|
|
|
|
|
"category": "软件开发"
|
|
|
|
|
}'
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
curl --location 'http://127.0.0.1:8080/vblog/api/v1/tokens' \
|
|
|
|
|
--header 'Content-Type: application/json' \
|
|
|
|
|
--data '{
|
|
|
|
|
"username": "admin",
|
|
|
|
|
"password": "123456"
|
|
|
|
|
}'
|
|
|
|
|
```
|
2024-12-08 17:00:22 +08:00
|
|
|
|
|
|
|
|
|
### 中间件鉴权
|
|
|
|
|
|
2024-12-08 17:31:55 +08:00
|
|
|
|

|
|
|
|
|
|
|
|
|
|
开发一个认证中间件: 用于根据用户携带的Token信息,判断用户身份,并把用户身份信息方到上下文中,传递给后面HandleFunc中使用
|
|
|
|
|
|
2024-12-08 17:00:22 +08:00
|
|
|
|
|
2024-12-15 10:13:35 +08:00
|
|
|
|
## ioc优化
|
|
|
|
|
|
|
|
|
|
+ 会用: mcube ioc / golang-ioc
|
|
|
|
|
+ 掌握原理: 自己造,手写一个简单
|
|
|
|
|
|
|
|
|
|
### 问题
|
2024-12-08 17:00:22 +08:00
|
|
|
|
|
2024-12-15 11:36:22 +08:00
|
|
|
|
+ 手动管理: main, 自己组装对象, 业务越复杂,组装难度越高
|
2024-12-08 17:00:22 +08:00
|
|
|
|
|
2024-12-15 11:36:22 +08:00
|
|
|
|

|
|
|
|
|
|
|
|
|
|
+ ioc: 引入了一个中间层, 这个中间层负责对象的管理, 自己对象自己去ioc获取依赖,而不是我们开发者 把依赖传递给他,完成 对象的 依赖 由被动 变成主动, ioc, 依赖倒置
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
### 基于mcube ioc来改造
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
https://www.mcube.top/docs/framework/
|
|
|
|
|
|
|
|
|
|
1. 对象注册到ioc: 把我们的对象实现了1个IOC对象(符合Ioc接口定义的对象), 可以通过继承基础类,直接实现接口ObjectImpl
|
|
|
|
|
+ 2 APIHandler: TokenApiHandler, BlogApiHandler
|
|
|
|
|
+ 3 Controller: UserServiceImpl, TokenServiceImpl, BlogServiceImpl
|
|
|
|
|
|
|
|
|
|
对象注册
|
|
|
|
|
```go
|
|
|
|
|
func init() {
|
|
|
|
|
ioc.Controller().Registry(&TokenServiceImpl{})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 定义一个struct, 用于实现 UserService就是刚才定义的接口
|
|
|
|
|
// 怎么才能判断这个结构体没有实现这个接口
|
|
|
|
|
type TokenServiceImpl struct {
|
|
|
|
|
ioc.ObjectImpl
|
|
|
|
|
|
|
|
|
|
// user service
|
|
|
|
|
user user.AdminService
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (*TokenServiceImpl) Name() string {
|
|
|
|
|
return token.AppName
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 他需要自己去获取依赖,通过ioc
|
|
|
|
|
func (i *TokenServiceImpl) Init() error {
|
|
|
|
|
i.user = user.GetService()
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
对象获取
|
|
|
|
|
```go
|
|
|
|
|
func GetService() Service {
|
|
|
|
|
return ioc.Controller().Get(AppName).(Service)
|
|
|
|
|
}
|
2024-12-15 12:10:11 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
启动的时候 只需要启动 HTTP Sever就可以啦
|
|
|
|
|
```go
|
|
|
|
|
func main() {
|
|
|
|
|
config.LoadConfig()
|
|
|
|
|
|
|
|
|
|
// 服务器启动
|
|
|
|
|
if err := server.GinServer.Run(http.Get().Addr()); err != nil {
|
|
|
|
|
log.Println(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-12-15 15:10:44 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
修改为使用cli来用
|
|
|
|
|
+ cobra 的 OnInitialize 进行ioc配置的读取,与加载
|
|
|
|
|
+ start 命令 调用 server.run 来进行 服务启动
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### ioc 托管Gin框架
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
// module_name
|
|
|
|
|
func (h *BlogApiHandler) Name() string {
|
|
|
|
|
return "blogs"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// router := r.Group("/vblog/api/v1/blogs")
|
|
|
|
|
// ioc_gin.ObjectRouter(h)
|
|
|
|
|
// 模块的名称, 会作为路径的一部分比如: /mcube_service/api/v1/hello_module/
|
|
|
|
|
// 路径构成规则 <service_name>/<path_prefix>/<service_version>/<module_name>
|
|
|
|
|
func (h *BlogApiHandler) Init() error {
|
|
|
|
|
h.blog = blog.GetService()
|
|
|
|
|
// 在ioc获取gin server *gin.Engine
|
|
|
|
|
ioc_gin.RootRouter()
|
|
|
|
|
|
|
|
|
|
// 获取模块路有: url前缀,
|
|
|
|
|
r := ioc_gin.ObjectRouter(h)
|
|
|
|
|
r.GET("", h.QueryBlog)
|
|
|
|
|
|
|
|
|
|
r.Use(middleware.Auth)
|
|
|
|
|
r.POST("", h.CreateBlog)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
func main() {
|
|
|
|
|
// load config and run with cobra cli
|
|
|
|
|
// 使用ioc的 gin来进行路由加载
|
|
|
|
|
cmd.Start()
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 测试验证
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
curl --location 'http://127.0.0.1:8080/api/vblog/v1/blogs' \
|
|
|
|
|
--header 'Content-Type: application/json' --header 'Authorization: Bearer eaabbe42-9bc0-4a8e-bd86-c92542d062bb' \
|
|
|
|
|
--data '{
|
|
|
|
|
"title": "POSTMAN测试01",
|
|
|
|
|
"author": "will",
|
|
|
|
|
"content": "post man 测试",
|
|
|
|
|
"summary": "Go全栈项目",
|
|
|
|
|
"category": "软件开发"
|
|
|
|
|
}'
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
curl --location 'http://127.0.0.1:8080/api/vblog/v1/tokens' \
|
|
|
|
|
--header 'Content-Type: application/json' \
|
|
|
|
|
--data '{
|
|
|
|
|
"username": "admin",
|
|
|
|
|
"password": "123456"
|
|
|
|
|
}'
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 其他的优化
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Make
|
|
|
|
|
|
|
|
|
|
```makefile
|
|
|
|
|
PKG := "gitlab.com/go-course-project/go17/vblog"
|
|
|
|
|
MOD_DIR := $(shell go env GOPATH)/pkg/mod
|
|
|
|
|
PKG_LIST := $(shell go list ${PKG}/... | grep -v /vendor/ | grep -v redis)
|
|
|
|
|
GO_FILES := $(shell find . -name '*.go' | grep -v /vendor/ | grep -v _test.go)
|
|
|
|
|
|
|
|
|
|
.PHONY: all dep lint vet test test-coverage build clean
|
|
|
|
|
|
|
|
|
|
all: build
|
|
|
|
|
|
|
|
|
|
dep: ## Get the dependencies
|
|
|
|
|
@go mod tidy
|
|
|
|
|
|
|
|
|
|
lint: ## Lint Golang files
|
|
|
|
|
@golint -set_exit_status ${PKG_LIST}
|
|
|
|
|
|
|
|
|
|
vet: ## Run go vet
|
|
|
|
|
@go vet ${PKG_LIST}
|
|
|
|
|
|
|
|
|
|
run: ## Run Server
|
|
|
|
|
@go run main.go start
|
|
|
|
|
|
|
|
|
|
help: ## Display this help screen
|
|
|
|
|
@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Cookie机制
|
|
|
|
|
|
|
|
|
|
需要手动携带Token, Post, 这会比较安全, 但是有些时候不方便, 如果你想要每次服务端调用 浏览器或者Postman这类工具 可以自动给你携带
|
|
|
|
|
|
|
|
|
|
颁发Token的时候, 通过客户端(浏览器/Postman), Set-Cookie头
|
|
|
|
|
|
|
|
|
|
1. 颁发Token是不是要 Set-Cookie
|
|
|
|
|
```go
|
|
|
|
|
// 设置Cookie
|
|
|
|
|
ctx.SetCookie(token.COOKIE_NAME, ins.AccessToken, ins.AccessTokenExpireTTL(), "/", application.Get().Domain(), false, true)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
2. 获取Token是不是也需要Cookie当作获取Token
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|