09-认证-自研微服务框架

认证

1. 开启https支持

func (e *Engine) RunTLS(addr, certFile, keyFile string) {err := http.ListenAndServeTLS(addr, certFile, keyFile, e.Handler())if err != nil {log.Fatal(err)}
}

1.1 测试

证书生成:

  • 安装openssl

    网站下载:http://slproweb.com/products/Win32OpenSSL.html

    (mac电脑 自行搜索安装)

  • 生成私钥文件

    ## 需要输入密码
    openssl genrsa -des3 -out ca.key 2048
    
  • 创建证书请求

    openssl req -new -key ca.key -out ca.csr
    
  • 生成ca.crt

    openssl x509 -req -days 365 -in ca.csr -signkey ca.key -out ca.crt
    

找到openssl.cnf 文件

  1. 打开copy_extensions = copy

  2. 打开 req_extensions = v3_req

  3. 找到[ v3_req ],添加 subjectAltName = @alt_names

  4. 添加新的标签 [ alt_names ] , 和标签字段

    [ alt_names ]
    IP.1 = 127.0.0.1
    DNS.1 = *.mszlu.com
    
  5. 生成证书私钥server.key

    openssl genpkey -algorithm RSA -out server.key
    
  6. 通过私钥server.key生成证书请求文件server.csr

    openssl req -new -nodes -key server.key -out server.csr -days 3650 -config ./openssl.cnf -extensions v3_req
    
  7. 生成SAN证书

    openssl x509 -req -days 365 -in server.csr -out server.pem -CA ca.crt -CAkey ca.key -CAcreateserial -extfile ./openssl.cnf -extensions v3_req
    
engine.RunTLS(":8118", "key/server.pem", "key/server.key")

postman测试

客户端需要生成对应的公钥和私钥

私钥:

openssl genpkey -algorithm RSA -out client.key

证书:

openssl req -new -nodes -key client.key -out client.csr -days 3650 -config ./openssl.cnf -extensions v3_req

SAN证书:

openssl x509 -req -days 365 -in client.csr -out client.pem -CA ca.crt -CAkey ca.key -CAcreateserial -extfile ./openssl.cnf -extensions v3_req

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

postman访问https://127.0.0.1:8118/user/jsonParam即可。

2. 认证支持

2.1 Basic认证

Basic 认证(基础认证),是最简单的认证方式。它简单地将用户名:密码进行 base64 编码后,放到 HTTP Authorization Header 中。HTTP 请求到达后端服务后,后端服务会解析出 Authorization Header 中的 base64 字符串,解码获取用户名和密码,并将用户名和密码跟数据库中记录的值进行比较,如果匹配则认证通过。
当然base64并不是加密技术,所以这种认证方式并不安全,即使密码进行加密,攻击者也可以进行重放攻击。

可以和SSL(可以理解为是HTTPS)一起使用保证其安全性

Authorization: Basic ${basic}
2.1.1 Context传递信息
type Context struct {W                     http.ResponseWriterR                     *http.Requestengine                *EnginequeryCache            url.ValuesformCache             url.ValuesDisallowUnknownFields boolIsValidate            boolStatusCode            intLogger                *msLog.LoggerKeys                  map[string]anymu                    sync.RWMutex
}
func (c *Context) Set(key string, value string) {c.mu.Lock()if c.Keys == nil {c.Keys = make(map[string]any)}c.Keys[key] = valuec.mu.Unlock()
}func (c *Context) Get(key string) (value any, exists bool) {c.mu.RLock()value, exists = c.Keys[key]c.mu.RUnlock()return
}
2.1.2 实现

中间件:

package msgoimport ("encoding/base64""net/http"
)type Accounts struct {UnAuthHandler func(ctx *Context)Users         map[string]string
}func (a *Accounts) BasicAuth(next HandlerFunc) HandlerFunc {return func(ctx *Context) {//判断请求中是否有Authorization的Headerusername, password, ok := ctx.R.BasicAuth()if !ok {a.UnAuthHandlers(ctx)return}pw, ok := a.Users[username]if !ok {a.UnAuthHandlers(ctx)return}if pw != password {a.UnAuthHandlers(ctx)return}ctx.Set("user", username)next(ctx)}
}func (a *Accounts) UnAuthHandlers(ctx *Context) {if a.UnAuthHandler != nil {a.UnAuthHandler(ctx)} else {ctx.W.WriteHeader(http.StatusUnauthorized)}
}func BasicAuth(username, password string) string {auth := username + ":" + passwordreturn base64.StdEncoding.EncodeToString([]byte(auth))
}
func (c *Context) SetBasicAuth(username, password string) {c.R.Header.Set("Authorization", "Basic "+basicAuth(username, password))
}

2.2 Digest认证

Digest 认证(摘要认证),是另一种 HTTP 认证协议,它与基本认证兼容,但修复了基本认证的严重缺陷。Digest 具有如下特点:

  • 绝不会用明文方式在网络上发送密码。
  • 可以有效防止恶意用户进行重放攻击。
  • 可以有选择地防止对报文内容的篡改。

完成摘要认证需要下面这四步:

  • 客户端请求服务端的资源。
  • 在客户端能够证明它知道密码从而确认其身份之前,服务端认证失败,返回401 Unauthorized,并返回WWW-Authenticate头,里面包含认证需要的信息。
  • 客户端根据WWW-Authenticate头中的信息,选择加密算法,并使用密码随机数 nonce,计算出密码摘要 response,并再次请求服务端。
  • 服务器将客户端提供的密码摘要与服务器内部计算出的摘要进行对比。如果匹配,就说明客户端知道密码,认证通过,并返回一些与授权会话相关的附加信息,放在 Authorization-Info 中。
    WWW-Authenticate头中包含的信息见下表:

在这里插入图片描述

虽然使用摘要可以避免密码以明文方式发送,一定程度上保护了密码的安全性,但是仅仅隐藏密码并不能保证请求是安全的。因为请求(包括密码摘要)仍然可以被截获,这样就可以重放给服务器,带来安全问题。

为了防止重放攻击,服务器向客户端发送了密码随机数 nonce,nonce 每次请求都会变化。客户端会根据 nonce 生成密码摘要,这种方式,可以使摘要随着随机数的变化而变化。服务端收到的密码摘要只对特定的随机数有效,而没有密码的话,攻击者就无法计算出正确的摘要,这样我们就可以防止重放攻击。

摘要认证可以保护密码,比基本认证安全很多。但摘要认证并不能保护内容,所以仍然要与 HTTPS 配合使用,来确保通信的安全。

2.3 Bearer 认证

Bearer 认证,也称为令牌认证,是一种 HTTP 身份验证方法。Bearer 认证的核心是 bearer token。bearer token 是一个加密字符串,通常由服务端根据密钥生成。客户端在请求服务端时,必须在请求头中包含Authorization: Bearer 。服务端收到请求后,解析出 ,并校验 的合法性,如果校验通过,则认证通过。跟Basic认证一样,Bearer 认证需要配合 HTTPS 一起使用,来保证认证安全性。

当前最流行的 token 编码方式是 JSON Web Token

2.3.1 JWT

JWT(全称:Json Web Token)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。

简单点说就是一种认证机制。

JWT一般是这样一个字符串,分为三个部分,以"."隔开:

A.B.C

Header

A部分:

JWT第一部分是头部分,它是一个描述JWT元数据的Json对象

{"alg": "HS256","typ": "JWT"
}

alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256),typ属性表示令牌的类型,JWT令牌统一写为JWT。

最后,使用Base64 URL算法将上述JSON对象转换为字符串保存。

Payload

B部分:

JWT第二部分是Payload,也是一个Json对象,除了包含需要传递的数据,还有七个默认的字段供选择。

分别是

iss:发行人exp:到期时间sub:主题aud:用户nbf:在此之前不可用iat:发布时间jti:JWT ID用于标识该JWT

{//默认字段"sub":"Go手写微服务框架教程",//自定义字段"name":"go框架","isAdmin":"true","loginTime":"2022-06-28 12:00:03"
}

这部分是可以被解密的,并不安全,所以不要存敏感信息。

使用Base64 URL算法转换为字符串保存

Signature

C部分:

JWT第三部分是签名。

首先需要指定一个secret,该secret仅仅保存在服务器中,保证不能让其他用户知道。然后使用Header指定的算法对Header和Payload进行计算,然后就得出一个签名哈希。也就是Signature。

那么Application Server如何进行验证呢?可以利用JWT前两段,用同一套哈希算法和同一个secret计算一个签名值,然后把计算出来的签名值和收到的JWT第三段比较,如果相同则认证通过。

使用JWT进行认证的步骤:

  • 客户端使用用户名和密码请求登录
  • 服务端收到请求后,会去验证用户名和密码。如果用户名和密码跟数据库记录不一致,则验证失败;如果一致则验证通过,服务端会签发一个 Token 返回给客户端
  • 客户端收到请求后会将 Token 缓存起来,比如放在浏览器 Cookie 中或者 LocalStorage 中,之后每次请求都会携带该 Token
  • 服务端收到请求后,会验证请求中的 Token,验证通过则进行业务逻辑处理,处理完后返回处理后的结果。
2.3.2 实现
go get -u github.com/golang-jwt/jwt/v4

func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) {if path == "" {path = "/"}http.SetCookie(c.W, &http.Cookie{Name:     name,Value:    url.QueryEscape(value),MaxAge:   maxAge,Path:     path,Domain:   domain,SameSite: c.sameSite,Secure:   secure,HttpOnly: httpOnly,})
}

在这里插入图片描述

package tokenimport ("github.com/golang-jwt/jwt/v4""github.com/mszlu521/msgo""time"
)type JwtHandler struct {//算法Alg string//登录认证Authenticator func(ctx *msgo.Context) (map[string]any, error)//过期时间 秒TimeOut time.Duration//时间函数 从此时开始计算过期TimeFuc func() time.Time//私钥PrivateKey string//keyKey string//save cookieSendCookie     boolCookieName     stringCookieMaxAge   intCookieDomain   stringSecureCookie   boolCookieHTTPOnly bool
}//登录处理,登录成功后,使用jwt生成tokenfunc (j *JwtHandler) LoginHandler(ctx *msgo.Context) (string, error) {data, err := j.Authenticator(ctx)if err != nil {return "", err}if j.Alg == "" {j.Alg = "HS256"}signingMethod := jwt.GetSigningMethod(j.Alg)token := jwt.New(signingMethod)claims := token.Claims.(jwt.MapClaims)if data != nil {for key, value := range data {claims[key] = value}}if j.TimeFuc == nil {j.TimeFuc = func() time.Time {return time.Now()}}expire := j.TimeFuc().Add(j.TimeOut)claims["exp"] = expire.Unix()claims["iat"] = j.TimeFuc().Unix()var tokenString stringvar errToken errorif j.usingPublicKeyAlgo() {tokenString, errToken = token.SignedString(j.PrivateKey)} else {tokenString, errToken = token.SignedString(j.Key)}if errToken != nil {return "", err}if j.SendCookie {if j.CookieName == "" {j.CookieName = "jwt_token"}if j.CookieMaxAge == 0 {j.CookieMaxAge = int(expire.Unix() - j.TimeFuc().Unix())}maxAge := j.CookieMaxAgectx.SetCookie(j.CookieName, tokenString, maxAge, "/", j.CookieDomain, j.SecureCookie, j.CookieHTTPOnly)}return tokenString, nil
}func (j *JwtHandler) usingPublicKeyAlgo() bool {switch j.Alg {case "RS256", "RS512", "RS384":return true}return false
}

退出登录

func (j *JwtHandler) LogoutHandler(ctx *msgo.Context) error {//清除cookie即可if j.SendCookie {if j.CookieName == "" {j.CookieName = JWTToken}ctx.SetCookie(j.CookieName, "", -1, "/", j.CookieDomain, j.SecureCookie, j.CookieHTTPOnly)return nil}return nil
}

刷新Token

package tokenimport ("github.com/golang-jwt/jwt/v4""github.com/mszlu521/msgo""time"
)const JWTToken = "jwt_token"type JwtHandler struct {//算法Alg string//登录认证Authenticator func(ctx *msgo.Context) (map[string]any, error)//过期时间 秒TimeOut        time.DurationRefreshTimeOut time.Duration//时间函数 从此时开始计算过期TimeFuc func() time.Time//私钥PrivateKey string//keyKey string//save cookieSendCookie     boolCookieName     stringCookieMaxAge   intCookieDomain   stringSecureCookie   boolCookieHTTPOnly bool
}type JWTResponse struct {Token        stringRefreshToken string
}//登录处理,登录成功后,使用jwt生成tokenfunc (j *JwtHandler) LoginHandler(ctx *msgo.Context) (*JWTResponse, error) {data, err := j.Authenticator(ctx)if err != nil {return nil, err}if j.Alg == "" {j.Alg = "HS256"}signingMethod := jwt.GetSigningMethod(j.Alg)token := jwt.New(signingMethod)claims := token.Claims.(jwt.MapClaims)if data != nil {for key, value := range data {claims[key] = value}}if j.TimeFuc == nil {j.TimeFuc = func() time.Time {return time.Now()}}expire := j.TimeFuc().Add(j.TimeOut)claims["exp"] = expire.Unix()claims["iat"] = j.TimeFuc().Unix()var tokenString stringvar errToken errorif j.usingPublicKeyAlgo() {tokenString, errToken = token.SignedString(j.PrivateKey)} else {tokenString, errToken = token.SignedString(j.Key)}if errToken != nil {return nil, errToken}if j.SendCookie {if j.CookieName == "" {j.CookieName = JWTToken}if j.CookieMaxAge == 0 {j.CookieMaxAge = int(expire.Unix() - j.TimeFuc().Unix())}maxAge := j.CookieMaxAgectx.SetCookie(j.CookieName, tokenString, maxAge, "/", j.CookieDomain, j.SecureCookie, j.CookieHTTPOnly)}jr := &JWTResponse{}refreshToken, err := j.refreshToken(data)if err != nil {return nil, err}jr.Token = tokenStringjr.RefreshToken = refreshTokenreturn jr, nil
}func (j *JwtHandler) LogoutHandler(ctx *msgo.Context) error {//清除cookie即可if j.SendCookie {if j.CookieName == "" {j.CookieName = JWTToken}ctx.SetCookie(j.CookieName, "", -1, "/", j.CookieDomain, j.SecureCookie, j.CookieHTTPOnly)return nil}return nil
}
func (j *JwtHandler) usingPublicKeyAlgo() bool {switch j.Alg {case "RS256", "RS512", "RS384":return true}return false
}func (j *JwtHandler) refreshToken(data map[string]any) (string, error) {signingMethod := jwt.GetSigningMethod(j.Alg)token := jwt.New(signingMethod)claims := token.Claims.(jwt.MapClaims)if data != nil {for key, value := range data {claims[key] = value}}if j.TimeFuc == nil {j.TimeFuc = func() time.Time {return time.Now()}}expire := j.TimeFuc().Add(j.RefreshTimeOut)claims["exp"] = expire.Unix()claims["iat"] = j.TimeFuc().Unix()var tokenString stringvar errToken errorif j.usingPublicKeyAlgo() {tokenString, errToken = token.SignedString(j.PrivateKey)} else {tokenString, errToken = token.SignedString(j.Key)}if errToken != nil {return "", errToken}return tokenString, nil
}
func (c *Context) GetCookie(name string) string {cookie, err := c.R.Cookie(name)if err != nil {return ""}if cookie != nil {return cookie.Value}return ""
}

func (j *JwtHandler) RefreshHandler(ctx *msgo.Context) (*JWTResponse, error) {var token string//检测refresh token是否过期storageToken, exists := ctx.Get(j.RefreshKey)if exists {token = storageToken.(string)}if token == "" {return nil, errors.New("token not exist")}t, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {if j.usingPublicKeyAlgo() {return []byte(j.PrivateKey), nil}return []byte(j.Key), nil})if err != nil {return nil, err}claims := t.Claims.(jwt.MapClaims)//未过期的情况下 重新生成token和refreshTokenif j.TimeFuc == nil {j.TimeFuc = func() time.Time {return time.Now()}}expire := j.TimeFuc().Add(j.RefreshTimeOut)claims["exp"] = expire.Unix()claims["iat"] = j.TimeFuc().Unix()var tokenString stringvar errToken errorif j.usingPublicKeyAlgo() {tokenString, errToken = t.SignedString(j.PrivateKey)} else {tokenString, errToken = t.SignedString(j.Key)}if errToken != nil {return nil, errToken}if j.SendCookie {if j.CookieName == "" {j.CookieName = JWTToken}if j.CookieMaxAge == 0 {j.CookieMaxAge = int(expire.Unix() - j.TimeFuc().Unix())}maxAge := j.CookieMaxAgectx.SetCookie(j.CookieName, tokenString, maxAge, "/", j.CookieDomain, j.SecureCookie, j.CookieHTTPOnly)}jr := &JWTResponse{}refreshToken, err := j.refreshToken(claims)if err != nil {return nil, err}jr.Token = tokenStringjr.RefreshToken = refreshTokenreturn jr, nil
}
2.3.3 测试
g.Get("/login", func(ctx *msgo.Context) {jwt := &token.JwtHandler{}jwt.Key = []byte("123456")jwt.SendCookie = truejwt.TimeOut = 10 * time.Minutejwt.Authenticator = func(ctx *msgo.Context) (map[string]any, error) {data := make(map[string]any)data["userId"] = 1return data, nil}token, err := jwt.LoginHandler(ctx)if err != nil {log.Println(err)ctx.JSON(http.StatusOK, err.Error())return}ctx.JSON(http.StatusOK, token)})
2.3.4 实现jwt认证中间件

func (j *JwtHandler) AuthInterceptor(next msgo.HandlerFunc) msgo.HandlerFunc {return func(ctx *msgo.Context) {//if j.Header == "" {j.Header = "Authorization"}token := ctx.R.Header.Get(j.Header)if token == "" {if j.SendCookie {token = ctx.GetCookie(j.CookieName)if token == "" {if j.AuthHandler == nil {ctx.W.WriteHeader(http.StatusUnauthorized)} else {j.AuthHandler(ctx, nil)}return}}}t, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {if j.usingPublicKeyAlgo() {return []byte(j.PrivateKey), nil}return []byte(j.Key), nil})if err != nil {if j.AuthHandler == nil {ctx.W.WriteHeader(http.StatusUnauthorized)} else {j.AuthHandler(ctx, err)}return}ctx.Set("claims", t.Claims.(jwt.MapClaims))next(ctx)}
}
jwt := token.JwtHandler{Key: []byte("123456"),}engine.Use(jwt.AuthInterceptor)

e {
token = ctx.GetCookie(j.CookieName)
if token == “” {
if j.AuthHandler == nil {
ctx.W.WriteHeader(http.StatusUnauthorized)
} else {
j.AuthHandler(ctx, nil)
}
return
}
}
}
t, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
if j.usingPublicKeyAlgo() {
return []byte(j.PrivateKey), nil
}
return []byte(j.Key), nil
})
if err != nil {
if j.AuthHandler == nil {
ctx.W.WriteHeader(http.StatusUnauthorized)
} else {
j.AuthHandler(ctx, err)
}
return
}
ctx.Set(“claims”, t.Claims.(jwt.MapClaims))
next(ctx)
}
}


~~~go
jwt := token.JwtHandler{Key: []byte("123456"),}engine.Use(jwt.AuthInterceptor)

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

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

相关文章

华为HCIP Datacom H12-831 卷24

多选题 1、如图所示,某园区部署OSPF实现网络互通,其中Area1部署为NSSA区域。某工程师为了实现R1访问R4的环回口地址,在R4的OSPF进程中引入直连路由。以下关于该场景的描述,错误的有哪些项? A、在R4引入直连路由后,R1通过转换后的…

Socket网络编程(三)——TCP快速入门

目录 概述TCP连接可靠性1. 三次握手过程2. 四次挥手过程3. 为什么挥手需要四次? 传输可靠性TCP核心APITCP传输初始化配置&建立连接客户端创建Socket建立连接服务端创建ServerSocket监听连接ServerSocket 和 Socket的关系 Socket基本数据类型传输客户端数据传输服…

postman测试接口

1、postman测试接口 (1)首先安装postman 下载地址:Download Postman | Get Started for Free 选择对应版本下载,然后安装即可 (2)使用postman发送请求 比如以下这个请求例子: 使用postman发…

UE4 材质多张图片拼接成一张图片(此处用2×2拼接)

UE4 材质多张图片拼接成一张图片&#xff08;此处用22拼接&#xff09; //TexCoord,TextureA,TextureB,TextureC,TextureDfloat3 ReturnTexture TextureA; if(TexCoord.x < 0.5 && TexCoord.y < 0.5) {ReturnTexture TextureA; } else if(TexCoord.x > 0.5…

php docx,pptx,excel表格上传阿里云,腾讯云存储后截取第一页生成缩略图

php把word转图片的方法:首先给服务器安装libreoffice;然后使用exec函数来调用命令行操作;最后通过“exec(“soffice --headless --invisible…””方法把word转图片即可。 服务器环境:centos7 *集成环境:宝塔 我们开始给服务器安装libreoffice 直接执行下面的代码就可以…

代码随想录Leetcode213. 打家劫舍 II

题目&#xff1a; 代码(首刷看解析 2024年2月29日&#xff09;&#xff1a; class Solution { public:int robRange(vector<int>& nums, int start, int end) {if (start end) return nums[start];vector<int> dp(nums.size(), 0); // 遍历dp[start] nums[s…

【Oracle】玩转Oracle数据库(七):RMAN恢复管理器

前言 嘿&#xff0c;数据库大魔法师们&#xff01;准备好迎接新的技术大招了吗&#xff1f;今天我们要探索的是Oracle数据库中的神奇利器——RMAN恢复管理器&#xff01;&#x1f6e1;️&#x1f4be; 在这篇博文【Oracle】玩转Oracle数据库&#xff08;七&#xff09;&#xf…

mov和mp4格式哪个好?专业讲师告诉你答案【详】

在数字化时代&#xff0c;我们经常面临着选择视频格式的困境&#xff0c;尤其是需要在不同设备和平台上播放或分享视频时。在这些选择中&#xff0c;MOV和MP4格式是两种最常见的选项之一。 然而&#xff0c;mov和mp4格式哪个好呢&#xff1f;这个问题并不容易回答&#xff0c;…

C++重点---STL简介

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、STL简介 STL&#xff08;Standard Template Library&#xff09;是C标准库中的一个重要组成部分&#xff0c;它提供了…

LabVIEW水下温盐深数据一体化采集与分析

LabVIEW水下温盐深数据一体化采集与分析 开发一个基于LabVIEW的水下温盐深数据一体化采集与分析系统&#xff0c;实现海洋环境监测的自动化和精确化。通过集成温度、盐度和深度传感器&#xff0c;结合USB数据采集卡&#xff0c;利用LabVIEW软件开发的图形化界面&#xff0c;实…

理解计算着色器中glsl语言的内置变量

概要 本文通过示例的方式&#xff0c;着重解释以下几个内置变量&#xff1a; gl_WorkGroupSizegl_NumWorkGroupsgl_LocalInvocationIDgl_WorkGroupIDgl_GlobalInvocationID 基本概念 局部工作组与工作项 一个3x2x1的局部工作组示例如下&#xff0c;每个小篮格子表示一个工作项…

403页面绕过

403页面绕过 文章目录 403页面绕过姿势一: 端口利用姿势二&#xff1a;修改HOST姿势三&#xff1a;覆盖请求URL姿势四&#xff1a;Referer标头绕过姿势五&#xff1a;代理IP姿势六&#xff1a;扩展名绕过 姿势一: 端口利用 拿到客户给的地址后&#xff0c;首先进行信息收集。端…

[数据集][目标检测]游泳者溺水数据集VOC+YOLO格式2类别895张

数据集制作单位&#xff1a;未来自主研究中心(FIRC) 数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;895 标注数量(xml文件个数)&#xff1a…

js 面试 什么是WebSockets?HTTP和HTTPS有什么不同?web worker是什么?

概念&#xff1a; webSocket 是一种在客户端和服务端之间建立持久连接的协议&#xff0c;它提供全双工通信通道&#xff0c;是服务器可以主动向客户端推送数据&#xff0c;同时也可以接受客户端发送的数据。 1 webSocket与https区别&#xff1f; 在网络通信中&#xff0c;We…

Android进阶之路 - RecyclerView停止滑动后Item自动居中(SnapHelper辅助类)

之前一直没注意 SnapHelper 辅助类的功能&#xff0c;去年的时候看到项目中仅通过俩行代码设置 RecyclerView 后就提升了用户体验&#xff0c;觉得还是很有必要了解一下&#xff0c;尝试过后才发现其 PagerSnapHelper、LinearSnapHelper 子类可以作用于不同场景&#xff0c;且听…

vue a-table 实现指定字段相同数据合并行

vue a-table 实现相同数据合并行 实现效果代码实现cloums数据格式数据源格式合并代码 实现效果 代码实现 cloums数据格式 const getColumns function () {return [{title: "分类",dataIndex: "checked",width: "150px",customRender: (text, …

在SAP HANA中使用OData(二)

通常有两种方式通过OData来暴露SAP HANA中的数据库对象&#xff0c;一是直接使用Database Object&#xff0c;比如前一篇和本篇文章介绍的例子&#xff0c;这种方式针对于数据已经存在于SAP HANA中&#xff0c;在Repository中没有对应的设计时对象(Design-time Object)&#xf…

Qt SQLite的创建和使用

重点&#xff1a; 1.SQLite创建数据库内容方法 链接&#xff1a;SQLite Expert Personal的简单使用-CSDN博客 2.和数据库进行链接方法 QSqlDatabase DB; //数据库连接bool MainWindow::openDatabase(QString aFile) {DBQSqlDatabase::addDatabase("QSQLITE"); /…

vue - - - - - vue3使用draggable拖拽组件

vue3使用draggable拖拽组件 一、组件安装二、插件使用三、遇到的问题1. missing required prop&#xff1a; “itemKey” 一、组件安装 yarn add vuedraggablenext // or npm i -S vuedraggablenext二、插件使用 <template><draggableitem-key"id"class&q…

【系统分析师】-软件工程

1、信息系统的生命周期 1、四阶段划分 立项阶段&#xff1a;企业全局、形成概念、需求分析。包含【系统分析师】-系统规划-CSDN博客开发阶段&#xff1a;总体规划--系统分析--设计--实施--验收运维阶段&#xff1a;通过验收、移交之后消亡阶段&#xff1a;更新改造、功能扩展…