From ccaa6a2774ab0de7dcff2e3d3231613a96efefc0 Mon Sep 17 00:00:00 2001 From: fit2cloud-chenyw Date: Fri, 26 Feb 2021 11:40:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=81=A2=E5=A4=8D=E8=AF=AF=E5=88=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/dataease/base/domain/ChartGroup.java | 23 + .../base/domain/ChartGroupExample.java | 670 ++++++++++++++++++ .../base/mapper/ChartGroupMapper.java | 30 + .../dataease/base/mapper/ChartGroupMapper.xml | 243 +++++++ .../controller/chart/ChartController.java | 26 + .../chart/ChartGroupController.java | 32 + .../request/chart/ChartGroupRequest.java | 11 + .../io/dataease/dto/chart/ChartGroupDTO.java | 13 + .../notice/controller/NoticeController.java | 36 + .../controller/request/MessageRequest.java | 11 + .../java/io/dataease/notice/domain/Mail.java | 18 + .../io/dataease/notice/domain/MailInfo.java | 15 + .../dataease/notice/domain/MessageDetail.java | 21 + .../notice/domain/MessageSettingDetail.java | 13 + .../io/dataease/notice/domain/UserDetail.java | 9 + .../dataease/notice/message/LinkMessage.java | 45 ++ .../io/dataease/notice/message/Message.java | 5 + .../dataease/notice/message/TextMessage.java | 64 ++ .../notice/sender/AbstractNoticeSender.java | 150 ++++ .../dataease/notice/sender/NoticeModel.java | 51 ++ .../dataease/notice/sender/NoticeSender.java | 9 + .../notice/sender/impl/MailNoticeSender.java | 50 ++ .../notice/sender/impl/WeComNoticeSender.java | 39 + .../dataease/notice/service/MailService.java | 79 +++ .../notice/service/NoticeSendService.java | 63 ++ .../notice/service/NoticeService.java | 160 +++++ .../io/dataease/notice/util/SendResult.java | 47 ++ .../dataease/notice/util/WxChatbotClient.java | 50 ++ .../service/chart/ChartGroupService.java | 111 +++ .../resources/db/migration/V10__chart.sql | 12 + frontend/src/assets/favicon.ico | Bin 0 -> 109986 bytes .../src/business/components/chart/Chart.vue | 45 ++ .../business/components/chart/data/AddDB.vue | 148 ++++ .../components/chart/data/ChartHome.vue | 25 + .../components/chart/data/TabDataPreview.vue | 62 ++ .../components/chart/data/ViewTable.vue | 122 ++++ .../business/components/chart/group/Group.vue | 540 ++++++++++++++ .../src/business/components/chart/router.js | 18 + 38 files changed, 3066 insertions(+) create mode 100644 backend/src/main/java/io/dataease/base/domain/ChartGroup.java create mode 100644 backend/src/main/java/io/dataease/base/domain/ChartGroupExample.java create mode 100644 backend/src/main/java/io/dataease/base/mapper/ChartGroupMapper.java create mode 100644 backend/src/main/java/io/dataease/base/mapper/ChartGroupMapper.xml create mode 100644 backend/src/main/java/io/dataease/controller/chart/ChartController.java create mode 100644 backend/src/main/java/io/dataease/controller/chart/ChartGroupController.java create mode 100644 backend/src/main/java/io/dataease/controller/request/chart/ChartGroupRequest.java create mode 100644 backend/src/main/java/io/dataease/dto/chart/ChartGroupDTO.java create mode 100644 backend/src/main/java/io/dataease/notice/controller/NoticeController.java create mode 100644 backend/src/main/java/io/dataease/notice/controller/request/MessageRequest.java create mode 100644 backend/src/main/java/io/dataease/notice/domain/Mail.java create mode 100644 backend/src/main/java/io/dataease/notice/domain/MailInfo.java create mode 100644 backend/src/main/java/io/dataease/notice/domain/MessageDetail.java create mode 100644 backend/src/main/java/io/dataease/notice/domain/MessageSettingDetail.java create mode 100644 backend/src/main/java/io/dataease/notice/domain/UserDetail.java create mode 100644 backend/src/main/java/io/dataease/notice/message/LinkMessage.java create mode 100644 backend/src/main/java/io/dataease/notice/message/Message.java create mode 100644 backend/src/main/java/io/dataease/notice/message/TextMessage.java create mode 100644 backend/src/main/java/io/dataease/notice/sender/AbstractNoticeSender.java create mode 100644 backend/src/main/java/io/dataease/notice/sender/NoticeModel.java create mode 100644 backend/src/main/java/io/dataease/notice/sender/NoticeSender.java create mode 100644 backend/src/main/java/io/dataease/notice/sender/impl/MailNoticeSender.java create mode 100644 backend/src/main/java/io/dataease/notice/sender/impl/WeComNoticeSender.java create mode 100644 backend/src/main/java/io/dataease/notice/service/MailService.java create mode 100644 backend/src/main/java/io/dataease/notice/service/NoticeSendService.java create mode 100644 backend/src/main/java/io/dataease/notice/service/NoticeService.java create mode 100644 backend/src/main/java/io/dataease/notice/util/SendResult.java create mode 100644 backend/src/main/java/io/dataease/notice/util/WxChatbotClient.java create mode 100644 backend/src/main/java/io/dataease/service/chart/ChartGroupService.java create mode 100644 backend/src/main/resources/db/migration/V10__chart.sql create mode 100644 frontend/src/assets/favicon.ico create mode 100644 frontend/src/business/components/chart/Chart.vue create mode 100644 frontend/src/business/components/chart/data/AddDB.vue create mode 100644 frontend/src/business/components/chart/data/ChartHome.vue create mode 100644 frontend/src/business/components/chart/data/TabDataPreview.vue create mode 100644 frontend/src/business/components/chart/data/ViewTable.vue create mode 100644 frontend/src/business/components/chart/group/Group.vue create mode 100644 frontend/src/business/components/chart/router.js diff --git a/backend/src/main/java/io/dataease/base/domain/ChartGroup.java b/backend/src/main/java/io/dataease/base/domain/ChartGroup.java new file mode 100644 index 0000000000..4d2128a4b6 --- /dev/null +++ b/backend/src/main/java/io/dataease/base/domain/ChartGroup.java @@ -0,0 +1,23 @@ +package io.dataease.base.domain; + +import java.io.Serializable; +import lombok.Data; + +@Data +public class ChartGroup implements Serializable { + private String id; + + private String name; + + private String pid; + + private Integer level; + + private String type; + + private String createBy; + + private Long createTime; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/backend/src/main/java/io/dataease/base/domain/ChartGroupExample.java b/backend/src/main/java/io/dataease/base/domain/ChartGroupExample.java new file mode 100644 index 0000000000..c3bf4973e6 --- /dev/null +++ b/backend/src/main/java/io/dataease/base/domain/ChartGroupExample.java @@ -0,0 +1,670 @@ +package io.dataease.base.domain; + +import java.util.ArrayList; +import java.util.List; + +public class ChartGroupExample { + protected String orderByClause; + + protected boolean distinct; + + protected List oredCriteria; + + public ChartGroupExample() { + oredCriteria = new ArrayList(); + } + + public void setOrderByClause(String orderByClause) { + this.orderByClause = orderByClause; + } + + public String getOrderByClause() { + return orderByClause; + } + + public void setDistinct(boolean distinct) { + this.distinct = distinct; + } + + public boolean isDistinct() { + return distinct; + } + + public List getOredCriteria() { + return oredCriteria; + } + + public void or(Criteria criteria) { + oredCriteria.add(criteria); + } + + public Criteria or() { + Criteria criteria = createCriteriaInternal(); + oredCriteria.add(criteria); + return criteria; + } + + public Criteria createCriteria() { + Criteria criteria = createCriteriaInternal(); + if (oredCriteria.size() == 0) { + oredCriteria.add(criteria); + } + return criteria; + } + + protected Criteria createCriteriaInternal() { + Criteria criteria = new Criteria(); + return criteria; + } + + public void clear() { + oredCriteria.clear(); + orderByClause = null; + distinct = false; + } + + protected abstract static class GeneratedCriteria { + protected List criteria; + + protected GeneratedCriteria() { + super(); + criteria = new ArrayList(); + } + + public boolean isValid() { + return criteria.size() > 0; + } + + public List getAllCriteria() { + return criteria; + } + + public List getCriteria() { + return criteria; + } + + protected void addCriterion(String condition) { + if (condition == null) { + throw new RuntimeException("Value for condition cannot be null"); + } + criteria.add(new Criterion(condition)); + } + + protected void addCriterion(String condition, Object value, String property) { + if (value == null) { + throw new RuntimeException("Value for " + property + " cannot be null"); + } + criteria.add(new Criterion(condition, value)); + } + + protected void addCriterion(String condition, Object value1, Object value2, String property) { + if (value1 == null || value2 == null) { + throw new RuntimeException("Between values for " + property + " cannot be null"); + } + criteria.add(new Criterion(condition, value1, value2)); + } + + public Criteria andIdIsNull() { + addCriterion("id is null"); + return (Criteria) this; + } + + public Criteria andIdIsNotNull() { + addCriterion("id is not null"); + return (Criteria) this; + } + + public Criteria andIdEqualTo(String value) { + addCriterion("id =", value, "id"); + return (Criteria) this; + } + + public Criteria andIdNotEqualTo(String value) { + addCriterion("id <>", value, "id"); + return (Criteria) this; + } + + public Criteria andIdGreaterThan(String value) { + addCriterion("id >", value, "id"); + return (Criteria) this; + } + + public Criteria andIdGreaterThanOrEqualTo(String value) { + addCriterion("id >=", value, "id"); + return (Criteria) this; + } + + public Criteria andIdLessThan(String value) { + addCriterion("id <", value, "id"); + return (Criteria) this; + } + + public Criteria andIdLessThanOrEqualTo(String value) { + addCriterion("id <=", value, "id"); + return (Criteria) this; + } + + public Criteria andIdLike(String value) { + addCriterion("id like", value, "id"); + return (Criteria) this; + } + + public Criteria andIdNotLike(String value) { + addCriterion("id not like", value, "id"); + return (Criteria) this; + } + + public Criteria andIdIn(List values) { + addCriterion("id in", values, "id"); + return (Criteria) this; + } + + public Criteria andIdNotIn(List values) { + addCriterion("id not in", values, "id"); + return (Criteria) this; + } + + public Criteria andIdBetween(String value1, String value2) { + addCriterion("id between", value1, value2, "id"); + return (Criteria) this; + } + + public Criteria andIdNotBetween(String value1, String value2) { + addCriterion("id not between", value1, value2, "id"); + return (Criteria) this; + } + + public Criteria andNameIsNull() { + addCriterion("`name` is null"); + return (Criteria) this; + } + + public Criteria andNameIsNotNull() { + addCriterion("`name` is not null"); + return (Criteria) this; + } + + public Criteria andNameEqualTo(String value) { + addCriterion("`name` =", value, "name"); + return (Criteria) this; + } + + public Criteria andNameNotEqualTo(String value) { + addCriterion("`name` <>", value, "name"); + return (Criteria) this; + } + + public Criteria andNameGreaterThan(String value) { + addCriterion("`name` >", value, "name"); + return (Criteria) this; + } + + public Criteria andNameGreaterThanOrEqualTo(String value) { + addCriterion("`name` >=", value, "name"); + return (Criteria) this; + } + + public Criteria andNameLessThan(String value) { + addCriterion("`name` <", value, "name"); + return (Criteria) this; + } + + public Criteria andNameLessThanOrEqualTo(String value) { + addCriterion("`name` <=", value, "name"); + return (Criteria) this; + } + + public Criteria andNameLike(String value) { + addCriterion("`name` like", value, "name"); + return (Criteria) this; + } + + public Criteria andNameNotLike(String value) { + addCriterion("`name` not like", value, "name"); + return (Criteria) this; + } + + public Criteria andNameIn(List values) { + addCriterion("`name` in", values, "name"); + return (Criteria) this; + } + + public Criteria andNameNotIn(List values) { + addCriterion("`name` not in", values, "name"); + return (Criteria) this; + } + + public Criteria andNameBetween(String value1, String value2) { + addCriterion("`name` between", value1, value2, "name"); + return (Criteria) this; + } + + public Criteria andNameNotBetween(String value1, String value2) { + addCriterion("`name` not between", value1, value2, "name"); + return (Criteria) this; + } + + public Criteria andPidIsNull() { + addCriterion("pid is null"); + return (Criteria) this; + } + + public Criteria andPidIsNotNull() { + addCriterion("pid is not null"); + return (Criteria) this; + } + + public Criteria andPidEqualTo(String value) { + addCriterion("pid =", value, "pid"); + return (Criteria) this; + } + + public Criteria andPidNotEqualTo(String value) { + addCriterion("pid <>", value, "pid"); + return (Criteria) this; + } + + public Criteria andPidGreaterThan(String value) { + addCriterion("pid >", value, "pid"); + return (Criteria) this; + } + + public Criteria andPidGreaterThanOrEqualTo(String value) { + addCriterion("pid >=", value, "pid"); + return (Criteria) this; + } + + public Criteria andPidLessThan(String value) { + addCriterion("pid <", value, "pid"); + return (Criteria) this; + } + + public Criteria andPidLessThanOrEqualTo(String value) { + addCriterion("pid <=", value, "pid"); + return (Criteria) this; + } + + public Criteria andPidLike(String value) { + addCriterion("pid like", value, "pid"); + return (Criteria) this; + } + + public Criteria andPidNotLike(String value) { + addCriterion("pid not like", value, "pid"); + return (Criteria) this; + } + + public Criteria andPidIn(List values) { + addCriterion("pid in", values, "pid"); + return (Criteria) this; + } + + public Criteria andPidNotIn(List values) { + addCriterion("pid not in", values, "pid"); + return (Criteria) this; + } + + public Criteria andPidBetween(String value1, String value2) { + addCriterion("pid between", value1, value2, "pid"); + return (Criteria) this; + } + + public Criteria andPidNotBetween(String value1, String value2) { + addCriterion("pid not between", value1, value2, "pid"); + return (Criteria) this; + } + + public Criteria andLevelIsNull() { + addCriterion("`level` is null"); + return (Criteria) this; + } + + public Criteria andLevelIsNotNull() { + addCriterion("`level` is not null"); + return (Criteria) this; + } + + public Criteria andLevelEqualTo(Integer value) { + addCriterion("`level` =", value, "level"); + return (Criteria) this; + } + + public Criteria andLevelNotEqualTo(Integer value) { + addCriterion("`level` <>", value, "level"); + return (Criteria) this; + } + + public Criteria andLevelGreaterThan(Integer value) { + addCriterion("`level` >", value, "level"); + return (Criteria) this; + } + + public Criteria andLevelGreaterThanOrEqualTo(Integer value) { + addCriterion("`level` >=", value, "level"); + return (Criteria) this; + } + + public Criteria andLevelLessThan(Integer value) { + addCriterion("`level` <", value, "level"); + return (Criteria) this; + } + + public Criteria andLevelLessThanOrEqualTo(Integer value) { + addCriterion("`level` <=", value, "level"); + return (Criteria) this; + } + + public Criteria andLevelIn(List values) { + addCriterion("`level` in", values, "level"); + return (Criteria) this; + } + + public Criteria andLevelNotIn(List values) { + addCriterion("`level` not in", values, "level"); + return (Criteria) this; + } + + public Criteria andLevelBetween(Integer value1, Integer value2) { + addCriterion("`level` between", value1, value2, "level"); + return (Criteria) this; + } + + public Criteria andLevelNotBetween(Integer value1, Integer value2) { + addCriterion("`level` not between", value1, value2, "level"); + return (Criteria) this; + } + + public Criteria andTypeIsNull() { + addCriterion("`type` is null"); + return (Criteria) this; + } + + public Criteria andTypeIsNotNull() { + addCriterion("`type` is not null"); + return (Criteria) this; + } + + public Criteria andTypeEqualTo(String value) { + addCriterion("`type` =", value, "type"); + return (Criteria) this; + } + + public Criteria andTypeNotEqualTo(String value) { + addCriterion("`type` <>", value, "type"); + return (Criteria) this; + } + + public Criteria andTypeGreaterThan(String value) { + addCriterion("`type` >", value, "type"); + return (Criteria) this; + } + + public Criteria andTypeGreaterThanOrEqualTo(String value) { + addCriterion("`type` >=", value, "type"); + return (Criteria) this; + } + + public Criteria andTypeLessThan(String value) { + addCriterion("`type` <", value, "type"); + return (Criteria) this; + } + + public Criteria andTypeLessThanOrEqualTo(String value) { + addCriterion("`type` <=", value, "type"); + return (Criteria) this; + } + + public Criteria andTypeLike(String value) { + addCriterion("`type` like", value, "type"); + return (Criteria) this; + } + + public Criteria andTypeNotLike(String value) { + addCriterion("`type` not like", value, "type"); + return (Criteria) this; + } + + public Criteria andTypeIn(List values) { + addCriterion("`type` in", values, "type"); + return (Criteria) this; + } + + public Criteria andTypeNotIn(List values) { + addCriterion("`type` not in", values, "type"); + return (Criteria) this; + } + + public Criteria andTypeBetween(String value1, String value2) { + addCriterion("`type` between", value1, value2, "type"); + return (Criteria) this; + } + + public Criteria andTypeNotBetween(String value1, String value2) { + addCriterion("`type` not between", value1, value2, "type"); + return (Criteria) this; + } + + public Criteria andCreateByIsNull() { + addCriterion("create_by is null"); + return (Criteria) this; + } + + public Criteria andCreateByIsNotNull() { + addCriterion("create_by is not null"); + return (Criteria) this; + } + + public Criteria andCreateByEqualTo(String value) { + addCriterion("create_by =", value, "createBy"); + return (Criteria) this; + } + + public Criteria andCreateByNotEqualTo(String value) { + addCriterion("create_by <>", value, "createBy"); + return (Criteria) this; + } + + public Criteria andCreateByGreaterThan(String value) { + addCriterion("create_by >", value, "createBy"); + return (Criteria) this; + } + + public Criteria andCreateByGreaterThanOrEqualTo(String value) { + addCriterion("create_by >=", value, "createBy"); + return (Criteria) this; + } + + public Criteria andCreateByLessThan(String value) { + addCriterion("create_by <", value, "createBy"); + return (Criteria) this; + } + + public Criteria andCreateByLessThanOrEqualTo(String value) { + addCriterion("create_by <=", value, "createBy"); + return (Criteria) this; + } + + public Criteria andCreateByLike(String value) { + addCriterion("create_by like", value, "createBy"); + return (Criteria) this; + } + + public Criteria andCreateByNotLike(String value) { + addCriterion("create_by not like", value, "createBy"); + return (Criteria) this; + } + + public Criteria andCreateByIn(List values) { + addCriterion("create_by in", values, "createBy"); + return (Criteria) this; + } + + public Criteria andCreateByNotIn(List values) { + addCriterion("create_by not in", values, "createBy"); + return (Criteria) this; + } + + public Criteria andCreateByBetween(String value1, String value2) { + addCriterion("create_by between", value1, value2, "createBy"); + return (Criteria) this; + } + + public Criteria andCreateByNotBetween(String value1, String value2) { + addCriterion("create_by not between", value1, value2, "createBy"); + return (Criteria) this; + } + + public Criteria andCreateTimeIsNull() { + addCriterion("create_time is null"); + return (Criteria) this; + } + + public Criteria andCreateTimeIsNotNull() { + addCriterion("create_time is not null"); + return (Criteria) this; + } + + public Criteria andCreateTimeEqualTo(Long value) { + addCriterion("create_time =", value, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeNotEqualTo(Long value) { + addCriterion("create_time <>", value, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeGreaterThan(Long value) { + addCriterion("create_time >", value, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeGreaterThanOrEqualTo(Long value) { + addCriterion("create_time >=", value, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeLessThan(Long value) { + addCriterion("create_time <", value, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeLessThanOrEqualTo(Long value) { + addCriterion("create_time <=", value, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeIn(List values) { + addCriterion("create_time in", values, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeNotIn(List values) { + addCriterion("create_time not in", values, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeBetween(Long value1, Long value2) { + addCriterion("create_time between", value1, value2, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeNotBetween(Long value1, Long value2) { + addCriterion("create_time not between", value1, value2, "createTime"); + return (Criteria) this; + } + } + + public static class Criteria extends GeneratedCriteria { + + protected Criteria() { + super(); + } + } + + public static class Criterion { + private String condition; + + private Object value; + + private Object secondValue; + + private boolean noValue; + + private boolean singleValue; + + private boolean betweenValue; + + private boolean listValue; + + private String typeHandler; + + public String getCondition() { + return condition; + } + + public Object getValue() { + return value; + } + + public Object getSecondValue() { + return secondValue; + } + + public boolean isNoValue() { + return noValue; + } + + public boolean isSingleValue() { + return singleValue; + } + + public boolean isBetweenValue() { + return betweenValue; + } + + public boolean isListValue() { + return listValue; + } + + public String getTypeHandler() { + return typeHandler; + } + + protected Criterion(String condition) { + super(); + this.condition = condition; + this.typeHandler = null; + this.noValue = true; + } + + protected Criterion(String condition, Object value, String typeHandler) { + super(); + this.condition = condition; + this.value = value; + this.typeHandler = typeHandler; + if (value instanceof List) { + this.listValue = true; + } else { + this.singleValue = true; + } + } + + protected Criterion(String condition, Object value) { + this(condition, value, null); + } + + protected Criterion(String condition, Object value, Object secondValue, String typeHandler) { + super(); + this.condition = condition; + this.value = value; + this.secondValue = secondValue; + this.typeHandler = typeHandler; + this.betweenValue = true; + } + + protected Criterion(String condition, Object value, Object secondValue) { + this(condition, value, secondValue, null); + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/io/dataease/base/mapper/ChartGroupMapper.java b/backend/src/main/java/io/dataease/base/mapper/ChartGroupMapper.java new file mode 100644 index 0000000000..92e6264833 --- /dev/null +++ b/backend/src/main/java/io/dataease/base/mapper/ChartGroupMapper.java @@ -0,0 +1,30 @@ +package io.dataease.base.mapper; + +import io.dataease.base.domain.ChartGroup; +import io.dataease.base.domain.ChartGroupExample; +import java.util.List; +import org.apache.ibatis.annotations.Param; + +public interface ChartGroupMapper { + long countByExample(ChartGroupExample example); + + int deleteByExample(ChartGroupExample example); + + int deleteByPrimaryKey(String id); + + int insert(ChartGroup record); + + int insertSelective(ChartGroup record); + + List selectByExample(ChartGroupExample example); + + ChartGroup selectByPrimaryKey(String id); + + int updateByExampleSelective(@Param("record") ChartGroup record, @Param("example") ChartGroupExample example); + + int updateByExample(@Param("record") ChartGroup record, @Param("example") ChartGroupExample example); + + int updateByPrimaryKeySelective(ChartGroup record); + + int updateByPrimaryKey(ChartGroup record); +} \ No newline at end of file diff --git a/backend/src/main/java/io/dataease/base/mapper/ChartGroupMapper.xml b/backend/src/main/java/io/dataease/base/mapper/ChartGroupMapper.xml new file mode 100644 index 0000000000..58f37d6da4 --- /dev/null +++ b/backend/src/main/java/io/dataease/base/mapper/ChartGroupMapper.xml @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + + + + + + + + and ${criterion.condition} + + + and ${criterion.condition} #{criterion.value} + + + and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} + + + and ${criterion.condition} + + #{listItem} + + + + + + + + + + + + + + + + + + and ${criterion.condition} + + + and ${criterion.condition} #{criterion.value} + + + and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} + + + and ${criterion.condition} + + #{listItem} + + + + + + + + + + + id, `name`, pid, `level`, `type`, create_by, create_time + + + + + delete from chart_group + where id = #{id,jdbcType=VARCHAR} + + + delete from chart_group + + + + + + insert into chart_group (id, `name`, pid, + `level`, `type`, create_by, + create_time) + values (#{id,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{pid,jdbcType=VARCHAR}, + #{level,jdbcType=INTEGER}, #{type,jdbcType=VARCHAR}, #{createBy,jdbcType=VARCHAR}, + #{createTime,jdbcType=BIGINT}) + + + insert into chart_group + + + id, + + + `name`, + + + pid, + + + `level`, + + + `type`, + + + create_by, + + + create_time, + + + + + #{id,jdbcType=VARCHAR}, + + + #{name,jdbcType=VARCHAR}, + + + #{pid,jdbcType=VARCHAR}, + + + #{level,jdbcType=INTEGER}, + + + #{type,jdbcType=VARCHAR}, + + + #{createBy,jdbcType=VARCHAR}, + + + #{createTime,jdbcType=BIGINT}, + + + + + + update chart_group + + + id = #{record.id,jdbcType=VARCHAR}, + + + `name` = #{record.name,jdbcType=VARCHAR}, + + + pid = #{record.pid,jdbcType=VARCHAR}, + + + `level` = #{record.level,jdbcType=INTEGER}, + + + `type` = #{record.type,jdbcType=VARCHAR}, + + + create_by = #{record.createBy,jdbcType=VARCHAR}, + + + create_time = #{record.createTime,jdbcType=BIGINT}, + + + + + + + + update chart_group + set id = #{record.id,jdbcType=VARCHAR}, + `name` = #{record.name,jdbcType=VARCHAR}, + pid = #{record.pid,jdbcType=VARCHAR}, + `level` = #{record.level,jdbcType=INTEGER}, + `type` = #{record.type,jdbcType=VARCHAR}, + create_by = #{record.createBy,jdbcType=VARCHAR}, + create_time = #{record.createTime,jdbcType=BIGINT} + + + + + + update chart_group + + + `name` = #{name,jdbcType=VARCHAR}, + + + pid = #{pid,jdbcType=VARCHAR}, + + + `level` = #{level,jdbcType=INTEGER}, + + + `type` = #{type,jdbcType=VARCHAR}, + + + create_by = #{createBy,jdbcType=VARCHAR}, + + + create_time = #{createTime,jdbcType=BIGINT}, + + + where id = #{id,jdbcType=VARCHAR} + + + update chart_group + set `name` = #{name,jdbcType=VARCHAR}, + pid = #{pid,jdbcType=VARCHAR}, + `level` = #{level,jdbcType=INTEGER}, + `type` = #{type,jdbcType=VARCHAR}, + create_by = #{createBy,jdbcType=VARCHAR}, + create_time = #{createTime,jdbcType=BIGINT} + where id = #{id,jdbcType=VARCHAR} + + \ No newline at end of file diff --git a/backend/src/main/java/io/dataease/controller/chart/ChartController.java b/backend/src/main/java/io/dataease/controller/chart/ChartController.java new file mode 100644 index 0000000000..8c752f76dd --- /dev/null +++ b/backend/src/main/java/io/dataease/controller/chart/ChartController.java @@ -0,0 +1,26 @@ +package io.dataease.controller.chart; + +import com.alibaba.fastjson.JSON; +import io.dataease.base.domain.DatasetTable; +import io.dataease.controller.request.dataset.DataSetTableRequest; +import io.dataease.datasource.dto.TableFiled; +import io.dataease.service.dataset.DataSetTableService; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("chart/table") +public class ChartController { + + + + @PostMapping("list") + public List list(@RequestBody DataSetTableRequest dataSetTableRequest) { + return new ArrayList<>(); + } + +} diff --git a/backend/src/main/java/io/dataease/controller/chart/ChartGroupController.java b/backend/src/main/java/io/dataease/controller/chart/ChartGroupController.java new file mode 100644 index 0000000000..2c642886c5 --- /dev/null +++ b/backend/src/main/java/io/dataease/controller/chart/ChartGroupController.java @@ -0,0 +1,32 @@ +package io.dataease.controller.chart; + +import io.dataease.base.domain.ChartGroup; +import io.dataease.controller.request.chart.ChartGroupRequest; +import io.dataease.dto.chart.ChartGroupDTO; +import io.dataease.service.chart.ChartGroupService; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +@RestController +@RequestMapping("chart/group") +public class ChartGroupController { + @Resource + private ChartGroupService chartGroupService; + + @PostMapping("/save") + public ChartGroupDTO save(@RequestBody ChartGroup ChartGroup) { + return chartGroupService.save(ChartGroup); + } + + @PostMapping("/tree") + public List tree(@RequestBody ChartGroupRequest ChartGroup) { + return chartGroupService.tree(ChartGroup); + } + + @PostMapping("/delete/{id}") + public void tree(@PathVariable String id) { + chartGroupService.delete(id); + } +} diff --git a/backend/src/main/java/io/dataease/controller/request/chart/ChartGroupRequest.java b/backend/src/main/java/io/dataease/controller/request/chart/ChartGroupRequest.java new file mode 100644 index 0000000000..27b0d12b9a --- /dev/null +++ b/backend/src/main/java/io/dataease/controller/request/chart/ChartGroupRequest.java @@ -0,0 +1,11 @@ +package io.dataease.controller.request.chart; + +import io.dataease.base.domain.ChartGroup; +import io.dataease.base.domain.DatasetGroup; +import lombok.Data; + + +@Data +public class ChartGroupRequest extends ChartGroup { + private String sort; +} diff --git a/backend/src/main/java/io/dataease/dto/chart/ChartGroupDTO.java b/backend/src/main/java/io/dataease/dto/chart/ChartGroupDTO.java new file mode 100644 index 0000000000..1542759994 --- /dev/null +++ b/backend/src/main/java/io/dataease/dto/chart/ChartGroupDTO.java @@ -0,0 +1,13 @@ +package io.dataease.dto.chart; + +import io.dataease.base.domain.DatasetGroup; +import lombok.Data; + +import java.util.List; + + +@Data +public class ChartGroupDTO extends DatasetGroup { + private String label; + private List children; +} diff --git a/backend/src/main/java/io/dataease/notice/controller/NoticeController.java b/backend/src/main/java/io/dataease/notice/controller/NoticeController.java new file mode 100644 index 0000000000..05ab59215c --- /dev/null +++ b/backend/src/main/java/io/dataease/notice/controller/NoticeController.java @@ -0,0 +1,36 @@ +package io.dataease.notice.controller; + +import io.dataease.notice.domain.MessageDetail; +import io.dataease.notice.service.NoticeService; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +@RestController +@RequestMapping("notice") +public class NoticeController { + @Resource + private NoticeService noticeService; + + @PostMapping("save/message/task") + public void saveMessage(@RequestBody MessageDetail messageDetail) { + noticeService.saveMessageTask(messageDetail); + } + + @GetMapping("/search/message/type/{type}") + public List searchMessage(@PathVariable String type) { + return noticeService.searchMessageByType(type); + } + + @GetMapping("/search/message/{testId}") + public List searchMessageSchedule(@PathVariable String testId) { + return noticeService.searchMessageByTestId(testId); + } + + @GetMapping("/delete/message/{identification}") + public int deleteMessage(@PathVariable String identification) { + return noticeService.delMessage(identification); + } +} + diff --git a/backend/src/main/java/io/dataease/notice/controller/request/MessageRequest.java b/backend/src/main/java/io/dataease/notice/controller/request/MessageRequest.java new file mode 100644 index 0000000000..b6642a8657 --- /dev/null +++ b/backend/src/main/java/io/dataease/notice/controller/request/MessageRequest.java @@ -0,0 +1,11 @@ +package io.dataease.notice.controller.request; + +import io.dataease.notice.domain.MessageDetail; +import lombok.Data; + +import java.util.List; + +@Data +public class MessageRequest { + private List messageDetail; +} diff --git a/backend/src/main/java/io/dataease/notice/domain/Mail.java b/backend/src/main/java/io/dataease/notice/domain/Mail.java new file mode 100644 index 0000000000..309bbe1368 --- /dev/null +++ b/backend/src/main/java/io/dataease/notice/domain/Mail.java @@ -0,0 +1,18 @@ +package io.dataease.notice.domain; + +import lombok.Data; + +@Data +public class Mail { + // 发送给谁 + private String to; + + // 发送主题 + private String subject; + + // 发送内容 + private String content; + + // 附件地址 + private String filePath; +} diff --git a/backend/src/main/java/io/dataease/notice/domain/MailInfo.java b/backend/src/main/java/io/dataease/notice/domain/MailInfo.java new file mode 100644 index 0000000000..d2f942a125 --- /dev/null +++ b/backend/src/main/java/io/dataease/notice/domain/MailInfo.java @@ -0,0 +1,15 @@ +package io.dataease.notice.domain; + +import lombok.Data; + +@Data +public class MailInfo { + private String host; + private String port; + private String account; + private String password; + private String ssl; + private String tls; + private String recipient; + +} diff --git a/backend/src/main/java/io/dataease/notice/domain/MessageDetail.java b/backend/src/main/java/io/dataease/notice/domain/MessageDetail.java new file mode 100644 index 0000000000..96d0a7d572 --- /dev/null +++ b/backend/src/main/java/io/dataease/notice/domain/MessageDetail.java @@ -0,0 +1,21 @@ +package io.dataease.notice.domain; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class MessageDetail { + private List userIds = new ArrayList<>(); + private String event; + private String taskType; + private String webhook; + private String type; + private String identification; + private String organizationId; + private Boolean isSet; + private String testId; + private Long createTime; + private String template; +} diff --git a/backend/src/main/java/io/dataease/notice/domain/MessageSettingDetail.java b/backend/src/main/java/io/dataease/notice/domain/MessageSettingDetail.java new file mode 100644 index 0000000000..07ef6ad484 --- /dev/null +++ b/backend/src/main/java/io/dataease/notice/domain/MessageSettingDetail.java @@ -0,0 +1,13 @@ +package io.dataease.notice.domain; + +import lombok.Data; + +import java.util.List; + +@Data +public class MessageSettingDetail { + private List jenkinsTask; + private List testCasePlanTask; + private List reviewTask; + private List defectTask; +} diff --git a/backend/src/main/java/io/dataease/notice/domain/UserDetail.java b/backend/src/main/java/io/dataease/notice/domain/UserDetail.java new file mode 100644 index 0000000000..91d080b042 --- /dev/null +++ b/backend/src/main/java/io/dataease/notice/domain/UserDetail.java @@ -0,0 +1,9 @@ +package io.dataease.notice.domain; + +import lombok.Data; + +@Data +public class UserDetail { + private String email; + private String phone; +} diff --git a/backend/src/main/java/io/dataease/notice/message/LinkMessage.java b/backend/src/main/java/io/dataease/notice/message/LinkMessage.java new file mode 100644 index 0000000000..b2712564c8 --- /dev/null +++ b/backend/src/main/java/io/dataease/notice/message/LinkMessage.java @@ -0,0 +1,45 @@ +package io.dataease.notice.message; + +import com.alibaba.fastjson.JSON; +import org.apache.commons.lang3.StringUtils; + +import java.util.HashMap; +import java.util.Map; + + +public class LinkMessage implements Message { + + private String title; + private String text; + private String picUrl; + private String messageUrl; + + public String toJsonString() { + Map items = new HashMap(); + items.put("msgtype", "link"); + + Map linkContent = new HashMap(); + if (StringUtils.isBlank(title)) { + throw new IllegalArgumentException("title should not be blank"); + } + linkContent.put("title", title); + + if (StringUtils.isBlank(messageUrl)) { + throw new IllegalArgumentException("messageUrl should not be blank"); + } + linkContent.put("messageUrl", messageUrl); + + if (StringUtils.isBlank(text)) { + throw new IllegalArgumentException("text should not be blank"); + } + linkContent.put("text", text); + + if (StringUtils.isNotBlank(picUrl)) { + linkContent.put("picUrl", picUrl); + } + + items.put("link", linkContent); + + return JSON.toJSONString(items); + } +} diff --git a/backend/src/main/java/io/dataease/notice/message/Message.java b/backend/src/main/java/io/dataease/notice/message/Message.java new file mode 100644 index 0000000000..5dbad90764 --- /dev/null +++ b/backend/src/main/java/io/dataease/notice/message/Message.java @@ -0,0 +1,5 @@ +package io.dataease.notice.message; + +public interface Message { + String toJsonString(); +} diff --git a/backend/src/main/java/io/dataease/notice/message/TextMessage.java b/backend/src/main/java/io/dataease/notice/message/TextMessage.java new file mode 100644 index 0000000000..91caa7e4d6 --- /dev/null +++ b/backend/src/main/java/io/dataease/notice/message/TextMessage.java @@ -0,0 +1,64 @@ +package io.dataease.notice.message; + +import com.alibaba.fastjson.JSON; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +public class TextMessage implements Message { + private String text; + private List mentionedMobileList; + private boolean isAtAll; + + public TextMessage(String text) { + this.text = text; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public boolean isAtAll() { + return isAtAll; + } + + public void setIsAtAll(boolean isAtAll) { + this.isAtAll = isAtAll; + } + + public List getMentionedMobileList() { + return mentionedMobileList; + } + + public void setMentionedMobileList(List mentionedMobileList) { + this.mentionedMobileList = mentionedMobileList; + } + + public String toJsonString() { + Map items = new HashMap(); + items.put("msgtype", "text"); + + Map textContent = new HashMap(); + if (StringUtils.isBlank(text)) { + throw new IllegalArgumentException("text should not be blank"); + } + textContent.put("content", text); + if (isAtAll) { + if (mentionedMobileList == null) mentionedMobileList = new ArrayList(); + mentionedMobileList.add("@all"); + } + if (mentionedMobileList != null && !mentionedMobileList.isEmpty()) { + textContent.put("mentioned_mobile_list", mentionedMobileList); + } + items.put("text", textContent); + return JSON.toJSONString(items); + } +} diff --git a/backend/src/main/java/io/dataease/notice/sender/AbstractNoticeSender.java b/backend/src/main/java/io/dataease/notice/sender/AbstractNoticeSender.java new file mode 100644 index 0000000000..099dc84ce4 --- /dev/null +++ b/backend/src/main/java/io/dataease/notice/sender/AbstractNoticeSender.java @@ -0,0 +1,150 @@ +package io.dataease.notice.sender; + +import io.dataease.commons.constants.NoticeConstants; +import io.dataease.commons.utils.LogUtil; +import io.dataease.notice.domain.MessageDetail; +import io.dataease.notice.domain.UserDetail; +import io.dataease.service.UserService; +import org.apache.commons.collections4.MapUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.RegExUtils; +import org.apache.commons.lang3.StringUtils; + +import javax.annotation.Resource; +import java.io.IOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public abstract class AbstractNoticeSender implements NoticeSender { + @Resource + private UserService userService; + + protected String getContext(MessageDetail messageDetail, NoticeModel noticeModel) { + // 如果配置了模版就直接使用模版 + if (StringUtils.isNotBlank(messageDetail.getTemplate())) { + return getContent(messageDetail.getTemplate(), noticeModel.getParamMap()); + } + // 处理 userIds 中包含的特殊值 + List realUserIds = getRealUserIds(messageDetail.getUserIds(), noticeModel.getRelatedUsers(), messageDetail.getEvent()); + messageDetail.setUserIds(realUserIds); + + // 处理 WeCom Ding context + String context = ""; + switch (messageDetail.getEvent()) { + case NoticeConstants.Event.CREATE: + case NoticeConstants.Event.UPDATE: + case NoticeConstants.Event.DELETE: + case NoticeConstants.Event.COMMENT: + context = noticeModel.getContext(); + break; + case NoticeConstants.Event.EXECUTE_FAILED: + context = noticeModel.getFailedContext(); + break; + case NoticeConstants.Event.EXECUTE_SUCCESSFUL: + context = noticeModel.getSuccessContext(); + break; + default: + break; + } + return context; + } + + protected String getHtmlContext(MessageDetail messageDetail, NoticeModel noticeModel) { + // 如果配置了模版就直接使用模版 + if (StringUtils.isNotBlank(messageDetail.getTemplate())) { + return getContent(messageDetail.getTemplate(), noticeModel.getParamMap()); + } + // 处理 userIds 中包含的特殊值 + List realUserIds = getRealUserIds(messageDetail.getUserIds(), noticeModel.getRelatedUsers(), messageDetail.getEvent()); + messageDetail.setUserIds(realUserIds); + + // 处理 mail context + String context = ""; + try { + switch (messageDetail.getEvent()) { + case NoticeConstants.Event.CREATE: + case NoticeConstants.Event.UPDATE: + case NoticeConstants.Event.DELETE: + case NoticeConstants.Event.COMMENT: + URL resource = this.getClass().getResource("/mail/" + noticeModel.getMailTemplate() + ".html"); + context = IOUtils.toString(resource, StandardCharsets.UTF_8); + break; + case NoticeConstants.Event.EXECUTE_FAILED: + URL resource1 = this.getClass().getResource("/mail/" + noticeModel.getFailedMailTemplate() + ".html"); + context = IOUtils.toString(resource1, StandardCharsets.UTF_8); + break; + case NoticeConstants.Event.EXECUTE_SUCCESSFUL: + URL resource2 = this.getClass().getResource("/mail/" + noticeModel.getSuccessMailTemplate() + ".html"); + context = IOUtils.toString(resource2, StandardCharsets.UTF_8); + break; + default: + break; + } + } catch (IOException e) { + LogUtil.error(e); + } + return getContent(context, noticeModel.getParamMap()); + } + + protected String getContent(String template, Map context) { + if (MapUtils.isNotEmpty(context)) { + for (String k : context.keySet()) { + if (context.get(k) != null) { + template = RegExUtils.replaceAll(template, "\\$\\{" + k + "}", context.get(k).toString()); + } else { + template = RegExUtils.replaceAll(template, "\\$\\{" + k + "}", "未设置"); + } + } + } + return template; + } + + protected List getUserPhones(List userIds) { + List list = userService.queryTypeByIds(userIds); + List phoneList = new ArrayList<>(); + list.forEach(u -> phoneList.add(u.getPhone())); + LogUtil.info("收件人地址: " + phoneList); + return phoneList.stream().distinct().collect(Collectors.toList()); + } + + protected List getUserEmails(List userIds) { + List list = userService.queryTypeByIds(userIds); + List phoneList = new ArrayList<>(); + list.forEach(u -> phoneList.add(u.getEmail())); + LogUtil.info("收件人地址: " + phoneList); + return phoneList.stream().distinct().collect(Collectors.toList()); + } + + private List getRealUserIds(List userIds, List relatedUsers, String event) { + List toUserIds = new ArrayList<>(); + for (String userId : userIds) { + switch (userId) { + case NoticeConstants.RelatedUser.EXECUTOR: + if (StringUtils.equals(NoticeConstants.Event.CREATE, event)) { + toUserIds.addAll(relatedUsers); + } + break; + case NoticeConstants.RelatedUser.FOUNDER: + if (StringUtils.equals(NoticeConstants.Event.UPDATE, event) + || StringUtils.equals(NoticeConstants.Event.DELETE, event)) { + toUserIds.addAll(relatedUsers); + } + break; + case NoticeConstants.RelatedUser.MAINTAINER: + if (StringUtils.equals(NoticeConstants.Event.COMMENT, event)) { + toUserIds.addAll(relatedUsers); + } + break; + default: + toUserIds.add(userId); + break; + } + } + + return toUserIds; + } +} diff --git a/backend/src/main/java/io/dataease/notice/sender/NoticeModel.java b/backend/src/main/java/io/dataease/notice/sender/NoticeModel.java new file mode 100644 index 0000000000..2dc6f7d47a --- /dev/null +++ b/backend/src/main/java/io/dataease/notice/sender/NoticeModel.java @@ -0,0 +1,51 @@ +package io.dataease.notice.sender; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +@Builder +public class NoticeModel { + /** + * 保存 测试id + */ + private String testId; + /** + * 保存状态 + */ + private String status; + /** + * Event + */ + private String event; + /** + * 消息主题 + */ + private String subject; + /** + * 消息内容 + */ + private String context; + private String successContext; + private String failedContext; + + /** + * html 消息模版 + */ + private String mailTemplate; + private String failedMailTemplate; + private String successMailTemplate; + + /** + * 保存特殊的用户 + */ + private List relatedUsers; + + /** + * 模版里的参数信息 + */ + private Map paramMap; +} diff --git a/backend/src/main/java/io/dataease/notice/sender/NoticeSender.java b/backend/src/main/java/io/dataease/notice/sender/NoticeSender.java new file mode 100644 index 0000000000..8958d71006 --- /dev/null +++ b/backend/src/main/java/io/dataease/notice/sender/NoticeSender.java @@ -0,0 +1,9 @@ +package io.dataease.notice.sender; + +import io.dataease.notice.domain.MessageDetail; +import org.springframework.scheduling.annotation.Async; + +public interface NoticeSender { + @Async + void send(MessageDetail messageDetail, NoticeModel noticeModel); +} diff --git a/backend/src/main/java/io/dataease/notice/sender/impl/MailNoticeSender.java b/backend/src/main/java/io/dataease/notice/sender/impl/MailNoticeSender.java new file mode 100644 index 0000000000..e82b56c66f --- /dev/null +++ b/backend/src/main/java/io/dataease/notice/sender/impl/MailNoticeSender.java @@ -0,0 +1,50 @@ +package io.dataease.notice.sender.impl; + + +import io.dataease.commons.utils.LogUtil; +import io.dataease.notice.domain.MessageDetail; +import io.dataease.notice.sender.AbstractNoticeSender; +import io.dataease.notice.sender.NoticeModel; +import io.dataease.notice.service.MailService; +import org.springframework.mail.javamail.JavaMailSenderImpl; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; +import java.util.List; + +@Component +public class MailNoticeSender extends AbstractNoticeSender { + @Resource + private MailService mailService; + + private void sendMail(MessageDetail messageDetail, String context, NoticeModel noticeModel) throws MessagingException { + LogUtil.info("发送邮件开始 "); + JavaMailSenderImpl javaMailSender = mailService.getMailSender(); + MimeMessage mimeMessage = javaMailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true); + helper.setFrom(javaMailSender.getUsername()); + LogUtil.info("发件人地址"+javaMailSender.getUsername()); + LogUtil.info("helper"+helper); + helper.setSubject("MeterSphere " + noticeModel.getSubject()); + List emails = super.getUserEmails(messageDetail.getUserIds()); + String[] users = emails.toArray(new String[0]); + LogUtil.info("收件人地址: " + emails); + helper.setText(context, true); + helper.setTo(users); + javaMailSender.send(mimeMessage); + } + + @Override + public void send(MessageDetail messageDetail, NoticeModel noticeModel) { + String context = super.getHtmlContext(messageDetail, noticeModel); + try { + sendMail(messageDetail, context, noticeModel); + LogUtil.info("发送邮件结束"); + } catch (Exception e) { + LogUtil.error(e); + } + } +} diff --git a/backend/src/main/java/io/dataease/notice/sender/impl/WeComNoticeSender.java b/backend/src/main/java/io/dataease/notice/sender/impl/WeComNoticeSender.java new file mode 100644 index 0000000000..1bd43b5664 --- /dev/null +++ b/backend/src/main/java/io/dataease/notice/sender/impl/WeComNoticeSender.java @@ -0,0 +1,39 @@ +package io.dataease.notice.sender.impl; + +import io.dataease.notice.sender.AbstractNoticeSender; +import io.dataease.commons.utils.LogUtil; +import io.dataease.notice.domain.MessageDetail; +import io.dataease.notice.message.TextMessage; +import io.dataease.notice.sender.NoticeModel; +import io.dataease.notice.util.WxChatbotClient; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.List; + +@Component +public class WeComNoticeSender extends AbstractNoticeSender { + + + public void sendWechatRobot(MessageDetail messageDetail, String context) { + List userIds = messageDetail.getUserIds(); + if (CollectionUtils.isEmpty(userIds)) { + return; + } + TextMessage message = new TextMessage(context); + List phoneLists = super.getUserPhones(userIds); + message.setMentionedMobileList(phoneLists); + try { + WxChatbotClient.send(messageDetail.getWebhook(), message); + } catch (IOException e) { + LogUtil.error(e.getMessage(), e); + } + } + + @Override + public void send(MessageDetail messageDetail, NoticeModel noticeModel) { + String context = super.getContext(messageDetail, noticeModel); + sendWechatRobot(messageDetail, context); + } +} diff --git a/backend/src/main/java/io/dataease/notice/service/MailService.java b/backend/src/main/java/io/dataease/notice/service/MailService.java new file mode 100644 index 0000000000..bd33b23f25 --- /dev/null +++ b/backend/src/main/java/io/dataease/notice/service/MailService.java @@ -0,0 +1,79 @@ +package io.dataease.notice.service; + +import io.dataease.base.domain.SystemParameter; +import io.dataease.commons.constants.ParamConstants; +import io.dataease.commons.utils.EncryptUtils; +import io.dataease.service.system.SystemParameterService; +import org.apache.commons.lang3.BooleanUtils; +import org.springframework.mail.javamail.JavaMailSenderImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Properties; + +@Service +@Transactional(propagation = Propagation.NOT_SUPPORTED) +public class MailService { + @Resource + private SystemParameterService systemParameterService; + + public JavaMailSenderImpl getMailSender() { + Properties props = new Properties(); + JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl(); + List paramList = systemParameterService.getParamList(ParamConstants.Classify.MAIL.getValue()); + javaMailSender.setDefaultEncoding("UTF-8"); + javaMailSender.setProtocol("smtp"); + props.put("mail.smtp.auth", "true"); + + for (SystemParameter p : paramList) { + switch (p.getParamKey()) { + case "smtp.host": + javaMailSender.setHost(p.getParamValue()); + break; + case "smtp.port": + javaMailSender.setPort(Integer.parseInt(p.getParamValue())); + break; + case "smtp.account": + javaMailSender.setUsername(p.getParamValue()); + break; + case "smtp.password": + javaMailSender.setPassword(EncryptUtils.aesDecrypt(p.getParamValue()).toString()); + break; + case "smtp.ssl": + if (BooleanUtils.toBoolean(p.getParamValue())) { + javaMailSender.setProtocol("smtps"); + props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); + } + break; + case "smtp.tls": + String result = BooleanUtils.toString(BooleanUtils.toBoolean(p.getParamValue()), "true", "false"); + props.put("mail.smtp.starttls.enable", result); + props.put("mail.smtp.starttls.required", result); + break; + /* case "smtp.anon": + boolean isAnon = BooleanUtils.toBoolean(p.getParamValue()); + if (isAnon) { + props.put("mail.smtp.auth", "false"); + javaMailSender.setUsername(null); + javaMailSender.setPassword(null); + } + break;*/ + default: + break; + } + } + + props.put("mail.smtp.timeout", "30000"); + props.put("mail.smtp.connectiontimeout", "5000"); + javaMailSender.setJavaMailProperties(props); + return javaMailSender; + } +} + + + + + diff --git a/backend/src/main/java/io/dataease/notice/service/NoticeSendService.java b/backend/src/main/java/io/dataease/notice/service/NoticeSendService.java new file mode 100644 index 0000000000..7ab13e1fbb --- /dev/null +++ b/backend/src/main/java/io/dataease/notice/service/NoticeSendService.java @@ -0,0 +1,63 @@ +package io.dataease.notice.service; + +import io.dataease.commons.constants.NoticeConstants; +import io.dataease.notice.domain.MessageDetail; +import io.dataease.notice.sender.NoticeModel; +import io.dataease.notice.sender.NoticeSender; +import io.dataease.notice.sender.impl.MailNoticeSender; +import io.dataease.notice.sender.impl.WeComNoticeSender; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.List; + +@Component +public class NoticeSendService { + @Resource + private MailNoticeSender mailNoticeSender; + @Resource + private WeComNoticeSender weComNoticeSender; + + @Resource + private NoticeService noticeService; + + private NoticeSender getNoticeSender(MessageDetail messageDetail) { + NoticeSender noticeSender = null; + switch (messageDetail.getType()) { + case NoticeConstants.Type.EMAIL: + noticeSender = mailNoticeSender; + break; + case NoticeConstants.Type.WECHAT_ROBOT: + noticeSender = weComNoticeSender; + break; +// case NoticeConstants.Type.NAIL_ROBOT: +// noticeSender = dingNoticeSender; +// break; + default: + break; + } + + return noticeSender; + } + + public void send(String taskType, NoticeModel noticeModel) { + List messageDetails; + switch (taskType) { + case NoticeConstants.Mode.API: + messageDetails = noticeService.searchMessageByType(NoticeConstants.TaskType.JENKINS_TASK); + break; + case NoticeConstants.Mode.SCHEDULE: + messageDetails = noticeService.searchMessageByTestId(noticeModel.getTestId()); + break; + default: + messageDetails = noticeService.searchMessageByType(taskType); + break; + } + messageDetails.forEach(messageDetail -> { + if (StringUtils.equals(messageDetail.getEvent(), noticeModel.getEvent())) { + this.getNoticeSender(messageDetail).send(messageDetail, noticeModel); + } + }); + } +} diff --git a/backend/src/main/java/io/dataease/notice/service/NoticeService.java b/backend/src/main/java/io/dataease/notice/service/NoticeService.java new file mode 100644 index 0000000000..e7c2a8d027 --- /dev/null +++ b/backend/src/main/java/io/dataease/notice/service/NoticeService.java @@ -0,0 +1,160 @@ +package io.dataease.notice.service; + +import io.dataease.base.domain.MessageTask; +import io.dataease.base.domain.MessageTaskExample; +import io.dataease.base.mapper.MessageTaskMapper; +import io.dataease.commons.exception.DEException; +import io.dataease.commons.user.SessionUser; +import io.dataease.commons.utils.SessionUtils; +import io.dataease.i18n.Translator; +import io.dataease.notice.domain.MessageDetail; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.*; +import java.util.stream.Collectors; + +@Service +@Transactional(rollbackFor = Exception.class) +public class NoticeService { + @Resource + private MessageTaskMapper messageTaskMapper; + + public void saveMessageTask(MessageDetail messageDetail) { + MessageTaskExample example = new MessageTaskExample(); + example.createCriteria().andIdentificationEqualTo(messageDetail.getIdentification()); + List messageTaskLists = messageTaskMapper.selectByExample(example); + if (messageTaskLists.size() > 0) { + delMessage(messageDetail.getIdentification()); + } + SessionUser user = SessionUtils.getUser(); + String orgId = user.getLastOrganizationId(); + long time = System.currentTimeMillis(); + String identification = messageDetail.getIdentification(); + if (StringUtils.isBlank(identification)) { + identification = UUID.randomUUID().toString(); + } + for (String userId : messageDetail.getUserIds()) { + checkUserIdExist(userId, messageDetail, orgId); + MessageTask messageTask = new MessageTask(); + messageTask.setId(UUID.randomUUID().toString()); + messageTask.setEvent(messageDetail.getEvent()); + messageTask.setTaskType(messageDetail.getTaskType()); + messageTask.setUserId(userId); + messageTask.setType(messageDetail.getType()); + messageTask.setWebhook(messageDetail.getWebhook()); + messageTask.setIdentification(identification); + messageTask.setIsSet(false); + messageTask.setOrganizationId(orgId); + messageTask.setTestId(messageDetail.getTestId()); + messageTask.setCreateTime(time); + setTemplate(messageDetail, messageTask); + messageTaskMapper.insert(messageTask); + } + } + + private void setTemplate(MessageDetail messageDetail, MessageTask messageTask) { + if (StringUtils.isNotBlank(messageDetail.getTemplate())) { + messageTask.setTemplate(messageDetail.getTemplate()); + } + } + + private void checkUserIdExist(String userId, MessageDetail list, String orgId) { + MessageTaskExample example = new MessageTaskExample(); + if (StringUtils.isBlank(list.getTestId())) { + example.createCriteria() + .andUserIdEqualTo(userId) + .andEventEqualTo(list.getEvent()) + .andTypeEqualTo(list.getType()) + .andTaskTypeEqualTo(list.getTaskType()) + .andWebhookEqualTo(list.getWebhook()) + .andOrganizationIdEqualTo(orgId); + } else { + example.createCriteria() + .andUserIdEqualTo(userId) + .andEventEqualTo(list.getEvent()) + .andTypeEqualTo(list.getType()) + .andTaskTypeEqualTo(list.getTaskType()) + .andWebhookEqualTo(list.getWebhook()) + .andTestIdEqualTo(list.getTestId()) + .andOrganizationIdEqualTo(orgId); + } + if (messageTaskMapper.countByExample(example) > 0) { + DEException.throwException(Translator.get("message_task_already_exists")); + } + } + + public List searchMessageByTestId(String testId) { + MessageTaskExample example = new MessageTaskExample(); + example.createCriteria().andTestIdEqualTo(testId); + List messageTaskLists = messageTaskMapper.selectByExampleWithBLOBs(example); + List scheduleMessageTask = new ArrayList<>(); + Map> MessageTaskMap = messageTaskLists.stream().collect(Collectors.groupingBy(MessageTask::getIdentification)); + MessageTaskMap.forEach((k, v) -> { + MessageDetail messageDetail = getMessageDetail(v); + scheduleMessageTask.add(messageDetail); + }); + scheduleMessageTask.sort(Comparator.comparing(MessageDetail::getCreateTime, Comparator.nullsLast(Long::compareTo)).reversed()); + return scheduleMessageTask; + } + + public List searchMessageByType(String type) { + SessionUser user = SessionUtils.getUser(); + String orgId = user.getLastOrganizationId(); + List messageDetails = new ArrayList<>(); + + MessageTaskExample example = new MessageTaskExample(); + example.createCriteria() + .andTaskTypeEqualTo(type) + .andOrganizationIdEqualTo(orgId); + List messageTaskLists = messageTaskMapper.selectByExampleWithBLOBs(example); + + Map> messageTaskMap = messageTaskLists.stream() + .collect(Collectors.groupingBy(NoticeService::fetchGroupKey)); + messageTaskMap.forEach((k, v) -> { + MessageDetail messageDetail = getMessageDetail(v); + messageDetails.add(messageDetail); + }); + + return messageDetails.stream() + .sorted(Comparator.comparing(MessageDetail::getCreateTime, Comparator.nullsLast(Long::compareTo)).reversed()) + .collect(Collectors.toList()) + .stream() + .distinct() + .collect(Collectors.toList()); + } + + private MessageDetail getMessageDetail(List messageTasks) { + Set userIds = new HashSet<>(); + + MessageDetail messageDetail = new MessageDetail(); + for (MessageTask m : messageTasks) { + userIds.add(m.getUserId()); + messageDetail.setEvent(m.getEvent()); + messageDetail.setTaskType(m.getTaskType()); + messageDetail.setWebhook(m.getWebhook()); + messageDetail.setIdentification(m.getIdentification()); + messageDetail.setType(m.getType()); + messageDetail.setIsSet(m.getIsSet()); + messageDetail.setCreateTime(m.getCreateTime()); + messageDetail.setTemplate(m.getTemplate()); + } + if (CollectionUtils.isNotEmpty(userIds)) { + messageDetail.setUserIds(new ArrayList<>(userIds)); + } + return messageDetail; + } + + private static String fetchGroupKey(MessageTask messageTask) { + return messageTask.getTaskType() + "#" + messageTask.getIdentification(); + } + + public int delMessage(String identification) { + MessageTaskExample example = new MessageTaskExample(); + example.createCriteria().andIdentificationEqualTo(identification); + return messageTaskMapper.deleteByExample(example); + } +} diff --git a/backend/src/main/java/io/dataease/notice/util/SendResult.java b/backend/src/main/java/io/dataease/notice/util/SendResult.java new file mode 100644 index 0000000000..6b154bdd3a --- /dev/null +++ b/backend/src/main/java/io/dataease/notice/util/SendResult.java @@ -0,0 +1,47 @@ +package io.dataease.notice.util; + +import com.alibaba.fastjson.JSON; + +import java.util.HashMap; +import java.util.Map; + +/** + * + */ +public class SendResult { + private boolean isSuccess; + private Integer errorCode; + private String errorMsg; + + public boolean isSuccess() { + return isSuccess; + } + + public void setIsSuccess(boolean isSuccess) { + this.isSuccess = isSuccess; + } + + public Integer getErrorCode() { + return errorCode; + } + + public void setErrorCode(Integer errorCode) { + this.errorCode = errorCode; + } + + public String getErrorMsg() { + return errorMsg; + } + + public void setErrorMsg(String errorMsg) { + this.errorMsg = errorMsg; + } + + public String toString() { + Map items = new HashMap(); + items.put("errorCode", errorCode); + items.put("errorMsg", errorMsg); + items.put("isSuccess", isSuccess); + return JSON.toJSONString(items); + } +} diff --git a/backend/src/main/java/io/dataease/notice/util/WxChatbotClient.java b/backend/src/main/java/io/dataease/notice/util/WxChatbotClient.java new file mode 100644 index 0000000000..192e9c42e1 --- /dev/null +++ b/backend/src/main/java/io/dataease/notice/util/WxChatbotClient.java @@ -0,0 +1,50 @@ +package io.dataease.notice.util; + +import com.alibaba.fastjson.JSONObject; +import io.dataease.notice.message.Message; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; + +import java.io.IOException; + +/** + * + */ +public class WxChatbotClient { + + static HttpClient httpclient = HttpClients.createDefault(); + + public static SendResult send(String webhook, Message message) throws IOException { + + if (StringUtils.isBlank(webhook)) { + return new SendResult(); + } + HttpPost httppost = new HttpPost(webhook); + httppost.addHeader("Content-Type", "application/json; charset=utf-8"); + StringEntity se = new StringEntity(message.toJsonString(), "utf-8"); + httppost.setEntity(se); + + SendResult sendResult = new SendResult(); + HttpResponse response = httpclient.execute(httppost); + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + String result = EntityUtils.toString(response.getEntity()); + JSONObject obj = JSONObject.parseObject(result); + + Integer errcode = obj.getInteger("errcode"); + sendResult.setErrorCode(errcode); + sendResult.setErrorMsg(obj.getString("errmsg")); + sendResult.setIsSuccess(errcode.equals(0)); + } + + return sendResult; + } + +} + + diff --git a/backend/src/main/java/io/dataease/service/chart/ChartGroupService.java b/backend/src/main/java/io/dataease/service/chart/ChartGroupService.java new file mode 100644 index 0000000000..0b4f68389e --- /dev/null +++ b/backend/src/main/java/io/dataease/service/chart/ChartGroupService.java @@ -0,0 +1,111 @@ +package io.dataease.service.chart; + +import io.dataease.base.domain.ChartGroup; +import io.dataease.base.domain.ChartGroupExample; +import io.dataease.base.mapper.ChartGroupMapper; +import io.dataease.commons.utils.BeanUtils; +import io.dataease.controller.request.chart.ChartGroupRequest; +import io.dataease.dto.chart.ChartGroupDTO; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + + +@Service +public class ChartGroupService { + @Resource + private ChartGroupMapper chartGroupMapper; + + public ChartGroupDTO save(ChartGroup chartGroup) { + if (StringUtils.isEmpty(chartGroup.getId())) { + chartGroup.setId(UUID.randomUUID().toString()); + chartGroup.setCreateTime(System.currentTimeMillis()); + chartGroupMapper.insert(chartGroup); + } else { + chartGroupMapper.updateByPrimaryKey(chartGroup); + } + ChartGroupDTO ChartGroupDTO = new ChartGroupDTO(); + BeanUtils.copyBean(ChartGroupDTO, chartGroup); + ChartGroupDTO.setLabel(ChartGroupDTO.getName()); + return ChartGroupDTO; + } + + public void delete(String id) { + ChartGroupRequest ChartGroup = new ChartGroupRequest(); + ChartGroup.setId(id); + List tree = tree(ChartGroup); + List ids = new ArrayList<>(); + getAllId(tree, ids); + ChartGroupExample ChartGroupExample = new ChartGroupExample(); + ChartGroupExample.createCriteria().andIdIn(ids); + chartGroupMapper.deleteByExample(ChartGroupExample); + } + + public List tree(ChartGroupRequest ChartGroup) { + ChartGroupExample ChartGroupExample = new ChartGroupExample(); + ChartGroupExample.Criteria criteria = ChartGroupExample.createCriteria(); + if (StringUtils.isNotEmpty(ChartGroup.getName())) { + criteria.andNameLike("%" + ChartGroup.getName() + "%"); + } + if (StringUtils.isNotEmpty(ChartGroup.getType())) { + criteria.andTypeEqualTo(ChartGroup.getType()); + } + if (StringUtils.isNotEmpty(ChartGroup.getId())) { + criteria.andIdEqualTo(ChartGroup.getId()); + } else { + criteria.andLevelEqualTo(0); + } + ChartGroupExample.setOrderByClause(ChartGroup.getSort()); + List ChartGroups = chartGroupMapper.selectByExample(ChartGroupExample); + List DTOs = ChartGroups.stream().map(ele -> { + ChartGroupDTO dto = new ChartGroupDTO(); + BeanUtils.copyBean(dto, ele); + dto.setLabel(ele.getName()); + return dto; + }).collect(Collectors.toList()); + getAll(DTOs, ChartGroup); + return DTOs; + } + + public void getAll(List list, ChartGroupRequest ChartGroup) { + for (ChartGroupDTO obj : list) { + ChartGroupExample ChartGroupExample = new ChartGroupExample(); + ChartGroupExample.Criteria criteria = ChartGroupExample.createCriteria(); + if (StringUtils.isNotEmpty(ChartGroup.getName())) { + criteria.andNameLike("%" + ChartGroup.getName() + "%"); + } + if (StringUtils.isNotEmpty(ChartGroup.getType())) { + criteria.andTypeEqualTo(ChartGroup.getType()); + } + criteria.andPidEqualTo(obj.getId()); + ChartGroupExample.setOrderByClause(ChartGroup.getSort()); + List ChartGroups = chartGroupMapper.selectByExample(ChartGroupExample); + List DTOs = ChartGroups.stream().map(ele -> { + ChartGroupDTO dto = new ChartGroupDTO(); + BeanUtils.copyBean(dto, ele); + dto.setLabel(ele.getName()); + return dto; + }).collect(Collectors.toList()); + obj.setChildren(DTOs); + if (CollectionUtils.isNotEmpty(DTOs)) { + getAll(DTOs, ChartGroup); + } + } + } + + public List getAllId(List list, List ids) { + for (ChartGroupDTO dto : list) { + ids.add(dto.getId()); + if (CollectionUtils.isNotEmpty(dto.getChildren())) { + getAllId(dto.getChildren(), ids); + } + } + return ids; + } +} diff --git a/backend/src/main/resources/db/migration/V10__chart.sql b/backend/src/main/resources/db/migration/V10__chart.sql new file mode 100644 index 0000000000..3d4e563eb4 --- /dev/null +++ b/backend/src/main/resources/db/migration/V10__chart.sql @@ -0,0 +1,12 @@ +-- chart start +CREATE TABLE IF NOT EXISTS `chart_group` ( + `id` varchar(50) NOT NULL COMMENT 'ID', + `name` varchar(64) NOT NULL COMMENT '名称', + `pid` varchar(50) COMMENT '父级ID', + `level` int(10) COMMENT '当前分组处于第几级', + `type` varchar(50) COMMENT 'group or scene', + `create_by` varchar(50) COMMENT '创建人ID', + `create_time` bigint(13) COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +-- chart end diff --git a/frontend/src/assets/favicon.ico b/frontend/src/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..9dc173ff67ac7432dc701a004fbba1d9164e8378 GIT binary patch literal 109986 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 F@Bi~>yWIc) literal 0 HcmV?d00001 diff --git a/frontend/src/business/components/chart/Chart.vue b/frontend/src/business/components/chart/Chart.vue new file mode 100644 index 0000000000..bc27092c0b --- /dev/null +++ b/frontend/src/business/components/chart/Chart.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/frontend/src/business/components/chart/data/AddDB.vue b/frontend/src/business/components/chart/data/AddDB.vue new file mode 100644 index 0000000000..bdc47a0553 --- /dev/null +++ b/frontend/src/business/components/chart/data/AddDB.vue @@ -0,0 +1,148 @@ + + + + + diff --git a/frontend/src/business/components/chart/data/ChartHome.vue b/frontend/src/business/components/chart/data/ChartHome.vue new file mode 100644 index 0000000000..a3e454d98f --- /dev/null +++ b/frontend/src/business/components/chart/data/ChartHome.vue @@ -0,0 +1,25 @@ + + + + + diff --git a/frontend/src/business/components/chart/data/TabDataPreview.vue b/frontend/src/business/components/chart/data/TabDataPreview.vue new file mode 100644 index 0000000000..8a8e52eb13 --- /dev/null +++ b/frontend/src/business/components/chart/data/TabDataPreview.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/frontend/src/business/components/chart/data/ViewTable.vue b/frontend/src/business/components/chart/data/ViewTable.vue new file mode 100644 index 0000000000..38a9ad1408 --- /dev/null +++ b/frontend/src/business/components/chart/data/ViewTable.vue @@ -0,0 +1,122 @@ + + + + + diff --git a/frontend/src/business/components/chart/group/Group.vue b/frontend/src/business/components/chart/group/Group.vue new file mode 100644 index 0000000000..d2a4b6f563 --- /dev/null +++ b/frontend/src/business/components/chart/group/Group.vue @@ -0,0 +1,540 @@ + + + + + diff --git a/frontend/src/business/components/chart/router.js b/frontend/src/business/components/chart/router.js new file mode 100644 index 0000000000..579847c8fb --- /dev/null +++ b/frontend/src/business/components/chart/router.js @@ -0,0 +1,18 @@ +const Chart = () => import('@/business/components/chart/Chart'); +const ChartHome = () => import('@/business/components/chart/data/ChartHome'); + +export default { + path: "/chart", + name: "ChartGroup", + redirect: "/chart/home", + components: { + content: Chart, + }, + children: [ + { + path: 'home', + name: 'home', + component: ChartHome, + } + ] +}