This commit is contained in:
jack ning
2025-11-15 09:07:09 +08:00
parent 96587849d8
commit 355f294725
24 changed files with 1034 additions and 16 deletions

View File

@@ -44,8 +44,8 @@ import lombok.experimental.SuperBuilder;
// @EntityListeners({TagEntityListener.class})
@Table(name = "bytedesk_core_tag")
public class TagEntity extends BaseEntity {
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 1L;
/**
* Name of the tag

View File

@@ -31,7 +31,6 @@ public class TagRequest extends BaseRequest {
private static final long serialVersionUID = 1L;
private String name;
private String description;

View File

@@ -33,7 +33,6 @@ public class TagResponse extends BaseResponse {
private static final long serialVersionUID = 1L;
private String name;
private String description;

View File

@@ -0,0 +1,73 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2024-05-11 18:14:28
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-06-04 15:35: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.
* 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.task;
import com.bytedesk.core.base.BaseEntity;
import com.bytedesk.core.constant.I18Consts;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
// import jakarta.persistence.EntityListeners;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import lombok.experimental.SuperBuilder;
/**
* 待办任务:
* 一个工单内可以关联多个任务,
* 每个人都可以创建待办任务
* 可以单纯创建待办,也可以关联工单创建待办任务
*
* Task entity for content categorization and organization
* Provides taskging functionality for various system entities
*
* Database Table: bytedesk_core_task
* Purpose: Stores task definitions, colors, and organization settings
*/
@Entity
@Data
@SuperBuilder
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
// @EntityListeners({TaskEntityListener.class})
@Table(name = "bytedesk_core_task")
public class TaskEntity extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* Name of the task
*/
private String name;
/**
* Description of the task
*/
@Builder.Default
private String description = I18Consts.I18N_DESCRIPTION;
/**
* Type of task (CUSTOMER, TICKET, ARTICLE, etc.)
*/
@Builder.Default
@Column(name = "task_type")
private String type = TaskTypeEnum.CUSTOMER.name();
}

View File

@@ -0,0 +1,51 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2025-02-25 09:52:34
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-03-20 17:00: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.
* Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
* contact: 270580156@qq.com
*
* Copyright (c) 2025 by bytedesk.com, All Rights Reserved.
*/
package com.bytedesk.core.task;
import org.springframework.stereotype.Component;
import org.springframework.util.SerializationUtils;
import com.bytedesk.core.config.BytedeskEventPublisher;
import com.bytedesk.core.task.event.TaskCreateEvent;
import com.bytedesk.core.task.event.TaskUpdateEvent;
import com.bytedesk.core.utils.ApplicationContextHolder;
import jakarta.persistence.PostPersist;
import jakarta.persistence.PostUpdate;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
public class TaskEntityListener {
@PostPersist
public void onPostPersist(TaskEntity task) {
log.info("onPostPersist: {}", task);
TaskEntity cloneTask = SerializationUtils.clone(task);
//
BytedeskEventPublisher bytedeskEventPublisher = ApplicationContextHolder.getBean(BytedeskEventPublisher.class);
bytedeskEventPublisher.publishEvent(new TaskCreateEvent(cloneTask));
}
@PostUpdate
public void onPostUpdate(TaskEntity task) {
log.info("onPostUpdate: {}", task);
TaskEntity cloneTask = SerializationUtils.clone(task);
//
BytedeskEventPublisher bytedeskEventPublisher = ApplicationContextHolder.getBean(BytedeskEventPublisher.class);
bytedeskEventPublisher.publishEvent(new TaskUpdateEvent(cloneTask));
}
}

View File

@@ -0,0 +1,44 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2025-02-25 09:44:18
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-06-04 15:50:06
* @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.
* Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
* contact: 270580156@qq.com
*
* Copyright (c) 2025 by bytedesk.com, All Rights Reserved.
*/
package com.bytedesk.core.task;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import com.bytedesk.core.rbac.organization.OrganizationEntity;
import com.bytedesk.core.rbac.organization.event.OrganizationCreateEvent;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
@AllArgsConstructor
public class TaskEventListener {
private final TaskRestService taskRestService;
@Order(3)
@EventListener
public void onOrganizationCreateEvent(OrganizationCreateEvent event) {
OrganizationEntity organization = (OrganizationEntity) event.getSource();
String orgUid = organization.getUid();
log.info("thread - organization created: {}", organization.getName());
taskRestService.initTasks(orgUid);
}
}

View File

@@ -0,0 +1,47 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2024-08-01 06:18:10
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-07-04 18:00:51
* @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.
* 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.task;
import java.time.ZonedDateTime;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import lombok.Data;
/**
* https://github.com/alibaba/easyexcel
*/
@Data
public class TaskExcel {
@ExcelProperty(index = 0, value = "标签名称")
@ColumnWidth(20)
private String name;
@ExcelProperty(index = 1, value = "类型")
@ColumnWidth(20)
private String type;
@ExcelProperty(index = 2, value = "颜色")
@ColumnWidth(20)
private String color;
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
@ExcelProperty(value = "创建时间", converter = com.bytedesk.core.converter.ZonedDateTimeConverter.class)
@ColumnWidth(25)
private ZonedDateTime createdAt;
}

View File

@@ -0,0 +1,122 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2025-03-11 08:54:35
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-06-04 17:12: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.
* Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
* contact: 270580156@qq.com
*
* Copyright (c) 2025 by bytedesk.com, All Rights Reserved.
*/
package com.bytedesk.core.task;
// import com.bytedesk.core.constant.I18Consts;
public class TaskInitData {
/**
* Technical Support Tasks
* 技术支持标签
*/
public static final String[] TECHNICAL_SUPPORT = {
// I18Consts.I18N_PREFIX + "thread.task.technical_support", // parent
"技术支持", // parent
};
/**
* Service Request Tasks
* 服务请求标签
*/
public static final String[] SERVICE_REQUEST = {
// I18Consts.I18N_PREFIX + "thread.task.service_request", // parent
"服务请求", // parent
};
/**
* Consultation Tasks
* 咨询标签
*/
public static final String[] CONSULTATION = {
// I18Consts.I18N_PREFIX + "thread.task.consultation", // parent
"咨询", // parent
};
/**
* Complaint & Suggestion Tasks
* 投诉与建议标签
*/
public static final String[] COMPLAINT_SUGGESTION = {
// I18Consts.I18N_PREFIX + "thread.task.complaint_suggestion", // parent
"投诉建议", // parent
};
/**
* Operation & Maintenance Tasks
* 运维标签
*/
public static final String[] OPERATION_MAINTENANCE = {
// I18Consts.I18N_PREFIX + "thread.task.operation_maintenance", // parent
"运维", // parent
// 其他
// I18Consts.I18N_PREFIX + "thread.task.other",
"其他",
};
/**
* Helper method to determine if a task is a parent task
*
* @param task The task key to check
* @return true if it's a parent task
*/
public static boolean isParentTask(String task) {
return !task.contains(".");
}
/**
* Helper method to get parent task key for a child task
*
* @param childTask The child task key
* @return The parent task key
*/
public static String getParentTask(String childTask) {
if (isParentTask(childTask)) {
return null;
}
// 由于已将常量转为中文,此方法可能需要重新实现
// 这里仅保留基本结构,具体实现需要根据新的标签体系来调整
return null;
}
/**
* Get all tasks as a single array
*
* @return Array containing all tasks
*/
public static String[] getAllTasks() {
int totalLength = TECHNICAL_SUPPORT.length + SERVICE_REQUEST.length +
CONSULTATION.length + COMPLAINT_SUGGESTION.length +
OPERATION_MAINTENANCE.length;
String[] allTasks = new String[totalLength];
int index = 0;
System.arraycopy(TECHNICAL_SUPPORT, 0, allTasks, index, TECHNICAL_SUPPORT.length);
index += TECHNICAL_SUPPORT.length;
System.arraycopy(SERVICE_REQUEST, 0, allTasks, index, SERVICE_REQUEST.length);
index += SERVICE_REQUEST.length;
System.arraycopy(CONSULTATION, 0, allTasks, index, CONSULTATION.length);
index += CONSULTATION.length;
System.arraycopy(COMPLAINT_SUGGESTION, 0, allTasks, index, COMPLAINT_SUGGESTION.length);
index += COMPLAINT_SUGGESTION.length;
System.arraycopy(OPERATION_MAINTENANCE, 0, allTasks, index, OPERATION_MAINTENANCE.length);
return allTasks;
}
}

View File

@@ -0,0 +1,46 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2024-11-06 21:43:58
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-08-26 10:52: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.
* 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.task;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.stereotype.Component;
import com.bytedesk.core.constant.BytedeskConsts;
import lombok.AllArgsConstructor;
@Component
@AllArgsConstructor
public class TaskInitializer implements SmartInitializingSingleton {
private final TaskRestService taskRestService;
@Override
public void afterSingletonsInstantiated() {
initPermissions();
// create default
String orgUid = BytedeskConsts.DEFAULT_ORGANIZATION_UID;
taskRestService.initTasks(orgUid);
}
private void initPermissions() {
// for (PermissionEnum permission : PermissionEnum.values()) {
// String permissionValue = TaskPermissions.ARTICLE_PREFIX + permission.name();
// authorityService.createForPlatform(permissionValue);
// }
}
}

View File

@@ -0,0 +1,20 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2024-11-05 16:58:18
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-05-06 11:55: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.
* 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.task;
import com.bytedesk.core.base.BasePermissions;
public class TaskPermissions extends BasePermissions {
}

View File

@@ -0,0 +1,30 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2024-05-11 18:25:55
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-06-20 12:52:47
* @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.
* 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.task;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface TaskRepository extends JpaRepository<TaskEntity, Long>, JpaSpecificationExecutor<TaskEntity> {
Optional<TaskEntity> findByUid(String uid);
Boolean existsByUid(String uid);
Optional<TaskEntity> findByNameAndOrgUidAndTypeAndDeletedFalse(String name, String orgUid, String type);
// Boolean existsByPlatform(String platform);
}

View File

@@ -0,0 +1,42 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2024-05-11 18:26:04
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-06-20 14:24:05
* @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.
* 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.task;
import com.bytedesk.core.base.BaseRequest;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import lombok.experimental.SuperBuilder;
@Data
@SuperBuilder
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@AllArgsConstructor
@NoArgsConstructor
public class TaskRequest extends BaseRequest {
private static final long serialVersionUID = 1L;
private String name;
private String description;
// @Builder.Default
// private String type = TaskTypeEnum.CUSTOMER.name();
}

View File

@@ -0,0 +1,42 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2024-05-11 18:26:12
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-06-04 15:36:28
* @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.
* 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.task;
import com.bytedesk.core.base.BaseResponse;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import lombok.experimental.SuperBuilder;
@Data
@SuperBuilder
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
public class TaskResponse extends BaseResponse {
private static final long serialVersionUID = 1L;
private String name;
private String description;
private String type;
}

View File

@@ -0,0 +1,123 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2024-05-11 18:25:36
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-08-20 17:05: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.
* 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.task;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.context.annotation.Description;
import com.bytedesk.core.annotation.ActionAnnotation;
import com.bytedesk.core.base.BaseRestController;
import com.bytedesk.core.utils.JsonResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
@RestController
@RequestMapping("/api/v1/task")
@AllArgsConstructor
@Tag(name = "Task Management", description = "Task management APIs for organizing and categorizing content with tasks")
@Description("Task Management Controller - Content taskging and categorization APIs")
public class TaskRestController extends BaseRestController<TaskRequest, TaskRestService> {
private final TaskRestService taskRestService;
@ActionAnnotation(title = "Task", action = "组织查询", description = "query task by org")
@Operation(summary = "Query Tasks by Organization", description = "Retrieve tasks for the current organization")
@Override
public ResponseEntity<?> queryByOrg(TaskRequest request) {
Page<TaskResponse> tasks = taskRestService.queryByOrg(request);
return ResponseEntity.ok(JsonResult.success(tasks));
}
@ActionAnnotation(title = "Task", action = "用户查询", description = "query task by user")
@Operation(summary = "Query Tasks by User", description = "Retrieve tasks for the current user")
@Override
public ResponseEntity<?> queryByUser(TaskRequest request) {
Page<TaskResponse> tasks = taskRestService.queryByUser(request);
return ResponseEntity.ok(JsonResult.success(tasks));
}
@ActionAnnotation(title = "Task", action = "查询详情", description = "query task by uid")
@Operation(summary = "Query Task by UID", description = "Retrieve a specific task by its unique identifier")
@Override
public ResponseEntity<?> queryByUid(TaskRequest request) {
TaskResponse task = taskRestService.queryByUid(request);
return ResponseEntity.ok(JsonResult.success(task));
}
@ActionAnnotation(title = "Task", action = "新建", description = "create task")
@Operation(summary = "Create Task", description = "Create a new task")
@Override
// @PreAuthorize("hasAuthority('TAG_CREATE')")
public ResponseEntity<?> create(TaskRequest request) {
TaskResponse task = taskRestService.create(request);
return ResponseEntity.ok(JsonResult.success(task));
}
@ActionAnnotation(title = "Task", action = "更新", description = "update task")
@Operation(summary = "Update Task", description = "Update an existing task")
@Override
// @PreAuthorize("hasAuthority('TAG_UPDATE')")
public ResponseEntity<?> update(TaskRequest request) {
TaskResponse task = taskRestService.update(request);
return ResponseEntity.ok(JsonResult.success(task));
}
@ActionAnnotation(title = "Task", action = "删除", description = "delete task")
@Operation(summary = "Delete Task", description = "Delete a task")
@Override
// @PreAuthorize("hasAuthority('TAG_DELETE')")
public ResponseEntity<?> delete(TaskRequest request) {
taskRestService.delete(request);
return ResponseEntity.ok(JsonResult.success());
}
@ActionAnnotation(title = "Task", action = "导出", description = "export task")
@Operation(summary = "Export Tasks", description = "Export tasks to Excel format")
@Override
// @PreAuthorize("hasAuthority('TAG_EXPORT')")
@GetMapping("/export")
public Object export(TaskRequest request, HttpServletResponse response) {
return exportTemplate(
request,
response,
taskRestService,
TaskExcel.class,
"Task",
"task"
);
}
}

View File

@@ -0,0 +1,194 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2024-05-11 18:25:45
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-08-22 07:04:17
* @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.
* 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.task;
import java.util.Optional;
import org.modelmapper.ModelMapper;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import com.bytedesk.core.base.BaseRestServiceWithExport;
import com.bytedesk.core.rbac.auth.AuthService;
import com.bytedesk.core.rbac.user.UserEntity;
import com.bytedesk.core.uid.UidUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@AllArgsConstructor
public class TaskRestService extends BaseRestServiceWithExport<TaskEntity, TaskRequest, TaskResponse, TaskExcel> {
private final TaskRepository taskRepository;
private final ModelMapper modelMapper;
private final UidUtils uidUtils;
private final AuthService authService;
@Override
protected Specification<TaskEntity> createSpecification(TaskRequest request) {
return TaskSpecification.search(request, authService);
}
@Override
protected Page<TaskEntity> executePageQuery(Specification<TaskEntity> spec, Pageable pageable) {
return taskRepository.findAll(spec, pageable);
}
@Cacheable(value = "task", key = "#uid", unless="#result==null")
@Override
public Optional<TaskEntity> findByUid(String uid) {
return taskRepository.findByUid(uid);
}
@Cacheable(value = "task", key = "#name + '_' + #orgUid + '_' + #type", unless="#result==null")
public Optional<TaskEntity> findByNameAndOrgUidAndType(String name, String orgUid, String type) {
return taskRepository.findByNameAndOrgUidAndTypeAndDeletedFalse(name, orgUid, type);
}
public Boolean existsByUid(String uid) {
return taskRepository.existsByUid(uid);
}
@Transactional
@Override
public TaskResponse create(TaskRequest request) {
// 判断是否已经存在
if (StringUtils.hasText(request.getUid()) && existsByUid(request.getUid())) {
return convertToResponse(findByUid(request.getUid()).get());
}
// 检查name+orgUid+type是否已经存在
if (StringUtils.hasText(request.getName()) && StringUtils.hasText(request.getOrgUid()) && StringUtils.hasText(request.getType())) {
Optional<TaskEntity> task = findByNameAndOrgUidAndType(request.getName(), request.getOrgUid(), request.getType());
if (task.isPresent()) {
return convertToResponse(task.get());
}
}
//
UserEntity user = authService.getUser();
if (user != null) {
request.setUserUid(user.getUid());
}
//
TaskEntity entity = modelMapper.map(request, TaskEntity.class);
if (!StringUtils.hasText(request.getUid())) {
entity.setUid(uidUtils.getUid());
}
//
TaskEntity savedEntity = save(entity);
if (savedEntity == null) {
throw new RuntimeException("Create task failed");
}
return convertToResponse(savedEntity);
}
@Transactional
@Override
public TaskResponse update(TaskRequest request) {
Optional<TaskEntity> optional = taskRepository.findByUid(request.getUid());
if (optional.isPresent()) {
TaskEntity entity = optional.get();
modelMapper.map(request, entity);
//
TaskEntity savedEntity = save(entity);
if (savedEntity == null) {
throw new RuntimeException("Update task failed");
}
return convertToResponse(savedEntity);
}
else {
throw new RuntimeException("Task not found");
}
}
@Override
protected TaskEntity doSave(TaskEntity entity) {
return taskRepository.save(entity);
}
@Override
public TaskEntity handleOptimisticLockingFailureException(ObjectOptimisticLockingFailureException e, TaskEntity entity) {
try {
Optional<TaskEntity> latest = taskRepository.findByUid(entity.getUid());
if (latest.isPresent()) {
TaskEntity latestEntity = latest.get();
// 合并需要保留的数据
latestEntity.setName(entity.getName());
// latestEntity.setOrder(entity.getOrder());
// latestEntity.setDeleted(entity.isDeleted());
return taskRepository.save(latestEntity);
}
} catch (Exception ex) {
log.error("无法处理乐观锁冲突: {}", ex.getMessage(), ex);
throw new RuntimeException("无法处理乐观锁冲突: " + ex.getMessage(), ex);
}
return null;
}
@Transactional
@Override
public void deleteByUid(String uid) {
Optional<TaskEntity> optional = taskRepository.findByUid(uid);
if (optional.isPresent()) {
optional.get().setDeleted(true);
save(optional.get());
// taskRepository.delete(optional.get());
}
else {
throw new RuntimeException("Task not found");
}
}
@Override
public void delete(TaskRequest request) {
deleteByUid(request.getUid());
}
@Override
public TaskResponse convertToResponse(TaskEntity entity) {
return modelMapper.map(entity, TaskResponse.class);
}
@Override
public TaskExcel convertToExcel(TaskEntity entity) {
return modelMapper.map(entity, TaskExcel.class);
}
public void initTasks(String orgUid) {
// log.info("initThreadTask");
// for (String task : TaskInitData.getAllTasks()) {
// TaskRequest taskRequest = TaskRequest.builder()
// .uid(Utils.formatUid(orgUid, task))
// .name(task)
// .order(0)
// .type(TaskTypeEnum.THREAD.name())
// .level(LevelEnum.ORGANIZATION.name())
// .platform(BytedeskConsts.PLATFORM_BYTEDESK)
// .orgUid(orgUid)
// .build();
// create(taskRequest);
// }
}
}

View File

@@ -0,0 +1,57 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2024-07-09 22:19:21
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-08-18 15:44: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.
* 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.task;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.StringUtils;
import com.bytedesk.core.base.BaseSpecification;
import com.bytedesk.core.rbac.auth.AuthService;
import jakarta.persistence.criteria.Predicate;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class TaskSpecification extends BaseSpecification<TaskEntity, TaskRequest> {
public static Specification<TaskEntity> search(TaskRequest request, AuthService authService) {
// log.info("request: {} orgUid: {} pageNumber: {} pageSize: {}",
// request, request.getOrgUid(), request.getPageNumber(), request.getPageSize());
return (root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
predicates.addAll(getBasicPredicates(root, criteriaBuilder, request, authService));
// name
if (StringUtils.hasText(request.getName())) {
predicates.add(criteriaBuilder.like(root.get("name"), "%" + request.getName() + "%"));
}
// description
if (StringUtils.hasText(request.getDescription())) {
predicates.add(criteriaBuilder.like(root.get("description"), "%" + request.getDescription() + "%"));
}
// type
if (StringUtils.hasText(request.getType())) {
predicates.add(criteriaBuilder.equal(root.get("type"), request.getType()));
}
//
if (StringUtils.hasText(request.getUserUid())) {
predicates.add(criteriaBuilder.equal(root.get("userUid"), request.getUserUid()));
}
//
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
}
}

View File

@@ -0,0 +1,20 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2024-07-23 17:02:46
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-03-11 08:57: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.
* 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.task;
public enum TaskTypeEnum {
THREAD,
CUSTOMER,
TICKET
}

View File

@@ -0,0 +1,36 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2025-02-25 09:59:29
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-02-25 10:00: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.
* Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
* contact: 270580156@qq.com
*
* Copyright (c) 2025 by bytedesk.com, All Rights Reserved.
*/
package com.bytedesk.core.task.event;
import org.springframework.context.ApplicationEvent;
import com.bytedesk.core.task.TaskEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class TaskCreateEvent extends ApplicationEvent {
private static final long serialVersionUID = 1L;
private TaskEntity task;
public TaskCreateEvent(TaskEntity task) {
super(task);
this.task = task;
}
}

View File

@@ -0,0 +1,35 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2025-02-25 12:31:16
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-02-25 12:31: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.
* Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
* contact: 270580156@qq.com
*
* Copyright (c) 2025 by bytedesk.com, All Rights Reserved.
*/
package com.bytedesk.core.task.event;
import org.springframework.context.ApplicationEvent;
import com.bytedesk.core.task.TaskEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class TaskDeleteEvent extends ApplicationEvent {
private static final long serialVersionUID = 1L;
private TaskEntity task;
public TaskDeleteEvent(TaskEntity task) {
super(task);
this.task = task;
}
}

View File

@@ -0,0 +1,36 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2025-02-25 09:59:29
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-02-25 10:01: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.
* Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
* contact: 270580156@qq.com
*
* Copyright (c) 2025 by bytedesk.com, All Rights Reserved.
*/
package com.bytedesk.core.task.event;
import org.springframework.context.ApplicationEvent;
import com.bytedesk.core.task.TaskEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class TaskUpdateEvent extends ApplicationEvent {
private static final long serialVersionUID = 1L;
private TaskEntity task;
public TaskUpdateEvent(TaskEntity task) {
super(task);
this.task = task;
}
}

View File

@@ -0,0 +1,5 @@
@NonNullApi
package com.bytedesk.core.task;
import org.springframework.lang.NonNullApi;

View File

@@ -17,7 +17,6 @@ import java.time.ZonedDateTime;
import com.bytedesk.core.base.BaseEntity;
import com.bytedesk.core.constant.I18Consts;
import com.bytedesk.ticket.ticket_settings.sub.TicketBasicSettingsEntity;
import com.bytedesk.ticket.ticket_settings.sub.TicketCategorySettingsEntity;
import jakarta.persistence.CascadeType;
@@ -82,8 +81,8 @@ public class TicketSettingsEntity extends BaseEntity {
//
// ====== 发布版本 ======
@ManyToOne(fetch = FetchType.LAZY, optional = true, cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE })
private TicketBasicSettingsEntity basicSettings;
// @ManyToOne(fetch = FetchType.LAZY, optional = true, cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE })
// private TicketBasicSettingsEntity basicSettings;
@ManyToOne(fetch = FetchType.LAZY, optional = true, cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE })
private TicketCategorySettingsEntity categorySettings;
@@ -104,8 +103,8 @@ public class TicketSettingsEntity extends BaseEntity {
// private TicketCustomFieldSettingsEntity customFieldSettings;
// ====== 草稿版本 ======
@ManyToOne(fetch = FetchType.LAZY, optional = true, cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE })
private TicketBasicSettingsEntity draftBasicSettings;
// @ManyToOne(fetch = FetchType.LAZY, optional = true, cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE })
// private TicketBasicSettingsEntity draftBasicSettings;
@ManyToOne(fetch = FetchType.LAZY, optional = true, cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE })
private TicketCategorySettingsEntity draftCategorySettings;

View File

@@ -61,7 +61,6 @@ public class TicketSettingsRequest extends BaseRequest {
private TicketCustomFieldSettingsRequest customFieldSettings;
private TicketCategorySettingsRequest categorySettings;
private TicketCategorySettingsRequest draftCategorySettings;
}

View File

@@ -34,7 +34,6 @@ import com.bytedesk.core.rbac.user.UserEntity;
import com.bytedesk.core.uid.UidUtils;
import com.bytedesk.ticket.ticket_settings.binding.TicketSettingsBindingEntity;
import com.bytedesk.ticket.ticket_settings.binding.TicketSettingsBindingRepository;
import com.bytedesk.ticket.ticket_settings.sub.TicketBasicSettingsEntity;
import com.bytedesk.ticket.ticket_settings.sub.TicketCategorySettingsEntity;
import com.bytedesk.ticket.ticket_settings.sub.dto.TicketCategorySettingsRequest;
import com.bytedesk.ticket.ticket_settings.sub.dto.TicketCategorySettingsResponse;
@@ -114,12 +113,12 @@ public class TicketSettingsRestService extends
// 初始化并绑定发布 + 草稿子配置,参考 WorkgroupSettings 的 create 逻辑
// Basic
TicketBasicSettingsEntity basic = TicketBasicSettingsEntity.fromRequest(request.getBasicSettings(), modelMapper);
basic.setUid(uidUtils.getUid());
entity.setBasicSettings(basic);
TicketBasicSettingsEntity draftBasic = TicketBasicSettingsEntity.fromRequest(request.getBasicSettings(), modelMapper);
draftBasic.setUid(uidUtils.getUid());
entity.setDraftBasicSettings(draftBasic);
// TicketBasicSettingsEntity basic = TicketBasicSettingsEntity.fromRequest(request.getBasicSettings(), modelMapper);
// basic.setUid(uidUtils.getUid());
// entity.setBasicSettings(basic);
// TicketBasicSettingsEntity draftBasic = TicketBasicSettingsEntity.fromRequest(request.getBasicSettings(), modelMapper);
// draftBasic.setUid(uidUtils.getUid());
// entity.setDraftBasicSettings(draftBasic);
TicketCategorySettingsEntity category = TicketCategorySettingsEntity
.fromRequest(request.getCategorySettings(), uidUtils::getUid);