diff --git a/JSD-8604需求确认书.docx b/JSD-8604需求确认书.docx
new file mode 100644
index 0000000..214019c
Binary files /dev/null and b/JSD-8604需求确认书.docx differ
diff --git a/README.md b/README.md
index d197ad0..a682739 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,6 @@
# open-JSD-8604
-JSD-8604 开源任务材料
\ No newline at end of file
+JSD-8604 钉钉webhook推送\
+免责说明:该源码为第三方爱好者提供,不保证源码和方案的可靠性,也不提供任何形式的源码教学指导和协助!\
+仅作为开发者学习参考使用!禁止用于任何商业用途!\
+为保护开发者隐私,开发者信息已隐去!若原开发者希望公开自己的信息,可联系hugh处理。
\ No newline at end of file
diff --git a/plugin.xml b/plugin.xml
new file mode 100644
index 0000000..ed9ae9b
--- /dev/null
+++ b/plugin.xml
@@ -0,0 +1,26 @@
+
+ com.fr.plugin.dingding.webhook
+
+ yes
+ 1.4
+ 10.0
+ 2018-07-31
+ fr.open
+
+
+ [2021-09-14]【1.1】重新打包
+ [2021-09-15]【1.2】增加定时调度链接传参
+ [2021-09-15]【1.3】增加获取默认图片
+ [2021-09-15]【1.4】增加授权和增加获取ticket的函数
+ ]]>
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/com/fr/plugin/dingding/webhook/OutputDBAccess.java b/src/main/java/com/fr/plugin/dingding/webhook/OutputDBAccess.java
new file mode 100644
index 0000000..d0328bf
--- /dev/null
+++ b/src/main/java/com/fr/plugin/dingding/webhook/OutputDBAccess.java
@@ -0,0 +1,43 @@
+package com.fr.plugin.dingding.webhook;
+
+import com.fr.decision.plugin.db.AbstractDecisionDBAccessProvider;
+import com.fr.plugin.dingding.webhook.dao.WebHookDao;
+import com.fr.plugin.dingding.webhook.entity.WebHookEntity;
+import com.fr.stable.db.accessor.DBAccessor;
+import com.fr.stable.db.dao.BaseDAO;
+import com.fr.stable.db.dao.DAOProvider;
+
+/**
+ * @Author fr,open
+ * @Date 2020/9/15
+ * @Description
+ **/
+public class OutputDBAccess extends AbstractDecisionDBAccessProvider {
+
+ private static DBAccessor dbAccessor;
+ public DBAccessor getDbAccessor() {
+ return dbAccessor;
+ }
+
+ @Override
+ public DAOProvider[] registerDAO() {
+ return new DAOProvider[]{
+ new DAOProvider() {
+ @Override
+ public Class getEntityClass() {
+ return WebHookEntity.class;
+ }
+
+ @Override
+ public Class extends BaseDAO> getDAOClass() {
+ return WebHookDao.class;
+ }
+ }
+ };
+ }
+
+ @Override
+ public void onDBAvailable(DBAccessor dbAccessor) {
+ OutputDBAccess.dbAccessor = dbAccessor;
+ }
+}
diff --git a/src/main/java/com/fr/plugin/dingding/webhook/OutputPluginLifecycleMonitor.java b/src/main/java/com/fr/plugin/dingding/webhook/OutputPluginLifecycleMonitor.java
new file mode 100644
index 0000000..ad24f59
--- /dev/null
+++ b/src/main/java/com/fr/plugin/dingding/webhook/OutputPluginLifecycleMonitor.java
@@ -0,0 +1,45 @@
+package com.fr.plugin.dingding.webhook;
+
+import com.fr.intelli.record.Focus;
+import com.fr.intelli.record.Original;
+import com.fr.plugin.context.PluginContext;
+import com.fr.plugin.context.PluginContexts;
+import com.fr.plugin.dingding.webhook.entity.OutputWebHook;
+import com.fr.plugin.dingding.webhook.entity.WebHookEntity;
+import com.fr.plugin.dingding.webhook.format.PNGOutputFormat;
+import com.fr.plugin.dingding.webhook.handle.WebHookHandle;
+import com.fr.plugin.observer.inner.AbstractPluginLifecycleMonitor;
+import com.fr.schedule.extension.report.job.output.BaseOutputFormat;
+import com.fr.schedule.feature.ScheduleOutputActionEntityRegister;
+import com.fr.schedule.feature.output.OutputActionHandler;
+import com.fr.stable.fun.Authorize;
+
+/**
+ * @Author fr.open
+ * @Date 2020/9/15
+ * @Description
+ **/
+@Authorize(callSignKey = PluginConstants.PLUGIN_ID)
+public class OutputPluginLifecycleMonitor extends AbstractPluginLifecycleMonitor {
+ @Override
+ @Focus(id = PluginConstants.PLUGIN_ID, text = "钉钉webhook", source = Original.PLUGIN)
+ public void afterRun(PluginContext pluginContext) {
+ if (!PluginContexts.currentContext().isAvailable()) {
+ return;
+ }
+ BaseOutputFormat.registerOutputFormat(new PNGOutputFormat());
+ OutputActionHandler.registerHandler(new WebHookHandle(), OutputWebHook.class.getName());
+ ScheduleOutputActionEntityRegister.getInstance().addClass(WebHookEntity.class);
+ }
+
+ @Override
+ @Focus(id = PluginConstants.PLUGIN_ID, text = "钉钉webhook", source = Original.PLUGIN)
+ public void beforeStop(PluginContext pluginContext) {
+ if (!PluginContexts.currentContext().isAvailable()) {
+ return;
+ }
+ BaseOutputFormat.removeOutputFormat(PNGOutputFormat.CONVERT_TO_PNG);
+ OutputActionHandler.removeOutputHandler(OutputWebHook.class.getName());
+ ScheduleOutputActionEntityRegister.getInstance().removeClass(WebHookEntity.class);
+ }
+}
diff --git a/src/main/java/com/fr/plugin/dingding/webhook/PluginConstants.java b/src/main/java/com/fr/plugin/dingding/webhook/PluginConstants.java
new file mode 100644
index 0000000..de46511
--- /dev/null
+++ b/src/main/java/com/fr/plugin/dingding/webhook/PluginConstants.java
@@ -0,0 +1,11 @@
+package com.fr.plugin.dingding.webhook;
+
+/**
+ * @Author fr.open
+ * @Date 2021/3/1
+ * @Description
+ **/
+public class PluginConstants {
+
+ public static final String PLUGIN_ID = "com.fr.plugin.dingding.webhook";
+}
diff --git a/src/main/java/com/fr/plugin/dingding/webhook/dao/WebHookDao.java b/src/main/java/com/fr/plugin/dingding/webhook/dao/WebHookDao.java
new file mode 100644
index 0000000..0940f15
--- /dev/null
+++ b/src/main/java/com/fr/plugin/dingding/webhook/dao/WebHookDao.java
@@ -0,0 +1,21 @@
+package com.fr.plugin.dingding.webhook.dao;
+
+import com.fr.plugin.dingding.webhook.entity.WebHookEntity;
+import com.fr.stable.db.dao.BaseDAO;
+import com.fr.stable.db.session.DAOSession;
+
+/**
+ * @Author fr.open
+ * @Date 2020/9/15
+ * @Description
+ **/
+public class WebHookDao extends BaseDAO {
+ public WebHookDao(DAOSession daoSession) {
+ super(daoSession);
+ }
+
+ @Override
+ protected Class getEntityClass() {
+ return WebHookEntity.class;
+ }
+}
diff --git a/src/main/java/com/fr/plugin/dingding/webhook/entity/OutputWebHook.java b/src/main/java/com/fr/plugin/dingding/webhook/entity/OutputWebHook.java
new file mode 100644
index 0000000..ee8f533
--- /dev/null
+++ b/src/main/java/com/fr/plugin/dingding/webhook/entity/OutputWebHook.java
@@ -0,0 +1,62 @@
+package com.fr.plugin.dingding.webhook.entity;
+
+import com.fr.schedule.base.bean.output.BaseOutputAction;
+import com.fr.schedule.base.entity.AbstractScheduleEntity;
+import com.fr.schedule.base.type.RunType;
+import com.fr.third.fasterxml.jackson.annotation.JsonSubTypes;
+
+/**
+ * @Author fr.open
+ * @Date 2020/9/15
+ * @Description
+ **/
+@JsonSubTypes.Type(value = OutputWebHook.class, name = "OutputWebHook")
+public class OutputWebHook extends BaseOutputAction {
+
+ private static final long serialVersionUID = 8921116228585639504L;
+
+ private String hookUrl = null;
+
+ public OutputWebHook() {
+ super();
+ }
+
+ @Override
+ public boolean willExecuteByUser() {
+ return false;
+ }
+
+ @Override
+ public RunType runType() {
+ return RunType.SEND_FILE;
+ }
+
+ @Override
+ public Class extends AbstractScheduleEntity> outputActionEntityClass() {
+ return WebHookEntity.class;
+ }
+
+ @Override
+ public AbstractScheduleEntity createOutputActionEntity() {
+ return (new WebHookEntity()).id(this.getId()).hookUrl(this.hookUrl);
+ }
+
+ @Override
+ public OutputWebHook id(String id) {
+ setId(id);
+ return this;
+ }
+
+ public String getHookUrl() {
+ return hookUrl;
+ }
+
+ public void setHookUrl(String hookUrl) {
+ this.hookUrl = hookUrl;
+ }
+
+ public OutputWebHook hookUrl(String hookUrl) {
+ setHookUrl(hookUrl);
+ return this;
+ }
+}
diff --git a/src/main/java/com/fr/plugin/dingding/webhook/entity/WebHookEntity.java b/src/main/java/com/fr/plugin/dingding/webhook/entity/WebHookEntity.java
new file mode 100644
index 0000000..6848e5d
--- /dev/null
+++ b/src/main/java/com/fr/plugin/dingding/webhook/entity/WebHookEntity.java
@@ -0,0 +1,48 @@
+package com.fr.plugin.dingding.webhook.entity;
+
+import com.fr.schedule.base.bean.BaseBean;
+import com.fr.schedule.base.entity.AbstractScheduleEntity;
+import com.fr.stable.db.entity.TableAssociation;
+import com.fr.third.javax.persistence.Column;
+import com.fr.third.javax.persistence.Entity;
+import com.fr.third.javax.persistence.Table;
+
+/**
+ * @Author fr.open
+ * @Date 2020/9/15
+ * @Description
+ **/
+@Entity
+@Table(name = "fine_output_dingding_webHook") //表名
+@TableAssociation(associated = true)
+public class WebHookEntity extends AbstractScheduleEntity {
+
+ @Column(name = "hookUrl")
+ private String hookUrl;
+
+ public WebHookEntity() {
+ }
+
+ @Override
+ public BaseBean createBean() {
+ return new OutputWebHook().id(this.getId()).hookUrl(this.hookUrl);
+ }
+
+ public String getHookUrl() {
+ return hookUrl;
+ }
+
+ public void setHookUrl(String hookUrl) {
+ this.hookUrl = hookUrl;
+ }
+
+ public WebHookEntity hookUrl(String hookUrl) {
+ setHookUrl(hookUrl);
+ return this;
+ }
+
+ public WebHookEntity id(String id) {
+ setId(id);
+ return this;
+ }
+}
diff --git a/src/main/java/com/fr/plugin/dingding/webhook/format/PNGOutputFormat.java b/src/main/java/com/fr/plugin/dingding/webhook/format/PNGOutputFormat.java
new file mode 100644
index 0000000..2329713
--- /dev/null
+++ b/src/main/java/com/fr/plugin/dingding/webhook/format/PNGOutputFormat.java
@@ -0,0 +1,40 @@
+package com.fr.plugin.dingding.webhook.format;
+
+import com.fr.io.exporter.ImageExporter;
+import com.fr.main.workbook.ResultWorkBook;
+import com.fr.schedule.extension.report.job.output.BaseOutputFormat;
+
+import java.io.OutputStream;
+import java.util.List;
+
+/**
+ * @Author fr.open
+ * @Date 2020/9/15
+ * @Description
+ **/
+public class PNGOutputFormat extends BaseOutputFormat {
+ public static final int CONVERT_TO_PNG = 64;
+
+ public PNGOutputFormat() {
+ }
+
+ @Override
+ public int getFormat() {
+ return CONVERT_TO_PNG;
+ }
+
+ @Override
+ public String getFileSuffix() {
+ return ".png";
+ }
+
+ @Override
+ public boolean withParentPath() {
+ return true;
+ }
+
+ @Override
+ public void flushWithParentPath(OutputStream var1, ResultWorkBook var2, String var3, final List var4) throws Exception {
+ new ImageExporter("png", 96).export(var1, var2);
+ }
+}
diff --git a/src/main/java/com/fr/plugin/dingding/webhook/fun/TicketFun.java b/src/main/java/com/fr/plugin/dingding/webhook/fun/TicketFun.java
new file mode 100644
index 0000000..006f465
--- /dev/null
+++ b/src/main/java/com/fr/plugin/dingding/webhook/fun/TicketFun.java
@@ -0,0 +1,66 @@
+package com.fr.plugin.dingding.webhook.fun;
+
+import com.fr.general.http.HttpRequest;
+import com.fr.general.http.HttpToolbox;
+import com.fr.intelli.record.Focus;
+import com.fr.intelli.record.Original;
+import com.fr.log.FineLoggerFactory;
+import com.fr.plugin.context.PluginContexts;
+import com.fr.plugin.dingding.webhook.PluginConstants;
+import com.fr.script.AbstractFunction;
+import com.fr.stable.ArrayUtils;
+import com.fr.stable.Primitive;
+import com.fr.stable.StringUtils;
+import com.fr.stable.exception.FormulaException;
+import com.fr.stable.fun.Authorize;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @Author fr.open
+ * @Date 2021/9/15
+ * @Description
+ **/
+@Authorize(callSignKey = PluginConstants.PLUGIN_ID)
+public class TicketFun extends AbstractFunction {
+ @Override
+ @Focus(id = PluginConstants.PLUGIN_ID, text = "钉钉webhook", source = Original.PLUGIN)
+ public Object run(Object[] args) throws FormulaException {
+ if (!PluginContexts.currentContext().isAvailable()) {
+ return null;
+ }
+ int len = ArrayUtils.getLength(args);
+ if (len == 0) {
+ return Primitive.ERROR_VALUE;
+ }
+ String user = (String) args[0];
+ String target = (String) args[1];
+
+ if(StringUtils.isBlank(user) || StringUtils.isBlank(target)){
+ return Primitive.ERROR_VALUE;
+ }
+ String url =StringUtils.EMPTY;
+ if(args.length < 3){
+ url = "http://xxxx/trusted";
+ }else {
+ url = (String) args[2];
+ if(StringUtils.isBlank(url)){
+ url = "http://xxxx/trusted";
+ }
+ }
+
+ Map param = new HashMap<>();
+ param.put("username",user);
+ param.put("target_site",target);
+ try {
+ String execute = HttpToolbox.executeAndParse(HttpRequest.custom().post(param).url(url).build());
+ return execute;
+ } catch (IOException e) {
+ FineLoggerFactory.getLogger().error(e.getMessage(),e);
+ }
+
+ return null;
+ }
+}
diff --git a/src/main/java/com/fr/plugin/dingding/webhook/handle/WebHookHandle.java b/src/main/java/com/fr/plugin/dingding/webhook/handle/WebHookHandle.java
new file mode 100644
index 0000000..52adae0
--- /dev/null
+++ b/src/main/java/com/fr/plugin/dingding/webhook/handle/WebHookHandle.java
@@ -0,0 +1,116 @@
+package com.fr.plugin.dingding.webhook.handle;
+
+import com.fr.base.Formula;
+import com.fr.base.PropertiesUtils;
+import com.fr.io.utils.ResourceIOUtils;
+import com.fr.json.JSONObject;
+import com.fr.log.FineLoggerFactory;
+import com.fr.plugin.dingding.webhook.entity.OutputWebHook;
+import com.fr.plugin.dingding.webhook.util.DesECBUtil;
+import com.fr.plugin.dingding.webhook.util.HttpUtil;
+import com.fr.schedule.base.constant.ScheduleConstants;
+import com.fr.schedule.feature.output.OutputActionHandler;
+import com.fr.stable.CodeUtils;
+import com.fr.stable.StringUtils;
+
+import java.net.URLEncoder;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @Author fr.open
+ * @Date 2020/9/15
+ * @Description
+ **/
+public class WebHookHandle extends OutputActionHandler {
+ private static final String key = "xxxx";
+
+ private static final String file = "/resources/Robot.png";
+
+ @Override
+ public void doAction(OutputWebHook outputWebHook, Map map) throws Exception {
+ String[] files = (String[]) map.get(ScheduleConstants.OUTPUT_FILES);
+ getUrl(outputWebHook, map);
+ String mediaId = StringUtils.EMPTY;
+ String imageFile = StringUtils.EMPTY;
+ if(ResourceIOUtils.exist(file)){
+ FineLoggerFactory.getLogger().info("read default image {}",file);
+ imageFile = file;
+ }else {
+ for (String file : files) {
+ if (file.endsWith("png")) {
+ imageFile = file;
+ continue;
+ }
+ }
+ }
+
+ JSONObject object = HttpUtil.uploadMedia(imageFile);
+ FineLoggerFactory.getLogger().info("upload media res is {}", object);
+ if (HttpUtil.isOk(object)) {
+ mediaId = object.getJSONObject("url").getString("media_id");
+ }
+ if (StringUtils.isBlank(mediaId)) {
+ throw new Exception("not contain media or upload failed");
+ }
+ String taskName = CodeUtils.encodeURIComponent((String) map.get(ScheduleConstants.TASK_NAME));
+ String path = CodeUtils.encodeURIComponent((String) map.get(ScheduleConstants.SAVE_DIRECTORY));
+ path = path.replaceAll("\\+", "%20");
+ String showType = (String) map.get(ScheduleConstants.SHOW_TYPE);
+ int taskType = (Integer) map.get(ScheduleConstants.TASK_TYPE);
+ String scheduleUsername = (String) map.get(ScheduleConstants.USERNAME);
+ String resultUrl = getParam(map, "resultUrl");
+ String mobileUrl = outputWebHook.getResultURL()+"/url/mobile/schedule/result?taskName=" + taskName + "&username=" + scheduleUsername + "&path=" + path + "&showType=" + showType + "&taskType=" + taskType;
+ HttpUtil.sendWebhook(mediaId, outputWebHook.getHookUrl(), getParam(map, "title"), getParam(map, "text"), delaSign(StringUtils.isNotBlank(resultUrl)?resultUrl:mobileUrl));
+ }
+
+ private String delaSign(String url) {
+ String path = url;
+ try {
+ String user = PropertiesUtils.getProperties("dingtalk").getProperty("resultUser");
+ String encode = URLEncoder.encode(DesECBUtil.encryptDES(user, key), "UTF-8");
+ if (path.indexOf("?") != -1) {
+ path += "&result_sign=" + encode;
+ } else {
+ path += "?result_sign=" + encode;
+ }
+ } catch (Exception e) {
+ FineLoggerFactory.getLogger().error(e.getMessage(), e);
+ }
+ FineLoggerFactory.getLogger().info("push dingding url is {}",path);
+ return path+"&op=h5";
+ }
+
+ private String getParam(Map map, String p) {
+ List