diff --git a/JSD-8688需求确认书.docx b/JSD-8688需求确认书.docx new file mode 100644 index 0000000..012d8fa Binary files /dev/null and b/JSD-8688需求确认书.docx differ diff --git a/README.md b/README.md index 892b6f0..084edf1 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ # open-JSD-8688 -JSD-8688 开源任务材料 \ No newline at end of file +JSD-8688 钉钉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..154bfe4 --- /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 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 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..1892a97 --- /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://1xxxx/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 list = (List) map.get(ScheduleConstants.RECORD_LIST); + Object title = StringUtils.EMPTY; + Object param = list.get(0).get(p); + if (param instanceof Formula) { + Formula formula = (Formula) param; + title = formula.getResult(); + } else { + title = param.toString(); + } + return title.toString(); + } + + private String getUrl(OutputWebHook outputWebHook, Map map) { + 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); + return outputWebHook.getResultURL() + "/url/mobile/schedule/result?taskName=" + taskName + "&username=" + scheduleUsername + "&path=" + path + "&showType=" + showType + "&taskType=" + taskType; + } + + private JSONObject getJSONObject(String md5, String base) { + JSONObject obj = JSONObject.create(); + obj.put("msgtype", "image"); + JSONObject image = JSONObject.create(); + image.put("base64", base); + image.put("md5", md5); + obj.put("image", image); + return obj; + } +} diff --git a/src/main/java/com/fr/plugin/dingding/webhook/js/FileDef.java b/src/main/java/com/fr/plugin/dingding/webhook/js/FileDef.java new file mode 100644 index 0000000..db245c5 --- /dev/null +++ b/src/main/java/com/fr/plugin/dingding/webhook/js/FileDef.java @@ -0,0 +1,54 @@ +package com.fr.plugin.dingding.webhook.js; + +import com.fr.plugin.transform.ExecuteFunctionRecord; +import com.fr.web.struct.Component; +import com.fr.web.struct.Filter; +import com.fr.web.struct.browser.RequestClient; +import com.fr.web.struct.category.ScriptPath; +import com.fr.web.struct.category.StylePath; + +/** + * @author fr.open + * @date 2021/9/15 + */ +public class FileDef extends Component { + public static final FileDef KEY = new FileDef(); + private FileDef(){} + /** + * 返回需要引入的JS脚本路径 + * @param client 请求客户端描述 + * @return JS脚本路径 + */ + @Override + public ScriptPath script(RequestClient client ) { + //如果不需要就直接返回 ScriptPath.EMPTY + return ScriptPath.build("com/fr/plugin/dingding/webhook/theme.js"); + } + + /** + * 返回需要引入的CSS样式路径 + * @param client 请求客户端描述 + * @return CSS样式路径 + */ + @Override + public StylePath style(RequestClient client ) { + //如果不需要就直接返回 StylePath.EMPTY; + return StylePath.EMPTY; + } + + /** + * 通过给定的资源过滤器控制是否加载这个资源 + * @return 资源过滤器 + */ + @ExecuteFunctionRecord + @Override + public Filter filter() { + return new Filter(){ + @Override + public boolean accept() { + //任何情况下我们都在平台组件加载时加载我们的组件 + return true; + } + }; + } +} diff --git a/src/main/java/com/fr/plugin/dingding/webhook/js/JSCSSBridge.java b/src/main/java/com/fr/plugin/dingding/webhook/js/JSCSSBridge.java new file mode 100644 index 0000000..056dea9 --- /dev/null +++ b/src/main/java/com/fr/plugin/dingding/webhook/js/JSCSSBridge.java @@ -0,0 +1,25 @@ +package com.fr.plugin.dingding.webhook.js; + +import com.fr.decision.fun.impl.AbstractWebResourceProvider; +import com.fr.decision.web.MainComponent; +import com.fr.plugin.transform.FunctionRecorder; +import com.fr.web.struct.Atom; + +/** + * @author open + * @date 2021/9/15 + */ +@FunctionRecorder +public class JSCSSBridge extends AbstractWebResourceProvider { + @Override + public Atom attach() { + //在平台主组件加载时添加我们自己的组件 + return MainComponent.KEY; + } + + @Override + public Atom client() { + //我们自己要引入的组件 + return FileDef.KEY; + } +} diff --git a/src/main/java/com/fr/plugin/dingding/webhook/util/DesECBUtil.java b/src/main/java/com/fr/plugin/dingding/webhook/util/DesECBUtil.java new file mode 100644 index 0000000..16db2f9 --- /dev/null +++ b/src/main/java/com/fr/plugin/dingding/webhook/util/DesECBUtil.java @@ -0,0 +1,66 @@ +package com.fr.plugin.dingding.webhook.util; + +import com.fr.third.org.apache.commons.codec.binary.Base64; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import java.security.Key; + +/** + * @Author fr.open + * @Date 2020/9/15 + * @Description + **/ +public class DesECBUtil { + /** + * 加密数据 + * + * @param encryptString + * @param encryptKey + * @return + * @throws Exception + */ + public static String encryptDES(String encryptString, String encryptKey) throws Exception { + Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(getKey(encryptKey), "DES")); + byte[] encryptedData = cipher.doFinal(encryptString.getBytes("UTF-8")); + return Base64.encodeBase64String(encryptedData); + } + + /** + * key 不足8位补位 + * + * @param + */ + public static byte[] getKey(String keyRule) { + Key key = null; + byte[] keyByte = keyRule.getBytes(); + // 创建一个空的八位数组,默认情况下为0 + byte[] byteTemp = new byte[8]; + // 将用户指定的规则转换成八位数组 + for (int i = 0; i < byteTemp.length && i < keyByte.length; i++) { + byteTemp[i] = keyByte[i]; + } + key = new SecretKeySpec(byteTemp, "DES"); + return key.getEncoded(); + } + + /*** + * 解密数据 + * @param decryptString + * @param decryptKey + * @return + * @throws Exception + */ + + public static String decryptDES(String decryptString, String decryptKey) throws Exception { + byte[] sourceBytes = Base64.decodeBase64(decryptString); + Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(getKey(decryptKey), "DES")); + byte[] decoded = cipher.doFinal(sourceBytes); + return new String(decoded, "UTF-8"); + + } + +} + diff --git a/src/main/java/com/fr/plugin/dingding/webhook/util/HttpUtil.java b/src/main/java/com/fr/plugin/dingding/webhook/util/HttpUtil.java new file mode 100644 index 0000000..34ba75f --- /dev/null +++ b/src/main/java/com/fr/plugin/dingding/webhook/util/HttpUtil.java @@ -0,0 +1,133 @@ +package com.fr.plugin.dingding.webhook.util; + +import com.fr.general.PropertiesUtils; +import com.fr.io.utils.ResourceIOUtils; +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.stable.StringUtils; +import com.fr.third.org.apache.http.NameValuePair; +import com.fr.third.org.apache.http.client.entity.UrlEncodedFormEntity; +import com.fr.third.org.apache.http.entity.ContentType; +import com.fr.third.org.apache.http.entity.mime.HttpMultipartMode; +import com.fr.third.org.apache.http.entity.mime.MultipartEntityBuilder; +import com.fr.third.org.apache.http.message.BasicNameValuePair; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @Author fr.open + * @Date 2020/9/15 + * @Description + **/ +public class HttpUtil { + + + public static JSONObject uploadMedia(String path) { + JSONObject result = null; + InputStream fileInputStream = ResourceIOUtils.read(path); + String fileName = ResourceIOUtils.getName(path); + // 定时调度图片没有后缀名,用钉钉上传文件接口以type=image的方式传会返回错误,这里伪造一个后缀 + fileName = addSuffixToScheduleImage(fileName, ".jpg"); + if (fileInputStream == null || StringUtils.isEmpty(fileName)) { + FineLoggerFactory.getLogger().warn(String.format("%s路径下未能找到文件!", path)); + } else { + try { + MultipartEntityBuilder builder = MultipartEntityBuilder.create() + .setMode(HttpMultipartMode.BROWSER_COMPATIBLE) + .setContentType(ContentType.MULTIPART_FORM_DATA) + .addBinaryBody("media", ResourceIOUtils.read(path), ContentType.MULTIPART_FORM_DATA, new String(fileName.getBytes("UTF-8"), "ISO_8859_1")); + Map header = new HashMap(); + header.put("Content-type", "multipart/form-data"); + String res = HttpsUtil.doEntityPost(uploadMediaUrl("image"),null,builder.build()); + result = new JSONObject(res); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(),e); + } + } + return result; + } + + public static String uploadMediaUrl(String type) { + return String.format(getBaseUrl() + "/xxxx=%s&type=%s", getCode(), type); + } + + private static String getCode() { + String property = PropertiesUtils.getProperties("dingtalk").getProperty("yuyuan_syscode"); + return property; + } + + private static String getBaseUrl() { + String property = PropertiesUtils.getProperties("dingtalk").getProperty("host"); + return property; + } + + public static String addSuffixToScheduleImage(String fileName, String suffix) { + if (!StringUtils.contains(fileName, ".")) { + return fileName + suffix; + } + return fileName; + } + + public static boolean isOk(JSONObject result) { + return result != null && result.optInt("code", -999) + ==1; + } + + public static String sendMessageUrl(String token) { + return getBaseUrl() + "/xxxx=" + token; + } + + public static String sendWebhook( String meadia, String hookUrl,String title, String text, String url) throws IOException { + String path = String.format(getBaseUrl() + "/xxxx=%s", getCode()); + + String result = null; + try { + List params = new ArrayList(); + params.add(new BasicNameValuePair("webhook", hookUrl)); + params.add(new BasicNameValuePair("x_yuyuan_sc", getCode())); + params.add(new BasicNameValuePair("msgtype", "card")); + params.add(new BasicNameValuePair("media_id", meadia)); + params.add(new BasicNameValuePair("title", title)); + params.add(new BasicNameValuePair("text", text)); + params.add(new BasicNameValuePair("singleURL", url)); + UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(params, "UTF-8"); + Map header = new HashMap(); + header.put("Content-type", "application/x-www-form-urlencoded"); + result = HttpsUtil.doEntityPost(path,header,urlEncodedFormEntity); + FineLoggerFactory.getLogger().info("send webhook result is {}", result); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + throw e; + } + return result; + } + + public static void getMessage(String msgid) throws Exception { + List params = new ArrayList(); + params.add(new BasicNameValuePair("msg_id", msgid)); + UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(params, "UTF-8"); + Map header = new HashMap(); + header.put("Content-type", "application/x-www-form-urlencoded"); + String result = null; + try { + result = HttpsUtil.doEntityPost(messageUrl(getCode()),header,urlEncodedFormEntity); + FineLoggerFactory.getLogger().info("query message result is {}", result); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + throw e; + } + JSONObject res = new JSONObject(result); + if (res.getInt("code") == 0) { + throw new Exception("get message error is" + res.getString("msg")); + } + } + + private static String messageUrl(String token) { + return getBaseUrl() + "/xxxx=" + token; + } +} diff --git a/src/main/java/com/fr/plugin/dingding/webhook/util/HttpsUtil.java b/src/main/java/com/fr/plugin/dingding/webhook/util/HttpsUtil.java new file mode 100644 index 0000000..8dd3919 --- /dev/null +++ b/src/main/java/com/fr/plugin/dingding/webhook/util/HttpsUtil.java @@ -0,0 +1,346 @@ +package com.fr.plugin.dingding.webhook.util; + +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.third.org.apache.http.HttpEntity; +import com.fr.third.org.apache.http.HttpResponse; +import com.fr.third.org.apache.http.HttpStatus; +import com.fr.third.org.apache.http.client.HttpClient; +import com.fr.third.org.apache.http.client.methods.HttpPost; +import com.fr.third.org.apache.http.config.Registry; +import com.fr.third.org.apache.http.config.RegistryBuilder; +import com.fr.third.org.apache.http.conn.socket.ConnectionSocketFactory; +import com.fr.third.org.apache.http.conn.socket.PlainConnectionSocketFactory; +import com.fr.third.org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import com.fr.third.org.apache.http.entity.StringEntity; +import com.fr.third.org.apache.http.impl.client.HttpClients; +import com.fr.third.org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import com.fr.third.org.apache.http.util.EntityUtils; + +import javax.net.ssl.*; +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.security.cert.CertificateException; +import java.util.Iterator; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @Author fr.open + * @Date 2020/9/15 + * @Description + **/ +public class HttpsUtil { + + private static HostnameVerifier hv = new HostnameVerifier() { + @Override + public boolean verify(String urlHostName, SSLSession session) { + System.out.println("Warning: URL Host: " + urlHostName + " vs. " + + session.getPeerHost()); + return true; + } + }; + + /** + * 发送get请求 + * + * @param url + * @param param + * @param header + * @return + * @throws IOException + */ + public static String sendGet(String url, Map param, Map header) { + String result = ""; + BufferedReader in = null; + String urlNameString = url; + try { + if (param != null) { + urlNameString += "?"; + urlNameString += param.entrySet() + .stream() + .map(entry -> entry.getKey() + "=" + entry.getValue()) + .collect(Collectors.joining("&")); + } + + URL realUrl = new URL(urlNameString); + // 打开和URL之间的连接 + HttpURLConnection connection; + if (url.startsWith("https")) { + trustAllHttpsCertificates(); + HttpsURLConnection.setDefaultHostnameVerifier(hv); + connection = (HttpURLConnection) realUrl.openConnection(); + } else { + connection = (HttpURLConnection) realUrl.openConnection(); + } + //设置超时时间 + connection.setDoInput(true); + connection.setRequestMethod("GET"); + connection.setConnectTimeout(5000); + connection.setReadTimeout(15000); + // 设置通用的请求属性 + if (header != null) { + Iterator> it = header.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = it.next(); + System.out.println(entry.getKey() + ":::" + entry.getValue()); + connection.setRequestProperty(entry.getKey(), entry.getValue()); + } + } + connection.setRequestProperty("accept", "*/*"); + connection.setRequestProperty("connection", "Keep-Alive"); + connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + // 建立实际的连接 + connection.connect(); + // 定义 BufferedReader输入流来读取URL的响应,设置utf8防止中文乱码 + in = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8")); + String line; + while ((line = in.readLine()) != null) { + result += line; + } + if (in != null) { + in.close(); + } + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e, "get url error ,url is:{},error is {}", urlNameString, e.getMessage()); + } + return result; + } + + public static String sendPost(String url, Map header, JSONObject body) { + PrintWriter out = null; + BufferedReader in = null; + String result = null; + String res = null; + try { + String urlNameString = url; + + URL realUrl = new URL(urlNameString); + // 打开和URL之间的连接 + HttpURLConnection conn; + if (url.startsWith("https")) { + trustAllHttpsCertificates(); + HttpsURLConnection.setDefaultHostnameVerifier(hv); + conn = (HttpURLConnection) realUrl.openConnection(); + } else { + conn = (HttpURLConnection) realUrl.openConnection(); + } + // 设置通用的请求属性 + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); +// conn.setRequestProperty("user-agent", +// "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8"); + if (header != null) { + header.forEach((k, v) -> { + conn.setRequestProperty(k, String.valueOf(v)); + }); + } + // 发送POST请求必须设置如下两行 + conn.setDoOutput(true); + conn.setDoInput(true); + //获取请求头 + + // 获取URLConnection对象对应的输出流 + out = new PrintWriter(conn.getOutputStream()); + // 发送请求参数 + if (body != null) { + FineLoggerFactory.getLogger().error("content data: {}", body.toString()); + FineLoggerFactory.getLogger().error("content cover data: {}", new String(body.toString().getBytes("UTF-8"), "UTF-8")); + out.print(new String(body.toString().getBytes("UTF-8"), "UTF-8")); + } + // flush输出流的缓冲 + out.flush(); + // 定义BufferedReader输入流来读取URL的响应 + in = new BufferedReader( + new InputStreamReader(conn.getInputStream())); + String line; + while ((line = in.readLine()) != null) { + result += line; + } + res = result; + if (res.startsWith("null")) { + res = res.replace("null", ""); + } + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + } + //使用finally块来关闭输出流、输入流 + finally { + try { + if (out != null) { + out.close(); + } + if (in != null) { + in.close(); + } + } catch (IOException e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + } + } + return res; + } + + + public static String doPost(String url, Map header, JSONObject json) { + HttpClient client = HttpClients.createDefault(); + if (url.startsWith("https")) { + SSLContext sslcontext = createIgnoreVerifySSL(); + Registry socketFactoryRegistry = RegistryBuilder.create() + .register("http", PlainConnectionSocketFactory.INSTANCE) + .register("https", new SSLConnectionSocketFactory(sslcontext)) + .build(); + PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry); + HttpClients.custom().setConnectionManager(connManager); + client = HttpClients.custom().setConnectionManager(connManager).build(); + } + HttpPost post = new HttpPost(url); + post.setHeader("accept", "*/*"); + post.setHeader("connection", "Keep-Alive"); + post.setHeader("Content-Type", "application/json"); + if (header != null) { + header.forEach((k, v) -> { + post.setHeader(k, String.valueOf(v)); + }); + } + try { + StringEntity s = new StringEntity(json.toString(),"UTF-8"); + s.setContentEncoding("UTF-8"); + s.setContentType("application/json; charset=UTF-8");//发送json数据需要设置contentType + post.setEntity(s); + HttpResponse res = client.execute(post); + if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + String result = EntityUtils.toString(res.getEntity());// 返回json格式: + return result; + } + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(),e); + } + return null; + } + + + public static String doEntityPost(String url, Map header, HttpEntity entity) { + HttpClient client = HttpClients.createDefault(); + if (url.startsWith("https")) { + SSLContext sslcontext = createIgnoreVerifySSL(); + Registry socketFactoryRegistry = RegistryBuilder.create() + .register("http", PlainConnectionSocketFactory.INSTANCE) + .register("https", new SSLConnectionSocketFactory(sslcontext)) + .build(); + PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry); + HttpClients.custom().setConnectionManager(connManager); + client = HttpClients.custom().setConnectionManager(connManager).build(); + } + HttpPost post = new HttpPost(url); + post.setHeader("accept", "*/*"); + post.setHeader("connection", "Keep-Alive"); + if (header != null) { + header.forEach((k, v) -> { + post.setHeader(k, String.valueOf(v)); + }); + } + try { + post.setEntity(entity); + HttpResponse res = client.execute(post); + if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + String result = EntityUtils.toString(res.getEntity());// 返回json格式: + return result; + } + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(),e); + } + return null; + } + + private static void trustAllHttpsCertificates() throws Exception { + TrustManager[] trustAllCerts = new TrustManager[1]; + TrustManager tm = new miTM(); + trustAllCerts[0] = tm; + SSLContext sc = SSLContext.getInstance("SSL", "SunJSSE"); + sc.init(null, trustAllCerts, null); + HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); + } + + + /** + * encode url by UTF-8 + * + * @param url url before encoding + * @return url after encoding + */ + public static String encodeUrl(String url) { + String eurl = url; + try { + eurl = URLEncoder.encode(url, "UTF-8"); + } catch (UnsupportedEncodingException e) { + } + return eurl; + } + + private static class miTM implements TrustManager, + X509TrustManager { + @Override + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return null; + } + + public boolean isServerTrusted( + java.security.cert.X509Certificate[] certs) { + return true; + } + + public boolean isClientTrusted( + java.security.cert.X509Certificate[] certs) { + return true; + } + + @Override + public void checkServerTrusted( + java.security.cert.X509Certificate[] certs, String authType) + throws CertificateException { + return; + } + + @Override + public void checkClientTrusted( + java.security.cert.X509Certificate[] certs, String authType) + throws CertificateException { + return; + } + } + + public static SSLContext createIgnoreVerifySSL() { + try { + SSLContext sc = SSLContext.getInstance("SSLv3"); + + // 实现一个X509TrustManager接口,用于绕过验证,不用修改里面的方法 + X509TrustManager trustManager = new X509TrustManager() { + @Override + public void checkClientTrusted( + java.security.cert.X509Certificate[] paramArrayOfX509Certificate, + String paramString) throws CertificateException { + } + + @Override + public void checkServerTrusted( + java.security.cert.X509Certificate[] paramArrayOfX509Certificate, + String paramString) throws CertificateException { + } + + @Override + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return null; + } + }; + + sc.init(null, new TrustManager[]{trustManager}, null); + return sc; + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + } + return null; + } +} diff --git a/src/main/resources/com/fr/plugin/dingding/webhook/theme.js b/src/main/resources/com/fr/plugin/dingding/webhook/theme.js new file mode 100644 index 0000000..f44350f --- /dev/null +++ b/src/main/resources/com/fr/plugin/dingding/webhook/theme.js @@ -0,0 +1,71 @@ +!(function () { + BI.config("dec.provider.schedule", function (provider) { + provider.registerTaskAttached({ + value: 64, + text: "png" + }, [DecCst.Schedule.TaskType.REPORT, DecCst.Schedule.TaskType.BI]); + }); + + var Plugin = BI.inherit(BI.Widget, { + props: { + baseCls: "", + value: { + hookUrl: null, + } + }, + render: function () { + var t = this, + e = this.options.value[0]==undefined?{hookUrl: null}: this.options.value[0]; + return { + type: "bi.flex_vertical", + tgap: 15, + items: [{ + type: "dec.label.editor.item", + errorTop: 16, + textCls: "dec-font-weight-bold", + text: "webHookUrl", + textWidth: 115, + editorWidth: 300, + value: e.hookUrl, + ref: function(e) { + t.hookUrl = e + } + }] + }; + }, + /** + * + * + * @returns {boolean} + */ + validation: function () { + var e = !0, + t = this.isVisible(); + return BI.isEmpty(this.hookUrl.getValue()) && (t && this.hookUrl.showError(BI.i18nText("Dec-Error_Null")), e = !1), + e + }, + /** + * + * outputActionList + * @returns {{}} + */ + getValue: function() { + var _self= this; + return {OutputWebHook: BI.extend(_self.value,{ + "@class": "com.fr.plugin.dingding.webhook.entity.OutputWebHook", + actionName: "com.fr.plugin.dingding.webhook.entity.OutputWebHook", + hookUrl: _self.hookUrl.getValue(), + })} + }, + }); + BI.shortcut("dec.schedule.task.file.handling.plugin", Plugin); + + BI.config("dec.provider.schedule", function (provider) { + provider.registerHandingWay({ + text: "webHook", + value: "com.fr.plugin.dingding.webhook.entity.OutputWebHook", // actionName + cardType: "dec.schedule.task.file.handling.plugin", + actions: [] // action + }, [DecCst.Schedule.TaskType.DEFAULT, DecCst.Schedule.TaskType.REPORT, DecCst.Schedule.TaskType.BI]); + }); +}());