web开发

1. 跨域

跨域是浏览器基于同源策略的一种安全手段。

同源:

  • 协议相同(protocol
  • 主机相同(host
  • 端口相同(port

当访问不是同源的服务地址时,就会发生跨域问题

image

1.1 go-zero跨域配置

  1. 第一种

    server := rest.MustNewServer(c.RestConf,
    		rest.WithCors("http://localhost:5173"))
    
    1
    2
  2. 第二种

    • 在有些情况下,我们需要传递一些自定义的header,这时候仍旧会出现跨域问题
    • 所以我们需要自定义跨域的header
    server := rest.MustNewServer(c.RestConf,
    		rest.WithCorsHeaders("xxxx"),
    	)
    
    1
    2
    3
  3. 第三种

    • 如果我们既想要自定义header,又想要控制跨域的域名
    server := rest.MustNewServer(c.RestConf,
            //顺序不能颠倒
    		rest.WithCors("http://localhost:5173"),
    		rest.WithCorsHeaders("xxxx"),
    	)
    
    1
    2
    3
    4
    5
  4. 第四种

    • 如果想要自定义跨域

      server := rest.MustNewServer(c.RestConf,
      		rest.WithCustomCors(func(header http.Header) {
      			var allowOrigin = "Access-Control-Allow-Origin"
      			var allOrigins = "http://localhost:5173"
      			var allowMethods = "Access-Control-Allow-Methods"
      			var allowHeaders = "Access-Control-Allow-Headers"
      			var exposeHeaders = "Access-Control-Expose-Headers"
      			var methods = "GET, HEAD, POST, PATCH, PUT, DELETE, OPTIONS"
      			var allowHeadersVal = "xxxx, Content-Type, Origin, X-CSRF-Token, Authorization, AccessToken, Token, Range"
      			var exposeHeadersVal = "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers"
      			var maxAgeHeader = "Access-Control-Max-Age"
      			var maxAgeHeaderVal = "86400"
      			header.Set(allowOrigin, allOrigins)
      			header.Set(allowMethods, methods)
      			header.Set(allowHeaders, allowHeadersVal)
      			header.Set(exposeHeaders, exposeHeadersVal)
      			header.Set(maxAgeHeader, maxAgeHeaderVal)
      		}, func(w http.ResponseWriter) {
      
      		}),
      	)
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21

2. 请求参数

2.1 form表单

type Request struct {
    Name    string  `form:"name"` // 必填参数
    Age     int     `form:"age,optional"` // optional定义非必填参数
}


var req Request
err := httpx.Parse(r, &req) // 解析参数
1
2
3
4
5
6
7
8

2.2 json

type Request struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

var req Request
err := httpx.Parse(r, &req) // 解析参数
1
2
3
4
5
6
7

2.3 path

type Request struct {
    Name string `path:"name"`
}

// Path定义
rest.Route{
    Method:  http.MethodGet,
    Path:    "/user/:name",
    Handler: handle,
}

var req Request
err := httpx.Parse(r, &req) // 解析参数
1
2
3
4
5
6
7
8
9
10
11
12
13

2.4 header参数

type Request struct {
    Authorization string `header:"authorization"`
}

var req Request
err := httpx.Parse(r, &req) // 解析参数
1
2
3
4
5
6

2.5 参数默认值

type Request struct {
    Age int `form:"age,default=18"`
}

var req Request
err := httpx.Parse(r, &req) // 解析参数
1
2
3
4
5
6

2.6 参数枚举值

type Request struct {
    Age int `form:"age,options=18|19"`
}

var req Request
err := httpx.Parse(r, &req) // 解析参数
1
2
3
4
5
6

2.7 参数区间定义

type Request struct {
    Age int `form:"age,range=[18:35)"`
}

var req Request
err := httpx.Parse(r, &req) // 解析参数
1
2
3
4
5
6

3. 中间件

3.1 内置中间件

  • 鉴权管理中间件 AuthorizeHandler
  • 熔断中间件 BreakerHandler
  • 内容安全中间件 ContentSecurityHandler
  • 解密中间件 CryptionHandler
  • 压缩管理中间件 GunzipHandler
  • 日志中间件 LogHandler
  • ContentLength 管理中间件 MaxBytesHandler
  • 限流中间件 MaxConnsHandler
  • 指标统计中间件 MetricHandler
  • 普罗米修斯指标中间件 PrometheusHandler
  • panic 恢复中间件 RecoverHandler
  • 负载监控中间件 SheddingHandler
  • 超时中间件 TimeoutHandler
  • 链路追踪中间件 TraceHandler
type MiddlewaresConf struct {
    Trace      bool `json:",default=true"`
    Log        bool `json:",default=true"`
    Prometheus bool `json:",default=true"`
    MaxConns   bool `json:",default=true"`
    Breaker    bool `json:",default=true"`
    Shedding   bool `json:",default=true"`
    Timeout    bool `json:",default=true"`
    Recover    bool `json:",default=true"`
    Metrics    bool `json:",default=true"`
    MaxBytes   bool `json:",default=true"`
    Gunzip     bool `json:",default=true"`
}
1
2
3
4
5
6
7
8
9
10
11
12
13
Name: HelloWorld.api
Host: 127.0.0.1
Port: 8080
Middlewares:
  Metrics: false
1
2
3
4
5

3.2 自定义中间件

server := rest.MustNewServer(rest.RestConf{})
defer server.Stop()

server.Use(middleware)

// 自定义的中间件
func middleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        w.Header().Add("X-Middleware", "static-middleware")
        next(w, r)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

4. redis集成

4.1 docker-compose部署配置

version: '3.3'
services:
  redis:
    container_name: zero-redis-node
    image: redis:latest
    restart: always
    ports:
      - "6379:6379"
    volumes:
      - ./volume/redis/dаta:/root/redis
    environment:
      - REDIS_PASSWORD=mszlu
      - REDIS_PORT=6379
      - REDIS_DATABASES=16
  redis-cluster-master:
    image: redis:latest
    container_name: redis-cluster-master
    restart: always
    command: redis-server --port 7000 --requirepass mszlu  --appendonly yes
    ports:
      - 7000:7000
    volumes:
      - ./volume/redis-cluster/data:/data
    networks:
      - redis-cluster
  redis-cluster-slave1:
    image: redis:latest
    container_name: redis-slave-1
    restart: always
    command: redis-server --slaveof redis-cluster-master 7000 --port 7001  --requirepass mszlu --masterauth mszlu  --appendonly yes
    ports:
      - 7001:7001
    depends_on:
      - master
    volumes:
      - ./volume/redis-cluster/data:/data
    networks:
      - redis-cluster
networks:
  redis-cluster:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

4.2 单节点

go-zero内置配置:

RedisConf struct {
		Host     string
		Type     string `json:",default=node,options=node|cluster"`
		Pass     string `json:",optional"`
		Tls      bool   `json:",optional"`
		NonBlock bool   `json:",default=true"`
		// PingTimeout is the timeout for ping redis.
		PingTimeout time.Duration `json:",default=1s"`
	}
1
2
3
4
5
6
7
8
9
  • Host:Redis 服务地址 ip:port, 如果是 Redis Cluster 则为 ip1:port1,ip2:port2,ip3:port3
  • Type:node 单节点 Redis ,cluster Redis 集群
  • Pass:认证密码
  • Tls:是否开启tls
  • NonBlock:是否以非阻塞模式启动
  • PingTimeout:ping超时时间,用于检测redis是否连接成功
go get github.com/redis/go-redis/v9
1
RedisConfig:
  Host: "127.0.0.1:6379"
  Pass: "mszlu"
  Type: "node"
  Tls: false
  NonBlock: false
  PingTimeout: 1s
1
2
3
4
5
6
7
package db

import "github.com/zeromicro/go-zero/core/stores/redis"

func NewRedis(conf redis.RedisConf) *redis.Redis {
	return redis.MustNewRedis(conf)
}

1
2
3
4
5
6
7
8

servicecontext中注入:

package svc

import (
	"github.com/zeromicro/go-zero/core/stores/redis"
	"github.com/zeromicro/go-zero/core/stores/sqlx"
	"user-api/internal/config"
	"user-api/internal/db"
)

type ServiceContext struct {
	Config      config.Config
	Conn        sqlx.SqlConn
	RedisClient *redis.Redis
}

func NewServiceContext(c config.Config) *ServiceContext {
	sqlConn := db.NewMysql(c.MysqlConfig)
	redisClient := db.NewRedis(c.RedisConfig)
	return &ServiceContext{
		Config:      c,
		Conn:        sqlConn,
		RedisClient: redisClient,
	}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

改造登录代码:

func (l *LoginLogic) Login(req *types.LoginReq) (resp *types.LoginResp, err error) {
	// todo: add your logic here and delete this line
	userModel := user.NewUserModel(l.svcCtx.Conn)
	u, err := userModel.FindByUsernameAndPwd(l.ctx, req.Username, req.Password)
	if err != nil {
		l.Logger.Error(err)
		return nil, biz.DBError
	}
	if u == nil {
		return nil, biz.NameOrPwdError
	}
	//登录成功 生成token
	secret := l.svcCtx.Config.Auth.Secret
	expire := l.svcCtx.Config.Auth.Expire
	token, err := biz.GetJwtToken(secret, time.Now().Unix(), expire, u.Id)
	if err != nil {
		l.Logger.Error(err)
		return nil, biz.TokenError
	}
    //把token存入redis
	err = l.svcCtx.RedisClient.SetexCtx(context.Background(), "token:"+token, strconv.FormatInt(u.Id, 10), int(expire))
	if err != nil {
		return nil, biz.RedisError
	}
	resp = &types.LoginResp{
		Token: token,
	}
	return
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

测试

4.3 集群

集群方式只需要改配置即可,go-zero对集群和单机做了适配。

go-zero内置redis的缺陷:

  • 不能选择redis数据库
  • 不能配置连接池
  • 对原生go-redis配置项不能自定义

实际开发中,如果对配置项需要精细化控制,建议直接使用go-redis库即可,并且自行对go-redis进行封装