基于go+vue的多人在线聊天的im系统

基于go+vue的多人在线聊天的im系统

在这里插入图片描述

文章目录

  • 基于go+vue的多人在线聊天的im系统
    • 一、前端部分
    • 二、后端部分
      • 1、中间件middleware设计jwt和cors
      • 2、配置文件设计
      • 3、Mysql和Redis连接
      • 4、路由设计
      • 5、核心功能设计

一、前端部分

打算优化一下界面,正在开发中。。。

二、后端部分

1、中间件middleware设计jwt和cors

jwt.go

package middlewares

import (
	"crypto/rsa"
	"fmt"
	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
	"im/global"
	"io/ioutil"
	"net/http"
	"os"
	"time"
)

func JWT() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		// 从请求头获取token
		token := ctx.Request.Header.Get("w-token")
		if token == "" {
			ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
				"msg": "请登录",
			})
			return
		}
		// 打开存储公钥文件
		file, _ := os.Open(global.SrvConfig.JWTInfo.PublicKeyPath)
		// 读取公钥文件
		bytes, _ := ioutil.ReadAll(file)
		// 解析公钥
		publickey, _ := jwt.ParseRSAPublicKeyFromPEM(bytes)

		jwtVerier := &JWTTokenVerifier{PublicKey: publickey}

		claim, err := jwtVerier.Verify(token)
		if err != nil {
			fmt.Println(err)
			ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
				"msg": "请登录",
			})
			return
		}
		ctx.Set("claim", claim)        //获取全部信息
		ctx.Set("name", claim.Subject) // 获取用户名
		ctx.Next()
	}
}

func Auth(token string) (*MyClaim, error) {
	if token == "" {
		return nil, fmt.Errorf("ws认证失败,token为空")
	}
	file, _ := os.Open(global.SrvConfig.JWTInfo.PublicKeyPath)
	bytes, _ := ioutil.ReadAll(file)
	publickey, _ := jwt.ParseRSAPublicKeyFromPEM(bytes)
	jwtVerier := &JWTTokenVerifier{PublicKey: publickey}
	return jwtVerier.Verify(token)
}

type JWTTokenVerifier struct {
	// 存储用于验证签名的公钥
	PublicKey *rsa.PublicKey
}

type MyClaim struct {
	Role int
	jwt.StandardClaims
}

func (v *JWTTokenVerifier) Verify(token string) (*MyClaim, error) {
	t, err := jwt.ParseWithClaims(token, &MyClaim{},
		func(*jwt.Token) (interface{}, error) {
			return v.PublicKey, nil
		})

	if err != nil {
		return nil, fmt.Errorf("cannot parse token: %v", err)
	}

	if !t.Valid {
		return nil, fmt.Errorf("token not valid")
	}

	clm, ok := t.Claims.(*MyClaim)
	if !ok {
		return nil, fmt.Errorf("token claim is not MyClaim")
	}

	if err := clm.Valid(); err != nil {
		return nil, fmt.Errorf("claim not valid: %v", err)
	}
	return clm, nil

}

type JWTTokenGen struct {
	privateKey *rsa.PrivateKey
	issuer     string
	nowFunc    func() time.Time
}

func NewJWTTokenGen(issuer string, privateKey *rsa.PrivateKey) *JWTTokenGen {
	return &JWTTokenGen{
		issuer:     issuer,
		nowFunc:    time.Now,
		privateKey: privateKey,
	}
}

func (t *JWTTokenGen) GenerateToken(userName string, expire time.Duration) (string, error) {
	nowSec := t.nowFunc().Unix()
	tkn := jwt.NewWithClaims(jwt.SigningMethodRS512, &MyClaim{
		StandardClaims: jwt.StandardClaims{
			Issuer:    t.issuer,
			IssuedAt:  nowSec,
			ExpiresAt: nowSec + int64(expire.Seconds()),
			Subject:   userName,
		},
	})
	return tkn.SignedString(t.privateKey)
}

cors.go

package middlewares

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func Cors() gin.HandlerFnc {
	return func(c *gin.Context) {
		method := c.Request.Method

		c.Header("Access-Control-Allow-Origin", "*")
		c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token, w-token")
		c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PATCH, PUT")
		c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
		c.Header("Access-Control-Allow-Credentials", "true")

		if method == "OPTIONS" {
			c.AbortWithStatus(http.StatusNoContent)
		}
	}
}

2、配置文件设计

config.yaml

jwt:
  privateKeyPath: ./config/private.key
  publicKeyPath: ./config/public.key
port: 9288
name: user-web
redis:
  ip: 
  port: 6379
mysql:
  ip: 
  username: 
  password: 
  db_name: im

config/config.go

package config

// 读取yaml配置文件,形成映射的相关类
type JWTconfig struct {
	PrivateKeyPath string `mapstructure:"privateKeyPath" json:"privateKeyPath"`
	PublicKeyPath  string `mapstructure:"publicKeyPath" json:"publicKeyPath"`
}

type RedisConfig struct {
	IP   string `mapstructure:"ip"`
	Port string `mapstructure:"port"`
}

type MysqlConfig struct {
	IP       string `mapstructure:"ip"`
	Username string `mapstructure:"username"`
	Password string `mapstructure:"password"`
	DbName   string `mapstructure:"db_name"`
}

type SrvConfig struct {
	Name      string      `mapstructure:"name" json:"name"`
	Port      int         `mapstructure:"port" json:"port"`
	JWTInfo   JWTconfig   `mapstructure:"jwt" json:"jwt"`
	RedisInfo RedisConfig `mapstructure:"redis" json:"redis"`
	MysqlInfo MysqlConfig `mapstructure:"mysql" json:"mysql"`
}

initalize/config.go

package Initialize

import (
	"fmt"
	"github.com/fsnotify/fsnotify"
	"github.com/spf13/viper"
	"im/global"
)

func InitConfig() {
	//从配置文件中读取出对应的配置
	var configFileName = fmt.Sprintf("./config.yaml" )
	v := viper.New()
	//文件的路径
	v.SetConfigFile(configFileName)
	if err := v.ReadInConfig(); err != nil {
		panic(err)
	}
	// 开启实时监控
	v.WatchConfig()
	//这个对象如何在其他文件中使用 - 全局变量
	if err := v.Unmarshal(&global.SrvConfig); err != nil {
		panic(err)
	}

	// 文件更新的回调函数
	v.OnConfigChange(func(in fsnotify.Event) {
		fmt.Println("配置改变")
		if err := v.Unmarshal(&global.SrvConfig); err != nil {
			panic(err)
		}
	})
}

func GetEnvInfo(env string) bool {
	viper.AutomaticEnv()
	return viper.IsSet(env)
}

global.go 声明全局变量

package global

import (
	"github.com/go-redis/redis/v8"
	"github.com/jinzhu/gorm"
	"im/config"
	"sync"
)

var (
	// 配置信息
	SrvConfig = config.SrvConfig{}
	// 分别管理存储已注册用户和在线用户
	// 已注册用户map,key为name value为password
	UserMap = sync.Map{}

	// 在线用户map,key为name value为连接句柄list
	LoginMap = sync.Map{}

	// redis客户端
	Redis *redis.Client

	// db服务
	DB *gorm.DB
)

3、Mysql和Redis连接

db.go

package Initialize

import (
	"fmt"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
	"im/global"
	"os"
)

var err error

func InitDB() {
	// 构建数据库连接字符串
	dbConfig := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local",
		global.SrvConfig.MysqlInfo.Username,
		global.SrvConfig.MysqlInfo.Password,
		global.SrvConfig.MysqlInfo.IP,
		global.SrvConfig.MysqlInfo.DbName)
	// 连接数据库
	global.DB, err = gorm.Open("mysql", dbConfig)
	if err != nil {
		fmt.Println("[Initialize] 数据库连接失败:%v", err)
		return
	}
	// 设置连接池参数
	global.DB.DB().SetMaxIdleConns(10)     //设置数据库连接池最大空闲连接数
	global.DB.DB().SetMaxOpenConns(100)    //设置数据库最大连接数
	global.DB.DB().SetConnMaxLifetime(100) //设置数据库连接超时时间

	// 测试数据库连接
	if err = global.DB.DB().Ping(); err != nil {
		fmt.Printf("[Initialize] 数据库连接测试失败:%v\n", err)
		os.Exit(0)
	}
	fmt.Println("[Initialize] 数据库连接测试成功")
}

redis.og

package Initialize

import (
	"context"
	"fmt"
	"github.com/go-redis/redis/v8"
	"im/global"
	"log"
	"sync"
	"time"
)

var once sync.Once

func InitRedis() {
	addr := fmt.Sprintf("%v:%v", global.SrvConfig.RedisInfo.IP, global.SrvConfig.RedisInfo.Port)
	// once.Do() 在一个应用程序生命周期内只会执行一次
	once.Do(func() {
		global.Redis = redis.NewClient(&redis.Options{
			Network:      "tcp",
			Addr:         addr,
			Password:     "",
			DB:           0,               // 指定Redis服务器的数据库索引,0为默认
			PoolSize:     15,              // 连接池最大连接数
			MinIdleConns: 10,              // 连接池最小连接数
			DialTimeout:  5 * time.Second, // 连接超时时间
			ReadTimeout:  3 * time.Second, // 读超时时间
			WriteTimeout: 3 * time.Second, // 写超时时间
			PoolTimeout:  4 * time.Second, // 连接池获取连接的超时时间

			IdleCheckFrequency: 60 * time.Second,
			IdleTimeout:        5 * time.Minute,
			MaxConnAge:         0 * time.Second,

			MaxRetries:      0,
			MinRetryBackoff: 8 * time.Millisecond,
			MaxRetryBackoff: 512 * time.Millisecond,
		})
		pong, err := global.Redis.Ping(context.Background()).Result()
		if err != nil {
			log.Fatal(err)
		}
		log.Println(pong)
	})
}

4、路由设计

	// 注册
	r.POST("/api/register", handle.Register)
	// 已注册用户列表
	r.GET("/api/list", handle.UserList)
	// 登录
	r.POST("/api/login", handle.Login)
	// ws连接
	r.GET("/api/ws", handle.WS)
	// 获取登录列表(目前没用到)
	r.GET("/api/loginlist", handle.LoginList)
	// JWT
	r.Use(middlewares.JWT())
	// 获取用户名
	r.GET("/api/user", handle.UserInfo)

5、核心功能设计

handle/handle.go

package handle

import (
	"fmt"
	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
	"im/global"
	"im/middlewares"
	"im/mysql"
	"io/ioutil"
	"net/http"
	"os"
	"time"
)

type Reg struct {
	Name     string `json:"name"`
	Password string `json:"password"`
}
type UList struct {
	Names []string `json:"names"`
}
type LoginStruct struct {
	Name     string `json:"name" `
	Password string `json:"password" `
}

func Register(c *gin.Context) {
	var reg Reg
	err := c.Bind(&reg)
	if err != nil {
		fmt.Println(err)
		c.JSON(http.StatusOK, gin.H{
			"msg":  "用户名或密码格式错误,请重试",
			"code": "4001",
		})
		return
	}
	mysql.StorageUserToMap()
	_, ok := global.UserMap.Load(reg.Name)
	if ok {
		fmt.Println("用户已存在")
		c.JSON(http.StatusOK, gin.H{
			"msg":  "用户已存在,请登录或更换用户名注册",
			"code": "4000",
		})
		return
	}
	if err := mysql.AddUserToMysql(reg.Name, reg.Password); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"msg":  "内部错误",
			"code": "5000",
		})
		return
	}
	mysql.StorageUserToMap()
	c.JSON(http.StatusOK, gin.H{
		"msg":  "创建用户成功,请登录",
		"code": "2000",
	})
}

func Login(c *gin.Context) {
	var loginData LoginStruct
	err := c.Bind(&loginData)
	if err != nil {
		fmt.Println(err)
		c.JSON(http.StatusOK, gin.H{
			"msg":  "用户名或密码格式错误,请重试",
			"code": "4001",
		})
		return
	}
	psw, ok := global.UserMap.Load(loginData.Name)
	if !ok {
		fmt.Println("用户不存在")
		c.JSON(http.StatusOK, gin.H{
			"msg":  "用户不存在,请注册",
			"code": "4003",
		})
		return
	}
	if loginData.Password != psw.(string) {
		c.JSON(http.StatusOK, gin.H{
			"msg":  "密码错误,请重新输入",
			"code": "4005",
		})
		return
	}

	file, err := os.Open(global.SrvConfig.JWTInfo.PrivateKeyPath)
	if err != nil {
		fmt.Println(err)
		return
	}
	pkBytes, err := ioutil.ReadAll(file)
	privateKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(pkBytes))
	tokenGen := middlewares.NewJWTTokenGen("user", privateKey)
	token, err := tokenGen.GenerateToken(loginData.Name, time.Hour*24*20)
	if err != nil {
		fmt.Println(err)
		return
	}

	c.JSON(http.StatusOK, &gin.H{
		"msg":   "登录成功",
		"code":  "2000",
		"name":  loginData.Name,
		"token": token,
	})
}

func LoginList(c *gin.Context) {
	var users UList
	global.LoginMap.Range(func(key, value interface{}) bool {
		users.Names = append(users.Names, key.(string))
		return true
	})
	c.JSON(http.StatusOK, &users)
}

func getLoginList() *UList {
	var users UList
	global.LoginMap.Range(func(key, value interface{}) bool {
		users.Names = append(users.Names, key.(string))
		return true
	})
	return &users
}

func UserInfo(c *gin.Context) {
	name, _ := c.Get("name")
	userName := name.(string)
	c.JSON(http.StatusOK, gin.H{
		"msg":  "成功",
		"code": "2000",
		"name": userName,
	})
}

func UserList(c *gin.Context) {
	var users UList
	global.UserMap.Range(func(key, value interface{}) bool {
		users.Names = append(users.Names, key.(string))
		return true
	})
	c.JSON(http.StatusOK, &users)
}

ws.go

// websocket 通信


package handle

import (
	"container/list"
	"context"
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
	"im/global"
	"im/middlewares"
	"log"
	"net/http"
)

type WsInfo struct {
	Type    string   `json:"type"`
	Content string   `json:"content"`
	To      []string `json:"to"`
	From    string   `json:"from"`
}

func WS(ctx *gin.Context) {
	var claim *middlewares.MyClaim
	wsConn, _ := Upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
	for {
		_, data, err := wsConn.ReadMessage()
		if err != nil {
			wsConn.Close()
			if claim != nil {
				RemoveWSConnFromMap(claim.Subject, wsConn)
				r, _ := json.Marshal(gin.H{
					"type":    "loginlist",
					"content": getLoginList(),
					"to":      []string{},
				})
				SendMsgToAllLoginUser(r)
			}
			fmt.Println(claim.Subject, "出错,断开连接:", err)
			fmt.Println("当前在线用户列表:", getLoginList().Names)
			return
		}

		var wsInfo WsInfo
		json.Unmarshal(data, &wsInfo)
		if wsInfo.Type == "auth" {
			claim, err = middlewares.Auth(wsInfo.Content)
			if err != nil {
				// 认证失败
				fmt.Println(err)
				rsp := WsInfo{
					Type:    "no",
					Content: "认证失败,请重新登录",
					To:      []string{},
				}
				r, _ := json.Marshal(rsp)
				wsConn.WriteMessage(websocket.TextMessage, r)
				wsConn.Close()

				continue
			}
			// 认证成功
			// 将连接加入map记录
			AddWSConnToMap(claim.Subject, wsConn)

			fmt.Println(claim.Subject, " 加入连接")
			fmt.Println("当前在线用户列表:", getLoginList().Names)

			rsp := WsInfo{
				Type:    "ok",
				Content: "连接成功,请发送消息",
				To:      []string{},
			}
			r, _ := json.Marshal(rsp)
			// 更新登录列表
			wsConn.WriteMessage(websocket.TextMessage, r)
			r, _ = json.Marshal(gin.H{
				"type":    "loginlist",
				"content": getLoginList(),
				"to":      []string{},
			})
			SendMsgToAllLoginUser(r)
			// 发送离线消息
			cmd := global.Redis.LRange(context.Background(), claim.Subject, 0, -1)
			msgs, err := cmd.Result()
			if err != nil {
				log.Println(err)
				continue
			}
			for _, msg := range msgs {
				wsConn.WriteMessage(websocket.TextMessage, []byte(msg))
			}
			global.Redis.Del(context.Background(), claim.Subject)

		} else {
			rsp, _ := json.Marshal(gin.H{
				"type":    "normal",
				"content": wsInfo.Content,
				"to":      []string{},
				"from":    claim.Subject,
			})
			SendMsgToOtherUser(rsp, claim.Subject, wsInfo.To...)
		}
	}
	wsConn.Close()
}

var (
	Upgrader = websocket.Upgrader{
		//允许跨域
		CheckOrigin: func(r *http.Request) bool {
			return true
		},
	}
)

func AddWSConnToMap(userName string, wsConn *websocket.Conn) {
	// 同一用户可以有多个ws连接(登录多次)
	loginListInter, ok := global.LoginMap.Load(userName)
	if !ok {
		// 之前没登录
		loginList := list.New()
		loginList.PushBack(wsConn)
		global.LoginMap.Store(userName, loginList)
	} else {
		// 多次登录
		loginList := loginListInter.(*list.List)
		loginList.PushBack(wsConn)
		global.LoginMap.Store(userName, loginList)
	}
}

func RemoveWSConnFromMap(userName string, wsConn *websocket.Conn) {
	loginListInter, ok := global.LoginMap.Load(userName)
	if !ok {
		fmt.Println("没有连接可以关闭")
	} else {
		// 有连接
		loginList := loginListInter.(*list.List)
		if loginList.Len() <= 1 {
			global.LoginMap.Delete(userName)
		} else {
			for e := loginList.Front(); e != nil; e = e.Next() {
				if e.Value.(*websocket.Conn) == wsConn {
					loginList.Remove(e)
					break
				}
			}
			global.LoginMap.Store(userName, loginList)
		}
	}
}

func SendMsgToOtherUser(data []byte, myName string, otherUserName ...string) {
	for _, otherName := range otherUserName {
		if otherName != myName {
			v, ok := global.LoginMap.Load(otherName)
			if ok {
				// 在线,发送给目标用户的所有客户端
				l := v.(*list.List)
				for e := l.Front(); e != nil; e = e.Next() {
					conn := e.Value.(*websocket.Conn)
					conn.WriteMessage(websocket.TextMessage, data)
				}
			} else {
				_, ok := global.UserMap.Load(otherName)
				if ok {
					//离线消息缓存到redis
					global.Redis.LPush(context.Background(), otherName, data)
				}
			}
		}
	}
}

func SendMsgToAllLoginUser(data []byte) {
	global.LoginMap.Range(func(key, value interface{}) bool {
		l := value.(*list.List)
		for e := l.Front(); e != nil; e = e.Next() {
			conn := e.Value.(*websocket.Conn)
			conn.WriteMessage(websocket.TextMessage, data)
		}
		return true
	})
}

mysql数据读取 mysql.go

package mysql

import (
	"fmt"
	"im/global"
)

type User struct {
	UserName string `gorm:"column:username"`
	Password string `gorm:"column:password"`
}

func StorageUserToMap() {
	var users []User
	err := global.DB.Find(&users).Error
	if err != nil {
		fmt.Printf("[mysql] 查询用户失败:%v\n", err)
		return
	}
	// 将查询到的用户名和密码存储到 UserMap 中
	for _, user := range users {
		global.UserMap.Store(user.UserName, user.Password)
	}
}

func AddUserToMysql(userName, psw string) error {
	// 创建用户模型
	user := User{
		UserName: userName,
		Password: psw,
	}
	// 插入用户记录
	err := global.DB.Create(&user).Error
	if err != nil {
		fmt.Printf("[mysql] 注册失败:%v\n", err)
		return err
	}
	fmt.Printf("[mysql] 注册成功\n")
	return nil
}

项目地址:https://github.com/jiangxyb/goim-websocket

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Realsense D455 调试

1 Realsense D455 配置&#xff1a; RGB&#xff1a;彩色相机&#xff0c;FOV&#xff08;h&#xff0c;v&#xff09;&#xff08; 90*65 &#xff09;红外点阵发射&#xff1a;位于上图中RGB右边&#xff0c;发射特定模式的红外光&#xff0c;通常是一种点阵图案&#xff0c…

React + 项目(从基础到实战) -- 第八期

ajax 请求的搭建 引入mockAP接口设计AJAX 通讯 前置知识 HTTP 协议 , 前后端通讯的桥梁API : XMLHttpRequest 和 fetch常用工具axios mock 引入 Mock.js (mockjs.com) 使用 mockJS 前端代码中引入 mockJs定义要模拟的路由 , 返回结果mockJs 劫持ajax请求(返回模拟的结果)…

记一次kafkakerberos认证问题

1&#xff0c;报错信息 排查思路&#xff1a;检查kerberos配置文件 kerberos.kafka.principalkafka/huawe_baseSECURITY.COM kerberos.kafka.keytabPath/etc/huawe_base.keytab kerberos.kafka.krb5ConfPath/etc/krb5.conf但是查看kafka_client_jass.conf文件&#xff0c;发现…

LoRA模型是什么?

AI Agent能力评测工具AgentBench评测结果 LoRA模型是什么&#xff1f; LoRA模型&#xff08;Low-Rank Adaptation of Large Language Models&#xff09;是一种针对大型语言模型&#xff08;LLMs&#xff09;的微调技术&#xff0c;其目的是在保持模型原有性能的基础上&#x…

数据库服务的运行与登录

打开数据库服务 数据库服务: SQL Server(MSSQLServer) 运行在服务器端的应用程序, 提供数据的存储 / 处理和事务等在使用DBMS的客户端之前必须首先打开该服务 客户端连接到服务器 关于客户端 / 服务器端的说明 客户端 : 数据库管理系统(DBMS), 应用程序服务器端 : 安装的数据…

深澜计费管理系统 /demo/proxy存在任意文件读取漏洞

声明&#xff1a; 本文仅用于技术交流&#xff0c;请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 简介 深澜计费管理系统是一款用于网络设备计费管理的软件…

nginx部署上线

1. windows配置nginx 打包命令 npm run build:prod 1. 安装 nginx mac windows 2. mac / windows 环境下ngnix部署启动项目 2. nginx 解决 history 的 404 问题 3. nginx配置代理解决生产环境跨域问题

元宇宙-虚拟世界的安全风险如何应对

元宇宙&#xff08;Metaverse&#xff09;是一个虚拟时空间的集合&#xff0c;由一系列的增强现实&#xff08;AR&#xff09;、虚拟现实&#xff08;VR&#xff09;和互联网&#xff08;Internet&#xff09;所组成。这个虚拟时空间是一个持续存在的、由众多虚拟世界互相连接而…

链表OJ - 5(合并两个有序链表)

题目描述&#xff08;来源&#xff09; 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 思路 需创建一个头结点&#xff0c;然后从两个链表的表头开始依次比较传入的两个链表的结点的大小&#xff0c;并将两个链表中较小的…

Linux LVM 逻辑卷管理

Logical Volume Manager&#xff0c;逻辑卷管理 能够在保持现有数据不变的情况下动态调整磁盘容量&#xff0c;从而提高磁盘管理的灵活性/boot分区用于存放引导文件&#xff0c;不能基于LVM创建 三大概念&#xff1a; 物理卷PV基于硬盘或分区设备创建而来&#xff0c;生成N多…

自媒体博客Spimes主题 X7.1 简约新闻自媒体类的 typecho 主题源码

spimes主题专为博客、自媒体、资讯类的网站设计开发&#xff0c;自适应兼容手机、平板设备。一款简约新闻自媒体类的 typecho 主题&#xff0c;设计上简约、干净、精致、响应式&#xff0c;后台设置更是强大而且实用的新闻自媒体类主题。 PS&#xff1a;5.0版本改动比较多&…

Golang插件系统实现

插件可以在解耦的基础上灵活扩展应用功能&#xff0c;本文介绍了如何基于Golang标准库实现插件功能&#xff0c;帮助我们构建更灵活可扩展的应用。原文: Plugins with Go 什么是插件 简单来说&#xff0c;插件就是可以被其他软件加载的软件&#xff0c;通常用于扩展应用程序的功…

数据库的特点

前面讲了&#xff0c;数据库是有组织的&#xff0c;规范的把数据保存起来的。 怎么个组织的&#xff0c;规范的&#xff1f; 数据库的特点&#xff1a; 1.将数据放到数据表格&#xff08;二维表&#xff09;中&#xff0c;在将表格放到库中。 2.一个数据库中可以有多张表&am…

【.Net动态Web API】背景与实现原理

&#x1f680;前言 本文是《.Net Core进阶编程课程》教程专栏的导航站&#xff08;点击链接&#xff0c;跳转到专栏主页&#xff0c;欢迎订阅&#xff0c;持续更新…&#xff09; 专栏介绍&#xff1a;通过源码实例来讲解Asp.Net Core进阶知识点&#xff0c;让大家完全掌握每一…

分布式限流——Redis + Lua脚本实现令牌桶算法

主要思路概括如下&#xff1a; 定义数据结构&#xff1a; 使用Redis存储令牌桶的状态&#xff0c;包括当前令牌数&#xff08;KEYS[1]&#xff09;和上一次令牌填充的时间戳&#xff08;KEYS[1]:last&#xff09;。 计算新增令牌&#xff1a; 获取当前系统时间与上次令牌填充时…

中科亿海微-CL1656功能验证开发板

I. 引言 A. 研究背景与意义 CL1656是一款精度高、功耗低、成本低的5V单片低功耗运放&#xff0c;由核心互联公司研发制造&#xff0c;CL1656 是一个 16-bit、快速、低功耗逐次逼近型 ADC&#xff0c;吞吐速率高达 250 kSPS&#xff0c;并且内置低噪声、宽 带宽采样保持放大器。…

Vision Pro 零基础教程:1.机器视觉概述

文章目录 机器视觉简介机器视觉的发展历史机器视觉的结构组成机器视觉的应用工业相机分类1. 按传感器类型分类&#xff1a;2. 按分辨率分类&#xff1a;3. 按扫描方式分类&#xff1a;4. 按输出信号类型分类&#xff1a;5. 按应用领域分类&#xff1a;6. 按接口类型分类&#x…

React【Day2】

React表单控制 受控绑定 概念&#xff1a;使用React组件的状态&#xff08;useState&#xff09;控制表单的状态 双向绑定 MVVM 报错记录&#xff1a; 错误代码&#xff1a; import { useState } from "react";const App () > {const [value, setValue] useS…

使用pytorch构建GAN模型的评估

本文为此系列的第六篇对GAN的评估&#xff0c;上一篇为Controllable GAN。文中使用训练好的分类模型的部分网络提取特征将真实分布与生成分布进行对比来评估模型的好坏&#xff0c;若有不懂的无监督知识点可以看本系列第一篇。 原理 一般来说&#xff0c;我们评估模型的好坏可…

DataGridView添加行号隔行变色

运行效果 颜色对应关系 类实现代码 using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms;namespace WindowsFormsApp1 {…