根據錯誤日誌,問題出現在執行MyBatis的update操作時,參數映射失敗。具體來説,在更新CsMessage時,MyBatis在動態SQL中找不到名為'sessionId'的參數。可用的參數是[param1, et]。
查看EnhancedMessageService中的deliverMessageToCustomer方法,其中有一行:
csMessageMapper.updateById(csMessage);
這裏我們使用MyBatis-Plus的updateById方法,它期望傳入一個實體對象。但是,從錯誤信息來看,在動態SQL中,參數被命名為'et'(這是MyBatis-Plus的默認命名,代表實體對象)和'param1'(代表第一個參數,也就是實體對象)。但是在XML中,我們使用了條件判斷,如:
<if test="sessionId != null and sessionId != ''">session_id = #{sessionId},</if>
這裏直接使用了屬性名'sessionId',而MyBatis在解析時會在傳入的參數中查找名為'sessionId'的屬性。由於我們傳入的是CsMessage對象,它確實有sessionId屬性,但是為什麼找不到呢?
可能的原因是在MyBatis的配置中,參數映射的方式有問題。但是,從錯誤信息來看,可用的參數是[param1, et],這意味着MyBatis將參數命名為param1,同時還有一個別名et(這是MyBatis-Plus的默認行為)。在XML中,我們直接使用屬性名,MyBatis會從實體對象中獲取屬性,但是這裏卻報錯説找不到'sessionId'參數。
另一種可能是,在XML中,我們使用了多個updateById的映射,而MyBatis在解析時出現了混淆。查看CsMessageMapper.xml,我們發現有兩個updateById的映射,這可能會導致MyBatis使用第一個映射,而第一個映射的參數配置可能不正確。
在CsMessageMapper.xml中,有兩個updateById的映射,這是不允許的。MyBatis會根據方法名和參數類型來匹配映射,但是同一個命名空間下不能有相同的id。因此,我們需要刪除重複的updateById映射。
解決方案:
- 刪除CsMessageMapper.xml中重複的updateById映射,只保留一個。
- 確保在updateById的映射中,使用正確的參數類型,並且使用et作為別名來引用實體屬性(這是MyBatis-Plus的默認行為),或者直接使用屬性名(因為MyBatis-Plus默認會將實體對象作為參數,並且屬性可以直接訪問)。
但是,我們注意到在XML中,我們使用的是:
<if test="sessionId != null and sessionId != ''">session_id = #{sessionId},</if>
這裏直接使用sessionId,而不是et.sessionId。在MyBatis-Plus中,默認的參數名是et,所以我們應該使用et.sessionId?實際上,MyBatis-Plus在更新時,會使用實體對象作為參數,並且默認的別名是et。但是,在動態SQL中,我們也可以直接使用屬性名,因為MyBatis會從實體對象中查找屬性。但是,如果同時有多個參數,就必須使用別名。
由於我們只有一個參數(即實體對象),所以可以直接使用屬性名。但是,從錯誤信息來看,MyBatis在參數映射時,可用的參數是[param1, et],這意味着我們有兩個參數?實際上,MyBatis-Plus的updateById方法只有一個參數,但是MyBatis-Plus在內部處理時,可能會將實體對象作為et和param1兩個別名。所以,我們可以通過et.sessionId來訪問,也可以直接使用sessionId。
但是,這裏報錯説找不到'sessionId',説明MyBatis在參數中沒有找到名為sessionId的參數。因此,我們可以嘗試修改XML,使用et.sessionId。
但是,我們也可以看到,在CsMessageMapper.xml中,有兩個updateById的映射,這可能是導致問題的根本原因。因為MyBatis在解析映射時,可能會使用第一個映射,而第一個映射可能沒有正確配置參數。
因此,我們首先刪除重複的updateById映射。
步驟:
- 打開CsMessageMapper.xml,刪除重複的updateById映射,只保留一個。
- 確保保留的updateById映射中,參數類型為CsMessage,並且使用正確的動態SQL。
另外,我們也可以考慮使用MyBatis-Plus的默認實現,而不需要自己寫updateById的映射。因為MyBatis-Plus已經提供了通用的updateById方法,我們不需要在XML中編寫。所以,我們可以將CsMessageMapper.xml中的updateById映射刪除,直接使用MyBatis-Plus的通用方法。
但是,我們注意到在CsMessageMapper.java中,我們繼承了BaseMapper,它已經包含了updateById方法。因此,我們不需要在XML中編寫updateById映射。所以,我們可以將CsMessageMapper.xml中的兩個updateById映射全部刪除。
然後,我們檢查CsMessageMapper.xml中是否還有其他重複的映射,確保每個映射的id都是唯一的。
另外,我們還可以在EnhancedMessageService中,使用MyBatis-Plus的updateById方法時,確保傳入的csMessage對象中的messageId不為空,因為它是主鍵。
我們修改CsMessageMapper.xml,刪除重複的updateById映射,只保留其他自定義的映射。
修改後的CsMessageMapper.xml應該只包含一個updateById映射(如果確實需要自定義,否則刪除)?但是,我們注意到在CsMessageMapper.xml中,有兩個updateById映射,我們將其全部刪除,因為MyBatis-Plus已經提供了實現。
但是,我們還有一個自定義的update方法:updateCsMessage,它也是更新操作,但是根據messageId更新。我們可能在其他地方使用了updateCsMessage,所以保留updateCsMessage。
所以,我們刪除CsMessageMapper.xml中兩個updateById映射,然後測試。
如果問題仍然存在,我們可以在CsMessageMapper.java中,使用@Param註解來明確參數名,或者在XML中使用_parameter(代表整個參數)來訪問屬性。但是,由於我們使用的是MyBatis-Plus,它已經幫我們處理了參數別名,所以刪除重複映射後應該可以解決問題。
我們修改CsMessageMapper.xml,刪除兩個updateById映射,然後重新部署測試。
如果問題仍然存在,我們可以嘗試在XML中使用et.sessionId,例如:
<if test="et.sessionId != null and et.sessionId != ''">session_id = #{et.sessionId},</if>
但是,由於我們刪除了自定義的updateById映射,那麼就會使用MyBatis-Plus自帶的updateById方法,它的XML映射是在MyBatis-Plus的jar包中定義的,我們不應該修改。所以,我們不應該在CsMessageMapper.xml中定義updateById。
因此,我們刪除CsMessageMapper.xml中重複的updateById映射,然後清理緩存,重新啓動應用。
如果問題仍然存在,我們再考慮其他方案。
現在,我們修改CsMessageMapper.xml,刪除兩個updateById映射。
修改後的CsMessageMapper.xml中,不再有updateById映射,而是使用MyBatis-Plus自帶的updateById方法。
我們重新部署應用,觀察錯誤是否解決。
如果錯誤仍然存在,那麼可能是MyBatis-Plus的版本問題,或者配置問題。我們可以嘗試在application.yml中配置mybatis-plus的configuration,設置use-actual-param-name為true,例如:
mybatis-plus:
configuration:
use-actual-param-name: true
但是,我們也可以考慮在EnhancedMessageService中,使用我們自定義的update方法,即使用updateDeliveredStatus方法,只更新送達狀態,而不是整個實體。這樣可能更安全。
在EnhancedMessageService的deliverMessageToCustomer方法中,我們更新csMessage的delivered字段,然後調用updateById。實際上,我們也可以只更新delivered字段,使用:
csMessageMapper.updateDeliveredStatus(csMessage.getMessageId(), true);
這樣,我們使用自定義的updateDeliveredStatus方法,它只更新delivered字段,避免動態SQL中的複雜判斷。
所以,我們可以修改deliverMessageToCustomer方法,將:
csMessage.setDelivered(true);
csMessageMapper.updateById(csMessage);
改為:
csMessageMapper.updateDeliveredStatus(csMessage.getMessageId(), true);
同樣,在else分支中,我們也使用updateDeliveredStatus方法。
這樣修改可以避免使用動態SQL,從而避免參數映射問題。
我們選擇這種方案,因為更安全,並且效率更高(只更新一個字段)。
修改EnhancedMessageService中的deliverMessageToCustomer方法:
原始代碼:
if (isOnline) {
try {
...
csMessage.setDelivered(true);
csMessageMapper.updateById(csMessage);
} catch (Exception e) {
...
csMessage.setDelivered(false);
csMessageMapper.updateById(csMessage);
}
} else {
...
csMessage.setDelivered(false);
csMessageMapper.updateById(csMessage);
}
修改為:
if (isOnline) {
try {
...
csMessageMapper.updateDeliveredStatus(csMessage.getMessageId(), true);
} catch (Exception e) {
...
csMessageMapper.updateDeliveredStatus(csMessage.getMessageId(), false);
}
} else {
...
csMessageMapper.updateDeliveredStatus(csMessage.getMessageId(), false);
}
注意:我們不再需要設置csMessage的delivered屬性,因為直接調用updateDeliveredStatus方法更新數據庫。但是,csMessage對象在內存中的delivered屬性並沒有更新,這可能會影響後續使用。但是,在這個方法中,csMessage對象之後並沒有被使用,所以可以這樣修改。
我們進行修改,然後測試。
同時,我們也要修改handleAgentReply方法中保存客服消息後的更新操作,將updateById改為updateDeliveredStatus嗎?不,在handleAgentReply方法中,我們保存客服消息後,在deliverMessageToCustomer方法中會更新送達狀態。所以,我們只需要修改deliverMessageToCustomer方法中的更新操作。
我們修改EnhancedMessageService中的deliverMessageToCustomer方法,將所有的updateById調用改為updateDeliveredStatus。
修改後,我們重新部署應用,觀察錯誤是否解決。
如果問題解決,那麼我們就避免了動態SQL的參數映射問題。
如果問題仍然存在,那麼可能是其他原因,我們需要進一步分析。
現在,我們按照上述方案修改代碼。
根據錯誤日誌,問題出現在執行 MyBatis 的 updateById 方法時,參數映射失敗。具體錯誤是:
Parameter 'sessionId' not found. Available parameters are [param1, et]
這個問題是由於 MyBatis-Plus 的 XML 映射文件中存在重複的 updateById 定義,導致參數映射混亂。
問題分析
查看 CsMessageMapper.xml 文件,確實存在兩個相同的 updateById 映射:
<update id="updateById" parameterType="com.storm.customerService.domain.CsMessage">
<!-- 第一個定義 -->
</update>
<update id="updateById" parameterType="com.storm.customerService.domain.CsMessage">
<!-- 第二個定義 -->
</update>
解決方案
方案1:修復 XML 映射文件(推薦)
刪除 CsMessageMapper.xml 中重複的 updateById 定義,只保留一個:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.storm.customerService.mapper.CsMessageMapper">
<resultMap type="com.storm.customerService.domain.CsMessage" id="CsMessageResult">
<result property="messageId" column="message_id" />
<result property="sessionId" column="session_id" />
<result property="senderType" column="sender_type" />
<result property="senderId" column="sender_id" />
<result property="senderName" column="sender_name" />
<result property="content" column="content" />
<result property="messageType" column="message_type" />
<result property="sendTime" column="send_time" />
<result property="delivered" column="delivered" />
</resultMap>
<sql id="selectCsMessageVo">
select message_id, session_id, sender_type, sender_id, sender_name, content, message_type, send_time, delivered from cs_message
</sql>
<!-- 刪除重複的 updateById 定義,只保留一個 -->
<update id="updateById" parameterType="com.storm.customerService.domain.CsMessage">
UPDATE cs_message
<set>
<if test="sessionId != null and sessionId != ''">session_id = #{sessionId},</if>
<if test="senderType != null and senderType != ''">sender_type = #{senderType},</if>
<if test="senderId != null and senderId != ''">sender_id = #{senderId},</if>
<if test="senderName != null and senderName != ''">sender_name = #{senderName},</if>
<if test="content != null and content != ''">content = #{content},</if>
<if test="messageType != null">message_type = #{messageType},</if>
<if test="sendTime != null">send_time = #{sendTime},</if>
<if test="delivered != null">delivered = #{delivered},</if>
</set>
WHERE message_id = #{messageId}
</update>
<!-- 其他映射保持不變 -->
<!-- ... -->
</mapper>
方案2:使用註解方法替代 XML 映射
在 EnhancedMessageService.java 中,將 updateById 調用改為使用註解方法:
/**
* 改進的消息投遞方法 - 修復版本
*/
private void deliverMessageToCustomer(String customerId, String agentName,
String content, CsMessage csMessage) {
// 使用改進的在線狀態檢查
boolean isOnline = isCustomerReallyOnline(customerId);
log.info("消息投遞檢查 - 客户: {}, 在線: {}, WebSocket狀態: {}, 會話存在: {}",
customerId, isOnline, customerOnlineStatus.get(customerId),
customerSessionMap.containsKey(customerId));
if (isOnline) {
try {
WebSocketSession session = customerSessionMap.get(customerId);
if (session != null && session.isOpen()) {
// 構建消息格式
Map<String, Object> messageData = new HashMap<>();
messageData.put("type", "AGENT_MESSAGE");
messageData.put("messageId", csMessage.getMessageId());
messageData.put("senderName", agentName);
messageData.put("content", content);
messageData.put("timestamp", System.currentTimeMillis());
messageData.put("agentName", agentName);
String jsonMessage = objectMapper.writeValueAsString(messageData);
// 通過WebSocket推送到客户APP
webSocketHandler.sendMessageToCustomer(customerId, jsonMessage, csMessage.getMessageId());
// 使用註解方法更新送達狀態
csMessageMapper.updateDeliveredStatus(csMessage.getMessageId(), true);
log.info("客服消息已推送到客户: {} - {}", customerId, content);
} else {
log.warn("WebSocket會話無效,消息暫存: {}", customerId);
csMessageMapper.updateDeliveredStatus(csMessage.getMessageId(), false);
}
} catch (Exception e) {
log.error("推送消息到客户失敗: {}", customerId, e);
// 推送失敗時更新狀態
updateCustomerOnlineStatus(customerId, false);
csMessageMapper.updateDeliveredStatus(csMessage.getMessageId(), false);
}
} else {
log.warn("客户不在線,消息暫存: {}", customerId);
// 消息保持未送達狀態,等客户上線後重新發送
csMessageMapper.updateDeliveredStatus(csMessage.getMessageId(), false);
}
}
方案3:簡化 XML 映射
如果確實需要保留 XML 映射,可以簡化 updateById 方法:
<update id="updateById" parameterType="com.storm.customerService.domain.CsMessage">
UPDATE cs_message
SET
session_id = #{sessionId},
sender_type = #{senderType},
sender_id = #{senderId},
sender_name = #{senderName},
content = #{content},
message_type = #{messageType},
send_time = #{sendTime},
delivered = #{delivered}
WHERE message_id = #{messageId}
</update>
推薦實施步驟
- 立即修復:採用方案2,使用註解方法
updateDeliveredStatus替代updateById - 長期修復:清理 XML 映射文件中的重複定義(方案1)
- 測試驗證:確保客服回覆功能正常工作
修改後的關鍵代碼
在 EnhancedMessageService.java 中替換所有有問題的 updateById 調用:
// 將原來的:
csMessage.setDelivered(true);
csMessageMapper.updateById(csMessage);
// 改為:
csMessageMapper.updateDeliveredStatus(csMessage.getMessageId(), true);
這樣修改後,將避免 MyBatis 參數映射的問題,確保客服回覆功能正常運行。