Compare commits

...

7 Commits

Author SHA1 Message Date
Tim
61c0336a78 fix: api fix 2025-08-22 16:25:22 +08:00
Tim
0ed9ad2f2a fix: 修复nginx /ws拦截问题 2025-08-22 15:26:41 +08:00
Tim
67e912381b Merge pull request #693 from nagisa77/feature/daily_bugfix_0822_a
fix: 发送信息,携带头像
2025-08-22 13:26:39 +08:00
Tim
a6a1c72a37 fix: 发送信息,携带头像 2025-08-22 13:26:04 +08:00
Tim
d77baa8a93 Merge pull request #692 from nagisa77/feature/daily_bugfix_0822_a
fix: 移动端 ui适配
2025-08-22 13:24:01 +08:00
Tim
fce4832407 fix: 移动端 ui适配 2025-08-22 13:23:35 +08:00
Tim
91c8cc9607 Merge pull request #689 from nagisa77/feature/daily_bugfix_0822
fix: 消息页面ui重构
2025-08-22 13:12:57 +08:00
5 changed files with 100 additions and 111 deletions

View File

@@ -92,7 +92,7 @@ public class SecurityConfig {
cfg.setAllowedHeaders(List.of("*"));
cfg.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", cfg);
source.registerCorsConfiguration("/api/**", cfg);
return source;
}
@@ -104,7 +104,7 @@ public class SecurityConfig {
.exceptionHandling(eh -> eh.accessDeniedHandler(customAccessDeniedHandler))
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.requestMatchers("/ws/**").permitAll()
.requestMatchers("/api/ws/**").permitAll()
.requestMatchers(HttpMethod.POST, "/api/auth/**").permitAll()
.requestMatchers(HttpMethod.GET, "/api/posts/**").permitAll()
.requestMatchers(HttpMethod.GET, "/api/comments/**").permitAll()
@@ -173,7 +173,7 @@ public class SecurityConfig {
response.getWriter().write("{\"error\": \"Invalid or expired token\"}");
return;
}
} else if (!uri.startsWith("/api/auth") && !publicGet && !uri.startsWith("/ws")) {
} else if (!uri.startsWith("/api/auth") && !publicGet && !uri.startsWith("/api/ws")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.getWriter().write("{\"error\": \"Missing token\"}");

View File

@@ -41,8 +41,7 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// Registers the "/ws" endpoint, enabling SockJS fallback options so that alternate transports may be used if WebSocket is not available.
registry.addEndpoint("/ws")
registry.addEndpoint("/api/ws")
// 安全改进:使用具体的允许源,而不是通配符
.setAllowedOrigins(
"http://127.0.0.1:8080",

View File

@@ -1,85 +1,83 @@
import { ref } from 'vue';
import { Client } from '@stomp/stompjs';
import SockJS from 'sockjs-client/dist/sockjs.min.js';
import { useRuntimeConfig } from '#app';
import { ref } from 'vue'
import { Client } from '@stomp/stompjs'
import SockJS from 'sockjs-client/dist/sockjs.min.js'
import { useRuntimeConfig } from '#app'
const client = ref(null);
const isConnected = ref(false);
const client = ref(null)
const isConnected = ref(false)
const connect = (token) => {
if (isConnected.value) {
return;
}
if (isConnected.value) {
return
}
const config = useRuntimeConfig();
const API_BASE_URL = config.public.apiBaseUrl;
const socketUrl = `${API_BASE_URL}/ws`;
const config = useRuntimeConfig()
const API_BASE_URL = config.public.apiBaseUrl
const socketUrl = `${API_BASE_URL}/api/ws`
const socket = new SockJS(socketUrl);
const stompClient = new Client({
webSocketFactory: () => socket,
connectHeaders: {
Authorization: `Bearer ${token}`,
},
debug: function (str) {
},
reconnectDelay: 5000,
heartbeatIncoming: 4000,
heartbeatOutgoing: 4000,
});
const socket = new SockJS(socketUrl)
const stompClient = new Client({
webSocketFactory: () => socket,
connectHeaders: {
Authorization: `Bearer ${token}`,
},
debug: function (str) {},
reconnectDelay: 5000,
heartbeatIncoming: 4000,
heartbeatOutgoing: 4000,
})
stompClient.onConnect = (frame) => {
isConnected.value = true;
};
stompClient.onConnect = (frame) => {
isConnected.value = true
}
stompClient.onStompError = (frame) => {
console.error('WebSocket STOMP error:', frame);
};
stompClient.onStompError = (frame) => {
console.error('WebSocket STOMP error:', frame)
}
stompClient.activate();
client.value = stompClient;
};
stompClient.activate()
client.value = stompClient
}
const disconnect = () => {
if (client.value) {
isConnected.value = false;
client.value.deactivate();
client.value = null;
}
};
if (client.value) {
isConnected.value = false
client.value.deactivate()
client.value = null
}
}
const subscribe = (destination, callback) => {
if (!isConnected.value || !client.value || !client.value.connected) {
return null;
}
if (!isConnected.value || !client.value || !client.value.connected) {
return null
}
try {
const subscription = client.value.subscribe(destination, (message) => {
try {
if (destination.includes('/queue/unread-count')) {
callback(message);
} else {
const parsedMessage = JSON.parse(message.body);
callback(parsedMessage);
}
} catch (error) {
callback(message);
}
});
return subscription;
} catch (error) {
return null;
}
};
try {
const subscription = client.value.subscribe(destination, (message) => {
try {
if (destination.includes('/queue/unread-count')) {
callback(message)
} else {
const parsedMessage = JSON.parse(message.body)
callback(parsedMessage)
}
} catch (error) {
callback(message)
}
})
return subscription
} catch (error) {
return null
}
}
export function useWebSocket() {
return {
client,
isConnected,
connect,
disconnect,
subscribe,
};
}
return {
client,
isConnected,
connect,
disconnect,
subscribe,
}
}

View File

@@ -196,10 +196,14 @@ async function sendMessage(content, clearInput) {
if (!response.ok) throw new Error('发送失败')
const newMessage = await response.json()
messages.value.push(newMessage)
messages.value.push({
...newMessage,
src: newMessage.sender.avatar,
iconClick: () => {
navigateTo(`/users/${newMessage.sender.id}`, { replace: true })
},
})
clearInput()
// Use a more reliable scroll approach
setTimeout(() => {
scrollToBottom()
}, 100)
@@ -277,7 +281,13 @@ watch(isConnected, (newValue) => {
subscription = subscribe(`/topic/conversation/${conversationId}`, (message) => {
// 避免重复显示当前用户发送的消息
if (message.sender.id !== currentUser.value.id) {
messages.value.push(message)
messages.value.push({
...message,
src: message.sender.avatar,
iconClick: () => {
navigateTo(`/users/${message.sender.id}`, { replace: true })
},
})
// 实时收到消息时自动标记为已读
markConversationAsRead()
setTimeout(() => {
@@ -445,6 +455,7 @@ onUnmounted(() => {
.message-input-area {
margin-left: 20px;
margin-right: 20px;
}
.loading-container,
@@ -453,4 +464,15 @@ onUnmounted(() => {
padding: 50px;
color: var(--text-color-secondary);
}
@media (max-width: 768px) {
.messages-list {
padding: 10px;
}
}
.message-input-area {
margin-left: 10px;
margin-right: 10px;
}
</style>

View File

@@ -283,7 +283,7 @@ function goToConversation(id) {
/* 响应式设计 */
@media (max-width: 768px) {
.messages-container {
padding: 16px 12px;
padding: 10px 10px;
}
.messages-title {
@@ -295,7 +295,7 @@ function goToConversation(id) {
}
.conversation-item {
padding: 12px 16px;
padding: 6px 8px;
}
.avatar-img {
@@ -315,34 +315,4 @@ function goToConversation(id) {
font-size: 13px;
}
}
@media (max-width: 480px) {
.messages-container {
padding: 12px 8px;
}
.conversations-list {
max-height: 400px;
}
.conversation-item {
padding: 10px 12px;
}
.avatar-img {
width: 36px;
height: 36px;
}
.conversation-avatar {
margin-right: 12px;
}
}
/* 大屏幕设备 */
@media (min-width: 1024px) {
.conversations-list {
max-height: 700px;
}
}
</style>