我的網頁聊天室設計

一、需求分析


1.用戶管理模塊


注冊功能

實現一個注冊頁面。注冊頁面上包含了一個輸入框,輸入用戶名和密碼.
注冊成功后可以跳轉到登錄頁面.


登錄功能

實現一個登錄頁面。登錄頁面上包含一個輸入框。輸入用戶名和密碼.
登錄成功后可以跳轉到主頁面.


2.主界面

用戶信息

左上角顯示用戶的昵稱.

會話列表

顯示當前用戶的會話列表.
選擇某個表項,就會在右側消息區顯示出歷史消息.

好友列表

顯示當前用戶的好友列表.
點擊好友列表中的表項,就會跳轉到會話列表,同時給會話列表新增一個表項.
并且提供了一個 "新增好友" 的按鈕,點擊后跳轉到新增好友頁面.


消息區域

在右側顯示消息區域.
最上方顯示會話的名稱.
中間顯示消息內容.
下方顯示一個輸入框和發送按鈕.
當用戶點擊發送按鈕,則會把輸入框中的消息通過網絡發送出去.


消息傳輸功能

選中好友,則會在會話列表中生成一個會話.
點擊選中會話,會在右側區域加載出歷史消息列表.
接下來在輸入框中輸入消息,點擊發送按鈕即可發送消息.
如果對方在線,就會即刻提示實時消息.
如果對方不在線,后續上線后就會看到歷史消息.

二、數據庫設計

我建立了5張表

user表

用來登錄操作,身份識別。

1.userId

2.username

3.password

CREATE TABLE `user` (`userId` int(11) NOT NULL AUTO_INCREMENT,`username` varchar(20) DEFAULT NULL,`password` varchar(20) DEFAULT NULL,PRIMARY KEY (`userId`),UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

friend表

用來關聯用戶的好友

1.userId

2.friendId

可以通過userId,查friendId。得到他的好友都是誰

CREATE TABLE `friend` (`userId` int(11) DEFAULT NULL,`friendId` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

message表

用來記錄消息,

1.消息id

2.消息從哪個用戶id來

3.消息屬于哪個會話id

4.消息內容是什么

5.消息發送的時間

CREATE TABLE `message` (`messageId` int(11) NOT NULL AUTO_INCREMENT,`fromId` int(11) DEFAULT NULL,`sessionId` int(11) DEFAULT NULL,`content` varchar(2048) DEFAULT NULL,`postTime` datetime DEFAULT NULL,PRIMARY KEY (`messageId`)
) ENGINE=InnoDB AUTO_INCREMENT=53 DEFAULT CHARSET=utf8;

message_session表

這里記錄了所創建的所有會話

1.會話id

2.會話創建時間?

CREATE TABLE `message_session` (`sessionId` int(11) NOT NULL AUTO_INCREMENT,`lastTime` datetime DEFAULT NULL,PRIMARY KEY (`sessionId`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

message_session_user表

這里記錄會話中都有哪些userId,也就是說這個會話都有誰在聊天

sessionId

userId?

CREATE TABLE `message_session_user` (`sessionId` int(11) DEFAULT NULL,`userId` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

?三、注冊功能的實現

1.寫mapper層

接口

    Integer insert(User user);

xml

useGeneratedKeys="true"

keyProperty="userId"

useGeneratedKeys是MyBatis提供的一個特性,

當插入數據時,數據庫會返回一個id值, 這個特性是可選的,但是建議使用,因為使用這個特性,可以獲得插入數據的id值。

keyProperty="userId"是MyBatis提供的一個屬性,用來指定id值的屬性名。 useGeneratedKeys="true"和keyProperty="userId"一起使用, 可以獲得插入數據的id值。

雖然數據庫已經是自增 userId 了

但是并沒有給實體類中的 userId 賦值,如果不這樣操作,

那么實體類中的 userId的值就為null

<!--    useGeneratedKeys="true" keyProperty="userId"
用來獲得id的值,并放入userId這個屬性用--><insert id="insert" useGeneratedKeys="true" keyProperty="userId">insert into user values (null,#{username},#{password})</insert>

?2.寫Controller層

    @RequestMapping("/register")public Object register(String username, String password){User user = null;try {user = new User();user.setUsername(username);user.setPassword(password);int ret = userMapper.insert(user);System.out.println("注冊" + ret);user.setPassword("");}catch (DuplicateKeyException e){System.out.println("注冊失敗!username=" + username);user = new User();}return user;}

try/catch主要解決由于數據庫中

username 添加了唯一約束的約束。確保username是唯一的。

如果這里添加了重復了username,那么就會出現異常。會報服務器錯誤,比如

接口會返回 500 Internal Server Error(HTTP 狀態碼)。
前端收到的響應是一個包含堆棧跟蹤的錯誤頁面(Spring 默認錯誤頁)或 JSON 錯誤信息。
用戶看到的提示可能是 “服務器內部錯誤”,而非友好的 “用戶名已存在”。

?

四、登錄功能的實現?

1.寫mapper層

接口

    //登錄,根據用戶名查詢用戶信息User selectByName(String username);

xml

    <select id="selectByName" resultType="com.qyy.www_chatroom.model.User">select * from user where username = #{username}</select>

resultType="com.qyy.www_chatroom.model.User?

告訴 MyBatis 將查詢結果映射到User類的對象中。MyBatis 會自動將列名(如username)映射到User類的屬性(如getUsername()方法對應的屬性)。?

2.寫Controller層

HttpSession session = request.getSession(true); // 創建或獲取現有會話

如果是false,僅返回現有會話,若無則返回?null

    @RequestMapping("/login")public Object login(String username, String password, HttpServletRequest request){//1.先去數據庫查username能不能找到對象。//如果能找到,看密碼是否匹配,如果匹配登錄成功,創建會話User user = userMapper.selectByName(username);if(user == null || !user.getPassword().equals(password)){System.out.println("登錄失敗!,用戶名或者密碼錯誤"+user);return  new User();}//2.如果匹配成功,則登錄成功,創建會話,我們需要HttpServletRequest來創建會話HttpSession session = request.getSession(true);session.setAttribute("user",user);//避免返回密碼user.setPassword("");return user;}

五、顯示用戶信息

由于當前用戶已經登錄,用戶信息存儲在session中,因此只需要調用HttpServletRequest

HttpServletRequest request

通過HttpSession session = request.getSession(false);得到當前現有的會話。

這里存儲的東西有點像

1.寫Controller層

    @RequestMapping("userInfo")public Object getUserInfo(HttpServletRequest request){//1.先從請求中獲取會話HttpSession session = request.getSession(false);if(session == null){//用戶沒登錄,返回空對象System.out.println("獲取不到session對象!");return new User();}//2.從會話中獲取到之前保存的用戶對象User user = (User)session.getAttribute("user");if(user == null){System.out.println("獲取不到 User 對象!");return new User();}user.setPassword("");return user;}

PS:HttpSession?的本質

  • 接口定義HttpSession?是 Servlet 規范中的接口(javax.servlet.http.HttpSession),定義了會話管理的方法(如?setAttribute()getAttribute())。
  • 底層實現:由 Servlet 容器(如 Tomcat)提供具體實現,通常使用?Map 結構?存儲屬性。例如:
    // 偽代碼示例(實際實現因容器而異)
    public class StandardSession implements HttpSession {private final Map<String, Object> attributes = new ConcurrentHashMap<>();@Overridepublic void setAttribute(String name, Object value) {attributes.put(name, value);}@Overridepublic Object getAttribute(String name) {return attributes.get(name);}
    }
    

1.?為什么使用 Map 結構?

  • 鍵值對存儲setAttribute(key, value)?和?getAttribute(key)?的設計天然適合 Map。
  • 線程安全:生產環境中,容器通常使用線程安全的 Map(如?ConcurrentHashMap),以處理多線程并發訪問。
  • 靈活性:Map 允許存儲任意類型的對象(Object),符合?HttpSession?的設計。

2.?不同容器的實現差異

Servlet 容器存儲方式
Tomcat使用?ConcurrentHashMap?存儲在內存中,支持持久化到文件或數據庫。
Jetty類似 Tomcat,支持分布式會話(如 Redis 集群)。
分布式環境會話數據可能存儲在 Redis、Memcached 等外部緩存中,通過序列化后傳輸。

3.?代碼驗證示例

通過反射查看 Tomcat 的 Session 實現(僅作原理演示,不建議在生產環境使用):

import javax.servlet.http.HttpSession;
import java.lang.reflect.Field;
import java.util.Map;public class SessionReflectionDemo {public static void main(String[] args) {// 假設這是從 request 中獲取的 SessionHttpSession session = getSessionFromSomewhere();try {// 獲取 StandardSession 類的私有屬性Field attributesField = session.getClass().getDeclaredField("attributes");attributesField.setAccessible(true);// 獲取內部的 MapMap<String, Object> attributesMap = (Map<String, Object>) attributesField.get(session);System.out.println("底層存儲類型: " + attributesMap.getClass().getName());} catch (Exception e) {e.printStackTrace();}}
}

  • 輸出結果(在 Tomcat 中):
    底層存儲類型: java.util.concurrent.ConcurrentHashMap
    

4.?分布式會話的特殊性

在分布式系統中,HttpSession?的底層可能不是直接的 Map:

  • 場景:多臺服務器共享會話數據(如微服務架構)。
  • 實現
    1. 粘性會話:請求始終路由到同一服務器,底層仍是本地 Map。
    2. 會話復制:服務器間同步會話數據(性能開銷大)。
    3. 外部存儲:會話數據存儲在 Redis/Memcached 中,通過序列化 / 反序列化傳輸,邏輯上類似 Map,但物理上是分布式緩存。

總結

  • 核心結論HttpSession?的底層實現在?單體應用?中通常是?Map 結構(如?ConcurrentHashMap),而在?分布式環境?中可能是外部緩存(如 Redis),但對外提供的 API(setAttribute/getAttribute)仍保持 Map 的語義。
  • 開發者視角:無需關心具體實現,只需使用?HttpSession?接口提供的方法即可。

六、會話列表顯示及新增會話

1.寫mapper層

查該用戶存在于哪些會話?

    //根據userId獲取到該用戶都在哪些會話存在。返回結果是一組sessionIdList<Integer> getSessionIdByUserId(int userId);
    <select id="getSessionIdByUserId" resultType="java.lang.Integer">
-- 子查詢select sessionId from message_sessionwhere sessionId in(select sessionId from message_session_user where userId = #{userId})order by lastTime desc</select>

?

    <select id="getFriendsBySessionId" resultType="com.qyy.www_chatroom.model.Friend">select userId as friendId,username as friendName from userwhere userId in(select userId from message_session_user where sessionId = #{sessionId} and userId != #{selfUserId})</select>

查詢最后一條消息

    //獲取指定對話的最后一條消息@Select("select content from message where sessionId = #{sessionId} order by  postTime desc limit 1")String getLastMessageBySessionId(int sessionId);

?

?根據sessionId 來查詢這個會話都包含哪些用戶(除去自己)

List<Friend> getFriendsBySessionId(@Param("sessionId") int sessionId,@Param("selfUserId") int selfUserId);
    <select id="getFriendsBySessionId" resultType="com.qyy.www_chatroom.model.Friend">select userId as friendId,username as friendName from userwhere userId in(select userId from message_session_user where sessionId = #{sessionId} and userId != #{selfUserId})</select>

?

?

新增一個會話記錄,返回會話的id?

    //3.新增一個會話記錄。返回會話的id@Options(useGeneratedKeys = true,keyProperty = "sessionId")@Insert("insert into message_session(sessionId, lastTime) values (null,now())")int addMessageSession(MessageSession messageSession);

?在消息會話用戶表中插入用戶,用來關聯會話與用戶的關系

    @Insert("insert into message_session_user(sessionId, userId) values (#{sessionId},#{userId})")int addMessageSessionUser(MessageSessionUser messageSessionuser);

?

2.寫Controller層

會話列表展示

    @RequestMapping("/sessionList")public Object getMessageList(HttpServletRequest request){List<MessageSession> messageSessionList = new ArrayList<>();//1.獲取當前用戶userId,從Spring的Session中獲取HttpSession session = request.getSession(false);if(session == null){System.out.println("getMessageList Session為空!");return messageSessionList;}User user = (User)session.getAttribute("user");if(user == null){System.out.println("getMessageList : user 為空");}//2.根據userId查詢數據庫,查出來會話有哪些。List<Integer> sessionIdList = messageSessionMapper.getSessionIdByUserId(user.getUserId());System.out.println("獲取到的sessionIdList:"+sessionIdList);for(int sessionId : sessionIdList){MessageSession messageSession = new MessageSession();messageSession.setSessionId(sessionId);//3.遍歷會話Id,查詢出每個會話里涉及到的好友有誰List<Friend> friends = messageSessionMapper.getFriendsBySessionId(sessionId, user.getUserId());messageSession.setFriends(friends);//4.遍歷會話 iD,查詢每個會話最后一條消息String lastMessage = messageMapper.getLastMessageBySessionId(sessionId);if(lastMessage == null){messageSession.setLastMessage("");}else {messageSession.setLastMessage(lastMessage);}messageSessionList.add(messageSession);}//最終目標構造出一個MessageSession對象數組。return  messageSessionList;}

新增會話,并關聯兩個用戶
@Transactional
?
注解的作用是確保三個數據庫操作(插入會話、插入兩個用戶關聯記錄)作為一個不可分割的原子操作?

    @Transactional@RequestMapping("/session")public Object addMessageSession(int toUserId, @SessionAttribute("user")User user){HashMap<String,Integer> resp = new HashMap<>();//進行數據庫插入操作//1.先給message—_Session表里插入記錄,使用這個參數的目的主要是為了獲取到會話的主鍵SessionId//里面的friends和lastMessage屬性這里用不上MessageSession messageSession = new MessageSession();messageSessionMapper.addMessageSession(messageSession);//2.給message_session_user表里插入記錄MessageSessionUser messageSessionUser = new MessageSessionUser();messageSessionUser.setSessionId((messageSession.getSessionId()));messageSessionUser.setUserId(user.getUserId());messageSessionMapper.addMessageSessionUser(messageSessionUser);//3.給message_session_user表中插入記錄MessageSessionUser messageSessionUser2 = new MessageSessionUser();messageSessionUser2.setSessionId(messageSession.getSessionId());messageSessionUser2.setUserId(toUserId);messageSessionMapper.addMessageSessionUser(messageSessionUser2);System.out.println("[addMessageSession] 新增會話成功!sessionId =" + messageSession.getSessionId()+"userId" +user.getUserId()+"userId2" + toUserId);resp.put("sessionId", messageSession.getSessionId());return resp;}

?

?

七、好友列表顯示

1.寫mapper層

@Mapper
public interface FriendMapper {//要查看是哪個用戶的好友List<Friend> selectFriendList(int userId);
}

xml

    <select id="selectFriendList" resultType="com.qyy.www_chatroom.model.Friend">select userId as friendId, username as friendName from userwhere userId in(select friendId from friend where userId = #{userId})</select>

sql

select userId as friendId, username as friendName from user
where userId in
(select friendId from friend where userId = #{userId})

例如小祁的好友列表如下?

?

2.寫Controller層

    @RequestMapping("friendList")public Object getFriendList(HttpServletRequest request){HttpSession session = request.getSession();if(session == null){System.out.println("getFriendList,session不存在");return new ArrayList<Friend>();}User user = (User)session.getAttribute("user");if(user == null){System.out.println("getFriendList,user不存在");return new ArrayList<Friend>();}List<Friend> friendList = friendMapper.selectFriendList(user.getUserId());return friendList;}

由于這里用戶已經登錄

這里還是根據從?HttpServletRequest?

中獲取Session,再從Session得到當前用戶 id。

再根據當前用戶id 來查好友列表

八、消息區域文本顯示?

1.寫mapper層

獲取歷史消息

    //獲取歷史消息//有的會話,歷史消息特別多。//此處做一個限制,默認只取最近100條消息//進行笛卡爾積。username = fromId。根據sessionId匹配//直接按發送時間升序,讓會話中最新消息在最下面,并顯示100條數據@Select("select messageId,fromId,username as fromName,sessionId,content" +" from user,message " +"where userId = fromId and sessionId = #{sessionId} " +"order by postTime asc limit 100")List<Message> getMessageBySessionId(int sessionId);

2.寫Controller層

歷史消息查詢

    @RequestMapping("/message")public Object getMessage(int sessionId){List<Message> messages = messageMapper.getMessageBySessionId(sessionId);System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++"+messages);return messages;}

九、消息傳輸功能的實現

WebSocket實現消息傳輸功能的核心流程

整個過程是實時、雙向的,無需客戶端頻繁發送請求,服務器可以主動推送消息給客戶端。

?

1.WebSocket配置類WebSocketConfig類實現WebSocketConfigurer 類(注冊路徑,復制用戶信息)

@Configuration
@EnableWebSocket //啟動websocket
public class WebSocketConfig implements WebSocketConfigurer {@Autowiredprivate WebSocketAPI webSocketAPI;@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {//通過這個方法。把剛才創建好的Handler類注冊到具體的路徑上registry.addHandler(webSocketAPI,"/WebSocketMessage")//通過這個攔截器就可以把用戶給HttpSession中添加的Attribute在WebSocketSession也被加入一份.addInterceptors(new HttpSessionHandshakeInterceptor());}
}

2.管理用戶在線狀態OnlineUserManager類

package com.qyy.www_chatroom.component;import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketSession;import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;//通過這個類來記錄當前用戶在線狀態,維護了()
//此處的哈希表要考慮線程安全問題,因此使用ConcurrentHashMap是很有必要的。
//此處是我們自己寫了一個哈希表進行手動管理。我們也可以借助redis中間件什么的進行處理。方便起見。我們這樣處理
@Component
public class OnlineUserManager {private ConcurrentHashMap<Integer, WebSocketSession> sessions = new ConcurrentHashMap<>();//1.用戶上線,給哈希表插入鍵值對//若兩個不同客戶端,使用同一個賬號登錄,也就是多開。//我們這里設置如果前一個客戶端登錄,就不允許后面客戶端再登錄了(代碼簡單)//要避免多開,因為多開需要寫更復雜的邏輯來處理。如果多個客戶端登錄,如果有人給你發消息。//我們需要給多個客戶端同時發到消息。因此邏輯上會變得更復雜。public void online(int userId,WebSocketSession session){if(sessions.get(userId) != null){//說明此時用戶已經在線了,那么我們就登錄失敗,不記錄映射關系.后續就收不到任何消息//因為我們通過映射關系實現消息轉發System.out.println("["+ userId + "] 已經被登陸了!");return;//這個客戶端斷開鏈接的時候,會觸發offline操作。進行offline的時候//不能就把userId:1 WebSocketSession:session1這個信息給刪了//刪除的時候,要看一下是否是刪除自己的會話。}sessions.put(userId,session);System.out.println("["+ userId + "] 上線!");}//2.用戶下線,針對這個哈希表進行刪除元素public void offline(int userId,WebSocketSession session){WebSocketSession exitSession = sessions.get(userId);//為了防止多開,刪除鍵值對的時候,要看一下是否是刪除自己的會話session。//不能把別人的刪了// 比如:多開后手機 B 因為網絡問題,突然觸發了一個錯誤的 offline(user1, sessionB) 方法(注意:sessionB 是手機 B 的會話,并未被服務器記錄)//如果沒有判斷 exitSession == session,直接執行 sessions.remove(user1)//結果:手機 A 的 sessionA 被錯誤刪除,user1 變成離線,但手機 A 其實還在線 ?if(exitSession == session){//如果這倆Session是同一個,才真正進行下線操作,否則就啥也不干sessions.remove(userId);System.out.println("["+ userId + "] 下線!");}}//3.根據userId獲取到WebSocketSessionpublic WebSocketSession getSession(int userId){return sessions.get(userId);}}

?3.建立連接后調用afterConnectionEstablished類(來更新用戶在線狀態)

    @Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {//這個方法會在websocket連接建立后被自動調用System.out.println("【WebSocket】 連接成功");//正因為前面加上了攔截器,就可以讓每一個往HttpSession中加入的Attribute//在WebSocketSession中也被加入一份User user = (User) session.getAttributes().get("user");if(user == null){return;}System.out.println("獲取到的userId-----------------" +user.getUserId());//獲取到userId之后,就把鍵值對存起來onlineUserManager.online((user.getUserId()),session);}

4.處理前端發來的消息handleTextMessage類

    @Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {//這個方法是在websocket收到消息后,被自動調用System.out.println("【WebSocket】 收到消息!"+message.toString());//session會話里面記錄了通信雙方是誰。Session中就持有了websocket的通信連接User user = (User) session.getAttributes().get("user");if(user == null){System.out.println("[WebsocketAPI] user == null 未登錄用戶,無法進行消息轉發");return;}//2.針對請求進行解析,把json格式的字符串,轉成一個java中的對象//message.getPayload()得到荷載信息這是json格式,我們把他轉成java對象MessageRequest req = objectMapper.readValue(message.getPayload(), MessageRequest.class);if(req.getType().equals("message")){//就進行轉發transferMessage(user,req);}else {System.out.println("[WebsocketAPI] req.type 有誤" + message.getPayload());}//后續主要實現這個方法//處理消息接收,轉發,以及保存消息記錄}

5.轉發消息類transferMessage類

    //通過這個方法來完成實際的消息轉發操作。//1.先構造一個待轉發的響應對象,MessageResponse//2.根據請求的SessionId,獲取這個MessageSession的里面都有哪些用戶//通過查詢數據庫就知道了//3.循環遍歷上述這個列表,給列表中的每一個用戶都發一份響應消息//知道了每個用戶的userId,進一步查詢剛才準備好的OnlineManager,就知道了對應的WebSocketSession//注:除了給好友發,我們還要給自己發。//一個會話可能有很多人,群聊前端寫起來比較麻煩,不過我們后端API都支持群聊//4.轉發的消息要記錄到數據庫中,這樣后續用戶如果下線之后,重新上線,還能通過歷史消息拿到之前的消息private void transferMessage(User fromUser, MessageRequest req) throws IOException {//考慮一下,如果發消息時候,對方不在線,該怎么處理。給李四發消息,如果不在線。第二步能獲取到李四,但是第三步WebSocketSession//找不到。不過我們存到數據庫中,如果哪天上線了,就能通過歷史消息看到張三發來的消息// 如果在線,就要立即收到消息。//1.先構造一個待轉發的響應對象,MessageResponseMessageResponse response = new MessageResponse();response.setType("message");//不設置也行,因為已經就地初始化了response.setFromId(fromUser.getUserId());response.setFromName(fromUser.getUsername());response.setSessionId(req.getSessionId());response.setContent(req.getContent());//還需要把java對象轉jsonString responseJson = objectMapper.writeValueAsString(response);System.out.println("transferMessage respJson:" +responseJson);//2.根據請求的SessionId,獲取這個MessageSession的里面都有哪些用戶//通過查詢數據庫就知道了List<Friend> friends = messageSessionMapper.getFriendsBySessionId(req.getSessionId(), fromUser.getUserId());//注:數據庫操作會把自身用戶排除掉。而發消息要給自己這邊也發一次,因此把當前用戶也要添加進去Friend myself = new Friend();myself.setFriendId(fromUser.getUserId());myself.setFriendName(fromUser.getUsername());friends.add(myself);//3.循環遍歷上述這個列表,給列表中的每一個用戶都發一份響應消息//知道了每個用戶的userId,進一步查詢剛才準備好的OnlineManager,就知道了對應的WebSocketSession//注:除了給好友發,我們還要給自己發。for(Friend friend : friends){WebSocketSession webSocketsession = onlineUserManager.getSession(friend.getFriendId());if(webSocketsession == null){//如果用戶不在線,就不發送continue;}webSocketsession.sendMessage(new TextMessage(responseJson));}//4.轉發的消息,還需要放到數據庫中,后續用戶如果下線,重新上線還可以通過歷史消息方式拿到之前的消息//需要往message表中寫入記錄Message message = new Message();message.setFromId(fromUser.getUserId());message.setSessionId(req.getSessionId());message.setContent(req.getContent());//自增主鍵和時間屬性都可以讓SQL、在數據庫中生成messageMapper.add(message);}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/89980.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/89980.shtml
英文地址,請注明出處:http://en.pswp.cn/web/89980.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

數據結構自學Days10 -- 二叉樹的常用實現

? 一、為什么要學習二叉樹&#xff1f; 1. &#x1f4e6; 組織數據的高效方式 二叉樹可以快速插入、刪除、查找數據&#xff0c;尤其在平衡時&#xff0c;時間復雜度為 $O(\log n)$。 適合表示分層結構&#xff08;如組織結構、文件系統、語法樹&#xff09;。 2. &#x…

Java注解家族--`@ResponseBody`

ResponseBody ResponseBody是 Spring 框架中的一個注解&#xff0c;在基于 Spring 的 Web 開發中扮演著重要角色&#xff0c;以下是對它的詳細總結&#xff1a; 1.定義與基本功能 定義&#xff1a;ResponseBody注解用于將 Controller 方法的返回值&#xff0c;通過適當的 HttpM…

react-window 大數據列表和表格數據渲染組件之虛擬滾動

簡介 React Window 是一個高效的 React 組件庫&#xff0c;專為渲染大數據列表和表格數據而設計。它通過”虛擬化”技術&#xff08;也稱為”窗口化”或”列表虛擬化”&#xff09;解決了在 React 應用中渲染大量數據時的性能問題。與傳統方法不同&#xff0c;React Window 只…

Eltable tree形式,序號列實現左對齊,并且每下一層都跟上一層的錯位距離拉大

要的是如圖所示效果序號加個class-name寫樣式然后給eltable加indent屬性就可以了&#xff0c;我設置的25

FOC算法中SIMULINK一些常用模塊(2)-Permanent Magnet Synchronous Machine模塊

一&#xff0c;介紹這三個模塊一起介紹了&#xff0c;由左到右&#xff0c;分別是電源模塊&#xff0c;驅動模塊和電機模塊。主要介紹一下電機模塊二&#xff0c;DC Voltage SourceDC Voltage Source 模塊是用于表示直流電壓源的基本組件&#xff0c;可以提供恒流直壓&#xff…

RPG62.制作敵人攻擊波數二:攻擊ui

1。經典創建userwidget&#xff0c;使用xmbtextblock&#xff0c;結構如下。然后設置動畫與音頻&#xff0c;上下的參數是一樣的&#xff0c;轉到圖表打開BP_SurvialGameMode2.再創建一個widget&#xff0c;結構如下新添的動畫打開XMBGameModeBase&#xff0c;創建構造函數AXMB…

DL00691-基于深度學習的軸承表面缺陷目標檢測含源碼python

DL00691-基于深度學習的軸承表面缺陷目標檢測含源碼python

Word 中為什么我的圖片一拖就亂跑,怎么精確定位?

核心原因&#xff1a;文字環繞方式 (Text Wrapping) 問題的根源在于圖片的**“文字環繞”**設置。 默認狀態&#xff1a;“嵌入型” (In Line with Text) 當您插入一張圖片時&#xff0c;Word默認會把它當作一個巨大的文字字符來處理。這就是為什么您拖動它時&#xff0c;它會像…

Linux物理地址空間入門:從硬件到內核內存的基石

目錄 一、物理地址空間是什么&#xff1f; 二、物理地址空間的構成&#xff1a;不僅僅是內存 三、Linux內核如何管理物理地址空間 &#xff08;1&#xff09;物理內存的碎片化問題 &#xff08;2&#xff09;物理地址的分區管理 &#xff08;3&#xff09;物理地址與內核…

【2025最新版】PDFelement全能PDF編輯器

工具https://pan.quark.cn/s/a56d17fd05dd強大全能的PDF編輯神器PDFelementPro 全能PDF工具套裝 PDF閱讀器 PDF創建器 PDF編輯器 PDF注釋器 PDF轉換器 OCR識別工具 表單填寫和創建 數據提取 批量處理 更多詳情萬興PDF專業版特性。格式轉換&#xff1a;PDFelement輕松…

基于單片機汽車駕駛防瞌睡防疲勞報警器自動熄火設計

&#xff08;一&#xff09;系統功能設計 51單片機汽車駕駛防疲勞防瞌睡報警器自動熄火15 本系統由STC89C52單片機、蜂鳴器、ADXL345重力加速度傳感器、繼電器控制、按鍵、指示燈及電源組成。 1、通過按鍵點亮led燈&#xff0c;代表車輛啟動和熄火。 2、車輛啟動后&#xff0c;…

OpenCV中的卷積高斯模糊與中值模糊

目錄 一、卷積高斯模糊 (Gaussian Blur) 1. 原理與數學基礎 2. OpenCV函數實現 3. 關鍵參數說明 4. 代碼示例 5. 特點與應用 二、中值模糊 (Median Blur) 1. 原理與數學基礎 2. OpenCV函數實現 3. 關鍵參數說明 4. 代碼示例 5. 特點與應用 三、兩種模糊方法對比分析…

macbookpro m1 max本兒上速搭一個elasticsearch+kibana環境

一、找個目錄&#xff0c;新建一個: docker-compose.yml version: "3.9" services:elasticsearch:image: docker.elastic.co/elasticsearch/elasticsearch:8.13.0 # 與 Kibana 版本一致container_name: elasticsearchenvironment:- discovery.typesingle-node- xpa…

部署zabbix企業級分布式監控

一. 監控系統的功能概述監控、從中文的字義來看&#xff0c;有兩個內容&#xff0c;一是檢測&#xff0c;二是控制。重點在第一個字眼&#xff0c;即檢測、預防的意思。監控&#xff0c;對應的英文單詞是 Monitoring。在計算機領域&#xff0c;可以將其分為5種監控類型。應用性…

【重學MySQL】redolog binlog

目錄 Buffer Pool是什么&#xff1f; redo log&#xff08;Innodb獨有&#xff09; 為什么需要redolog&#xff1f; 類比的方式巧記redolog binlog&#xff08;Server層獨有&#xff09; binlog是干啥的&#xff1f; 為什么有了 binlog&#xff0c; 還要有 redo log&…

企業信息化建設技術底座建設解決方案

1、企業數字化底座與數字化綜述2、企業數字化底座與數字化總體架構3、企業數字化底座與數字化規劃設計4、企業數字化底座與數字化建設運營5、企業數字化底座與數字化未來展望篇幅有限以下只展示部分截圖&#xff1a;

Spring Cloud Alibaba 之 Nacos

Spring Cloud Alibaba 之 Nacos . Nacos官方文檔&#xff1a; https://nacos.io/docs/latest/overview/?spm5238cd80.47ee59c.0.0.770fcd36HoVbU6 1.什么是Nacos Nacos&#xff08;Dynamic Naming and Configuration Service&#xff09;是阿里巴巴開源的一款動態服務發現、…

Car Kit重構車機開發體驗,讓車載應用開發駛入快車道

在智能座艙成為汽車行業“新四化”核心戰場的今天&#xff0c;開發者們正面臨這樣的挑戰&#xff1a;如何讓手機應用快速適配車機場景&#xff1f;如何實現手機與車機無感流轉&#xff1f;如何在保障駕駛安全的前提下提供沉浸式交互體驗&#xff1f; HarmonyOS SDK 車服務&…

ruoyi-flowable-plus Excel 導入數據 Demo

&#x1f4c1; 項目結構簡述 ruoyi-flowable-plus 是基于 RuoYi 的擴展項目&#xff0c;使用&#xff1a; 后端&#xff1a;Spring Boot MyBatis Flowable前端&#xff1a;Vue.js &#x1f4e5; Excel 導入功能 Demo 以導入用戶數據為例&#xff0c;展示完整導入流程。 …

kafka 日志索引 AbstractIndex

AbstractIndexAbstractIndex 是 Kafka 日志&#xff08;Log&#xff09;子系統中一個至關重要的基礎類。它為 Kafka 的各種索引文件&#xff08;如偏移量索引 .index 和時間戳索引 .timeindex&#xff09;提供了一個統一的、抽象的框架。這個類的設計目標是實現極高的讀寫性能和…