diff --git a/README.md b/README.md
index 263d989cba..f1e3beac44 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-06-05 09:43:27
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-10 08:23:06
+ * @LastEditTime: 2024-12-05 09:21:15
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -128,14 +128,14 @@ AI powered live-chat, email support, Omnichannel customer service & team im,al
- [Email](mailto:270580156@qq.com)
- [Wechat](./images/wechat.png)
-- 微语技术支持群:
+
diff --git a/README.zh.md b/README.zh.md
index ca2ff14965..323daf75b8 100644
--- a/README.zh.md
+++ b/README.zh.md
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-06-05 09:44:23
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-30 10:32:00
+ * @LastEditTime: 2024-12-06 18:19:26
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -187,5 +187,6 @@
-- 此为开源社区版,支持完全免费商用,无任何限制
+- 此为开源社区版,在保留商标Logo等版权信息前提下,支持免费商用
+
- 严禁用于含有木马、病毒、色情、赌博、诈骗等违法违规业务
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/flow/Flow.java b/modules/TODO.md
similarity index 84%
rename from modules/ai/src/main/java/com/bytedesk/ai/flow/Flow.java
rename to modules/TODO.md
index 30e6dce370..ec154449c5 100644
--- a/modules/ai/src/main/java/com/bytedesk/ai/flow/Flow.java
+++ b/modules/TODO.md
@@ -1,8 +1,8 @@
-/*
+
+# TODO
-public class Flow {
-
-}
+- [X] finished
+- [ ] todo
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/flow/FlowEdge.java b/modules/ai/TODO.md
similarity index 84%
rename from modules/ai/src/main/java/com/bytedesk/ai/flow/FlowEdge.java
rename to modules/ai/TODO.md
index 5f49ee3781..e17bfe8269 100644
--- a/modules/ai/src/main/java/com/bytedesk/ai/flow/FlowEdge.java
+++ b/modules/ai/TODO.md
@@ -1,8 +1,8 @@
-/*
+
+# TODO
-public class FlowEdge {
-
-}
+- [X] finished
+- [ ] todo
diff --git a/modules/ai/pom.xml b/modules/ai/pom.xml
index 3b5857b8d6..87c9284099 100644
--- a/modules/ai/pom.xml
+++ b/modules/ai/pom.xml
@@ -19,7 +19,7 @@
1.0.0-SNAPSHOT
- 0.35.0
+ 0.36.2
1.0.0-rc.1
1.0.89
@@ -149,7 +149,6 @@
${agentsflex.version}
-
diff --git a/modules/service/src/main/java/com/bytedesk/service/counter_visitor/CounterVisitorInitializer.java b/modules/ai/src/main/java/com/bytedesk/ai/controller/AiRouteController.java
similarity index 61%
rename from modules/service/src/main/java/com/bytedesk/service/counter_visitor/CounterVisitorInitializer.java
rename to modules/ai/src/main/java/com/bytedesk/ai/controller/AiRouteController.java
index e9eb0ce60b..19d7262b87 100644
--- a/modules/service/src/main/java/com/bytedesk/service/counter_visitor/CounterVisitorInitializer.java
+++ b/modules/ai/src/main/java/com/bytedesk/ai/controller/AiRouteController.java
@@ -1,8 +1,8 @@
/*
* @Author: jackning 270580156@qq.com
- * @Date: 2024-11-06 21:43:58
+ * @Date: 2024-12-02 12:28:02
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-06 21:58:31
+ * @LastEditTime: 2024-12-02 12:41:46
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -12,19 +12,20 @@
* 联系:270580156@qq.com
* Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
*/
-package com.bytedesk.service.counter_visitor;
+package com.bytedesk.ai.controller;
-import org.springframework.beans.factory.SmartInitializingSingleton;
-import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
-@Component
-public class CounterVisitorInitializer implements SmartInitializingSingleton {
+@Controller
+@RequestMapping("/ai")
+public class AiRouteController {
- @Override
- public void afterSingletonsInstantiated() {
- init();
+ // http://127.0.0.1:9003/ai/
+ @GetMapping({"", "/"})
+ public String index() {
+ return "ai/index";
}
-
- private void init() {}
}
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/flow/FlowGroup.java b/modules/ai/src/main/java/com/bytedesk/ai/flow/FlowGroup.java
deleted file mode 100644
index 646b5c57f2..0000000000
--- a/modules/ai/src/main/java/com/bytedesk/ai/flow/FlowGroup.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * @Author: jackning 270580156@qq.com
- * @Date: 2024-09-06 15:25:34
- * @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-09-06 15:25:37
- * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
- * Please be aware of the BSL license restrictions before installing Bytedesk IM –
- * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
- * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
- * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
- * contact: 270580156@qq.com
- * 联系:270580156@qq.com
- * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
- */
-package com.bytedesk.ai.flow;
-
-public class FlowGroup {
-
-}
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/flow/FlowResult.java b/modules/ai/src/main/java/com/bytedesk/ai/flow/FlowResult.java
deleted file mode 100644
index eefbb17c5c..0000000000
--- a/modules/ai/src/main/java/com/bytedesk/ai/flow/FlowResult.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * @Author: jackning 270580156@qq.com
- * @Date: 2024-09-06 15:28:51
- * @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-09-06 15:28:54
- * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
- * Please be aware of the BSL license restrictions before installing Bytedesk IM –
- * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
- * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
- * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
- * contact: 270580156@qq.com
- * 联系:270580156@qq.com
- * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
- */
-package com.bytedesk.ai.flow;
-
-public class FlowResult {
-
-}
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/flow/FlowSetting.java b/modules/ai/src/main/java/com/bytedesk/ai/flow/FlowSetting.java
deleted file mode 100644
index 947d6fd658..0000000000
--- a/modules/ai/src/main/java/com/bytedesk/ai/flow/FlowSetting.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * @Author: jackning 270580156@qq.com
- * @Date: 2024-09-06 16:07:35
- * @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-09-06 16:07:38
- * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
- * Please be aware of the BSL license restrictions before installing Bytedesk IM –
- * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
- * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
- * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
- * contact: 270580156@qq.com
- * 联系:270580156@qq.com
- * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
- */
-package com.bytedesk.ai.flow;
-
-public class FlowSetting {
-
-}
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/flow/FlowTheme.java b/modules/ai/src/main/java/com/bytedesk/ai/flow/FlowTheme.java
deleted file mode 100644
index aefa7ed850..0000000000
--- a/modules/ai/src/main/java/com/bytedesk/ai/flow/FlowTheme.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * @Author: jackning 270580156@qq.com
- * @Date: 2024-09-06 16:07:23
- * @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-09-06 16:07:26
- * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
- * Please be aware of the BSL license restrictions before installing Bytedesk IM –
- * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
- * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
- * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
- * contact: 270580156@qq.com
- * 联系:270580156@qq.com
- * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
- */
-package com.bytedesk.ai.flow;
-
-public class FlowTheme {
-
-}
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/flow/FlowVariable.java b/modules/ai/src/main/java/com/bytedesk/ai/flow/FlowVariable.java
deleted file mode 100644
index 7744eef63b..0000000000
--- a/modules/ai/src/main/java/com/bytedesk/ai/flow/FlowVariable.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * @Author: jackning 270580156@qq.com
- * @Date: 2024-09-06 16:06:57
- * @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-09-06 16:07:00
- * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
- * Please be aware of the BSL license restrictions before installing Bytedesk IM –
- * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
- * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
- * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
- * contact: 270580156@qq.com
- * 联系:270580156@qq.com
- * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
- */
-package com.bytedesk.ai.flow;
-
-public class FlowVariable {
-
-}
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/model/LlmModelJsonService.java b/modules/ai/src/main/java/com/bytedesk/ai/model/LlmModelJsonLoader.java
similarity index 96%
rename from modules/ai/src/main/java/com/bytedesk/ai/model/LlmModelJsonService.java
rename to modules/ai/src/main/java/com/bytedesk/ai/model/LlmModelJsonLoader.java
index fbed97c879..155cf8d2fb 100644
--- a/modules/ai/src/main/java/com/bytedesk/ai/model/LlmModelJsonService.java
+++ b/modules/ai/src/main/java/com/bytedesk/ai/model/LlmModelJsonLoader.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-09-24 15:26:54
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-12 12:07:12
+ * @LastEditTime: 2024-12-11 17:22:02
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -31,7 +31,7 @@ import org.springframework.stereotype.Service;
@Slf4j
@Service
-public class LlmModelJsonService {
+public class LlmModelJsonLoader {
@Autowired
private ResourceLoader resourceLoader;
@@ -39,7 +39,7 @@ public class LlmModelJsonService {
// 加载models.json,模型列表
public Map> loadModels() {
try {
- Resource resource = resourceLoader.getResource("classpath:json/models.json");
+ Resource resource = resourceLoader.getResource("classpath:ai/models.json");
ObjectMapper objectMapper = new ObjectMapper();
MapType mapType = objectMapper.getTypeFactory().constructMapType(
Map.class, // 或者使用具体的Map实现类,如LinkedHashMap.class
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/model/LlmModelRestService.java b/modules/ai/src/main/java/com/bytedesk/ai/model/LlmModelRestService.java
index 93dd24751a..03094ec5d8 100644
--- a/modules/ai/src/main/java/com/bytedesk/ai/model/LlmModelRestService.java
+++ b/modules/ai/src/main/java/com/bytedesk/ai/model/LlmModelRestService.java
@@ -27,7 +27,7 @@ import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
-import com.bytedesk.ai.model.LlmModelJsonService.ModelJson;
+import com.bytedesk.ai.model.LlmModelJsonLoader.ModelJson;
import com.bytedesk.core.base.BaseRestService;
import com.bytedesk.core.constant.BytedeskConsts;
import com.bytedesk.core.uid.UidUtils;
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/provider/LlmProviderInitializer.java b/modules/ai/src/main/java/com/bytedesk/ai/provider/LlmProviderInitializer.java
index a1f74029f6..c616dd341b 100644
--- a/modules/ai/src/main/java/com/bytedesk/ai/provider/LlmProviderInitializer.java
+++ b/modules/ai/src/main/java/com/bytedesk/ai/provider/LlmProviderInitializer.java
@@ -21,10 +21,10 @@ import java.util.Optional;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.stereotype.Component;
-import com.bytedesk.ai.model.LlmModelJsonService;
-import com.bytedesk.ai.model.LlmModelJsonService.ModelJson;
+import com.bytedesk.ai.model.LlmModelJsonLoader;
+import com.bytedesk.ai.model.LlmModelJsonLoader.ModelJson;
import com.bytedesk.ai.model.LlmModelRestService;
-import com.bytedesk.ai.provider.LlmProviderJsonService.ProviderJson;
+import com.bytedesk.ai.provider.LlmProviderJsonLoader.ProviderJson;
import com.bytedesk.core.enums.LevelEnum;
import lombok.AllArgsConstructor;
@@ -37,11 +37,11 @@ public class LlmProviderInitializer implements SmartInitializingSingleton {
private final LlmProviderRestService llmProviderService;
- private final LlmProviderJsonService llmProviderJsonService;
+ private final LlmProviderJsonLoader llmProviderJsonService;
private final LlmModelRestService llmModelService;
- private final LlmModelJsonService llmModelJsonService;
+ private final LlmModelJsonLoader llmModelJsonService;
@Override
public void afterSingletonsInstantiated() {
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/provider/LlmProviderJsonService.java b/modules/ai/src/main/java/com/bytedesk/ai/provider/LlmProviderJsonLoader.java
similarity index 96%
rename from modules/ai/src/main/java/com/bytedesk/ai/provider/LlmProviderJsonService.java
rename to modules/ai/src/main/java/com/bytedesk/ai/provider/LlmProviderJsonLoader.java
index 8f5b662f34..1e6cf351cc 100644
--- a/modules/ai/src/main/java/com/bytedesk/ai/provider/LlmProviderJsonService.java
+++ b/modules/ai/src/main/java/com/bytedesk/ai/provider/LlmProviderJsonLoader.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-09-24 15:26:54
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-12 11:40:43
+ * @LastEditTime: 2024-12-11 17:21:46
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -30,14 +30,14 @@ import org.springframework.stereotype.Service;
@Slf4j
@Service
-public class LlmProviderJsonService {
+public class LlmProviderJsonLoader {
@Autowired
private ResourceLoader resourceLoader;
public Map loadProviders() {
try {
- Resource resource = resourceLoader.getResource("classpath:json/providers.json");
+ Resource resource = resourceLoader.getResource("classpath:ai/providers.json");
ObjectMapper objectMapper = new ObjectMapper();
MapType mapType = objectMapper.getTypeFactory().constructMapType(
Map.class, // 或者使用具体的Map实现类,如LinkedHashMap.class
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/provider/LlmProviderRestService.java b/modules/ai/src/main/java/com/bytedesk/ai/provider/LlmProviderRestService.java
index b43205c8f0..6413eb45cb 100644
--- a/modules/ai/src/main/java/com/bytedesk/ai/provider/LlmProviderRestService.java
+++ b/modules/ai/src/main/java/com/bytedesk/ai/provider/LlmProviderRestService.java
@@ -26,7 +26,7 @@ import org.springframework.data.jpa.domain.Specification;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.springframework.stereotype.Service;
-import com.bytedesk.ai.provider.LlmProviderJsonService.ProviderJson;
+import com.bytedesk.ai.provider.LlmProviderJsonLoader.ProviderJson;
import com.bytedesk.core.base.BaseRestService;
import com.bytedesk.core.constant.AvatarConsts;
import com.bytedesk.core.constant.BytedeskConsts;
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotEntity.java b/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotEntity.java
index d2ab6e3374..ce61470eac 100644
--- a/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotEntity.java
+++ b/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotEntity.java
@@ -81,7 +81,7 @@ public class RobotEntity extends BaseEntity {
private String level = LevelEnum.ORGANIZATION.name();
// @Builder.Default
- // @Column(columnDefinition = TypeConsts.COLUMN_TYPE_JSON)
+ // @Column(columnDefinition = TypeConsts.COLUMN_TYPE_JSONB)
// @JdbcTypeCode(SqlTypes.JSON)
// private String flow = BytedeskConsts.EMPTY_JSON_STRING;
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotJsonService.java b/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotJsonLoader.java
similarity index 95%
rename from modules/ai/src/main/java/com/bytedesk/ai/robot/RobotJsonService.java
rename to modules/ai/src/main/java/com/bytedesk/ai/robot/RobotJsonLoader.java
index 0bebd58a43..2cddb1730d 100644
--- a/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotJsonService.java
+++ b/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotJsonLoader.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-09-24 15:26:54
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-23 13:04:18
+ * @LastEditTime: 2024-12-11 17:20:19
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -28,7 +28,7 @@ import org.springframework.stereotype.Service;
@Slf4j
@Service
-public class RobotJsonService {
+public class RobotJsonLoader {
@Autowired
private ResourceLoader resourceLoader;
@@ -36,7 +36,7 @@ public class RobotJsonService {
// 加载robots.json,智能体列表
public List loadRobots() {
try {
- Resource resource = resourceLoader.getResource("classpath:json/robots.json");
+ Resource resource = resourceLoader.getResource("classpath:ai/robots.json");
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(resource.getInputStream(),
objectMapper.getTypeFactory().constructCollectionType(List.class, RobotJson.class));
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotRestController.java b/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotRestController.java
index 8d40d39f4c..0a7bd29296 100644
--- a/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotRestController.java
+++ b/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotRestController.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-03-22 16:37:01
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-19 13:42:02
+ * @LastEditTime: 2024-12-03 12:49:18
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -16,7 +16,6 @@ package com.bytedesk.ai.robot;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
-import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@@ -25,7 +24,6 @@ import org.springframework.web.bind.annotation.RestController;
import com.bytedesk.core.action.ActionAnnotation;
import com.bytedesk.core.base.BaseRestController;
-import com.bytedesk.core.rbac.role.RolePermissions;
import com.bytedesk.core.thread.ThreadRequest;
import com.bytedesk.core.thread.ThreadResponse;
import com.bytedesk.core.utils.JsonResult;
@@ -41,7 +39,7 @@ public class RobotRestController extends BaseRestController {
private final RobotRestService robotService;
- @PreAuthorize(RolePermissions.ROLE_ADMIN)
+ // @PreAuthorize(RolePermissions.ROLE_ADMIN) // not needed
@Override
public ResponseEntity> queryByOrg(RobotRequest request) {
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotRestService.java b/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotRestService.java
index fce5358b00..3db4725d10 100644
--- a/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotRestService.java
+++ b/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotRestService.java
@@ -31,7 +31,7 @@ import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import com.alibaba.fastjson2.JSON;
-import com.bytedesk.ai.robot.RobotJsonService.RobotJson;
+import com.bytedesk.ai.robot.RobotJsonLoader.RobotJson;
import com.bytedesk.ai.settings.RobotServiceSettings;
import com.bytedesk.ai.utils.ConvertAiUtils;
import com.bytedesk.core.base.BaseRestService;
@@ -74,7 +74,7 @@ public class RobotRestService extends BaseRestService threadOptional = threadService.findByTopicAndOwner(topic, owner);
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/utils/ConvertAiUtils.java b/modules/ai/src/main/java/com/bytedesk/ai/utils/ConvertAiUtils.java
index e281369676..df2164bf55 100644
--- a/modules/ai/src/main/java/com/bytedesk/ai/utils/ConvertAiUtils.java
+++ b/modules/ai/src/main/java/com/bytedesk/ai/utils/ConvertAiUtils.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-06-06 11:28:01
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-09-26 14:47:27
+ * @LastEditTime: 2024-12-05 13:10:21
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -16,6 +16,7 @@ package com.bytedesk.ai.utils;
import org.modelmapper.ModelMapper;
+import com.alibaba.fastjson2.JSON;
import com.bytedesk.ai.robot.RobotEntity;
import com.bytedesk.ai.robot.RobotResponse;
import com.bytedesk.ai.robot.RobotProtobuf;
@@ -44,6 +45,10 @@ public class ConvertAiUtils {
return userProtobuf;
}
+ public static String convertToUserProtobufString(RobotEntity entity) {
+ return JSON.toJSONString(convertToUserProtobuf(entity));
+ }
+
public static ServiceSettingsResponseVisitor convertToServiceSettingsResponseVisitor(
RobotServiceSettings serviceSettings) {
return modelMapper.map(serviceSettings, ServiceSettingsResponseVisitor.class);
diff --git a/modules/ai/src/main/resources/templates/ftl/ai/index.ftl b/modules/ai/src/main/resources/templates/ftl/ai/index.ftl
new file mode 100644
index 0000000000..dc902235d0
--- /dev/null
+++ b/modules/ai/src/main/resources/templates/ftl/ai/index.ftl
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ai
+
+
+
+
\ No newline at end of file
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/flow/FlowEvent.java b/modules/core/TODO.md
similarity index 84%
rename from modules/ai/src/main/java/com/bytedesk/ai/flow/FlowEvent.java
rename to modules/core/TODO.md
index b09edd3fc3..4730394cb1 100644
--- a/modules/ai/src/main/java/com/bytedesk/ai/flow/FlowEvent.java
+++ b/modules/core/TODO.md
@@ -1,8 +1,8 @@
-/*
+
+# TODO
-public class FlowEvent {
-
-}
+- [X] finished
+- [ ] todo
diff --git a/modules/core/src/main/java/com/bytedesk/core/action/ActionAspect.java b/modules/core/src/main/java/com/bytedesk/core/action/ActionAspect.java
index 1c0982d389..55a877107f 100644
--- a/modules/core/src/main/java/com/bytedesk/core/action/ActionAspect.java
+++ b/modules/core/src/main/java/com/bytedesk/core/action/ActionAspect.java
@@ -45,7 +45,7 @@ import lombok.extern.slf4j.Slf4j;
@AllArgsConstructor
public class ActionAspect {
- private final ActionService actionService;
+ private final ActionRestService actionService;
private final IpService ipService;
diff --git a/modules/core/src/main/java/com/bytedesk/core/action/ActionEntity.java b/modules/core/src/main/java/com/bytedesk/core/action/ActionEntity.java
index d301c6acab..0eb1b083bb 100644
--- a/modules/core/src/main/java/com/bytedesk/core/action/ActionEntity.java
+++ b/modules/core/src/main/java/com/bytedesk/core/action/ActionEntity.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-04-25 15:31:38
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-10-22 12:07:01
+ * @LastEditTime: 2024-12-04 14:13:53
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -82,10 +82,7 @@ public class ActionEntity extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY)
private UserEntity user;
-
@Builder.Default
- // @Enumerated(EnumType.STRING)
- // private PlatformEnum platform = PlatformEnum.BYTEDESK;
private String platform = PlatformEnum.BYTEDESK.name();
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/action/ActionController.java b/modules/core/src/main/java/com/bytedesk/core/action/ActionRestController.java
similarity index 90%
rename from modules/core/src/main/java/com/bytedesk/core/action/ActionController.java
rename to modules/core/src/main/java/com/bytedesk/core/action/ActionRestController.java
index 5043d3e59d..dd43660aeb 100644
--- a/modules/core/src/main/java/com/bytedesk/core/action/ActionController.java
+++ b/modules/core/src/main/java/com/bytedesk/core/action/ActionRestController.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-04-25 15:40:19
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-05-31 07:39:58
+ * @LastEditTime: 2024-12-04 14:08:57
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -16,7 +16,6 @@ package com.bytedesk.core.action;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -28,11 +27,10 @@ import lombok.AllArgsConstructor;
@RestController
@AllArgsConstructor
@RequestMapping("/api/v1/action")
-public class ActionController extends BaseRestController {
+public class ActionRestController extends BaseRestController {
- private final ActionService actionService;
+ private final ActionRestService actionService;
- @GetMapping("/query/org")
@Override
public ResponseEntity> queryByOrg(ActionRequest request) {
diff --git a/modules/core/src/main/java/com/bytedesk/core/action/ActionService.java b/modules/core/src/main/java/com/bytedesk/core/action/ActionRestService.java
similarity index 95%
rename from modules/core/src/main/java/com/bytedesk/core/action/ActionService.java
rename to modules/core/src/main/java/com/bytedesk/core/action/ActionRestService.java
index b1f9cf5e3c..ef6c23ddf5 100644
--- a/modules/core/src/main/java/com/bytedesk/core/action/ActionService.java
+++ b/modules/core/src/main/java/com/bytedesk/core/action/ActionRestService.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-04-25 15:41:47
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-10-23 18:11:59
+ * @LastEditTime: 2024-12-04 14:09:24
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -35,7 +35,7 @@ import lombok.AllArgsConstructor;
@Service
@AllArgsConstructor
-public class ActionService extends BaseRestService {
+public class ActionRestService extends BaseRestService {
private final ActionRepository actionRepository;
@@ -50,7 +50,7 @@ public class ActionService extends BaseRestService {
diff --git a/modules/core/src/main/java/com/bytedesk/core/black/BlackService.java b/modules/core/src/main/java/com/bytedesk/core/black/BlackService.java
index 332885ce6c..e7a6afcc1c 100644
--- a/modules/core/src/main/java/com/bytedesk/core/black/BlackService.java
+++ b/modules/core/src/main/java/com/bytedesk/core/black/BlackService.java
@@ -72,7 +72,7 @@ public class BlackService extends BaseRestService, JpaSpecificationExecutor {
Optional findByUid(String uid);
@@ -38,4 +41,13 @@ public interface CategoryRepository extends JpaRepository,
Boolean existsByUid(String uid);
+ @Modifying
+ @Transactional
+ @Query("UPDATE CategoryEntity c SET c.postCount = c.postCount + 1 WHERE c.id = ?1")
+ void incrementPostCount(Long categoryId);
+
+ @Modifying
+ @Transactional
+ @Query("UPDATE CategoryEntity c SET c.postCount = c.postCount - 1 WHERE c.id = ?1")
+ void decrementPostCount(Long categoryId);
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/config/BytedeskProperties.java b/modules/core/src/main/java/com/bytedesk/core/config/BytedeskProperties.java
index c41e7c83d9..894b0e9105 100644
--- a/modules/core/src/main/java/com/bytedesk/core/config/BytedeskProperties.java
+++ b/modules/core/src/main/java/com/bytedesk/core/config/BytedeskProperties.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-30 09:14:39
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-19 11:51:23
+ * @LastEditTime: 2024-11-28 17:52:27
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -18,6 +18,7 @@ import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
@@ -100,12 +101,24 @@ public class BytedeskProperties {
private List clusterNodes = new ArrayList<>();
//
- public Boolean isAdmin(String receiver) {
+ public Boolean isAdmin(@NonNull String receiver) {
+ // receiver 是否为空
+ if (receiver == null || receiver.isEmpty()) {
+ return false;
+ }
+ // receiver 是否等于当前用户的手机号或者邮箱
return receiver.equals(this.mobile) || receiver.equals(this.email);
}
//
- public Boolean isInWhitelist(String receiver) {
+ public Boolean isInWhitelist(@NonNull String receiver) {
+ // receiver 是否为空
+ if (receiver == null || receiver.isEmpty()) {
+ return false;
+ }
+ if (this.mobileWhitelist == null || this.emailWhiteList == null) {
+ return false;
+ }
return this.mobileWhitelist.contains(receiver) || this.emailWhiteList.contains(receiver);
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/config/DatabaseTypeChecker.java b/modules/core/src/main/java/com/bytedesk/core/config/DatabaseTypeChecker.java
new file mode 100644
index 0000000000..0e5ab686c1
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/config/DatabaseTypeChecker.java
@@ -0,0 +1,89 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-12-06 07:59:37
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-12-06 09:59:35
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.config;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import jakarta.annotation.PostConstruct;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Component
+public class DatabaseTypeChecker {
+
+ @Value("${spring.datasource.url}")
+ private String dataSourceUrl;
+
+ @Autowired
+ private DataSource dataSource;
+
+ @PostConstruct
+ public void checkDatabaseType() {
+ String databaseType = getDatabaseType();
+ log.info("checkDatabaseType Connected to: {}", databaseType);
+ }
+
+ public String getDatabaseType() {
+ if (dataSourceUrl.contains("mysql")) {
+ return "MySQL";
+ } else if (dataSourceUrl.contains("postgresql")) {
+ return "PostgreSQL";
+ } else if (dataSourceUrl.contains("oracle")) {
+ return "Oracle";
+ } else {
+ return "Unknown";
+ }
+ }
+
+ public String getDatabaseTypeByDatasource() {
+ String dataSourceClassName = dataSource.getClass().getName();
+ if (dataSourceClassName.contains("Mysql") || dataSourceClassName.contains("MySQL")) {
+ return "MySQL";
+ } else if (dataSourceClassName.contains("Postgres") || dataSourceClassName.contains("PostgreSQL")) {
+ return "PostgreSQL";
+ } else if (dataSourceClassName.contains("Oracle")) {
+ return "Oracle";
+ } else {
+ return "Unknown";
+ }
+ }
+
+ public String getDatabaseTypeByDatasourceMetaData() {
+ try (Connection connection = dataSource.getConnection()) {
+ DatabaseMetaData metaData = connection.getMetaData();
+ String databaseProductName = metaData.getDatabaseProductName();
+ if (databaseProductName.contains("MySQL")) {
+ return "MySQL";
+ } else if (databaseProductName.contains("PostgreSQL")) {
+ return "PostgreSQL";
+ } else if (databaseProductName.contains("Oracle")) {
+ return "Oracle";
+ } else {
+ return "Unknown";
+ }
+ } catch (SQLException e) {
+ e.printStackTrace();
+ return "Error";
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/core/src/main/java/com/bytedesk/core/constant/AvatarConsts.java b/modules/core/src/main/java/com/bytedesk/core/constant/AvatarConsts.java
index b0d121dace..e5212331a4 100644
--- a/modules/core/src/main/java/com/bytedesk/core/constant/AvatarConsts.java
+++ b/modules/core/src/main/java/com/bytedesk/core/constant/AvatarConsts.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:21:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-11 16:44:26
+ * @LastEditTime: 2024-12-05 07:47:24
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -25,129 +25,56 @@ public class AvatarConsts {
// Prevents instantiation
private AvatarConsts() {}
- /**
- * default avatar
- * 默认注册用户头像URL
- */
- public static final String DEFAULT_AVATAR_URL = "https://cdn.weiyuai.cn/avatars/admin.png";
+ // replace with your own CDN host address
+ public static final String DEFAULT_HOST = "https://cdn.weiyuai.cn";
- /**
- * default agent avatar
- * 默认客服头像
- */
- public static final String DEFAULT_AGENT_AVATAR_URL = "https://cdn.weiyuai.cn/avatars/agent_default_avatar.png";
+ public static final String DEFAULT_AVATAR_URL = DEFAULT_HOST + "/avatars/admin.png";
- /**
- * default visitor avatar
- * 默认访客头像
- */
- public static final String DEFAULT_VISITOR_AVATAR_URL = "https://cdn.weiyuai.cn/avatars/visitor_default_avatar.png";
+ public static final String DEFAULT_AGENT_AVATAR_URL = DEFAULT_HOST + "/avatars/agent.png";
- /**
- * default uniapp avatar
- * 默认UNIAPP头像
- */
- public static final String DEFAULT_UNIAPP_AVATAR_URL = "https://cdn.weiyuai.cn/avatars/uniapp.png";
+ public static final String DEFAULT_VISITOR_AVATAR_URL = DEFAULT_HOST + "/avatars/visitor_default_avatar.png";
- /**
- * default im avatar
- * 默认IM注册用户头像
- */
- public static final String DEFAULT_USER_AVATAR_URL = "https://cdn.weiyuai.cn/avatars/visitor_default_avatar.png";
+ public static final String DEFAULT_UNIAPP_AVATAR_URL = DEFAULT_HOST + "/avatars/uniapp.png";
- /**
- * default web avatar
- * web访客默认头像
- */
- public static final String DEFAULT_WEB_AVATAR_URL = "https://cdn.weiyuai.cn/avatars/chrome.png";
+ public static final String DEFAULT_USER_AVATAR_URL = DEFAULT_HOST + "/avatars/user.png";
- /**
- * default wap avatar
- * wap访客默认头像
- */
- public static final String DEFAULT_WAP_AVATAR_URL = "https://cdn.weiyuai.cn/avatars/chrome.png";
+ public static final String DEFAULT_WEB_AVATAR_URL = DEFAULT_HOST + "/avatars/chrome.png";
- /**
- * default wechat avatar
- * wechat访客默认头像-公众号
- */
- public static final String DEFAULT_WECHAT_MP_AVATAR_URL = "https://cdn.weiyuai.cn/avatars/wechat.png";
+ public static final String DEFAULT_WAP_AVATAR_URL = DEFAULT_HOST + "/avatars/chrome.png";
- /**
- * default wechat mini avatar
- * wechat访客默认头像-小程序
- */
- public static final String DEFAULT_WECHAT_MINI_AVATAR_URL = "https://cdn.weiyuai.cn/avatars/wechat.png";
+ public static final String DEFAULT_WECHAT_MP_AVATAR_URL = DEFAULT_HOST + "/avatars/wechat.png";
- /**
- * default wechat kf avatar
- * wechat访客默认头像-微信客服
- */
- public static final String DEFAULT_WECHAT_KEFU_AVATAR_URL = "https://cdn.weiyuai.cn/avatars/wechat.png";
+ public static final String DEFAULT_WECHAT_MINI_AVATAR_URL = DEFAULT_HOST + "/avatars/wechat.png";
- /**
- * default android avatar
- * 安卓访客默认头像
- */
- public static final String DEFAULT_ANDROID_AVATAR_URL = "https://cdn.weiyuai.cn/avatars/android.png";
+ public static final String DEFAULT_WECHAT_KEFU_AVATAR_URL = DEFAULT_HOST + "/avatars/wechat.png";
- /**
- * default ios avatar
- * 苹果访客默认头像
- */
- public static final String DEFAULT_IOS_AVATAR_URL = "https://cdn.weiyuai.cn/avatars/apple.png";
+ public static final String DEFAULT_ANDROID_AVATAR_URL = DEFAULT_HOST + "/avatars/android.png";
- /**
- * default flutter-android avatar
- * Flutter-安卓访客默认头像
- */
- public static final String DEFAULT_FLUTTER_ANDROID_AVATAR_URL = "https://cdn.weiyuai.cn/avatars/android.png";
+ public static final String DEFAULT_IOS_AVATAR_URL = DEFAULT_HOST + "/avatars/apple.png";
- /**
- * default flutter-ios avatar
- * Flutter-苹果访客默认头像
- */
- public static final String DEFAULT_FLUTTER_IOS_AVATAR_URL = "https://cdn.weiyuai.cn/avatars/apple.png";
+ public static final String DEFAULT_FLUTTER_ANDROID_AVATAR_URL = DEFAULT_HOST + "/avatars/android.png";
- /**
- * default flutter-web avatar
- * Flutter-WEB访客默认头像
- */
- public static final String DEFAULT_FLUTTER_WEB_AVATAR_URL = "https://cdn.weiyuai.cn/avatars/chrome.png";
+ public static final String DEFAULT_FLUTTER_IOS_AVATAR_URL = DEFAULT_HOST + "/avatars/apple.png";
- /**
- * default system avatar
- * 默认系统头像
- */
- public static final String DEFAULT_SYSTEM_AVATAR_URL = "https://cdn.weiyuai.cn/avatars/system_default_avatar.png";
+ public static final String DEFAULT_FLUTTER_WEB_AVATAR_URL = DEFAULT_HOST + "/avatars/chrome.png";
- /**
- * default work group avatar
- * 默认工作组头像
- */
- public static final String DEFAULT_WORK_GROUP_AVATAR_URL = "https://cdn.weiyuai.cn/avatars/workgroup_default_avatar.png";
+ public static final String DEFAULT_SYSTEM_AVATAR_URL = DEFAULT_HOST + "/avatars/system_default_avatar.png";
- /**
- * default group avatar
- * 默认群组头像
- */
- public static final String DEFAULT_GROUP_AVATAR_URL = "https://cdn.weiyuai.cn/avatars/group.png";
+ public static final String DEFAULT_WORK_GROUP_AVATAR_URL = DEFAULT_HOST + "/avatars/workgroup_default_avatar.png";
+
+ public static final String DEFAULT_GROUP_AVATAR_URL = DEFAULT_HOST + "/avatars/group.png";
- // 文件助手头像
- public static final String DEFAULT_FILE_ASSISTANT_AVATAR_URL = "https://cdn.weiyuai.cn/avatars/file.png";
+ public static final String DEFAULT_FILE_ASSISTANT_AVATAR_URL = DEFAULT_HOST + "/avatars/file.png";
- // 系统通知-公众号头像
- public static final String DEFAULT_SYSTEM_NOTIFICATION_AVATAR_URL = "https://cdn.weiyuai.cn/avatars/notification.png";
+ public static final String DEFAULT_SYSTEM_NOTIFICATION_AVATAR_URL = DEFAULT_HOST + "/avatars/notification.png";
- // 创建LLM对话默认使用智谱AI作为头像
- public static final String LLM_THREAD_DEFAULT_AVATAR = "https://cdn.weiyuai.cn/assets/images/llm/provider/zhipu.png";
- public static final String LLM_THREAD_DEFAULT_AVATAR_BASE_URL = "https://cdn.weiyuai.cn/assets/images/llm/provider/";
+ public static final String LLM_THREAD_DEFAULT_AVATAR = DEFAULT_HOST + "/assets/images/llm/provider/zhipu.png";
- // 机器人头像
- public static final String DEFAULT_ROBOT_AVATAR = "https://cdn.weiyuai.cn/avatars/robot.png";
+ public static final String LLM_THREAD_DEFAULT_AVATAR_BASE_URL = DEFAULT_HOST + "/assets/images/llm/provider/";
+
+ public static final String DEFAULT_ROBOT_AVATAR = DEFAULT_HOST + "/avatars/robot.png";
/**
- * 测试头像:
* https://cdn.weiyuai.cn/avatars/girl.png
* https://cdn.weiyuai.cn/avatars/boy.png
*/
diff --git a/modules/core/src/main/java/com/bytedesk/core/constant/BytedeskConsts.java b/modules/core/src/main/java/com/bytedesk/core/constant/BytedeskConsts.java
index 7f556b79bc..06f18d3ca0 100644
--- a/modules/core/src/main/java/com/bytedesk/core/constant/BytedeskConsts.java
+++ b/modules/core/src/main/java/com/bytedesk/core/constant/BytedeskConsts.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-02-02 21:48:19
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-05 14:17:24
+ * @LastEditTime: 2024-11-28 20:59:59
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -14,8 +14,6 @@
*/
package com.bytedesk.core.constant;
-// import java.io.File;
-
/**
*
* @author bytedesk.com
diff --git a/modules/core/src/main/java/com/bytedesk/core/constant/I18Consts.java b/modules/core/src/main/java/com/bytedesk/core/constant/I18Consts.java
index 2d0b1243d1..42c15af6eb 100644
--- a/modules/core/src/main/java/com/bytedesk/core/constant/I18Consts.java
+++ b/modules/core/src/main/java/com/bytedesk/core/constant/I18Consts.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-04-26 22:25:47
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-10-30 12:23:34
+ * @LastEditTime: 2024-12-03 12:32:19
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -28,6 +28,12 @@ public class I18Consts {
// public static final String ZH_TW = "zh-TW";
public static final String I18N_PREFIX = "i18n.";
+ // captcha
+ public static final String I18N_AUTH_CAPTCHA_SEND_SUCCESS = I18N_PREFIX + "auth.captcha.send.success";
+ public static final String I18N_AUTH_CAPTCHA_ERROR = I18N_PREFIX + "auth.captcha.error";
+ public static final String I18N_AUTH_CAPTCHA_EXPIRED = I18N_PREFIX + "auth.captcha.expired";
+ public static final String I18N_AUTH_CAPTCHA_ALREADY_SEND = I18N_PREFIX + "auth.captcha.already.send";
+ public static final String I18N_AUTH_CAPTCHA_VALIDATE_FAILED = I18N_PREFIX + "auth.captcha.validate.failed";
// "文件助手"
public static final String I18N_FILE_ASSISTANT_NAME = I18N_PREFIX + "file.assistant";
// "手机、电脑文件互传"
@@ -47,6 +53,7 @@ public class I18Consts {
public static final String I18N_TOP_TIP = I18N_PREFIX + "top.tip";
public static final String I18N_LEAVEMSG_TIP = I18N_PREFIX + "leavemsg.tip";
public static final String I18N_REENTER_TIP = I18N_PREFIX + "reenter.tip";
+ public static final String I18N_QUEUE_TIP = I18N_PREFIX + "queue.tip";
//
public static final String I18N_WORKGROUP_NICKNAME = I18N_PREFIX + "workgroup.nickname";
public static final String I18N_WORKGROUP_DESCRIPTION = I18N_PREFIX + "workgroup.description";
diff --git a/modules/core/src/main/java/com/bytedesk/core/constant/TypeConsts.java b/modules/core/src/main/java/com/bytedesk/core/constant/TypeConsts.java
index ba9db048a6..0507bea28a 100644
--- a/modules/core/src/main/java/com/bytedesk/core/constant/TypeConsts.java
+++ b/modules/core/src/main/java/com/bytedesk/core/constant/TypeConsts.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:21:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-05 18:10:47
+ * @LastEditTime: 2024-12-11 10:22:58
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -102,7 +102,7 @@ public class TypeConsts {
//
public static final String COLUMN_TYPE_TEXT = "TEXT"; // length = 65534
- public static final String COLUMN_TYPE_JSON = "json"; //
+ public static final String COLUMN_TYPE_JSON = "json"; // replace with jsonb?
//
// public static final String ACTION_TYPE_FAILED = "failed";
// public static final String ACTION_TYPE_LOG = "log";
diff --git a/modules/core/src/main/java/com/bytedesk/core/enums/ClientEnum.java b/modules/core/src/main/java/com/bytedesk/core/enums/ClientEnum.java
index d274e6dd40..2f8c2b5681 100644
--- a/modules/core/src/main/java/com/bytedesk/core/enums/ClientEnum.java
+++ b/modules/core/src/main/java/com/bytedesk/core/enums/ClientEnum.java
@@ -36,6 +36,9 @@ public enum ClientEnum {
FLUTTER_WEB,
FLUTTER_ANDROID,
FLUTTER_IOS,
+ FLUTTER_MACOS,
+ FLUTTER_WINDOWS,
+ FLUTTER_LINUX,
//
UNIAPP,
UNIAPP_WEB,
diff --git a/modules/core/src/main/java/com/bytedesk/core/enums/DatabaseTypeEnum.java b/modules/core/src/main/java/com/bytedesk/core/enums/DatabaseTypeEnum.java
new file mode 100644
index 0000000000..b1aca18910
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/enums/DatabaseTypeEnum.java
@@ -0,0 +1,8 @@
+package com.bytedesk.core.enums;
+
+public enum DatabaseTypeEnum {
+ MYSQL,
+ POSTGRESQL,
+ ORACLE,
+ SQLSERVER;
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/exception/GlobalControllerAdvice.java b/modules/core/src/main/java/com/bytedesk/core/exception/GlobalControllerAdvice.java
index 958bba9486..016592e8e5 100644
--- a/modules/core/src/main/java/com/bytedesk/core/exception/GlobalControllerAdvice.java
+++ b/modules/core/src/main/java/com/bytedesk/core/exception/GlobalControllerAdvice.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-04-26 09:31:29
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-13 23:30:53
+ * @LastEditTime: 2024-11-28 17:46:33
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -109,6 +109,7 @@ public class GlobalControllerAdvice {
@ExceptionHandler(RuntimeException.class)
public ResponseEntity> handleRuntimeException(RuntimeException e) {
// 方便测试,打印异常堆栈信息
+ e.printStackTrace();
log.error("not handled exception:", e.getMessage());
// e.printStackTrace();
return ResponseEntity.ok().body(JsonResult.error(e.getMessage()));
@@ -121,6 +122,8 @@ public class GlobalControllerAdvice {
@ExceptionHandler(value = NullPointerException.class)
public ResponseEntity> handleNullPointerException(NullPointerException ex) {
+ log.error("not handled exception:", ex);
+ // ex.printStackTrace();
return ResponseEntity.badRequest().body(JsonResult.error("Null Pointer Exception"));
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/ip/IpRestController.java b/modules/core/src/main/java/com/bytedesk/core/ip/IpRestController.java
index db3dd0567c..a9e50b14e0 100644
--- a/modules/core/src/main/java/com/bytedesk/core/ip/IpRestController.java
+++ b/modules/core/src/main/java/com/bytedesk/core/ip/IpRestController.java
@@ -65,7 +65,7 @@ public class IpRestController {
*
* @return json
*/
- @GetMapping("/")
+ @GetMapping({"", "/"})
public JsonResult> ip(HttpServletRequest request) {
return new JsonResult<>("your ip", 200, IpUtils.getClientIp(request));
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/kaptcha/KaptchaCacheService.java b/modules/core/src/main/java/com/bytedesk/core/kaptcha/KaptchaCacheService.java
index a6db35df91..22c72f6542 100644
--- a/modules/core/src/main/java/com/bytedesk/core/kaptcha/KaptchaCacheService.java
+++ b/modules/core/src/main/java/com/bytedesk/core/kaptcha/KaptchaCacheService.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-04-16 17:48:50
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-12 16:04:35
+ * @LastEditTime: 2024-11-29 10:58:39
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -16,47 +16,59 @@ package com.bytedesk.core.kaptcha;
import java.util.concurrent.TimeUnit;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
-import com.github.benmanes.caffeine.cache.Cache;
-import com.github.benmanes.caffeine.cache.Caffeine;
-// import lombok.extern.slf4j.Slf4j;
+import com.bytedesk.core.enums.ClientEnum;
+import com.bytedesk.core.redis.RedisConsts;
-/**
- * TODO: 用Redis替代
- */
-// @Slf4j
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
@Service
-// @AllArgsConstructor
+@AllArgsConstructor
public class KaptchaCacheService {
+ private final StringRedisTemplate stringRedisTemplate;
+
// 验证码5分钟过期
- Cache kaptchaCache = Caffeine.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).build();
+ private static final long EXPIRE_TIME = 5; // 5分钟
+
+ // 验证码5分钟过期
+ // Cache kaptchaCache = Caffeine.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).build();
public void putKaptcha(String key, String value) {
- kaptchaCache.put(key, value);
+ // kaptchaCache.put(key, value);
+ stringRedisTemplate.opsForValue().set(RedisConsts.BYTEDESK_REDIS_PREFIX + key, value, EXPIRE_TIME, TimeUnit.MINUTES);
}
public Boolean hasKaptcha(String key) {
- return kaptchaCache.getIfPresent(key) != null;
+ // return kaptchaCache.getIfPresent(key) != null;
+ return stringRedisTemplate.hasKey(RedisConsts.BYTEDESK_REDIS_PREFIX + key);
}
public String getKaptcha(String key) {
- return kaptchaCache.getIfPresent(key);
+ // return kaptchaCache.getIfPresent(key);
+ return stringRedisTemplate.opsForValue().get(RedisConsts.BYTEDESK_REDIS_PREFIX + key);
}
- public Boolean checkKaptcha(String key, String value, String client) {
+ public Boolean checkKaptcha(String key, String value, @NonNull String client) {
// flutter手机端验证码暂时不做校验, TODO: 后续需要优化
- if (client != null && client.contains("flutter")) {
+ if (client != null && client.toLowerCase().contains(ClientEnum.FLUTTER.name().toLowerCase())) {
return true;
}
+ log.info("checkKaptcha key: " + key + ", value: " + value);
- String cachedValue = kaptchaCache.getIfPresent(key);
+ // String cachedValue = kaptchaCache.getIfPresent(key);
+ String cachedValue = stringRedisTemplate.opsForValue().get(RedisConsts.BYTEDESK_REDIS_PREFIX + key);
return cachedValue != null && cachedValue.equals(value);
}
public void removeKaptcha(String key) {
- kaptchaCache.invalidate(key);
+ // kaptchaCache.invalidate(key);
+ stringRedisTemplate.delete(RedisConsts.BYTEDESK_REDIS_PREFIX + key);
}
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/kaptcha/KaptchaRestController.java b/modules/core/src/main/java/com/bytedesk/core/kaptcha/KaptchaRestController.java
index 1e51a94007..4e53f4bf84 100644
--- a/modules/core/src/main/java/com/bytedesk/core/kaptcha/KaptchaRestController.java
+++ b/modules/core/src/main/java/com/bytedesk/core/kaptcha/KaptchaRestController.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-07-18 19:17:59
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-20 16:06:18
+ * @LastEditTime: 2024-11-29 10:57:57
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -73,7 +73,7 @@ public class KaptchaRestController {
e.printStackTrace();
}
//
- return ResponseEntity.ok(JsonResult.error("kaptcha failed"));
+ return ResponseEntity.ok(JsonResult.error("kaptcha get failed"));
}
// 验证验证码
@@ -87,7 +87,7 @@ public class KaptchaRestController {
if (kaptchaCacheService.checkKaptcha(captchaUid, captchaCode, client)) {
return ResponseEntity.ok(JsonResult.success("kaptcha success"));
} else {
- return ResponseEntity.ok(JsonResult.error("kaptcha failed", -1));
+ return ResponseEntity.ok(JsonResult.error("kaptcha check failed", -1));
}
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/message/MessagePersistCache.java b/modules/core/src/main/java/com/bytedesk/core/message/MessagePersistCache.java
index 18f1347ff1..633785a1a8 100644
--- a/modules/core/src/main/java/com/bytedesk/core/message/MessagePersistCache.java
+++ b/modules/core/src/main/java/com/bytedesk/core/message/MessagePersistCache.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-07-16 11:09:19
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-31 10:45:29
+ * @LastEditTime: 2024-12-04 17:14:03
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -18,57 +18,69 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
+import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
-import com.github.benmanes.caffeine.cache.Cache;
-import com.github.benmanes.caffeine.cache.CacheLoader;
-import com.github.benmanes.caffeine.cache.Caffeine;
+import com.bytedesk.core.redis.RedisConsts;
+
+// import com.github.benmanes.caffeine.cache.Cache;
+// import com.github.benmanes.caffeine.cache.CacheLoader;
+// import com.github.benmanes.caffeine.cache.Caffeine;
import jakarta.annotation.PostConstruct;
+import lombok.AllArgsConstructor;
@Component
+@AllArgsConstructor
public class MessagePersistCache {
- // 假设我们使用"myList"作为缓存中的键
- String defaultPersistKey = "messageList";
+ private static final String DEFAULT_PERSIST_KEY = RedisConsts.BYTEDESK_REDIS_PREFIX + "messageList";
+ private static final long EXPIRE_TIME = 1; // 1天
+ private final StringRedisTemplate stringRedisTemplate;
+ // 假设我们使用"myList"作为缓存中的键
+ // String defaultPersistKey = "messageList";
// 创建一个缓存实例,设置过期时间为5天
- private Cache> messageCache;
+ // private Cache> messageCache;
@PostConstruct
public void init() {
- // 初始化caffeinecache,设置缓存的最大大小、过期时间等参数
- messageCache = Caffeine.newBuilder()
- .expireAfterWrite(1, TimeUnit.DAYS)
- .build(new CacheLoader>() {
- @Override
- public List load(String key) throws Exception {
- // 当缓存中没有找到对应的键时,使用load方法初始化
- return new ArrayList<>();
- }
- });
+ // 初始化caffeineCache,设置缓存的最大大小、过期时间等参数
+ // messageCache = Caffeine.newBuilder()
+ // .expireAfterWrite(1, TimeUnit.DAYS)
+ // .build(new CacheLoader>() {
+ // @Override
+ // public List load(String key) throws Exception {
+ // // 当缓存中没有找到对应的键时,使用load方法初始化
+ // return new ArrayList<>();
+ // }
+ // });
+ // 初始化 Redis 配置
}
-
// 模拟 push 操作:向列表中添加元素
public void pushForPersist(String messageJSON) {
- push(defaultPersistKey, messageJSON);
+ // push(defaultPersistKey, messageJSON);
+ push(DEFAULT_PERSIST_KEY, messageJSON);
}
// 模拟 pop 操作:从列表中移除元素
public List getListForPersist() {
- return getList(defaultPersistKey);
+ // return getList(defaultPersistKey);
+ return getList(DEFAULT_PERSIST_KEY);
}
// 模拟 push 操作:向列表中添加元素
public void push(String listKey, String messageJSON) {
- List cachedList = messageCache.getIfPresent(listKey);
- if (cachedList == null) {
- // 如果缓存中没有找到对应的键,则使用load方法初始化
- cachedList = new ArrayList<>();
- }
- cachedList.add(messageJSON);
- messageCache.put(listKey, cachedList);
+ // List cachedList = messageCache.getIfPresent(listKey);
+ // if (cachedList == null) {
+ // // 如果缓存中没有找到对应的键,则使用load方法初始化
+ // cachedList = new ArrayList<>();
+ // }
+ // cachedList.add(messageJSON);
+ // messageCache.put(listKey, cachedList);
+ stringRedisTemplate.opsForList().rightPush(listKey, messageJSON);
+ stringRedisTemplate.expire(listKey, EXPIRE_TIME, TimeUnit.DAYS);
}
public void pushGroup(String groupUid, String messageJSON) {
@@ -76,58 +88,34 @@ public class MessagePersistCache {
}
public List getList(String listKey) {
- List cachedList = messageCache.getIfPresent(listKey);
- if (cachedList != null && !cachedList.isEmpty()) {
- // 只需要返回一次即可
- remove(listKey);
- return cachedList;
+ // List cachedList = messageCache.getIfPresent(listKey);
+ // if (cachedList != null && !cachedList.isEmpty()) {
+ // // 只需要返回一次即可
+ // remove(listKey);
+ // return cachedList;
+ // }
+ // return null;
+ Long size = stringRedisTemplate.opsForList().size(listKey);
+ if (size == null || size == 0) {
+ return new ArrayList<>();
}
- return null;
+ List messages = stringRedisTemplate.opsForList().range(listKey, 0, size - 1);
+ // 从缓存中删除列表内容
+ clearCache(listKey);
+ return messages;
}
- public void remove(String listKey) {
- messageCache.invalidate(listKey);
+ // 清空缓存
+ public void clearCache(String listKey) {
+ stringRedisTemplate.delete(listKey);
}
- // public List getAllKeyList() {
- // List keys = new ArrayList<>();
- // messageCache.asMap().keySet().forEach(key -> {
- // if (key != defaultPersistKey) {
- // keys.add(key);
- // }
- // });
- // return keys;
+ // public void remove(String listKey) {
+ // messageCache.invalidate(listKey);
// }
- public void clear() {
- messageCache.invalidateAll();
- }
-
- // 模拟 pop 操作:从列表中移除元素
- // public String getFirst(String listKey) {
- // List cachedList = messageCache.getIfPresent(listKey);
- // if (cachedList != null && !cachedList.isEmpty()) {
- // String messageJSON = cachedList.remove(0);
- // // log.info("Popped element: " + messageJSON);
- // return messageJSON;
- // }
- // return null;
+ // public void clear() {
+ // messageCache.invalidateAll();
// }
- // public String removeMessage(String listKey, MessageProtobuf messageObject) {
- // List cachedList = messageCache.getIfPresent(listKey);
- // if (cachedList != null && !cachedList.isEmpty()) {
- // for (int i = 0; i < cachedList.size(); i++) {
- // String element = cachedList.get(i);
- // MessageProtobuf messageElement = JSON.parseObject(element,
- // MessageProtobuf.class);
- // // 回执消息内容中存储被回执消息的uid
- // if (messageElement.getUid().equals(messageObject.getContent())) {
- // cachedList.remove(i);
- // }
- // }
- // messageCache.put(listKey, cachedList);
- // }
- // return null;
- // }
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/message/MessagePersistService.java b/modules/core/src/main/java/com/bytedesk/core/message/MessagePersistService.java
index 95028f6a7a..65d8336fdc 100644
--- a/modules/core/src/main/java/com/bytedesk/core/message/MessagePersistService.java
+++ b/modules/core/src/main/java/com/bytedesk/core/message/MessagePersistService.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-04-16 18:04:37
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-10-23 18:13:27
+ * @LastEditTime: 2024-12-04 17:03:41
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -86,8 +86,8 @@ public class MessagePersistService {
// 处理消息通知,已处理的消息返回true,未处理的消息返回false
private boolean dealWithMessageNotification(MessageTypeEnum type, MessageProtobuf messageProtobuf) {
- String content = messageProtobuf.getContent();
- log.info("dealWithMessageNotification: {}, {}", type, content);
+ // String content = messageProtobuf.getContent();
+ // log.info("dealWithMessageNotification: {}, {}", type, content);
// 正在输入/消息预知 - 不保存
if (type.equals(MessageTypeEnum.TYPING)
@@ -175,7 +175,7 @@ public class MessagePersistService {
private void dealWithMessageReceipt(MessageTypeEnum type, @Nonnull MessageProtobuf message) {
log.info("dealWithMessageReceipt: {}", type);
// 回执消息内容存储被回执消息的uid
- // 当status已经为read时,不处理。防止deliverd在后面更新read消息
+ // 当status已经为read时,不处理。防止delivered在后面更新read消息
Optional messageOpt = messageService.findByUid(message.getContent());
if (messageOpt.isPresent() && messageOpt.get().getStatus() != MessageStatusEnum.READ.name()) {
MessageEntity messageEntity = messageOpt.get();
diff --git a/modules/core/src/main/java/com/bytedesk/core/message/MessageRepository.java b/modules/core/src/main/java/com/bytedesk/core/message/MessageRepository.java
index b3bd13289d..02aa92eccf 100644
--- a/modules/core/src/main/java/com/bytedesk/core/message/MessageRepository.java
+++ b/modules/core/src/main/java/com/bytedesk/core/message/MessageRepository.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:21:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-05 11:04:54
+ * @LastEditTime: 2024-12-05 12:04:13
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -16,8 +16,6 @@ package com.bytedesk.core.message;
import java.util.Optional;
-import org.springframework.data.domain.Page;
-import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
@@ -33,15 +31,5 @@ public interface MessageRepository extends JpaRepository, J
Optional findByUid(String uid);
- Page findByThreadTopic(String topic, Pageable pageable);
- // Page findByThreadsUidIn(String[] threadTids, Pageable pageable);
-
- // Optional findFirstByThreadsUidInOrderByCreatedAtDesc(String[]
- // threadTids);
-
boolean existsByUid(String uid);
-
- // Page findByOrgUidAndDeleted(String orgUid, Boolean deleted, Pageable
- // pageable);
-
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/message/MessageController.java b/modules/core/src/main/java/com/bytedesk/core/message/MessageRestController.java
similarity index 93%
rename from modules/core/src/main/java/com/bytedesk/core/message/MessageController.java
rename to modules/core/src/main/java/com/bytedesk/core/message/MessageRestController.java
index 78040d0ac3..d307b9afe3 100644
--- a/modules/core/src/main/java/com/bytedesk/core/message/MessageController.java
+++ b/modules/core/src/main/java/com/bytedesk/core/message/MessageRestController.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:21:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-08 12:43:27
+ * @LastEditTime: 2024-12-05 10:43:44
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -39,31 +39,23 @@ import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
- *
+ * @author Jackning <270580156@qq.com>
*/
@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/api/v1/message")
-public class MessageController extends BaseRestController {
+public class MessageRestController extends BaseRestController {
private final MessageService messageService;
- // private final MqService stompMqService;
-
private final IMessageSendService messageSendService;
private final MessageUnreadService messageUnreadService;
private final ModelMapper modelMapper;
- /**
- * 管理后台 根据 orgUids 查询
- *
- * @param request
- * @return
- */
- @GetMapping("/query/org")
+ @Override
public ResponseEntity> queryByOrg(MessageRequest request) {
Page messagePage = messageService.queryByOrg(request);
@@ -71,12 +63,16 @@ public class MessageController extends BaseRestController {
return ResponseEntity.ok(JsonResult.success(messagePage));
}
- /**
- * @return json
- */
- @GetMapping("/query/topic")
public ResponseEntity> queryByUser(MessageRequest request) {
+ Page response = messageService.queryByUser(request);
+ //
+ return ResponseEntity.ok(JsonResult.success(response));
+ }
+
+ @GetMapping("/query/topic")
+ public ResponseEntity> queryByTopic(MessageRequest request) {
+
Page response = messageService.queryByThreadTopic(request);
//
return ResponseEntity.ok(JsonResult.success(response));
diff --git a/modules/core/src/main/java/com/bytedesk/core/message/MessageService.java b/modules/core/src/main/java/com/bytedesk/core/message/MessageService.java
index 8b5ed5bde8..f572429c5c 100644
--- a/modules/core/src/main/java/com/bytedesk/core/message/MessageService.java
+++ b/modules/core/src/main/java/com/bytedesk/core/message/MessageService.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:21:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-10-23 18:13:10
+ * @LastEditTime: 2024-12-05 12:05:09
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -28,9 +28,9 @@ import org.springframework.lang.NonNull;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.springframework.stereotype.Service;
-// import com.alibaba.fastjson2.JSON;
import com.bytedesk.core.base.BaseRestService;
-// import com.bytedesk.core.config.BytedeskEventPublisher;
+import com.bytedesk.core.rbac.auth.AuthService;
+import com.bytedesk.core.rbac.user.UserEntity;
import com.bytedesk.core.utils.ConvertUtils;
import lombok.AllArgsConstructor;
@@ -42,11 +42,29 @@ public class MessageService extends BaseRestService queryByOrg(MessageRequest request) {
- // 优先加载最新聊天记录,也即:id越大越新
+ Pageable pageable = PageRequest.of(request.getPageNumber(), request.getPageSize(), Sort.Direction.DESC,
+ "createdAt");
+
+ Specification specs = MessageSpecification.search(request);
+
+ Page messagePage = messageRepository.findAll(specs, pageable);
+
+ return messagePage.map(ConvertUtils::convertToMessageResponse);
+ }
+
+ @Override
+ public Page queryByUser(MessageRequest request) {
+
+ UserEntity user = authService.getUser();
+ if (user == null) {
+ throw new RuntimeException("User not found");
+ }
+ request.setUserUid(user.getUid());
+
Pageable pageable = PageRequest.of(request.getPageNumber(), request.getPageSize(), Sort.Direction.DESC,
"createdAt");
@@ -60,32 +78,32 @@ public class MessageService extends BaseRestService queryByThreadTopic(MessageRequest request) {
- // 优先加载最新聊天记录,也即:id越大越新
Pageable pageable = PageRequest.of(request.getPageNumber(), request.getPageSize(), Sort.Direction.DESC,
"createdAt");
- Page messagePage = messageRepository.findByThreadTopic(request.getThreadTopic(), pageable);
+ Specification specs = MessageSpecification.search(request);
+
+ Page messagePage = messageRepository.findAll(specs, pageable);
return messagePage.map(ConvertUtils::convertToMessageResponse);
}
- public Page queryUnread(MessageRequest request) {
-
- // 优先加载最新聊天记录,也即:id越大越新
- // Pageable pageable = PageRequest.of(request.getPageNumber(),
- // request.getPageSize(), Sort.Direction.DESC,"createdAt");
- // Specification specs = MessageSpecification.unread(request);
- // Page messagePage = messageRepository.findAll(specs, pageable);
- // return messagePage.map(ConvertUtils::convertToMessageResponse);
-
- return null;
- }
-
@Cacheable(value = "message", key = "#uid", unless = "#result == null")
public Optional findByUid(String uid) {
return messageRepository.findByUid(uid);
}
+ @Override
+ public MessageResponse create(MessageRequest request) {
+ // TODO Auto-generated method stub
+ throw new UnsupportedOperationException("Unimplemented method 'create'");
+ }
+
+ @Override
+ public MessageResponse update(MessageRequest request) {
+ // TODO Auto-generated method stub
+ throw new UnsupportedOperationException("Unimplemented method 'update'");
+ }
@Caching(put = {
@CachePut(value = "message", key = "#message.uid"),
@@ -122,24 +140,6 @@ public class MessageService extends BaseRestService queryByUser(MessageRequest request) {
- // TODO Auto-generated method stub
- throw new UnsupportedOperationException("Unimplemented method 'queryByUser'");
- }
-
- @Override
- public MessageResponse create(MessageRequest request) {
- // TODO Auto-generated method stub
- throw new UnsupportedOperationException("Unimplemented method 'create'");
- }
-
- @Override
- public MessageResponse update(MessageRequest request) {
- // TODO Auto-generated method stub
- throw new UnsupportedOperationException("Unimplemented method 'update'");
- }
-
@Override
public MessageResponse convertToResponse(MessageEntity entity) {
// TODO Auto-generated method stub
@@ -147,11 +147,10 @@ public class MessageService extends BaseRestService topicSet = topicService.findByTopic(topic);
log.info("topicList size {}", topicSet.size());
topicSet.forEach(topicElement -> {
@@ -122,8 +130,10 @@ public class MessageSocketService {
}
private void doSendMessage(String topic, @NonNull MessageProto.Message messageProto, String clientId) {
- // log.debug("doSendMessage: user={}, content={}, topic={}, type={}, clientId={}",
- // messageProto.getUser().getNickname(), messageProto.getContent(), topic, messageProto.getType(), clientId);
+ // log.debug("doSendMessage: user={}, content={}, topic={}, type={},
+ // clientId={}",
+ // messageProto.getUser().getNickname(), messageProto.getContent(), topic,
+ // messageProto.getType(), clientId);
MqttQoS mqttQoS = MqttQoS.AT_LEAST_ONCE;
boolean dup = false;
boolean retain = false;
@@ -144,5 +154,4 @@ public class MessageSocketService {
}
}
-
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/message/MessageSpecification.java b/modules/core/src/main/java/com/bytedesk/core/message/MessageSpecification.java
index 68e7381710..bfa71abf52 100644
--- a/modules/core/src/main/java/com/bytedesk/core/message/MessageSpecification.java
+++ b/modules/core/src/main/java/com/bytedesk/core/message/MessageSpecification.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-06-05 22:53:57
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-06-28 15:19:04
+ * @LastEditTime: 2024-12-05 11:22:36
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -21,6 +21,7 @@ import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.StringUtils;
import com.bytedesk.core.base.BaseSpecification;
+import com.bytedesk.core.topic.TopicUtils;
import jakarta.persistence.criteria.Predicate;
@@ -29,11 +30,28 @@ public class MessageSpecification extends BaseSpecification {
public static Specification search(MessageRequest request) {
return (root, query, criteriaBuilder) -> {
List predicates = new ArrayList<>();
- predicates.addAll(getBasicPredicates(root, criteriaBuilder, request.getOrgUid()));
+ // predicates.addAll(getBasicPredicates(root, criteriaBuilder, request.getOrgUid()));
+ predicates.add(criteriaBuilder.equal(root.get("deleted"), false));
+ //
+ if (StringUtils.hasText(request.getOrgUid())) {
+ predicates.add(criteriaBuilder.equal(root.get("orgUid"), request.getOrgUid()));
+ }
//
if (StringUtils.hasText(request.getContent())) {
predicates.add(criteriaBuilder.like(root.get("content"), "%" + request.getContent() + "%"));
}
+ //
+ String topic = request.getThreadTopic();
+ if (StringUtils.hasText(topic)) {
+ Predicate topicPredicate = criteriaBuilder.equal(root.get("threadTopic"), topic);
+ if (TopicUtils.isOrgMemberTopic(topic)) {
+ String reverseTopic = TopicUtils.getOrgMemberTopicReverse(topic);
+ Predicate reverseTopicPredicate = criteriaBuilder.equal(root.get("threadTopic"), reverseTopic);
+ predicates.add(criteriaBuilder.or(topicPredicate, reverseTopicPredicate));
+ } else {
+ predicates.add(topicPredicate);
+ }
+ }
//
if (StringUtils.hasText(request.getClient())) {
predicates.add(criteriaBuilder.like(root.get("client"), "%" + request.getClient() + "%"));
diff --git a/modules/core/src/main/java/com/bytedesk/core/message/MessageTypeEnum.java b/modules/core/src/main/java/com/bytedesk/core/message/MessageTypeEnum.java
index bad6713e19..9b4c3ec49a 100644
--- a/modules/core/src/main/java/com/bytedesk/core/message/MessageTypeEnum.java
+++ b/modules/core/src/main/java/com/bytedesk/core/message/MessageTypeEnum.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-06-05 21:50:54
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-05 16:15:12
+ * @LastEditTime: 2024-12-03 17:06:25
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
diff --git a/modules/core/src/main/java/com/bytedesk/core/message/MessageUtils.java b/modules/core/src/main/java/com/bytedesk/core/message/MessageUtils.java
index 53cb73f04b..7e24c4d2f1 100644
--- a/modules/core/src/main/java/com/bytedesk/core/message/MessageUtils.java
+++ b/modules/core/src/main/java/com/bytedesk/core/message/MessageUtils.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-08-31 16:23:54
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-07 14:15:26
+ * @LastEditTime: 2024-12-04 14:20:54
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -41,7 +41,8 @@ public class MessageUtils {
String topic = TopicUtils.getSystemTopic(userUid);
ThreadProtobuf thread = ThreadUtils.getThreadProtobuf(topic, ThreadTypeEnum.CHANNEL, sender);
//
- MessageExtra extra = MessageUtils.getMessageExtra(orgUid);
+ MessageExtra messageExtra = MessageUtils.getMessageExtra(orgUid);
+ String extra = JSON.toJSONString(messageExtra);
//
MessageProtobuf message = MessageProtobuf.builder()
.uid(messageUid)
@@ -52,7 +53,7 @@ public class MessageUtils {
.client(ClientEnum.SYSTEM)
.thread(thread)
.user(sender)
- .extra(JSON.toJSONString(extra))
+ .extra(extra)
.build();
return message;
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/notice/NoticeEntity.java b/modules/core/src/main/java/com/bytedesk/core/notification/NotificationEntity.java
similarity index 60%
rename from modules/core/src/main/java/com/bytedesk/core/notice/NoticeEntity.java
rename to modules/core/src/main/java/com/bytedesk/core/notification/NotificationEntity.java
index 030f757335..c6b18272b1 100644
--- a/modules/core/src/main/java/com/bytedesk/core/notice/NoticeEntity.java
+++ b/modules/core/src/main/java/com/bytedesk/core/notification/NotificationEntity.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-09-01 09:27:49
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-20 10:16:11
+ * @LastEditTime: 2024-12-04 17:30:02
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -12,10 +12,16 @@
* 联系:270580156@qq.com
* Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
*/
-package com.bytedesk.core.notice;
+package com.bytedesk.core.notification;
+
+import org.hibernate.annotations.JdbcTypeCode;
+import org.hibernate.type.SqlTypes;
import com.bytedesk.core.base.BaseEntity;
+import com.bytedesk.core.constant.BytedeskConsts;
+import com.bytedesk.core.constant.TypeConsts;
+import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
@@ -26,9 +32,7 @@ import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
- * 通知
- * 1. 一条消息通知-每个用户写一条
- * 2. 定期清理一段时间以前的通知
+ * 注意与message类型notice区分
*/
@Data
@Entity
@@ -37,12 +41,22 @@ import lombok.experimental.Accessors;
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
-@Table(name = "bytedesk_core_notice")
-public class NoticeEntity extends BaseEntity {
+@Table(name = "bytedesk_core_notification")
+public class NotificationEntity extends BaseEntity {
private String title;
private String content;
- private String url;
+ @Column(name = "notification_type")
+ private String type;
+
+ @Builder.Default
+ @Column(name = "notification_status")
+ private String status = NotificationStatusEnum.UNREAD.name();
+
+ @Builder.Default
+ @Column(columnDefinition = TypeConsts.COLUMN_TYPE_JSON)
+ @JdbcTypeCode(SqlTypes.JSON)
+ private String extra = BytedeskConsts.EMPTY_JSON_STRING;
}
diff --git a/modules/ticket/src/main/java/com/bytedesk/ticket/ticket/TicketRequest.java b/modules/core/src/main/java/com/bytedesk/core/notification/NotificationExtra.java
similarity index 76%
rename from modules/ticket/src/main/java/com/bytedesk/ticket/ticket/TicketRequest.java
rename to modules/core/src/main/java/com/bytedesk/core/notification/NotificationExtra.java
index f56f4321bf..80e35a2530 100644
--- a/modules/ticket/src/main/java/com/bytedesk/ticket/ticket/TicketRequest.java
+++ b/modules/core/src/main/java/com/bytedesk/core/notification/NotificationExtra.java
@@ -1,8 +1,8 @@
/*
* @Author: jackning 270580156@qq.com
- * @Date: 2024-03-23 11:51:12
+ * @Date: 2024-12-04 17:29:46
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-03-23 11:51:13
+ * @LastEditTime: 2024-12-04 17:29:48
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -12,10 +12,15 @@
* 联系:270580156@qq.com
* Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
*/
-package com.bytedesk.ticket.ticket;
+package com.bytedesk.core.notification;
-import com.bytedesk.core.base.BaseRequest;
+import lombok.Builder;
+import lombok.Data;
-public class TicketRequest extends BaseRequest {
+@Data
+@Builder
+public class NotificationExtra {
+
+ private String url;
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/notice/NoticePermissions.java b/modules/core/src/main/java/com/bytedesk/core/notification/NotificationPermissions.java
similarity index 52%
rename from modules/core/src/main/java/com/bytedesk/core/notice/NoticePermissions.java
rename to modules/core/src/main/java/com/bytedesk/core/notification/NotificationPermissions.java
index b58484948d..aedb49f7e9 100644
--- a/modules/core/src/main/java/com/bytedesk/core/notice/NoticePermissions.java
+++ b/modules/core/src/main/java/com/bytedesk/core/notification/NotificationPermissions.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-11-05 17:07:48
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-05 17:15:10
+ * @LastEditTime: 2024-12-03 17:09:04
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -12,15 +12,15 @@
* 联系:270580156@qq.com
* Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
*/
-package com.bytedesk.core.notice;
+package com.bytedesk.core.notification;
-public class NoticePermissions {
+public class NotificationPermissions {
- public static final String NOTICE_PREFIX = "NOTICE_";
- // Notice permissions
- public static final String NOTICE_CREATE = "hasAuthority('NOTICE_CREATE')";
- public static final String NOTICE_READ = "hasAuthority('NOTICE_READ')";
- public static final String NOTICE_UPDATE = "hasAuthority('NOTICE_UPDATE')";
- public static final String NOTICE_DELETE = "hasAuthority('NOTICE_DELETE')";
- public static final String NOTICE_EXPORT = "hasAuthority('NOTICE_EXPORT')";
+ public static final String NOTIFICATION_PREFIX = "NOTIFICATION_";
+ // Notification permissions
+ public static final String NOTIFICATION_CREATE = "hasAuthority('NOTIFICATION_CREATE')";
+ public static final String NOTIFICATION_READ = "hasAuthority('NOTIFICATION_READ')";
+ public static final String NOTIFICATION_UPDATE = "hasAuthority('NOTIFICATION_UPDATE')";
+ public static final String NOTIFICATION_DELETE = "hasAuthority('NOTIFICATION_DELETE')";
+ public static final String NOTIFICATION_EXPORT = "hasAuthority('NOTIFICATION_EXPORT')";
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/notice/NoticeRepository.java b/modules/core/src/main/java/com/bytedesk/core/notification/NotificationRepository.java
similarity index 80%
rename from modules/core/src/main/java/com/bytedesk/core/notice/NoticeRepository.java
rename to modules/core/src/main/java/com/bytedesk/core/notification/NotificationRepository.java
index c91d4bd0e4..a375535cdd 100644
--- a/modules/core/src/main/java/com/bytedesk/core/notice/NoticeRepository.java
+++ b/modules/core/src/main/java/com/bytedesk/core/notification/NotificationRepository.java
@@ -12,14 +12,14 @@
* 联系:270580156@qq.com
* Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
*/
-package com.bytedesk.core.notice;
+package com.bytedesk.core.notification;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
-public interface NoticeRepository extends JpaRepository, JpaSpecificationExecutor {
+public interface NotificationRepository extends JpaRepository, JpaSpecificationExecutor {
- Optional findByUid(String uid);
+ Optional findByUid(String uid);
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/notice/NoticeRequest.java b/modules/core/src/main/java/com/bytedesk/core/notification/NotificationRequest.java
similarity index 88%
rename from modules/core/src/main/java/com/bytedesk/core/notice/NoticeRequest.java
rename to modules/core/src/main/java/com/bytedesk/core/notification/NotificationRequest.java
index bafa2b5b12..0763dd377c 100644
--- a/modules/core/src/main/java/com/bytedesk/core/notice/NoticeRequest.java
+++ b/modules/core/src/main/java/com/bytedesk/core/notification/NotificationRequest.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-09-01 09:28:40
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-10-28 10:42:18
+ * @LastEditTime: 2024-12-04 17:31:03
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -12,7 +12,7 @@
* 联系:270580156@qq.com
* Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
*/
-package com.bytedesk.core.notice;
+package com.bytedesk.core.notification;
import com.bytedesk.core.base.BaseRequest;
@@ -29,11 +29,10 @@ import lombok.experimental.Accessors;
@EqualsAndHashCode(callSuper = false)
@AllArgsConstructor
@NoArgsConstructor
-public class NoticeRequest extends BaseRequest {
+public class NotificationRequest extends BaseRequest {
private String title;
// private String content;
- private String url;
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/notice/NoticeResponse.java b/modules/core/src/main/java/com/bytedesk/core/notification/NotificationResponse.java
similarity index 86%
rename from modules/core/src/main/java/com/bytedesk/core/notice/NoticeResponse.java
rename to modules/core/src/main/java/com/bytedesk/core/notification/NotificationResponse.java
index ab9b1cbc02..75962bc86c 100644
--- a/modules/core/src/main/java/com/bytedesk/core/notice/NoticeResponse.java
+++ b/modules/core/src/main/java/com/bytedesk/core/notification/NotificationResponse.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-09-01 09:28:51
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-10-28 10:42:56
+ * @LastEditTime: 2024-12-04 17:31:11
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -12,7 +12,7 @@
* 联系:270580156@qq.com
* Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
*/
-package com.bytedesk.core.notice;
+package com.bytedesk.core.notification;
import java.time.LocalDateTime;
@@ -31,13 +31,15 @@ import lombok.experimental.Accessors;
@EqualsAndHashCode(callSuper = false)
@AllArgsConstructor
@NoArgsConstructor
-public class NoticeResponse extends BaseResponse {
+public class NotificationResponse extends BaseResponse {
private String title;
private String content;
- private String url;
+ private String type;
+
+ private String extra;
private LocalDateTime createdAt;
diff --git a/modules/core/src/main/java/com/bytedesk/core/notice/NoticeRestController.java b/modules/core/src/main/java/com/bytedesk/core/notification/NotificationRestController.java
similarity index 63%
rename from modules/core/src/main/java/com/bytedesk/core/notice/NoticeRestController.java
rename to modules/core/src/main/java/com/bytedesk/core/notification/NotificationRestController.java
index c072e92d3b..4eef23e012 100644
--- a/modules/core/src/main/java/com/bytedesk/core/notice/NoticeRestController.java
+++ b/modules/core/src/main/java/com/bytedesk/core/notification/NotificationRestController.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-09-01 09:28:15
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-20 10:17:27
+ * @LastEditTime: 2024-12-04 12:55:18
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -12,7 +12,7 @@
* 联系:270580156@qq.com
* Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
*/
-package com.bytedesk.core.notice;
+package com.bytedesk.core.notification;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
@@ -25,38 +25,38 @@ import com.bytedesk.core.utils.JsonResult;
import lombok.AllArgsConstructor;
@RestController
-@RequestMapping("/api/v1/notice")
+@RequestMapping("/api/v1/notification")
@AllArgsConstructor
-public class NoticeRestController extends BaseRestController {
+public class NotificationRestController extends BaseRestController {
- private NoticeRestService noticeService;
+ private NotificationRestService noticeService;
@Override
- public ResponseEntity> queryByOrg(NoticeRequest request) {
- Page page = noticeService.queryByOrg(request);
+ public ResponseEntity> queryByOrg(NotificationRequest request) {
+ Page page = noticeService.queryByOrg(request);
return ResponseEntity.ok(JsonResult.success(page));
}
@Override
- public ResponseEntity> queryByUser(NoticeRequest request) {
- Page page = noticeService.queryByUser(request);
+ public ResponseEntity> queryByUser(NotificationRequest request) {
+ Page page = noticeService.queryByUser(request);
return ResponseEntity.ok(JsonResult.success(page));
}
@Override
- public ResponseEntity> create(NoticeRequest request) {
- NoticeResponse notice = noticeService.create(request);
+ public ResponseEntity> create(NotificationRequest request) {
+ NotificationResponse notice = noticeService.create(request);
return ResponseEntity.ok(JsonResult.success(notice));
}
@Override
- public ResponseEntity> update(NoticeRequest request) {
- NoticeResponse notice = noticeService.update(request);
+ public ResponseEntity> update(NotificationRequest request) {
+ NotificationResponse notice = noticeService.update(request);
return ResponseEntity.ok(JsonResult.success(notice));
}
@Override
- public ResponseEntity> delete(NoticeRequest request) {
+ public ResponseEntity> delete(NotificationRequest request) {
noticeService.delete(request);
return ResponseEntity.ok(JsonResult.success());
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/notice/NoticeRestService.java b/modules/core/src/main/java/com/bytedesk/core/notification/NotificationRestService.java
similarity index 70%
rename from modules/core/src/main/java/com/bytedesk/core/notice/NoticeRestService.java
rename to modules/core/src/main/java/com/bytedesk/core/notification/NotificationRestService.java
index 0977091dc2..3359c5e55b 100644
--- a/modules/core/src/main/java/com/bytedesk/core/notice/NoticeRestService.java
+++ b/modules/core/src/main/java/com/bytedesk/core/notification/NotificationRestService.java
@@ -12,7 +12,7 @@
* 联系:270580156@qq.com
* Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
*/
-package com.bytedesk.core.notice;
+package com.bytedesk.core.notification;
import java.util.Optional;
@@ -33,46 +33,46 @@ import lombok.AllArgsConstructor;
@Service
@AllArgsConstructor
-public class NoticeRestService extends BaseRestService {
+public class NotificationRestService extends BaseRestService {
- private NoticeRepository noticeRepository;
+ private NotificationRepository noticeRepository;
private ModelMapper modelMapper;
private UidUtils uidUtils;
@Override
- public Page queryByOrg(NoticeRequest request) {
+ public Page queryByOrg(NotificationRequest request) {
Pageable pageable = PageRequest.of(request.getPageNumber(), request.getPageSize(), Sort.Direction.ASC,
"updatedAt");
- Specification specification = NoticeSpecification.search(request);
+ Specification specification = NotificationSpecification.search(request);
- Page page = noticeRepository.findAll(specification, pageable);
+ Page page = noticeRepository.findAll(specification, pageable);
return page.map(this::convertToResponse);
}
@Override
- public Page queryByUser(NoticeRequest request) {
+ public Page queryByUser(NotificationRequest request) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'queryByUser'");
}
@Cacheable(value = "notice", key = "#uid", unless = "#result == null")
@Override
- public Optional findByUid(String uid) {
+ public Optional findByUid(String uid) {
return noticeRepository.findByUid(uid);
}
@Override
- public NoticeResponse create(NoticeRequest request) {
+ public NotificationResponse create(NotificationRequest request) {
- NoticeEntity entity = modelMapper.map(request, NoticeEntity.class);
+ NotificationEntity entity = modelMapper.map(request, NotificationEntity.class);
entity.setUid(uidUtils.getUid());
//
- NoticeEntity savedEntity = save(entity);
+ NotificationEntity savedEntity = save(entity);
if (savedEntity == null) {
throw new RuntimeException("Create notice failed");
}
@@ -80,13 +80,13 @@ public class NoticeRestService extends BaseRestService entity = noticeRepository.findByUid(uid);
+ Optional entity = noticeRepository.findByUid(uid);
if (entity.isPresent()) {
// noticeRepository.delete(entity.get());
entity.get().setDeleted(true);
@@ -105,20 +105,20 @@ public class NoticeRestService extends BaseRestService search(NoticeRequest request) {
+ public static Specification search(NotificationRequest request) {
return (root, query, criteriaBuilder) -> {
List predicates = new ArrayList<>();
predicates.addAll(getBasicPredicates(root, criteriaBuilder, request.getOrgUid()));
diff --git a/modules/core/src/main/java/com/bytedesk/core/notification/NotificationStatusEnum.java b/modules/core/src/main/java/com/bytedesk/core/notification/NotificationStatusEnum.java
new file mode 100644
index 0000000000..e068abbfa2
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/notification/NotificationStatusEnum.java
@@ -0,0 +1,20 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-12-04 14:27:51
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-12-04 14:27:54
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.notification;
+
+public enum NotificationStatusEnum {
+ UNREAD,
+ READ
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/notification/NotificationTypeEnum.java b/modules/core/src/main/java/com/bytedesk/core/notification/NotificationTypeEnum.java
new file mode 100644
index 0000000000..a41ded603d
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/notification/NotificationTypeEnum.java
@@ -0,0 +1,21 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-12-04 14:26:31
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-12-04 17:27:01
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.notification;
+
+public enum NotificationTypeEnum {
+ ORGANIZATION_INVITATION,
+ ORGANIZATION_INVITATION_ACCEPT,
+ ORGANIZATION_INVITATION_REJECT;
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/push/PushRestService.java b/modules/core/src/main/java/com/bytedesk/core/push/PushRestService.java
index 9ce10c3031..d2cb56867d 100644
--- a/modules/core/src/main/java/com/bytedesk/core/push/PushRestService.java
+++ b/modules/core/src/main/java/com/bytedesk/core/push/PushRestService.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-04-25 15:41:33
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-23 10:48:19
+ * @LastEditTime: 2024-11-28 21:00:10
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -15,7 +15,6 @@
package com.bytedesk.core.push;
import java.time.ZoneId;
-// import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@@ -78,9 +77,7 @@ public class PushRestService extends BaseRestService register(@RequestBody UserRequest userRequest, HttpServletRequest request) {
if (!kaptchaCacheService.checkKaptcha(userRequest.getCaptchaUid(), userRequest.getCaptchaCode(), userRequest.getClient())) {
- return ResponseEntity.ok().body(JsonResult.error("captcha code failed", -1, false));
+ return ResponseEntity.ok().body(JsonResult.error(I18Consts.I18N_AUTH_CAPTCHA_ERROR, -1, false));
}
// validate sms code
// 验证手机验证码
if (!pushService.validateCode(userRequest.getMobile(), userRequest.getCode(), request)) {
- return ResponseEntity.ok().body(JsonResult.error("validate code failed", -1, false));
+ return ResponseEntity.ok().body(JsonResult.error(I18Consts.I18N_AUTH_CAPTCHA_VALIDATE_FAILED, -1, false));
}
UserResponse userResponse = userService.register(userRequest);
@@ -80,7 +81,7 @@ public class AuthController {
log.debug("login {}", authRequest.toString());
if (!kaptchaCacheService.checkKaptcha(authRequest.getCaptchaUid(), authRequest.getCaptchaCode(), authRequest.getClient())) {
- return ResponseEntity.ok().body(JsonResult.error("captcha code failed", -1, false));
+ return ResponseEntity.ok().body(JsonResult.error(I18Consts.I18N_AUTH_CAPTCHA_ERROR, -1, false));
}
Authentication authentication = authenticationManager.authenticate(
@@ -98,16 +99,16 @@ public class AuthController {
authRequest.getType());
if (!kaptchaCacheService.checkKaptcha(authRequest.getCaptchaUid(), authRequest.getCaptchaCode(), authRequest.getClient())) {
- return ResponseEntity.ok().body(JsonResult.error("captcha code failed", -1, false));
+ return ResponseEntity.ok().body(JsonResult.error(I18Consts.I18N_AUTH_CAPTCHA_ERROR, -1, false));
}
// send mobile code
Boolean result = pushService.sendCode(authRequest, request);
if (!result) {
- return ResponseEntity.ok().body(JsonResult.error("already send, don't repeat", -1, false));
+ return ResponseEntity.ok().body(JsonResult.error(I18Consts.I18N_AUTH_CAPTCHA_ALREADY_SEND, -1, false));
}
- return ResponseEntity.ok().body(JsonResult.success("send mobile code success"));
+ return ResponseEntity.ok().body(JsonResult.success(I18Consts.I18N_AUTH_CAPTCHA_SEND_SUCCESS));
}
@ActionAnnotation(title = "auth", action = BytedeskConsts.ACTION_LOGIN_MOBILE, description = "Login With mobile & code")
@@ -116,12 +117,12 @@ public class AuthController {
log.debug("login mobile {}", authRequest.toString());
if (!kaptchaCacheService.checkKaptcha(authRequest.getCaptchaUid(), authRequest.getCaptchaCode(), authRequest.getClient())) {
- return ResponseEntity.ok().body(JsonResult.error("captcha code failed", -1, false));
+ return ResponseEntity.ok().body(JsonResult.error(I18Consts.I18N_AUTH_CAPTCHA_ERROR, -1, false));
}
// validate mobile & code
// 验证手机验证码
if (!pushService.validateCode(authRequest.getMobile(), authRequest.getCode(), request)) {
- return ResponseEntity.ok().body(JsonResult.error("validate code failed", -1, false));
+ return ResponseEntity.ok().body(JsonResult.error(I18Consts.I18N_AUTH_CAPTCHA_VALIDATE_FAILED, -1, false));
}
// if mobile already exists, if none, then register
@@ -149,15 +150,15 @@ public class AuthController {
log.debug("send email code {}", authRequest.toString());
if (!kaptchaCacheService.checkKaptcha(authRequest.getCaptchaUid(), authRequest.getCaptchaCode(), authRequest.getClient())) {
- return ResponseEntity.ok().body(JsonResult.error("captcha code failed", -1, false));
+ return ResponseEntity.ok().body(JsonResult.error(I18Consts.I18N_AUTH_CAPTCHA_ERROR, -1, false));
}
// send email code
Boolean result = pushService.sendCode(authRequest, request);
if (!result) {
- return ResponseEntity.ok(JsonResult.error("already send, don't repeat", -1, false));
+ return ResponseEntity.ok(JsonResult.error(I18Consts.I18N_AUTH_CAPTCHA_ALREADY_SEND, -1, false));
}
- return ResponseEntity.ok(JsonResult.success("send email code success"));
+ return ResponseEntity.ok(JsonResult.success(I18Consts.I18N_AUTH_CAPTCHA_SEND_SUCCESS));
}
@ActionAnnotation(title = "auth", action = BytedeskConsts.ACTION_LOGIN_EMAIL, description = "Login With email & code")
@@ -167,7 +168,7 @@ public class AuthController {
// validate email & code
if (!pushService.validateCode(authRequest.getEmail(), authRequest.getCode(), request)) {
- return ResponseEntity.ok(JsonResult.error("validate code failed", -1, false));
+ return ResponseEntity.ok(JsonResult.error(I18Consts.I18N_AUTH_CAPTCHA_VALIDATE_FAILED, -1, false));
}
// 邮箱是否已经注册,如果没有,则自动注册
diff --git a/modules/core/src/main/java/com/bytedesk/core/rbac/auth/AuthEventListener.java b/modules/core/src/main/java/com/bytedesk/core/rbac/auth/AuthEventListener.java
index 3b111406c5..d8f4bafc5d 100644
--- a/modules/core/src/main/java/com/bytedesk/core/rbac/auth/AuthEventListener.java
+++ b/modules/core/src/main/java/com/bytedesk/core/rbac/auth/AuthEventListener.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-08-19 11:36:50
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-22 17:36:37
+ * @LastEditTime: 2024-12-04 14:13:24
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -60,9 +60,9 @@ public class AuthEventListener {
contentObject.put(I18Consts.I18N_NOTICE_CONTENT, action.getAction());
contentObject.put(I18Consts.I18N_NOTICE_IP, action.getIp());
contentObject.put(I18Consts.I18N_NOTICE_IP_LOCATION, action.getIpLocation());
+ String content = JSON.toJSONString(contentObject);
//
- MessageProtobuf message = MessageUtils.createNoticeMessage(uidUtils.getCacheSerialUid(), user.getUid(), user.getOrgUid(),
- JSON.toJSONString(contentObject));
+ MessageProtobuf message = MessageUtils.createNoticeMessage(uidUtils.getUid(), user.getUid(), user.getOrgUid(), content);
messageSendService.sendProtobufMessage(message);
}
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/rbac/auth/AuthService.java b/modules/core/src/main/java/com/bytedesk/core/rbac/auth/AuthService.java
index fbd4b03587..3af9e67b25 100644
--- a/modules/core/src/main/java/com/bytedesk/core/rbac/auth/AuthService.java
+++ b/modules/core/src/main/java/com/bytedesk/core/rbac/auth/AuthService.java
@@ -49,7 +49,7 @@ public class AuthService {
private ModelMapper modelMapper;
- public UserEntity getCurrentUser() {
+ public UserEntity getUser() {
// not logged in
if (SecurityContextHolder.getContext().getAuthentication() == null) {
return null;
diff --git a/modules/core/src/main/java/com/bytedesk/core/rbac/auth/AuthUser.java b/modules/core/src/main/java/com/bytedesk/core/rbac/auth/AuthUser.java
index 51802baef0..cef0113150 100644
--- a/modules/core/src/main/java/com/bytedesk/core/rbac/auth/AuthUser.java
+++ b/modules/core/src/main/java/com/bytedesk/core/rbac/auth/AuthUser.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-06-12 17:59:34
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-06-12 18:00:36
+ * @LastEditTime: 2024-12-05 10:20:34
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -14,33 +14,33 @@
*/
package com.bytedesk.core.rbac.auth;
-import org.modelmapper.ModelMapper;
-import org.springframework.security.core.context.SecurityContextHolder;
+// import org.modelmapper.ModelMapper;
+// import org.springframework.security.core.context.SecurityContextHolder;
-import com.bytedesk.core.rbac.user.UserEntity;
-import com.bytedesk.core.rbac.user.UserDetailsImpl;
+// import com.bytedesk.core.rbac.user.UserEntity;
+// import com.bytedesk.core.rbac.user.UserDetailsImpl;
-public class AuthUser {
+// public class AuthUser {
- public static UserEntity getCurrentUser() {
- // not logged in
- if (SecurityContextHolder.getContext().getAuthentication() == null) {
- return null;
- }
- //
- try {
- UserDetailsImpl userDetails = (UserDetailsImpl) SecurityContextHolder.getContext().getAuthentication()
- .getPrincipal();
- return new ModelMapper().map(userDetails, UserEntity.class);
- } catch (Exception e) {
- // TODO: handle exception
- // FIXME: 验证码错误时会报错:java.lang.ClassCastException: class java.lang.String cannot be cast to
- // class com.bytedesk.core.rbac.user.UserDetailsImpl (java.lang.String is in
- // module java.base of loader 'bootstrap';
- // com.bytedesk.core.rbac.user.UserDetailsImpl is in unnamed module of loader 'app')
- e.printStackTrace();
- }
- //
- return null;
- }
-}
+// public static UserEntity getCurrentUser() {
+// // not logged in
+// if (SecurityContextHolder.getContext().getAuthentication() == null) {
+// return null;
+// }
+// //
+// try {
+// UserDetailsImpl userDetails = (UserDetailsImpl) SecurityContextHolder.getContext().getAuthentication()
+// .getPrincipal();
+// return new ModelMapper().map(userDetails, UserEntity.class);
+// } catch (Exception e) {
+// // TODO: handle exception
+// // FIXME: 验证码错误时会报错:java.lang.ClassCastException: class java.lang.String cannot be cast to
+// // class com.bytedesk.core.rbac.user.UserDetailsImpl (java.lang.String is in
+// // module java.base of loader 'bootstrap';
+// // com.bytedesk.core.rbac.user.UserDetailsImpl is in unnamed module of loader 'app')
+// e.printStackTrace();
+// }
+// //
+// return null;
+// }
+// }
diff --git a/modules/core/src/main/java/com/bytedesk/core/rbac/organization/OrganizationService.java b/modules/core/src/main/java/com/bytedesk/core/rbac/organization/OrganizationService.java
index 0adea40057..34a303f409 100644
--- a/modules/core/src/main/java/com/bytedesk/core/rbac/organization/OrganizationService.java
+++ b/modules/core/src/main/java/com/bytedesk/core/rbac/organization/OrganizationService.java
@@ -64,7 +64,7 @@ public class OrganizationService extends BaseRestService queryByUser(OrganizationRequest request) {
- UserEntity user = authService.getCurrentUser();
+ UserEntity user = authService.getUser();
Pageable pageable = PageRequest.of(request.getPageNumber(), request.getPageSize(), Sort.Direction.DESC,
"id");
Page orgPage = organizationRepository.findByUser(user, pageable);
@@ -81,7 +81,7 @@ public class OrganizationService extends BaseRestService new RuntimeException("User not found."));
String orgUid = uidUtils.getUid();
diff --git a/modules/core/src/main/java/com/bytedesk/core/rbac/role/RolePermissions.java b/modules/core/src/main/java/com/bytedesk/core/rbac/role/RolePermissions.java
index 3b7909e824..044124e534 100644
--- a/modules/core/src/main/java/com/bytedesk/core/rbac/role/RolePermissions.java
+++ b/modules/core/src/main/java/com/bytedesk/core/rbac/role/RolePermissions.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-11-04 21:13:47
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-08 11:21:59
+ * @LastEditTime: 2024-12-05 10:16:58
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -29,7 +29,7 @@ public class RolePermissions {
public static final String ROLE_SUPER = "hasRole('SUPER')";
public static final String ROLE_ADMIN = "hasAnyRole('SUPER', 'ADMIN')";
public static final String ROLE_MEMBER = "hasAnyRole('SUPER', 'ADMIN', 'MEMBER')";
- public static final String ROLE_CUSTOMER_SERVICE = "hasAnyRole('SUPER', 'ADMIN', 'CS')";
+ public static final String ROLE_AGENT = "hasAnyRole('SUPER', 'ADMIN', 'AGENT')";
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/rbac/role/RoleService.java b/modules/core/src/main/java/com/bytedesk/core/rbac/role/RoleService.java
index 7daa2eb69a..282d543032 100644
--- a/modules/core/src/main/java/com/bytedesk/core/rbac/role/RoleService.java
+++ b/modules/core/src/main/java/com/bytedesk/core/rbac/role/RoleService.java
@@ -108,8 +108,8 @@ public class RoleService extends BaseRestService getRoleUids() {
+ OrganizationEntity organization = this.currentOrganization;
+ if (organization == null) {
+ return new HashSet<>();
+ }
+ return userOrganizationRoles.stream().filter(u -> u.getOrganization().equals(organization))
+ .flatMap(uor -> uor.getRoles().stream()).map(r -> r.getUid()).collect(Collectors.toSet());
+ }
+
// 判断organization是否包含role
public boolean containsRole(RoleEntity role) {
OrganizationEntity organization = this.currentOrganization;
@@ -181,8 +194,6 @@ public class UserEntity extends BaseEntityNoOrg {
uor.getRoles().remove(role);
}
}
- // 遍历uor的roles,删除role
- // uor.getRoles().clear();
}
}
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserPermissions.java b/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserPermissions.java
index 6ceb097ef9..8fe7f2fbc8 100644
--- a/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserPermissions.java
+++ b/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserPermissions.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-11-05 16:56:40
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-05 16:56:43
+ * @LastEditTime: 2024-12-05 10:15:38
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -23,6 +23,9 @@ public class UserPermissions {
public static final String USER_UPDATE = "hasAuthority('USER_UPDATE')";
public static final String USER_DELETE = "hasAuthority('USER_DELETE')";
public static final String USER_EXPORT = "hasAuthority('USER_EXPORT')";
+ //
+ public static final String USER_ANY = "hasAnyAuthority('USER_CREATE', 'USER_READ', 'USER_UPDATE', 'USER_EXPORT', 'USER_DELETE')";
+
}
\ No newline at end of file
diff --git a/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserRestController.java b/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserRestController.java
index 737da2045f..f63a5fe535 100644
--- a/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserRestController.java
+++ b/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserRestController.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:21:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-08 10:21:54
+ * @LastEditTime: 2024-12-05 10:14:59
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -44,14 +44,9 @@ public class UserRestController {
private final PushRestService pushService;
- /**
- * get user info
- *
- * @return
- */
@GetMapping("/profile")
public ResponseEntity> getProfile() {
- UserEntity user = authService.getCurrentUser(); // 返回的是缓存,导致修改后的数据无法获取
+ UserEntity user = authService.getUser(); // 返回的是缓存,导致修改后的数据无法获取
Optional userOptional = userService.findByUid(user.getUid());
if (userOptional.isPresent()) {
UserResponse userResponse = ConvertUtils.convertToUserResponse(userOptional.get());
@@ -61,12 +56,6 @@ public class UserRestController {
}
}
- /**
- * update user info
- *
- * @param userRequest
- * @return
- */
@ActionAnnotation(title = "user", action = "update", description = "update user info")
@PostMapping("/update")
public ResponseEntity> update(@RequestBody UserRequest userRequest) {
@@ -115,7 +104,6 @@ public class UserRestController {
return ResponseEntity.ok(JsonResult.success(userResponse));
}
- /** */
@ActionAnnotation(title = "user", action = "logout", description = "logout")
@PostMapping("/logout")
public ResponseEntity> logout() {
diff --git a/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserService.java b/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserService.java
index bbb0e3b68e..93da4854be 100644
--- a/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserService.java
+++ b/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserService.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:21:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-22 12:55:38
+ * @LastEditTime: 2024-12-06 12:04:46
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -37,7 +37,8 @@ import com.bytedesk.core.event.GenericApplicationEvent;
import com.bytedesk.core.exception.EmailExistsException;
import com.bytedesk.core.exception.MobileExistsException;
import com.bytedesk.core.exception.UsernameExistsException;
-import com.bytedesk.core.rbac.auth.AuthUser;
+import com.bytedesk.core.rbac.auth.AuthService;
+// import com.bytedesk.core.rbac.auth.AuthUser;
import com.bytedesk.core.rbac.organization.OrganizationEntity;
import com.bytedesk.core.rbac.organization.OrganizationRepository;
import com.bytedesk.core.rbac.role.RoleConsts;
@@ -72,6 +73,8 @@ public class UserService {
private final BytedeskEventPublisher bytedeskEventPublisher;
+ private final AuthService authService;
+
@Transactional
public UserResponse register(UserRequest request) {
log.info("register {}", request.toString());
@@ -135,7 +138,7 @@ public class UserService {
@Transactional
public UserResponse update(UserRequest request) {
- UserEntity currentUser = AuthUser.getCurrentUser();
+ UserEntity currentUser = authService.getUser();
Optional userOptional = findByUid(currentUser.getUid());
if (userOptional.isPresent()) {
UserEntity user = userOptional.get();
@@ -201,7 +204,7 @@ public class UserService {
@Transactional
public UserResponse changePassword(UserRequest request) {
- UserEntity currentUser = AuthUser.getCurrentUser();
+ UserEntity currentUser = authService.getUser();
Optional userOptional = findByUid(currentUser.getUid());
if (userOptional.isPresent()) {
UserEntity user = userOptional.get();
@@ -228,7 +231,7 @@ public class UserService {
@Transactional
public UserResponse changeEmail(UserRequest request) {
- UserEntity currentUser = AuthUser.getCurrentUser();
+ UserEntity currentUser = authService.getUser();
Optional userOptional = findByUid(currentUser.getUid());
if (userOptional.isPresent()) {
UserEntity user = userOptional.get();
@@ -255,7 +258,7 @@ public class UserService {
@Transactional
public UserResponse changeMobile(UserRequest request) {
- UserEntity currentUser = AuthUser.getCurrentUser();
+ UserEntity currentUser = authService.getUser();
Optional userOptional = findByUid(currentUser.getUid());
if (userOptional.isPresent()) {
UserEntity user = userOptional.get();
@@ -331,11 +334,12 @@ public class UserService {
}
public UserEntity updateUserRolesFromMember(UserEntity user, Set roleUids) {
- //
- // Optional orgOptional = organizationRepository.findByUid(orgUid);
- // if (!orgOptional.isPresent()) {
- // throw new RuntimeException("Organization not found..!!");
- // }
+ // 首先判断是否有变化,如果无变化则不更新
+ if (user.getRoleUids() != null && user.getRoleUids().equals(roleUids)) {
+ return user;
+ }
+
+ // 删除所有角色
user.removeOrganizationRoles();
//
// 增加角色,遍历roleUids,逐个添加
@@ -355,9 +359,6 @@ public class UserService {
if (savedEntity == null) {
throw new RuntimeException("User create failed..!!");
}
- // TODO: 发送邮件
- // TODO: 短信通知
- // TODO: 系统通知Notice
return savedEntity;
}
@@ -521,7 +522,7 @@ public class UserService {
public void logout() {
// TODO: 清理token,使其过期
- UserEntity user = AuthUser.getCurrentUser();
+ UserEntity user = authService.getUser();
bytedeskEventPublisher.publishGenericApplicationEvent(new GenericApplicationEvent(this, new UserLogoutEvent(this, user)));
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserTypeEnum.java b/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserTypeEnum.java
index f314d1b4b5..cd49345028 100644
--- a/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserTypeEnum.java
+++ b/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserTypeEnum.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-09-03 12:37:26
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-09-07 11:46:22
+ * @LastEditTime: 2024-12-05 09:11:59
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -22,5 +22,6 @@ public enum UserTypeEnum {
MEMBER,
ASSISTANT,
CHANNEL,
- USER
+ USER,
+ MONITOR
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/session/CookieController.java b/modules/core/src/main/java/com/bytedesk/core/session/CookieController.java
index eecfb4a368..1d492b733c 100644
--- a/modules/core/src/main/java/com/bytedesk/core/session/CookieController.java
+++ b/modules/core/src/main/java/com/bytedesk/core/session/CookieController.java
@@ -46,7 +46,7 @@ public class CookieController {
* @param username
* @return
*/
- @GetMapping("/")
+ @GetMapping({"", "/"})
public ResponseEntity> getCookie(@CookieValue(value = "v_vid", defaultValue = "no vid") String vid) {
return ResponseEntity.ok(JsonResult.success("get cookie", 200, vid));
diff --git a/modules/core/src/main/java/com/bytedesk/core/session/SessionController.java b/modules/core/src/main/java/com/bytedesk/core/session/SessionController.java
index 1331e6c88e..9e4c3b0824 100644
--- a/modules/core/src/main/java/com/bytedesk/core/session/SessionController.java
+++ b/modules/core/src/main/java/com/bytedesk/core/session/SessionController.java
@@ -46,7 +46,7 @@ public class SessionController {
* @param session
* @return
*/
- @GetMapping("/")
+ @GetMapping({"", "/"})
public ResponseEntity> getSession(HttpSession session) {
log.info("sessionId:[{}]", session.getId());
//
diff --git a/modules/core/src/main/java/com/bytedesk/core/socket/mqtt/MqttConnectionService.java b/modules/core/src/main/java/com/bytedesk/core/socket/mqtt/MqttConnectionService.java
index aa9d7506d5..fdf264ff1f 100644
--- a/modules/core/src/main/java/com/bytedesk/core/socket/mqtt/MqttConnectionService.java
+++ b/modules/core/src/main/java/com/bytedesk/core/socket/mqtt/MqttConnectionService.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-11-20 16:20:41
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-21 13:12:31
+ * @LastEditTime: 2024-12-04 16:36:08
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -74,16 +74,16 @@ public class MqttConnectionService {
// }
// 获取所有已连接的clientId
- public Set isConnectedClientIds() {
+ public Set getConnectedClientIds() {
long now = System.currentTimeMillis();
// 获取所有未过期的clientId
return stringRedisTemplate.opsForZSet().rangeByScore(RedisConsts.CONNECTED_MQTT_CLIENT_IDS, now, Double.MAX_VALUE);
}
- public Set isConnectedUserUids() {
+ public Set getConnectedUserUids() {
// 用户clientId格式: userUid/client/deviceUid
// 将clientId按/分割,取第一个元素为userUid
- Set clientIds = isConnectedClientIds();
+ Set clientIds = getConnectedClientIds();
return clientIds.stream().map(clientId -> clientId.split("/")[0]).collect(Collectors.toSet());
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/socket/mqtt/MqttRestController.java b/modules/core/src/main/java/com/bytedesk/core/socket/mqtt/MqttRestController.java
index c27138a27c..a107d1c9ee 100644
--- a/modules/core/src/main/java/com/bytedesk/core/socket/mqtt/MqttRestController.java
+++ b/modules/core/src/main/java/com/bytedesk/core/socket/mqtt/MqttRestController.java
@@ -59,7 +59,7 @@ public class MqttRestController {
@GetMapping("/connected/clients")
public ResponseEntity> isConnectedClients() {
- Set clientIds = mqttConnectionService.isConnectedClientIds();
+ Set clientIds = mqttConnectionService.getConnectedClientIds();
return ResponseEntity.ok(JsonResult.success(clientIds));
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadCloseEvent.java b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadCloseEvent.java
new file mode 100644
index 0000000000..afb6480ce4
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadCloseEvent.java
@@ -0,0 +1,33 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-04-23 08:51:27
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-12-02 15:35:48
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.thread;
+
+import org.springframework.context.ApplicationEvent;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class ThreadCloseEvent extends ApplicationEvent {
+
+ private ThreadEntity thread;
+
+ public ThreadCloseEvent(Object source, ThreadEntity thread) {
+ super(source);
+ this.thread = thread;
+ }
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadEntity.java b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadEntity.java
index a72cd359a7..4b91311603 100644
--- a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadEntity.java
+++ b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadEntity.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:21:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-09 10:58:01
+ * @LastEditTime: 2024-12-11 10:23:19
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -14,6 +14,10 @@
*/
package com.bytedesk.core.thread;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
@@ -143,8 +147,7 @@ public class ThreadEntity extends BaseEntity {
@Builder.Default
@Column(columnDefinition = TypeConsts.COLUMN_TYPE_JSON)
- // 用于兼容postgreSQL,否则会报错,[ERROR: column "extra" is of type json but expression is
- // of type character varying
+ // for postgres compatibility,[ERROR: column "extra" is of type json but expression is of type character varying
@JdbcTypeCode(SqlTypes.JSON)
private String extra = BytedeskConsts.EMPTY_JSON_STRING;
@@ -154,7 +157,6 @@ public class ThreadEntity extends BaseEntity {
* 在用户私聊中,存储对方用户信息
* 机器人会话中,存储访客信息
* 群组会话中,存储群组信息
- * FIXME: 同事对话中,对方更新头像之后,不能及时同步更新
* 注意:h2 db 不能使用 user, 所以重定义为 thread_user
*/
@Builder.Default
@@ -167,37 +169,52 @@ public class ThreadEntity extends BaseEntity {
* 技能组客服对话中,存储技能组信息
* 机器人对话中,存储机器人信息
* 用户私聊、群聊、同事会话中,无需存储,使用owner字段信息
- * FIXME: 头像、昵称和机器人大模型中参数修改之后,不能及时同步更新
*/
@Builder.Default
@Column(columnDefinition = TypeConsts.COLUMN_TYPE_JSON)
@JdbcTypeCode(SqlTypes.JSON)
private String agent = BytedeskConsts.EMPTY_JSON_STRING;
+ private String agentUid;
- // 机器人和agent可以同时存在,人工接待的时候,机器人可以同时给出答案,客服可以选用
- // 存储机器人信息
- // @Builder.Default
- // @Column(columnDefinition = TypeConsts.COLUMN_TYPE_JSON)
- // @JdbcTypeCode(SqlTypes.JSON)
- // private String robot = BytedeskConsts.EMPTY_JSON_STRING;
+ // multi agent assistants: monitoring agent、quality check agent、robot agent
+ @Builder.Default
+ @Column(columnDefinition = TypeConsts.COLUMN_TYPE_JSON)
+ @JdbcTypeCode(SqlTypes.JSON)
+ private String multiAgents = BytedeskConsts.EMPTY_JSON_STRING;
// belongs to user
@JsonIgnore
@ManyToOne(fetch = FetchType.LAZY)
private UserEntity owner;
- public void reInit() {
+ /**
+ // 示例1: 技术支持
+ thread.setRequiredSkills("java,spring,mysql");
+ // 示例2: 多语言支持
+ thread.setRequiredSkills("english,spanish");
+ // 示例3: 产品支持
+ thread.setRequiredSkills("product-a,technical");
+ // 示例4: 分级支持
+ thread.setRequiredSkills("level-2,database");
+ */
+ @Column(length = 500)
+ private String requiredSkills; // 会话所需技能,逗号分隔
+
+ public List getRequiredSkillList() {
+ if (requiredSkills == null || requiredSkills.isEmpty()) {
+ return new ArrayList<>();
+ }
+ return Arrays.asList(requiredSkills.split(","));
+ }
+
+ public ThreadEntity reInit() {
this.state = ThreadStateEnum.INITIAL.name();
- this.unread = false;
- this.unreadCount = 1;
- this.mute = false;
this.hide = false;
- this.star = 0;
- this.folded = false;
this.solved = false;
this.rated = false;
this.autoClose = false;
this.robot = false;
+ return this;
}
//
@@ -205,6 +222,22 @@ public class ThreadEntity extends BaseEntity {
return this.state.equals(ThreadStateEnum.CLOSED.name());
}
+ public Boolean isStarted() {
+ return this.state.equals(ThreadStateEnum.STARTED.name());
+ }
+
+ public Boolean isOffline() {
+ return this.state.equals(ThreadStateEnum.OFFLINE.name());
+ }
+
+ public Boolean isQueuing() {
+ return this.state.equals(ThreadStateEnum.QUEUING.name());
+ }
+
+ public Boolean isProcessing() {
+ return !this.isClosed() && !this.isOffline();
+ }
+
public Boolean isCustomerService() {
return this.type.equals(ThreadTypeEnum.AGENT.name())
|| this.type.equals(ThreadTypeEnum.WORKGROUP.name());
@@ -242,4 +275,24 @@ public class ThreadEntity extends BaseEntity {
return this.client.equals(ClientEnum.WECHAT_MINI.name());
}
+ public ThreadEntity setOffline() {
+ this.state = ThreadStateEnum.OFFLINE.name();
+ return this;
+ }
+
+ public ThreadEntity setStart() {
+ this.state = ThreadStateEnum.STARTED.name();
+ return this;
+ }
+
+ public ThreadEntity setClose() {
+ this.state = ThreadStateEnum.CLOSED.name();
+ return this;
+ }
+
+ public ThreadEntity setQueuing() {
+ this.state = ThreadStateEnum.QUEUING.name();
+ return this;
+ }
+
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadEventListener.java b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadEventListener.java
index d181f532a6..ae8d8c99fc 100644
--- a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadEventListener.java
+++ b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadEventListener.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-06-28 13:32:23
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-10-23 22:50:54
+ * @LastEditTime: 2024-12-05 12:11:04
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -14,7 +14,7 @@
*/
package com.bytedesk.core.thread;
-import java.util.List;
+// import java.util.List;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@@ -24,6 +24,7 @@ import com.bytedesk.core.message.MessageCreateEvent;
import com.bytedesk.core.message.MessageTypeEnum;
import com.bytedesk.core.quartz.event.QuartzOneMinEvent;
import com.bytedesk.core.rbac.user.UserEntity;
+import com.bytedesk.core.rbac.user.UserUpdateEvent;
import com.bytedesk.core.topic.TopicCacheService;
import com.bytedesk.core.topic.TopicRequest;
import com.bytedesk.core.topic.TopicService;
@@ -40,9 +41,8 @@ public class ThreadEventListener {
private final TopicCacheService topicCacheService;
- private final ThreadRestService threadService;
-
- private final ThreadPersistCache threadPersistCache;
+ // private final ThreadRestService threadService;
+ // private final ThreadPersistCache threadPersistCache;
@EventListener
public void onThreadCreateEvent(ThreadCreateEvent event) {
@@ -57,6 +57,7 @@ public class ThreadEventListener {
// 创建客服会话之后,需要订阅topic
if (thread.getType().equals(ThreadTypeEnum.AGENT.name())
|| thread.getType().equals(ThreadTypeEnum.WORKGROUP.name())
+ || thread.getType().equals(ThreadTypeEnum.MEMBER.name())
|| thread.getType().equals(ThreadTypeEnum.LLM.name())) {
// 防止首次消息延迟,立即订阅
TopicRequest request = TopicRequest.builder()
@@ -88,6 +89,7 @@ public class ThreadEventListener {
if (thread.getType().equals(ThreadTypeEnum.AGENT.name())
|| thread.getType().equals(ThreadTypeEnum.WORKGROUP.name())
+ || thread.getType().equals(ThreadTypeEnum.MEMBER.name())
|| thread.getType().equals(ThreadTypeEnum.LLM.name())) {
// 防止首次消息延迟,立即订阅
TopicRequest request = TopicRequest.builder()
@@ -123,11 +125,19 @@ public class ThreadEventListener {
@EventListener
public void onQuartzOneMinEvent(QuartzOneMinEvent event) {
- List threadList = threadPersistCache.getListForPersist();
- if (threadList != null) {
- threadList.forEach(thread -> {
- threadService.save(thread);
- });
- }
+ // List threadList = threadPersistCache.getListForPersist();
+ // if (threadList != null) {
+ // threadList.forEach(thread -> {
+ // threadService.save(thread);
+ // });
+ // }
}
+
+ @EventListener
+ public void onUserUpdateEvent(UserUpdateEvent event) {
+ // todo: on user avatar update, update thread entity user avatar
+ // update member thread avatar
+ }
+
+
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadPersistCache.java b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadPersistCache.java
index ec70860aa9..a248b001e9 100644
--- a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadPersistCache.java
+++ b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadPersistCache.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-08-31 10:43:14
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-31 11:03:26
+ * @LastEditTime: 2024-12-05 11:56:11
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -18,94 +18,138 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
+import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
-import com.github.benmanes.caffeine.cache.Cache;
-import com.github.benmanes.caffeine.cache.CacheLoader;
-import com.github.benmanes.caffeine.cache.Caffeine;
+import com.bytedesk.core.redis.RedisConsts;
+// import com.github.benmanes.caffeine.cache.Cache;
+// import com.github.benmanes.caffeine.cache.CacheLoader;
+// import com.github.benmanes.caffeine.cache.Caffeine;
import jakarta.annotation.PostConstruct;
+import lombok.AllArgsConstructor;
@Component
+@AllArgsConstructor
public class ThreadPersistCache {
-
- // 假设我们使用"myList"作为缓存中的键
- String defaultPersistKey = "threadList";
- // 创建一个缓存实例,设置过期时间为5天
- private Cache> threadCache;
+ private static final String DEFAULT_PERSIST_KEY = RedisConsts.BYTEDESK_REDIS_PREFIX + "threadList";
+ private static final long EXPIRE_TIME = 1; // 1天
+ private final StringRedisTemplate stringRedisTemplate;
+
+ // // 假设我们使用"myList"作为缓存中的键
+ // String defaultPersistKey = "threadList";
+ // // 创建一个缓存实例,设置过期时间为5天
+ // private Cache> threadCache;
@PostConstruct
public void init() {
- // 初始化caffeinecache,设置缓存的最大大小、过期时间等参数
- threadCache = Caffeine.newBuilder()
- .expireAfterWrite(1, TimeUnit.DAYS)
- .build(new CacheLoader>() {
- @Override
- public List load(String key) throws Exception {
- // 当缓存中没有找到对应的键时,使用load方法初始化
- return new ArrayList<>();
- }
- });
+ // 初始化caffeineCache,设置缓存的最大大小、过期时间等参数
+ // threadCache = Caffeine.newBuilder()
+ // .expireAfterWrite(1, TimeUnit.DAYS)
+ // .build(new CacheLoader>() {
+ // @Override
+ // public List load(String key) throws Exception {
+ // // 当缓存中没有找到对应的键时,使用load方法初始化
+ // return new ArrayList<>();
+ // }
+ // });
+ // 初始化 Redis 配置
}
// 模拟 push 操作:向列表中添加元素
- public void pushForPersist(ThreadEntity thread) {
- // 通过thread.uid判断defaultPersistKey中是否已经存在 则替换掉,不存在,则插入
- String uid = thread.getUid();
- List cachedList = threadCache.getIfPresent(defaultPersistKey);
- if (cachedList == null) {
- cachedList = new ArrayList<>();
- }
-
- boolean found = false;
- for (int i = 0; i < cachedList.size(); i++) {
- if (cachedList.get(i).getUid().equals(uid)) {
- found = true;
- cachedList.set(i, thread); // 替换已存在的Thread对象
- break;
- }
- }
-
- if (!found) {
- cachedList.add(thread);
- }
- threadCache.put(defaultPersistKey, cachedList);
- // push(defaultPersistKey, thread);
+ public void pushForPersist(String threadJSON) {
+ push(DEFAULT_PERSIST_KEY, threadJSON);
}
// 模拟 pop 操作:从列表中移除元素
- public List getListForPersist() {
- return getList(defaultPersistKey);
+ public List getListForPersist() {
+ return getList(DEFAULT_PERSIST_KEY);
}
// 模拟 push 操作:向列表中添加元素
- public void push(String listKey, ThreadEntity thread) {
- List cachedList = threadCache.getIfPresent(listKey);
- if (cachedList == null) {
- // 如果缓存中没有找到对应的键,则使用load方法初始化
- cachedList = new ArrayList<>();
+ public void push(String listKey, String threadJSON) {
+ stringRedisTemplate.opsForList().rightPush(listKey, threadJSON);
+ stringRedisTemplate.expire(listKey, EXPIRE_TIME, TimeUnit.DAYS);
+ }
+
+ public void pushGroup(String groupUid, String threadJSON) {
+ // 可以在这里实现推送到特定组的逻辑
+ }
+
+ public List getList(String listKey) {
+ Long size = stringRedisTemplate.opsForList().size(listKey);
+ if (size == null || size == 0) {
+ return new ArrayList<>();
}
- cachedList.add(thread);
- threadCache.put(listKey, cachedList);
+ List threads = stringRedisTemplate.opsForList().range(listKey, 0, size - 1);
+ // 从缓存中删除列表内容
+ clearCache(listKey);
+ return threads;
}
- public List getList(String listKey) {
- List cachedList = threadCache.getIfPresent(listKey);
- if (cachedList != null && !cachedList.isEmpty()) {
- // 只需要返回一次即可
- remove(listKey);
- return cachedList;
- }
- return null;
+ // 清空缓存
+ public void clearCache(String listKey) {
+ stringRedisTemplate.delete(listKey);
}
- public void remove(String listKey) {
- threadCache.invalidate(listKey);
- }
+ // 模拟 push 操作:向列表中添加元素
+ // public void pushForPersist(ThreadEntity thread) {
+ // // 通过thread.uid判断defaultPersistKey中是否已经存在 则替换掉,不存在,则插入
+ // String uid = thread.getUid();
+ // List cachedList = threadCache.getIfPresent(defaultPersistKey);
+ // if (cachedList == null) {
+ // cachedList = new ArrayList<>();
+ // }
- public void clear() {
- threadCache.invalidateAll();
- }
+ // boolean found = false;
+ // for (int i = 0; i < cachedList.size(); i++) {
+ // if (cachedList.get(i).getUid().equals(uid)) {
+ // found = true;
+ // cachedList.set(i, thread); // 替换已存在的Thread对象
+ // break;
+ // }
+ // }
+
+ // if (!found) {
+ // cachedList.add(thread);
+ // }
+ // threadCache.put(defaultPersistKey, cachedList);
+ // // push(defaultPersistKey, thread);
+ // }
+
+ // 模拟 pop 操作:从列表中移除元素
+ // public List getListForPersist() {
+ // return getList(defaultPersistKey);
+ // }
+
+ // // 模拟 push 操作:向列表中添加元素
+ // public void push(String listKey, ThreadEntity thread) {
+ // List cachedList = threadCache.getIfPresent(listKey);
+ // if (cachedList == null) {
+ // // 如果缓存中没有找到对应的键,则使用load方法初始化
+ // cachedList = new ArrayList<>();
+ // }
+ // cachedList.add(thread);
+ // threadCache.put(listKey, cachedList);
+ // }
+
+ // public List getList(String listKey) {
+ // List cachedList = threadCache.getIfPresent(listKey);
+ // if (cachedList != null && !cachedList.isEmpty()) {
+ // // 只需要返回一次即可
+ // remove(listKey);
+ // return cachedList;
+ // }
+ // return null;
+ // }
+
+ // public void remove(String listKey) {
+ // threadCache.invalidate(listKey);
+ // }
+
+ // public void clear() {
+ // threadCache.invalidateAll();
+ // }
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadRequest.java b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadRequest.java
index 784c948626..10d5a95c78 100644
--- a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadRequest.java
+++ b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadRequest.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-02-21 10:01:12
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-07 15:54:50
+ * @LastEditTime: 2024-12-02 11:20:20
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -52,6 +52,9 @@ public class ThreadRequest extends BaseRequest {
@Builder.Default
private Boolean unread = false;
+ @Builder.Default
+ private Integer serialNumber = 1;
+
@Builder.Default
private Integer unreadCount = 0;
diff --git a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadResponse.java b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadResponse.java
index 46a6c69d33..0d9423f85f 100644
--- a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadResponse.java
+++ b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadResponse.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-02-21 10:01:27
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-09-20 09:50:26
+ * @LastEditTime: 2024-12-02 11:16:32
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -52,6 +52,8 @@ public class ThreadResponse extends BaseResponse {
private Boolean unread;
+ private Integer serialNumber;
+
private Integer unreadCount;
private Boolean mute;
diff --git a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadRestController.java b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadRestController.java
index fbf977eeac..f8df0b06e5 100644
--- a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadRestController.java
+++ b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadRestController.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:21:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-23 14:17:52
+ * @LastEditTime: 2024-12-04 15:44:31
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -139,7 +139,7 @@ public class ThreadRestController extends BaseRestController {
//
JSONObject jsonObject = new JSONObject();
jsonObject.put("status", "failure");
- jsonObject.put("message", "download faied " + e.getMessage());
+ jsonObject.put("message", "download failed " + e.getMessage());
try {
response.getWriter().println(JSON.toJSONString(jsonObject));
} catch (IOException e1) {
diff --git a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadRestService.java b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadRestService.java
index f39c100326..a8163f02e0 100644
--- a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadRestService.java
+++ b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadRestService.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:21:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-23 12:45:34
+ * @LastEditTime: 2024-12-04 15:45:38
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -34,7 +34,9 @@ import org.springframework.util.StringUtils;
import com.alibaba.fastjson2.JSON;
import com.bytedesk.core.base.BaseRestService;
+import com.bytedesk.core.config.BytedeskEventPublisher;
import com.bytedesk.core.enums.ClientEnum;
+import com.bytedesk.core.event.GenericApplicationEvent;
import com.bytedesk.core.message.IMessageSendService;
import com.bytedesk.core.message.MessageProtobuf;
import com.bytedesk.core.message.MessageTypeEnum;
@@ -67,6 +69,8 @@ public class ThreadRestService extends BaseRestService queryByOrg(ThreadRequest request) {
// 优先加载最近更新的会话记录,updatedAt越大越新
@@ -83,7 +87,7 @@ public class ThreadRestService extends BaseRestService query(ThreadRequest pageParam) {
- UserEntity user = authService.getCurrentUser();
+ UserEntity user = authService.getUser();
// 优先加载最近更新的会话记录,updatedAt越大越新
Pageable pageable = PageRequest.of(pageParam.getPageNumber(), pageParam.getPageSize(), Sort.Direction.DESC,
@@ -105,7 +109,7 @@ public class ThreadRestService extends BaseRestService threadOptional = findByTopicAndOwner(request.getTopic(), owner);
if (threadOptional.isPresent()) {
@@ -113,7 +117,7 @@ public class ThreadRestService extends BaseRestService threadOptional = findByTopicAndOwner(topic, user);
@@ -284,6 +287,8 @@ public class ThreadRestService extends BaseRestService(this, new ThreadCloseEvent(this, updateThread)));
// 发布关闭消息, 通知用户
String content = threadRequest.getAutoClose()
? I18Consts.I18N_AUTO_CLOSED
diff --git a/modules/core/src/main/java/com/bytedesk/core/topic/TopicEntity.java b/modules/core/src/main/java/com/bytedesk/core/topic/TopicEntity.java
index d7b82ac99e..2c4107729b 100644
--- a/modules/core/src/main/java/com/bytedesk/core/topic/TopicEntity.java
+++ b/modules/core/src/main/java/com/bytedesk/core/topic/TopicEntity.java
@@ -45,7 +45,7 @@ public class TopicEntity extends BaseEntityNoOrg {
private static final long serialVersionUID = 1L;
- /** 为防止后添加的记录,clientIds缺失,所以用数组代替,这样每个用户在topic中只有一条记录,cliendIds可共用 */
+ /** 为防止后添加的记录,clientIds缺失,所以用数组代替,这样每个用户在topic中只有一条记录,clientIds可共用 */
@Builder.Default
@Column(columnDefinition = TypeConsts.COLUMN_TYPE_TEXT)
@Convert(converter = StringSetConverter.class)
diff --git a/modules/core/src/main/java/com/bytedesk/core/topic/TopicEventListener.java b/modules/core/src/main/java/com/bytedesk/core/topic/TopicEventListener.java
index 44c29892f6..ce50478799 100644
--- a/modules/core/src/main/java/com/bytedesk/core/topic/TopicEventListener.java
+++ b/modules/core/src/main/java/com/bytedesk/core/topic/TopicEventListener.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-05-29 15:11:57
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-09-25 10:37:07
+ * @LastEditTime: 2024-12-04 17:42:56
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -15,14 +15,17 @@
package com.bytedesk.core.topic;
import java.util.List;
+import java.util.Set;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson2.JSON;
import com.bytedesk.core.event.GenericApplicationEvent;
import com.bytedesk.core.quartz.event.QuartzFiveSecondEvent;
+import com.bytedesk.core.quartz.event.QuartzOneMinEvent;
import com.bytedesk.core.rbac.user.UserEntity;
import com.bytedesk.core.rbac.user.UserLogoutEvent;
+import com.bytedesk.core.socket.mqtt.MqttConnectionService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -35,6 +38,9 @@ public class TopicEventListener {
private final TopicService topicService;
private final TopicCacheService topicCacheService;
+
+ private final MqttConnectionService mqttConnectionService;
+
// 此处使用static,否则在定时器中无法读取初始化时期的数据
// private final static TopicCacheService topicCacheService = new TopicCacheService();
// private final static String cacheKey = "topicList";
@@ -72,12 +78,29 @@ public class TopicEventListener {
}
}
+ @EventListener
+ public void onQuartzOneMinEvent(QuartzOneMinEvent event) {
+ Set clientIds = mqttConnectionService.getConnectedClientIds();
+ // log.info("topic QuartzOneMinEvent {}", clientIds);
+ // current connected clientIds
+ if (clientIds != null) {
+ // todo: clear topic clientIds not in clientIds
+
+ // add clientIds to topic
+ for (String clientId : clientIds) {
+ // 用户clientId格式: userUid/client/deviceUid
+ // log.info("topic onQuartzOneMinEvent connected clientId: {}", clientId);
+ topicService.addClientId(clientId);
+ }
+ }
+ }
+
@EventListener
public void onUserLogoutEvent(GenericApplicationEvent event) {
UserLogoutEvent userLogoutEvent = event.getObject();
UserEntity user = userLogoutEvent.getUser();
log.info("topic onUserLogoutEvent: {}", user.getUsername());
- // TODO: 用户登录之后,删除相关clientId
+ // TODO: user logout event, remove user from topic
}
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/topic/TopicService.java b/modules/core/src/main/java/com/bytedesk/core/topic/TopicService.java
index 8dc1c215bb..4f2f83c650 100644
--- a/modules/core/src/main/java/com/bytedesk/core/topic/TopicService.java
+++ b/modules/core/src/main/java/com/bytedesk/core/topic/TopicService.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-04-13 16:14:36
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-11-20 11:19:23
+ * @LastEditTime: 2024-12-04 16:56:26
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -18,21 +18,18 @@ import java.util.LinkedList;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
import org.modelmapper.ModelMapper;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.springframework.scheduling.annotation.Async;
-import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson2.JSONObject;
import com.bytedesk.core.action.ActionRequest;
-import com.bytedesk.core.action.ActionService;
+import com.bytedesk.core.action.ActionRestService;
import com.bytedesk.core.action.ActionTypeEnum;
import com.bytedesk.core.uid.UidUtils;
@@ -50,9 +47,9 @@ public class TopicService {
private final UidUtils uidUtils;
- private final ActionService actionService;
+ private final ActionRestService actionService;
- private final ConcurrentHashMap concurrentMap = new ConcurrentHashMap<>();
+ // private final ConcurrentHashMap concurrentMap = new ConcurrentHashMap<>();
public void create(String topic, String uid) {
TopicRequest topicRequest = TopicRequest.builder()
@@ -122,7 +119,7 @@ public class TopicService {
}
public void unsubscribe(String topic, String clientId) {
- // 用户clientId格式: uid/client/deviceUid
+ // 用户clientId格式: userUid/client/deviceUid
Optional topicOptional = findByClientId(clientId);
if (topicOptional.isPresent()) {
TopicEntity topicElement = topicOptional.get();
@@ -140,9 +137,9 @@ public class TopicService {
@Async
public void addClientId(String clientId) {
- concurrentMap.remove(clientId);
+ // concurrentMap.remove(clientId);
- // 用户clientId格式: uid/client/deviceUid
+ // 用户clientId格式: userUid/client/deviceUid
Optional topicOptional = findByClientId(clientId);
if (topicOptional.isPresent()) {
TopicEntity topic = topicOptional.get();
@@ -154,6 +151,19 @@ public class TopicService {
}
}
+ private void doRemoveClientId(String clientId) {
+ // 用户clientId格式: userUid/client/deviceUid
+ Optional topicOptional = findByClientId(clientId);
+ if (topicOptional.isPresent()) {
+ TopicEntity topic = topicOptional.get();
+ if (topic.getClientIds().contains(clientId)) {
+ log.info("removeClientId: {}", clientId);
+ topic.getClientIds().remove(clientId);
+ save(topic);
+ }
+ }
+ }
+
@Async
public void removeClientId(String clientId) {
// TODO: 防止客户端频繁闪断重连的情况,延迟执行,防止频繁删除
@@ -161,27 +171,14 @@ public class TopicService {
doRemoveClientId(clientId);
}
- private void doRemoveClientId(String clientId) {
- // 用户clientId格式: uid/client/deviceUid
- Optional topicOptional = findByClientId(clientId);
- if (topicOptional.isPresent()) {
- TopicEntity topic = topicOptional.get();
- if (topic.getClientIds().contains(clientId)) {
- log.info("removeClientId: {}", clientId);
- topic.getClientIds().remove(clientId);
- save(topic);
- }
- }
- }
-
// 5分钟没有重连成功的话,就删除掉
- @Scheduled(fixedDelay = 5 * 60 * 1000)
- public void scheduleTask() {
- // log.info("scheduleTask");
- concurrentMap.forEach((key, value) -> {
- doRemoveClientId(key);
- });
- }
+ // @Scheduled(fixedDelay = 5 * 60 * 1000)
+ // public void scheduleTask() {
+ // // log.info("scheduleTask");
+ // concurrentMap.forEach((key, value) -> {
+ // doRemoveClientId(key);
+ // });
+ // }
@Cacheable(value = "topic", key = "#uid")
public Optional findByUid(String uid) {
@@ -190,9 +187,9 @@ public class TopicService {
@Cacheable(value = "topic", key = "#clientId", unless = "#result == null")
public Optional findByClientId(String clientId) {
- // 用户clientId格式: uid/client/deviceUid
- final String uid = clientId.split("/")[0];
- return findByUserUid(uid);
+ // 用户clientId格式: userUid/client/deviceUid
+ final String userUid = clientId.split("/")[0];
+ return findByUserUid(userUid);
}
@Cacheable(value = "topic", key = "#uid", unless = "#result == null")
@@ -203,8 +200,6 @@ public class TopicService {
@Cacheable(value = "topic", key = "#topic", unless="#result == null")
public Set findByTopic(String topic) {
return topicRepository.findByTopicsContains(topic);
- // return topics;
- // return topics.stream().map(this::convertToTopicResponse).toList();
}
@Caching(put = {
diff --git a/modules/core/src/main/java/com/bytedesk/core/topic/TopicUtils.java b/modules/core/src/main/java/com/bytedesk/core/topic/TopicUtils.java
index bb173b38b6..a1514aa329 100644
--- a/modules/core/src/main/java/com/bytedesk/core/topic/TopicUtils.java
+++ b/modules/core/src/main/java/com/bytedesk/core/topic/TopicUtils.java
@@ -124,7 +124,7 @@ public class TopicUtils {
//////////////////////////////////////////////////////////////////////////
public static Boolean isOrgMemberTopic(String topic) {
- return topic.startsWith(TOPIC_ORG_MEMBER_PATTERN);
+ return topic.startsWith(TOPIC_ORG_MEMBER_PREFIX);
}
public static String formatOrgMemberTopic(String memberUid) {
diff --git a/modules/core/src/main/java/com/bytedesk/core/uid/worker/DisposableWorkerIdAssigner.java b/modules/core/src/main/java/com/bytedesk/core/uid/worker/DisposableWorkerIdAssigner.java
index d7e6cf4129..6e5e1f1975 100644
--- a/modules/core/src/main/java/com/bytedesk/core/uid/worker/DisposableWorkerIdAssigner.java
+++ b/modules/core/src/main/java/com/bytedesk/core/uid/worker/DisposableWorkerIdAssigner.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2021-02-24 15:52:39
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-05-04 11:24:01
+ * @LastEditTime: 2024-12-07 11:10:29
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -12,22 +12,6 @@
* 联系:270580156@qq.com
* Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
*/
-/*
- * Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
package com.bytedesk.core.uid.worker;
import com.bytedesk.core.uid.UidGeneratorEntity;
diff --git a/modules/core/src/main/java/com/bytedesk/core/utils/DateUtils.java b/modules/core/src/main/java/com/bytedesk/core/utils/DateUtils.java
index 5232bae3b4..739004a072 100644
--- a/modules/core/src/main/java/com/bytedesk/core/utils/DateUtils.java
+++ b/modules/core/src/main/java/com/bytedesk/core/utils/DateUtils.java
@@ -30,8 +30,8 @@ public class DateUtils {
private static final String timeFormat = "HH:mm:ss";
public static String formatDatetimeNow() {
- SimpleDateFormat dateFormater = new SimpleDateFormat(datetimeFormat);
- return dateFormater.format(new Date());
+ SimpleDateFormat dateFormatter = new SimpleDateFormat(datetimeFormat);
+ return dateFormatter.format(new Date());
}
public static String formatDatetimeToString(Date date) {
@@ -40,9 +40,9 @@ public class DateUtils {
}
public static Date formatStringToDateTime(String date) {
- SimpleDateFormat dateFormater = new SimpleDateFormat(datetimeFormat);
+ SimpleDateFormat dateFormatter = new SimpleDateFormat(datetimeFormat);
try {
- return dateFormater.parse(date);
+ return dateFormatter.parse(date);
} catch (ParseException e) {
e.printStackTrace();
}
@@ -50,13 +50,13 @@ public class DateUtils {
}
public static String formatDatetimeUid() {
- SimpleDateFormat dateFormater = new SimpleDateFormat(datetimeUidFormat);
- return dateFormater.format(new Date());
+ SimpleDateFormat dateFormatter = new SimpleDateFormat(datetimeUidFormat);
+ return dateFormatter.format(new Date());
}
public static String formatDateNow() {
- SimpleDateFormat dateFormater = new SimpleDateFormat(dateFormat);
- return dateFormater.format(new Date());
+ SimpleDateFormat dateFormatter = new SimpleDateFormat(dateFormat);
+ return dateFormatter.format(new Date());
}
public static Date formatStringToTime(String time) {
@@ -78,13 +78,13 @@ public class DateUtils {
}
public static String formatDateSlashNow() {
- SimpleDateFormat dateFormater = new SimpleDateFormat(dateFormatSlash);
- return dateFormater.format(new Date());
+ SimpleDateFormat dateFormatter = new SimpleDateFormat(dateFormatSlash);
+ return dateFormatter.format(new Date());
}
public static String formatDateSlashNowNoZero() {
- SimpleDateFormat dateFormater = new SimpleDateFormat(dateFormatSlashNoZero);
- return dateFormater.format(new Date());
+ SimpleDateFormat dateFormatter = new SimpleDateFormat(dateFormatSlashNoZero);
+ return dateFormatter.format(new Date());
}
public static String formatDateToString(Date date) {
@@ -108,7 +108,7 @@ public class DateUtils {
// 将英文日期格式 转未 中文日期格式
// 29 January, 2017
// enDate:29 January,2017, cnDate 2017-01-29
- public static String transformEndateToCnDate(String enDate) {
+ public static String transformEnDateToCnDate(String enDate) {
if (!StringUtils.hasText(enDate)) {
return "";
}
@@ -157,7 +157,7 @@ public class DateUtils {
// 将英文日期格式 转未 中文日期格式
// 日期 + 1
public static String transformDateForVoa(String enDate) {
- String cnDate = transformEndateToCnDate(enDate);
+ String cnDate = transformEnDateToCnDate(enDate);
return dateStringAddOneDayToString(cnDate);
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/utils/PhoneToRegionUtil.java b/modules/core/src/main/java/com/bytedesk/core/utils/PhoneToRegionUtil.java
new file mode 100644
index 0000000000..e2f50fc671
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/utils/PhoneToRegionUtil.java
@@ -0,0 +1,111 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-12-04 11:29:17
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-12-04 12:37:00
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.utils;
+
+import java.util.Locale;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.google.i18n.phonenumbers.NumberParseException;
+import com.google.i18n.phonenumbers.PhoneNumberToCarrierMapper;
+import com.google.i18n.phonenumbers.PhoneNumberUtil;
+import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
+import com.google.i18n.phonenumbers.Phonenumber;
+import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
+import com.google.i18n.phonenumbers.geocoding.PhoneNumberOfflineGeocoder;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * https://github.com/google/libphonenumber/
+ * https://www.baeldung.com/java-libphonenumber
+ * 基于google的libphonenumber将手机号转成地区及供应商信息
+ */
+@Slf4j
+public class PhoneToRegionUtil {
+ private PhoneToRegionUtil() {
+ }
+
+ // 手机号工具
+ private final static PhoneNumberUtil PHONE_NUMBER_UTIL = PhoneNumberUtil.getInstance();
+ // 运营商
+ private final static PhoneNumberToCarrierMapper CARRIER_MAPPER = PhoneNumberToCarrierMapper.getInstance();
+ // 地址
+ private final static PhoneNumberOfflineGeocoder GEO_CODER = PhoneNumberOfflineGeocoder.getInstance();
+
+ /**
+ * 验证当前手机号是否有效
+ * @param phone 手机号
+ * @return 校验结果
+ */
+ public static boolean isValidNumber(String phone){
+ return PHONE_NUMBER_UTIL.isValidNumber(getPhoneNumber(phone));
+ }
+
+ /**
+ * 获取手机号运营商
+ * @param phone 手机号
+ * @return 运营商
+ */
+ public static String getPhoneCarrier(String phone){
+ return isValidNumber(phone) ? CARRIER_MAPPER.getNameForNumber(getPhoneNumber(phone), Locale.CHINA) : "";
+ }
+
+ /**
+ * 获取手机号归属地
+ * @param phone 手机号
+ * @return 归属地
+ */
+ public static String getPhoneRegin(String phone){
+ return isValidNumber(phone) ? GEO_CODER.getDescriptionForNumber(getPhoneNumber(phone),Locale.CHINESE) : "";
+ }
+
+ /**
+ * 生成PhoneNumber
+ * @param phone 手机号
+ * @return PhoneNumber
+ */
+ private static Phonenumber.PhoneNumber getPhoneNumber(String phone){
+ Phonenumber.PhoneNumber phoneNumber = new Phonenumber.PhoneNumber();
+ phoneNumber.setCountryCode(86);
+ phoneNumber.setNationalNumber(Long.parseLong(phone));
+ return phoneNumber;
+ }
+
+ public String formatPhoneNumber(String phoneNumber, String regionCode) {
+ try {
+ PhoneNumber number = PHONE_NUMBER_UTIL.parse(phoneNumber, regionCode);
+ return PHONE_NUMBER_UTIL.format(number, PhoneNumberFormat.E164); // E.164 格式
+ } catch (NumberParseException e) {
+ System.err.println("NumberParseException was thrown: " + e.toString());
+ return null;
+ }
+ }
+
+ /**
+ * Get the phone number's attribution information: carrier, region
+ *
+ * @param phone Phone number
+ * @return Attribution information
+ */
+ public static JSONObject getPhoneAffiliationInfo(String phone){
+ JSONObject affiliation = new JSONObject();
+ affiliation.put("phone",phone);
+ affiliation.put("carrier",getPhoneCarrier(phone));
+ affiliation.put("region",getPhoneRegin(phone));
+ return affiliation;
+ }
+
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowController.java b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowController.java
new file mode 100644
index 0000000000..715b41a9cf
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowController.java
@@ -0,0 +1,110 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-12-10 12:15:41
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-12-11 11:24:19
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 技术/商务联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.workflow.flow;
+
+import lombok.RequiredArgsConstructor;
+
+import org.springframework.data.domain.Page;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import com.bytedesk.core.base.BaseRestController;
+import com.bytedesk.core.utils.JsonResult;
+
+@RestController
+@RequestMapping("/api/v1/flow")
+@RequiredArgsConstructor
+@CrossOrigin(origins = {"http://localhost:9012", "http://localhost:5173"})
+public class FlowController extends BaseRestController {
+
+ private final FlowRestService flowService;
+
+ // http://localhost:8080/api/v1/flow/query/org
+ @Override
+ public ResponseEntity> queryByOrg(FlowRequest request) {
+
+ Page page = flowService.queryByOrg(request);
+
+ return ResponseEntity.ok(JsonResult.success(page));
+ }
+
+ @Override
+ public ResponseEntity> queryByUser(FlowRequest request) {
+
+ Page page = flowService.queryByUser(request);
+
+ return ResponseEntity.ok(JsonResult.success(page));
+ }
+
+ @Override
+ public ResponseEntity> create(FlowRequest request) {
+
+ FlowResponse response = flowService.create(request);
+
+ return ResponseEntity.ok(JsonResult.success(response));
+ }
+
+ @Override
+ public ResponseEntity> update(FlowRequest request) {
+
+ FlowResponse response = flowService.update(request);
+
+ return ResponseEntity.ok(JsonResult.success(response));
+ }
+
+ @Override
+ public ResponseEntity> delete(FlowRequest request) {
+
+ flowService.delete(request);
+
+ return ResponseEntity.ok(JsonResult.success());
+ }
+
+ // @GetMapping
+ // public List getFlows(@RequestParam String orgUid) {
+ // return flowService.getFlowsByOrgUid(orgUid);
+ // }
+
+ // @GetMapping("/{id}")
+ // public FlowEntity getFlow(@PathVariable Long id) {
+ // return flowService.getFlowById(id);
+ // }
+
+ // @PostMapping
+ // public FlowEntity createFlow(@RequestBody FlowEntity flow) {
+ // return flowService.createFlow(flow);
+ // }
+
+ // @PutMapping("/{id}")
+ // public FlowEntity updateFlow(@PathVariable Long id, @RequestBody FlowEntity
+ // flow) {
+ // return flowService.updateFlow(id, flow);
+ // }
+
+ // @DeleteMapping("/{id}")
+ // public void deleteFlow(@PathVariable Long id) {
+ // flowService.deleteFlow(id);
+ // }
+
+ // @GetMapping("/public/{publicId}")
+ // public FlowEntity getPublicFlow(@PathVariable String publicId) {
+ // return flowService.getFlowByPublicId(publicId);
+ // }
+
+ // @GetMapping("/domain/{customDomain}")
+ // public FlowEntity getFlowByDomain(@PathVariable String customDomain) {
+ // return flowService.getFlowByCustomDomain(customDomain);
+ // }
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowEntity.java b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowEntity.java
new file mode 100644
index 0000000000..bb45786192
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowEntity.java
@@ -0,0 +1,100 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-12-10 17:01:55
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-12-11 17:24:07
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 技术/商务联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.workflow.flow;
+
+import com.bytedesk.core.base.BaseEntity;
+import com.bytedesk.core.constant.TypeConsts;
+import com.bytedesk.core.enums.LevelEnum;
+
+import jakarta.persistence.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import org.hibernate.annotations.JdbcTypeCode;
+import org.hibernate.type.SqlTypes;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@Entity
+@Table(name = "bytedesk_core_workflow_flow")
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public class FlowEntity extends BaseEntity {
+
+ private String name;
+
+ private String description;
+
+ private String icon;
+
+ @Builder.Default
+ @Column(name = "flow_groups", columnDefinition = TypeConsts.COLUMN_TYPE_JSON)
+ @JdbcTypeCode(SqlTypes.JSON)
+ private String groups = "[]";
+
+ @Builder.Default
+ @Column(name = "flow_events", columnDefinition = TypeConsts.COLUMN_TYPE_JSON)
+ @JdbcTypeCode(SqlTypes.JSON)
+ private String events = "[]";
+
+ @Builder.Default
+ @Column(name = "flow_variables", columnDefinition = TypeConsts.COLUMN_TYPE_JSON)
+ @JdbcTypeCode(SqlTypes.JSON)
+ private String variables = "[]";
+
+ @Builder.Default
+ @Column(name = "flow_edges", columnDefinition = TypeConsts.COLUMN_TYPE_JSON)
+ @JdbcTypeCode(SqlTypes.JSON)
+ private String edges = "[]";
+
+ @Builder.Default
+ @Column(name = "flow_theme", columnDefinition = TypeConsts.COLUMN_TYPE_JSON)
+ @JdbcTypeCode(SqlTypes.JSON)
+ private String theme = "{}";
+
+ private String selectedThemeTemplateId;
+
+ @Builder.Default
+ @Column(name = "flow_settings", columnDefinition = TypeConsts.COLUMN_TYPE_JSON)
+ @JdbcTypeCode(SqlTypes.JSON)
+ private String settings = "{}";
+
+ private String publicId;
+
+ private String customDomain;
+
+ @Builder.Default
+ @Column(columnDefinition = TypeConsts.COLUMN_TYPE_JSON)
+ @JdbcTypeCode(SqlTypes.JSON)
+ private String resultsTablePreferences = "{}";
+
+ @Builder.Default
+ private boolean isArchived = false;
+
+ @Builder.Default
+ private boolean isClosed = false;
+
+ private String whatsAppCredentialsId;
+
+ private Integer riskLevel;
+
+ @Builder.Default
+ private String level = LevelEnum.PLATFORM.name();
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowRepository.java b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowRepository.java
new file mode 100644
index 0000000000..5288c47f06
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowRepository.java
@@ -0,0 +1,37 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-12-10 11:35:08
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-12-11 11:13:40
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 技术/商务联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.workflow.flow;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+// import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Repository;
+import java.util.Optional;
+// import java.util.List;
+
+@Repository
+public interface FlowRepository extends JpaRepository, JpaSpecificationExecutor {
+
+ Optional findByUid(String uid);
+
+ // @Query("SELECT f FROM FlowEntity f WHERE f.deleted = false AND f.orgUid = ?1")
+ // List findByOrgUid(String orgUid);
+
+ // @Query("SELECT f FROM FlowEntity f WHERE f.deleted = false AND f.publicId = ?1")
+ // FlowEntity findByPublicId(String publicId);
+
+ // @Query("SELECT f FROM FlowEntity f WHERE f.deleted = false AND f.customDomain = ?1")
+ // FlowEntity findByCustomDomain(String customDomain);
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowRequest.java b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowRequest.java
new file mode 100644
index 0000000000..2c1a32261f
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowRequest.java
@@ -0,0 +1,78 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-12-11 10:56:48
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-12-11 17:24:22
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 技术/商务联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.workflow.flow;
+
+import com.bytedesk.core.base.BaseRequest;
+import com.bytedesk.core.enums.LevelEnum;
+
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.AllArgsConstructor;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class FlowRequest extends BaseRequest {
+
+ private String name;
+
+ private String description;
+
+ private String icon;
+
+ @Builder.Default
+ private String groups = "[]";
+
+ @Builder.Default
+ private String events = "[]";
+
+ @Builder.Default
+ private String variables = "[]";
+
+ @Builder.Default
+ private String edges = "[]";
+
+ @Builder.Default
+ private String theme = "{}";
+
+ private String selectedThemeTemplateId;
+
+ @Builder.Default
+ private String settings = "{}";
+
+ private String publicId;
+
+ private String customDomain;
+
+ @Builder.Default
+ private String resultsTablePreferences = "{}";
+
+ @Builder.Default
+ private boolean isArchived = false;
+
+ @Builder.Default
+ private boolean isClosed = false;
+
+ private String whatsAppCredentialsId;
+
+ private Integer riskLevel;
+
+ @Builder.Default
+ private String level = LevelEnum.PLATFORM.name();
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowResponse.java b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowResponse.java
new file mode 100644
index 0000000000..d3fa1e1c99
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowResponse.java
@@ -0,0 +1,74 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-12-11 16:08:09
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-12-11 17:24:37
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.workflow.flow;
+
+import com.bytedesk.core.base.BaseResponse;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public class FlowResponse extends BaseResponse {
+
+ private String name;
+
+ private String description;
+
+ private String icon;
+
+ @Builder.Default
+ private String groups = "[]";
+
+ @Builder.Default
+ private String events = "[]";
+
+ @Builder.Default
+ private String variables = "[]";
+
+ @Builder.Default
+ private String edges = "[]";
+
+ @Builder.Default
+ private String theme = "{}";
+
+ private String selectedThemeTemplateId;
+
+ @Builder.Default
+ private String settings = "{}";
+
+ private String publicId;
+
+ private String customDomain;
+
+ @Builder.Default
+ private String resultsTablePreferences = "{}";
+
+ @Builder.Default
+ private boolean isArchived = false;
+
+ @Builder.Default
+ private boolean isClosed = false;
+
+ private String whatsAppCredentialsId;
+
+ private String level;
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowRestService.java b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowRestService.java
new file mode 100644
index 0000000000..27839dd358
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowRestService.java
@@ -0,0 +1,170 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-12-10 11:35:14
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-12-11 11:39:58
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 技术/商务联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.workflow.flow;
+
+import lombok.AllArgsConstructor;
+
+import org.modelmapper.ModelMapper;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.orm.ObjectOptimisticLockingFailureException;
+import org.springframework.stereotype.Service;
+
+import com.bytedesk.core.base.BaseRestService;
+import com.bytedesk.core.utils.Utils;
+
+import java.util.Optional;
+
+@Service
+@AllArgsConstructor
+public class FlowRestService extends BaseRestService {
+
+ private final FlowRepository flowRepository;
+
+ private final ModelMapper modelMapper;
+
+ @Override
+ public Page queryByOrg(FlowRequest request) {
+ Pageable pageable = PageRequest.of(request.getPageNumber(), request.getPageSize(), Sort.Direction.ASC,
+ "updatedAt");
+ Specification spec = FlowSpecification.search(request);
+ Page page = flowRepository.findAll(spec, pageable);
+ return page.map(this::convertToResponse);
+ }
+
+ @Override
+ public Page queryByUser(FlowRequest request) {
+ Pageable pageable = PageRequest.of(request.getPageNumber(), request.getPageSize(), Sort.Direction.ASC,
+ "updatedAt");
+ Specification spec = FlowSpecification.search(request);
+ Page page = flowRepository.findAll(spec, pageable);
+ return page.map(this::convertToResponse);
+ }
+
+ @Override
+ public Optional findByUid(String uid) {
+ return flowRepository.findByUid(uid);
+ }
+
+ @Override
+ public FlowResponse create(FlowRequest request) {
+
+ FlowEntity flow = modelMapper.map(request, FlowEntity.class);
+ flow.setUid(Utils.getUid());
+
+ FlowEntity savedFlow = save(flow);
+ if (savedFlow == null) {
+ throw new RuntimeException("Failed to create flow");
+ }
+
+ return convertToResponse(flowRepository.save(flow));
+ }
+
+ @Override
+ public FlowResponse update(FlowRequest request) {
+
+ Optional flow = flowRepository.findByUid(request.getUid());
+ if (flow.isPresent()) {
+ FlowEntity flowEntity = flow.get();
+ modelMapper.map(request, flowEntity);
+ return convertToResponse(flowRepository.save(flowEntity));
+ }
+ return null;
+ }
+
+ @Override
+ public FlowEntity save(FlowEntity entity) {
+ try {
+ return flowRepository.save(entity);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Override
+ public void deleteByUid(String uid) {
+ Optional flow = flowRepository.findByUid(uid);
+ if (flow.isPresent()) {
+ flow.get().setDeleted(true);
+ save(flow.get());
+ }
+ }
+
+ @Override
+ public void delete(FlowRequest request) {
+ deleteByUid(request.getUid());
+ }
+
+ @Override
+ public void handleOptimisticLockingFailureException(ObjectOptimisticLockingFailureException e, FlowEntity entity) {
+ // TODO Auto-generated method stub
+ throw new UnsupportedOperationException("Unimplemented method 'handleOptimisticLockingFailureException'");
+ }
+
+ @Override
+ public FlowResponse convertToResponse(FlowEntity entity) {
+ return modelMapper.map(entity, FlowResponse.class);
+ }
+
+ // public FlowEntity createFlow(FlowEntity flow) {
+ // flow.setCreatedAt(LocalDateTime.now());
+ // flow.setUpdatedAt(LocalDateTime.now());
+ // return flowRepository.save(flow);
+ // }
+
+ // public FlowEntity updateFlow(String id, FlowEntity flow) {
+ // FlowEntity existingFlow = flowRepository.findById(id)
+ // .orElseThrow(() -> new RuntimeException("Flow not found"));
+
+ // existingFlow.setName(flow.getName());
+ // existingFlow.setDescription(flow.getDescription());
+ // // existingFlow.setGroups(flow.getGroups());
+ // // existingFlow.setEdges(flow.getEdges());
+ // // existingFlow.setVariables(flow.getVariables());
+ // existingFlow.setSettings(flow.getSettings());
+ // existingFlow.setUpdatedAt(LocalDateTime.now());
+
+ // return flowRepository.save(existingFlow);
+ // }
+
+ // public void deleteFlow(String id) {
+ // flowRepository.deleteById(id);
+ // }
+
+ // // public List getWorkspaceFlows(String workspaceId) {
+ // // return flowRepository.findByWorkspaceId(workspaceId);
+ // // }
+
+ // public FlowEntity getFlow(String id) {
+ // return flowRepository.findById(id)
+ // .orElseThrow(() -> new RuntimeException("Flow not found"));
+ // }
+
+ // public FlowEntity publishFlow(String id, String publishedTypeflowId) {
+ // FlowEntity flow = getFlow(id);
+ // // flow.setPublishedTypeflowId(publishedTypeflowId);
+ // flow.setUpdatedAt(LocalDateTime.now());
+ // return flowRepository.save(flow);
+ // }
+
+ // public void validateFlowAccess(String flowId, Map context) {
+ // // TODO: 实现访问权限验证逻辑
+ // }
+
+}
diff --git a/modules/ticket/src/main/java/com/bytedesk/ticket/ticket/TicketSpecification.java b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowSpecification.java
similarity index 72%
rename from modules/ticket/src/main/java/com/bytedesk/ticket/ticket/TicketSpecification.java
rename to modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowSpecification.java
index 0e7f449167..3e3895a77c 100644
--- a/modules/ticket/src/main/java/com/bytedesk/ticket/ticket/TicketSpecification.java
+++ b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowSpecification.java
@@ -1,18 +1,18 @@
/*
* @Author: jackning 270580156@qq.com
- * @Date: 2024-06-30 20:46:30
+ * @Date: 2024-12-11 11:17:52
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-06-30 20:46:32
+ * @LastEditTime: 2024-12-11 14:46:52
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
* 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
* Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
* contact: 270580156@qq.com
- * 联系:270580156@qq.com
+ * 技术/商务联系:270580156@qq.com
* Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
*/
-package com.bytedesk.ticket.ticket;
+package com.bytedesk.core.workflow.flow;
import java.util.ArrayList;
import java.util.List;
@@ -25,15 +25,17 @@ import jakarta.persistence.criteria.Predicate;
import lombok.extern.slf4j.Slf4j;
@Slf4j
-public class TicketSpecification extends BaseSpecification {
+public class FlowSpecification extends BaseSpecification {
- public static Specification search(TicketRequest request) {
+ public static Specification search(FlowRequest request) {
log.info("request: {}", request);
return (root, query, criteriaBuilder) -> {
List predicates = new ArrayList<>();
- predicates.addAll(getBasicPredicates(root, criteriaBuilder, request.getOrgUid()));
+ // predicates.addAll(getBasicPredicates(root, criteriaBuilder, request.getOrgUid()));
+ predicates.add(criteriaBuilder.equal(root.get("deleted"), false));
//
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
}
+
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowTemplate.java b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowTemplate.java
new file mode 100644
index 0000000000..6a25f20718
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowTemplate.java
@@ -0,0 +1,49 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-12-11 12:24:30
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-12-11 12:31:16
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 技术/商务联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.workflow.flow;
+
+import lombok.Data;
+import java.time.LocalDateTime;
+import java.util.List;
+
+import com.bytedesk.core.workflow.flow.model.Event;
+import com.bytedesk.core.workflow.flow.model.Group;
+import com.bytedesk.core.workflow.flow.model.Edge;
+import com.bytedesk.core.workflow.flow.model.Variable;
+
+@Data
+public class FlowTemplate {
+ private String version;
+ private String id;
+ private String name;
+ private List events;
+ private List groups;
+ private List edges;
+ private List variables;
+ private Object theme;
+ private String selectedThemeTemplateId;
+ private Object settings;
+ private LocalDateTime createdAt;
+ private LocalDateTime updatedAt;
+ private String icon;
+ private String folderId;
+ private String publicId;
+ private String customDomain;
+ private String workspaceId;
+ private Object resultsTablePreferences;
+ private boolean isArchived;
+ private boolean isClosed;
+ private String whatsAppCredentialsId;
+}
\ No newline at end of file
diff --git a/modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowTemplateLoader.java b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowTemplateLoader.java
new file mode 100644
index 0000000000..65c63632a8
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/FlowTemplateLoader.java
@@ -0,0 +1,102 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-12-11 12:24:40
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-12-11 14:29:16
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 技术/商务联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.workflow.flow;
+
+import com.alibaba.fastjson2.JSON;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.RequiredArgsConstructor;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.stereotype.Component;
+
+import jakarta.annotation.PostConstruct;
+import java.io.IOException;
+import java.io.InputStream;
+import java.time.LocalDateTime;
+
+@Component
+@RequiredArgsConstructor
+public class FlowTemplateLoader {
+
+ private final ResourceLoader resourceLoader;
+ private final FlowRepository flowRepository;
+ private final ObjectMapper objectMapper;
+
+ @PostConstruct
+ public void loadTemplates() throws IOException {
+ // 加载所有模板文件
+ String[] templates = {
+ "workflows/audio-chat-gpt.json",
+ "workflows/basic-chat-gpt.json",
+ "workflows/chat-gpt-personas.json",
+ "workflows/customer-support.json",
+ "workflows/digital-product-payment.json",
+ "workflows/dog-insurance-offer.json",
+ "workflows/faq.json",
+ "workflows/high-ticket-lead-follow-up.json",
+ "workflows/lead-gen-ai.json",
+ "workflows/lead-gen.json",
+ "workflows/lead-magnet.json",
+ "workflows/lead-scoring.json",
+ "workflows/movie-recommendation.json",
+ "workflows/nps.json",
+ "workflows/onboarding.json",
+ "workflows/openai-assistant-chat.json",
+ "workflows/openai-conditions.json",
+ "workflows/product-recommendation.json",
+ "workflows/quick-carb-calculator.json",
+ "workflows/quiz.json",
+ "workflows/savings-estimator.json",
+ "workflows/skin-typology.json"
+ };
+
+ for (String templatePath : templates) {
+ Resource resource = resourceLoader.getResource("classpath:" + templatePath);
+ try (InputStream is = resource.getInputStream()) {
+ FlowTemplate template = objectMapper.readValue(is, FlowTemplate.class);
+ saveTemplate(template);
+ }
+ }
+ }
+
+ private void saveTemplate(FlowTemplate template) {
+ // 检查是否已存在
+ if (flowRepository.findByUid(template.getId()).isPresent()) {
+ return;
+ }
+
+ // 转换为 FlowEntity
+ FlowEntity flow = FlowEntity.builder().build();
+ flow.setUid(template.getId());
+ flow.setName(template.getName());
+ flow.setIcon(template.getIcon());
+ flow.setGroups(JSON.toJSONString(template.getGroups()));
+ flow.setEvents(JSON.toJSONString(template.getEvents()));
+ flow.setVariables(JSON.toJSONString(template.getVariables()));
+ flow.setEdges(JSON.toJSONString(template.getEdges()));
+ flow.setTheme(JSON.toJSONString(template.getTheme()));
+ flow.setSettings(JSON.toJSONString(template.getSettings()));
+ flow.setSelectedThemeTemplateId(template.getSelectedThemeTemplateId());
+ flow.setPublicId(template.getPublicId());
+ flow.setCustomDomain(template.getCustomDomain());
+ flow.setResultsTablePreferences(JSON.toJSONString(template.getResultsTablePreferences()));
+ flow.setArchived(template.isArchived());
+ flow.setClosed(template.isClosed());
+ flow.setWhatsAppCredentialsId(template.getWhatsAppCredentialsId());
+ flow.setCreatedAt(LocalDateTime.now());
+ flow.setUpdatedAt(LocalDateTime.now());
+ flowRepository.save(flow);
+ }
+}
\ No newline at end of file
diff --git a/modules/core/src/main/java/com/bytedesk/core/workflow/flow/Settings.java b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/Settings.java
new file mode 100644
index 0000000000..e8615e8a86
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/Settings.java
@@ -0,0 +1,99 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-12-10 11:35:07
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-12-11 11:17:21
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 技术/商务联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.workflow.flow;
+
+// import lombok.Data;
+
+// @Data
+// public class Settings {
+// private General general;
+// private Typing typing;
+// private Theme theme;
+// private Security security;
+// private Analytics analytics;
+
+// @Data
+// public static class General {
+// private boolean isPublic;
+// private boolean isClosed;
+// private boolean isArchived;
+// }
+
+// @Data
+// public static class Typing {
+// private boolean enabled;
+// private int speed;
+// private int delay;
+// }
+
+// @Data
+// public static class Theme {
+// private ThemeGeneral general;
+// private Chat chat;
+
+// @Data
+// public static class ThemeGeneral {
+// private String font;
+// private String background;
+// private String containerWidth;
+// }
+
+// @Data
+// public static class Chat {
+// private ChatBubble hostBubbles;
+// private ChatBubble guestBubbles;
+// private ChatInput inputs;
+// private ChatButton buttons;
+// }
+
+// @Data
+// public static class ChatBubble {
+// private String backgroundColor;
+// private String color;
+// }
+
+// @Data
+// public static class ChatInput {
+// private String backgroundColor;
+// private String color;
+// private String placeholderColor;
+// }
+
+// @Data
+// public static class ChatButton {
+// private String backgroundColor;
+// private String color;
+// }
+// }
+
+// @Data
+// public static class Security {
+// private ReCaptcha reCaptcha;
+
+// @Data
+// public static class ReCaptcha {
+// private boolean enabled;
+// private String siteKey;
+// private String secretKey;
+// }
+// }
+
+// @Data
+// public static class Analytics {
+// private boolean enabled;
+// private String provider;
+// private String trackingId;
+// }
+// }
diff --git a/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/Edge.java b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/Edge.java
new file mode 100644
index 0000000000..fbc9370bf0
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/Edge.java
@@ -0,0 +1,17 @@
+package com.bytedesk.core.workflow.flow.model;
+
+import lombok.Data;
+
+@Data
+public class Edge {
+ private String id;
+ private EdgeConnection from;
+ private EdgeConnection to;
+}
+
+@Data
+class EdgeConnection {
+ private String eventId;
+ private String blockId;
+ private String groupId;
+}
\ No newline at end of file
diff --git a/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/Event.java b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/Event.java
new file mode 100644
index 0000000000..2046b1697c
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/Event.java
@@ -0,0 +1,17 @@
+package com.bytedesk.core.workflow.flow.model;
+
+import lombok.Data;
+
+@Data
+public class Event {
+ private String id;
+ private String outgoingEdgeId;
+ private Coordinates graphCoordinates;
+ private String type;
+}
+
+@Data
+class Coordinates {
+ private int x;
+ private int y;
+}
\ No newline at end of file
diff --git a/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/Group.java b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/Group.java
new file mode 100644
index 0000000000..1c35f71d16
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/Group.java
@@ -0,0 +1,14 @@
+package com.bytedesk.core.workflow.flow.model;
+
+import lombok.Data;
+import java.util.List;
+
+import com.bytedesk.core.workflow.flow.model.block.model.Block;
+
+@Data
+public class Group {
+ private String id;
+ private String title;
+ private Coordinates graphCoordinates;
+ private List blocks;
+}
\ No newline at end of file
diff --git a/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/Variable.java b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/Variable.java
new file mode 100644
index 0000000000..55e9d6f73f
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/Variable.java
@@ -0,0 +1,10 @@
+package com.bytedesk.core.workflow.flow.model;
+
+import lombok.Data;
+
+@Data
+public class Variable {
+ private String id;
+ private String name;
+ private boolean isSessionVariable;
+}
\ No newline at end of file
diff --git a/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/exception/BlockNotFoundException.java b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/exception/BlockNotFoundException.java
new file mode 100644
index 0000000000..35f3d6a0c2
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/exception/BlockNotFoundException.java
@@ -0,0 +1,21 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-12-10 11:45:32
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-12-11 10:00:32
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 技术/商务联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.workflow.flow.model.block.exception;
+
+public class BlockNotFoundException extends RuntimeException {
+ public BlockNotFoundException(String id) {
+ super("Block not found with id: " + id);
+ }
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/handler/ABTestBlockHandler.java b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/handler/ABTestBlockHandler.java
new file mode 100644
index 0000000000..7a1db2bab1
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/handler/ABTestBlockHandler.java
@@ -0,0 +1,157 @@
+package com.bytedesk.core.workflow.flow.model.block.handler;
+
+import org.springframework.stereotype.Component;
+
+import com.bytedesk.core.workflow.flow.model.block.model.Block;
+import com.bytedesk.core.workflow.flow.model.block.model.BlockType;
+import com.bytedesk.core.workflow.flow.model.block.model.options.ABTestBlockOptions;
+import com.bytedesk.core.workflow.flow.model.block.model.options.SelectionMode;
+import com.bytedesk.core.workflow.flow.model.block.model.options.TestVariant;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import lombok.extern.slf4j.Slf4j;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@Slf4j
+@Component
+public class ABTestBlockHandler implements BlockHandler {
+ private final ObjectMapper objectMapper;
+ private final Random random;
+ private final Map sequentialCounters;
+ private final Map> persistentSelections;
+
+ public ABTestBlockHandler(ObjectMapper objectMapper) {
+ this.objectMapper = objectMapper;
+ this.random = new Random();
+ this.sequentialCounters = new ConcurrentHashMap<>();
+ this.persistentSelections = new ConcurrentHashMap<>();
+ }
+
+ @Override
+ public String getType() {
+ return BlockType.AB_TEST.name();
+ }
+
+ @Override
+ public Map processBlock(Block block, Map context) {
+ ABTestBlockOptions options = objectMapper.convertValue(block.getOptions(), ABTestBlockOptions.class);
+ Map result = new HashMap<>(context);
+
+ try {
+ TestVariant selectedVariant;
+ String persistedVariantId = (String) context.get(block.getId() + "_variant");
+
+ if (persistedVariantId != null && options.isPersistentSelection()) {
+ selectedVariant = options.getVariants().stream()
+ .filter(v -> v.getId().equals(persistedVariantId))
+ .findFirst()
+ .orElse(null);
+ } else {
+ // selectedVariant = selectVariant(
+ // options.getVariants(),
+ // options.getSelectionMode(),
+ // block.getId()
+ // );
+
+ // if (options.isPersistentSelection()) {
+ // result.put(block.getId() + "_variant", selectedVariant.getId());
+ // }
+ }
+
+ // if (selectedVariant != null) {
+ // if (options.getVariableName() != null) {
+ // result.put(options.getVariableName(), selectedVariant.getId());
+ // }
+ // result.put("selectedVariant", selectedVariant);
+ // result.put("success", true);
+ // } else {
+ // result.put("error", "No variant selected");
+ // result.put("success", false);
+ // }
+
+ } catch (Exception e) {
+ log.error("A/B test selection failed", e);
+ result.put("error", e.getMessage());
+ result.put("success", false);
+ }
+
+ return result;
+ }
+
+ @Override
+ public boolean validateOptions(Block block) {
+ try {
+ ABTestBlockOptions options = objectMapper.convertValue(block.getOptions(), ABTestBlockOptions.class);
+ return options.getVariants() != null &&
+ !options.getVariants().isEmpty() &&
+ options.getVariants().stream()
+ .allMatch(v -> v.getGroupId() != null && v.getName() != null);
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ private TestVariant selectVariant(List variants, SelectionMode mode, Long seed) {
+ if (variants == null || variants.isEmpty()) {
+ return null;
+ }
+
+ switch (mode) {
+ case RANDOM:
+ return selectRandomVariant(variants);
+ case SEQUENTIAL:
+ return selectSequentialVariant(variants, seed);
+ case WEIGHTED:
+ return selectWeightedVariant(variants);
+ default:
+ return selectRandomVariant(variants);
+ }
+ }
+
+ private TestVariant selectRandomVariant(List variants) {
+ return variants.get(random.nextInt(variants.size()));
+ }
+
+ private TestVariant selectWeightedVariant(List variants) {
+ double randomValue = random.nextDouble() * 100;
+ double cumulativePercentage = 0;
+
+ for (TestVariant variant : variants) {
+ cumulativePercentage += variant.getPercentage();
+ if (randomValue <= cumulativePercentage) {
+ return variant;
+ }
+ }
+
+ return variants.get(variants.size() - 1);
+ }
+ private TestVariant selectSequentialVariant(List variants, Long seed) {
+ AtomicInteger counter = sequentialCounters.computeIfAbsent(seed.toString(), k -> new AtomicInteger(0));
+ int index = counter.getAndIncrement() % variants.size();
+ return variants.get(index);
+ }
+
+ private boolean validateVariantPercentages(List variants) {
+ if (variants.stream().anyMatch(v -> v.getPercentage() < 0 || v.getPercentage() > 100)) {
+ return false;
+ }
+
+ double totalPercentage = variants.stream()
+ .mapToDouble(TestVariant::getPercentage)
+ .sum();
+
+ return Math.abs(totalPercentage - 100.0) < 0.001; // 允许0.001的误差
+ }
+
+ private void savePersistentSelection(String sessionId, String blockId, TestVariant variant) {
+ persistentSelections.computeIfAbsent(sessionId, k -> new HashMap<>())
+ .put(blockId, variant);
+ }
+
+ private TestVariant getPersistentSelection(String sessionId, String blockId) {
+ return persistentSelections.getOrDefault(sessionId, Collections.emptyMap())
+ .get(blockId);
+ }
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/handler/AudioBlockHandler.java b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/handler/AudioBlockHandler.java
new file mode 100644
index 0000000000..d506a482e3
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/handler/AudioBlockHandler.java
@@ -0,0 +1,111 @@
+package com.bytedesk.core.workflow.flow.model.block.handler;
+
+import org.springframework.stereotype.Component;
+
+import com.bytedesk.core.workflow.flow.model.block.model.Block;
+import com.bytedesk.core.workflow.flow.model.block.model.BlockType;
+import com.bytedesk.core.workflow.flow.model.block.model.options.AudioBlockOptions;
+import com.bytedesk.core.workflow.flow.model.block.model.options.TextToSpeechConfig;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import lombok.extern.slf4j.Slf4j;
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Component
+public class AudioBlockHandler implements BlockHandler {
+ private final ObjectMapper objectMapper;
+
+ public AudioBlockHandler(ObjectMapper objectMapper) {
+ this.objectMapper = objectMapper;
+ }
+
+ @Override
+ public String getType() {
+ return BlockType.AUDIO.name();
+ }
+
+ @Override
+ public Map processBlock(Block block, Map context) {
+ AudioBlockOptions options = objectMapper.convertValue(block.getOptions(), AudioBlockOptions.class);
+ Map result = new HashMap<>(context);
+
+ try {
+ switch (options.getSourceType().toUpperCase()) {
+ case "URL":
+ handleUrlAudio(options, result);
+ break;
+ case "UPLOAD":
+ handleUploadedAudio(options, result);
+ break;
+ case "TEXT_TO_SPEECH":
+ handleTextToSpeech(options, result, context);
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported audio source type: " + options.getSourceType());
+ }
+
+ result.put("autoplay", options.isAutoplay());
+
+ if (options.getVariableName() != null) {
+ result.put(options.getVariableName(), true);
+ }
+
+ result.put("success", true);
+ result.put("blockType", "audio");
+
+ } catch (Exception e) {
+ log.error("Audio block processing failed", e);
+ result.put("error", e.getMessage());
+ result.put("success", false);
+ }
+
+ return result;
+ }
+
+ @Override
+ public boolean validateOptions(Block block) {
+ try {
+ AudioBlockOptions options = objectMapper.convertValue(block.getOptions(), AudioBlockOptions.class);
+ return options.getSourceType() != null &&
+ (("URL".equalsIgnoreCase(options.getSourceType()) && options.getUrl() != null) ||
+ ("UPLOAD".equalsIgnoreCase(options.getSourceType()) && options.getContent() != null) ||
+ ("TEXT_TO_SPEECH".equalsIgnoreCase(options.getSourceType()) && options.getTtsConfig() != null));
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ private void handleUrlAudio(AudioBlockOptions options, Map result) {
+ result.put("audioUrl", options.getUrl());
+ result.put("mimeType", options.getMimeType());
+ }
+
+ private void handleUploadedAudio(AudioBlockOptions options, Map result) {
+ result.put("audioContent", options.getContent());
+ result.put("mimeType", options.getMimeType());
+ }
+
+ private void handleTextToSpeech(AudioBlockOptions options, Map result, Map context) {
+ TextToSpeechConfig ttsConfig = options.getTtsConfig();
+
+ // 处理文本中的变量
+ String processedText = processTemplate(ttsConfig.getText(), context);
+
+ // TODO: 实现文字转语音的具体逻辑
+ throw new UnsupportedOperationException("Text-to-speech not implemented yet");
+ }
+
+ private String processTemplate(String template, Map context) {
+ if (template == null) return null;
+
+ for (Map.Entry entry : context.entrySet()) {
+ String placeholder = "{{" + entry.getKey() + "}}";
+ if (template.contains(placeholder)) {
+ template = template.replace(placeholder, String.valueOf(entry.getValue()));
+ }
+ }
+ return template;
+ }
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/handler/BlockHandler.java b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/handler/BlockHandler.java
new file mode 100644
index 0000000000..b965d82802
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/handler/BlockHandler.java
@@ -0,0 +1,11 @@
+package com.bytedesk.core.workflow.flow.model.block.handler;
+
+import java.util.Map;
+
+import com.bytedesk.core.workflow.flow.model.block.model.Block;
+
+public interface BlockHandler {
+ String getType();
+ Map processBlock(Block block, Map context);
+ boolean validateOptions(Block block);
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/handler/ChatBlockHandler.java b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/handler/ChatBlockHandler.java
new file mode 100644
index 0000000000..22086ec3f3
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/handler/ChatBlockHandler.java
@@ -0,0 +1,85 @@
+package com.bytedesk.core.workflow.flow.model.block.handler;
+
+import org.springframework.stereotype.Component;
+
+import com.bytedesk.core.workflow.flow.model.block.model.Block;
+import com.bytedesk.core.workflow.flow.model.block.model.BlockType;
+import com.bytedesk.core.workflow.flow.model.block.model.options.ChatBlockOptions;
+import com.bytedesk.core.workflow.flow.model.block.model.options.Message;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import lombok.extern.slf4j.Slf4j;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Component
+public class ChatBlockHandler implements BlockHandler {
+ private final ObjectMapper objectMapper;
+
+ public ChatBlockHandler(ObjectMapper objectMapper) {
+ this.objectMapper = objectMapper;
+ }
+
+ @Override
+ public String getType() {
+ return BlockType.CHAT.name();
+ }
+
+ @Override
+ public Map processBlock(Block block, Map context) {
+ ChatBlockOptions options = objectMapper.convertValue(block.getOptions(), ChatBlockOptions.class);
+ Map result = new HashMap<>(context);
+
+ try {
+ // 处理历史消息中的变量
+ List processedMessages = options.getMessages().stream()
+ .map(msg -> processMessage(msg, context))
+ .collect(Collectors.toList());
+
+ // 执行聊天请求
+ result.put("messages", processedMessages);
+ result.put("success", true);
+ } catch (Exception e) {
+ log.error("Chat processing failed", e);
+ result.put("error", e.getMessage());
+ result.put("success", false);
+ }
+
+ return result;
+ }
+
+ @Override
+ public boolean validateOptions(Block block) {
+ try {
+ ChatBlockOptions options = objectMapper.convertValue(block.getOptions(), ChatBlockOptions.class);
+ return options.getMessages() != null &&
+ !options.getMessages().isEmpty() &&
+ options.getModel() != null;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ private Message processMessage(Message message, Map context) {
+ Message processed = new Message();
+ processed.setRole(message.getRole());
+ processed.setContent(processTemplate(message.getContent(), context));
+ processed.setFunctions(message.getFunctions());
+ return processed;
+ }
+
+ private String processTemplate(String template, Map context) {
+ if (template == null) return null;
+
+ for (Map.Entry entry : context.entrySet()) {
+ String placeholder = "{{" + entry.getKey() + "}}";
+ if (template.contains(placeholder)) {
+ template = template.replace(placeholder, String.valueOf(entry.getValue()));
+ }
+ }
+ return template;
+ }
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/handler/ChoiceBlockHandler.java b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/handler/ChoiceBlockHandler.java
new file mode 100644
index 0000000000..2810d0e00c
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/handler/ChoiceBlockHandler.java
@@ -0,0 +1,82 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-12-10 11:46:33
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-12-11 13:24:53
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 技术/商务联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.workflow.flow.model.block.handler;
+
+import org.springframework.stereotype.Component;
+
+import com.bytedesk.core.workflow.flow.model.block.model.Block;
+import com.bytedesk.core.workflow.flow.model.block.model.BlockType;
+import com.bytedesk.core.workflow.flow.model.block.model.options.ChoiceBlockOptions;
+import com.bytedesk.core.workflow.flow.model.block.model.options.ChoiceItem;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Component
+public class ChoiceBlockHandler implements BlockHandler {
+ private final ObjectMapper objectMapper;
+
+ public ChoiceBlockHandler(ObjectMapper objectMapper) {
+ this.objectMapper = objectMapper;
+ }
+
+ @Override
+ public String getType() {
+ return BlockType.CHOICE_INPUT.name();
+ }
+
+ @Override
+ public Map processBlock(Block block, Map context) {
+ ChoiceBlockOptions options = objectMapper.convertValue(block.getOptions(), ChoiceBlockOptions.class);
+
+ if (options.isDynamicItems() && options.getDynamicVariableId() != null) {
+ Object dynamicItems = context.get(options.getDynamicVariableId());
+ if (dynamicItems instanceof List) {
+ options.setItems(convertDynamicItems((List>) dynamicItems));
+ }
+ }
+
+ Map result = new HashMap<>(context);
+ result.put("choices", options.getItems());
+ result.put("isMultiple", options.isMultipleChoice());
+ return result;
+ }
+
+ @Override
+ public boolean validateOptions(Block block) {
+ try {
+ ChoiceBlockOptions options = objectMapper.convertValue(block.getOptions(), ChoiceBlockOptions.class);
+ return options.getItems() != null && !options.getItems().isEmpty() ||
+ (options.isDynamicItems() && options.getDynamicVariableId() != null);
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ private List convertDynamicItems(List> items) {
+ return items.stream()
+ .map(item -> {
+ ChoiceItem choice = new ChoiceItem();
+ choice.setId(String.valueOf(item.hashCode()));
+ choice.setContent(item.toString());
+ choice.setValue(item.toString());
+ return choice;
+ })
+ .collect(Collectors.toList());
+ }
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/handler/ConditionBlockHandler.java b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/handler/ConditionBlockHandler.java
new file mode 100644
index 0000000000..1d78375b80
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/handler/ConditionBlockHandler.java
@@ -0,0 +1,92 @@
+/*
+ * @Author: jack ning github@bytedesk.com
+ * @Date: 2024-12-10 11:46:43
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-12-10 17:48:10
+ * @FilePath: /backend/src/main/java/io/typebot/features/block/handler/ConditionBlockHandler.java
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
+package com.bytedesk.core.workflow.flow.model.block.handler;
+
+import org.springframework.stereotype.Component;
+
+import com.bytedesk.core.workflow.flow.model.block.model.Block;
+import com.bytedesk.core.workflow.flow.model.block.model.BlockType;
+import com.bytedesk.core.workflow.flow.model.block.model.options.ConditionBlockOptions;
+import com.bytedesk.core.workflow.flow.model.block.model.options.ConditionItem;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Component
+public class ConditionBlockHandler implements BlockHandler {
+ private final ObjectMapper objectMapper;
+
+ public ConditionBlockHandler(ObjectMapper objectMapper) {
+ this.objectMapper = objectMapper;
+ }
+
+ @Override
+ public String getType() {
+ return BlockType.CONDITION.name();
+ }
+
+ @Override
+ public Map processBlock(Block block, Map context) {
+ ConditionBlockOptions options = objectMapper.convertValue(block.getOptions(), ConditionBlockOptions.class);
+
+ boolean conditionMet = evaluateConditions(options.getItems(), context);
+
+ Map result = new HashMap<>(context);
+ result.put("conditionMet", conditionMet);
+ return result;
+ }
+
+ @Override
+ public boolean validateOptions(Block block) {
+ try {
+ ConditionBlockOptions options = objectMapper.convertValue(block.getOptions(), ConditionBlockOptions.class);
+ return options.getItems() != null && !options.getItems().isEmpty();
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ private boolean evaluateConditions(List items, Map context) {
+ return items.stream().anyMatch(item -> evaluateCondition(item, context));
+ }
+
+ private boolean evaluateCondition(ConditionItem item, Map context) {
+ Object value = context.get(item.getVariableId());
+ if (value == null)
+ return false;
+
+ String stringValue = value.toString();
+ String comparison = item.getValue();
+
+ switch (item.getComparisonOperator().toLowerCase()) {
+ case "equals":
+ return stringValue.equals(comparison);
+ case "contains":
+ return stringValue.contains(comparison);
+ case "greater":
+ return compareNumbers(stringValue, comparison) > 0;
+ case "less":
+ return compareNumbers(stringValue, comparison) < 0;
+ default:
+ return false;
+ }
+ }
+
+ private int compareNumbers(String value1, String value2) {
+ try {
+ double num1 = Double.parseDouble(value1);
+ double num2 = Double.parseDouble(value2);
+ return Double.compare(num1, num2);
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/handler/EmailBlockHandler.java b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/handler/EmailBlockHandler.java
new file mode 100644
index 0000000000..a008b98a1b
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/handler/EmailBlockHandler.java
@@ -0,0 +1,150 @@
+package com.bytedesk.core.workflow.flow.model.block.handler;
+
+import jakarta.mail.internet.MimeMessage;
+import org.springframework.stereotype.Component;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.mail.javamail.JavaMailSenderImpl;
+import org.springframework.mail.javamail.MimeMessageHelper;
+
+import com.bytedesk.core.workflow.flow.model.block.model.Block;
+import com.bytedesk.core.workflow.flow.model.block.model.BlockType;
+import com.bytedesk.core.workflow.flow.model.block.model.options.Attachment;
+import com.bytedesk.core.workflow.flow.model.block.model.options.EmailBlockOptions;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import lombok.extern.slf4j.Slf4j;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+@Slf4j
+@Component
+public class EmailBlockHandler implements BlockHandler {
+ private final ObjectMapper objectMapper;
+
+ public EmailBlockHandler(ObjectMapper objectMapper) {
+ this.objectMapper = objectMapper;
+ }
+
+ @Override
+ public String getType() {
+ return BlockType.EMAIL.name();
+ }
+
+ @Override
+ public Map processBlock(Block block, Map context) {
+ EmailBlockOptions options = objectMapper.convertValue(block.getOptions(), EmailBlockOptions.class);
+ Map result = new HashMap<>(context);
+
+ try {
+ JavaMailSender mailSender = createMailSender(options);
+ MimeMessage message = mailSender.createMimeMessage();
+ MimeMessageHelper helper = new MimeMessageHelper(message, true);
+
+ helper.setFrom(options.getFrom());
+ helper.setTo(options.getTo());
+ helper.setSubject(processTemplate(options.getSubject(), context));
+ helper.setText(processTemplate(options.getBody(), context), true);
+
+ if (options.getAttachments() != null) {
+ for (Attachment attachment : options.getAttachments()) {
+ addAttachment(helper, attachment);
+ }
+ }
+
+ mailSender.send(message);
+ result.put("success", true);
+ result.put("message", "Email sent successfully");
+
+ } catch (Exception e) {
+ log.error("Failed to send email", e);
+ result.put("success", false);
+ result.put("error", e.getMessage());
+ }
+
+ return result;
+ }
+
+ @Override
+ public boolean validateOptions(Block block) {
+ try {
+ EmailBlockOptions options = objectMapper.convertValue(block.getOptions(), EmailBlockOptions.class);
+ return options.getFrom() != null &&
+ options.getTo() != null &&
+ options.getSubject() != null &&
+ options.getBody() != null &&
+ options.getProvider() != null;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ private JavaMailSender createMailSender(EmailBlockOptions options) {
+ JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
+ Map credentials = options.getCredentials();
+
+ switch (options.getProvider().getType().toUpperCase()) {
+ case "SMTP":
+ configureSMTP(mailSender, credentials);
+ break;
+ case "SENDGRID":
+ configureSendGrid(mailSender, credentials);
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported email provider: " + options.getProvider().getType());
+ }
+
+ return mailSender;
+ }
+
+ private void configureSMTP(JavaMailSenderImpl mailSender, Map credentials) {
+ mailSender.setHost(credentials.get("host"));
+ mailSender.setPort(Integer.parseInt(credentials.get("port")));
+ mailSender.setUsername(credentials.get("username"));
+ mailSender.setPassword(credentials.get("password"));
+
+ Properties props = mailSender.getJavaMailProperties();
+ props.put("mail.transport.protocol", "smtp");
+ props.put("mail.smtp.auth", "true");
+ props.put("mail.smtp.starttls.enable", "true");
+ }
+
+ private void configureSendGrid(JavaMailSenderImpl mailSender, Map credentials) {
+ mailSender.setHost("smtp.sendgrid.net");
+ mailSender.setPort(587);
+ mailSender.setUsername("apikey");
+ mailSender.setPassword(credentials.get("apiKey"));
+
+ Properties props = mailSender.getJavaMailProperties();
+ props.put("mail.transport.protocol", "smtp");
+ props.put("mail.smtp.auth", "true");
+ props.put("mail.smtp.starttls.enable", "true");
+ }
+
+ private void addAttachment(MimeMessageHelper helper, Attachment attachment) throws Exception {
+ if (attachment.getContent() != null) {
+ byte[] content = Base64.getDecoder().decode(attachment.getContent());
+ helper.addAttachment(
+ attachment.getFilename(),
+ new ByteArrayResource(content),
+ attachment.getType()
+ );
+ } else if (attachment.getUrl() != null) {
+ log.warn("URL attachments not implemented yet");
+ }
+ }
+
+ private String processTemplate(String template, Map context) {
+ if (template == null) return null;
+
+ for (Map.Entry entry : context.entrySet()) {
+ String placeholder = "{{" + entry.getKey() + "}}";
+ if (template.contains(placeholder)) {
+ template = template.replace(placeholder, String.valueOf(entry.getValue()));
+ }
+ }
+ return template;
+ }
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/handler/FileBlockHandler.java b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/handler/FileBlockHandler.java
new file mode 100644
index 0000000000..7be8aeed27
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/workflow/flow/model/block/handler/FileBlockHandler.java
@@ -0,0 +1,185 @@
+package com.bytedesk.core.workflow.flow.model.block.handler;
+
+import org.springframework.stereotype.Component;
+
+import com.bytedesk.core.workflow.flow.model.block.model.Block;
+import com.bytedesk.core.workflow.flow.model.block.model.BlockType;
+import com.bytedesk.core.workflow.flow.model.block.model.options.FileBlockOptions;
+import com.bytedesk.core.workflow.flow.model.block.model.options.StorageConfig;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.multipart.MultipartFile;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.Collections;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Component
+public class FileBlockHandler implements BlockHandler {
+ private final ObjectMapper objectMapper;
+ private static final long DEFAULT_MAX_SIZE = 10 * 1024 * 1024; // 10MB
+
+ public FileBlockHandler(ObjectMapper objectMapper) {
+ this.objectMapper = objectMapper;
+ }
+
+ @Override
+ public String getType() {
+ return BlockType.FILE.name();
+ }
+
+ @Override
+ public Map processBlock(Block block, Map context) {
+ FileBlockOptions options = objectMapper.convertValue(block.getOptions(), FileBlockOptions.class);
+ Map result = new HashMap<>(context);
+
+ try {
+ // 验证存储配置
+ validateStorageConfig(options.getStorageConfig());
+
+ // 构建上传配置
+ Map uploadConfig = buildUploadConfig(options, context);
+ result.putAll(uploadConfig);
+
+ // 处理已上传的文件(如果有)
+ if (context.containsKey("files")) {
+ handleUploadedFiles(context.get("files"), options, result);
+ }
+
+ result.put("success", true);
+ result.put("blockType", "file");
+
+ } catch (Exception e) {
+ log.error("File block processing failed", e);
+ result.put("error", e.getMessage());
+ result.put("success", false);
+ }
+
+ return result;
+ }
+
+ @Override
+ public boolean validateOptions(Block block) {
+ try {
+ FileBlockOptions options = objectMapper.convertValue(block.getOptions(), FileBlockOptions.class);
+ return options.getStorageConfig() != null &&
+ options.getStorageConfig().getProvider() != null &&
+ validateStorageProvider(options.getStorageConfig().getProvider());
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ private Map buildUploadConfig(FileBlockOptions options, Map context) {
+ Map config = new HashMap<>();
+
+ // 基本配置
+ config.put("buttonLabel", processTemplate(options.getButtonLabel(), context));
+ config.put("mimeTypes", options.getMimeTypes());
+ config.put("maxSize", options.getMaxSize() != null ? options.getMaxSize() : DEFAULT_MAX_SIZE);
+ config.put("isMultiple", options.isMultiple());
+
+ // 存储配置
+ StorageConfig storage = options.getStorageConfig();
+ Map storageConfig = new HashMap<>();
+ storageConfig.put("provider", storage.getProvider());
+ storageConfig.put("path", storage.getPath());
+ storageConfig.put("bucket", storage.getBucket());
+
+ config.put("storage", storageConfig);
+ return config;
+ }
+
+ private void handleUploadedFiles(Object filesObj, FileBlockOptions options, Map result) {
+ List files = convertToFileList(filesObj);
+ List