diff --git a/src/main/java/com/youlai/boot/core/aspect/LogAspect.java b/src/main/java/com/youlai/boot/core/aspect/LogAspect.java index 4b7ed91b..d25bfc11 100644 --- a/src/main/java/com/youlai/boot/core/aspect/LogAspect.java +++ b/src/main/java/com/youlai/boot/core/aspect/LogAspect.java @@ -138,6 +138,9 @@ public class LogAspect { log.setBrowser(userAgent.getBrowser().getName()); log.setBrowserVersion(userAgent.getBrowser().getVersion(userAgentString)); } + //获取方法名 + String methodName = joinPoint.getSignature().getName(); + log.setMethod(methodName); // 保存日志到数据库 logService.save(log); } diff --git a/src/main/java/com/youlai/boot/system/controller/UserOnlineController.java b/src/main/java/com/youlai/boot/system/controller/UserOnlineController.java new file mode 100644 index 00000000..d0333150 --- /dev/null +++ b/src/main/java/com/youlai/boot/system/controller/UserOnlineController.java @@ -0,0 +1,55 @@ +package com.youlai.boot.system.controller; + +import com.youlai.boot.common.result.Result; +import com.youlai.boot.system.service.UserOnlineService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Map; + +/** + * 在线用户控制器 + * + * @author You Lai + * @since 3.0.0 + */ +@Tag(name = "13.在线用户接口") +@RestController +@RequestMapping("/api/v1/users/online") +@RequiredArgsConstructor +public class UserOnlineController { + + private final UserOnlineService userOnlineService; + + /** + * 获取在线用户列表 + * + * @return 在线用户列表 + */ + @Operation(summary = "获取在线用户列表") + @GetMapping + @PreAuthorize("@ss.hasPerm('sys:monitor:online')") + public Result> getOnlineUsers() { + return Result.success(userOnlineService.getOnlineUsers()); + } + + /** + * 获取在线用户统计信息 + * + * @return 在线用户统计 + */ + @Operation(summary = "获取在线用户统计") + @GetMapping("/stats") + @PreAuthorize("@ss.hasPerm('sys:monitor:online')") + public Result> getOnlineStats() { + return Result.success(Map.of( + "count", userOnlineService.getOnlineUserCount() + )); + } +} \ No newline at end of file diff --git a/src/main/java/com/youlai/boot/system/controller/WebSocketMessageController.java b/src/main/java/com/youlai/boot/system/controller/WebSocketMessageController.java new file mode 100644 index 00000000..46a6f212 --- /dev/null +++ b/src/main/java/com/youlai/boot/system/controller/WebSocketMessageController.java @@ -0,0 +1,83 @@ +package com.youlai.boot.system.controller; + +import com.youlai.boot.core.security.model.SysUserDetails; +import com.youlai.boot.system.service.WebSocketMessageService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.messaging.simp.SimpMessageHeaderAccessor; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Controller; + +import java.util.Map; + +/** + * WebSocket消息控制器 + * 用于处理WebSocket客户端发送的消息 + * + * @author You Lai + * @since 3.0.0 + */ +@Controller +@RequiredArgsConstructor +@Slf4j +public class WebSocketMessageController { + + private final WebSocketMessageService webSocketMessageService; + + /** + * 处理发送到指定用户的消息 + * 客户端发送消息到 /app/sendToUser/{username} + * + * @param message 消息内容 + * @param headerAccessor 消息头访问器 + * @param username 接收消息的用户名 + */ + @MessageMapping("/sendToUser/{username}") + public void sendToUser(@Payload String message, SimpMessageHeaderAccessor headerAccessor, String username) { + Authentication authentication = (Authentication) headerAccessor.getUser(); + if (authentication != null) { + SysUserDetails userDetails = (SysUserDetails) authentication.getPrincipal(); + String sender = userDetails.getUsername(); + + // 构建消息 + Map messageData = Map.of( + "sender", sender, + "content", message, + "timestamp", System.currentTimeMillis() + ); + + // 发送点对点消息 + webSocketMessageService.sendPrivateMessage(username, messageData); + log.info("用户[{}]向用户[{}]发送消息: {}", sender, username, message); + } + } + + /** + * 处理广播消息 + * 客户端发送消息到 /app/broadcast + * + * @param message 消息内容 + * @param headerAccessor 消息头访问器 + */ + @MessageMapping("/broadcast") + public void broadcast(@Payload String message, SimpMessageHeaderAccessor headerAccessor) { + Authentication authentication = (Authentication) headerAccessor.getUser(); + if (authentication != null) { + SysUserDetails userDetails = (SysUserDetails) authentication.getPrincipal(); + String sender = userDetails.getUsername(); + + // 构建消息 + Map messageData = Map.of( + "sender", sender, + "content", message, + "timestamp", System.currentTimeMillis() + ); + + // 发送广播消息 + webSocketMessageService.broadcastMessage(messageData); + log.info("用户[{}]发送广播消息: {}", sender, message); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/youlai/boot/system/controller/WebSocketTestController.java b/src/main/java/com/youlai/boot/system/controller/WebSocketTestController.java new file mode 100644 index 00000000..d857b6ae --- /dev/null +++ b/src/main/java/com/youlai/boot/system/controller/WebSocketTestController.java @@ -0,0 +1,54 @@ +package com.youlai.boot.system.controller; + +import com.youlai.boot.common.result.Result; +import com.youlai.boot.system.service.WebSocketMessageService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +/** + * WebSocket测试控制器 + * + * @author You Lai + * @since 3.0.0 + */ +@Tag(name = "12.WebSocket接口") +@RestController +@RequestMapping("/api/v1/websocket") +@RequiredArgsConstructor +public class WebSocketTestController { + + private final WebSocketMessageService webSocketMessageService; + + /** + * 发送字典更新事件 + * + * @param dictCode 字典编码 + * @return 操作结果 + */ + @Operation(summary = "发送字典更新事件") + @PostMapping("/dict/{dictCode}/updated") + public Result sendDictUpdatedEvent( + @Parameter(description = "字典编码") @PathVariable String dictCode + ) { + webSocketMessageService.sendDictUpdatedEvent(dictCode); + return Result.success(); + } + + /** + * 发送字典删除事件 + * + * @param dictCode 字典编码 + * @return 操作结果 + */ + @Operation(summary = "发送字典删除事件") + @PostMapping("/dict/{dictCode}/deleted") + public Result sendDictDeletedEvent( + @Parameter(description = "字典编码") @PathVariable String dictCode + ) { + webSocketMessageService.sendDictDeletedEvent(dictCode); + return Result.success(); + } +} \ No newline at end of file diff --git a/src/main/java/com/youlai/boot/system/model/entity/Log.java b/src/main/java/com/youlai/boot/system/model/entity/Log.java index 0d49eb00..56ecdb99 100644 --- a/src/main/java/com/youlai/boot/system/model/entity/Log.java +++ b/src/main/java/com/youlai/boot/system/model/entity/Log.java @@ -56,6 +56,11 @@ public class Log implements Serializable { */ private String requestUri; + /** + * 请求方法 + */ + private String method; + /** * IP 地址 */ diff --git a/src/main/java/com/youlai/boot/system/service/WebSocketMessageService.java b/src/main/java/com/youlai/boot/system/service/WebSocketMessageService.java new file mode 100644 index 00000000..e183d7af --- /dev/null +++ b/src/main/java/com/youlai/boot/system/service/WebSocketMessageService.java @@ -0,0 +1,123 @@ +package com.youlai.boot.system.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.stereotype.Service; + +/** + * WebSocket消息服务 + * + * @author Ray + * @since 3.0.0 + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class WebSocketMessageService { + + private final SimpMessagingTemplate messagingTemplate; + private final ObjectMapper objectMapper; + + /** + * 字典事件类型 + */ + public enum DictEventType { + /** + * 字典更新 + */ + DICT_UPDATED, + + /** + * 字典删除 + */ + DICT_DELETED + } + + /** + * 字典事件消息 + */ + public static class DictEvent { + /** + * 事件类型 + */ + private String type; + + /** + * 字典编码 + */ + private String dictCode; + + /** + * 时间戳 + */ + private long timestamp; + + public DictEvent(DictEventType type, String dictCode) { + this.type = type.name(); + this.dictCode = dictCode; + this.timestamp = System.currentTimeMillis(); + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getDictCode() { + return dictCode; + } + + public void setDictCode(String dictCode) { + this.dictCode = dictCode; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + } + + /** + * 向所有客户端发送字典更新事件 + * + * @param dictCode 字典编码 + */ + public void sendDictUpdatedEvent(String dictCode) { + DictEvent event = new DictEvent(DictEventType.DICT_UPDATED, dictCode); + sendDictEvent(event); + } + + /** + * 向所有客户端发送字典删除事件 + * + * @param dictCode 字典编码 + */ + public void sendDictDeletedEvent(String dictCode) { + DictEvent event = new DictEvent(DictEventType.DICT_DELETED, dictCode); + sendDictEvent(event); + } + + /** + * 发送字典事件消息 + * + * @param event 字典事件 + */ + private void sendDictEvent(DictEvent event) { + try { + String message = objectMapper.writeValueAsString(event); + messagingTemplate.convertAndSend("/topic/dict", message); + log.info("Sent dict event to clients: {}", message); + } catch (JsonProcessingException e) { + log.error("Failed to send dict event", e); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/youlai/boot/system/service/impl/DeptServiceImpl.java b/src/main/java/com/youlai/boot/system/service/impl/DeptServiceImpl.java index 39a60deb..db0ec596 100644 --- a/src/main/java/com/youlai/boot/system/service/impl/DeptServiceImpl.java +++ b/src/main/java/com/youlai/boot/system/service/impl/DeptServiceImpl.java @@ -108,7 +108,7 @@ public class DeptServiceImpl extends ServiceImpl implements De .orderByAsc(Dept::getSort) ); if (CollectionUtil.isEmpty(deptList)) { - return Collections.EMPTY_LIST; + return Collections.emptyList(); } Set deptIds = deptList.stream() @@ -238,10 +238,11 @@ public class DeptServiceImpl extends ServiceImpl implements De if (StrUtil.isNotBlank(ids)) { String[] menuIds = ids.split(","); for (String deptId : menuIds) { + String patten = "%," + deptId + ",%"; this.update(new LambdaUpdateWrapper() .eq(Dept::getId, deptId) .or() - .apply("CONCAT (',',tree_path,',') LIKE CONCAT('%,',{0},',%')", deptId) + .apply("CONCAT (',',tree_path,',') LIKE {0}", patten) .set(Dept::getIsDeleted, 1) .set(Dept::getUpdateBy, SecurityUtils.getUserId()) ); diff --git a/src/main/java/com/youlai/boot/system/service/impl/DictServiceImpl.java b/src/main/java/com/youlai/boot/system/service/impl/DictServiceImpl.java index 2573b4d3..a2a04c6f 100644 --- a/src/main/java/com/youlai/boot/system/service/impl/DictServiceImpl.java +++ b/src/main/java/com/youlai/boot/system/service/impl/DictServiceImpl.java @@ -116,7 +116,11 @@ public class DictServiceImpl extends ServiceImpl implements Di throw new BusinessException("字典不存在"); } // 校验 code 是否唯一 +<<<<<<< HEAD + String dictCode = dictForm.getCode(); +======= String dictCode = dictForm.getDictCode(); +>>>>>>> 95412501fc69777ad7db6fef970b479c9651984d if (!entity.getDictCode().equals(dictCode)) { long count = this.count(new LambdaQueryWrapper() .eq(Dict::getDictCode, dictCode) @@ -126,6 +130,9 @@ public class DictServiceImpl extends ServiceImpl implements Di // 更新字典 Dict dict = dictConverter.toEntity(dictForm); dict.setId(id); +<<<<<<< HEAD + return this.updateById(dict); +======= boolean result = this.updateById(dict); if (result) { // 更新字典数据 @@ -145,6 +152,7 @@ public class DictServiceImpl extends ServiceImpl implements Di } } return result; +>>>>>>> 95412501fc69777ad7db6fef970b479c9651984d } /**