From 63f1d4d1dcdbbfd82dc3594e4b8108acd1fbab86 Mon Sep 17 00:00:00 2001 From: ZhangAo Date: Tue, 14 Oct 2025 17:30:21 +0800 Subject: [PATCH] u --- .../link-backend/.idea/jarRepositories.xml | 10 +- code/link/link-backend/.idea/misc.xml | 2 +- code/link/link-backend/pom.xml | 10 +- .../link/api/controller/AuthController.java | 2 + .../controller/ColumnConfigController.java | 5 - .../com/link/api/controller/WebConfig.java | 2 +- .../com/link/api/util/PaginationUtils.java | 6 +- .../link/im/controller/CompanyController.java | 43 +++++ .../link/im/handler/ImMessageProcessor.java | 72 ++++++++ .../link/im/handler/ImWebSocketHandler.java | 16 +- .../link/im/manager/UserChannelManager.java | 38 +++- .../java/com/link/im/model/ImMessage.java | 12 ++ .../java/com/link/im/model/ImRequest.java | 82 +++++++++ .../java/com/link/im/model/ImResponse.java | 89 ++++++++++ .../java/com/link/im/utils/CommonUtils.java | 21 +++ .../main/java/com/link/im/utils/DbUtils.java | 41 +++++ .../main/resources/dbconfigs/G3HD.properties | 2 +- .../link/api/controller/AuthController.class | Bin 2295 -> 2295 bytes .../controller/ColumnConfigController.class | Bin 2896 -> 2896 bytes .../com/link/api/controller/WebConfig.class | Bin 1540 -> 1449 bytes .../com/link/api/util/PaginationUtils.class | Bin 20185 -> 20185 bytes .../im/controller/CompanyController.class | Bin 0 -> 2494 bytes .../link/im/handler/ImMessageProcessor.class | Bin 0 -> 3589 bytes .../link/im/handler/ImWebSocketHandler.class | Bin 4094 -> 4430 bytes .../link/im/manager/UserChannelManager.class | Bin 3262 -> 3991 bytes .../classes/com/link/im/model/ImMessage.class | Bin 1879 -> 2348 bytes .../classes/com/link/im/model/ImRequest.class | Bin 0 -> 2320 bytes .../com/link/im/model/ImResponse.class | Bin 0 -> 3054 bytes .../com/link/im/utils/CommonUtils.class | Bin 0 -> 681 bytes .../classes/com/link/im/utils/DbUtils.class | Bin 0 -> 3108 bytes .../target/classes/dbconfigs/G3HD.properties | 2 +- code/link/link-frontend/src/App.vue | 11 +- code/link/link-frontend/src/layout/index.vue | 97 +++++++++++ code/link/link-frontend/src/router/index.js | 48 ++++- .../link-frontend/src/stores/userStore.js | 7 +- .../src/stores/websocketStore.js | 164 ++++++++++-------- code/link/link-frontend/src/utils/axios.js | 37 ++-- .../link-frontend/src/views/auth/index.vue | 127 ++++++++++++++ .../link-frontend/src/views/home/index.vue | 67 +++++++ 39 files changed, 880 insertions(+), 133 deletions(-) create mode 100644 code/link/link-backend/src/main/java/com/link/im/controller/CompanyController.java create mode 100644 code/link/link-backend/src/main/java/com/link/im/handler/ImMessageProcessor.java create mode 100644 code/link/link-backend/src/main/java/com/link/im/model/ImRequest.java create mode 100644 code/link/link-backend/src/main/java/com/link/im/model/ImResponse.java create mode 100644 code/link/link-backend/src/main/java/com/link/im/utils/CommonUtils.java create mode 100644 code/link/link-backend/src/main/java/com/link/im/utils/DbUtils.java create mode 100644 code/link/link-backend/target/classes/com/link/im/controller/CompanyController.class create mode 100644 code/link/link-backend/target/classes/com/link/im/handler/ImMessageProcessor.class create mode 100644 code/link/link-backend/target/classes/com/link/im/model/ImRequest.class create mode 100644 code/link/link-backend/target/classes/com/link/im/model/ImResponse.class create mode 100644 code/link/link-backend/target/classes/com/link/im/utils/CommonUtils.class create mode 100644 code/link/link-backend/target/classes/com/link/im/utils/DbUtils.class create mode 100644 code/link/link-frontend/src/layout/index.vue create mode 100644 code/link/link-frontend/src/views/auth/index.vue create mode 100644 code/link/link-frontend/src/views/home/index.vue diff --git a/code/link/link-backend/.idea/jarRepositories.xml b/code/link/link-backend/.idea/jarRepositories.xml index aa4ad020..05657f84 100644 --- a/code/link/link-backend/.idea/jarRepositories.xml +++ b/code/link/link-backend/.idea/jarRepositories.xml @@ -21,15 +21,15 @@ - + \ No newline at end of file diff --git a/code/link/link-backend/pom.xml b/code/link/link-backend/pom.xml index 676dcab2..3e384b0f 100644 --- a/code/link/link-backend/pom.xml +++ b/code/link/link-backend/pom.xml @@ -58,11 +58,11 @@ spring-boot-starter-logging - - org.springframework.boot - spring-boot-devtools - true - + + + + + org.junit.jupiter diff --git a/code/link/link-backend/src/main/java/com/link/api/controller/AuthController.java b/code/link/link-backend/src/main/java/com/link/api/controller/AuthController.java index 4842e6c5..af1e3a4c 100644 --- a/code/link/link-backend/src/main/java/com/link/api/controller/AuthController.java +++ b/code/link/link-backend/src/main/java/com/link/api/controller/AuthController.java @@ -24,6 +24,7 @@ public class AuthController { response.put("message", "Missing parameters"); return response; } + UserInfo userInfo = UserRepository.getUserPasswordById(orgId, userId); if (userInfo != null && userInfo.getPassword().equals(password)) { String token = JwtUtil.generateToken(userId, orgId); @@ -33,6 +34,7 @@ public class AuthController { } else { response.put("message", "Invalid credentials"); } + return response; } diff --git a/code/link/link-backend/src/main/java/com/link/api/controller/ColumnConfigController.java b/code/link/link-backend/src/main/java/com/link/api/controller/ColumnConfigController.java index f8520309..2f862779 100644 --- a/code/link/link-backend/src/main/java/com/link/api/controller/ColumnConfigController.java +++ b/code/link/link-backend/src/main/java/com/link/api/controller/ColumnConfigController.java @@ -19,10 +19,7 @@ public class ColumnConfigController { */ @GetMapping("/api/columns") public Result getColumnConfig(@RequestParam String tableName, HttpServletRequest request) { - - try { - String orgId = UserContext.getOrgId(); System.out.println("api/columns:"+orgId); List> data = JsonUtils.queryColumnConfig(orgId, tableName); @@ -32,10 +29,8 @@ public class ColumnConfigController { } } - @GetMapping("/api/columnsEdit") public Result getColumnEditConfig(@RequestParam String tableName, HttpServletRequest request) { - try { String orgId = UserContext.getOrgId(); List> data = JsonUtils.queryColumnEditConfig(orgId, tableName); diff --git a/code/link/link-backend/src/main/java/com/link/api/controller/WebConfig.java b/code/link/link-backend/src/main/java/com/link/api/controller/WebConfig.java index bf488458..c6acaf20 100644 --- a/code/link/link-backend/src/main/java/com/link/api/controller/WebConfig.java +++ b/code/link/link-backend/src/main/java/com/link/api/controller/WebConfig.java @@ -8,7 +8,7 @@ public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") - .allowedOriginPatterns("http://localhost:5173", "http://localhost:8089","http://localhost:5174") // 前端地址 + .allowedOriginPatterns("*") // 前端地址 .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true); diff --git a/code/link/link-backend/src/main/java/com/link/api/util/PaginationUtils.java b/code/link/link-backend/src/main/java/com/link/api/util/PaginationUtils.java index cccabf46..76a96f9b 100644 --- a/code/link/link-backend/src/main/java/com/link/api/util/PaginationUtils.java +++ b/code/link/link-backend/src/main/java/com/link/api/util/PaginationUtils.java @@ -3,11 +3,15 @@ package com.link.api.util; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.link.im.manager.UserChannelManager; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; import java.sql.*; import java.sql.Date; import java.text.SimpleDateFormat; import java.util.*; +import java.util.stream.Collectors; public class PaginationUtils { @@ -470,5 +474,5 @@ public class PaginationUtils { } } -} +} diff --git a/code/link/link-backend/src/main/java/com/link/im/controller/CompanyController.java b/code/link/link-backend/src/main/java/com/link/im/controller/CompanyController.java new file mode 100644 index 00000000..9172b256 --- /dev/null +++ b/code/link/link-backend/src/main/java/com/link/im/controller/CompanyController.java @@ -0,0 +1,43 @@ +package com.link.im.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.link.api.util.DruidDataSourceManager; +import com.link.api.util.PaginationUtils; +import com.link.api.util.Result; +import com.link.api.util.UserContext; +import com.link.im.manager.UserChannelManager; +import com.link.im.utils.DbUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@RestController +@CrossOrigin +public class CompanyController { + + @GetMapping("/api/loadCompanyList") + public Result getCompanyList(){ + try { + String orgId = UserContext.getOrgId(); + String sql = "select b_id,b_name from b_user where b_canuse = '1' "; + + List> maps = DbUtils.queryForList(orgId, sql); + + for (Map map : maps) { + String bId = map.get("b_id").toString(); + + map.put("_online",UserChannelManager.isOnline(bId)); + } + + return Result.ok("成功",maps); + } catch (Exception e) { + return Result.fail("公司列表查询异常:" + e.getMessage()); + } + } +} diff --git a/code/link/link-backend/src/main/java/com/link/im/handler/ImMessageProcessor.java b/code/link/link-backend/src/main/java/com/link/im/handler/ImMessageProcessor.java new file mode 100644 index 00000000..873105e3 --- /dev/null +++ b/code/link/link-backend/src/main/java/com/link/im/handler/ImMessageProcessor.java @@ -0,0 +1,72 @@ +package com.link.im.handler; + +import com.link.api.util.UserContext; +import com.link.im.manager.UserChannelManager; +import com.link.im.manager.WriteManager; +import com.link.im.model.ImRequest; +import com.link.im.model.ImResponse; +import com.link.im.utils.CommonUtils; +import com.link.im.utils.DbUtils; +import io.netty.channel.ChannelHandlerContext; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.sql.SQLException; +import java.util.List; +import java.util.Map; + +@Component +public class ImMessageProcessor { + public void process(ChannelHandlerContext ctx, ImRequest request) { + String type = request.getType(); + if (type == null) { + WriteManager.writeToChannel(ctx.channel(), "消息类型不能为空"); + return; + } + + switch (type) { + case "ping": + handlePing(ctx); + break; + case "login": + handleLogin(ctx, request); + break; + default: + WriteManager.writeToChannel(ctx.channel(), "未知的消息类型: " + type); + break; + } + } + + private void handlePing(ChannelHandlerContext ctx) { + WriteManager.writeToChannel(ctx.channel(), ImResponse.success("pong",null)); + } + + private void handleLogin(ChannelHandlerContext ctx,ImRequest request){ + String fromUserid = request.getFrom(); + UserChannelManager.addUser(fromUserid,ctx.channel()); + String orgId = request.getOrgId(); + + System.out.println(orgId); + + // 1.通知给好友 + String sql1 = "select b_frienduser_Id from IM_Friends where b_user_id = '" + fromUserid +"'"; + try { + List> maps = DbUtils.queryForList(orgId, sql1); + for (Map map : maps) { + String bFrienduserId = map.get("b_frienduser_Id").toString(); + if (CommonUtils.isNotEmpty(bFrienduserId)) { + boolean friendOnline = UserChannelManager.isOnline(bFrienduserId); + if(friendOnline){ + + } + } + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + // 2.通知给群组 + + // 3.通知给全部(测试用,先留,后面会删) + } + +} diff --git a/code/link/link-backend/src/main/java/com/link/im/handler/ImWebSocketHandler.java b/code/link/link-backend/src/main/java/com/link/im/handler/ImWebSocketHandler.java index b995395e..9360461b 100644 --- a/code/link/link-backend/src/main/java/com/link/im/handler/ImWebSocketHandler.java +++ b/code/link/link-backend/src/main/java/com/link/im/handler/ImWebSocketHandler.java @@ -5,6 +5,8 @@ import com.link.im.manager.GroupManager; import com.link.im.manager.UserChannelManager; import com.link.im.manager.WriteManager; import com.link.im.model.ImMessage; +import com.link.im.model.ImRequest; +import com.link.im.model.ImResponse; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; @@ -17,6 +19,7 @@ import org.slf4j.LoggerFactory; public class ImWebSocketHandler extends SimpleChannelInboundHandler { private static final Logger logger = LoggerFactory.getLogger(ImWebSocketHandler.class); private static final ObjectMapper MAPPER = new ObjectMapper(); + private final ImMessageProcessor imMessageProcessor = new ImMessageProcessor(); @Override public void handlerAdded(ChannelHandlerContext ctx) { @@ -35,14 +38,15 @@ public class ImWebSocketHandler extends SimpleChannelInboundHandler userChannelMap = new ConcurrentHashMap<>(); /** - * 添加用户与Channel绑定 + * 添加用户与Channel绑定,如果已有绑定,先移除旧的 */ public static void addUser(String userId, Channel channel) { - if (userId != null && channel != null) { - userChannelMap.put(userId, channel); + if (userId == null || channel == null) return; + + // 如果已有旧的 channel,先移除 + Channel oldChannel = userChannelMap.put(userId, channel); + + if (oldChannel != null && oldChannel != channel) { + try { + if (oldChannel.isActive()) { + oldChannel.close(); + } + logger.info("用户 {} 已有旧 channel,被替换: oldChannelId={}, newChannelId={}", + userId, oldChannel.id().asShortText(), channel.id().asShortText()); + } catch (Exception e) { + logger.error("关闭旧 channel 异常: userId={}", userId, e); + } } + + logger.info("用户上线: userId={}, channelId={}", userId, channel.id().asShortText()); } /** * 移除用户绑定 */ public static void removeUserById(String userId) { - if (userId != null) { - userChannelMap.remove(userId); + if (userId == null) return; + + Channel removed = userChannelMap.remove(userId); + if (removed != null) { + try { + if (removed.isActive()) { + removed.close(); + } + } catch (Exception e) { + logger.error("关闭 channel 异常: userId={}", userId, e); + } + logger.info("用户下线: userId={}, channelId={}", userId, removed.id().asShortText()); } } @@ -60,5 +83,4 @@ public class UserChannelManager { Channel ch = userChannelMap.get(userId); return ch != null && ch.isActive(); } - } diff --git a/code/link/link-backend/src/main/java/com/link/im/model/ImMessage.java b/code/link/link-backend/src/main/java/com/link/im/model/ImMessage.java index 12d370d5..975132d5 100644 --- a/code/link/link-backend/src/main/java/com/link/im/model/ImMessage.java +++ b/code/link/link-backend/src/main/java/com/link/im/model/ImMessage.java @@ -24,6 +24,18 @@ public class ImMessage { return type; } + @Override + public String toString() { + return "ImMessage{" + + "type='" + type + '\'' + + ", msg='" + msg + '\'' + + ", from='" + from + '\'' + + ", to='" + to + '\'' + + ", content='" + content + '\'' + + ", timestamp=" + timestamp + + '}'; + } + public void setType(String type) { this.type = type; } diff --git a/code/link/link-backend/src/main/java/com/link/im/model/ImRequest.java b/code/link/link-backend/src/main/java/com/link/im/model/ImRequest.java new file mode 100644 index 00000000..6aad21af --- /dev/null +++ b/code/link/link-backend/src/main/java/com/link/im/model/ImRequest.java @@ -0,0 +1,82 @@ +package com.link.im.model; + +public class ImRequest { + private String type; + private String from; + private String to; + private String content; + private String orgId; + private long timestamp; + + // 无参构造 + public ImRequest() {} + + // 全参构造 + public ImRequest(String type, String from, String to, String content, long timestamp,String orgId) { + this.type = type; + this.from = from; + this.to = to; + this.content = content; + this.timestamp = timestamp; + this.orgId = orgId; + } + + public String getOrgId() { + return orgId; + } + + public void setOrgId(String orgId) { + this.orgId = orgId; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getFrom() { + return from; + } + + public void setFrom(String from) { + this.from = from; + } + + public String getTo() { + return to; + } + + public void setTo(String to) { + this.to = to; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + @Override + public String toString() { + return "ImRequest{" + + "type='" + type + '\'' + + ", from='" + from + '\'' + + ", to='" + to + '\'' + + ", content='" + content + '\'' + + ", timestamp=" + timestamp + + '}'; + } +} \ No newline at end of file diff --git a/code/link/link-backend/src/main/java/com/link/im/model/ImResponse.java b/code/link/link-backend/src/main/java/com/link/im/model/ImResponse.java new file mode 100644 index 00000000..f6e695f1 --- /dev/null +++ b/code/link/link-backend/src/main/java/com/link/im/model/ImResponse.java @@ -0,0 +1,89 @@ +package com.link.im.model; + +public class ImResponse { + private String type; + private int code; + private String message; + private long timestamp; + private Object data; + + // 无参构造 + public ImResponse() {} + + // 全参构造 + public ImResponse(String type, int code, String message, long timestamp, Object data) { + this.type = type; + this.code = code; + this.message = message; + this.timestamp = timestamp; + this.data = data; + } + + public static ImResponse success(String type, Object data) { + return new ImResponse(type, 200, "success", System.currentTimeMillis(), data); + } + + public static ImResponse success(String type, String message, Object data) { + return new ImResponse(type, 200, message, System.currentTimeMillis(), data); + } + + public static ImResponse error(String type, String message) { + return new ImResponse(type, 500, message, System.currentTimeMillis(), null); + } + + public static ImResponse error(String type, int code, String message) { + return new ImResponse(type, code, message, System.currentTimeMillis(), null); + } + + // Getter & Setter + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + public Object getData() { + return data; + } + + public void setData(Object data) { + this.data = data; + } + + @Override + public String toString() { + return "ImResponse{" + + "type='" + type + '\'' + + ", code=" + code + + ", message='" + message + '\'' + + ", timestamp=" + timestamp + + ", data=" + data + + '}'; + } +} \ No newline at end of file diff --git a/code/link/link-backend/src/main/java/com/link/im/utils/CommonUtils.java b/code/link/link-backend/src/main/java/com/link/im/utils/CommonUtils.java new file mode 100644 index 00000000..f48ea696 --- /dev/null +++ b/code/link/link-backend/src/main/java/com/link/im/utils/CommonUtils.java @@ -0,0 +1,21 @@ +package com.link.im.utils; + +public class CommonUtils { + /** + * 判断字符串是否为空(null 或 "") + * @param str 待判断的字符串 + * @return true 如果字符串为 null 或 空串 + */ + public static boolean isEmpty(String str) { + return str == null || str.trim().isEmpty(); + } + + /** + * 判断字符串是否非空(不是 null 且不为空串) + * @param str 待判断的字符串 + * @return true 如果字符串非空 + */ + public static boolean isNotEmpty(String str) { + return !isEmpty(str); + } +} diff --git a/code/link/link-backend/src/main/java/com/link/im/utils/DbUtils.java b/code/link/link-backend/src/main/java/com/link/im/utils/DbUtils.java new file mode 100644 index 00000000..dd6564c2 --- /dev/null +++ b/code/link/link-backend/src/main/java/com/link/im/utils/DbUtils.java @@ -0,0 +1,41 @@ +package com.link.im.utils; + + +import com.link.api.util.DruidDataSourceManager; + +import java.sql.*; +import java.util.*; + +public class DbUtils { + + /** + * 根据 SQL 查询数据 + * + * @param orgCode 数据源组织编码 + * @param sql 查询 SQL + * @return List>,每一行作为 Map + */ + public static List> queryForList(String orgCode, String sql) throws SQLException { + List> resultList = new ArrayList<>(); + + try (Connection conn = DruidDataSourceManager.getConnection(orgCode); + PreparedStatement stmt = conn.prepareStatement(sql); + ResultSet rs = stmt.executeQuery()) { + + ResultSetMetaData metaData = rs.getMetaData(); + int columnCount = metaData.getColumnCount(); + + while (rs.next()) { + Map rowMap = new LinkedHashMap<>(); + for (int i = 1; i <= columnCount; i++) { + String columnName = metaData.getColumnLabel(i); + Object value = rs.getObject(i); + rowMap.put(columnName, value); + } + resultList.add(rowMap); + } + } + + return resultList; + } +} diff --git a/code/link/link-backend/src/main/resources/dbconfigs/G3HD.properties b/code/link/link-backend/src/main/resources/dbconfigs/G3HD.properties index 7506e981..c25db874 100644 --- a/code/link/link-backend/src/main/resources/dbconfigs/G3HD.properties +++ b/code/link/link-backend/src/main/resources/dbconfigs/G3HD.properties @@ -1,4 +1,4 @@ -url=jdbc:sqlserver://127.0.0.1:1433;databaseName=G3HY2025 +url=jdbc:sqlserver://192.168.1.189:1433;databaseName=G3-Link;encrypt=false username=sa password=g3soft driver=com.microsoft.sqlserver.jdbc.SQLServerDriver \ No newline at end of file diff --git a/code/link/link-backend/target/classes/com/link/api/controller/AuthController.class b/code/link/link-backend/target/classes/com/link/api/controller/AuthController.class index e69a9c6d578582bb5f52310a8dbcf575d38ea331..393969b2398931a535574d5acfdb0b12128da3a7 100644 GIT binary patch delta 45 zcmew^_+4i_!0{}U04Y&XR diff --git a/code/link/link-backend/target/classes/com/link/api/controller/WebConfig.class b/code/link/link-backend/target/classes/com/link/api/controller/WebConfig.class index 219b96650ce338a2cfcdc512e68bf466037b93bb..3b2514fa572b0f8ee824bd60fa5e323e4ec92653 100644 GIT binary patch delta 416 zcmYk1J5K^p5QV?H?6TKodAKSnzTXJyqT&lw3T*EGieTMl!)DW_+&+C)C656`%r6vdwp_N^QDm;_cXj`|-fZ~6?agJgp-?j3b=jlx zcy8pARVps~5gc&%`ea-ft+ZrO^h+>9Sgr~VO6LnJVXN?lYIny&g7L0sla<=-EilZ8 zbc#`OawK~EfF%_i7d@jx%>2L%W1basi0fW$2chZdAZ%G)Ul7JUCkSmXAq(!m<)qmm zb@peT6MJw-laPEaRg%<5aU@C=l~8#k+z}&+88c4gfSs=*?7Sr4G?X(?Ts%$ zNQ|%GB{3-pi9Uc2Wmvl<;f9;_ul4V>_WIVZ-1lJj-~a2sK#{7?1Evhme5QG>d0~hQ zGlp58IbIrK!@Oa^@XD}gy`o{s>T4U8H6=}1Q@PyushR@}Dl%~tY2E~^@K(`#R6DE1 z_1f`%oHV{39`7p#4jPTqN*vcuzSQamC*K;CrG@3i`~1>;seGR={YA~YfcLD*!cW!D z3U_DwV$>PyU$d5q0@hg#_`pX+;rzVt`^Z^wnxR~)=$F$SQNfisBvmY`M7`(=b=iJ? zC^6eMHIF0@It>gnB9&s4G5N#}|G<+Bwq`=7MJD?PH_e1zlDc6wNxe`fEsU{lGZS`M z+qZTuNwZ;)q&n=Dh90*wmY2NOZPB-PhcPT5;0s2MvVc_SW`iC!$+yu4JL_-J1cb*t Y5yYo55<4X#^BZ~y=R diff --git a/code/link/link-backend/target/classes/com/link/api/util/PaginationUtils.class b/code/link/link-backend/target/classes/com/link/api/util/PaginationUtils.class index 1ec6d0f654e5927b024411ace53b56553dd74922..d5435facbf9a49a7e37c5773c9cbf40211f7b2ab 100644 GIT binary patch delta 1154 zcmWlXeN2~S7=^F%A{c5Dkr9)eMkycp1!<8eiJM>%qEm*FG}B3tfheM)BUT}{q2egi zQo%A~Wo_io*_dXzOsc6kC5_OPe~{}#x5}-ov`%~exX$yud+z%==e>LPs&}tCR>mqS zR2iE&pe~6)BooCrGDxL>$<*>Fo0vwUPqs0g*Udd-(r&&-7KfS1ab|IXY|fCwC1!J# zCp4IB#h5dgqbJGNeCDdwy1_ied>v(hzM?=EDO5j;f+7lo;t^y7CEhffyUi~1oOy?$ zP)>Z|z=9Hzc+MSHu$T&#QqFQ#QfXFs)v<;Ks_C%qH2Yb{4c7A)HHu_|3e6(QwamK7 z+-06(W9avU{P4PZB5iayTZy;P1e!=>I}g%K8aqg*#fEptut|Kkf5Q_b)m*{RP%vFk_QN!#pnl{C9Q1@_1aa^|USDc1vgjc-AYNV!UlwLL) zG+0g6N6nMwZ-GXK+9v0QO;6-5+Zd{3VwJ*u8b^x8^N=PmO%pv{lRQeP?wv-Nrcmwk zje3N7rL$R6Ly76>VFg)4Y9_;#?W=Q`qGC)3RGMCNIMr?7w*tT&q>PslMXn2gh|()$XbX>Nn@cs>=yn#V*gD@{;zjRc-9jK0ec%oVD)Je!kNIZmC`Ot3~6?g?d+|I;a)uP@_WX^g5wq zI_=oI^|8J+ujo@IsX~#jrSi6INoD>1fz$>sGVf{93)C_h%R1t<>o{>#xswe((EyFXuU3wd$%>V^N%+-Q1)8 zq$$>%#azuJQwy1=QtNW_0Q1$%0-YdB->^_UWCu%G7~~FNYLM?kwYkeYZk{r)vN)6; zpA(Ut=gIQj=~3=y1$h+m2rF4*uBV7Ht8&)TXx(J?`0E88=MqmSl49kUODWVt)|KW? z^D|09eeL?JI8bDjRs+_U!UM zcXP=4WAk_F=%rq;M+137x#n`JRA^me)|qYWr5JdMXx~ktG}Mw@9A4JIAoerV8{EtR zMiL^O4_H7mt2s&ut!~}sU%z#;PVd;oG4nWQ?fE%3?&cqUZl0!7-*Y~QpfiXuQ~B-xVK#*hjV%Z->-7+Sdx(D>g^P~CB|C7LDO_Q?cS@5; zSKucNCrACrRRqgz?`m_cqN&g|)M$YBAH+Kv%mK&XtaXoKb*v^+W z5vsIqHjkNq1{xZwpOhIka~S=U$Ur3#r{Ubeu8x0(}F;PQpstrZkmKWVljA)ono|!8G3;`wAH05yy!OW({`I( z$%Cq5m8vPx4xV#{U(zmiYB#N_<)n3o>iI!?xTw7vrYem#m*_1OXrET9K^1yeO?po) zdS7iitahKiGtcRWep#ju^@l!DuUZ0hG>B4b5UWpvRG-t$_4*O{Ih_4;~fR6tFGeH&S diff --git a/code/link/link-backend/target/classes/com/link/im/controller/CompanyController.class b/code/link/link-backend/target/classes/com/link/im/controller/CompanyController.class new file mode 100644 index 0000000000000000000000000000000000000000..fd04807c4be86ea7156931c48ec0db388dba20d4 GIT binary patch literal 2494 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 literal 0 HcmV?d00001 diff --git a/code/link/link-backend/target/classes/com/link/im/handler/ImWebSocketHandler.class b/code/link/link-backend/target/classes/com/link/im/handler/ImWebSocketHandler.class index 185fef10baac96f24902ac785dbd3dc1b150ea73..fecb8e7d0be1dca6b72e0e25a2354e5217cf5b60 100644 GIT binary patch literal 4430 zcmbtX`&S(08Gb%knB8Gaa7jWW7dOFh$FUY`n7Kj`WE&Cbp)kVS0woZWBcyS(r7 zKJV?@fBfg;zX0gK$1+-RQo^@ol;LCujrdLo-^KSNXfi5sGKf(z#;cG3ufiCTRTzgZ z<|brJ!jO@IDZ|245KqWB09y!2OK@Z~BO}C4OE@JaXM&im!uN4Hh$mIR{y2Qr?< zGZKCn#Ir)lj|8CSf;b~~J}=?NGM#)u!cS!U6hD)2R){6QFEUgbRx-(LhH$@SC)Knu z)-j>>yK_-m9q1Yy>=|Nc?~hw4bxcb;+@47p>Vy`bOk1X!&>U@4HxufkqZ2&t3}{nR z+@^zJJvG47X)VbIZ7WVs7O7}*g&4Xysp=_pTr(2}x7EINJB8vyx~V&d8OocQM;HR# zR)RBZ?bl6yJd+yb_OLc;&*C#;3^?O@y0p3zd^BdoC%JPhhk(>69+<8~ zf+rYSoBDN2HM!%=s&QH}xuJIFM(<3wWjcJui3)J#ac4$o+vE|3>H+SITZuu<)>51Z zC9GRK07E=wO$!K$r+0{JiF+8vHZ8sY!xO2-iMzNu?l@EGDL$HZO`lPR3BJ!nuR!7& zBia0k3{N`@dscZXMM4bn)lhDkIAcWr_WlWNT2l?pOsYLIaX#hfmPu>nsdSPd`hQ?y zxGm;TkWoarUZwCSZ)ein?wRJMGi>W3Yz0#fZl0O~*j%{g+{dTM+HjF2-XeqS+e9wf z%JT+xYnkLYo$35vG!Kv4)+rIJuJVewMmDV`vao|8cqmT!;$}i9rYB9!$=I}gvcPOU zK4W@n%HUp<_L-wr#!Ps@dZ@7b3&+G^@}?ZKGIpHz>LTN6`%-?`c=GQSyfWM|lrbGW z#YgnCPWE+~rsZfNAgGZ2oJ@(0Q^31ftMpMy+1-YgPIF?mrGPJQjc13a;I919sDgX( zWrhY(jXuASD7Xh-lJK&EU*MMnz2N_tW9w$}NJcjjqWEq9=bL9D%a`9>d12wx_dmG# z*SA()y6`|m!LRUZ39l%473UqFA>lU)-o$wYZ{fUz-zs<;??`x8!F!mKa6!TE zFefCQKUqpP{CJ+7CwsZD?oM>(S*F ziIrDATzT>G^4xQuzWn_1+?AWxE=c&3f+ul_p@Q4CWlOj$-anA=p@J)zXK3_mRz5I< z1Fejs_EBx71a!EYZlvn7xBvXv{9Gi*&CTqYm2=sD-#EK`=IX!BT@r>}#We+e_!>jR zb6#Zm#&d+)KybdG>Lb=5|E@p3Q>mwm1`+V!b;IvO{#b8O+}?D z7st`&byHqw)4_(%0p+za2FWv%VRut=$&@6CJ4;DqDANhy-VKiTB}hPBDFJbgZ1|`TSVT7a21p*fUHb^j3UP+g?>eQkqG36e`C;nmE%1rr!YYT#VZ7bDf816b%#DKV5FoekW zi4o1nP~E-%7G@MN;}+9Zao~3`qQbp`F&rz6*{ihqd(DQuUtD^s*WMPXjBkvv(ALEH z-K*&FFBj=4Y8Pbf(T!#KVZV_S3X@wp;e@R_+;_s3Q{r{l@*S&|CMjA(c+m}_B5qg$ z^!^RvzBeN}H$ghMv1OjvTPvzvOM0&scPx9BA|=V4+^wDAK(U5=T&z7wNbW3^gFr#YHawTP|xhw36M zw{H$hm`nvC2%(-xJ3vYriZDfeOwsk20;0)zZ2X-t2|+!Cpe>6q**lLK@f6;c#r99I z?Rd*I1X|m(*l`j2j?ZIf_>T4^)RiL#KD>*Tb}wSjBKBT!Nv|g)wIo)!Wj{F*gYbfoNfcfx1=m5+`w&48wnTEeeB7Nr?oK>{eh+s5Wd|!TK>GuPwYC`kBQ(iy z9FKZF(rJr;UoXh7&(U6p)3znt6%fu4Pz$lT#XWL_BDI^maI^?ny^pNkM^=wPe4W&Z z$n{7Zq;xw!sz{3d;6dj1Ap!s890 F{{n#|#UTIy delta 1727 zcmah|+fy5L6#jOT@JlvJ4GB$wv=E8C0KupYwp^OV+M-mTDy(K4OeYjs>p9UMco!Bqs18zKshulb@$BkY*ENw{*2awY62>LYi zOyGO?)tt@gIs)5)>40&jbN z;&37|kQhuwy3!+kgQ;c(#a-z{@4=45@a{yiqq}<=-x!{PXnqACr>;AL_V>kBo()j^dB;iH_TFyNdFnql+ABk zbQ`NZLnEok=)i$!f24gVl}fUGj^xP55M`rL;;uCI=qEBkP8U#7AsJDtkX2JUp@1Fb zyN+KGN`=x6wJdZUoHi-Wuax^E+A-T4mOBpVkUkSnYdRlAXW8whu zEBPl#PIVlD97>EM@^O}%(Maw=xXHQMtRif&%}5?Ox>9Sw7IxUdv@vWYP@MnEq5i|n zc7a<2wh7!SupNrP4jrxhZ6~u*a{w;Y96$?=irn&Cz2_)xw8g^*-)!JUb|A`18(7^Y(rq+r4q9OOX~gs8#nFbHEHBPHcBqXG zw95LI70^bbf;-SoUpcToy2t`T(+GH1TSF;qZFnAQ?8tE~Z0-s(B*w99<*LM&Pz&X$ zg^;nk>UCigk%v4rTd>JU|N{ zCw8GW$wI9Imlq27yumqKJA=R_RP!`KGw?_d#oRdS7`=@2u_JfVuHr7-ZOSg}rj#`9 RrQFL$qrL|(;huG_e*sds2(bVF diff --git a/code/link/link-backend/target/classes/com/link/im/manager/UserChannelManager.class b/code/link/link-backend/target/classes/com/link/im/manager/UserChannelManager.class index 6dd28505c47e1b284436256293c13b4fd0622226..2281d39aaea5f57da427732f0478b5e24ddbc7fd 100644 GIT binary patch literal 3991 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 P{N|fHE^nXUNe2D}K&P<1 diff --git a/code/link/link-backend/target/classes/com/link/im/model/ImMessage.class b/code/link/link-backend/target/classes/com/link/im/model/ImMessage.class index 222c106424cdf46291dd6bc80dd6cd00dca24b10..158194ca661e2b370b603eb0e006e8c51703adee 100644 GIT binary patch literal 2348 zcmb`J-EJFI5Xb+=-d#J+lDN)Cn>L}QK;pPg-O>X6NJ86`kOZeqX;q0Eq>Zz9k)sT~F2nrxCvNdI}4TX5iP` zUgw*-=huB^XxF#>UANbBS}u9Ms+^_#zdSoaUEFoUm%+iF({+3|bh~8AwcPNzF41&l z`Rri|`7nqI#=X6hy)!AKa)8BbyHZq(TeTtiH+6v}I^yA*YtL5L5_ak|D*A4PXTedo>27+`g^9SV*QD<@-o|Acvp8?#l86f;=0sc+F^>g> z^T`Q$c;vMY+%C%&XZv2JX>G~IV>}VD$D-8A9HX@*R;pH(Q4-UtmBT=c@mhH>QnIq? zJ^^2o_@_3OQBk<`AA`a3PsyI_zjB)#Ps=$xbUOzMD`}lL)5pP8IAimbjk9eY{`1c0 zKjR&~!x=tU3mGXUt&EgXYgSWh7F282S!>podJ;-{Kw0MMCbgnAt8buwrIBH3pg;|b z!+?z|wCrdT-o<-dNrLyeqfGlJWckQ}MM+S-+DE2Z>myrT>BFeb8GUA+GyAZrvsNFu z(-W@fs0p4hi7^y0fpeHbiK8$@xM}8}(I;;qC)o!lizU|;o`&7%LTyF)oRyic80^OK(LEBX&$g4l{O4Mh+`>6eCAAcASeGxnq=yKQQSr zrjnowL!htY)>UzJ(7JVMX|tuA?slc@{B~48Ot$sZ7}<)1y?To9Rs1B-0j|wns3@Lyno` zaVq{N@*!oJcAevQ@)&bJ(=q%Q8>vhm;bZQS;B`Jm`7l`3%m4cPL^Hlerl%vAn*#g{OkVwjgsoXa>m!E>-6aaA`9-f^6ST(DYxOg@=B;?;SQ|p{V9H4!k(v?~6@6 zISyKaCU$C3=r)4pdp8K(5DE?VWw`6NT3+1;&)e!%%Kx>q&D5n`KRJ$$-gvPW`iUQd z$*TLwzO-nlI(M}g4zw;03@h+h)r7<@J89ymT(Ibtbd_&+Rt6V>#Rlsk7x2A#>2?8| z>4(iHyY9nq_;2&I?xtRq4HfpHv$*DO1#EpW^>T@SwoL;xXj6f%+tkOw=5T{Ukwc04 z1&#gVkHD|)(7Zl8@oNd)!EEI*geGaMjFrZ^GDBKssLPC^GNTZ3kuZfch&Y6=6=Z>I zINwNomB)q=3y>pR(;+&7mEAc+H)#}KEKtS?7WhOtpb%A9z?pB8<}9=+=Pb5KcglJj z%4MTXrZZx;$?8^^mbZ$i(1%7!!0HDS>kiT+lxJyJR(?Sit6x;+6}QXC6?Ri{R%Gir zxyQ}xa>eZ%a>b3CbPMaUiR*-m+!Em@upfl-+|(G2BXUzNa>tc((!O9r0}Y$ao1;oc zCg}EsBOA!A}SBBW2>57j%!Rh@BbNu;Q10PS$5E t{+#irN~Sq-aEdQ);MMbcGo8u5UUg^2pMmLl4<>$#DN_j&g63uMh2Jm4=+OWG diff --git a/code/link/link-backend/target/classes/com/link/im/model/ImRequest.class b/code/link/link-backend/target/classes/com/link/im/model/ImRequest.class new file mode 100644 index 0000000000000000000000000000000000000000..c364c4273d1dfa089c2f064fa91bef5fcedab7f0 GIT binary patch literal 2320 zcmbW1ZF3V<6vzK}vri3c8$w$Qr6r)WX-J7EpcN8@N~N(-Mi^#%2J z`~rRw-#DW)q|E4yAHWag@ISj@Q#Tu5_#)@td-j}jf9K)m?|=UI3&3?e%3~JmQ#gZ< zMcfi`TSP-dQ^Y40KF!0%x{W)s-W9Q7<1-ugY;4;2Tom_3Y*}bo_(DMse>m_IirX*T z@7;RG?d{hehkd`de~aI~=?7f}EesT_cF+sGUZ`LM{r#;yg{jc*dWWIgJy1|B1@pGw z^TVb>uCn@+&KtoV1J7*xJ@4UBch~Dbad$h>RYBYBJazlNoQFz1eC{7AEN-`hZoT98 zzODP+dY2J8^{wug-gig5i9}yl#yS6=rZw&+)P)CL_&nHq@_BMU##B@^HtI~Z3i&%0nhj|BQMZ7JdBw_*Q9h}3w z!rAnk+&%I;dtRTVOQQWKGu2peu#E>I9ulEemSJkFa87FFFp$luT6s8Ba&oACL*5Xt z9S1kT4-5Y>61;W9v$OlcYjZ$N_u#k<#HcfD;?w z65ioSoZsbT;`|G8eB8jYI9IO>pjB%F}}=+%-wVAOeI0JB=M2CzoaFGXH6&{4n? zPGcHHrgMhPpJm)RTqdyGd>@uXAKolGNmX!{qhz&cC+X&*og}PBJ4w}uc9Oish(O7t zJLm|G8jt)f@OO?s#egf6|E|bOSikY3hB9Sbky2(wDp(y>q{&sNOsU3+jPsFpkxWZb zg!$Mi$;^~-CxCDoYRFX0WYW@17c-fzWHV`_F2qci$+SFyDX#XANouE1%Vf%>nN~BI z*0Py$X{I$YU7f%rL!Q)0dZ}od)jK`SbS;yqp3S7EnQoBj<^(30z=TO=OX0mtCL_(X zp2>7Ao5@HsHOSPQz$9-WVUk%_cpo27#xt&QC$Ih`%wM@VIpcRSnXcmot;D&>*Cy}H ztX|;TPBYyj)8+&wd6fy1ygeoVxkIL(8B~~_{R#W4SC}g1k8z588kfS#F`VLCu_Bi! prelSkC}v`XkthnW!aT<5!cUBGjAFV4trO`!OaA0OB#DNPUIS(pS55!` literal 0 HcmV?d00001 diff --git a/code/link/link-backend/target/classes/com/link/im/model/ImResponse.class b/code/link/link-backend/target/classes/com/link/im/model/ImResponse.class new file mode 100644 index 0000000000000000000000000000000000000000..2fbd0c74d2d9c1de1210b2dd6d3c12c21b5d2284 GIT binary patch literal 3054 zcmbVOYi}Dx6g}(5uI+5&*m<=iBn}1QI8I$k3xy_WX&%%eZrUao!h2({(k*^O>kU$c zkoXb&43I!d97H8PSN%PFM%+8Q-d)@4MB+nc?##~IbMC$8PWfdPl*d1ctB~!(*PG@UR=h9?ze~P>$g!@{m*8jZs{^sUmY(l@CnL8ES;VP?D1tQTrVuIz4NHev%Zs{f=Dy`#6g~FAv9^g8_%|#5$=eu$8{XZN>b<=E`7xHuf7B1qs5x_Gb zlNb6?4s~l!$Z9OR;0wzYmeT4R7!QEtvakzVEhHV*AKkFmk!+C1KlFx8Quki9f!cacrboI(SWhkFJ5?el2NO1C(GK=k>03ESPCk7pH7^hsKckTYyC3ve=eT?dF#B-U1U0i z9Y_Xq^xR&iCpwA{7n7_I*Hg??QZZ9i#Y}~zIR%MpPve`kD$p#^9L=3U`a>Lhi$+;& z4&iOGRr>(uk);)1ULX*@{0qVa4`ldqZt)bM-1I5JxusJ?aubnL@_I!+h3xsA5$A^y zK@xF{V3_EPB2CmsiR75TTP1Yz>EN-&ficO92rVQm4lXP#4lg1s4p2sp?C~gF0v!hl z8>DN#41!!l-XXt<7+FC21(f|<3NO>KKM`L%L*jSZ4Z%ypq@XonpLViL_sHWM(PFqb zC*Oluav*jHf;px-5Q~J64?vg_2pRjZi;&``Bm}M*V}%6PrLe%_url-)$2BUVU5^bX zBnMCWhw-EUyjfpFsXjbkM2x}+*BykC6H!t`G?gFV|Ee#Zr0YK^%E$2}MPyy^bV?=R z9o(RxyTy5eP;XVT0;5-)@6wMII~l7qbA4qYOizLdv74ukS@LB;cI74uzII)slE}4$ zTX-*kQ}|c%?^nw8Ku+)DHl5;tJWf;8C%v3*5~o`OIB`$AoH$n!J_yWHq&H8u137&d zoTo@Hr)A=_GJunez5H3DMS`OEGLNmMutC6_X0Vsi2T@j3X5KlbF=h_ z^m1A!P8$O_@nhiTiQ8VnUEFgybsoDD$h~=b5Xk8x-0!O$J#@Y&&DPU4aVibqJ= zr;Ol4|GFGbKU1Kbr}7I7?VmwSUut8Rau%X#WWJ4Ldc@ucw~Ftk^(n$AcR0rpm0b53c8JB!3H15-XQTQ@B=h7G=L~5 zDflSFTd=^0qwqU-XWl&T%j@G4fJMw(FfeLi4CBg7Sjc12#FU9?24h)-Las1q)!HG0 zz8^Ky`65*^5T8o@~JY|h+OU;@>r-mE$Z?_Bn-0+HwqkIgr|-OoURmp;;ctO z5QPUSUScpsvJsrg3x?rpqr=a+I6&T}te^&`Q|j_odbwPGhF!ly?he`|O#K?Vc0+wFgJor~$|zxg`h)Zt u(E7Ahh9Qdcq>fwx`q^ zQj|2uvPmN~ZrGMwmxMlN+R6)Ti6yh?Xv)gWM6GnR;8>}AbTDy7j(q~^wSsBS9nIP? zEAI##=!{)8t{c&mk(r9d9ox!G^{tn5c_N7wm3G7lCbOwRIx}jd-Eh`{2>7oXsluAg z6N#&4Qkn~k&oC3Vvo}VJoXpXMa9vP9mD;rF7=wmm2y}Rx`D>}@DKlS4IdRh&DeY1- z-4beVI%w|vS`luCDy0UWEalRip@ z%eDKTI1;IjX(wms+og~KLqS48k_1&SslZflMZpwPJTjX!b29(Df@y(9R)0D>dD5_X z{*Eat9*A308N(^qJo5B&vFiEL2*$GoJ82%ZWN)Y?vhS6IQn3l`0xc`#I%M0%oUEDx zOT|^pFkclZq?xgb46;mHMGn`Py9yh56%Gmln^qv$jx*-ukddEeHF=bCv(ze?n_sz7~ZRI;JHOl7C6 za8_Wmt0txE_|D6uyjgCpN{g~^x#LKi6|bxSJEe>eAB&Z#BG6gR&&XNsRTLex3)ZCU zYVXKKjEpg5+Ju}koyv&2HlR@H8d{wkS=^ZOnz-D<0zJ<*Usna9sOGGhEI8(vydVYI zIxF0jYe2maBV8%Q)(zE`_o-g*CMNI7(*4A%ofn@kW6tupxHw;_6EhMfuat1-aLIe+ zZNMfY*fJZ|=#;P4>Rf@qy_E$h?N!__B}t`|6#!#$QiiaBI%D-GQ`tP*td3goLXM@+ z=S}LjSL&CE-q$sb9SHNsQ~p(feS~(tHLju@1xNp`2N2)8+nf2WafJbF;av3;(191Y z3ScX?!6%Jf8fVkL>yhzEPq3x_1}huY&FudX45UqDDYhgkGyDMJSvH zv_KKH{N)QuNkHIB9Dj%)F+zICcR&rR;ovvetp&nr5h{V|G(}G9IIY)0TAfy3M13_T z?xG2*dR767+CgzuZ(3C}7(rOo>N$5s_d5qFutrqwZ&GR^1miEdh7*b$CZ;(cj z)kbZ4BaIqeBR5JyX$=dSU&7Z&c;?mO!7Eygrj+29rMA3(3_-DWVKP7-b|zB z61-=mH7#^6;;iOdM0;p-_<*~EZk3_U#ECMZ=uD`RTg2rl@_h8MZmMr zS{C+vhX&lmJ$%KFo=$v@`=wEST0O$=c+B;E{0h1L6aU}|*B$snw3bGmz337>Vjr4B ztJo&w`jj{;E^xn*UlF;|I7j&%@tOF7dO2}Ra80&IPF;Z{TnlQ+nMRUPA#E2)-i@%+zs;i4ZE=i3N+D!ZtR60 zb>c3qd&qe*o>n`Ujhwy48NZaoJ?!I0Y8CcV+{V%KaDX1Xy*DW9r9~$?mgp*^;7#{m zSJ0=RU%^}M`=I-Nn@#yI!V2CIe`D`o2*oK54fDkh;Ql39!e`ja$iU$HA*5N&5?w1B z>I3V5o1dcSfv$!1uh9m^q)Qp47ikMx*QqQ+t%}kwqpU9La_ -import { onBeforeUnmount, onMounted } from 'vue' +import { onBeforeUnmount } from 'vue' +import Layout from '@/layout/index.vue' import { useWebSocket } from './stores/websocketStore' const websocket = useWebSocket() -onMounted(() => { - websocket.connect('ws://127.0.0.1:8090/ws') -}) - onBeforeUnmount(() => { - websocket.disconnect() + // websocket.disconnect() }) diff --git a/code/link/link-frontend/src/layout/index.vue b/code/link/link-frontend/src/layout/index.vue new file mode 100644 index 00000000..c404a82f --- /dev/null +++ b/code/link/link-frontend/src/layout/index.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/code/link/link-frontend/src/router/index.js b/code/link/link-frontend/src/router/index.js index f89695ad..af06cfe2 100644 --- a/code/link/link-frontend/src/router/index.js +++ b/code/link/link-frontend/src/router/index.js @@ -1,14 +1,30 @@ +import { ElMessage } from 'element-plus' import { createRouter, createWebHistory } from 'vue-router' +import { useUserStore } from '@/stores/userStore' +import Login from '@/views/auth/index.vue' +import Home from '@/views/home/index.vue' import Im from '@/views/im/index.vue' - +import { useWebSocket } from '../stores/websocketStore' +// 新增登录页面 const routes = [ { path: '/', - redirect: '/im', + redirect: '/home', + }, + { + path: '/home', + component: Home, + meta: { title: '首页' }, }, { path: '/im', component: Im, + meta: { title: 'IM聊天' }, + }, + { + path: '/login', + component: Login, + meta: { title: '登录' }, }, ] @@ -17,16 +33,36 @@ const router = createRouter({ routes, }) -// 全局前置守卫 router.beforeEach((to, from, next) => { - // 设置页面标题 document.title = to.meta.title ? `${to.meta.title} - 系统名称` : '系统名称' - // 通过检查,继续跳转 + const userStore = useUserStore() + const websocket = useWebSocket() + + // 登录页直接放行,不主动断开 + if (to.path === '/login') { + next() + return + } + + if (!userStore.isLogined()) { + ElMessage.warning('请先登录') + + userStore.resetUserInfo() + next({ path: '/login', query: { redirect: to.fullPath } }) + return + } + + // 登录状态下,确保连接 + if (!websocket.isConnected.value) { + websocket.connect() + } + next() }) -router.afterEach(() => { +router.afterEach((to, from) => { + }) export default router diff --git a/code/link/link-frontend/src/stores/userStore.js b/code/link/link-frontend/src/stores/userStore.js index 419ec6f7..3a56bd79 100644 --- a/code/link/link-frontend/src/stores/userStore.js +++ b/code/link/link-frontend/src/stores/userStore.js @@ -4,18 +4,19 @@ import { ref } from 'vue' export const useUserStore = defineStore( 'userInfo', () => { - const userInfo = ref({ - orgid: '', + orgid: 'g3hd', userid: '', token: '', + username: '', }) // 设置用户信息 - function setUserInfo(orgid, userid, token) { + function setUserInfo(orgid, userid, username, token) { userInfo.value.orgid = orgid userInfo.value.userid = userid userInfo.value.token = token + userInfo.value.username = username } // 重置用户信息 diff --git a/code/link/link-frontend/src/stores/websocketStore.js b/code/link/link-frontend/src/stores/websocketStore.js index 0eff01d1..036a4140 100644 --- a/code/link/link-frontend/src/stores/websocketStore.js +++ b/code/link/link-frontend/src/stores/websocketStore.js @@ -1,7 +1,15 @@ +// stores/websocket.js import { ElNotification } from 'element-plus' -import { onScopeDispose, ref } from 'vue' +import { ref } from 'vue' +import { useUserStore } from './userStore' + +let instance = null // 单例缓存 export function useWebSocket() { + if (instance) + return instance // 已存在则直接返回 + + const userStore = useUserStore() const socket = ref(null) const isConnected = ref(false) const listeners = ref(new Set()) @@ -9,25 +17,19 @@ export function useWebSocket() { let reconnectAttempts = 0 let pingTimer = null let pongTimeout = null - const PING_INTERVAL = 10000 // 收到PONG后间隔再发送下一次PING - const PONG_TIMEOUT = 15000 // PING发送后15秒未收到PONG就重连 - + const PING_INTERVAL = 10000 + const PONG_TIMEOUT = 15000 + const currentUrl = 'ws://127.0.0.1:8090/ws' let reconnectNotification = null - let currentUrl = null let isReconnecting = false + let manualClose = false // ✅ 标记是否为手动关闭 - // -------------------- - // 私有方法 - // -------------------- + // ---------------- 清理 ---------------- function cleanup() { - if (pingTimer) { - clearTimeout(pingTimer) - pingTimer = null - } - if (pongTimeout) { - clearTimeout(pongTimeout) - pongTimeout = null - } + clearTimeout(pingTimer) + clearTimeout(pongTimeout) + pingTimer = null + pongTimeout = null } function handleDisconnect() { @@ -39,23 +41,14 @@ export function useWebSocket() { listeners.value.forEach(cb => cb(message)) } - // -------------------- - // 心跳逻辑(链式) - // -------------------- + // ---------------- 心跳 ---------------- function sendPing() { if (!socket.value || socket.value.readyState !== WebSocket.OPEN) return - try { - sendMessage('PING') - } - catch (err) { - console.error('发送 PING 失败', err) - } + sendMessage({ type: 'ping' }) - // 设置 PONG 超时 - if (pongTimeout) - clearTimeout(pongTimeout) + clearTimeout(pongTimeout) pongTimeout = setTimeout(() => { console.warn('未收到PONG,触发重连') handleReconnect() @@ -63,15 +56,8 @@ export function useWebSocket() { } function handlePong() { - // 收到 PONG,清理超时 - if (pongTimeout) { - clearTimeout(pongTimeout) - pongTimeout = null - } - - // 等 PING_INTERVAL 后再发送下一次 PING - if (pingTimer) - clearTimeout(pingTimer) + clearTimeout(pongTimeout) + clearTimeout(pingTimer) pingTimer = setTimeout(sendPing, PING_INTERVAL) } @@ -79,62 +65,57 @@ export function useWebSocket() { sendPing() } - // -------------------- - // 重连逻辑 - // -------------------- + // ---------------- 重连 ---------------- function handleReconnect() { - if (isReconnecting) - return + if (isReconnecting || manualClose) + return // ✅ 如果是手动断开则不重连 + isReconnecting = true cleanup() isConnected.value = false socket.value?.close() reconnectAttempts++ - if (reconnectNotification) - reconnectNotification.close() + reconnectNotification?.close() reconnectNotification = ElNotification({ title: '提示', message: `WebSocket 连接失败,正在第 ${reconnectAttempts} 次重连...`, type: 'warning', duration: 0, - key: 'websocket-reconnect', }) const delay = Math.min(3000 * 2 ** (reconnectAttempts - 1), 30000) setTimeout(() => { isReconnecting = false - connect(currentUrl) + connect() }, delay) } function clearReconnectNotification() { - if (reconnectNotification) { - reconnectNotification.close() - reconnectNotification = null - } + reconnectNotification?.close() + reconnectNotification = null } - // -------------------- - // 公共方法 - // -------------------- - function connect(url) { - currentUrl = url + // ---------------- 公共方法 ---------------- + function connect() { if (isConnected.value) return - if (socket.value) - disconnect() - const newSocket = new WebSocket(url) + manualClose = false // ✅ 每次连接前重置为 false + + const newSocket = new WebSocket(currentUrl) newSocket.onopen = () => { isConnected.value = true isReconnecting = false clearReconnectNotification() + if (userStore.isLogined) + sendLogin(userStore.userInfo.userid) + ElNotification({ title: '提示', - message: 'WebSocket 连接成功', + message: '上线成功', type: 'success', duration: 2000, }) @@ -144,26 +125,42 @@ export function useWebSocket() { } newSocket.onmessage = (event) => { - let message + let resData try { - message = JSON.parse(event.data) + console.log(event.data) - if (message.type === 'PONG') { - handlePong() - return - } - - notifyListeners(message) + resData = JSON.parse(event.data) } catch (err) { console.error('消息解析失败:', err) notifyListeners({ type: 'ERROR', data: '消息解析失败' }) + return + } + + if (resData.code !== 200) { + ElNotification({ + title: '错误', + message: resData.message || '发生未知错误', + type: 'error', + showClose: true, + duration: 3000, + }) + return + } + + if (resData.type === 'pong') { + handlePong() + } + else { + notifyListeners(resData) } } newSocket.onclose = () => { handleDisconnect() - if (!isReconnecting) + + // ✅ 非手动断开才重连 + if (!manualClose && !isReconnecting) handleReconnect() } @@ -175,14 +172,25 @@ export function useWebSocket() { } function disconnect() { + if (!socket.value) + return + + manualClose = true socket.value?.close() cleanup() + clearReconnectNotification() + + ElNotification({ + type: 'warning', + title: '提示', + message: '已下线', + }) } - function sendMessage(type, data) { + function sendMessage(data) { if (isConnected.value && socket.value) { - const message = { type, data } - socket.value.send(JSON.stringify(message)) + data.orgId = userStore.userInfo.orgid || '' + socket.value.send(JSON.stringify(data)) } } @@ -191,13 +199,23 @@ export function useWebSocket() { return () => listeners.value.delete(callback) } - onScopeDispose(() => disconnect()) + function sendLogin(userId) { + if (!isConnected.value) + return + sendMessage({ + type: 'login', + from: userId, + }) + } - return { + instance = { isConnected, connect, disconnect, sendMessage, onMessage, + sendLogin, } + + return instance } diff --git a/code/link/link-frontend/src/utils/axios.js b/code/link/link-frontend/src/utils/axios.js index 0a76188b..66bf30df 100644 --- a/code/link/link-frontend/src/utils/axios.js +++ b/code/link/link-frontend/src/utils/axios.js @@ -1,34 +1,51 @@ // src/utils/axios.js import axios from 'axios' +import { useUserStore } from '@/stores/userStore' const instance = axios.create({ baseURL: 'http://localhost:8089/api', // 后端接口地址 - timeout: 5000 -}) + timeout: 5000, +}) // 请求拦截器:自动添加 token instance.interceptors.request.use( - config => { - const token = localStorage.getItem('token') - if (token) { - config.headers.Authorization = `Bearer ${token}` + (config) => { + const userStore = useUserStore() + + if (userStore.userInfo.token) { + config.headers.Authorization = `Bearer ${userStore.userInfo.token}` } return config }, - error => Promise.reject(error) + error => Promise.reject(error), ) // 响应拦截器:统一处理错误或过期 token instance.interceptors.response.use( response => response, - error => { + (error) => { + const userStore = useUserStore() if (error.response && error.response.status === 401) { alert('登录失效,请重新登录') - window.localStorage.removeItem('token') + userStore.resetUserInfo() window.location.href = '/login' } return Promise.reject(error) - } + }, ) +export function axiosPost(url, data = {}, config = {}) { + return instance.post(url, data, { + headers: { 'Content-Type': 'application/json' }, + ...config, + }).then(res => res.data) +} + +export function axiosGet(url, params = {}, config = {}) { + return instance.get(url, { + params, + ...config, + }).then(res => res.data) +} + export default instance diff --git a/code/link/link-frontend/src/views/auth/index.vue b/code/link/link-frontend/src/views/auth/index.vue new file mode 100644 index 00000000..048f0fc2 --- /dev/null +++ b/code/link/link-frontend/src/views/auth/index.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/code/link/link-frontend/src/views/home/index.vue b/code/link/link-frontend/src/views/home/index.vue new file mode 100644 index 00000000..314950a6 --- /dev/null +++ b/code/link/link-frontend/src/views/home/index.vue @@ -0,0 +1,67 @@ + + + + +