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

news2024/11/25 12:23:41

基于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

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

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

相关文章

软件测试入门学习笔记

系统测试流程规范 一.研发模型 1.瀑布模型 从可行性研究&#xff08;或系统分析&#xff09;开始&#xff0c;需求 2.增量迭代模型 3.敏捷开发模型 二.质量模型

神经网络中正则化和正则化率的含义

在神经网络中,正则化是一种用于防止模型过拟合的技术。过拟合是指模型在训练数据上表现得很好,但是对于未见过的新数据,其泛化能力却很差。正则化通过在损失函数中添加一个额外的项来惩罚模型的复杂度,从而鼓励模型学习更加简单、更加泛化的特征。 正则化的含义 正则化通常…

如何修改WordPress数据库表前缀以提高安全性

WordPress作为世界上最受欢迎的内容管理系统之一&#xff0c;吸引了数以百万计的用户。然而&#xff0c;正因为其广泛的使用&#xff0c;WordPress网站也成为了黑客攻击的目标之一。其中一个最常见的安全漏洞是使用默认的数据库表前缀wp_&#xff0c;使得黑客能够更轻松地进行大…

引导过程和服务控制

目录 一.Linux操作系统的引导过程 1.开机自检&#xff08;BIOS&#xff09;&#xff1a; 2.MBR引导&#xff1a; 3.启动GRUB菜单&#xff1a; 4.加载Linux内核 5.init进程初始化 二.系统初始化进程 1.init 进程 2.Systemd 2.1 systemd 概述 2.2 Systemd 与 传统 Sy…

设计千万级并发系统架构需要考虑的各方面因素

设计千万级并发系统架构需要考虑多方面因素&#xff0c;包括系统的可伸缩性、高可用性、性能、安全性等。 1、分布式架构&#xff1a; 使用微服务架构&#xff1a;将系统拆分成多个独立的服务&#xff0c;每个服务都可以独立部署和扩展。 使用分布式服务框架&#xff1a;如S…

Unity类银河恶魔城学习记录12-11 P133 Merge Skill Tree with Parry skill源代码

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili Parry_Skill.cs using UnityEngine; using UnityEngine.UI;public class P…

Python多态

1.多态 多态定义&#xff1a;多态&#xff08;polymorphism&#xff09;是指同一个方法调用由于对象不同可能会产生不同的行为 注意以下2点&#xff1a; 1.多态是方法的多态&#xff0c;属性没有多态。 2.多态的存在有2个必要条件&#xff1a;继承、方法重写 class Animal:de…

LearnOpenGl练习题-着色器

LearnOpenGl练习题-着色器 题目地址&#xff1a;着色器 - LearnOpenGL CN 题目一&#xff1a;修改顶点着色器让三角形上下颠倒&#xff1a; #version 330 core layout (location 0) in vec3 aPos; layout (location 1) in vec3 aColor; out vec3 ourColor; void main() {gl…

openEuler 22.03 LTS SP3源码编译部署OpenStack-Bobcat(Neutron使用OVN)

openEuler 22.03 LTS SP3部署OpenStack-Bobcat 说明机器详情安装操作系统注意事项基础准备Controller节点 && Compute节点 && Block节点关闭防火墙关闭selinux设置静态IP更新安装前准备Controller节点 && Compute节点 && Block节点设置主机名配…

简单认识Git(dirsearch、githack下载),git泄露(ctfhub)

目录 dirsearch下载地址: githack下载&#xff08;一次不成功可多试几次&#xff09; 一、什么是Git 1.git结构 2.git常用命令及示例 3.Git泄露原理 二、Git泄露 1.Log 2.Stash 3.Index 工具准备&#xff1a;dirsearch、githack dirsearch下载地址: GitHub - mauroso…

配置流策略实现不同网段间限制互访

在创建了 vlanif 接口并配置 IP 地址后,不同 vlan 间就可以互通了。 如果我们想要限制vlan 之间的通信,就可以通过配置流策略实现。 配置步骤(以华为交换机为例) 1、ACL 定义拒绝通过的数据流 2、定义流分类,按照ACL 对报文进行分类 3、配置流行为,动作为deny 4、配置流…

计算机不联网是否有IP地址

计算机不联网是否会有IP地址&#xff0c;这个问题涉及到计算机网络的基础知识。要深入探讨这个问题&#xff0c;我们需要从IP地址的定义、作用&#xff0c;以及计算机在不联网状态下的网络配置等多个方面进行分析。 首先&#xff0c;IP地址&#xff08;Internet Protocol Addre…

深度学习——常用激活函数解析与对比

1、 简介 在神经网络中&#xff0c;激活函数扮演着至关重要的角色。它们的主要目的是引入非线性因素&#xff0c;使得网络能够学习和表示更加复杂的函数映射。以下是激活函数应具备的特点&#xff0c;以及这些特点为何重要的详细解释&#xff1a; 引入非线性有助于优化网络&am…

求奖金(if)(C语言)

一、N-S流程图&#xff1b; 二、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>int main() {//初始化变量值&#xff1b;int I 0;float bonus 0;//提示用户&#xff1b;printf("请输入利润I&#xff1a;");//获取用户值&#xf…

怎么给一个字典进行按值或key来排序?

字典是具有指定数字或键的特定数据集或组。在 Python 以外的编程语言中&#xff0c;它们也被称为哈希映射或关联数组。 一般来说&#xff0c;它是键值对的形式&#xff0c;就像现实世界的字典一样。 要创建字典&#xff0c;请从左括号开始&#xff0c;添加键并键入一个冒号。…

SpringBoot 项目Docker部署三种方式

一种&#xff1a;直接拷贝jar到服务器部署 1.增加docker配置文件 新建Dockerfile文件&#xff0c;负责Docker的配置 FROM openjdk:21#ENV timezone RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \&& echo Asia/Shanghai >/etc/timezone# WORKD…

[2021最新]Java时间戳和日期时间互转换

代码&#xff1a; import java.text.ParseException; import java.text.SimpleDateFormat;public class MainProcess {public static void main(String[] args) throws ParseException {// 1.set formatSimpleDateFormat timeSmat new SimpleDateFormat("yyyy-MM-dd HH:…

【Jupyter Notebook】快捷键

在命令模式下&#xff0c;单元格边框是灰色&#xff08;缺省&#xff09;的。这些快捷键主要用于操作单元格。 Enter&#xff1a;进入编辑模式Shift Enter&#xff1a;运行当前单元格并选中下一个单元格Ctrl Enter&#xff1a;运行当前单元格Alt Enter&#xff1a;运行当前单…

高效生产管理:选择顺通网络ERP系统派单的理由

显然&#xff0c;传统的生产管理模式已经难以满足现代企业的需求&#xff0c;因此选择一款适合自身业务特点的生产管理软件成为了企业的当务之急。顺通鞋业ERP系统作为一款功能强大的生产管理软件&#xff0c;凭借其出色的派单功能&#xff0c;正逐渐成为众多企业的首选。通过系…

DBA-现在应该刚刚入门吧

说来话长 在2023年以前&#xff0c;我的DBA生涯都是“孤独的”。成长路径除了毕业前的实习期有人带&#xff0c;后续几乎都是靠自学。如何自学&#xff0c;看视频、看文档、网上查阅资料、项目实战。 可能是学疏才浅 &#xff0c;一直都是在中小公司混&#xff0c;在中小公司通…