go+redis基于tcp实现聊天室

go+redis实现聊天室

基于tcp连接通过redis实现了消息的广播,命令改名,查询在线人数,查询用户活跃度

server+clinet

server

聊天室服务端的流程可以分为几个主要部分:初始化、监听连接、处理每个连接以及消息的处理和转发

1. 初始化

  • 程序启动时,init() 函数会执行。
  • init() 函数中删除了Redis中的旧数据,包括用户信息、在线状态和活跃度排名。

2. 监听TCP连接

  • main() 函数中,程序开始监听TCP端口9527。
  • 如果监听成功,程序将进入一个无限循环,等待新的客户端连接。
  • 当有新的客户端连接时,Accept() 方法接受该连接,并为每个新连接启动一个新的goroutine来处理它(handleConnection(conn))。

3. 处理每个连接

  • handleConnection() 函数是每个客户端连接的主要处理函数。
  • 它首先调用 userAdd(conn) 将新用户的地址添加到Redis中,并设置其在线状态。
  • 然后订阅两个Redis频道:“channel1”用于接收广播消息,另一个是基于客户端远程地址的个人频道。
  • 启动三个goroutine:
    • readClient(conn, quit) 用于从客户端读取消息。
    • broadCast(conn, sub) 用于从“channel1”频道接收并转发广播消息给客户端。
    • personalMessage(conn, sub1) 用于从个人频道接收并转发个人消息给客户端。
  • 最后,它等待 quit 通道接收到信号,表示客户端已断开连接,然后调用 userRemove(conn) 移除用户信息。

4. 消息处理

  • readClient() 函数负责从客户端读取原始消息,并通过 module.Decode(reader) 解码。
  • 解码后的消息被传递给 writeToRedis(msg, conn, quit) 函数,该函数将消息推送到Redis列表 messageQueue 中。
  • messageProcessing(conn, quit) 函数从 messageQueue 中弹出消息,并根据消息格式进行处理。
  • 根据消息的目标类型(个人或频道),分别调用 handleIndividualCommands()handleChannelCommands() 来处理具体的命令。

5. 命令处理

  • handleIndividualCommands()handleChannelCommands() 分别处理个人消息和频道消息。
  • 对于个人消息,可能的命令包括更改用户名、显示菜单等。
  • 对于频道消息,可能的命令包括获取在线用户列表、退出聊天室等。
  • 每个命令都有相应的逻辑来更新Redis数据库,并向相关的频道发布消息。

6. 用户管理

  • userAdd()userRemove() 函数用于在用户连接和断开时更新Redis中的用户信息。
  • changeName() 函数允许用户更改他们的昵称,并确保昵称不重复。
  • getOnlineUsers() 函数返回当前在线的用户数量。
  • topActiveUsers() 函数返回活跃度最高的前五名用户。

7. 活跃度更新

  • 每次用户发送消息时,都会调用 updateActivity(conn) 来增加该用户的活跃度分数。
package mainimport ("bufio""chatRoom/chatRoom4.0/module""context""fmt""github.com/redis/go-redis/v9""io""log""net""strconv""strings""time"
)var ctx = context.Background()var rdb = redis.NewClient(&redis.Options{Addr:     "127.0.0.1:6380",Password: "",DB:       2,
})func init() {err := rdb.Del(ctx, "usersName").Err()if err != nil {log.Fatalf("删除哈希键失败: %v", err)}err = rdb.Del(ctx, "usersOnline").Err()if err != nil {log.Fatalf("删除哈希键失败: %v", err)}err = rdb.Del(ctx, "activityRanking").Err()if err != nil {log.Fatalf("删除有序集合键失败: %v", err)}
}
func main() {fmt.Println("开始监听tcp连接端口")listener, err := net.Listen("tcp", "127.0.0.1:9527")if err != nil {log.Fatalf("tcp连接端口监听失败: %v", err)}defer listener.Close()fmt.Println("tcp连接端口连接成功")for {conn, err := listener.Accept()if err != nil {log.Printf("接受连接失败: %v", err)continue}go handleConnection(conn)}
}func handleConnection(conn net.Conn) {defer conn.Close()userAdd(conn)sub := rdb.Subscribe(ctx, "channel1")sub1 := rdb.Subscribe(ctx, conn.RemoteAddr().String())quit := make(chan bool)go readClient(conn, quit)go broadCast(conn, sub)go personalMessage(conn, sub1)<-quit // 等待客户端断开连接userRemove(conn)
}func userAdd(conn net.Conn) {err := rdb.HSet(ctx, "usersName", conn.RemoteAddr().String(), conn.RemoteAddr().String()).Err()if err != nil {log.Printf("用户信息添加失败: %v", err)} else {log.Printf("用户信息添加成功: %s", conn.RemoteAddr().String())}err = rdb.HSet(ctx, "usersOnline", conn.RemoteAddr().String(), "online").Err()if err != nil {log.Printf("用户在线信息添加失败: %v", err)} else {log.Printf("用户在线信息添加成功: %s", conn.RemoteAddr().String())}err = rdb.ZAdd(ctx, "activityRanking", redis.Z{0, conn.RemoteAddr().String()}).Err()if err != nil {log.Printf("用户活跃信息添加失败: %v", err)} else {log.Printf("用户活跃信息添加成功: %s", conn.RemoteAddr().String())}}func changeName(conn net.Conn, newName string) int {data, err := rdb.HGetAll(ctx, "usersName").Result()if err != nil {fmt.Println("", err)}for _, val := range data {if val == newName {return 2}}oldName, err := rdb.HSet(ctx, "usersName", conn.RemoteAddr().String(), newName).Result()if err != nil {log.Printf("名字修改失败: %v", err)return 0} else {log.Printf("名字修改成功 <旧名字> %s <新名字> %s", strconv.Itoa(int(oldName)), newName)return 1}}
func personalMessage(conn net.Conn, sub1 *redis.PubSub) {for {log.Println("开始接收" + conn.RemoteAddr().String() + "个人订阅消息")msg, err := sub1.ReceiveMessage(ctx)if err != nil {log.Printf("接收消息失败: %v", err)return}log.Printf("接收处理过后消息 %s: %s", msg.Channel, msg.Payload)king, err := module.Encode(msg.Payload + "\n")if err != nil {log.Printf("编码消息失败: %v", err)continue}_, err = conn.Write(king)if err != nil {log.Printf("发送消息失败: %v", err)return}log.Println("个人信息发送成功")}
}func broadCast(conn net.Conn, sub *redis.PubSub) {for {log.Println("开始接收channel1广播消息")msg, err := sub.ReceiveMessage(ctx)if err != nil {log.Printf("接收消息失败: %v", err)return}log.Printf("接收消息 %s: %s", msg.Channel, msg.Payload)king, err := module.Encode(msg.Payload + "\n")if err != nil {log.Printf("编码消息失败: %v", err)continue}_, err = conn.Write(king)if err != nil {log.Printf("发送消息失败: %v", err)return}}
}func readClient(conn net.Conn, quit chan bool) {reader := bufio.NewReader(conn)for {msg, err := module.Decode(reader)if err == io.EOF {select {case quit <- true:default:return}}if err != nil {log.Printf("解码消息失败: %v", err)select {case quit <- true:default:return}}if len(msg) == 0 {continue}log.Printf("收到client发来的数据: %s", msg)writeToRedis(msg, conn, quit)}
}func writeToRedis(msg string, conn net.Conn, quit chan bool) {log.Printf("接受到的信息转存redis: %s", msg)err := rdb.RPush(ctx, "messageQueue", msg).Err()if err != nil {log.Printf("信息转存redis失败: %v", err)} else {log.Printf("接受到的信息转存redis成功: %s", msg)}messageProcessing(conn, quit)
}func messageProcessing(conn net.Conn, quit chan bool) {log.Printf("开始读取redis队列消息")result, err := rdb.LPop(ctx, "messageQueue").Result()if err != nil {if err == redis.Nil {log.Println("消息队列已空")} else {log.Fatalf("redis队列信息弹出失败: %v", err)}return}log.Printf("redis队列弹出信息为: %s", result)time.Sleep(time.Second)parts := strings.SplitN(result, ":", 4)if len(parts) != 4 {log.Println("无效的消息格式")return}targetType, target, specialMark, message := parts[0], parts[1], parts[2], parts[3]switch targetType {case "individual":log.Println("individual")handleIndividualCommands(target, specialMark, message, conn)case "channel":log.Println("信息为channel类型")handleChannelCommands(target, specialMark, message, conn, quit)default:log.Printf("未知的目标类型: %s", targetType)}updateActivity(conn)
}func getUsersChannel(target string) (channelName string) {data, err := rdb.HGetAll(ctx, "usersName").Result()if err != nil {fmt.Println("", err)}for key, val := range data {if val == target {channelName = key}}return channelName
}
func getUsersName(conn net.Conn) (userName string) {data, err := rdb.HGetAll(ctx, "usersName").Result()if err != nil {fmt.Println("", err)}for key, val := range data {if key == conn.RemoteAddr().String() {userName = val}}return userName
}
func handleIndividualCommands(target, specialMark, message string, conn net.Conn) {log.Println("开始处理Individual信息")switch specialMark {case "changeNameFirst":isSuc := changeName(conn, message)if isSuc == 1 {rdb.Publish(ctx, conn.RemoteAddr().String(), "individual:"+getUsersName(conn)+":changeNameFirst1:"+message)rdb.Publish(ctx, "channel1", "channel:channel1:系统消息:用户 < "+getUsersName(conn)+" > 上线了!")} else if isSuc == 2 {rdb.Publish(ctx, conn.RemoteAddr().String(), "individual:"+getUsersName(conn)+":changeNameFirst0:"+"名字重复,请更换名字再次尝试")} else {rdb.Publish(ctx, conn.RemoteAddr().String(), "individual:"+getUsersName(conn)+":changeNameFirst2:"+"未知原因导致改名失败")}case "menu":menu := " \n * ./cd1 或 ./menu       功能菜单\n * ./cd2 或 ./changeName 更改昵称\n * ./cd3 或 ./online     在线用户数量查询\n*./cd4 或 ./ activity      查询活跃排行\n * ./cd5 或 ./quit      退出聊天室\n"rdb.Publish(ctx, getUsersChannel(target), "individual:"+getUsersName(conn)+":系统消息:"+menu)case "changeName":isSuc := changeName(conn, message)if isSuc == 1 {rdb.Publish(ctx, conn.RemoteAddr().String(), "individual:"+getUsersName(conn)+":changeName1:"+message)} else if isSuc == 2 {rdb.Publish(ctx, conn.RemoteAddr().String(), "individual:"+getUsersName(conn)+":changeNameFirst0:"+"名字重复,请更换名字再次尝试")} else {rdb.Publish(ctx, conn.RemoteAddr().String(), "individual:"+getUsersName(conn)+":changeNameFirst2:"+"未知原因导致改名失败")}case "":rdb.Publish(ctx, getUsersChannel(target), "individual:"+getUsersName(conn)+":系统消息:"+message)default:rdb.Publish(ctx, conn.RemoteAddr().String(), "信息格式错误")log.Printf("未知的个人命令: %s", specialMark)}
}func handleChannelCommands(target, specialMark, message string, conn net.Conn, quit chan bool) {log.Println("开始处理channel信息")switch specialMark {case "onlineList":onlineList, err := getOnlineUsers()if err != nil {log.Printf("获取在线列表失败: %v", err)} else {rdb.Publish(ctx, "channel1", "channel:channel1:"+getUsersName(conn)+":"+onlineList)}case "quit":log.Println("用户退出", getUsersName(conn))quit <- truecase "activity":topUsers := topActiveUsers()activityMsg := "< 系统消息 > 活跃度排名\n"data, err := rdb.HGetAll(ctx, "usersName").Result()if err != nil {log.Println(err)}for i, user := range topUsers {name := ""for field, val := range data {if user == field {name = vallog.Println("name:", name)}}activityMsg += fmt.Sprintf("        第%d名 : %s\n", i+1, name)}rdb.Publish(ctx, "channel1", "channel:channel1:系统消息:"+message+activityMsg)default:rdb.Publish(ctx, "channel1", "channel:channel1:"+getUsersName(conn)+":"+message)}
}func userRemove(conn net.Conn) {name := getUsersName(conn)rdb.HDel(ctx, "usersName", conn.RemoteAddr().String())rdb.HDel(ctx, "usersOnline", conn.RemoteAddr().String())rdb.ZRem(ctx, "activityRanking", conn.RemoteAddr().String())msg := "用户已下线:" + namerdb.Publish(ctx, "channel1", "channel:channel1:"+"系统消息:"+msg)log.Printf("用户已下线: %s", name)
}func getOnlineUsers() (string, error) {data, err := rdb.HGetAll(ctx, "usersOnline").Result()if err != nil {return "", err}count := 0for _, val := range data {if val == "online" {count++}}return "当前在线人数为" + strconv.Itoa(count) + "人", nil
}func topActiveUsers() []string {members, err := rdb.ZRevRangeWithScores(ctx, "activityRanking", 0, -1).Result()if err != nil {log.Printf("无法获取活跃用户排名: %v", err)return nil}var usernames []stringfor _, member := range members {if len(usernames) >= 5 {return usernames}usernames = append(usernames, member.Member.(string))}return usernames
}func updateActivity(conn net.Conn) {log.Printf("用户发言增加活跃度: %s", conn.RemoteAddr().String())rdb.ZIncrBy(ctx, "activityRanking", 1, conn.RemoteAddr().String())log.Printf("活跃度增加成功: %s", conn.RemoteAddr().String())
}

clinet

1. 初始化连接

  • main() 函数中,客户端尝试通过TCP协议连接到运行在本地(127.0.0.1)9527端口上的服务器。
  • 如果连接失败,程序将打印错误信息并退出。
  • 连接成功后,客户端会提示用户输入昵称,并将这个昵称通过特定格式的消息发送给服务器以设置用户的初始昵称。

2. 用户输入处理

  • 客户端使用一个无限循环来读取用户的命令或消息。
  • 对于每个用户输入,客户端会根据输入的内容构造不同的消息:
    • ./cd1./menu:显示功能菜单。
    • ./cd2./changeName:允许用户更改昵称。
    • ./cd3./online:查询在线用户数量。
    • ./cd4./activity:查询活跃度排名。
    • ./cd5./quit:退出聊天室。
    • 其他输入被视为普通消息,发送到频道 channel1

3. 消息编码和发送

  • 构造好的消息会被编码并通过TCP连接发送到服务器。
  • 如果编码或发送过程中出现错误,客户端会打印错误信息并退出。

4. 接收服务器消息

  • readMsg() 函数在一个单独的goroutine中运行,用于从服务器接收消息。
  • 当接收到消息时,readMsg() 会解码消息,并根据消息类型(个人或频道)以及特殊标记进行处理。
  • 例如,如果消息是关于昵称变更成功的确认,则更新本地存储的用户名 name 并打印相应的系统消息。
  • 如果消息是退出指令,则向 exit 通道发送信号,通知主函数关闭连接并退出。

5. 退出逻辑

  • 主函数中的无限循环监听 exit 通道。
  • exit 通道接收到信号时,主函数打印退出信息并结束程序。

6. 辅助函数

  • getUserInput(prompt string) 函数用于从标准输入读取用户输入,并返回去掉前后空白的字符串。
  • 根据不同的提示信息,该函数会在控制台上打印对应的提示。

流程图简述

  1. 启动:客户端启动并尝试连接服务器。
  2. 连接成功:如果连接成功,客户端提示用户输入昵称,并将昵称发送给服务器。
  3. 用户输入循环:客户端进入一个无限循环,等待用户输入命令或消息。
  4. 消息构造与发送:根据用户输入构造消息,编码后发送给服务器。
  5. 接收消息:另一个goroutine不断从服务器接收消息,并根据消息内容执行相应操作。
  6. 退出:当用户选择退出或者服务器断开连接时,客户端退出循环并关闭连接。
package mainimport ("bufio""chatRoom/chatRoom4.0/module""fmt""io""log""net""os""strings""time"
)var name stringfunc main() {conn, err := net.Dial("tcp", "127.0.0.1:9527")if err != nil {fmt.Println("服务器连接失败 err =", err)return}defer conn.Close()fmt.Println("服务器连接成功")getName := getUserInput("请输入你的昵称:")data, err := module.Encode("individual::changeNameFirst:" + getName)if err != nil {fmt.Println("encode msg failed, err:", err)return}_, err = conn.Write(data)if err != nil {fmt.Println("发送数据失败1 err =", err)}var exit = make(chan bool)defer close(exit)fmt.Println("--------------欢迎进入多人聊天室系统----------------")fmt.Println("       * ./cd1 或 ./menu       功能菜单")fmt.Println("       * ./cd2 或 ./changeName 更改昵称")fmt.Println("       * ./cd3 或 ./online     在线用户数量查询")fmt.Println("       * ./cd4 或 ./activity     在线用户数量查询")fmt.Println("       * ./cd5 或 ./quit       退出聊天室")fmt.Println("---------------指令字母不区分大小写-----------------")go readMsg(conn, exit)go func() {for {msg := getUserInput("")switch {case strings.EqualFold(msg, "./cd1") || strings.EqualFold(msg, "./menu"):msg = "individual:" + name + ":menu:"case strings.EqualFold(msg, "./cd2") || strings.EqualFold(msg, "./changeName"):newMsg := getUserInput("请输入新的昵称:")msg = "individual:" + name + ":changeName:" + newMsgcase strings.EqualFold(msg, "./cd3") || strings.EqualFold(msg, "./online"):msg = "channel:channel1:onlineList:"case strings.EqualFold(msg, "./cd4") || strings.EqualFold(msg, "./activity"):msg = "channel:channel1:activity:"case strings.EqualFold(msg, "./cd5") || strings.EqualFold(msg, "./quit"):msg = "channel:channel1:quit:"default:msg = "channel:channel1::" + msg}data, err = module.Encode(msg)if err != nil {fmt.Println("encode msg failed, err:", err)return}_, err = conn.Write(data)if err != nil {fmt.Println("发送数据失败2 err =", err)return}}}()for {select {case <-exit:fmt.Println("退出成功")return}}
}func getUserInput(prompt string) string {time.Sleep(time.Millisecond * 100)switch prompt {case "请输入你的昵称:":fmt.Print("请输入你的昵称:")case "请输入新的昵称:":fmt.Println("请输入新的昵称:")}reader := bufio.NewReader(os.Stdin)input, err := reader.ReadString('\n')if err != nil {fmt.Println("用户输入获取失败:err =", err)return "客户端信息读取错误"}return strings.TrimSpace(input)
}func readMsg(conn net.Conn, exit chan bool) {defer conn.Close()reader := bufio.NewReader(conn)for {msg, err := module.Decode(reader)if err == io.EOF {fmt.Println("服务器连接已断开 ")exit <- true}if err != nil {fmt.Println("服务器断开连接 2 err =", err)return}if msg == "" {continue}parts := strings.SplitN(msg, ":", 4)if len(parts) != 4 {log.Println("无效的消息格式")return}targetType, target, specialMark, message := parts[0], parts[1], parts[2], parts[3]message = strings.TrimRight(message, "\r\n")switch targetType {case "individual":switch specialMark {case "changeNameFirst1":fmt.Println("【 ", time.Now().Format("15:04"), " 】你当前昵称为:", target)name = targetcase "changeName1":fmt.Println("【 ", time.Now().Format("15:04"), " 】昵称为修改成功")fmt.Println("【 ", time.Now().Format("15:04"), " 】你当前昵称为:", target)name = targetcase "quit":fmt.Println("【 ", time.Now().Format("15:04"), " 】开始退出客户端...")exit <- truecase "系统消息":fmt.Println("【 ", time.Now().Format("15:04"), " 】< 系统消息 >"+message)default:fmt.Println("【 ", time.Now().Format("15:04"), " 】< 系统消息 > 未知错误")}case "channel":switch specialMark {case "系统消息":fmt.Println("【 ", time.Now().Format("15:04"), " 】< 系统消息 > "+message)default:fmt.Println("【 ", time.Now().Format("15:04"), " 】< "+specialMark+" >"+message)}}}
}

tcp防粘包

由于客户端和服务端基于tcp连接会出现粘包现象,因此需要对信息进行封包后再发送,同时接收端也要进行解包

package moduleimport ("bufio""bytes""encoding/binary"
)func Encode(message string) ([]byte, error) {// 读取消息的长度,转换成int32类型(占4个字节)var length = int32(len(message))var pkg = new(bytes.Buffer)// 写入消息头err := binary.Write(pkg, binary.LittleEndian, length)if err != nil {return nil, err}// 写入消息实体err = binary.Write(pkg, binary.LittleEndian, []byte(message))if err != nil {return nil, err}return pkg.Bytes(), nil
}// Decode 解码消息
func Decode(reader *bufio.Reader) (string, error) {// 读取消息的长度lengthByte, _ := reader.Peek(4) // 读取前4个字节的数据lengthBuff := bytes.NewBuffer(lengthByte)var length int32err := binary.Read(lengthBuff, binary.LittleEndian, &length)if err != nil {return "", err}// Buffered返回缓冲中现有的可读取的字节数。if int32(reader.Buffered()) < length+4 {return "", err}// 读取真正的消息数据pack := make([]byte, int(4+length))_, err = reader.Read(pack)if err != nil {return "", err}return string(pack[4:]), nil
}

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

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

相关文章

【优选算法】(第八篇)

目录 串联所有单词的⼦串&#xff08;hard&#xff09; 题目解析 讲解算法原理 编写代码 最⼩覆盖⼦串&#xff08;hard&#xff09; 题目解析 讲解算法原理 编写代码 串联所有单词的⼦串&#xff08;hard&#xff09; 题目解析 1.题目链接&#xff1a;. - 力扣&#…

Redis:list类型

Redis&#xff1a;list类型 list命令非阻塞LPUSHLRANGELPUSHXRPUSHRPUSHXLPOPRPOPLINDEXLINSERTLLENLREMLTRIMLSET 阻塞BLPOPBRPOP 内部编码ziplistlinkedlistquicklist 几乎每种语言都有顺序表、数组、链表这样的顺序结构&#xff0c;Redis也做出了相应的支持。 如图&#xff…

Stable Diffusion绘画 | AI 图片智能扩充,超越PS扩图的AI扩图功能(附安装包)

来到「文生图」页面&#xff0c;输入固定的起手式提示词。 第1步&#xff0c;开启 ControlNet&#xff0c;将需要扩充的图片加载进来&#xff1a; 控制类型选择「Inpaint」&#xff0c;预处理器选择「inpaint_onlylama」&#xff0c;缩放模式选择「缩放后填充空白」&#xff1…

基于SpringBoot+Vue的网约车管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

(Django)初步使用

前言 Django 是一个功能强大、架构良好、安全可靠的 Python Web 框架&#xff0c;适用于各种规模的项目开发。它的高效开发、数据库支持、安全性、良好的架构设计以及活跃的社区和丰富的文档&#xff0c;使得它成为众多开发者的首选框架。 目录 安装 应用场景 良好的架构设计…

Leetcode—763. 划分字母区间【中等】

2024每日刷题&#xff08;175&#xff09; Leetcode—763. 划分字母区间 C实现代码 class Solution { public:vector<int> partitionLabels(string s) {int rightmost[26];int l 0;int r 0;for(int i 0; i < s.length(); i) {rightmost[s[i] - a] i;}vector<…

[C语言]第十一节 函数递归一基础知识到高级技巧的全景探索

目录 11.1. 递归是什么&#xff1f; 11.1.1 递归的思想&#xff1a; 11.2 递归的限制条件 举例1&#xff1a;求n的阶乘 画图推演 举例2&#xff1a;顺序打印⼀个整数的每⼀位 画图推演 11.3. 递归与迭代 举例3&#xff1a;求第n个斐波那契数 11.1. 递归是什么&#xff…

一款基于 Java 的可视化 HTTP API 接口快速开发框架,干掉 CRUD,效率爆炸(带私活源码)

平常我们经常需要编写 API&#xff0c;但其实常常只是一些简单的增删改查&#xff0c;写这些代码非常枯燥无趣。 今天给大家带来的是一款基于 Java 的可视化 HTTP API 接口快速开发框架&#xff0c;通过 UI 界面编写接口&#xff0c;无需定义 Controller、Service、Dao 等 Jav…

【Java数据结构】栈 (Stack)

【本节目标】 1. 栈的概念及使用 2. 相关 OJ 题 一、概念 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last…

【2024】前端学习笔记11-网页布局-弹性布局flex

学习笔记 网页布局弹性布局&#xff1a;flex案例&#xff1a;flex布局案例 网页布局 在页面布局中&#xff0c;display属性用于设置一个元素的显示方式。它可以指定元素是作为块级元素、内联元素还是充当表格元素显示。 display的常见属性值&#xff1a; block&#xff1a;将…

k8s 中微服务之 MetailLB 搭配 ingress-nginx 实现七层负载

目录 1 MetailLB 搭建 1.1 MetalLB 的作用和原理 1.2 MetalLB功能 1.3 部署 MetalLB 1.3.1 创建deployment控制器和创建一个服务 1.3.2 下载MealLB清单文件 1.3.3 使用 docker 对镜像进行拉取 1.3.4 将镜像上传至私人仓库 1.3.5 将官方仓库地址修改为本地私人地址 1.3.6 运行清…

ensp回顾--聚合链路技术简介与详细案例(构建基于交换机到交换机的聚合链路)

文章目录 什么是聚合链路&#xff1f;聚合链路的工作原理聚合链路的优势使用场景 案例ensp版本图例pc的ip地址具体步骤连通性测试 在现代网络中&#xff0c;聚合链路&#xff08;Link Aggregation&#xff09;是一种常见的技术&#xff0c;用于提高网络连接的带宽和可靠性。本文…

RNN经典案例——构建人名分类器

RNN经典案例——人名分类器 一、数据处理1.1 去掉语言中的重音标记1.2 读取数据1.3 构建人名类别与人名对应关系字典1.4 将人名转换为对应的onehot张量 二、构建RNN模型2.1 构建传统RNN模型2.2 构建LSTM模型2.3 构建GRU模型 三、构建训练函数并进行训练3.1 从输出结果中获得指定…

【可答疑】基于51单片机的智能台灯(含仿真、代码、报告、演示视频等)

✨哈喽大家好&#xff0c;这里是每天一杯冰美式oh&#xff0c;985电子本硕&#xff0c;大厂嵌入式在职0.3年&#xff0c;业余时间做做单片机小项目&#xff0c;有需要也可以提供就业指导&#xff08;免费&#xff09;~ &#x1f431;‍&#x1f409;这是51单片机毕业设计100篇…

数据分析-28-交互式数据分析EDA工具和低代码数据科学工具

文章目录 1 数据分析的七步指南1.1 第一步:问题定义和数据采集1.2 第二步:数据清洗和预处理1.3 第三步:数据探索和分析1.4 第四步:模型建立和分析1.5 第五步:数据可视化1.6 第六步:结果解释和报告1.7 第七步:部署和维护1.8 基础的数据分析库1.9 低代码数据科学工具2 EDA…

STM32 通用定时器

一、概述 STM32内部集成了多个定时/计数器&#xff0c;根据型号不同&#xff0c;STM32系列芯片最多包含8个定时/计数器。其中&#xff0c;TIM6、TIM7为基本定时器&#xff0c;TIM2~TIM5为通用定时器&#xff0c;TIM1、TIM8为高级控制定时器。 1.定时器的类型 基本定时器通用定…

实战案例:结合大模型与爬虫技术实现12306智能查票系统

大语言模型&#xff0c;例如 GPT-4&#xff0c;拥有强大的知识储备和语言理解能力&#xff0c;能够进行流畅的对话、创作精彩的故事&#xff0c;甚至编写代码。然而&#xff0c;它们也面临着一些难以克服的困境&#xff0c;就像一个空有知识却无法行动的巨人 信息滞后&#xf…

Linux 之 安装软件、GCC编译器、Linux 操作系统基础

安装软件、GCC编译器、Linux 操作系统基础 学习任务&#xff1a; 安装 Vmware虚拟机、掌握Ubuntu 系统的使用认识 Ubuntu 操作系统的终端和 Shell掌握软件安装、文件系统、掌握磁盘管理与解压缩掌握 VIM 编辑器、Makefile 基本语法熟悉 Linux 常见指令操作 安装好开发软件&…

[Go语言快速上手]初识Go语言

目录 一、什么是Go语言 二、第一段Go程序 1、Go语言结构 注意 2、Go基础语法 关键字 运算符优先级 三、Go语言数据类型 示例 小结 一、什么是Go语言 Go语言&#xff0c;通常被称为Golang&#xff0c;是一种静态类型、编译型的计算机编程语言。它由Google的Robert Gr…

用HTML5+CSS+JavaScript庆祝国庆

用HTML5CSSJavaScript庆祝国庆 中华人民共和国的国庆日是每年的10月1日。 1949年10月1日&#xff0c;中华人民共和国中央人民政府成立&#xff0c;在首都北京天安门广场举行了开国大典&#xff0c;中央人民政府主席毛泽东庄严宣告中华人民共和国成立&#xff0c;并亲手升起了…