Эх сурвалжийг харах

新增 websoket 告知消息

hubin 1 жил өмнө
parent
commit
25e81d98ac

+ 1 - 0
build.gradle

@@ -68,6 +68,7 @@ dependencyManagement {
 dependencies {
     implementation("com.aizuda:aizuda-service-parent:1.0.0")
     implementation("com.github.ben-manes.caffeine:caffeine")
+    implementation("org.springframework.boot:spring-boot-starter-websocket")
 
     // 日志
     api('org.springframework.boot:spring-boot-starter-log4j2')

+ 75 - 0
src/main/java/com/aizuda/boot/modules/system/controller/SysMessageController.java

@@ -0,0 +1,75 @@
+package com.aizuda.boot.modules.system.controller;
+
+import com.aizuda.boot.modules.system.entity.vo.InformMessageVO;
+import com.aizuda.core.api.ApiController;
+import com.aizuda.core.api.PageParam;
+import com.aizuda.core.validation.Create;
+import com.aizuda.core.validation.Update;
+import com.aizuda.boot.modules.system.entity.SysMessage;
+import com.aizuda.boot.modules.system.service.ISysMessageService;
+import com.baomidou.kisso.annotation.Permission;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.AllArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import jakarta.validation.constraints.NotEmpty;
+import java.util.List;
+
+/**
+ * 消息发送表 前端控制器
+ *
+ * @author 青苗
+ * @since 2023-10-03
+ */
+@Tag(name = "系统消息表")
+@RestController
+@AllArgsConstructor
+@RequestMapping("/sys/message")
+public class SysMessageController extends ApiController {
+    private ISysMessageService sysMessageService;
+
+    @Operation(summary = "分页列表")
+    @Permission("sys:message:page")
+    @PostMapping("/page")
+    public Page<SysMessage> getPage(@RequestBody PageParam<SysMessage> dto) {
+        return sysMessageService.page(dto.page(), dto.getData());
+    }
+
+    @Operation(summary = "告知消息")
+    @Permission(ignore = true)
+    @GetMapping("/inform")
+    public InformMessageVO inform() {
+        return sysMessageService.getInformByUser();
+    }
+
+    @Operation(summary = "查询 id 信息")
+    @Permission("sys:message:get")
+    @GetMapping("/get")
+    public SysMessage get(@RequestParam Long id) {
+        return sysMessageService.getById(id);
+    }
+
+    @Operation(summary = "根据 id 修改信息")
+    @Permission("sys:message:update")
+    @PostMapping("/update")
+    public boolean update(@Validated(Update.class) @RequestBody SysMessage sysMessage) {
+        return sysMessageService.updateById(sysMessage);
+    }
+
+    @Operation(summary = "创建添加")
+    @Permission("sys:message:create")
+    @PostMapping("/create")
+    public boolean create(@Validated(Create.class) @RequestBody SysMessage sysMessage) {
+        return sysMessageService.save(sysMessage);
+    }
+
+    @Operation(summary = "根据 ids 删除")
+    @Permission("sys:message:delete")
+    @PostMapping("/delete")
+    public boolean delete(@NotEmpty @RequestBody List<Long> ids) {
+        return sysMessageService.removeByIds(ids);
+    }
+}

+ 100 - 0
src/main/java/com/aizuda/boot/modules/system/entity/SysMessage.java

@@ -0,0 +1,100 @@
+package com.aizuda.boot.modules.system.entity;
+
+import com.aizuda.core.ApiConstants;
+import com.aizuda.core.bean.BaseEntity;
+import com.aizuda.core.bean.SuperEntity;
+import com.aizuda.core.validation.Create;
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.PositiveOrZero;
+import jakarta.validation.constraints.Size;
+import java.util.Date;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Setter;
+import lombok.Getter;
+
+/**
+ * 系统消息表
+ *
+ * @author 青苗
+ * @since 2023-10-03
+ */
+@Getter
+@Setter
+@Schema(name = "SysMessage", description = "系统消息表")
+@TableName("sys_message")
+public class SysMessage  extends SuperEntity {
+
+	/**
+	 * 创建人ID
+	 */
+	@TableField(fill = FieldFill.INSERT)
+	protected Long createId;
+
+	/**
+	 * 创建人
+	 */
+	@TableField(fill = FieldFill.INSERT)
+	protected String createBy;
+
+	/**
+	 * 创建时间
+	 */
+	@JsonFormat(pattern = ApiConstants.DATE_MM)
+	@TableField(fill = FieldFill.INSERT)
+	protected Date createTime;
+
+	/**
+	 * 修改人
+	 */
+	@TableField(fill = FieldFill.UPDATE)
+	protected String updateBy;
+
+	/**
+	 * 修改时间
+	 */
+	@JsonFormat(pattern = ApiConstants.DATE_MM)
+	@TableField(fill = FieldFill.UPDATE)
+	protected Date updateTime;
+
+	@Schema(description = "标题")
+	@Size(max = 255)
+	private String title;
+
+	@Schema(description = "内容")
+	@Size(max = 800)
+	private String content;
+
+	@Schema(description = "类别 0,通知 1,消息 2,待办")
+	@NotNull(groups = Create.class)
+	@PositiveOrZero
+	private Integer category;
+
+	@Schema(description = "接收人ID")
+	@PositiveOrZero
+	private Long userId;
+
+	@Schema(description = "接收人")
+	@Size(max = 50)
+	private String username;
+
+	@Schema(description = "已查看 0,否 1,是")
+	@PositiveOrZero
+	private Integer viewed;
+
+	@Schema(description = "发送状态 0,未发送 1,成功 2,失败")
+	@PositiveOrZero
+	private Integer sendStatus;
+
+	@Schema(description = "发送失败原因")
+	@Size(max = 255)
+	private String sendFailure;
+
+	@Schema(description = "发送时间")
+	private Date sendTime;
+
+}

+ 22 - 0
src/main/java/com/aizuda/boot/modules/system/entity/vo/InformMessageVO.java

@@ -0,0 +1,22 @@
+package com.aizuda.boot.modules.system.entity.vo;
+
+import com.aizuda.boot.modules.system.entity.SysMessage;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+
+@Getter
+@Setter
+public class InformMessageVO {
+    @Schema(description = "通知")
+    private List<SysMessage> noticeList;
+
+    @Schema(description = "消息")
+    private List<SysMessage> messageList;
+
+    @Schema(description = "待办")
+    private List<SysMessage> todoList;
+
+}

+ 18 - 0
src/main/java/com/aizuda/boot/modules/system/mapper/SysMessageMapper.java

@@ -0,0 +1,18 @@
+package com.aizuda.boot.modules.system.mapper;
+
+import com.aizuda.boot.modules.system.entity.SysMessage;
+import com.aizuda.service.mapper.CrudMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ * 系统消息表 Mapper 接口
+ * </p>
+ *
+ * @author 青苗
+ * @since 2023-10-03
+ */
+@Mapper
+public interface SysMessageMapper extends CrudMapper<SysMessage> {
+
+}

+ 20 - 0
src/main/java/com/aizuda/boot/modules/system/service/ISysMessageService.java

@@ -0,0 +1,20 @@
+package com.aizuda.boot.modules.system.service;
+
+import com.aizuda.boot.modules.system.entity.vo.InformMessageVO;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.aizuda.service.service.IBaseService;
+import com.aizuda.boot.modules.system.entity.SysMessage;
+
+/**
+ * 系统消息表 服务类
+ *
+ * @author 青苗
+ * @since 2023-10-03
+ */
+public interface ISysMessageService extends IBaseService<SysMessage> {
+
+    Page<SysMessage> page(Page<SysMessage> page, SysMessage sysMessage);
+
+    InformMessageVO getInformByUser();
+
+}

+ 53 - 0
src/main/java/com/aizuda/boot/modules/system/service/impl/SysMessageServiceImpl.java

@@ -0,0 +1,53 @@
+package com.aizuda.boot.modules.system.service.impl;
+
+import com.aizuda.boot.modules.system.entity.SysMessage;
+import com.aizuda.boot.modules.system.entity.vo.InformMessageVO;
+import com.aizuda.boot.modules.system.mapper.SysMessageMapper;
+import com.aizuda.boot.modules.system.service.ISysMessageService;
+import com.aizuda.core.api.ApiAssert;
+import com.aizuda.service.service.BaseServiceImpl;
+import com.aizuda.service.web.UserSession;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 系统消息表 服务实现类
+ *
+ * @author 青苗
+ * @since 2023-10-03
+ */
+@Service
+public class SysMessageServiceImpl extends BaseServiceImpl<SysMessageMapper, SysMessage> implements ISysMessageService {
+
+    @Override
+    public Page<SysMessage> page(Page<SysMessage> page, SysMessage sysMessage) {
+        LambdaQueryWrapper<SysMessage> lqw = Wrappers.lambdaQuery(sysMessage);
+        return super.page(page, lqw);
+    }
+
+    @Override
+    public boolean updateById(SysMessage sysMessage) {
+        ApiAssert.fail(null == sysMessage.getId(), "主键不存在无法更新");
+        return super.updateById(sysMessage);
+    }
+
+    @Override
+    public InformMessageVO getInformByUser() {
+        UserSession userSession = UserSession.getLoginInfo();
+        InformMessageVO vo = new InformMessageVO();
+        vo.setNoticeList(this.listCategoryByUser(userSession.getId(), 0));
+        vo.setMessageList(this.listCategoryByUser(userSession.getId(), 1));
+        vo.setTodoList(this.listCategoryByUser(userSession.getId(), 2));
+        return vo;
+    }
+
+    public List<SysMessage> listCategoryByUser(Long userId, Integer category) {
+        return lambdaQuery().select(SysMessage::getId, SysMessage::getTitle, SysMessage::getContent, SysMessage::getCreateTime)
+                .eq(SysMessage::getCategory, category).eq(SysMessage::getUserId, userId).eq(SysMessage::getViewed, 0)
+                .orderByDesc(SysMessage::getCreateTime).last("LIMIT 5").list();
+    }
+}

+ 26 - 0
src/main/java/com/aizuda/boot/modules/ws/WsConfiguration.java

@@ -0,0 +1,26 @@
+package com.aizuda.boot.modules.ws;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+/**
+ * websocket 配置
+ *
+ * @author 青苗
+ * @since 2023-10-03
+ */
+@Configuration
+public class WsConfiguration {
+
+    /**
+     * 用于扫描和注册所有携带ServerEndPoint注解的实例。
+     */
+    @Bean
+    @ConditionalOnMissingBean
+    public ServerEndpointExporter serverEndpointExporter() {
+        return new ServerEndpointExporter();
+    }
+
+}

+ 38 - 0
src/main/java/com/aizuda/boot/modules/ws/WsEndpointConfigurator.java

@@ -0,0 +1,38 @@
+package com.aizuda.boot.modules.ws;
+
+import jakarta.websocket.HandshakeResponse;
+import jakarta.websocket.server.HandshakeRequest;
+import jakarta.websocket.server.ServerEndpointConfig;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.collections.MapUtils;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * WebSocket Endpoint 配置
+ *
+ * @author 青苗
+ * @since 2023-10-03
+ */
+public class WsEndpointConfigurator extends ServerEndpointConfig.Configurator {
+
+    @Override
+    public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {
+        Map<String, List<String>> headers = request.getHeaders();
+        if (MapUtils.isNotEmpty(headers)) {
+            Map<String, Object> propMap = config.getUserProperties();
+            List<String> userAgents = headers.get("User-Agent");
+            if (CollectionUtils.isNotEmpty(userAgents)) {
+                propMap.put("userAgent", userAgents.get(0));
+            }
+            List<String> cookieList = headers.get("Cookie");
+            if (CollectionUtils.isNotEmpty(cookieList)) {
+                String pid = cookieList.get(0);
+                if (null != pid && pid.contains("pid=")) {
+                    propMap.put("accessToken", pid.split("pid=")[1].split(";")[0]);
+                }
+            }
+        }
+    }
+}

+ 122 - 0
src/main/java/com/aizuda/boot/modules/ws/WsMessageServer.java

@@ -0,0 +1,122 @@
+package com.aizuda.boot.modules.ws;
+
+import com.baomidou.kisso.security.token.SSOToken;
+import jakarta.websocket.*;
+import jakarta.websocket.server.ServerEndpoint;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * websocket 消息服务端
+ *
+ * @author 青苗
+ * @since 2023-10-03
+ */
+@Slf4j
+@Component
+@ServerEndpoint(value = "/ws", configurator = WsEndpointConfigurator.class)
+public class WsMessageServer {
+    private static Map<String, WsSession> ONLINE_SESSIONS = new ConcurrentHashMap<>();
+    private static Map<String, String> TOKEN_SESSION_IDS = new ConcurrentHashMap<>();
+
+    /**
+     * 客户端打开连接
+     */
+    @OnOpen
+    public void onOpen(Session session) {
+        if (null != ONLINE_SESSIONS.get(session.getId())) {
+            return;
+        }
+        SSOToken ssoToken = null;
+        Map<String, Object> userProperties = session.getUserProperties();
+        if (MapUtils.isNotEmpty(userProperties)) {
+            String accessToken = (String) userProperties.get("accessToken");
+            if (StringUtils.isNotBlank(accessToken)) {
+                if (StringUtils.isNotBlank(accessToken)) {
+                    ssoToken = SSOToken.parser(accessToken, false);
+                }
+            }
+        }
+        if (null == ssoToken) {
+            try {
+                // 未授权关闭接入会话
+                session.close();
+            } catch (IOException e) {
+            }
+            return;
+        }
+        final String sessionId = session.getId();
+        ONLINE_SESSIONS.put(sessionId, new WsSession(ssoToken.getId(), session));
+        TOKEN_SESSION_IDS.put(ssoToken.getId(), sessionId);
+    }
+
+    /**
+     * 客户端发送消息
+     *
+     * @param session 连接会话
+     * @param message 客户端发送过来的消息内容
+     */
+    @OnMessage
+    public void onMessage(Session session, String message) {
+        if (Objects.equals("ping", message)) {
+            try {
+                this.sendText(session, "pong");
+            } catch (IOException e) {
+                // to do nothing
+            }
+        }
+    }
+
+    /**
+     * 关闭连接
+     */
+    @OnClose
+    public void onClose(Session session) {
+        WsSession wsSession = ONLINE_SESSIONS.get(session.getId());
+        if (null != wsSession) {
+            ONLINE_SESSIONS.remove(session.getId());
+            // 删除在线用户会话
+            // this.getUserSessionService().removeBySid(session.getId());
+        }
+    }
+
+    /**
+     * 通信发生异常
+     */
+    @OnError
+    public void onError(Session session, Throwable error) {
+        log.info("WebSocket onError sessionId={}", session.getId());
+        error.printStackTrace();
+    }
+
+    /**
+     * 发送文本消息给指定 userId 连接客户端
+     *
+     * @param userId 用户ID
+     * @param text   文本消息
+     */
+    public void sendText(String userId, String text) throws IOException {
+        String sessionId = TOKEN_SESSION_IDS.get(userId);
+        WsSession wsSession = ONLINE_SESSIONS.get(sessionId);
+        if (null != wsSession) {
+            this.sendText(wsSession.getSession(), text);
+        }
+    }
+
+    /**
+     * 发送文本消息
+     *
+     * @param session 连接会话
+     * @param text    文本消息
+     */
+    private void sendText(Session session, String text) throws IOException {
+        session.getBasicRemote().sendText(text);
+    }
+}

+ 27 - 0
src/main/java/com/aizuda/boot/modules/ws/WsSession.java

@@ -0,0 +1,27 @@
+package com.aizuda.boot.modules.ws;
+
+import jakarta.websocket.Session;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * websocket会话Session
+ *
+ * @author 青苗
+ * @since 2023-10-03
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+public class WsSession {
+    /**
+     * 用户ID
+     */
+    private String userId;
+    /**
+     * 会话Session
+     */
+    private Session session;
+
+}