list);
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/codegen/CodegenColumnDO.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/codegen/CodegenColumnDO.java
index 4e7f330e52..f1990f3fc8 100644
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/codegen/CodegenColumnDO.java
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/codegen/CodegenColumnDO.java
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.infra.enums.codegen.CodegenColumnListConditionEnu
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.generator.config.po.TableField;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
@@ -29,7 +30,7 @@ public class CodegenColumnDO extends BaseDO {
private Long id;
/**
* 表编号
- *
+ *
* 关联 {@link CodegenTableDO#getId()}
*/
private Long tableId;
@@ -41,7 +42,8 @@ public class CodegenColumnDO extends BaseDO {
*/
private String columnName;
/**
- * 字段类型
+ * 数据库字段类型
+ * 关联 {@link TableField.MetaInfo#getJdbcType()}
*/
private String dataType;
/**
@@ -69,7 +71,7 @@ public class CodegenColumnDO extends BaseDO {
/**
* Java 属性类型
- *
+ *
* 例如说 String、Boolean 等等
*/
private String javaType;
@@ -79,7 +81,7 @@ public class CodegenColumnDO extends BaseDO {
private String javaField;
/**
* 字典类型
- *
+ *
* 关联 DictTypeDO 的 type 属性
*/
private String dictType;
@@ -104,7 +106,7 @@ public class CodegenColumnDO extends BaseDO {
private Boolean listOperation;
/**
* List 查询操作的条件类型
- *
+ *
* 枚举 {@link CodegenColumnListConditionEnum}
*/
private String listOperationCondition;
@@ -117,7 +119,7 @@ public class CodegenColumnDO extends BaseDO {
/**
* 显示类型
- *
+ *
* 枚举 {@link CodegenColumnHtmlTypeEnum}
*/
private String htmlType;
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileConfigMapper.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileConfigMapper.java
index e770dcafd9..a2bf16be46 100755
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileConfigMapper.java
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileConfigMapper.java
@@ -6,15 +6,7 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO;
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Select;
-import java.time.LocalDateTime;
-
-/**
- * 文件配置 Mapper
- *
- * @author 芋道源码
- */
@Mapper
public interface FileConfigMapper extends BaseMapperX {
@@ -26,7 +18,4 @@ public interface FileConfigMapper extends BaseMapperX {
.orderByDesc(FileConfigDO::getId));
}
- @Select("SELECT COUNT(*) FROM infra_file_config WHERE update_time > #{maxUpdateTime}")
- Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
-
}
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileContentDAOImpl.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileContentDAOImpl.java
index c4dcfe8a0e..2492c803d0 100644
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileContentDAOImpl.java
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileContentDAOImpl.java
@@ -1,11 +1,14 @@
package cn.iocoder.yudao.module.infra.dal.mysql.file;
+import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.file.core.client.db.DBFileContentFrameworkDAO;
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileContentDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
+import java.util.List;
+import java.util.Optional;
@Repository
public class FileContentDAOImpl implements DBFileContentFrameworkDAO {
@@ -27,9 +30,11 @@ public class FileContentDAOImpl implements DBFileContentFrameworkDAO {
@Override
public byte[] selectContent(Long configId, String path) {
- FileContentDO fileContentDO = fileContentMapper.selectOne(
- buildQuery(configId, path).select(FileContentDO::getContent));
- return fileContentDO != null ? fileContentDO.getContent() : null;
+ List list = fileContentMapper.selectList(
+ buildQuery(configId, path).select(FileContentDO::getContent).orderByDesc(FileContentDO::getId));
+ return Optional.ofNullable(CollUtil.getFirst(list))
+ .map(FileContentDO::getContent)
+ .orElse(null);
}
private LambdaQueryWrapper buildQuery(Long configId, String path) {
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/mq/consumer/file/FileConfigRefreshConsumer.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/mq/consumer/file/FileConfigRefreshConsumer.java
index 671b419431..a3b41197fb 100644
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/mq/consumer/file/FileConfigRefreshConsumer.java
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/mq/consumer/file/FileConfigRefreshConsumer.java
@@ -23,7 +23,7 @@ public class FileConfigRefreshConsumer extends AbstractChannelMessageListener getDatabaseTableList(Long dataSourceConfigId, String name, String comment) {
List tables = databaseTableService.getTableList(dataSourceConfigId, name, comment);
- // 移除置顶前缀的表名 // TODO 未来做成可配置
- tables.removeIf(table -> table.getName().toUpperCase().startsWith("QRTZ_"));
- tables.removeIf(table -> table.getName().toUpperCase().startsWith("ACT_"));
- tables.removeIf(table -> table.getName().toUpperCase().startsWith("FLW_"));
// 移除已经生成的表
// 移除在 Codegen 中,已经存在的
Set existsTables = CollectionUtils.convertSet(
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenBuilder.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenBuilder.java
index 90f5816f37..8293ffef0b 100644
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenBuilder.java
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenBuilder.java
@@ -18,6 +18,8 @@ import org.springframework.stereotype.Component;
import java.util.*;
import static cn.hutool.core.text.CharSequenceUtil.*;
+import static cn.hutool.core.util.RandomUtil.randomEle;
+import static cn.hutool.core.util.RandomUtil.randomInt;
/**
* 代码生成器的 Builder,负责:
@@ -128,6 +130,7 @@ public class CodegenBuilder {
// 初始化 Column 列的默认字段
processColumnOperation(column); // 处理 CRUD 相关的字段的默认值
processColumnUI(column); // 处理 UI 相关的字段的默认值
+ processColumnExample(column); // 处理字段的 swagger example 示例
}
return columns;
}
@@ -169,4 +172,42 @@ public class CodegenBuilder {
}
}
+ /**
+ * 处理字段的 swagger example 示例
+ *
+ * @param column 字段
+ */
+ private void processColumnExample(CodegenColumnDO column) {
+ // id、price、count 等可能是整数的后缀
+ if (StrUtil.endWithAnyIgnoreCase(column.getJavaField(), "id", "price", "count")) {
+ column.setExample(String.valueOf(randomInt(1, Short.MAX_VALUE)));
+ return;
+ }
+ // name
+ if (StrUtil.endWithIgnoreCase(column.getJavaField(), "name")) {
+ column.setExample(randomEle(new String[]{"张三", "李四", "王五", "赵六", "芋艿"}));
+ return;
+ }
+ // status
+ if (StrUtil.endWithAnyIgnoreCase(column.getJavaField(), "status", "type")) {
+ column.setExample(randomEle(new String[]{"1", "2"}));
+ return;
+ }
+ // url
+ if (StrUtil.endWithIgnoreCase(column.getColumnName(), "url")) {
+ column.setExample("https://www.iocoder.cn");
+ return;
+ }
+ // reason
+ if (StrUtil.endWithIgnoreCase(column.getColumnName(), "reason")) {
+ column.setExample(randomEle(new String[]{"不喜欢", "不对", "不好", "不香"}));
+ return;
+ }
+ // description、memo、remark
+ if (StrUtil.endWithAnyIgnoreCase(column.getColumnName(), "description", "memo", "remark")) {
+ column.setExample(randomEle(new String[]{"你猜", "随便", "你说的对"}));
+ return;
+ }
+ }
+
}
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/db/DatabaseTableServiceImpl.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/db/DatabaseTableServiceImpl.java
index 6b8346cf0d..d0c80ba61e 100644
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/db/DatabaseTableServiceImpl.java
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/db/DatabaseTableServiceImpl.java
@@ -52,7 +52,11 @@ public class DatabaseTableServiceImpl implements DatabaseTableService {
StrategyConfig.Builder strategyConfig = new StrategyConfig.Builder();
if (StrUtil.isNotEmpty(name)) {
strategyConfig.addInclude(name);
+ } else {
+ // 移除工作流和定时任务前缀的表名 // TODO 未来做成可配置
+ strategyConfig.addExclude("ACT_[\\S\\s]+|QRTZ_[\\S\\s]+|FLW_[\\S\\s]+");
}
+
GlobalConfig globalConfig = new GlobalConfig.Builder().dateType(DateType.TIME_PACK).build(); // 只使用 Date 类型,不使用 LocalDate
ConfigBuilder builder = new ConfigBuilder(null, dataSourceConfig, strategyConfig.build(),
null, globalConfig, null);
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigService.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigService.java
index 02197b98ee..b3fc6bad23 100755
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigService.java
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigService.java
@@ -21,7 +21,7 @@ public interface FileConfigService {
/**
* 初始化文件客户端
*/
- void initFileClients();
+ void initLocalCache();
/**
* 创建文件配置
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java
index f68c09eb1f..590ff8116b 100755
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java
@@ -1,10 +1,8 @@
package cn.iocoder.yudao.module.infra.service.file;
-import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.IdUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import cn.iocoder.yudao.framework.file.core.client.FileClient;
@@ -20,8 +18,6 @@ import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper;
import cn.iocoder.yudao.module.infra.mq.producer.file.FileConfigProducer;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
@@ -31,7 +27,6 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.validation.Validator;
-import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -50,18 +45,6 @@ import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG
@Slf4j
public class FileConfigServiceImpl implements FileConfigService {
- /**
- * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
- * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
- */
- private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
-
- /**
- * 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新
- */
- @Getter
- private volatile LocalDateTime maxUpdateTime;
-
@Resource
private FileClientFactory fileClientFactory;
/**
@@ -79,20 +62,14 @@ public class FileConfigServiceImpl implements FileConfigService {
@Resource
private Validator validator;
- @Resource
- @Lazy // 注入自己,所以延迟加载
- private FileConfigService self;
-
@Override
@PostConstruct
- public void initFileClients() {
- // 获取文件配置,如果有更新
- List configs = loadFileConfigIfUpdate(maxUpdateTime);
- if (CollUtil.isEmpty(configs)) {
- return;
- }
+ public void initLocalCache() {
+ // 第一步:查询数据
+ List configs = fileConfigMapper.selectList();
+ log.info("[initLocalCache][缓存文件配置,数量为:{}]", configs.size());
- // 创建或更新支付 Client
+ // 第二步:构建缓存:创建或更新文件 Client
configs.forEach(config -> {
fileClientFactory.createOrUpdateFileClient(config.getId(), config.getStorage(), config.getConfig());
// 如果是 master,进行设置
@@ -100,36 +77,6 @@ public class FileConfigServiceImpl implements FileConfigService {
masterFileClient = fileClientFactory.getFileClient(config.getId());
}
});
-
- // 写入缓存
- maxUpdateTime = CollectionUtils.getMaxValue(configs, FileConfigDO::getUpdateTime);
- log.info("[initFileClients][初始化 FileConfig 数量为 {}]", configs.size());
- }
-
- @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
- public void schedulePeriodicRefresh() {
- self.initFileClients();
- }
-
- /**
- * 如果文件配置发生变化,从数据库中获取最新的全量文件配置。
- * 如果未发生变化,则返回空
- *
- * @param maxUpdateTime 当前文件配置的最大更新时间
- * @return 文件配置列表
- */
- private List loadFileConfigIfUpdate(LocalDateTime maxUpdateTime) {
- // 第一步,判断是否要更新。
- if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
- log.info("[loadFileConfigIfUpdate][首次加载全量文件配置]");
- } else { // 判断数据库中是否有更新的文件配置
- if (fileConfigMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
- return null;
- }
- log.info("[loadFileConfigIfUpdate][增量加载全量文件配置]");
- }
- // 第二步,如果有更新,则从数据库加载所有文件配置
- return fileConfigMapper.selectList();
}
@Override
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/SemaphoreUtils.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/SemaphoreUtils.java
new file mode 100644
index 0000000000..67a87f1693
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/SemaphoreUtils.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.infra.websocket;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.concurrent.Semaphore;
+
+/**
+ * 信号量相关处理
+ *
+ */
+@Slf4j
+public class SemaphoreUtils {
+
+ /**
+ * 获取信号量
+ *
+ * @param semaphore
+ * @return
+ */
+ public static boolean tryAcquire(Semaphore semaphore) {
+ boolean flag = false;
+
+ try {
+ flag = semaphore.tryAcquire();
+ } catch (Exception e) {
+ log.error("获取信号量异常", e);
+ }
+
+ return flag;
+ }
+
+ /**
+ * 释放信号量
+ *
+ * @param semaphore
+ */
+ public static void release(Semaphore semaphore) {
+
+ try {
+ semaphore.release();
+ } catch (Exception e) {
+ log.error("释放信号量异常", e);
+ }
+ }
+}
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/WebSocketConfig.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/WebSocketConfig.java
new file mode 100644
index 0000000000..380bc9317e
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/WebSocketConfig.java
@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.infra.websocket;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+/**
+ * websocket 配置
+ */
+@Configuration
+public class WebSocketConfig {
+ @Bean
+ public ServerEndpointExporter serverEndpointExporter() {
+ return new ServerEndpointExporter();
+ }
+}
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/WebSocketServer.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/WebSocketServer.java
new file mode 100644
index 0000000000..f0cfdd9dc5
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/WebSocketServer.java
@@ -0,0 +1,86 @@
+package cn.iocoder.yudao.module.infra.websocket;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import javax.websocket.*;
+import javax.websocket.server.ServerEndpoint;
+import java.util.concurrent.Semaphore;
+
+/**
+ * websocket 消息处理
+ */
+@Component
+@ServerEndpoint("/websocket/message")
+@Slf4j
+public class WebSocketServer {
+
+ /**
+ * 默认最多允许同时在线用户数100
+ */
+ public static int socketMaxOnlineCount = 100;
+
+ private static final Semaphore SOCKET_SEMAPHORE = new Semaphore(socketMaxOnlineCount);
+
+ /**
+ * 连接建立成功调用的方法
+ */
+ @OnOpen
+ public void onOpen(Session session) throws Exception {
+ // 尝试获取信号量
+ boolean semaphoreFlag = SemaphoreUtils.tryAcquire(SOCKET_SEMAPHORE);
+ if (!semaphoreFlag) {
+ // 未获取到信号量
+ log.error("当前在线人数超过限制数:{}", socketMaxOnlineCount);
+ WebSocketUsers.sendMessage(session, "当前在线人数超过限制数:" + socketMaxOnlineCount);
+ session.close();
+ } else {
+ String userId = WebSocketUsers.getParam("userId", session);
+ if (userId != null) {
+ // 添加用户
+ WebSocketUsers.addSession(userId, session);
+ log.info("用户【userId={}】建立连接,当前连接用户总数:{}", userId, WebSocketUsers.getUsers().size());
+ WebSocketUsers.sendMessage(session, "接收内容:连接成功");
+ } else {
+ WebSocketUsers.sendMessage(session, "接收内容:连接失败");
+ }
+ }
+ }
+
+ /**
+ * 连接关闭时处理
+ */
+ @OnClose
+ public void onClose(Session session) {
+ log.info("用户【sessionId={}】关闭连接!", session.getId());
+ // 移除用户
+ WebSocketUsers.removeSession(session);
+ // 获取到信号量则需释放
+ SemaphoreUtils.release(SOCKET_SEMAPHORE);
+ }
+
+ /**
+ * 抛出异常时处理
+ */
+ @OnError
+ public void onError(Session session, Throwable exception) throws Exception {
+ if (session.isOpen()) {
+ // 关闭连接
+ session.close();
+ }
+ String sessionId = session.getId();
+ log.info("用户【sessionId={}】连接异常!异常信息:{}", sessionId, exception);
+ // 移出用户
+ WebSocketUsers.removeSession(session);
+ // 获取到信号量则需释放
+ SemaphoreUtils.release(SOCKET_SEMAPHORE);
+ }
+
+ /**
+ * 收到客户端消息时调用的方法
+ */
+ @OnMessage
+ public void onMessage(Session session, String message) {
+ WebSocketUsers.sendMessage(session, "接收内容:" + message);
+ }
+}
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/WebSocketUsers.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/WebSocketUsers.java
new file mode 100644
index 0000000000..281a97c7d9
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/WebSocketUsers.java
@@ -0,0 +1,178 @@
+package cn.iocoder.yudao.module.infra.websocket;
+
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.bouncycastle.util.Strings;
+
+import javax.validation.constraints.NotNull;
+import javax.websocket.Session;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * websocket 客户端用户
+ */
+@Slf4j
+public class WebSocketUsers {
+
+ /**
+ * 用户集
+ * TODO 需要登录用户的session?
+ */
+ private static final Map SESSION_MAP = new ConcurrentHashMap<>();
+
+ /**
+ * 存储用户
+ *
+ * @param userId 唯一键
+ * @param session 用户信息
+ */
+ public static void addSession(String userId, Session session) {
+ SESSION_MAP.put(userId, session);
+ }
+
+ /**
+ * 移除用户
+ *
+ * @param session 用户信息
+ * @return 移除结果
+ */
+ public static boolean removeSession(Session session) {
+ String key = null;
+ boolean flag = SESSION_MAP.containsValue(session);
+ if (flag) {
+ Set> entries = SESSION_MAP.entrySet();
+ for (Map.Entry entry : entries) {
+ Session value = entry.getValue();
+ if (value.equals(session)) {
+ key = entry.getKey();
+ break;
+ }
+ }
+ } else {
+ return true;
+ }
+ return removeSession(key);
+ }
+
+ /**
+ * 移出用户
+ *
+ * @param userId 用户id
+ */
+ public static boolean removeSession(String userId) {
+ log.info("用户【userId={}】退出", userId);
+ Session remove = SESSION_MAP.remove(userId);
+ if (remove != null) {
+ boolean containsValue = SESSION_MAP.containsValue(remove);
+ log.info("用户【userId={}】退出{},当前连接用户总数:{}", userId, containsValue ? "失败" : "成功", SESSION_MAP.size());
+ return containsValue;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * 获取在线用户列表
+ *
+ * @return 返回用户集合
+ */
+ public static Map getUsers() {
+ return SESSION_MAP;
+ }
+
+ /**
+ * 向所有在线人发送消息
+ *
+ * @param message 消息内容
+ */
+ public static void sendMessageToAll(String message) {
+ SESSION_MAP.forEach((userId, session) -> {
+ if (session.isOpen()) {
+ sendMessage(session, message);
+ }
+ });
+ }
+
+ /**
+ * 异步发送文本消息
+ *
+ * @param session 用户session
+ * @param message 消息内容
+ */
+ public static void sendMessageAsync(Session session, String message) {
+ if (session.isOpen()) {
+ // TODO 需要加synchronized锁(synchronized(session))?单个session创建线程?
+ session.getAsyncRemote().sendText(message);
+ } else {
+ log.warn("用户【session={}】不在线", session.getId());
+ }
+ }
+
+ /**
+ * 同步发送文本消息
+ *
+ * @param session 用户session
+ * @param message 消息内容
+ */
+ public static void sendMessage(Session session, String message) {
+ try {
+ if (session.isOpen()) {
+ // TODO 需要加synchronized锁(synchronized(session))?单个session创建线程?
+ session.getBasicRemote().sendText(message);
+ } else {
+ log.warn("用户【session={}】不在线", session.getId());
+ }
+ } catch (IOException e) {
+ log.error("发送消息异常", e);
+ }
+
+ }
+
+ /**
+ * 根据用户id发送消息
+ *
+ * @param userId 用户id
+ * @param message 消息内容
+ */
+ public static void sendMessage(String userId, String message) {
+ Session session = SESSION_MAP.get(userId);
+ //判断是否存在该用户的session,并且是否在线
+ if (session == null || !session.isOpen()) {
+ return;
+ }
+ sendMessage(session, message);
+ }
+
+
+ /**
+ * 获取session中的指定参数值
+ *
+ * @param key 参数key
+ * @param session 用户session
+ */
+ public static String getParam(@NotNull String key, Session session) {
+ //TODO 目前只针对获取一个key的值,后期根据情况拓展多个 或者直接在onClose onOpen上获取参数?
+ String value = null;
+ Map> parameters = session.getRequestParameterMap();
+ if (MapUtil.isNotEmpty(parameters)) {
+ value = parameters.get(key).get(0);
+ } else {
+ String queryString = session.getQueryString();
+ if (!StrUtil.isEmpty(queryString)) {
+ String[] params = Strings.split(queryString, '&');
+ for (String paramPair : params) {
+ String[] nameValues = Strings.split(paramPair, '=');
+ if (key.equals(nameValues[0])) {
+ value = nameValues[1];
+ }
+ }
+ }
+ }
+ return value;
+ }
+}
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/data.ts.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/data.ts.vm
index 33e204a46f..8138f129c2 100644
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/data.ts.vm
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/data.ts.vm
@@ -1,8 +1,4 @@
-import { reactive } from 'vue'
-import { useI18n } from '@/hooks/web/useI18n'
-import { DICT_TYPE } from '@/utils/dict'
-import { required } from '@/utils/formRules'
-import { VxeCrudSchema, useVxeCrudSchemas } from '@/hooks/web/useVxeCrudSchemas'
+import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
const { t } = useI18n() // 国际化
// 表单校验
export const rules = reactive({
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm
index 06fe3002ad..389458c512 100644
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm
@@ -1,7 +1,7 @@
-
+
@@ -40,10 +40,10 @@
preIcon="ep:delete"
:title="t('action.del')"
v-hasPermi="['${permissionPrefix}:delete']"
- @click="handleDelete(row.id)"
+ @click="deleteData(row.id)"
/>
-
+
@@ -75,12 +75,6 @@