一、需求分析
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:
- 場景:多臺服務器共享會話數據(如微服務架構)。
- 實現:
- 粘性會話:請求始終路由到同一服務器,底層仍是本地 Map。
- 會話復制:服務器間同步會話數據(性能開銷大)。
- 外部存儲:會話數據存儲在 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
@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);}