跳到主要内容

Nginx 代理 EventsStream 接口配置优化

· 阅读需 2 分钟
素明诚
Full stack development

处理 HTTP 流(如 SSE-Server Sent Events)的场景,这种情况下,配置需要优化以支持长连接和实时数据流

配置优化

proxy_buffering off;

这个设置非常适合事件流。对于 SSE 或 WebSocket 等实时数据传输技术,禁用缓冲可以减少发送消息到客户端的延迟,因为它允许服务器立即发送响应而不是等待缓冲区填满。

chunked_transfer_encoding on;

适合事件流。对于动态生成且大小未知的内容(如实时生成的事件数据),启用分块传输可以持续地发送数据到客户端,而不必等待所有内容生成完成。

tcp_nopush on;

处理事件流时可能不是最佳选择。虽然它可以优化数据包的传输效率,但对于需要尽快把小消息推送到客户端的实时通信来说,可能会引入不必要的延迟。

tcp_nodelay on;

非常适合事件流。此设置确保数据包立即发送,对于需要低延迟的实时数据传输(如 SSE)非常关键。

keepalive_timeout 120;

适合事件流。长时间的 keepalive 超时对于维持 SSE 这样的长连接非常有利,因为它可以减少因连接超时而频繁重连的情况。

配置

建议只是针对需要设置的接口进行配置如下

    location /api/chat-process {
# 后台接口地址
proxy_pass http://127.0.0.1:5008/api/chat-process;
proxy_redirect default;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Headers X-Requested-With;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
proxy_buffering off;
chunked_transfer_encoding on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 300;
}

Cookie 和 Session 的区别

· 阅读需 2 分钟
素明诚
Full stack development

Session 指的是服务器为了跟踪和区分用户的状态而在服务器端存储的数据。当用户与 Web 应用交互时,服务器会创建一个会话(Session),用来保存关于这个用户的信息,这可以包括用户的登录状态、购物车内容或任何其他必要的数据,以保持用户在多个页面请求之间的持续性。

工作原理

会话创建:用户首次访问网站时,服务器会生成一个独一无二的会话标识符(Session ID),并以此来创建一个会话。这个会话标识符通常会通过 Cookie 发送到用户的浏览器,保存在用户端。

会话存储:服务器使用会话 ID 识别来自同一浏览器的后续请求,并可以根据需要检索和修改会话数据。

会话结束:会话可以通过服务器设置的超时,用户的主动登出或者通过其他机制结束。

Cookie:通常用来存储 Session ID,是服务器发送到用户浏览器并保存在本地的小数据片段。浏览器每次向服务器发送请求时都会自动附带这些 Cookie,使服务器能够识别用户和会话状态。

Session 数据:实际的数据保存在服务器端,通过从浏览器发送的 Cookie 中提取的 Session ID 进行访问和管理。

安全性

由于 Session 数据存储在服务器端,它比存储在用户浏览器中的 Cookie 更安全。没有直接的方式可以从用户端查看或修改存储在服务器上的 Session 数据。所以 Cookie 一般用来保存用户信息,Session 的主要作用就是通过服务端记录用户的状态

用户态和内核态

· 阅读需 3 分钟
素明诚
Full stack development

用户态

用户态是普通的程序运行模式,例如你的文档编辑器或游戏。

在用户态,程序有限制地访问计算机的资源,比如不能直接控制硬件设备。

这样做的好处是保证了系统的安全,防止程序随意修改关键的系统信息或其他程序的数据。

内核态

内核态是操作系统运行的模式,它有权访问计算机的所有资源,包括内存和外设。

当操作系统需要执行重要的任务,如管理内存或响应硬件请求时,它会在内核态下运行。

这种模式可以让操作系统有效地控制硬件和管理系统资源。

从用户态切换到内核态的情况

系统调用:当你的程序需要操作系统帮助完成一些特定任务(比如读取文件)时,它会通过系统调用请求操作系统的服务,此时会从用户态切换到内核态。

异常:如果程序运行出错(如试图访问未授权的内存区域),系统会自动切换到内核态来处理这个错误,防止程序崩溃。

外部中断:当外围设备(如打印机、键盘)完成任务时,会通知 CPU,这时候系统也会切换到内核态来处理这些外部事件。

切换的过程

在操作系统中,“切换”通常指的是 CPU 从用户态切换到内核态的过程。

外部中断发生:当外围设备(如打印机或键盘)完成任务或需要系统处理某些事情(比如用户按下了键盘上的一个键),设备会向 CPU 发送一个中断信号。

中断信号的接收:CPU 接收到这个中断信号后,会当前暂停正在执行的用户态程序的运行。

状态的切换:CPU 将控制权转移给操作系统的中断处理程序,这通常涉及到从用户态切换到内核态。因为内核态允许执行访问硬件和执行中断服务例程等操作。

执行中断服务例程:一旦在内核态,操作系统的中断服务例程(ISR)就会开始执行,处理来自外部设备的请求或响应。例如,如果是键盘产生的中断,ISR 可能会读取键盘缓冲区中的数据。

返回用户态:中断处理完成后,操作系统会将 CPU 的状态从内核态切换回用户态,继续执行之前被中断的程序。

为什么要有用户态和内核态?

由于需要限制不同的程序之间的访问能力, 防止他们获取别的程序的内存数据, 或者获取外围设备的数据, 并发送到网络

Axios 状态码处理范围

· 阅读需 2 分钟
素明诚
Full stack development
状态码范围Axios 行为
200 - 299触发 .then() 方法
小于 200触发 .catch() 方法
300 - 399根据具体情况(如重定向)处理
400 - 499触发 .catch() 方法
500 及以上触发 .catch() 方法

获取重定向信息

axios.get('/url', {
maxRedirects: 0 // 不自动重定向
}).then(response => {
console.log('响应状态码:', response.status); // 例如 302
console.log('重定向到:', response.headers.location); // 重定向目标 URL
}).catch(error => {
console.log('处理错误', error);
});

修改 validateStatus 函数

通过修改 validateStatus 函数,你可以定义哪些 HTTP 状态码应该解析为成功响应

axios.get('/api', {
validateStatus: function (status) {
return true; // 所有响应都视为成功,都会传递给.then()
}
})
.then(response => {
console.log('任何状态码都在这里处理:', response.status);
})
.catch(error => {
console.log('处理错误', error);
});

建议

前端开发中,通常会对 Axios 进行封装以便更高效地处理业务逻辑。一种常见做法是与前端团队约定一套自定义状态码,通过这些状态码来控制不同的逻辑流程。在这种设计中,服务端不管请求的实际结果如何,总是返回 HTTP 状态码 200。随后,通过响应体中的自定义 code 来判断具体的业务状态,并据此进行相应的处理。这种方法简化了响应的判断逻辑,使得前端开发者可以更直接地使用 Axios 来实现业务需求,同时也降低了处理网络或服务器错误的复杂性。

Go Gin 封装 translations 插件

· 阅读需 1 分钟
素明诚
Full stack development

translate.go

package util

import (
"errors"
"fmt"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/locales/en"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
enTranslations "github.com/go-playground/validator/v10/translations/en"
chTranslations "github.com/go-playground/validator/v10/translations/zh"
"strings"
)

var trans ut.Translator

func init() {
if err := transInit("zh"); err != nil {
LogRus.Fatal("Failed to initialize translator:", err)
}
}

func transInit(locale string) error {
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
zhT := zh.New() // Chinese translator
enT := en.New() // English translator
uni := ut.New(enT, zhT, enT) // Universal translator

var ok bool
trans, ok = uni.GetTranslator(locale)
if !ok {
return fmt.Errorf("uni.GetTranslator(%s) failed", locale)
}

// Register translation based on locale
switch locale {
case "zh":
return chTranslations.RegisterDefaultTranslations(v, trans)
case "en":
return enTranslations.RegisterDefaultTranslations(v, trans)
default:
return enTranslations.RegisterDefaultTranslations(v, trans)
}
}

return fmt.Errorf("failed to assert Validator")
}

func TranslateErrors(err error) string {
var errs validator.ValidationErrors
if errors.As(err, &errs) {
var errMessages []string
for _, e := range errs {
translatedMsg := e.Translate(trans)
errMessages = append(errMessages, translatedMsg)
}
return strings.Join(errMessages, ", ")
}
return err.Error()
}

Login.go

直接使用封装好的 TranslateErrors 进行翻译

func Login(ctx *gin.Context) {
// 未登录,登录流程
var req LoginRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
ctx.JSON(http.StatusBadRequest, LoginResponse{Code: 1, Msg: util.TranslateErrors(err)})
return
}
}

如何上传镜像到 Docker Hub

· 阅读需 1 分钟
素明诚
Full stack development

创建 Docker Hub 账户:如果还没有 Docker Hub 账户,首先访问 Docker Hub 注册一个账户

https://hub.docker.com/## 进入终端

登录 Docker Hub: 在本地终端使用以下命令登录 Docker Hub

docker login

按照提示输入您的 Docker Hub 用户名和密码

标记您的镜像: 在上传镜像之前,需要将您的本地镜像标记为远程仓库的格式使用以下命令

docker tag local-image:tag username/repository:tag

其中 local-image:tag 是您本地的镜像名和标签,username/repository:tag 是您在 Docker Hub 上的用户名、仓库名和标签

上传镜像: 使用以下命令上传镜像

docker push username/repository:tag

确保替换 username/repository:tag 为您标记的镜像名

确认上传: 上传完成后,您可以在 Docker Hub 上的账户仓库中看到您的镜像

Nextjs 报错 Warning Extra attributes from the server style

· 阅读需 2 分钟
素明诚
Full stack development

问题原因

发生此错误的原因是 nextjs 从服务器端收到了客户端无法识别的额外属性,简单的说就是客户端渲染和服务端渲染不匹配。

我这个项目刚初始化,使用的是 next+radix-ui,配置全局主题的时候出现的错误

cf131e6a0d82e7d521bb6bf7454f15e7## 解决方法 1,忽略这个警告

修改 layout.tsx 的 html 属性,增加 suppressHydrationWarning

<html suppressHydrationWarning={true}>

解决方法 2,关闭浏览器插件

如果你有很多浏览器插件,尤其是涉及到样式和颜色,或者是文字类的,都会导致这个问题,你可以选择在开发环境下禁用这个插件

Chrome 推出了一个新的测试版功能,允许用户为特定 URL 关闭扩展,这有助于解决相关错误。您可以通过访问 chrome://flags/#extensions-menu-access-control 启用“Extensions Menu Access Control”标志,并重启 Chrome。启用后,您可以通过扩展程序图标来切换或启用/禁用特定 URL 的扩展。

18e725a41967c18898147d0c3ed1f519## 解决方法 3,不使用错误的嵌套

错误的嵌套如下,或者是例如li元素中嵌套ul元素,情况有很多

<a>
<a><a/>
<a/>

如果你有这个错误,大概率是这些问题导致的

Go Gin 常用的验证规则

· 阅读需 3 分钟
素明诚
Full stack development

在 Gin 框架中,数据验证是通过集成的 go-playground/validator 包来实现的。这个包支持许多内置的验证规则,可以用于验证输入数据的各种条件。

常用规则

标签描述
required字段必须有值,不能为默认零值或空
len字段长度必须等于指定的值
min对于数值,最小值必须不小于指定值;对于字符串或数组,长度必须不小于指定值
max对于数值,最大值必须不大于指定值;对于字符串或数组,长度必须不大于指定值
eq字段值必须等于指定的值
ne字段值必须不等于指定的值
lt字段值必须小于指定的值
lte字段值必须小于或等于指定的值
gt字段值必须大于指定的值
gte字段值必须大于或等于指定的值
email字段值必须是有效的电子邮件地址格式
url字段值必须是有效的 URL 格式
alnum字段必须是字母数字字符
numeric字段必须是有效的数字
hexadecimal字段必须是十六进制格式
hexcolor字段必须是有效的十六进制颜色代码
rgb字段必须是有效的 RGB 颜色
rgba字段必须是有效的 RGBA 颜色
hsl字段必须是有效的 HSL 颜色
hsla字段必须是有效的 HSLA 颜色
e164字段必须是有效的 E.164 格式的电话号码
oneof字段值必须是指定列表中的一个
uuid字段值必须是有效的 UUID
uuid3字段值必须是有效的 UUID3 格式
uuid4字段值必须是有效的 UUID4 格式
uuid5字段值必须是有效的 UUID5 格式
ascii字段值必须是 ASCII 字符
printascii字段值必须是可打印的 ASCII 字符

Gin 中使用验证规则

安装 Gin

go get -u github.com/gin-gonic/gin

使用验证规则

type LoginRequest struct {
Username string `form:"username" binding:"required"`
Password string `form:"password" binding:"required,min=6"`
}

处理请求:在你的路由处理函数中,使用 ShouldBindShouldBindJSONShouldBindQuery 等方法来绑定请求数据并触发验证

func login(c *gin.Context) {
var req LoginRequest
if err := c.ShouldBind(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 处理登录逻辑...
}

参考链接

https://github.com/go-playground/validator

HttpOnly 的用处

· 阅读需 2 分钟
素明诚
Full stack development

HttpOnly 是一个设置在 cookies 上的属性,用于阻止客户端 JavaScript 访问这些 cookies,以提高网站安全性,特别是防止跨站脚本(XSS)攻击

背景和详细说明

背景 HttpOnly 最初由 Microsoft 引入,并在 2002 年首次出现在 Internet Explorer 中,目的是增强 Web 应用的安全性

原因 出现是为了减少 XSS 攻击的风险,这类攻击可能会劫持用户会话或窃取敏感信息,如认证 tokens

功能 HttpOnly 属性用于限制 cookies 仅可通过 HTTP(S) 请求访问,使得客户端脚本(如 JavaScript)无法读取或修改这些 cookies

设置方法 在设置 cookie 时,将 HttpOnly 标志加入响应头中 例如在 Go 语言的 HTTP 服务器代码中,你可以这样设置:

    router.GET("/set-cookie", func(c *gin.Context) {
// 设置一个 HttpOnly 的 Cookie
http.SetCookie(c.Writer, &http.Cookie{
Name: "session_token",
Value: "some_secure_token",
Expires: time.Now().Add(24 * time.Hour), // 设置过期时间
HttpOnly: true, // 确保 HttpOnly 标志被设置
})

c.String(http.StatusOK, "Cookie set successfully")
})

设置后的好处

安全性增强 设置 HttpOnly 后,即使网站出现 XSS 漏洞,也难以通过客户端脚本直接获取标记为 HttpOnly 的 cookies,有效降低信息泄露风险

数据保护 保护用户数据不被恶意脚本窃取,尤其是在涉及身份验证和用户会话管理时

go get 命令详解

· 阅读需 2 分钟
素明诚
Full stack development

常用命令

-d:下载模块到本地系统,但不安装。这通常用于仅下载源码而不进行编译的场景。

-u:更新已有的模块及其依赖到新版本。这包括直接依赖和间接依赖。

  • -u=patch:限制更新到补丁版本,即在主要和次要版本号不变的情况下更新到最新修订版。
  • -u=minor:更新到允许的最新次要版本,可能会包括主要版本号不变的新功能。

-t:除了获取包本身,还会获取包用于测试的依赖。

-insecure:允许使用不安全的协议,如 HTTP,进行网络操作。这在访问某些未使用 HTTPS 的私有仓库时可能需要,但通常不推荐因安全风险。

-v:显示详细的操作信息

版本控制参数

可以指定获取特定版本、分支或修订版的包。

  • 例如:go get foo@v1.2.3go get foo@latestgo get foo@branch-name

随着 Go 语言的版本更新,go get 的功能在 Go 1.16 版本以后有所变化,逐渐转向主要用于旧的 GOPATH 模式下的包管理,而在模块模式下(即使用 go.mod),更推荐使用 go install 来安装可执行文件。以下是 Go 1.16 之后,针对模块模式的 go get 行为:

  • 在 Go 模块模式下,go get 主要用于添加依赖到当前模块或更新模块和它们的依赖。
  • go install pkg@version 安装特定版本的可执行模块。