增加构建通知

This commit is contained in:
dengqichen 2025-11-12 16:53:53 +08:00
parent 62c1a8b0d1
commit 13f8be9146
24 changed files with 999 additions and 55 deletions

View File

@ -229,7 +229,13 @@ public enum ResponseCode {
DEPLOY_PERMISSION_DENIED(3009, "deploy.permission.denied"), DEPLOY_PERMISSION_DENIED(3009, "deploy.permission.denied"),
DEPLOY_ENVIRONMENT_LOCKED(3010, "deploy.environment.locked"), DEPLOY_ENVIRONMENT_LOCKED(3010, "deploy.environment.locked"),
DEPLOY_APPROVAL_REQUIRED(3011, "deploy.approval.required"), 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 int code;
private final String messageKey; // 国际化消息key private final String messageKey; // 国际化消息key

View File

@ -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<Void> send(@Valid @RequestBody SendNotificationRequest request) {
notificationService.send(request);
return Response.success();
}
}

View File

@ -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.framework.controller.BaseController;
import com.qqchen.deploy.backend.notification.dto.NotificationChannelDTO; import com.qqchen.deploy.backend.notification.dto.NotificationChannelDTO;
import com.qqchen.deploy.backend.notification.dto.NotificationChannelQuery; 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.entity.NotificationChannel;
import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum; import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum;
import com.qqchen.deploy.backend.notification.service.INotificationChannelService; 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.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@ -42,9 +40,6 @@ public class NotificationChannelApiController extends BaseController<Notificatio
@Resource @Resource
private INotificationChannelService notificationChannelService; private INotificationChannelService notificationChannelService;
@Resource
private INotificationSendService notificationSendService;
@Override @Override
public Response<NotificationChannelDTO> create(@Validated @RequestBody NotificationChannelDTO dto) { public Response<NotificationChannelDTO> create(@Validated @RequestBody NotificationChannelDTO dto) {
return super.create(dto); return super.create(dto);
@ -128,14 +123,6 @@ public class NotificationChannelApiController extends BaseController<Notificatio
return Response.success(); return Response.success();
} }
@Operation(summary = "发送通知消息")
@PostMapping("/send")
public Response<Void> send(
@Parameter(description = "通知请求", required = true) @RequestBody BaseSendNotificationRequest request
) {
notificationSendService.send(request);
return Response.success();
}
@Override @Override
protected void exportData(HttpServletResponse response, List<NotificationChannelDTO> data) { protected void exportData(HttpServletResponse response, List<NotificationChannelDTO> data) {

View File

@ -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<NotificationTemplate, NotificationTemplateDTO, Long, NotificationTemplateQuery> {
@Resource
private INotificationTemplateService notificationTemplateService;
@Operation(summary = "根据编码获取模板")
@GetMapping("/code/{code}")
public Response<NotificationTemplateDTO> getByCode(@Parameter(description = "模板编码", required = true) @PathVariable String code) {
NotificationTemplateDTO template = notificationTemplateService.getByCode(code);
return Response.success(template);
}
@Operation(summary = "渲染模板预览")
@PostMapping("/render")
public Response<String> renderTemplate(@Valid @RequestBody TemplateRenderRequest request) {
String result = notificationTemplateService.renderTemplate(request.getTemplateCode(), request.getParams());
return Response.success(result);
}
@Operation(summary = "启用通知模板")
@PutMapping("/{id}/enable")
public Response<Void> enable(@Parameter(description = "模板ID", required = true) @PathVariable Long id) {
notificationTemplateService.enable(id);
return Response.success();
}
@Operation(summary = "禁用通知模板")
@PutMapping("/{id}/disable")
public Response<Void> disable(@Parameter(description = "模板ID", required = true) @PathVariable Long id) {
notificationTemplateService.disable(id);
return Response.success();
}
@Override
protected void exportData(HttpServletResponse response, List<NotificationTemplateDTO> data) {
// TODO: 实现导出功能
}
}

View File

@ -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;
}
}

View File

@ -58,7 +58,7 @@ public abstract class NotificationChannelConverter implements BaseConverter<Noti
// 更新基本字段 // 更新基本字段
entity.setName(dto.getName()); entity.setName(dto.getName());
entity.setChannelType(dto.getChannelType()); entity.setChannelType(dto.getChannelType());
entity.setStatus(dto.getStatus()); entity.setEnabled(dto.getEnabled());
entity.setDescription(dto.getDescription()); entity.setDescription(dto.getDescription());
// 更新config字段 // 更新config字段

View File

@ -0,0 +1,16 @@
package com.qqchen.deploy.backend.notification.converter;
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import com.qqchen.deploy.backend.notification.dto.NotificationTemplateDTO;
import com.qqchen.deploy.backend.notification.entity.NotificationTemplate;
import org.mapstruct.Mapper;
/**
* 通知模板转换器
*
* @author qqchen
* @since 2025-11-12
*/
@Mapper(componentModel = "spring")
public interface NotificationTemplateConverter extends BaseConverter<NotificationTemplate, NotificationTemplateDTO> {
}

View File

@ -1,7 +1,6 @@
package com.qqchen.deploy.backend.notification.dto; package com.qqchen.deploy.backend.notification.dto;
import com.qqchen.deploy.backend.framework.dto.BaseDTO; 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 com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
@ -32,8 +31,8 @@ public class NotificationChannelDTO extends BaseDTO {
@NotNull(message = "渠道配置不能为空") @NotNull(message = "渠道配置不能为空")
private BaseNotificationConfigDTO config; private BaseNotificationConfigDTO config;
@Schema(description = "状态", example = "ENABLED") @Schema(description = "是否启用", example = "true")
private NotificationChannelStatusEnum status; private Boolean enabled;
@Schema(description = "描述", example = "研发部通知群,用于部署通知") @Schema(description = "描述", example = "研发部通知群,用于部署通知")
private String description; private String description;

View File

@ -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;
}

View File

@ -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<String, Object> params;
}

View File

@ -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;
}

View File

@ -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<String, Object> params;
}

View File

@ -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<String, Object> params;
}

View File

@ -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.annotation.LogicDelete;
import com.qqchen.deploy.backend.framework.domain.Entity; 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.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum;
import com.vladmihalcea.hibernate.type.json.JsonType; import com.vladmihalcea.hibernate.type.json.JsonType;
import jakarta.persistence.*; import jakarta.persistence.*;
@ -46,11 +45,10 @@ public class NotificationChannel extends Entity<Long> {
private Map<String, Object> config; private Map<String, Object> config;
/** /**
* 状态 * 是否启用
*/ */
@Enumerated(EnumType.STRING) @Column(nullable = false)
@Column(nullable = false, length = 20) private Boolean enabled = true;
private NotificationChannelStatusEnum status = NotificationChannelStatusEnum.ENABLED;
/** /**
* 描述 * 描述

View File

@ -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<Long> {
/**
* 模板名称
*/
@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;
}

View File

@ -25,24 +25,5 @@ public interface INotificationChannelRepository extends IBaseRepository<Notifica
*/ */
boolean existsByNameAndDeletedFalse(String name); boolean existsByNameAndDeletedFalse(String name);
/**
* 根据渠道类型和状态查询渠道列表
*
* @param channelType 渠道类型
* @param status 状态
* @return 渠道列表
*/
List<NotificationChannel> findByChannelTypeAndStatusAndDeletedFalse(
NotificationChannelTypeEnum channelType,
NotificationChannelStatusEnum status
);
/**
* 根据状态查询渠道列表
*
* @param status 状态
* @return 渠道列表
*/
List<NotificationChannel> findByStatusAndDeletedFalse(NotificationChannelStatusEnum status);
} }

View File

@ -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<NotificationTemplate, Long> {
/**
* 根据编码查找模板
*/
Optional<NotificationTemplate> findByCode(String code);
/**
* 根据编码和启用状态查找模板
*/
Optional<NotificationTemplate> findByCodeAndEnabled(String code, Boolean enabled);
/**
* 根据渠道类型查找模板
*/
List<NotificationTemplate> 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<NotificationTemplate> findByConditions(
@Param("name") String name,
@Param("code") String code,
@Param("channelType") NotificationChannelTypeEnum channelType,
@Param("enabled") Boolean enabled,
Pageable pageable
);
}

View File

@ -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);
}

View File

@ -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<NotificationTemplate, NotificationTemplateDTO, NotificationTemplateQuery, Long> {
/**
* 渲染模板
*
* @param templateCode 模板编码
* @param params 模板参数
* @return 渲染后的内容
*/
String renderTemplate(String templateCode, Map<String, Object> 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);
}

View File

@ -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.dto.BaseSendNotificationRequest;
import com.qqchen.deploy.backend.notification.entity.config.WeworkNotificationConfig; import com.qqchen.deploy.backend.notification.entity.config.WeworkNotificationConfig;
import com.qqchen.deploy.backend.notification.entity.NotificationChannel; 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.factory.NotificationChannelAdapterFactory;
import com.qqchen.deploy.backend.notification.repository.INotificationChannelRepository; import com.qqchen.deploy.backend.notification.repository.INotificationChannelRepository;
import com.qqchen.deploy.backend.notification.service.INotificationChannelService; import com.qqchen.deploy.backend.notification.service.INotificationChannelService;
@ -97,7 +96,7 @@ public class NotificationChannelServiceImpl
NotificationChannel channel = notificationChannelRepository.findById(id) NotificationChannel channel = notificationChannelRepository.findById(id)
.orElseThrow(() -> new BusinessException(ResponseCode.DATA_NOT_FOUND)); .orElseThrow(() -> new BusinessException(ResponseCode.DATA_NOT_FOUND));
channel.setStatus(NotificationChannelStatusEnum.ENABLED); channel.setEnabled(true);
notificationChannelRepository.save(channel); notificationChannelRepository.save(channel);
log.info("启用通知渠道: id={}, name={}", id, channel.getName()); log.info("启用通知渠道: id={}, name={}", id, channel.getName());
@ -109,7 +108,7 @@ public class NotificationChannelServiceImpl
NotificationChannel channel = notificationChannelRepository.findById(id) NotificationChannel channel = notificationChannelRepository.findById(id)
.orElseThrow(() -> new BusinessException(ResponseCode.DATA_NOT_FOUND)); .orElseThrow(() -> new BusinessException(ResponseCode.DATA_NOT_FOUND));
channel.setStatus(NotificationChannelStatusEnum.DISABLED); channel.setEnabled(false);
notificationChannelRepository.save(channel); notificationChannelRepository.save(channel);
log.info("禁用通知渠道: id={}, name={}", id, channel.getName()); log.info("禁用通知渠道: id={}, name={}", id, channel.getName());
@ -130,7 +129,7 @@ public class NotificationChannelServiceImpl
.orElseThrow(() -> new BusinessException(ResponseCode.DATA_NOT_FOUND)); .orElseThrow(() -> new BusinessException(ResponseCode.DATA_NOT_FOUND));
// 3. 校验渠道状态 // 3. 校验渠道状态
if (channel.getStatus() != NotificationChannelStatusEnum.ENABLED) { if (!channel.getEnabled()) {
throw new BusinessException(ResponseCode.DATA_NOT_FOUND); throw new BusinessException(ResponseCode.DATA_NOT_FOUND);
} }

View File

@ -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<String, Object> 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<String>) params.get("mentionedMobileList"));
}
if (params.containsKey("mentionedUserList")) {
request.setMentionedUserList((List<String>) params.get("mentionedUserList"));
}
}
notificationSendService.send(request);
}
/**
* 发送邮件通知
*/
private void sendEmailNotification(Long channelId, String content, Map<String, Object> 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<String>) params.get("toReceivers"));
// 可选参数
if (params != null) {
if (params.containsKey("ccReceivers")) {
request.setCcReceivers((List<String>) params.get("ccReceivers"));
}
if (params.containsKey("bccReceivers")) {
request.setBccReceivers((List<String>) params.get("bccReceivers"));
}
}
notificationSendService.send(request);
}
}

View File

@ -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<NotificationTemplate, NotificationTemplateDTO, NotificationTemplateQuery, Long> 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<String, Object> 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<String, Object> 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);
}
}

View File

@ -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), (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), (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), (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), (323, NOW(), 104, 'workflow:form:create', '表单创建', 'FUNCTION', 3),
(324, NOW(), 104, 'workflow:form:update', '表单修改', 'FUNCTION', 4), (324, NOW(), 104, 'workflow:form:update', '表单修改', 'FUNCTION', 4),
(325, NOW(), 104, 'workflow:form:delete', '表单删除', 'FUNCTION', 5), (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) -- -- 团队配置管理 (无对应菜单menu_id=NULL)
@ -1195,11 +1206,11 @@ INSERT INTO workflow_node_definition (
-- 企业微信通知渠道示例 -- 企业微信通知渠道示例
INSERT INTO sys_notification_channel 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 VALUES
('研发部企业微信群', 'WEWORK', ('研发部企业微信群', 'WEWORK',
'{"webhookUrl":"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=example-key-please-replace"}', '{"webhookUrl":"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=example-key-please-replace"}',
'DISABLED', 0,
'研发部通知群用于部署通知示例数据请修改为实际的Webhook地址', '研发部通知群用于部署通知示例数据请修改为实际的Webhook地址',
'admin', NOW(), 'admin', NOW(), 0, 0); '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), (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), (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); (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>
<#if buildTime??>**构建时间**: ${buildTime}</#if>
<#if duration??>**构建耗时**: ${duration}</#if>
<#if buildStatus == "FAILURE">
****
</#if>
---
*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',
'<h2>🚀 Jenkins构建通知</h2>
<table style="border-collapse: collapse; width: 100%;">
<tr>
<td style="padding: 8px; border: 1px solid #ddd;"><strong></strong></td>
<td style="padding: 8px; border: 1px solid #ddd;">${projectName}</td>
</tr>
<tr>
<td style="padding: 8px; border: 1px solid #ddd;"><strong></strong></td>
<td style="padding: 8px; border: 1px solid #ddd;">#${buildNumber}</td>
</tr>
<tr>
<td style="padding: 8px; border: 1px solid #ddd;"><strong></strong></td>
<td style="padding: 8px; border: 1px solid #ddd;">
<#if buildStatus == "SUCCESS">
<span style="color: green;"> </span>
<#elseif buildStatus == "FAILURE">
<span style="color: red;"> </span>
<#else>
<span style="color: orange;">🔄 </span>
</#if>
</td>
</tr>
<#if buildTime??>
<tr>
<td style="padding: 8px; border: 1px solid #ddd;"><strong></strong></td>
<td style="padding: 8px; border: 1px solid #ddd;">${buildTime}</td>
</tr>
</#if>
<#if duration??>
<tr>
<td style="padding: 8px; border: 1px solid #ddd;"><strong></strong></td>
<td style="padding: 8px; border: 1px solid #ddd;">${duration}</td>
</tr>
</#if>
</table>
<#if buildStatus == "FAILURE">
<p style="color: red; font-weight: bold;"> </p>
</#if>
<hr>
<p style="color: #666; font-size: 12px;">Deploy Ease Platform </p>', 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>❌ 部署失败</#if>
****: ${version}
****: ${deployTime}
<#if deployUrl??>
**访**: ${deployUrl}
</#if>
<#if deployStatus == "SUCCESS">
🎉 **线**
<#else>
****
</#if>
---
*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',
'<h2>📦 应用部署通知</h2>
<table style="border-collapse: collapse; width: 100%;">
<tr>
<td style="padding: 8px; border: 1px solid #ddd;"><strong></strong></td>
<td style="padding: 8px; border: 1px solid #ddd;">${appName}</td>
</tr>
<tr>
<td style="padding: 8px; border: 1px solid #ddd;"><strong></strong></td>
<td style="padding: 8px; border: 1px solid #ddd;">${environment}</td>
</tr>
<tr>
<td style="padding: 8px; border: 1px solid #ddd;"><strong></strong></td>
<td style="padding: 8px; border: 1px solid #ddd;">
<#if deployStatus == "SUCCESS">
<span style="color: green;"> </span>
<#else>
<span style="color: red;"> </span>
</#if>
</td>
</tr>
<tr>
<td style="padding: 8px; border: 1px solid #ddd;"><strong></strong></td>
<td style="padding: 8px; border: 1px solid #ddd;">${version}</td>
</tr>
<tr>
<td style="padding: 8px; border: 1px solid #ddd;"><strong></strong></td>
<td style="padding: 8px; border: 1px solid #ddd;">${deployTime}</td>
</tr>
<#if deployUrl??>
<tr>
<td style="padding: 8px; border: 1px solid #ddd;"><strong>访</strong></td>
<td style="padding: 8px; border: 1px solid #ddd;"><a href="${deployUrl}">${deployUrl}</a></td>
</tr>
</#if>
</table>
<#if deployStatus == "SUCCESS">
<p style="color: green; font-weight: bold;">🎉 线</p>
<#else>
<p style="color: red; font-weight: bold;"> </p>
</#if>
<hr>
<p style="color: #666; font-size: 12px;">Deploy Ease Platform </p>', 1);

View File

@ -926,11 +926,11 @@ CREATE TABLE sys_notification_channel
name VARCHAR(100) NOT NULL COMMENT '渠道名称(如:研发部企业微信群)', name VARCHAR(100) NOT NULL COMMENT '渠道名称(如:研发部企业微信群)',
channel_type VARCHAR(50) NOT NULL COMMENT '渠道类型WEWORK, FEISHU, DINGTALK, SMS, EMAIL, SLACK', channel_type VARCHAR(50) NOT NULL COMMENT '渠道类型WEWORK, FEISHU, DINGTALK, SMS, EMAIL, SLACK',
config JSON NOT NULL COMMENT '渠道配置JSON格式不同渠道存储不同字段', 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 '描述说明', description VARCHAR(500) NULL COMMENT '描述说明',
INDEX idx_channel_type (channel_type), INDEX idx_channel_type (channel_type),
INDEX idx_status (status), INDEX idx_enabled (enabled),
INDEX idx_deleted (deleted) INDEX idx_deleted (deleted)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='通知渠道配置表'; ) 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) 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='部署记录表'; ) 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='通知模板表';