mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-02-12 01:51:02 +08:00
Compare commits
7 Commits
feature/da
...
feature/da
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61c0336a78 | ||
|
|
0ed9ad2f2a | ||
|
|
67e912381b | ||
|
|
a6a1c72a37 | ||
|
|
d77baa8a93 | ||
|
|
fce4832407 | ||
|
|
91c8cc9607 |
@@ -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\"}");
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user