diff --git a/README.md b/README.md index 89e3c2e..ec42425 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ # open-JSD-7874 -JSD-7874 政务钉钉集成 开源材料 \ No newline at end of file +JSD-7874 政务钉钉集成 开源材料\ +免责说明:该源码为第三方爱好者提供,不保证源码和方案的可靠性,也不提供任何形式的源码教学指导和协助!\ +仅作为开发者学习参考使用!禁止用于任何商业用途!\ +为保护开发者隐私,开发者信息已隐去!若原开发者希望公开自己的信息,可联系hugh处理。 \ No newline at end of file diff --git a/lib/commons-codec-1.15.jar b/lib/commons-codec-1.15.jar new file mode 100644 index 0000000..f14985a Binary files /dev/null and b/lib/commons-codec-1.15.jar differ diff --git a/lib/commons-lang3-3.10.jar b/lib/commons-lang3-3.10.jar new file mode 100644 index 0000000..e51c729 Binary files /dev/null and b/lib/commons-lang3-3.10.jar differ diff --git a/lib/commons-text-1.9.jar b/lib/commons-text-1.9.jar new file mode 100644 index 0000000..cc0c690 Binary files /dev/null and b/lib/commons-text-1.9.jar differ diff --git a/lib/httpclient-4.5.5.jar b/lib/httpclient-4.5.5.jar new file mode 100644 index 0000000..7796b0e Binary files /dev/null and b/lib/httpclient-4.5.5.jar differ diff --git a/lib/httpcore-4.4.9.jar b/lib/httpcore-4.4.9.jar new file mode 100644 index 0000000..cddba28 Binary files /dev/null and b/lib/httpcore-4.4.9.jar differ diff --git a/lib/joda-time-2.10.jar b/lib/joda-time-2.10.jar new file mode 100644 index 0000000..1909c2f Binary files /dev/null and b/lib/joda-time-2.10.jar differ diff --git a/lib/zwdd-sdk-java-1.2.0.jar b/lib/zwdd-sdk-java-1.2.0.jar new file mode 100644 index 0000000..d2ad279 Binary files /dev/null and b/lib/zwdd-sdk-java-1.2.0.jar differ diff --git a/plugin.xml b/plugin.xml new file mode 100644 index 0000000..0d95de4 --- /dev/null +++ b/plugin.xml @@ -0,0 +1,20 @@ + + com.fr.plugin.zwddscan + + yes + 1.0 + 10.0 + 2020-07-31 + fr.open + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/GlobalRequestFilterPlaceHolder.java b/src/main/java/com/fr/plugin/GlobalRequestFilterPlaceHolder.java new file mode 100644 index 0000000..f8981a9 --- /dev/null +++ b/src/main/java/com/fr/plugin/GlobalRequestFilterPlaceHolder.java @@ -0,0 +1,110 @@ +package com.fr.plugin; + +import com.fr.decision.ExtraDecisionClassManager; +import com.fr.decision.fun.GlobalRequestFilterProvider; +import com.fr.decision.fun.impl.AbstractGlobalRequestFilterProvider; +import com.fr.event.Event; +import com.fr.event.EventDispatcher; +import com.fr.event.Listener; +import com.fr.log.FineLoggerFactory; +import com.fr.plugin.context.PluginContext; +import com.fr.plugin.injectable.PluginModule; +import com.fr.plugin.observer.PluginEventType; +import com.fr.stable.StringUtils; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Set; + +/** + * @author Peng + */ +public class GlobalRequestFilterPlaceHolder extends AbstractGlobalRequestFilterProvider { + private static final String CURRENT_PLUGIN_ID = "com.fr.plugin.zwddscan";//需要求个这两个配置 + private static final String CURRENT_FILTER_NAME = "ddzwLogin"; + private static GlobalRequestFilterProvider PLACE_HOLDER_IMPL_FILTER; + + @Override + public void init(FilterConfig filterConfig) { + Set providers = ExtraDecisionClassManager.getInstance().getArray(GlobalRequestFilterProvider.MARK_STRING); + if (providers != null) { + for (GlobalRequestFilterProvider provider : providers) { + String filterName = provider.filterName(); + if (StringUtils.isNotEmpty(filterName) && CURRENT_FILTER_NAME.equals(filterName)) { + PLACE_HOLDER_IMPL_FILTER = provider; + break; + } + } + } + + com.fr.stable.Filter filter = new com.fr.stable.Filter() { + @Override + public boolean accept(PluginContext context) { + String pluginId = context.getID(); + return context.contain(PluginModule.ExtraDecision, GlobalRequestFilterProvider.MARK_STRING) && CURRENT_PLUGIN_ID.equals(pluginId); + } + }; + + EventDispatcher.listen(PluginEventType.AfterRun, new Listener() { + @Override + public void on(Event event, PluginContext context) { + Set providers = context.getRuntime().get(PluginModule.ExtraDecision, GlobalRequestFilterProvider.MARK_STRING); + if (providers != null) { + for (GlobalRequestFilterProvider provider : providers) { + String filterName = provider.filterName(); + if (StringUtils.isNotEmpty(filterName) && CURRENT_FILTER_NAME.equals(filterName)) { + PLACE_HOLDER_IMPL_FILTER = provider; + break; + } + } + } + } + }, filter); + + EventDispatcher.listen(PluginEventType.BeforeStop, new Listener() { + @Override + public void on(Event event, PluginContext context) { + PLACE_HOLDER_IMPL_FILTER = null; + } + }, filter); + } + + @Override + public String filterName() { + return "GlobalRequestFilterPlaceHolder"; + } + + @Override + public String[] urlPatterns() { + return new String[]{ + "/*" + }; + } + + @Override + public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) { + try { + process(request, response, filterChain); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e, e.getMessage()); + try { + filterChain.doFilter(request, response); + } catch (Exception ex) { + FineLoggerFactory.getLogger().error(ex, ex.getMessage()); + } + } + } + + public void process(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws Exception { + if (PLACE_HOLDER_IMPL_FILTER == null) { + if(FineLoggerFactory.getLogger().isDebugEnabled()) { + FineLoggerFactory.getLogger().debug("[GlobalRequestFilterPlaceHolder] placeHolderImplFilter 为 null"); + } + filterChain.doFilter(request, response); + } else { + PLACE_HOLDER_IMPL_FILTER.doFilter(request, response, filterChain); + } + } +} diff --git a/src/main/java/com/fr/plugin/HttpApi.java b/src/main/java/com/fr/plugin/HttpApi.java new file mode 100644 index 0000000..cdcb716 --- /dev/null +++ b/src/main/java/com/fr/plugin/HttpApi.java @@ -0,0 +1,241 @@ +package com.fr.plugin; + +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.third.fasterxml.jackson.core.JsonGenerationException; +import com.fr.third.fasterxml.jackson.databind.JsonMappingException; +import com.fr.third.fasterxml.jackson.databind.ObjectMapper; + +import javax.net.ssl.*; +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.security.cert.X509Certificate; +import java.util.Base64; + +public class HttpApi { + public HttpApi() { + } + + private static ObjectMapper objectMapper = new ObjectMapper(); + + public static String serialize(Object object) { + Writer write = new StringWriter(); + try { + objectMapper.writeValue(write, object); + } catch (JsonGenerationException e) { + } catch (JsonMappingException e) { + } catch (IOException e) { + } + return write.toString(); + } + + private final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() { + public boolean verify(String hostname, SSLSession session) { + return true; + } + }; + + private static void trustAllHosts() { + // Create a trust manager that does not validate certificate chains + TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[] {}; + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) { + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) { + } + } }; + // Install the all-trusting trust manager + try { + SSLContext sc = SSLContext.getInstance("TLS"); + sc.init(null, trustAllCerts, new java.security.SecureRandom()); + HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static String sendPostByForm(String url, String params) { + PrintWriter out = null; + BufferedReader in = null; + String result = ""; + try { + trustAllHosts(); + URL realUrl = new URL(url); + URLConnection conn = realUrl.openConnection(); + conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + if (realUrl.getProtocol().equalsIgnoreCase("https")) { + HttpsURLConnection https = (HttpsURLConnection) realUrl.openConnection(); + https.setHostnameVerifier(DO_NOT_VERIFY); + conn = https; + } else { + conn = realUrl.openConnection(); + } + conn.setDoOutput(true); + conn.setDoInput(true); + + out = new PrintWriter(conn.getOutputStream()); + + out.print(params); + + out.flush(); + + in = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String line; + while ((line = in.readLine()) != null) { + result = result + line; + } + return result; + } catch (Exception e) { + printexception2Fr(e); + } finally { + try { + if (out != null) { + out.close(); + } + if (in != null) { + in.close(); + } + } catch (IOException ex) { + printexception2Fr(ex); + } + } + return null; + } + + public static void printexception2Fr (Exception e) { + StringWriter writer = new StringWriter(); + e.printStackTrace(new PrintWriter(writer)); + String s = writer.toString(); + FineLoggerFactory.getLogger().error("错误:{}", s); + } + + + public static String sendJsonPost(String var0, JSONObject var1, String var2) { + PrintWriter var3 = null; + BufferedReader var4 = null; + HttpURLConnection var5 = null; + String var6 = ""; + + try { + URL var7 = new URL(var0); + var5 = (HttpURLConnection) var7.openConnection(); + var5.setRequestProperty("Content-Type", "application/json;charset=utf8"); +// var5.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=utf8"); + var5.setRequestProperty("accept", "*/*"); + var5.setRequestProperty("connection", "Keep-Alive"); + var5.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + var5.setRequestProperty("Accept-Charset", var2); + var5.setRequestMethod("POST"); + var5.setDoOutput(true); + var5.setDoInput(true); + var3 = new PrintWriter(var5.getOutputStream()); + var3.print(var1.toString()); + var3.flush(); + + String var8; + for (var4 = new BufferedReader(new InputStreamReader(var5.getInputStream(), var2)); (var8 = var4.readLine()) != null; var6 = var6 + var8) { + ; + } + } catch (Exception var17) { + var17.printStackTrace(); + } finally { + try { + if (var3 != null) { + var3.close(); + } + + if (var4 != null) { + var4.close(); + } + } catch (Exception var16) { + ; + } + + var5.disconnect(); + } + + System.out.println(var6); + return var6; + } + /** + * 发送传统的表单提交 + * + * @param url + * @return + */ + public static String sendGet(String url) { + StringBuilder sb = new StringBuilder(); + PrintWriter out = null; + BufferedReader in = null; + HttpURLConnection conn = null; + try { + URL realUrl = new URL(url); + // 打开和URL之间的连接 + conn = (HttpURLConnection) realUrl.openConnection(); + // 设置通用的请求属性 + conn.setRequestProperty("accept", "*/*"); + conn.setRequestMethod("GET"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + conn.setConnectTimeout(100); + conn.setReadTimeout(200); + // 发送POST请求必须设置如下两行 + conn.setDoOutput(true); + conn.setDoInput(true); + // 获取URLConnection对象对应的输出流 + out = new PrintWriter(conn.getOutputStream()); + + // flush输出流的缓冲 + out.flush(); + // 定义BufferedReader输入流来读取URL的响应 + in = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String line; + sb = new StringBuilder(); + while ((line = in.readLine()) != null) { + sb.append(line); + } + } catch (Exception e) { + if (conn != null) { + InputStream errorStream = ( conn).getErrorStream(); + if (errorStream != null) { + in = new BufferedReader(new InputStreamReader(errorStream)); + String line; + sb = new StringBuilder(); + try { + while ((line = in.readLine()) != null) { + sb.append(line); + } + } catch (Exception ee) { + FineLoggerFactory.getLogger().error("", ee); + } + FineLoggerFactory.getLogger().error("错误响应:=======》" + sb); + } + } + FineLoggerFactory.getLogger().error("发送 GET 请求出现异常!", e); + } + // 使用finally块来关闭输出流、输入流 + finally { + try { + if (out != null) { + out.close(); + } + if (in != null) { + in.close(); + } + } catch (IOException ex) { + FineLoggerFactory.getLogger().error("", ex); + } + } + return sb.toString(); + } + +} diff --git a/src/main/java/com/fr/plugin/ZWDDLifeCycleMonitor.java b/src/main/java/com/fr/plugin/ZWDDLifeCycleMonitor.java new file mode 100644 index 0000000..3746e9a --- /dev/null +++ b/src/main/java/com/fr/plugin/ZWDDLifeCycleMonitor.java @@ -0,0 +1,20 @@ +package com.fr.plugin; + +import com.fr.plugin.context.PluginContext; +import com.fr.plugin.observer.inner.AbstractPluginLifecycleMonitor; +import com.fr.plugin.transform.ExecuteFunctionRecord; +import com.fr.plugin.transform.FunctionRecorder; + +@FunctionRecorder +public class ZWDDLifeCycleMonitor extends AbstractPluginLifecycleMonitor { + + @Override + @ExecuteFunctionRecord + public void afterRun(PluginContext pluginContext) { + + } + + @Override + public void beforeStop(PluginContext pluginContext) { + } +} diff --git a/src/main/java/com/fr/plugin/ZWDDScanConfig.java b/src/main/java/com/fr/plugin/ZWDDScanConfig.java new file mode 100644 index 0000000..5f26bd6 --- /dev/null +++ b/src/main/java/com/fr/plugin/ZWDDScanConfig.java @@ -0,0 +1,57 @@ +package com.fr.plugin; + +import com.fr.config.*; +import com.fr.config.holder.Conf; +import com.fr.config.holder.factory.Holders; + +@Visualization(category = "政务钉钉扫码配置") +public class ZWDDScanConfig extends DefaultConfiguration { + + private static volatile ZWDDScanConfig config = null; + + public static ZWDDScanConfig getInstance() { + if (config == null) { + config = ConfigContext.getConfigInstance(ZWDDScanConfig.class); + } + return config; + } + @Identifier(value = "appKey", name = "钉钉appkey", description = "",status = Status.SHOW) + private Conf appKey = Holders.simple(""); + @Identifier(value = "secret", name = "钉钉appsecret", description = "",status = Status.SHOW) + private Conf appsecret = Holders.simple(""); + @Identifier(value = "domainName", name = "钉钉域名", description = "",status = Status.SHOW) + private Conf domainName = Holders.simple("openplatform.dg-work.cn"); + + public String getAppKey() { + return appKey.get(); + } + + public void setAppKey(String appKey) { + this.appKey .set(appKey); + } + + public String getAppsecret() { + return appsecret.get(); + } + + public void setAppsecret(String appsecret) { + this.appsecret .set(appsecret); + } + + public String getDomainName() { + return domainName.get(); + } + + public void setDomainName(String domainName) { + this.domainName .set(domainName); + } + + @Override + public Object clone() throws CloneNotSupportedException { + ZWDDScanConfig clone = (ZWDDScanConfig) super.clone(); + clone.appKey = (Conf) this.appKey.clone(); + clone.appsecret = (Conf) this.appKey.clone(); + clone.domainName = (Conf) this.appKey.clone(); + return clone; + } +} diff --git a/src/main/java/com/fr/plugin/ZWDDloginFilter.java b/src/main/java/com/fr/plugin/ZWDDloginFilter.java new file mode 100644 index 0000000..c240da2 --- /dev/null +++ b/src/main/java/com/fr/plugin/ZWDDloginFilter.java @@ -0,0 +1,208 @@ +package com.fr.plugin; + +import com.alibaba.xxpt.gateway.shared.client.http.ExecutableClient; +import com.alibaba.xxpt.gateway.shared.client.http.GetClient; +import com.alibaba.xxpt.gateway.shared.client.http.PostClient; +import com.fr.decision.fun.impl.AbstractGlobalRequestFilterProvider; +import com.fr.decision.webservice.bean.authentication.OriginUrlResponseBean; +import com.fr.decision.webservice.utils.DecisionStatusService; +import com.fr.decision.webservice.v10.login.LoginService; +import com.fr.general.ComparatorUtils; +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.plugin.transform.FunctionRecorder; +import com.fr.third.org.apache.commons.lang3.StringUtils; +import com.fr.web.utils.WebUtils; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +/** + * 参考实现: + * https://openplatform-portal.dg-work.cn/portal/#/helpdoc?docKey=kfzn&slug=engk1k + */ +@FunctionRecorder(localeKey = "fr2") +public class ZWDDloginFilter extends AbstractGlobalRequestFilterProvider { + @Override + public String filterName() { + return "ddzwLogin"; + } + + @Override + public String[] urlPatterns() { + return new String[]{ + "/ds22222" + }; + } + + @Override + public void init(FilterConfig filterConfig) { + FineLoggerFactory.getLogger().info("政务钉钉扫码扫码初始化"); + ZWDDScanConfig.getInstance(); + super.init(filterConfig); + } + + private String getToAuthUrl(HttpServletRequest request) { + String url = "https://xxx.zzl.ink:8080/webroot/decision/url/ddcallback"; + String apiUrl = "https://login.xxx-work.cn/oauth2/auth.htm"; + String clientId = "frt2"; + String requestURI = ""; + try { + requestURI = URLEncoder.encode(url, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return apiUrl + "?response_type=code&client_id=" + clientId + "&redirect_uri=" + requestURI + "&scope=get_user_info&authType=QRCODE"; + } + + @Override + public void doFilter(HttpServletRequest request, HttpServletResponse httpServletResponse, FilterChain filterChain) { + try { + if (ComparatorUtils.equals(request.getMethod(), "GET") && "/login".equals(request.getPathInfo())) { + String isAdmin = request.getParameter("isAdmin"); + if (ComparatorUtils.equals(isAdmin, "1")) { + try { + filterChain.doFilter(request, httpServletResponse); + } catch (IOException e) { + printException2FrLog(e); + } catch (ServletException e) { + printException2FrLog(e); + } + return; + } + String original = WebUtils.getHTTPRequestParameter(request, "origin"); + try { + String originUrl = ((OriginUrlResponseBean) DecisionStatusService.originUrlStatusService().get(original)).getOriginUrl(); + FineLoggerFactory.getLogger().error("---------回调地址---------:{}", original); + request.getSession(true).setAttribute("callBack", originUrl); + } catch (Exception e) { + e.printStackTrace(); + } + sendRedirect(httpServletResponse, getToAuthUrl(request)); + return; + } + if (ComparatorUtils.equals(request.getMethod(), "GET") && "/url/ddcallback".equals(request.getPathInfo())) { + String code = request.getParameter("code"); + FineLoggerFactory.getLogger().error("---------访问回调地址了---------:{}", code); + if (StringUtils.isNotBlank(code)) { + //通过code进行登录 + String userName = getUserName(code); + if (StringUtils.isNotBlank(userName)) { + login(request, httpServletResponse, userName); + //跳回页面 + Object callBack = request.getSession().getAttribute("callBack"); + if (callBack != null) { + String url = callBack.toString(); + try { + FineLoggerFactory.getLogger().error("---------回调地址成功的地址---------:{}", url); + sendRedirect(httpServletResponse, url); + } catch (Exception e) { + e.printStackTrace(); + } + } else { + sendRedirect(httpServletResponse, getHomeURI()); + return; + } + }else{ + sendRedirect(httpServletResponse, getToAuthUrl(request)); + } + } + } + filterChain.doFilter(request, httpServletResponse); + } catch (IOException | ServletException e) { + printException2FrLog(e); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private String getHomeURI() { + return "/webroot/decision"; + } + + private String getUserName(String code) { + try { + ZWDDScanConfig instance = ZWDDScanConfig.getInstance(); + String appkey = instance.getAppKey(); + String appsecret = instance.getAppsecret(); + //第一步获取accessToken + ExecutableClient executableClient = ExecutableClient.getInstance(); + executableClient.setAccessKey(appkey); + executableClient.setSecretKey(appsecret); + executableClient.setDomainName(instance.getDomainName()); + executableClient.setProtocal("https"); + executableClient.init(); +//executableClient要单例,并且使用前要初始化,只需要初始化一次 + + String api = "/gettoken.json"; + GetClient getClient = executableClient.newGetClient(api); +//设置参数 + getClient.addParameter("appkey", appkey); + getClient.addParameter("appsecret", appsecret); +//调用API + String sendGet = getClient.get(); + JSONObject entries = new JSONObject(sendGet); + boolean success = entries.getBoolean("success"); + if (success) { + String accessToken = entries.getJSONObject("content").getJSONObject("data").getString("accessToken"); + api = "/rpc/oauth2/getuserinfo_bycode.json"; + PostClient postClient = executableClient.newPostClient(api); + //设置参数 + postClient.addParameter("access_token", accessToken); + postClient.addParameter("code", code); + String body = postClient.post(); + FineLoggerFactory.getLogger().info("获取用户信息请求响应 {} ", body); + FineLoggerFactory.getLogger().info("请求响应 {} ", body); + JSONObject jsonObject = new JSONObject(body); + if (jsonObject.getBoolean("success")) { + return jsonObject.getJSONObject("content").getJSONObject("data").getString("account"); + } + FineLoggerFactory.getLogger().info("登录获取用户信息失败"); + } else { + FineLoggerFactory.getLogger().info("登录获取的accessToken 失败 "); + } + } catch (Exception e) { + printException2FrLog(e); + } + return ""; + } + + + public static void printException2FrLog(Throwable e) { + StringWriter writer = new StringWriter(); + e.printStackTrace(new PrintWriter(writer)); + String s = writer.toString(); + FineLoggerFactory.getLogger().error("错误:{}", s); + } + + private void sendRedirect(HttpServletResponse res, String url) { + res.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); + res.setHeader("Location", url); + } + + + private void login(HttpServletRequest req, HttpServletResponse res, String username) { + String token = null; + try { + token = LoginService.getInstance().login(req, res, username); + req.setAttribute("fine_auth_token", token); + FineLoggerFactory.getLogger().error("login success"); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + FineLoggerFactory.getLogger().error("login failed"); + } + } + + private boolean isLogin(HttpServletRequest req) { + return LoginService.getInstance().isLogged(req); + } +} diff --git a/src/main/resources/com/fr/plugin/demo.properties b/src/main/resources/com/fr/plugin/demo.properties new file mode 100644 index 0000000..9bc5f29 --- /dev/null +++ b/src/main/resources/com/fr/plugin/demo.properties @@ -0,0 +1 @@ +Plugin-Test_Function_Abs=Test ABS \ No newline at end of file diff --git a/src/main/resources/com/fr/plugin/demo_zh_CN.properties b/src/main/resources/com/fr/plugin/demo_zh_CN.properties new file mode 100644 index 0000000..aa910e8 --- /dev/null +++ b/src/main/resources/com/fr/plugin/demo_zh_CN.properties @@ -0,0 +1 @@ +Plugin-Test_Function_Abs=测试ABS函数 \ No newline at end of file diff --git a/政务钉钉扫码插件使用手册.docx b/政务钉钉扫码插件使用手册.docx new file mode 100644 index 0000000..18b9f94 Binary files /dev/null and b/政务钉钉扫码插件使用手册.docx differ