Gin 框架之Cookie与Session

文章目录

    • 一、Cookie和Session的由来
    • 二、Cookie简介
      • 1. 什么是Cookie
      • 2. Cookie规范
      • 3. 安全性
      • 4. Cookie 关键配置
    • 三、Session简介
      • 1. 什么是Session
      • 2. Session 安全性
      • 3. 如何让客户端携带 sess_id
    • 四、使用 Gin 的 Session 插件
      • 4.1 介绍
      • 4.2 基本使用
    • 五、 session与store
      • 5.1 会话(Session)
      • 5.2 存储(Store)
    • 六、小黄书实践

一、Cookie和Session的由来

我们知道HTTP协议无连接的, 也就是不保存用户的状态信息。

早期(十几年前)的网页是静态的, 数据都是写死的, 人们访问网页只是用来查看新闻的, 没有保存用户状态的需求。

而往后出现了像论坛、博客、网购这一类需要保存用户信息的网站, 如果网站不保存用户的状态信息, 意味着用户每次访问都需要重新输入用户名和密码, 这无疑对用户的体验是极其不好的。

于是, 就出现了会话跟踪技术, 我们可以把它理解为客户端与服务端之间的一次会晤, 一次会晤包含的多次请求与响应, 每次请求都带着请求参数, 比如请求登入的请求参数是用户名和密码, 服务端就会拿着请求参数与数据库去比对, 找到相应的用户信息。

如何实现会话跟踪 : 在HTTP协议中可以使用Cookie来完成, 在Web开发中可以使用Session来完成

  • Cookie是存在浏览器中的键值对, 每次发送请求都携带者参数, 但是容易被截获, 不安全
  • 于是就出现了Session, 它是存在于服务端的键值对, key为随机字符串, 安全性提高了, 但所有的数据都存在服务器中, 服务器的压力很大
  • 之后便产生了Token的概念, 服务端签发加密后的字符串给客户端浏览器保存, 客户端每次请求携带用户名和密码, 并加上由服务端签发的用户名和密码加密的字符串, 服务端收到请求后再对用户名密码加密, 与后面携带的密文对比, 由于它也是保存在客户端浏览器上的, 所以也叫Cookie

二、Cookie简介

1. 什么是Cookie

  • Cookie是服务器保存在客户端浏览器之上的key-value键值对 : username='jarvis';password="123"
  • 它是随着服务器的响应发送给客户端, 客户端将其保存, 下一次请求时会将Cookie放在其中, 服务器通过识别Cookie就能知道是哪个客户端浏览器

2. Cookie规范

  • Cookie大小上限为4KB
  • 一个服务器最多在客户端浏览器上保存20个Cookie
  • 一个浏览器最多保存300个Cookie

上面是HTTP中Cookie的规范, 现在浏览器的竞争, 有些Cookie大小能打到8KB, 最多可以保存500个Cookie

不同浏览器之间的Cookie是不共享的

3. 安全性

  • Cookie保存在浏览器本地, 意味着很容易被窃取和篡改

4. Cookie 关键配置

在使用 Cookie 的时候,要注意“安全使用”。

  • Domain: 也就是 Cookie 可以用在什么域名下,按照最小化原则来设定。
  • Path: Cookie 可以用在什么路径下,同样按照最小化原则来设定。
  • Max-AgeExpires: 过期时间,只保留必要时间。
  • Http-Only: 设置为 true 的话,那么浏览器上的 JS 代码将无法使用这个 Cookie。永远设置为 true。
  • Secure: 只能用于 HTTPS 协议,生产环境永远设置为 true。
  • SameSite: 是否允许跨站发送 Cookie,尽量避免。

在Go语言中,标准库net/http提供了用于处理HTTP请求和响应的功能,包括处理Cookie的相关功能。可以通过http.Cookie{}查看。

对应解释如下:

type Cookie struct {Name  string      // Cookie的名称,用于唯一标识一个CookieValue string      // Cookie的值,存储在Cookie中的具体数据Path       string    // Cookie的路径,指定了哪些路径下的页面可以访问该CookieDomain     string    // Cookie的域,指定了哪些域名下可以访问该CookieExpires    time.Time // Cookie的过期时间,表示Cookie在何时之前有效RawExpires string    // 仅用于读取Cookie,保存未解析的过期时间信息// MaxAge=0 表示没有指定'Max-Age'属性// MaxAge<0 表示立即删除Cookie,相当于 'Max-Age: 0'// MaxAge>0 表示'Max-Age'属性存在,并以秒为单位给出MaxAge   intSecure   bool   // Secure属性,指定是否只在使用HTTPS协议时才发送CookieHttpOnly bool   // HttpOnly属性,设置为true时,禁止通过JavaScript访问该CookieSameSite SameSite // SameSite属性,控制是否允许跨站发送CookieRaw      string   // 保存未解析的原始Cookie字符串Unparsed []string // 保存未解析的属性-值对文本
}

三、Session简介

1. 什么是Session

  • 存放在服务器上的键值对 : {'weqweq':{'username':'jarvis','password':'123'}}
  • Cookie可以保存状态, 但本身最大只能支持4069字节, 并且不安全, 于是就出现了Session
  • 它能支持更多字节, 并且保存在服务端上, 具有较高的安全性,Session基于Cookie, 本地存放服务器返回给浏览器的随机字符串
  • 客户端浏览器请求中携带随机字符串(session_id), 服务端收到后与数据库中存储的session做对比

ps : session存储的方式多种多样, 可以是数据库、缓存、硬盘等等

2. Session 安全性

Session 关键在于服务器要给浏览器一个 sess_id,也就是 Session 的 ID。

后续每一次请求都带上这个 Session ID,服务端就知道你是谁了。

但是后端服务器是“只认 ID 不认人”的。也就是说,如果攻击者拿到了你的 ID,那么服务器就会把攻击者当成你。在下图中,攻击者窃取到了 sess_id,就可以冒充是你了。

3. 如何让客户端携带 sess_id

因为 sess_id 是标识你身份的东西,所以你需要在每一次访问系统的时候都带上。

  • 最佳方式就是用 Cookie,也就是 sess_id 放到 Cookie 里面。sess_id 自身没有任何敏感信息,所以放 Cookie 也可以。
  • 也可以考虑放 Header,比如说在 Header 里面带一个 sess_id。这就需要前端的研发记得在 Header 里面带上。
  • 还可以考虑放查询参数,也就是 ?sess_id=xxx

理论上来说还可以放 body,但是基本没人这么干。在一些禁用了 Cookie 功能的浏览器上,只能考虑后两者。

在浏览器上,你可以通过插件 cookieEditor 来查看某个网站的 Cookie 信息。

四、使用 Gin 的 Session 插件

4.1 介绍

Gin框架本身并不内置对Session的支持,但你可以使用第三方的Session中间件来实现。其中比较常用的是 github.com/gin-contrib/sessions

4.2 基本使用

首先,确保你已经安装了Session中间件:

go get github.com/gin-contrib/sessions

然后在你的Gin应用程序中使用它。以下是一个基本的使用示例:

package mainimport ("github.com/gin-contrib/sessions""github.com/gin-contrib/sessions/cookie""github.com/gin-gonic/gin""net/http""time"
)func main() {// 创建Gin引擎r := gin.Default()// 使用Cookie存储Sessionstore := cookie.NewStore([]byte("secret"))r.Use(sessions.Sessions("mysession", store))// 路由:设置Sessionr.GET("/set", func(c *gin.Context) {// 获取默认Sessionsession := sessions.Default(c)// 设置Session中的键值对session.Set("user", "example@example.com")session.Set("expires", time.Now().Add(24*time.Hour).Unix())// 保存Sessionsession.Save()// 返回JSON响应c.JSON(http.StatusOK, gin.H{"message": "Session set successfully"})})// 路由:获取Sessionr.GET("/get", func(c *gin.Context) {// 获取默认Sessionsession := sessions.Default(c)// 从Session中获取键值对user := session.Get("user")expires := session.Get("expires")// 返回JSON响应c.JSON(http.StatusOK, gin.H{"user": user, "expires": expires})})// 启动Gin应用,监听端口8080r.Run(":8080")
}

在上述示例中,我们使用github.com/gin-contrib/sessions/cookie包创建了一个基于Cookie的Session存储。路由"/set"用于设置Session,路由"/get"用于获取Session。请注意,这里的Session数据是存储在客户端的Cookie中的,因此在实际应用中需要注意安全性。

五、 session与store

在Web应用中,会话(session)是一种用于在不同请求之间存储和共享用户信息的机制。通常,会话用于跟踪用户的身份验证状态、存储用户首选项和其他与用户相关的数据。在Gin框架中,会话的管理通常通过sessionstore两个概念来完成。

5.1 会话(Session)

  • 概念: 会话是在服务器端存储用户状态的一种机制。每个用户访问网站时,服务器都会为其创建一个唯一的会话标识符,该标识符存储在用户的浏览器中,通常通过Cookie来实现。服务器可以根据这个标识符来识别用户,并在多个请求之间共享用户的状态信息。
  • 作用: 主要用于存储用户的身份验证状态、用户的首选项、购物车内容等用户相关的信息,以便在用户访问不同页面或进行不同请求时能够保持一致的用户状态。

5.2 存储(Store)

  • 概念: 存储是用于实际存储和检索会话数据的地方。存储可以是内存、数据库、文件系统等,具体取决于应用程序的需求。存储负责维护会话数据的持久性和安全性。
  • 作用: 存储的主要作用是提供对会话数据的 CRUD(Create、Read、Update、Delete)操作。当用户发起请求时,存储会根据会话标识符检索相应的会话数据,服务器可以通过存储来实现会话的管理。

在Gin框架中,常用的Session中间件是 github.com/gin-contrib/sessions,而存储则可以选择不同的后端,例如使用Cookie、Redis、内存等。

在Gin应用中引入该中间件,通过创建Session对象和设定存储引擎,可以方便地进行Session的处理。以下是一个基本的使用示例:

package mainimport ("github.com/gin-contrib/sessions""github.com/gin-contrib/sessions/cookie""github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()// 使用Cookie存储Sessionstore := cookie.NewStore([]byte("secret"))r.Use(sessions.Sessions("mysession", store))r.GET("/set", func(c *gin.Context) {// 设置Sessionsession := sessions.Default(c)session.Set("user", "example@example.com")session.Save()c.JSON(http.StatusOK, gin.H{"message": "Session set successfully"})})r.GET("/get", func(c *gin.Context) {// 获取Sessionsession := sessions.Default(c)user := session.Get("user")c.JSON(http.StatusOK, gin.H{"user": user})})r.Run(":8080")
}
  • cookie.NewStore([]byte("secret")) 在上述示例中,使用了Cookie存储。cookie.NewStore 创建了一个基于Cookie的存储引擎,使用了一个密钥来加密Cookie中的会话数据。
  • 其他存储引擎: 除了Cookie存储,github.com/gin-contrib/sessions 还支持其他存储引擎,例如Redis、文件系统等。可以根据实际需求选择合适的存储引擎。
// 使用Redis存储Session的例子
import ("github.com/gin-contrib/sessions""github.com/gin-contrib/sessions/redis""github.com/gin-gonic/gin"
)func main() {r := gin.Default()// 使用Redis存储Sessionstore, _ := redis.NewStore(10, "tcp", "localhost:6379", "", []byte("secret"))r.Use(sessions.Sessions("mysession", store))// ...
}

在实际项目中,选择存储引擎时要考虑性能、可扩展性和持久性等因素。 Cookie 存储适用于小型应用,而对于大型应用,可能需要选择支持分布式的存储引擎,如 Redis。

总体而言,使用Gin的Session中间件能够方便地管理会话,而不同的存储引擎则提供了灵活的选择,以适应不同的应用场景。

六、小黄书实践

目录结构

├── docker-compose.yaml         # Docker Compose配置文件,定义了整个应用的容器化部署
├── go.mod                      # Go模块文件,用于管理项目的依赖项
├── go.sum                      # Go模块文件,包含依赖项的版本信息
├── internal                    # 内部模块,包含应用程序的业务逻辑
│   ├── domain                  # 领域层,包含领域对象和领域逻辑
│   │   └── user.go            # 用户领域对象的定义
│   ├── repository              # 数据仓库层,用于与数据库交互
│   │   ├── dao                 # 数据访问对象,与数据库的交互接口
│   │   │   ├── init.go         # 数据库初始化
│   │   │   └── user.go         # 用户数据访问对象的定义
│   │   └── user.go             # 用户数据仓库接口的定义
│   ├── service                 # 服务层,包含业务逻辑的实现
│   │   └── user.go            # 用户服务的实现
│   └── web                     # Web层,处理HTTP请求和路由
│       ├── middleware          # 中间件,用于处理HTTP请求的中间逻辑
│       │   └── login.go       # 登录中间件的定义
│       ├── user.go             # 用户相关的HTTP处理函数和路由定义
│       └── user_test.go        # 用户相关的测试文件
├── main.go                     # 主程序入口,应用程序的启动文件
├── pkg                         # 外部可导出的包,供其他应用程序或模块使用
├── script                      # 脚本目录,包含各种初始化和辅助脚本└── mysql                   # MySQL初始化脚本目录└── init.sql            # 数据库初始化SQL脚本

internal/domain/user.go 文件代码:

package domainimport "time"// User 领域对象,是DDD 中的 entity
type User struct {Id       int64Email    stringPassword stringCtime    time.Time
}

internal/repository/user.go

package repositoryimport ("context""webook/internal/domain""webook/internal/repository/dao"
)var (ErrUserDuplicateEmail = dao.ErrUserDuplicateEmailErrUserNotFound       = dao.ErrUserNotFound
)type UserRepository struct {dao *dao.UserDAO
}func NewUserRepository(dao *dao.UserDAO) *UserRepository {return &UserRepository{dao: dao,}
}func (r *UserRepository) Create(ctx context.Context, u domain.User) error {return r.dao.Insert(ctx, dao.User{Email:    u.Email,Password: u.Password,})
}
func (r *UserRepository) FindByEmail(ctx context.Context, email string) (domain.User, error) {u, err := r.dao.FindByEmail(ctx, email)if err != nil {return domain.User{}, err}return domain.User{Id:       u.Id,Email:    u.Email,Password: u.Password,}, nil
}

internal/repository/dao/init.go

package daoimport "gorm.io/gorm"func InitTable(db *gorm.DB) error {return db.AutoMigrate(&User{})
}

internal/repository/dao/user.go

package daoimport ("context""errors""github.com/go-sql-driver/mysql""gorm.io/gorm""time"
)var (ErrUserDuplicateEmail = errors.New("邮箱冲突")ErrUserNotFound       = gorm.ErrRecordNotFound
)type UserDAO struct {db *gorm.DB
}func NewUserDAO(db *gorm.DB) *UserDAO {return &UserDAO{db: db,}
}
func (dao *UserDAO) Insert(ctx context.Context, u User) error {// 存毫秒数now := time.Now().UnixNano()u.Ctime = nowu.Utime = nowerr := dao.db.WithContext(ctx).Create(&u).Error// 类型断言,判断是否是mysql的唯一冲突错误if mysqlErr, ok := err.(*mysql.MySQLError); ok {const uniqueConflictsErrNo uint16 = 1062if mysqlErr.Number == uniqueConflictsErrNo {// 邮箱已存在return ErrUserDuplicateEmail}}return err
}
func (dao *UserDAO) FindByEmail(ctx context.Context, email string) (User, error) {var u Usererr := dao.db.WithContext(ctx).Where("email = ?", email).First(&u).Errorreturn u, err
}// User 直接对应数据库中的表
// 有些人叫做entity,有些人叫做model
type User struct {Id int64 `gorm:"primaryKey,autoIncrement"`// 全部用户唯一Email    string `gorm:"unique"`Password string// 创建时间,毫秒数,解决时区问题Ctime int64// 更新时间Utime int64
}

internal/service/user.go

package serviceimport ("context""errors""golang.org/x/crypto/bcrypt""webook/internal/domain""webook/internal/repository""webook/internal/repository/dao"
)var (ErrUserDuplicateEmail    = dao.ErrUserDuplicateEmailErrInvalidUserOrPassword = errors.New("账号/邮箱或密码不对")
)type UserService struct {repo *repository.UserRepository
}func NewUserService(repo *repository.UserRepository) *UserService {return &UserService{repo: repo,}
}func (svc *UserService) SignUp(ctx context.Context, u domain.User) error {// 先加密密码hash, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)if err != nil {return err}u.Password = string(hash)// 然后就是,存起来return svc.repo.Create(ctx, u)
}func (svc *UserService) Login(ctx context.Context, email, password string) (domain.User, error) {// 先找用户u, err := svc.repo.FindByEmail(ctx, email)if err == repository.ErrUserNotFound {return domain.User{}, ErrInvalidUserOrPassword}if err != nil {return domain.User{}, err}// 比较密码了err = bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))if err != nil {// DEBUGreturn domain.User{}, ErrInvalidUserOrPassword}return u, nil
}

internal/web/user.go

package webimport (regexp "github.com/dlclark/regexp2""github.com/gin-contrib/sessions""github.com/gin-gonic/gin""net/http""webook/internal/domain""webook/internal/service"
)type UserHandler struct {svc         *service.UserServiceemailExp    *regexp.RegexppasswordExp *regexp.Regexp
}func NewUserHandler(svc *service.UserService) *UserHandler {const (emailRegexPattern    = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"passwordRegexPattern = `^(?=.*[A-Za-z])(?=.*\d)(?=.*[$@$!%*#?&])[A-Za-z\d$@$!%*#?&]{8,}$`)emailExp := regexp.MustCompile(emailRegexPattern, regexp.None)passwordExp := regexp.MustCompile(passwordRegexPattern, regexp.None)return &UserHandler{svc:         svc,emailExp:    emailExp,passwordExp: passwordExp,}
}func (u *UserHandler) RegisterRoutes(server *gin.Engine) {ug := server.Group("/user")   //ug is user groupug.GET("/profile", u.Profile) // 查询用户信息接口ug.POST("/signup", u.SignUp)  // 注册接口ug.POST("/login", u.Login)    // 登录接口ug.POST("/logout", u.Logout)  // 登出接口ug.POST("/edit", u.Edit)      // 修改用户信息接口}func (u *UserHandler) RegisterRoutesV1(ug *gin.RouterGroup) {ug.GET("/profile", u.Profile) // 查询用户信息接口ug.POST("/signup", u.SignUp)  // 注册接口ug.POST("/login", u.Login)    // 登录接口ug.POST("/logout", u.Logout)  // 登出接口ug.POST("/edit", u.Edit)      // 修改用户信息接口}
func (u *UserHandler) Profile(ctx *gin.Context) {
}
func (u *UserHandler) SignUp(ctx *gin.Context) {type SignUpRequest struct {Email           string `json:"email"`Password        string `json:"password"`ConfirmPassword string `json:"confirmPassword"`}var request SignUpRequest// 如果 Bind 方法发现输入有问题,它就会直接返回一 个错误响应到前端。if err := ctx.Bind(&request); err != nil {return}ok, err := u.emailExp.MatchString(request.Email)if err != nil {ctx.String(http.StatusOK, "系统错误")return}if !ok {ctx.String(http.StatusOK, "邮箱格式错误")return}ok, err = u.passwordExp.MatchString(request.Password)if err != nil {ctx.String(http.StatusOK, "系统错误")return}if !ok {ctx.String(http.StatusOK, "密码必须包含至少一个数字、一个字母、一个特殊字符,并且长度至少为8位")return}if request.Password != request.ConfirmPassword {ctx.String(http.StatusOK, "两次密码不一致")return}// 调用以一下svc 的方法err = u.svc.SignUp(ctx, domain.User{Email:    request.Email,Password: request.Password,})if err == service.ErrUserDuplicateEmail {ctx.String(http.StatusOK, "邮箱冲突")return}if err != nil {ctx.String(http.StatusOK, "系统错误")return}ctx.String(http.StatusOK, "注册成功")
}
func (u *UserHandler) Login(ctx *gin.Context) {type LoginRequest struct {Email    string `json:"email"`Password string `json:"password"`}var request LoginRequestif err := ctx.Bind(&request); err != nil {return}// 调用以一下svc 的方法user, err := u.svc.Login(ctx, request.Email, request.Password)if err == service.ErrInvalidUserOrPassword {ctx.String(http.StatusOK, "账号或密码错误")return}if err != nil {ctx.String(http.StatusOK, "系统错误")return}// 登录成功设置sessionsess := sessions.Default(ctx)// 设置session的key和valuesess.Set("userId", user.Id)sess.Save()ctx.String(http.StatusOK, "登录成功")return
}func (u *UserHandler) Logout(ctx *gin.Context) {}
func (u *UserHandler) Edit(ctx *gin.Context) {}

main.go

package mainimport ("github.com/gin-contrib/cors""github.com/gin-contrib/sessions""github.com/gin-contrib/sessions/cookie""github.com/gin-gonic/gin""gorm.io/driver/mysql""gorm.io/gorm""strings""time""webook/internal/repository""webook/internal/repository/dao""webook/internal/service""webook/internal/web""webook/internal/web/middleware"
)func main() {db := initDB()ser := initWebServer()u := initUser(db)u.RegisterRoutes(ser)ser.Run(":8080")}func initWebServer() *gin.Engine {ser := gin.Default()ser.Use(func(ctx *gin.Context) {println("这是一个中间件")})ser.Use(func(ctx *gin.Context) {println("这是二个中间件")})ser.Use(cors.New(cors.Config{//AllowOrigins: []string{"*"},//AllowMethods: []string{"POST", "GET"},AllowHeaders: []string{"Content-Type", "Authorization"},//ExposeHeaders: []string{"x-jwt-token"},// 是否允许你带 cookie 之类的东西AllowCredentials: true,AllowOriginFunc: func(origin string) bool {if strings.HasPrefix(origin, "http://localhost") {// 你的开发环境return true}return strings.Contains(origin, "yourcompany.com")},MaxAge: 12 * time.Hour,}))// 用 cookie 存储 sessionstore := cookie.NewStore([]byte("secret"))// 使用 session 中间件ser.Use(sessions.Sessions("mysession", store))//ser.Use(middleware.NewLoginMiddlewareBuilder().Build())//ser.Use(middleware.NewLoginMiddlewareBuilder().//	IgnorePaths("/users/signup").//	IgnorePaths("/users/login").Build())// v1middleware.IgnorePaths = []string{"sss"}ser.Use(middleware.CheckLogin())// 不能忽略sss这条路径server1 := gin.Default()server1.Use(middleware.CheckLogin())return ser
}func initUser(db *gorm.DB) *web.UserHandler {ud := dao.NewUserDAO(db)repo := repository.NewUserRepository(ud)svc := service.NewUserService(repo)u := web.NewUserHandler(svc)return u
}
func initDB() *gorm.DB {db, err := gorm.Open(mysql.Open("root:root@tcp(localhost:13316)/webook"))if err != nil {// panic 相当于 整个 goroutine 结束panic("failed to connect database")}err = dao.InitTable(db)if err != nil {panic("failed to init table")}return db
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/634808.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Win10 打开文件突然鼠标变成一个蓝色大圈卡住点不了也打不开文件,重启电脑也是这样

环境: Win10 专业版 加密客户端环境 问题描述: Win10 打开桌面word文件突然鼠标变成一个蓝色大圈卡住点不了也打不开文件,重启电脑也是这样,只有蓝色圈变大没有鼠标指针出现圈卡着不会动,和那些有鼠标箭头加小蓝色圈不一样 解决方案: 某网上查看的,还是要自己排查…

linux 更新镜像源

打开终端&#xff0c;备份一下旧的 源 文件&#xff0c;以防万一 cd /etc/apt/ ls sudo cp sources.list sources.list.bak ls然后打开清华大学开源软件镜像站 搜索一下你的linux发行版本&#xff0c;我这里是ubuntu发行版本 点击这个上面图中的问号 查看一下自己的版本号&a…

MySQL复合查询解析

&#x1f388;行百里者半九十&#x1f388; &#x1f388;目录&#x1f388; 概念多表查询自连接子查询单行子查询多行子查询in关键字all关键字any关键字 多列子查询在from中使用子查询合并查询unionunion all 总结 概念 之前我们很多的查询都只是对于单表进行查询&#xff0c…

飞书修改不了名称?飞书如何修改名称,修改昵称

飞书如何修改名称 点击编辑信息 在这里修改姓名就可以啦

Day31 46全排列 47全排列II 回溯去重tips 51N皇后 37解数独

46 全排列 给定一个 没有重复 数字的序列&#xff0c;返回其所有可能的全排列。 示例: 输入: [1,2,3]输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ] 排列问题与组合问题的不同之处就在于&#xff0c;没有startIndex&#xff0c;同时需要设置一个used数组…

Ant Design Vue上传多个图片

模板代码&#xff1a; 定义变量&#xff1a; 文件限制的函数&#xff1a; 上传的函数&#xff1a; 样式函数&#xff1a; 完整代码&#xff1a; <template><div class"dialog-upload" v-if"showUploadDialog"><div class"dialog-uplo…

【PIE-Engine 数据资源】全球 10 米土地覆盖产品 (ESA-2020)

文章目录 一、 简介二、描述三、波段四、示例代码参考资料 一、 简介 数据名称全球 10 米土地覆盖产品 (ESA-2020)时间范围2020年空间范围全球数据来源ESA WorldCover project 2020代码片段var imagespie. ImageCollection (“ESA/WORLD_COVER_2020”) 二、描述 全球 10 米土…

nvm, node.js, npm, yarn 安装配置

文章目录 nvm 安装node.js 安装npm yarn 配置 nvm 安装 nvm 是一个 node.js 管理工具&#xff0c;可以快捷下载安装使用多个版本的node.js linux 命令行输入&#xff1a; curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bashwget -qO- https…

主机自动重启或自动关机故障

公司有两台品牌机&#xff0c;使用约1年半&#xff0c;最近频繁出现自动重启或自动关机现象。 排除插线板、电源线等电源的问题&#xff0c;拆机查看。 故障后的主机拆开后&#xff0c;先摸了一下散热片&#xff0c;并不烫手。 仔细查看内部&#xff0c;排线规整&#xff0c…

base64编码与图片之间相互转换

题记&#xff1a; Base64是网络上最常见的用于传输8Bit字节码的编码方式之一 1、将base64转换成图片 <img src"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0 DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg&quo…

白码ERP实现销售订单自动生成生产订单功能

某ERP项目中&#xff0c;业务员创建销售单后&#xff0c;还需要确认产品库存是否充足&#xff0c;如不充足&#xff0c;还需要手动创建生成订单通知车间进行生产&#xff0c;比较麻烦&#xff0c;客户希望系统可以自动进行判断&#xff0c;自动根据现有库存生成生产订单。 目标…

Pyside6入门教学——编写一个UI界面并显示

1、安装Pyside6 输入下列命令安装Pyside6。 pip install Pyside6 2、设计UI 打开Qt设计工具&#xff08;在安装Pyside6包的目录下&#xff09;。 【注】我这用的是anaconda虚拟环境&#xff0c;所以我的路径是D:\App\Anaconda3\envs\snake\Lib\site-packages\PySide6。设计…

三使用Docker Hub管理镜像

使用Docker Hub管理镜像 Docker Hub是Docker官方维护的Docker Registry&#xff0c;上面存放着很多优秀的镜像。不仅如此&#xff0c;Docker Hub还提供认证、工作组结构、工作流工具、构建触发器等工具来简化我们的工作。 前文已经讲过&#xff0c;我们可使用docker search 命…

freemaker导出

一、创建ftl模板文件 1、将xls文件转换为xml格式 需要将xml中内容格式化一下&#xff0c; 在线 XML 格式化 | 菜鸟工具 (jyshare.com) 将格式化好的内容&#xff0c;保存在 ftl 文件中&#xff0c;放入项目文件。 二、后端对数据做组装 Java代码中对导出文件做赋值 Override…

k8s之对外服务ingress

一、service 1、service作用 ①集群内部&#xff1a;不断跟踪pod的变化&#xff0c;不断更新endpoint中的pod对象&#xff0c;基于pod的IP地址不断变化的一种服务发现机制&#xff08;endpoint存储最终对外提供服务的IP地址和端口&#xff09; ②集群外部&#xff1a;类似负…

Vue3 在 history 模式下通过 vite 打包部署白屏

Vue3 在 history 模式下通过 vite 打包部署后白屏; 起因 hash 模式 url 后面跟个 # 强迫症犯了改成了 history,就此一波拉锯战开始了 ... 期间 nigix 和 router 各种反复排查尝试最终一波三折后可算是成功了 ... Vue官方文档 具体配置可供参考如下: 先简要介绍下,当前项目打包…

【EI会议征稿通知】第七届先进电子材料、计算机与软件工程国际学术会议(AEMCSE 2024)

第七届先进电子材料、计算机与软件工程国际学术会议(AEMCSE 2024&#xff09; 2024 7th International Conference on Advanced Electronic Materials, Computers and Software Engineering 第七届先进电子材料、计算机与软件工程国际学术会议(AEMCSE 2024)将于2024年5月10-1…

AI智慧导诊系统(源码),利用人工智能技术,通过根据患者症状描述智能推荐挂号科室。

什么是智慧导诊系统? 简单地说&#xff0c;智慧导诊系统是一种利用人工智能技术&#xff0c;为医生提供帮助的系统。它可以通过分析患者的症状和病史为医生提供疾病诊断和治疗方案的建议。 智慧导诊系统可根据患者症状描述智能推荐挂号科室。系统采用对话式询问患者症状&…

2024-01-17(SpringCloud)

1.使用openFeign的itemClient接口去做远程调用其他微服务中的接口。但我们直接使用了itemClient接口&#xff0c;而不是该接口的实现类&#xff0c;说明我们是使用该接口的代理对象帮我们做远程调用的。这个代理对象是invocationHandler来生成的。 2.openFeign底层发起远程调用…

什么是DOM?(JavaScript DOM是什么?)

1、DOM简洁 DOM是js中最重要的一部分&#xff0c;没有DOM就不会通过js实现和用户之间的交互。 window是最大的浏览器对象&#xff0c;在它的下面还有很多子对象&#xff0c;我们要学习的DOM就是window对象下面的document对象 DOM&#xff08;Document Object Model&#xff09…