diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java index 5da0fc69..f8a1db4c 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java @@ -229,7 +229,13 @@ public enum ResponseCode { DEPLOY_PERMISSION_DENIED(3009, "deploy.permission.denied"), DEPLOY_ENVIRONMENT_LOCKED(3010, "deploy.environment.locked"), DEPLOY_APPROVAL_REQUIRED(3011, "deploy.approval.required"), - DEPLOY_RECORD_NOT_FOUND(3012, "deploy.record.not.found"); + DEPLOY_RECORD_NOT_FOUND(3012, "deploy.record.not.found"), + + // 通知模板相关错误码 (3100-3119) + NOTIFICATION_TEMPLATE_NOT_FOUND(3100, "notification.template.not.found"), + NOTIFICATION_TEMPLATE_CODE_EXISTS(3101, "notification.template.code.exists"), + NOTIFICATION_TEMPLATE_DISABLED(3102, "notification.template.disabled"), + NOTIFICATION_TEMPLATE_RENDER_ERROR(3103, "notification.template.render.error"); private final int code; private final String messageKey; // 国际化消息key diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/api/NotificationApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/api/NotificationApiController.java new file mode 100644 index 00000000..be71f1c3 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/api/NotificationApiController.java @@ -0,0 +1,36 @@ +package com.qqchen.deploy.backend.notification.api; + +import com.qqchen.deploy.backend.framework.api.Response; +import com.qqchen.deploy.backend.notification.dto.SendNotificationRequest; +import com.qqchen.deploy.backend.notification.service.INotificationService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** + * 通知发送API控制器 + * + * @author qqchen + * @since 2025-11-12 + */ +@Slf4j +@RestController +@RequestMapping("/api/v1/notification") +@Tag(name = "通知发送", description = "通知发送相关接口") +@Validated +public class NotificationApiController { + + @Resource + private INotificationService notificationService; + + @Operation(summary = "发送通知消息") + @PostMapping("/send") + public Response send(@Valid @RequestBody SendNotificationRequest request) { + notificationService.send(request); + return Response.success(); + } +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/api/NotificationChannelApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/api/NotificationChannelApiController.java index b71127d9..14804bf6 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/notification/api/NotificationChannelApiController.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/api/NotificationChannelApiController.java @@ -4,11 +4,9 @@ import com.qqchen.deploy.backend.framework.api.Response; import com.qqchen.deploy.backend.framework.controller.BaseController; import com.qqchen.deploy.backend.notification.dto.NotificationChannelDTO; import com.qqchen.deploy.backend.notification.dto.NotificationChannelQuery; -import com.qqchen.deploy.backend.notification.dto.BaseSendNotificationRequest; import com.qqchen.deploy.backend.notification.entity.NotificationChannel; import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum; import com.qqchen.deploy.backend.notification.service.INotificationChannelService; -import com.qqchen.deploy.backend.notification.service.INotificationSendService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -42,9 +40,6 @@ public class NotificationChannelApiController extends BaseController create(@Validated @RequestBody NotificationChannelDTO dto) { return super.create(dto); @@ -128,14 +123,6 @@ public class NotificationChannelApiController extends BaseController send( - @Parameter(description = "通知请求", required = true) @RequestBody BaseSendNotificationRequest request - ) { - notificationSendService.send(request); - return Response.success(); - } @Override protected void exportData(HttpServletResponse response, List data) { diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/api/NotificationTemplateApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/api/NotificationTemplateApiController.java new file mode 100644 index 00000000..d68d1ace --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/api/NotificationTemplateApiController.java @@ -0,0 +1,73 @@ +package com.qqchen.deploy.backend.notification.api; + +import com.qqchen.deploy.backend.framework.api.Response; +import com.qqchen.deploy.backend.framework.controller.BaseController; +import com.qqchen.deploy.backend.notification.dto.NotificationTemplateDTO; +import com.qqchen.deploy.backend.notification.dto.template.NotificationTemplateQuery; +import com.qqchen.deploy.backend.notification.dto.template.TemplateRenderRequest; +import com.qqchen.deploy.backend.notification.entity.NotificationTemplate; +import com.qqchen.deploy.backend.notification.service.INotificationTemplateService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Arrays; +import java.util.List; + +/** + * 通知模板API控制器 + * + * @author qqchen + * @since 2025-11-12 + */ +@Slf4j +@RestController +@RequestMapping("/api/v1/notification-template") +@Tag(name = "通知模板管理", description = "通知模板的增删改查、渲染预览、发送测试等功能") +@Validated +public class NotificationTemplateApiController extends BaseController { + + @Resource + private INotificationTemplateService notificationTemplateService; + + + + @Operation(summary = "根据编码获取模板") + @GetMapping("/code/{code}") + public Response getByCode(@Parameter(description = "模板编码", required = true) @PathVariable String code) { + NotificationTemplateDTO template = notificationTemplateService.getByCode(code); + return Response.success(template); + } + + @Operation(summary = "渲染模板预览") + @PostMapping("/render") + public Response renderTemplate(@Valid @RequestBody TemplateRenderRequest request) { + String result = notificationTemplateService.renderTemplate(request.getTemplateCode(), request.getParams()); + return Response.success(result); + } + + @Operation(summary = "启用通知模板") + @PutMapping("/{id}/enable") + public Response enable(@Parameter(description = "模板ID", required = true) @PathVariable Long id) { + notificationTemplateService.enable(id); + return Response.success(); + } + + @Operation(summary = "禁用通知模板") + @PutMapping("/{id}/disable") + public Response disable(@Parameter(description = "模板ID", required = true) @PathVariable Long id) { + notificationTemplateService.disable(id); + return Response.success(); + } + + @Override + protected void exportData(HttpServletResponse response, List data) { + // TODO: 实现导出功能 + } +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/config/FreeMarkerConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/config/FreeMarkerConfig.java new file mode 100644 index 00000000..3cd2d2e7 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/config/FreeMarkerConfig.java @@ -0,0 +1,45 @@ +package com.qqchen.deploy.backend.notification.config; + +import freemarker.template.Configuration; +import freemarker.template.TemplateExceptionHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +import java.nio.charset.StandardCharsets; + +/** + * FreeMarker配置 + * + * @author qqchen + * @since 2025-11-12 + */ +@Component +public class FreeMarkerConfig { + + @Bean + public Configuration freemarkerConfig() { + Configuration config = new Configuration(Configuration.VERSION_2_3_32); + + // 设置默认编码 + config.setDefaultEncoding(StandardCharsets.UTF_8.name()); + + // 设置异常处理器 + config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); + + // 设置日期格式 + config.setDateFormat("yyyy-MM-dd"); + config.setTimeFormat("HH:mm:ss"); + config.setDateTimeFormat("yyyy-MM-dd HH:mm:ss"); + + // 设置数字格式(避免科学计数法) + config.setNumberFormat("0.######"); + + // 设置布尔值格式 + config.setBooleanFormat("true,false"); + + // 禁用自动转义(因为我们可能需要输出HTML) + config.setAutoEscapingPolicy(Configuration.DISABLE_AUTO_ESCAPING_POLICY); + + return config; + } +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/converter/NotificationChannelConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/converter/NotificationChannelConverter.java index a096eb91..4a1e47da 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/notification/converter/NotificationChannelConverter.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/converter/NotificationChannelConverter.java @@ -58,7 +58,7 @@ public abstract class NotificationChannelConverter implements BaseConverter { +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/NotificationChannelDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/NotificationChannelDTO.java index f8e6fb48..ef94190c 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/NotificationChannelDTO.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/NotificationChannelDTO.java @@ -1,7 +1,6 @@ package com.qqchen.deploy.backend.notification.dto; import com.qqchen.deploy.backend.framework.dto.BaseDTO; -import com.qqchen.deploy.backend.notification.enums.NotificationChannelStatusEnum; import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; @@ -32,8 +31,8 @@ public class NotificationChannelDTO extends BaseDTO { @NotNull(message = "渠道配置不能为空") private BaseNotificationConfigDTO config; - @Schema(description = "状态", example = "ENABLED") - private NotificationChannelStatusEnum status; + @Schema(description = "是否启用", example = "true") + private Boolean enabled; @Schema(description = "描述", example = "研发部通知群,用于部署通知") private String description; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/NotificationTemplateDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/NotificationTemplateDTO.java new file mode 100644 index 00000000..9bacbb9e --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/NotificationTemplateDTO.java @@ -0,0 +1,47 @@ +package com.qqchen.deploy.backend.notification.dto; + +import com.qqchen.deploy.backend.framework.dto.BaseDTO; +import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 通知模板DTO + * + * @author qqchen + * @since 2025-11-12 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Schema(description = "通知模板DTO") +public class NotificationTemplateDTO extends BaseDTO { + + @Schema(description = "模板名称", example = "Jenkins构建通知") + @NotBlank(message = "模板名称不能为空") + @Size(max = 100, message = "模板名称长度不能超过100个字符") + private String name; + + @Schema(description = "模板编码", example = "jenkins_build_wework") + @NotBlank(message = "模板编码不能为空") + @Size(max = 50, message = "模板编码长度不能超过50个字符") + private String code; + + @Schema(description = "模板描述", example = "用于Jenkins构建结果通知的模板") + @Size(max = 500, message = "模板描述长度不能超过500个字符") + private String description; + + @Schema(description = "渠道类型", example = "WEWORK") + @NotNull(message = "渠道类型不能为空") + private NotificationChannelTypeEnum channelType; + + @Schema(description = "内容模板", example = "### 构建通知\\n**项目**: ${projectName}") + @NotBlank(message = "内容模板不能为空") + private String contentTemplate; + + @Schema(description = "是否启用", example = "true") + private Boolean enabled = true; +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/SendNotificationRequest.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/SendNotificationRequest.java new file mode 100644 index 00000000..8b593ccf --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/SendNotificationRequest.java @@ -0,0 +1,29 @@ +package com.qqchen.deploy.backend.notification.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.Map; + +/** + * 发送通知请求 + * + * @author qqchen + * @since 2025-11-12 + */ +@Data +@Schema(description = "发送通知请求") +public class SendNotificationRequest { + + @Schema(description = "渠道ID", required = true, example = "1") + @NotNull(message = "渠道ID不能为空") + private Long channelId; + + @Schema(description = "通知模板ID", required = true, example = "1") + @NotNull(message = "通知模板ID不能为空") + private Long notificationTemplateId; + + @Schema(description = "模板参数", example = "{\"projectName\":\"测试项目\",\"buildNumber\":\"123\"}") + private Map params; +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/template/NotificationTemplateQuery.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/template/NotificationTemplateQuery.java new file mode 100644 index 00000000..9b72154e --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/template/NotificationTemplateQuery.java @@ -0,0 +1,37 @@ +package com.qqchen.deploy.backend.notification.dto.template; + +import com.qqchen.deploy.backend.framework.annotation.QueryField; +import com.qqchen.deploy.backend.framework.enums.QueryType; +import com.qqchen.deploy.backend.framework.query.BaseQuery; +import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 通知模板查询条件 + * + * @author qqchen + * @since 2025-11-12 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Schema(description = "通知模板查询条件") +public class NotificationTemplateQuery extends BaseQuery { + + @QueryField(type = QueryType.LIKE) + @Schema(description = "模板名称(模糊查询)", example = "Jenkins") + private String name; + + @QueryField(type = QueryType.LIKE) + @Schema(description = "模板编码(模糊查询)", example = "jenkins") + private String code; + + @QueryField(type = QueryType.EQUAL) + @Schema(description = "渠道类型", example = "WEWORK") + private NotificationChannelTypeEnum channelType; + + @QueryField(type = QueryType.EQUAL) + @Schema(description = "是否启用", example = "true") + private Boolean enabled; +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/template/TemplateRenderRequest.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/template/TemplateRenderRequest.java new file mode 100644 index 00000000..2aeb8ded --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/template/TemplateRenderRequest.java @@ -0,0 +1,25 @@ +package com.qqchen.deploy.backend.notification.dto.template; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +import java.util.Map; + +/** + * 模板渲染请求 + * + * @author qqchen + * @since 2025-11-12 + */ +@Data +@Schema(description = "模板渲染请求") +public class TemplateRenderRequest { + + @Schema(description = "模板编码", example = "jenkins_build_wework") + @NotBlank(message = "模板编码不能为空") + private String templateCode; + + @Schema(description = "模板参数", example = "{\"projectName\": \"deploy-ease-platform\", \"buildNumber\": 123}") + private Map params; +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/template/TemplateSendRequest.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/template/TemplateSendRequest.java new file mode 100644 index 00000000..c88ba4d5 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/template/TemplateSendRequest.java @@ -0,0 +1,30 @@ +package com.qqchen.deploy.backend.notification.dto.template; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.Map; + +/** + * 模板发送请求 + * + * @author qqchen + * @since 2025-11-12 + */ +@Data +@Schema(description = "模板发送请求") +public class TemplateSendRequest { + + @Schema(description = "模板编码", example = "jenkins_build_wework") + @NotBlank(message = "模板编码不能为空") + private String templateCode; + + @Schema(description = "渠道ID", example = "1") + @NotNull(message = "渠道ID不能为空") + private Long channelId; + + @Schema(description = "模板参数", example = "{\"projectName\": \"deploy-ease-platform\", \"buildNumber\": 123}") + private Map params; +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/entity/NotificationChannel.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/entity/NotificationChannel.java index 12091b97..59ad29c9 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/notification/entity/NotificationChannel.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/entity/NotificationChannel.java @@ -2,7 +2,6 @@ package com.qqchen.deploy.backend.notification.entity; import com.qqchen.deploy.backend.framework.annotation.LogicDelete; import com.qqchen.deploy.backend.framework.domain.Entity; -import com.qqchen.deploy.backend.notification.enums.NotificationChannelStatusEnum; import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum; import com.vladmihalcea.hibernate.type.json.JsonType; import jakarta.persistence.*; @@ -46,11 +45,10 @@ public class NotificationChannel extends Entity { private Map config; /** - * 状态 + * 是否启用 */ - @Enumerated(EnumType.STRING) - @Column(nullable = false, length = 20) - private NotificationChannelStatusEnum status = NotificationChannelStatusEnum.ENABLED; + @Column(nullable = false) + private Boolean enabled = true; /** * 描述 diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/entity/NotificationTemplate.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/entity/NotificationTemplate.java new file mode 100644 index 00000000..fc547b81 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/entity/NotificationTemplate.java @@ -0,0 +1,59 @@ +package com.qqchen.deploy.backend.notification.entity; + +import com.qqchen.deploy.backend.framework.annotation.LogicDelete; +import com.qqchen.deploy.backend.framework.domain.Entity; +import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum; +import jakarta.persistence.*; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 通知模板实体 + * + * @author qqchen + * @since 2025-11-12 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@jakarta.persistence.Entity +@Table(name = "sys_notification_template") +@LogicDelete +public class NotificationTemplate extends Entity { + + /** + * 模板名称 + */ + @Column(nullable = false, length = 100) + private String name; + + /** + * 模板编码(唯一标识) + */ + @Column(nullable = false, length = 50, unique = true) + private String code; + + /** + * 模板描述 + */ + @Column(length = 500) + private String description; + + /** + * 渠道类型 + */ + @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 20) + private NotificationChannelTypeEnum channelType; + + /** + * 内容模板(FreeMarker格式) + */ + @Column(nullable = false, columnDefinition = "TEXT") + private String contentTemplate; + + /** + * 是否启用 + */ + @Column(nullable = false) + private Boolean enabled = true; +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/repository/INotificationChannelRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/repository/INotificationChannelRepository.java index adb634aa..906ea7ec 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/notification/repository/INotificationChannelRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/repository/INotificationChannelRepository.java @@ -16,7 +16,7 @@ import java.util.List; */ @Repository public interface INotificationChannelRepository extends IBaseRepository { - + /** * 根据名称和删除标记查询是否存在 * @@ -24,25 +24,6 @@ public interface INotificationChannelRepository extends IBaseRepository findByChannelTypeAndStatusAndDeletedFalse( - NotificationChannelTypeEnum channelType, - NotificationChannelStatusEnum status - ); - - /** - * 根据状态查询渠道列表 - * - * @param status 状态 - * @return 渠道列表 - */ - List findByStatusAndDeletedFalse(NotificationChannelStatusEnum status); + } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/repository/INotificationTemplateRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/repository/INotificationTemplateRepository.java new file mode 100644 index 00000000..e422ec90 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/repository/INotificationTemplateRepository.java @@ -0,0 +1,60 @@ +package com.qqchen.deploy.backend.notification.repository; + +import com.qqchen.deploy.backend.framework.repository.IBaseRepository; +import com.qqchen.deploy.backend.notification.entity.NotificationTemplate; +import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +/** + * 通知模板Repository接口 + * + * @author qqchen + * @since 2025-11-12 + */ +@Repository +public interface INotificationTemplateRepository extends IBaseRepository { + + /** + * 根据编码查找模板 + */ + Optional findByCode(String code); + + /** + * 根据编码和启用状态查找模板 + */ + Optional findByCodeAndEnabled(String code, Boolean enabled); + + /** + * 根据渠道类型查找模板 + */ + List findByChannelTypeAndEnabled(NotificationChannelTypeEnum channelType, Boolean enabled); + + /** + * 检查编码是否存在(排除指定ID) + */ + @Query("SELECT COUNT(t) > 0 FROM NotificationTemplate t WHERE t.code = :code AND t.id != :id") + boolean existsByCodeAndIdNot(@Param("code") String code, @Param("id") Long id); + + /** + * 分页查询模板 + */ + @Query("SELECT t FROM NotificationTemplate t WHERE " + + "(:name IS NULL OR t.name LIKE %:name%) AND " + + "(:code IS NULL OR t.code LIKE %:code%) AND " + + "(:channelType IS NULL OR t.channelType = :channelType) AND " + + "(:enabled IS NULL OR t.enabled = :enabled)") + Page findByConditions( + @Param("name") String name, + @Param("code") String code, + @Param("channelType") NotificationChannelTypeEnum channelType, + @Param("enabled") Boolean enabled, + Pageable pageable + ); +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/service/INotificationService.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/service/INotificationService.java new file mode 100644 index 00000000..ee08b28d --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/service/INotificationService.java @@ -0,0 +1,19 @@ +package com.qqchen.deploy.backend.notification.service; + +import com.qqchen.deploy.backend.notification.dto.SendNotificationRequest; + +/** + * 通知服务接口 + * + * @author qqchen + * @since 2025-11-12 + */ +public interface INotificationService { + + /** + * 发送通知消息 + * + * @param request 发送请求(包含渠道ID、模板ID和参数) + */ + void send(SendNotificationRequest request); +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/service/INotificationTemplateService.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/service/INotificationTemplateService.java new file mode 100644 index 00000000..9ccc3c67 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/service/INotificationTemplateService.java @@ -0,0 +1,58 @@ +package com.qqchen.deploy.backend.notification.service; + +import com.qqchen.deploy.backend.framework.service.IBaseService; +import com.qqchen.deploy.backend.notification.dto.NotificationTemplateDTO; +import com.qqchen.deploy.backend.notification.dto.template.NotificationTemplateQuery; +import com.qqchen.deploy.backend.notification.entity.NotificationTemplate; + +import java.util.Map; + +/** + * 通知模板Service接口 + * + * @author qqchen + * @since 2025-11-12 + */ +public interface INotificationTemplateService extends IBaseService { + + /** + * 渲染模板 + * + * @param templateCode 模板编码 + * @param params 模板参数 + * @return 渲染后的内容 + */ + String renderTemplate(String templateCode, Map params); + + + /** + * 根据编码获取模板 + * + * @param code 模板编码 + * @return 模板DTO + */ + NotificationTemplateDTO getByCode(String code); + + /** + * 检查编码是否存在 + * + * @param code 模板编码 + * @param id 排除的ID(编辑时使用) + * @return 是否存在 + */ + boolean existsByCode(String code, Long id); + + /** + * 启用通知模板 + * + * @param id 模板ID + */ + void enable(Long id); + + /** + * 禁用通知模板 + * + * @param id 模板ID + */ + void disable(Long id); +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/service/impl/NotificationChannelServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/service/impl/NotificationChannelServiceImpl.java index 40b1baf2..8b6b4aa1 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/notification/service/impl/NotificationChannelServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/service/impl/NotificationChannelServiceImpl.java @@ -15,7 +15,6 @@ import com.qqchen.deploy.backend.notification.dto.NotificationChannelQuery; import com.qqchen.deploy.backend.notification.dto.BaseSendNotificationRequest; import com.qqchen.deploy.backend.notification.entity.config.WeworkNotificationConfig; import com.qqchen.deploy.backend.notification.entity.NotificationChannel; -import com.qqchen.deploy.backend.notification.enums.NotificationChannelStatusEnum; import com.qqchen.deploy.backend.notification.factory.NotificationChannelAdapterFactory; import com.qqchen.deploy.backend.notification.repository.INotificationChannelRepository; import com.qqchen.deploy.backend.notification.service.INotificationChannelService; @@ -97,7 +96,7 @@ public class NotificationChannelServiceImpl NotificationChannel channel = notificationChannelRepository.findById(id) .orElseThrow(() -> new BusinessException(ResponseCode.DATA_NOT_FOUND)); - channel.setStatus(NotificationChannelStatusEnum.ENABLED); + channel.setEnabled(true); notificationChannelRepository.save(channel); log.info("启用通知渠道: id={}, name={}", id, channel.getName()); @@ -109,7 +108,7 @@ public class NotificationChannelServiceImpl NotificationChannel channel = notificationChannelRepository.findById(id) .orElseThrow(() -> new BusinessException(ResponseCode.DATA_NOT_FOUND)); - channel.setStatus(NotificationChannelStatusEnum.DISABLED); + channel.setEnabled(false); notificationChannelRepository.save(channel); log.info("禁用通知渠道: id={}, name={}", id, channel.getName()); @@ -130,7 +129,7 @@ public class NotificationChannelServiceImpl .orElseThrow(() -> new BusinessException(ResponseCode.DATA_NOT_FOUND)); // 3. 校验渠道状态 - if (channel.getStatus() != NotificationChannelStatusEnum.ENABLED) { + if (!channel.getEnabled()) { throw new BusinessException(ResponseCode.DATA_NOT_FOUND); } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/service/impl/NotificationServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/service/impl/NotificationServiceImpl.java new file mode 100644 index 00000000..3b003bf7 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/service/impl/NotificationServiceImpl.java @@ -0,0 +1,147 @@ +package com.qqchen.deploy.backend.notification.service.impl; + +import com.qqchen.deploy.backend.framework.enums.ResponseCode; +import com.qqchen.deploy.backend.framework.exception.BusinessException; +import com.qqchen.deploy.backend.notification.dto.EmailSendNotificationRequest; +import com.qqchen.deploy.backend.notification.dto.SendNotificationRequest; +import com.qqchen.deploy.backend.notification.dto.WeworkSendNotificationRequest; +import com.qqchen.deploy.backend.notification.entity.NotificationChannel; +import com.qqchen.deploy.backend.notification.entity.NotificationTemplate; +import com.qqchen.deploy.backend.notification.enums.WeworkMessageTypeEnum; +import com.qqchen.deploy.backend.notification.repository.INotificationChannelRepository; +import com.qqchen.deploy.backend.notification.repository.INotificationTemplateRepository; +import com.qqchen.deploy.backend.notification.service.INotificationSendService; +import com.qqchen.deploy.backend.notification.service.INotificationService; +import com.qqchen.deploy.backend.notification.service.INotificationTemplateService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; + +/** + * 通知服务实现类 + * + * @author qqchen + * @since 2025-11-12 + */ +@Slf4j +@Service +public class NotificationServiceImpl implements INotificationService { + + @Resource + private INotificationTemplateService notificationTemplateService; + + @Resource + private INotificationSendService notificationSendService; + + @Resource + private INotificationChannelRepository notificationChannelRepository; + + @Resource + private INotificationTemplateRepository notificationTemplateRepository; + + @Override + public void send(SendNotificationRequest request) { + // 1. 获取通知模板 + NotificationTemplate template = notificationTemplateRepository.findById(request.getNotificationTemplateId()) + .orElseThrow(() -> new BusinessException(ResponseCode.NOTIFICATION_TEMPLATE_NOT_FOUND)); + + // 2. 获取通知渠道 + NotificationChannel channel = notificationChannelRepository.findById(request.getChannelId()) + .orElseThrow(() -> new BusinessException(ResponseCode.DATA_NOT_FOUND)); + + // 3. 验证模板和渠道类型是否匹配 + if (!template.getChannelType().equals(channel.getChannelType())) { + throw new BusinessException(ResponseCode.INVALID_PARAM); + } + + // 4. 验证模板和渠道是否启用 + if (!template.getEnabled()) { + throw new BusinessException(ResponseCode.NOTIFICATION_TEMPLATE_DISABLED); + } + if (!channel.getEnabled()) { + throw new BusinessException(ResponseCode.DATA_NOT_FOUND); + } + + // 5. 渲染模板内容 + String content = notificationTemplateService.renderTemplate(template.getCode(), request.getParams()); + + // 6. 根据渠道类型发送通知 + switch (template.getChannelType()) { + case WEWORK -> sendWeworkNotification(request.getChannelId(), content, request.getParams()); + case EMAIL -> sendEmailNotification(request.getChannelId(), content, request.getParams()); + default -> throw new BusinessException(ResponseCode.INVALID_PARAM); + } + } + + /** + * 发送企业微信通知 + */ + private void sendWeworkNotification(Long channelId, String content, Map params) { + WeworkSendNotificationRequest request = new WeworkSendNotificationRequest(); + request.setChannelId(channelId); + request.setContent(content); + + // 根据内容判断消息类型,或从参数中获取 + WeworkMessageTypeEnum messageType = WeworkMessageTypeEnum.MARKDOWN; // 默认使用 MARKDOWN + if (params != null && params.containsKey("messageType")) { + messageType = WeworkMessageTypeEnum.valueOf(params.get("messageType").toString()); + } + request.setMessageType(messageType); + + // 从参数中获取@人员信息 + if (params != null) { + if (params.containsKey("mentionedMobileList")) { + request.setMentionedMobileList((List) params.get("mentionedMobileList")); + } + if (params.containsKey("mentionedUserList")) { + request.setMentionedUserList((List) params.get("mentionedUserList")); + } + } + + notificationSendService.send(request); + } + + /** + * 发送邮件通知 + */ + private void sendEmailNotification(Long channelId, String content, Map params) { + EmailSendNotificationRequest request = new EmailSendNotificationRequest(); + request.setChannelId(channelId); + request.setContent(content); + + // 邮件标题从参数中获取,或使用默认值 + String subject = "系统通知"; + if (params != null && params.containsKey("subject")) { + subject = params.get("subject").toString(); + } + request.setSubject(subject); + + // 是否HTML格式从参数中获取 + Boolean isHtml = false; + if (params != null && params.containsKey("isHtml")) { + isHtml = (Boolean) params.get("isHtml"); + } + request.setIsHtml(isHtml); + + // 收件人信息(必须) + if (params == null || !params.containsKey("toReceivers")) { + throw new BusinessException(ResponseCode.INVALID_PARAM); + } + request.setToReceivers((List) params.get("toReceivers")); + + // 可选参数 + if (params != null) { + if (params.containsKey("ccReceivers")) { + request.setCcReceivers((List) params.get("ccReceivers")); + } + if (params.containsKey("bccReceivers")) { + request.setBccReceivers((List) params.get("bccReceivers")); + } + } + + notificationSendService.send(request); + } +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/service/impl/NotificationTemplateServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/service/impl/NotificationTemplateServiceImpl.java new file mode 100644 index 00000000..d7ecf891 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/service/impl/NotificationTemplateServiceImpl.java @@ -0,0 +1,118 @@ +package com.qqchen.deploy.backend.notification.service.impl; + +import com.qqchen.deploy.backend.framework.exception.BusinessException; +import com.qqchen.deploy.backend.framework.enums.ResponseCode; +import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; +import com.qqchen.deploy.backend.notification.converter.NotificationTemplateConverter; +import com.qqchen.deploy.backend.notification.dto.EmailSendNotificationRequest; +import com.qqchen.deploy.backend.notification.dto.NotificationTemplateDTO; +import com.qqchen.deploy.backend.notification.dto.WeworkSendNotificationRequest; +import com.qqchen.deploy.backend.notification.dto.template.NotificationTemplateQuery; +import com.qqchen.deploy.backend.notification.entity.NotificationTemplate; +import com.qqchen.deploy.backend.notification.enums.WeworkMessageTypeEnum; +import com.qqchen.deploy.backend.notification.repository.INotificationTemplateRepository; +import com.qqchen.deploy.backend.notification.service.INotificationSendService; +import com.qqchen.deploy.backend.notification.service.INotificationTemplateService; +import freemarker.template.Configuration; +import freemarker.template.Template; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.StringReader; +import java.io.StringWriter; +import java.util.List; +import java.util.Map; + +/** + * 通知模板Service实现类 + * + * @author qqchen + * @since 2025-11-12 + */ +@Slf4j +@Service +public class NotificationTemplateServiceImpl extends BaseServiceImpl implements INotificationTemplateService { + + @Resource + private INotificationTemplateRepository notificationTemplateRepository; + + @Resource + private NotificationTemplateConverter notificationTemplateConverter; + + @Resource + private Configuration freemarkerConfig; + + @Override + protected void validateUniqueConstraints(NotificationTemplateDTO dto) { + // 检查编码是否重复 + if (existsByCode(dto.getCode(), dto.getId())) { + throw new BusinessException(ResponseCode.NOTIFICATION_TEMPLATE_CODE_EXISTS); + } + } + + @Override + public String renderTemplate(String templateCode, Map params) { + NotificationTemplate template = getTemplateByCode(templateCode); + return processTemplate(template.getContentTemplate(), params); + } + + + @Override + public NotificationTemplateDTO getByCode(String code) { + NotificationTemplate template = getTemplateByCode(code); + return notificationTemplateConverter.toDto(template); + } + + @Override + public boolean existsByCode(String code, Long id) { + if (id == null) { + return notificationTemplateRepository.findByCode(code).isPresent(); + } + return notificationTemplateRepository.existsByCodeAndIdNot(code, id); + } + + /** + * 根据编码获取模板实体 + */ + private NotificationTemplate getTemplateByCode(String code) { + return notificationTemplateRepository.findByCodeAndEnabled(code, true) + .orElseThrow(() -> new BusinessException(ResponseCode.NOTIFICATION_TEMPLATE_NOT_FOUND)); + } + + + /** + * FreeMarker模板处理 + */ + private String processTemplate(String templateContent, Map params) { + try { + Template template = new Template("notification", new StringReader(templateContent), freemarkerConfig); + StringWriter writer = new StringWriter(); + template.process(params, writer); + return writer.toString(); + } catch (Exception e) { + log.error("模板渲染失败: {}", e.getMessage(), e); + throw new BusinessException(ResponseCode.NOTIFICATION_TEMPLATE_RENDER_ERROR); + } + } + + @Override + @Transactional + public void enable(Long id) { + NotificationTemplate template = notificationTemplateRepository.findById(id) + .orElseThrow(() -> new BusinessException(ResponseCode.NOTIFICATION_TEMPLATE_NOT_FOUND)); + template.setEnabled(true); + notificationTemplateRepository.save(template); + } + + @Override + @Transactional + public void disable(Long id) { + NotificationTemplate template = notificationTemplateRepository.findById(id) + .orElseThrow(() -> new BusinessException(ResponseCode.NOTIFICATION_TEMPLATE_NOT_FOUND)); + template.setEnabled(false); + notificationTemplateRepository.save(template); + } +} diff --git a/backend/src/main/resources/db/changelog/changes/v1.0.0-data.sql b/backend/src/main/resources/db/changelog/changes/v1.0.0-data.sql index fd5f141a..663b6ea3 100644 --- a/backend/src/main/resources/db/changelog/changes/v1.0.0-data.sql +++ b/backend/src/main/resources/db/changelog/changes/v1.0.0-data.sql @@ -75,8 +75,10 @@ VALUES (203, '定时任务管理', '/deploy/schedule-jobs', 'Deploy/ScheduleJob/List', 'ClockCircleOutlined', 'deploy:schedule-job', 2, 200, 3, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), -- 环境管理 (204, '环境管理', '/deploy/environments', 'Deploy/Environment/List', 'CloudOutlined', 'deploy:environment', 2, 200, 4, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), --- 消息中心 -(205, '消息中心', '/deploy/notification-channels', 'Deploy/NotificationChannel/List', 'BellOutlined', 'deploy:notification-channel', 2, 200, 5, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), +-- 消息渠道管理 +(205, '消息渠道管理', '/deploy/notification-channels', 'Deploy/NotificationChannel/List', 'BellOutlined', 'deploy:notification-channel', 2, 200, 5, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), +-- 通知模板 +(206, '通知模板', '/deploy/notification-templates', 'Deploy/NotificationTemplate/List', 'FileTextOutlined', 'deploy:notification-template', 2, 200, 6, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), -- 资源管理 (300, '资源管理', '/resource', NULL, 'DatabaseOutlined', NULL, 1, NULL, 3, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), @@ -301,7 +303,16 @@ INSERT INTO sys_permission (id, create_time, menu_id, code, name, type, sort) VA (323, NOW(), 104, 'workflow:form:create', '表单创建', 'FUNCTION', 3), (324, NOW(), 104, 'workflow:form:update', '表单修改', 'FUNCTION', 4), (325, NOW(), 104, 'workflow:form:delete', '表单删除', 'FUNCTION', 5), -(326, NOW(), 104, 'workflow:form:publish', '发布表单', 'FUNCTION', 6); +(326, NOW(), 104, 'workflow:form:publish', '发布表单', 'FUNCTION', 6), + +-- 通知模板管理 (menu_id=206) +(2061, NOW(), 206, 'notification:template:view', '查看通知模板', 'FUNCTION', 1), +(2062, NOW(), 206, 'notification:template:create', '新增通知模板', 'FUNCTION', 2), +(2063, NOW(), 206, 'notification:template:update', '编辑通知模板', 'FUNCTION', 3), +(2064, NOW(), 206, 'notification:template:delete', '删除通知模板', 'FUNCTION', 4), +(2065, NOW(), 206, 'notification:template:toggle', '启用/禁用通知模板', 'FUNCTION', 5), +(2066, NOW(), 206, 'notification:template:copy', '复制通知模板', 'FUNCTION', 6), +(2067, NOW(), 206, 'notification:template:preview', '预览通知模板', 'FUNCTION', 7); -- -- -- 团队配置管理 (无对应菜单,menu_id=NULL) @@ -1195,11 +1206,11 @@ INSERT INTO workflow_node_definition ( -- 企业微信通知渠道示例 INSERT INTO sys_notification_channel -(name, channel_type, config, status, description, create_by, create_time, update_by, update_time, version, deleted) +(name, channel_type, config, enabled, description, create_by, create_time, update_by, update_time, version, deleted) VALUES ('研发部企业微信群', 'WEWORK', '{"webhookUrl":"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=example-key-please-replace"}', - 'DISABLED', + 0, '研发部通知群,用于部署通知(示例数据,请修改为实际的Webhook地址)', 'admin', NOW(), 'admin', NOW(), 0, 0); @@ -1308,3 +1319,144 @@ INSERT INTO deploy_server_category (id, name, code, icon, description, sort, ena (3, '中间件服务器', 'MIDDLEWARE_SERVER', 'cluster', '消息队列、搜索引擎等中间件', 3, 1, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 1, 0), (4, '应用服务器', 'APP_SERVER', 'cloud-server', '业务应用服务器', 4, 1, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 1, 0), (5, '其他', 'OTHER', 'hdd', '其他类型服务器', 99, 1, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 1, 0); + +-- ===================================================== +-- 通知模板初始数据 +-- ===================================================== + +-- 插入通知模板初始数据 +INSERT INTO sys_notification_template (id, create_by, create_time, update_by, update_time, version, deleted, + name, code, description, channel_type, content_template, enabled) VALUES +-- Jenkins构建通知模板 +(1, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 1, 0, +'Jenkins构建通知-企业微信', 'jenkins_build_wework', 'Jenkins构建结果通知模板(企业微信)', 'WEWORK', +'### 🚀 Jenkins构建通知 + +**项目名称**: ${projectName} +**构建号**: #${buildNumber} +**构建状态**: <#if buildStatus == "SUCCESS">✅ 成功<#elseif buildStatus == "FAILURE">❌ 失败<#else>🔄 构建中 +<#if buildTime??>**构建时间**: ${buildTime} +<#if duration??>**构建耗时**: ${duration} + +<#if buildStatus == "FAILURE"> +⚠️ **构建失败,请及时处理!** + + +--- +*Deploy Ease Platform 自动通知*', 1), + +(2, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 1, 0, +'Jenkins构建通知-邮件', 'jenkins_build_email', 'Jenkins构建结果通知模板(邮件)', 'EMAIL', +'

🚀 Jenkins构建通知

+ + + + + + + + + + + + + + <#if buildTime??> + + + + + + <#if duration??> + + + + + +
项目名称${projectName}
构建号#${buildNumber}
构建状态 + <#if buildStatus == "SUCCESS"> + ✅ 成功 + <#elseif buildStatus == "FAILURE"> + ❌ 失败 + <#else> + 🔄 构建中 + +
构建时间${buildTime}
构建耗时${duration}
+ +<#if buildStatus == "FAILURE"> +

⚠️ 构建失败,请及时处理!

+ + +
+

Deploy Ease Platform 自动通知

', 1), + +-- 部署通知模板 +(3, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 1, 0, +'部署通知-企业微信', 'deploy_notification_wework', '应用部署结果通知模板(企业微信)', 'WEWORK', +'### 📦 应用部署通知 + +**应用名称**: ${appName} +**部署环境**: ${environment} +**部署状态**: <#if deployStatus == "SUCCESS">✅ 部署成功<#else>❌ 部署失败 +**部署版本**: ${version} +**部署时间**: ${deployTime} + +<#if deployUrl??> +**访问地址**: ${deployUrl} + + +<#if deployStatus == "SUCCESS"> +🎉 **应用已成功部署上线!** +<#else> +⚠️ **部署失败,请检查日志!** + + +--- +*Deploy Ease Platform 自动通知*', 1), + +(4, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 1, 0, +'部署通知-邮件', 'deploy_notification_email', '应用部署结果通知模板(邮件)', 'EMAIL', +'

📦 应用部署通知

+ + + + + + + + + + + + + + + + + + + + + + <#if deployUrl??> + + + + + +
应用名称${appName}
部署环境${environment}
部署状态 + <#if deployStatus == "SUCCESS"> + ✅ 部署成功 + <#else> + ❌ 部署失败 + +
部署版本${version}
部署时间${deployTime}
访问地址${deployUrl}
+ +<#if deployStatus == "SUCCESS"> +

🎉 应用已成功部署上线!

+<#else> +

⚠️ 部署失败,请检查日志!

+ + +
+

Deploy Ease Platform 自动通知

', 1); diff --git a/backend/src/main/resources/db/changelog/changes/v1.0.0-schema.sql b/backend/src/main/resources/db/changelog/changes/v1.0.0-schema.sql index fed4c164..3ad34cf0 100644 --- a/backend/src/main/resources/db/changelog/changes/v1.0.0-schema.sql +++ b/backend/src/main/resources/db/changelog/changes/v1.0.0-schema.sql @@ -926,11 +926,11 @@ CREATE TABLE sys_notification_channel name VARCHAR(100) NOT NULL COMMENT '渠道名称(如:研发部企业微信群)', channel_type VARCHAR(50) NOT NULL COMMENT '渠道类型(WEWORK, FEISHU, DINGTALK, SMS, EMAIL, SLACK)', config JSON NOT NULL COMMENT '渠道配置(JSON格式,不同渠道存储不同字段)', - status VARCHAR(20) NOT NULL DEFAULT 'ENABLED' COMMENT '状态(ENABLED-启用, DISABLED-禁用)', + enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用', description VARCHAR(500) NULL COMMENT '描述说明', INDEX idx_channel_type (channel_type), - INDEX idx_status (status), + INDEX idx_enabled (enabled), INDEX idx_deleted (deleted) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='通知渠道配置表'; @@ -1144,3 +1144,26 @@ CREATE TABLE deploy_record CONSTRAINT fk_deploy_record_team_app FOREIGN KEY (team_application_id) REFERENCES deploy_team_application (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='部署记录表'; +-- 通知模板表 +CREATE TABLE sys_notification_template +( + id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', + create_by VARCHAR(100) NULL COMMENT '创建人', + create_time DATETIME(6) NULL COMMENT '创建时间', + update_by VARCHAR(100) NULL COMMENT '更新人', + update_time DATETIME(6) NULL COMMENT '更新时间', + version INT NOT NULL DEFAULT 1 COMMENT '版本号', + deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除', + + name VARCHAR(100) NOT NULL COMMENT '模板名称', + code VARCHAR(50) NOT NULL COMMENT '模板编码', + description VARCHAR(500) NULL COMMENT '模板描述', + channel_type VARCHAR(20) NOT NULL COMMENT '渠道类型', + content_template TEXT NOT NULL COMMENT '内容模板', + enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用', + + UNIQUE KEY uk_code (code), + KEY idx_channel_type (channel_type), + KEY idx_enabled (enabled) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='通知模板表'; +