commit ac664f8b997f121996eb0a214602e8011904ec18 Author: pioneer Date: Tue Nov 29 16:19:13 2022 +0800 open diff --git a/README.md b/README.md new file mode 100644 index 0000000..cbada4b --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# open-JSD-10219 + +JSD-10219 竹云单点集成\ +免责说明:该源码为第三方爱好者提供,不保证源码和方案的可靠性,也不提供任何形式的源码教学指导和协助!\ +仅作为开发者学习参考使用!禁止用于任何商业用途!\ +为保护开发者隐私,开发者信息已隐去!若原开发者希望公开自己的信息,可联系【pioneer】处理。 \ No newline at end of file diff --git a/lib/finekit-10.0.jar b/lib/finekit-10.0.jar new file mode 100644 index 0000000..611c8f5 Binary files /dev/null and b/lib/finekit-10.0.jar differ diff --git a/lib/gson-2.8.6.jar b/lib/gson-2.8.6.jar new file mode 100644 index 0000000..4765c4a Binary files /dev/null and b/lib/gson-2.8.6.jar differ diff --git a/plugin.xml b/plugin.xml new file mode 100644 index 0000000..f4bc4a0 --- /dev/null +++ b/plugin.xml @@ -0,0 +1,23 @@ + + com.fr.plugin.third.party.jsdbacbj + + yes + 1.0.1 + 10.0 + 2019-01-01 + fr.open + + + ]]> + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/third/party/jsdbacbj/OutputPluginLifecycleMonitor.java b/src/main/java/com/fr/plugin/third/party/jsdbacbj/OutputPluginLifecycleMonitor.java new file mode 100644 index 0000000..a62b6ba --- /dev/null +++ b/src/main/java/com/fr/plugin/third/party/jsdbacbj/OutputPluginLifecycleMonitor.java @@ -0,0 +1,27 @@ +package com.fr.plugin.third.party.jsdbacbj; + +import com.fr.intelli.record.Focus; +import com.fr.intelli.record.Original; +import com.fr.plugin.context.PluginContext; +import com.fr.plugin.observer.inner.AbstractPluginLifecycleMonitor; +import com.fr.plugin.third.party.jsdbacbj.config.CustomDataConfig; +import com.fr.record.analyzer.EnableMetrics; +import com.fr.stable.fun.Authorize; + + +/** + * 配置信息初始化 + */ +@EnableMetrics +@Authorize(callSignKey = "com.fr.plugin.third.party.jsdbacbj") +public class OutputPluginLifecycleMonitor extends AbstractPluginLifecycleMonitor { + @Override + @Focus(id = "com.fr.plugin.third.party.jsdbacbj", text = "plugin-jsdbacbj", source = Original.PLUGIN) + public void afterRun(PluginContext pluginContext) { + CustomDataConfig.getInstance(); + } + + @Override + public void beforeStop(PluginContext pluginContext) { + } +} diff --git a/src/main/java/com/fr/plugin/third/party/jsdbacbj/Utils.java b/src/main/java/com/fr/plugin/third/party/jsdbacbj/Utils.java new file mode 100644 index 0000000..5960410 --- /dev/null +++ b/src/main/java/com/fr/plugin/third/party/jsdbacbj/Utils.java @@ -0,0 +1,355 @@ +package com.fr.plugin.third.party.jsdbacbj; + +import com.fanruan.api.log.LogKit; +import com.fanruan.api.util.StringKit; +import com.fr.decision.authority.data.User; +import com.fr.decision.webservice.bean.user.UserBean; +import com.fr.decision.webservice.bean.user.UserUpdateBean; +import com.fr.decision.webservice.impl.user.UserPageQueryAuthorityParam; +import com.fr.decision.webservice.utils.ControllerFactory; +import com.fr.decision.webservice.utils.controller.UserController; +import com.fr.decision.webservice.v10.user.UserService; +import com.fr.plugin.third.party.jsdbacbj.config.CustomDataConfig; +import com.fr.stable.StringUtils; +import com.fr.stable.query.data.DataList; +import com.fr.stable.query.restriction.Restriction; +import com.fr.third.org.apache.http.HttpEntity; +import com.fr.third.org.apache.http.HttpStatus; +import com.fr.third.org.apache.http.client.config.RequestConfig; +import com.fr.third.org.apache.http.client.methods.CloseableHttpResponse; +import com.fr.third.org.apache.http.client.methods.HttpGet; +import com.fr.third.org.apache.http.client.methods.HttpPost; +import com.fr.third.org.apache.http.conn.ssl.NoopHostnameVerifier; +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.CloseableHttpClient; +import com.fr.third.org.apache.http.impl.client.HttpClients; +import com.fr.third.org.apache.http.ssl.SSLContextBuilder; +import com.fr.third.org.apache.http.ssl.TrustStrategy; +import com.fr.third.org.apache.http.util.EntityUtils; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class Utils { + public static String DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"; + public static RequestConfig REQUEST_CONFIG = RequestConfig.custom() + .setConnectionRequestTimeout(30000) + .setSocketTimeout(30000) // 服务端相应超时 + .setConnectTimeout(30000) // 建立socket链接超时时间 + .build(); + + public static CloseableHttpClient createSslHttpClient() { + try { + SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() { + + @Override + public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException { + return true; + } + }).build(); + HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE; + SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, hostnameVerifier); + return HttpClients.custom() + .setSSLSocketFactory(sslsf) + .build(); + } catch (Exception e) { + LogKit.error(e.getMessage(), e); + } + return HttpClients.createDefault(); + } + + public static CloseableHttpClient createDefaultHttpClient() { + CloseableHttpClient httpClient = HttpClients.custom() + .build(); + return httpClient; + } + + public static synchronized CloseableHttpClient createHttpClient(String url) { + CloseableHttpClient httpClient = null; + if (StringKit.isEmpty(url)) { + httpClient = createDefaultHttpClient(); + return httpClient; + } + + if (url.startsWith("https://")) { + httpClient = createSslHttpClient(); + return httpClient; + } + httpClient = createDefaultHttpClient(); + return httpClient; + } + + public static synchronized String createHttpGetContent(CloseableHttpClient httpClient, String url, String basicAuth) throws IOException { + if ((httpClient == null) || (StringKit.isEmpty(url))) { + return ""; + } + + HttpGet httpGet = new HttpGet(url); + httpGet.addHeader("User-Agent", Utils.DEFAULT_USER_AGENT); + if (StringKit.isNotEmpty(basicAuth)) { + httpGet.addHeader("Authorization", basicAuth); + } + + httpGet.setConfig(Utils.REQUEST_CONFIG); + CloseableHttpResponse response = httpClient.execute(httpGet); + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode != HttpStatus.SC_OK) { + response.close(); + LogKit.info("http请求出错,http status:" + statusCode); + return ""; + } + + HttpEntity httpEntity = response.getEntity(); + if (httpEntity == null) { + response.close(); + LogKit.info("http请求出错,http响应内容为空"); + return ""; + } + String responseContent = EntityUtils.toString(httpEntity, "UTF-8"); + response.close(); + if (StringKit.isEmpty(responseContent)) { + LogKit.info("http请求出错,http响应内容为空1"); + return ""; + } + return responseContent; + } + + public static synchronized String createHttpPostContent(CloseableHttpClient httpClient, String url, String bodyContent, String basicAuth, String contentType) throws IOException { + if ((httpClient == null) || (StringKit.isEmpty(url))) { + return ""; + } + + HttpPost httpPost = new HttpPost(url); + httpPost.addHeader("User-Agent", Utils.DEFAULT_USER_AGENT); + httpPost.setConfig(Utils.REQUEST_CONFIG); + if (StringKit.isNotEmpty(basicAuth)) { + httpPost.addHeader("Authorization", basicAuth); + } + if (StringKit.isNotEmpty(contentType)) { + httpPost.addHeader("Content-Type", contentType); + } + StringEntity bodyEntity = new StringEntity(bodyContent, "UTF-8"); + httpPost.setEntity(bodyEntity); + CloseableHttpResponse response = httpClient.execute(httpPost); + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode != HttpStatus.SC_OK) { + response.close(); + LogKit.info("http请求出错,http status:" + statusCode); + return ""; + } + + HttpEntity httpEntity = response.getEntity(); + if (httpEntity == null) { + response.close(); + LogKit.info("http请求出错,http响应内容为空"); + return ""; + } + String responseContent = EntityUtils.toString(httpEntity, "UTF-8"); + response.close(); + if (StringKit.isEmpty(responseContent)) { + LogKit.info("http请求出错,http响应内容为空1"); + return ""; + } + return responseContent; + } + + + /** + * 获取完整请求链接 + * + * @param req 请求 + * @return + */ + public static String getFullRequestUrl(HttpServletRequest req) { + if (req == null) { + return ""; + } + String url = req.getRequestURL().toString(); + String queryUrl = req.getQueryString(); + if ((queryUrl == null) || "null".equalsIgnoreCase(queryUrl)) { + queryUrl = ""; + } else { + queryUrl = "?" + queryUrl; + } + String fullUrl = url + queryUrl; + return fullUrl; + } + + + /** + * 重定向 + * + * @param res + * @param url + */ + public static void sendRedirect(HttpServletResponse res, String url) { + if ((res == null) || (StringKit.isEmpty(url))) { + return; + } + res.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); + res.setHeader("Location", url); + } + + + public static synchronized String getUuid() { + String uuid = UUID.randomUUID().toString().replace("-", ""); + return uuid; + } + + public static String getFullAuthorizeUrl(String state) { + CustomDataConfig config = CustomDataConfig.getInstance(); + //String tempUrl = URLEncoder.encode(config.getFrUrl(), "UTF-8"); + String tempUrl = config.getFrUrl(); + String url = config.getAuthorizeUrl() + "?client_id=" + config.getClientId() + "&response_type=code&redirect_uri=" + tempUrl + "&state=" + state; + LogKit.info("登录集成及用户同步,请求用户授权地址:" + url); + return url; + } + + public static String getFullAuthorizeUrlWithState() { + String state = getUuid(); + String oAuthLoginUrl = getFullAuthorizeUrl(state); + return oAuthLoginUrl; + } + + public static String getLogoutUrl() { + + return CustomDataConfig.getInstance().getLogoutUrl() + "?redirctToUrl=" + CustomDataConfig.getInstance().getFrUrl() + "&redirectToLogin=true&entityId=" + CustomDataConfig.getInstance().getClientId(); + } + + + /** + * 获取用户信息 + * + * @param userCount 用户数,-1为不限制用户数 + * @param isAdminContained 是否包含管理员用户 + * @return + * @throws Exception + */ + public static List getAllUsers(int userCount, boolean isAdminContained) throws Exception { + List userBeans = new ArrayList<>(); + if (userCount == 0) { + return userBeans; + } + int limitCount = userCount; + if (userCount <= -1) { + limitCount = Integer.MAX_VALUE; + } + List adminUserIds = UserService.getInstance().getAdminUserIdList(); + if ((adminUserIds == null) || (adminUserIds.size() <= 0)) { + return userBeans; + } + String adminUserId = adminUserIds.get(0); + + UserController userController = ControllerFactory.getInstance().getUserController(adminUserId); + + DataList dataList = userController.findPageUsersPerfectMatch(adminUserId, new UserPageQueryAuthorityParam(1, limitCount, "", null), new Restriction[0]); + if (dataList == null) { + return userBeans; + } + List users = dataList.getList(); + if ((users == null) || (users.size() <= 0)) { + return userBeans; + } + User user; + UserBean userBean; + String userId; + for (int i = 0, max = users.size() - 1; i <= max; i++) { + user = (User) users.get(i); + userId = user.getId(); + if ((!isAdminContained) && (adminUserIds.contains(userId))) { + continue; + } + userBean = new UserBean(user.getEmail(), user.isEnable(), user.getMobile(), user.getRealName(), user.getUserName(), user.getId()); + userBean.setCreationType(user.getCreationType().toInteger()); + userBeans.add(userBean); + } + return userBeans; + } + + + /** + * 获取不包含管理员的用户信息 + * + * @param userCount + * @return + * @throws Exception + */ + public static List getAllUsersWithoutAdmin(int userCount) throws Exception { + return getAllUsers(userCount, false); + } + + public static List getAllUserNames(int userCount, boolean isAdminContained) throws Exception { + List userNames = new ArrayList<>(); + List userBeans = getAllUsers(userCount, isAdminContained); + UserBean userBean; + for (int i = 0, max = userBeans.size() - 1; i <= max; i++) { + userBean = userBeans.get(i); + userNames.add(userBean.getUsername()); + } + return userNames; + } + + public static List getAllUserNamesWithoutAdmin(int userCount) throws Exception { + return getAllUserNames(userCount, false); + } + + /** + * 通过用户名删除用户,管理员用户无法删除 + * + * @param username 用户名 + * @return + * @throws Exception + */ + public static int deleteUsersByUsername(String username) throws Exception { + if (StringUtils.isEmpty(username)) { + return 0; + } + User user = UserService.getInstance().getUserByUserName(username); + if (user == null) { + return 0; + } + String userId = user.getId(); + List adminUserIds = UserService.getInstance().getAdminUserIdList(); + if ((adminUserIds != null) && (adminUserIds.size() >= 1) && (adminUserIds.contains(userId))) { + return 0; + } + UserUpdateBean userUpdateBean = new UserUpdateBean(); + userUpdateBean.setRemoveUserIds(new String[]{userId}); + return UserService.getInstance().deleteUsers(userUpdateBean); + } + + public static int deleteUsersByUsernames(List usernames) throws Exception { + if ((usernames == null) || (usernames.size() <= 0)) { + return 0; + } + String username; + int totalCount = 0, count; + for (int i = 0, max = usernames.size() - 1; i <= max; i++) { + username = usernames.get(i); + count = deleteUsersByUsername(username); + totalCount = totalCount + count; + } + return totalCount; + } + + public static void editRealNameByUsername(String username, String realName) throws Exception { + if (StringUtils.isEmpty(username) || StringUtils.isEmpty(realName)) { + return; + } + User user = UserService.getInstance().getUserByUserName(username); + if (user == null) { + return; + } + UserBean userBean = new UserBean(user.getEmail(), user.isEnable(), user.getMobile(), realName, user.getUserName(), user.getId()); + UserService.getInstance().editAccount(username, userBean); + } +} diff --git a/src/main/java/com/fr/plugin/third/party/jsdbacbj/config/CustomDataConfig.java b/src/main/java/com/fr/plugin/third/party/jsdbacbj/config/CustomDataConfig.java new file mode 100644 index 0000000..0b081ab --- /dev/null +++ b/src/main/java/com/fr/plugin/third/party/jsdbacbj/config/CustomDataConfig.java @@ -0,0 +1,284 @@ +package com.fr.plugin.third.party.jsdbacbj.config; + +import com.fr.config.*; +import com.fr.config.holder.Conf; +import com.fr.config.holder.factory.Holders; + +import java.util.HashMap; +import java.util.Map; + +/** + * 配置数据保存 + */ +@Visualization(category = "登录集成配置") +public class CustomDataConfig extends DefaultConfiguration { + public String getNameSpace() { + return this.getClass().getName(); + } + + private static volatile CustomDataConfig config = null; + + public static CustomDataConfig getInstance() { + if (config == null) { + config = ConfigContext.getConfigInstance(CustomDataConfig.class); + } + return config; + } + + + private static volatile Map URL_MAP = new HashMap<>(); + + public synchronized static void addMapUrl(String key, String url) { + URL_MAP.put(key, url); + } + + public synchronized static String getMapUrl(String key) { + if (!URL_MAP.containsKey(key)) { + return ""; + } + String url = URL_MAP.get(key); + URL_MAP.remove(key); + return url; + } + + + @Identifier(value = "clientId", name = "应用标识", description = "", status = Status.SHOW) + private Conf clientId = Holders.simple(""); + + @Identifier(value = "clientSecret", name = "应用密钥", description = "", status = Status.SHOW) + private Conf clientSecret = Holders.simple(""); + + @Identifier(value = "authorizeUrl", name = "请求用户授权地址", description = "", status = Status.SHOW) + private Conf authorizeUrl = Holders.simple(""); + + @Identifier(value = "accessTokenUrl", name = "获取授权Token地址", description = "", status = Status.SHOW) + private Conf accessTokenUrl = Holders.simple(""); + + @Identifier(value = "profileUrl", name = "获取用户信息地址", description = "", status = Status.SHOW) + private Conf profileUrl = Holders.simple(""); + + @Identifier(value = "frUrl", name = "报表地址", description = "", status = Status.SHOW) + private Conf frUrl = Holders.simple(""); + + @Identifier(value = "logoutUrl", name = "统一认证注销地址", description = "", status = Status.SHOW) + private Conf logoutUrl = Holders.simple(""); + + + + + @Identifier(value = "userDataUrl", name = "用户同步API地址", description = "", status = Status.HIDE) + private Conf userDataUrl = Holders.simple(""); + + @Identifier(value = "userSystemCode", name = "用户同步系统编码(systemCode)", description = "", status = Status.HIDE) + private Conf userSystemCode = Holders.simple(""); + + @Identifier(value = "userIntegrationKey", name = "用户同步集成密钥(integrationKey)", description = "", status = Status.HIDE) + private Conf userIntegrationKey = Holders.simple(""); + + @Identifier(value = "userLoopTimes", name = "用户同步循环最大次数", description = "", status = Status.HIDE) + private Conf userLoopTimes = Holders.simple(10000); + + + @Identifier(value = "dataSyncUrl", name = "待办发送接口地址", description = "", status = Status.HIDE) + private Conf dataSyncUrl = Holders.simple(""); + + @Identifier(value = "dataUsername", name = "待办发送接口用户名", description = "", status = Status.HIDE) + private Conf dataUsername = Holders.simple(""); + + @Identifier(value = "dataPassword", name = "待办发送接口密码", description = "", status = Status.HIDE) + private Conf dataPassword = Holders.simple(""); + + @Identifier(value = "appName", name = "待办发送来源", description = "", status = Status.HIDE) + private Conf appName = Holders.simple(""); + + @Identifier(value = "modelName", name = "待办发送模块名", description = "", status = Status.HIDE) + private Conf modelName = Holders.simple(""); + + @Identifier(value = "docCreator", name = "待办发送待办创建者", description = "", status = Status.HIDE) + private Conf docCreator = Holders.simple(""); + + + + @Identifier(value = "loginTypeNameParameter", name = "登录类型参数名称", description = "", status = Status.HIDE) + private Conf loginTypeNameParameter = Holders.simple("loginType"); + + @Identifier(value = "loginTypeValue", name = "登录类型值", description = "", status = Status.HIDE) + private Conf loginTypeValue = Holders.simple("oauth"); + + public String getLoginTypeNameParameter() { + return loginTypeNameParameter.get(); + } + + public void setLoginTypeNameParameter(String loginTypeNameParameter) { + this.loginTypeNameParameter.set(loginTypeNameParameter); + } + + public String getLoginTypeValue() { + return loginTypeValue.get(); + } + + public void setLoginTypeValue(String loginTypeValue) { + this.loginTypeValue.set(loginTypeValue); + } + + public Integer getUserLoopTimes() { + return userLoopTimes.get(); + } + + public void setUserLoopTimes(Integer userLoopTimes) { + this.userLoopTimes.set(userLoopTimes); + } + + public String getUserDataUrl() { + return userDataUrl.get(); + } + + public void setUserDataUrl(String userDataUrl) { + this.userDataUrl.set(userDataUrl); + } + + public String getUserSystemCode() { + return userSystemCode.get(); + } + + public void setUserSystemCode(String userSystemCode) { + this.userSystemCode.set(userSystemCode); + } + + public String getUserIntegrationKey() { + return userIntegrationKey.get(); + } + + public void setUserIntegrationKey(String userIntegrationKey) { + this.userIntegrationKey.set(userIntegrationKey); + } + + public String getDataSyncUrl() { + return dataSyncUrl.get(); + } + + public void setDataSyncUrl(String dataSyncUrl) { + this.dataSyncUrl.set(dataSyncUrl); + } + + public String getDataUsername() { + return dataUsername.get(); + } + + public void setDataUsername(String dataUsername) { + this.dataUsername.set(dataUsername); + } + + public String getDataPassword() { + return dataPassword.get(); + } + + public void setDataPassword(String dataPassword) { + this.dataPassword.set(dataPassword); + } + + public String getAppName() { + return appName.get(); + } + + public void setAppName(String appName) { + this.appName.set(appName); + } + + public String getModelName() { + return modelName.get(); + } + + public void setModelName(String modelName) { + this.modelName.set(modelName); + } + + public String getDocCreator() { + return docCreator.get(); + } + + public void setDocCreator(String docCreator) { + this.docCreator.set(docCreator); + } + + public String getClientId() { + return clientId.get(); + } + + public void setClientId(String clientId) { + this.clientId.set(clientId); + } + + public String getClientSecret() { + return clientSecret.get(); + } + + public void setClientSecret(String clientSecret) { + this.clientSecret.set(clientSecret); + } + + public String getAuthorizeUrl() { + return authorizeUrl.get(); + } + + public void setAuthorizeUrl(String authorizeUrl) { + this.authorizeUrl.set(authorizeUrl); + } + + public String getAccessTokenUrl() { + return accessTokenUrl.get(); + } + + public void setAccessTokenUrl(String accessTokenUrl) { + this.accessTokenUrl.set(accessTokenUrl); + } + + public String getProfileUrl() { + return profileUrl.get(); + } + + public void setProfileUrl(String profileUrl) { + this.profileUrl.set(profileUrl); + } + + public String getFrUrl() { + return frUrl.get(); + } + + public void setFrUrl(String frUrl) { + this.frUrl.set(frUrl); + } + + public String getLogoutUrl() { + return logoutUrl.get(); + } + + public void setLogoutUrl(String logoutUrl) { + this.logoutUrl.set(logoutUrl); + } + + @Override + public Object clone() throws CloneNotSupportedException { + CustomDataConfig cloned = (CustomDataConfig) super.clone(); + cloned.dataSyncUrl = (Conf) dataSyncUrl.clone(); + cloned.dataUsername = (Conf) dataUsername.clone(); + cloned.dataPassword = (Conf) dataPassword.clone(); + cloned.appName = (Conf) appName.clone(); + cloned.modelName = (Conf) modelName.clone(); + cloned.docCreator = (Conf) docCreator.clone(); + cloned.userDataUrl = (Conf) userDataUrl.clone(); + cloned.userSystemCode = (Conf) userSystemCode.clone(); + cloned.userIntegrationKey = (Conf) userIntegrationKey.clone(); + cloned.userLoopTimes = (Conf) userLoopTimes.clone(); + cloned.loginTypeNameParameter = (Conf) loginTypeNameParameter.clone(); + cloned.loginTypeValue = (Conf) loginTypeValue.clone(); + cloned.clientId = (Conf) clientId.clone(); + cloned.clientSecret = (Conf) clientSecret.clone(); + cloned.authorizeUrl = (Conf) authorizeUrl.clone(); + cloned.accessTokenUrl = (Conf) accessTokenUrl.clone(); + cloned.profileUrl = (Conf) profileUrl.clone(); + cloned.frUrl = (Conf) frUrl.clone(); + cloned.logoutUrl = (Conf) logoutUrl.clone(); + return cloned; + } +} diff --git a/src/main/java/com/fr/plugin/third/party/jsdbacbj/http/CustomConfigHttpHandler.java b/src/main/java/com/fr/plugin/third/party/jsdbacbj/http/CustomConfigHttpHandler.java new file mode 100644 index 0000000..e732586 --- /dev/null +++ b/src/main/java/com/fr/plugin/third/party/jsdbacbj/http/CustomConfigHttpHandler.java @@ -0,0 +1,44 @@ +package com.fr.plugin.third.party.jsdbacbj.http; + +import com.fr.decision.fun.impl.BaseHttpHandler; +import com.fr.json.JSONObject; +import com.fr.plugin.third.party.jsdbacbj.Utils; +import com.fr.third.springframework.web.bind.annotation.RequestMethod; +import com.fr.web.utils.WebUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * + */ +public class CustomConfigHttpHandler extends BaseHttpHandler { + + @Override + public RequestMethod getMethod() { + return RequestMethod.POST; + } + + @Override + public String getPath() { + return "/jsdbacbj/oauth/config"; + } + + @Override + public boolean isPublic() { + return true; + } + + @Override + public void handle(HttpServletRequest req, HttpServletResponse res) throws Exception { + res.setContentType("application/json; charset=utf-8"); + String logoutUrl = Utils.getLogoutUrl(); + String oAuthLoginUrl = Utils.getFullAuthorizeUrlWithState(); + JSONObject configJson = new JSONObject(); + configJson.put("logoutUrl", logoutUrl); + configJson.put("oAuthLoginUrl", oAuthLoginUrl); + WebUtils.printAsJSON(res, configJson); + } + + +} diff --git a/src/main/java/com/fr/plugin/third/party/jsdbacbj/http/CustomHttpHandlerProvider.java b/src/main/java/com/fr/plugin/third/party/jsdbacbj/http/CustomHttpHandlerProvider.java new file mode 100644 index 0000000..da01d45 --- /dev/null +++ b/src/main/java/com/fr/plugin/third/party/jsdbacbj/http/CustomHttpHandlerProvider.java @@ -0,0 +1,16 @@ +package com.fr.plugin.third.party.jsdbacbj.http; + +import com.fr.decision.fun.impl.AbstractHttpHandlerProvider; +import com.fr.decision.fun.impl.BaseHttpHandler; + + +public class CustomHttpHandlerProvider extends AbstractHttpHandlerProvider { + @Override + public BaseHttpHandler[] registerHandlers() { + return new BaseHttpHandler[]{ + new CustomConfigHttpHandler(), + + + }; + } +} diff --git a/src/main/java/com/fr/plugin/third/party/jsdbacbj/http/CustomURLAliasProvider.java b/src/main/java/com/fr/plugin/third/party/jsdbacbj/http/CustomURLAliasProvider.java new file mode 100644 index 0000000..94fb621 --- /dev/null +++ b/src/main/java/com/fr/plugin/third/party/jsdbacbj/http/CustomURLAliasProvider.java @@ -0,0 +1,14 @@ +package com.fr.plugin.third.party.jsdbacbj.http; + +import com.fr.decision.fun.impl.AbstractURLAliasProvider; +import com.fr.decision.webservice.url.alias.URLAlias; +import com.fr.decision.webservice.url.alias.URLAliasFactory; + +public class CustomURLAliasProvider extends AbstractURLAliasProvider { + @Override + public URLAlias[] registerAlias() { + return new URLAlias[]{ + URLAliasFactory.createPluginAlias("/jsdbacbj/oauth/config", "/jsdbacbj/oauth/config", true), + }; + } +} diff --git a/src/main/java/com/fr/plugin/third/party/jsdbacbj/http/SessionGlobalRequestFilterProvider.java b/src/main/java/com/fr/plugin/third/party/jsdbacbj/http/SessionGlobalRequestFilterProvider.java new file mode 100644 index 0000000..9b963c7 --- /dev/null +++ b/src/main/java/com/fr/plugin/third/party/jsdbacbj/http/SessionGlobalRequestFilterProvider.java @@ -0,0 +1,552 @@ +package com.fr.plugin.third.party.jsdbacbj.http; + +import com.fanruan.api.log.LogKit; +import com.fanruan.api.util.StringKit; +import com.fr.decision.authority.data.User; +import com.fr.decision.fun.impl.AbstractGlobalRequestFilterProvider; +import com.fr.decision.webservice.v10.login.LoginService; +import com.fr.decision.webservice.v10.user.UserService; +import com.fr.general.ComparatorUtils; +import com.fr.json.JSONArray; +import com.fr.json.JSONObject; +import com.fr.plugin.third.party.jsdbacbj.Utils; +import com.fr.plugin.third.party.jsdbacbj.config.CustomDataConfig; +import com.fr.third.org.apache.commons.codec.digest.DigestUtils; +import com.fr.third.org.apache.http.HttpEntity; +import com.fr.third.org.apache.http.HttpStatus; +import com.fr.third.org.apache.http.NameValuePair; +import com.fr.third.org.apache.http.client.config.RequestConfig; +import com.fr.third.org.apache.http.client.entity.UrlEncodedFormEntity; +import com.fr.third.org.apache.http.client.methods.CloseableHttpResponse; +import com.fr.third.org.apache.http.client.methods.HttpGet; +import com.fr.third.org.apache.http.client.methods.HttpPost; +import com.fr.third.org.apache.http.impl.client.CloseableHttpClient; +import com.fr.third.org.apache.http.message.BasicNameValuePair; +import com.fr.third.org.apache.http.util.EntityUtils; +import com.fr.web.utils.WebUtils; + +import javax.servlet.FilterChain; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.*; + + +public class SessionGlobalRequestFilterProvider extends AbstractGlobalRequestFilterProvider { + private static String DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"; + + @Override + public String filterName() { + return "com.fr.plugin.third.party.jsdbacbj"; + } + + @Override + public String[] urlPatterns() { + return new String[]{"/decision", "/decision/*"}; + } + + @Override + public void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain filterChain) { + try { + String fullUrl = req.getRequestURL().toString(); + String queryUrl = req.getQueryString(); + if ((queryUrl == null) || "null".equalsIgnoreCase(queryUrl)) { + queryUrl = ""; + } else { + queryUrl = "?" + queryUrl; + } + + String fullUrl1 = fullUrl + queryUrl; + String method = req.getMethod(); + LogKit.info("登录集成,记录访问地址:" + method + " " + fullUrl1); + if (!"GET".equalsIgnoreCase(method)) { + filterChain.doFilter(req, res); + return; + } + + if (isNoAuthRequest(req)) { + filterChain.doFilter(req, res); + return; + } + + if (fullUrl.indexOf("/remote/") >= 0) { + filterChain.doFilter(req, res); + return; + } + + if (fullUrl.indexOf("terminal=H5") >= 0) { + filterChain.doFilter(req, res); + return; + } + + if (fullUrl.indexOf("__device__=") >= 0) { + filterChain.doFilter(req, res); + return; + } + + if (fullUrl.indexOf("/weixin/") >= 0) { + filterChain.doFilter(req, res); + return; + } + + + if (fullUrl.indexOf("/dingtalk/") >= 0) { + filterChain.doFilter(req, res); + return; + } + + if (isLoginRequest(req)) { + LogKit.info("登录集成,登录页,访问地址:" + method + " " + fullUrl1); + filterChain.doFilter(req, res); + return; + } + //boolean option = isLogged(req); + boolean option = LoginService.getInstance().isLogged(req); + if (option && (!isOauthCodeRequest(req)) && (!isAllowLoginWithParameter(req))) { + LogKit.info("登录集成,标记a,访问地址:" + method + " " + fullUrl1); + filterChain.doFilter(req, res); + return; + } + + String state, reqUrl; + if (isAllowLoginWithParameter(req) || ((!option) && (!isOauthCodeRequest(req)) && isReportRequest(req))) { + LogKit.info("登录集成,标记b,访问地址:" + method + " " + fullUrl1); + reqUrl = getRequestUrl(req); + state = Utils.getUuid(); + CustomDataConfig.getInstance().addMapUrl(state, reqUrl); + String tempUrl = Utils.getFullAuthorizeUrl(state); + LogKit.info("登录集成,请求认证地址:" + tempUrl); + sendRedirect(res, tempUrl); + return; + } + + String loginUsername = getOauthLoginUsername(req); + if (StringKit.isEmpty(loginUsername)) { + filterChain.doFilter(req, res); + return; + } + LogKit.info("登录集成,用户名:" + loginUsername); + + User user = UserService.getInstance().getUserByUserName(loginUsername); + boolean tipsOption = false; + String tipsContent = ""; + if (user == null) { + tipsOption = true; + LogKit.info("登录集成,用户名:" + loginUsername + "在报表平台不存在"); + tipsContent = "在报表服务器上不存在"; + } else if (!user.isEnable()) { + tipsOption = true; + LogKit.info("登录集成,用户名:" + loginUsername + "在报表平台上被禁用"); + tipsContent = "在报表平台上被禁用"; + } + + if (tipsOption) { + String jumpContent = "\n" + + "\n" + + " \n" + + " 提示\n" + + "\n" + + "\n" + + " \n" + + "
\n" + + " \n" + + "
\n" + + "\n" + + ""; + + jumpContent = "\n" + + "\n" + + " \n" + + " 提示\n" + + "\t\n" + + "\t\n" + + "\n" + + "\n" + + "
\n" + + " \n" + + "
\n" + + "\n" + + ""; + res.setContentType("text/html;charset=UTF-8"); + WebUtils.printAsString(res, jumpContent); + res.setStatus(200); + return; + } + + loginUsername = user.getUserName(); + LogKit.info("登录集成,报表平台用户名:" + loginUsername); + + String loginToken = LoginService.getInstance().login(req, res, loginUsername); + req.setAttribute("fine_auth_token", loginToken); + + reqUrl = getRealUrl(req); + if (StringKit.isNotEmpty(reqUrl)) { + sendRedirect(res, reqUrl); + return; + } + + filterChain.doFilter(req, res); + } catch (Exception e) { + LogKit.error("登录集成出错," + e.getMessage(), e); + } + } + + private String getRealUrl(HttpServletRequest req) { + if (req == null) { + return ""; + } + String state = WebUtils.getHTTPRequestParameter(req, "state"); + if (StringKit.isEmpty(state)) { + return ""; + } + String url = CustomDataConfig.getInstance().getMapUrl(state); + return url; + } + + + private String getOauthLoginUsername(HttpServletRequest req) throws IOException { + if (req == null) { + return ""; + } + String oAuthCode = WebUtils.getHTTPRequestParameter(req, "code"); + if (StringKit.isEmpty(oAuthCode)) { + return ""; + } + LogKit.info("登录集成,OAuth Code:" + oAuthCode); + + RequestConfig requestConfig = RequestConfig.custom() + .setConnectionRequestTimeout(10000) + .setSocketTimeout(10000) // 服务端相应超时 + .setConnectTimeout(10000) // 建立socket链接超时时间 + .build(); + + //获取授权Token + + String accessTokenUrl = CustomDataConfig.getInstance().getAccessTokenUrl(); + LogKit.info("登录集成,获取授权Token地址:" + accessTokenUrl); + HttpPost httpPost = new HttpPost(accessTokenUrl); + httpPost.addHeader("User-Agent", DEFAULT_USER_AGENT); + + + List params = new ArrayList(); + NameValuePair clientIdNameValuePair = new BasicNameValuePair("client_id", CustomDataConfig.getInstance().getClientId()); + NameValuePair clientSecretNameValuePair = new BasicNameValuePair("client_secret", CustomDataConfig.getInstance().getClientSecret()); + NameValuePair redirectUriNameValuePair = new BasicNameValuePair("redirect_uri", CustomDataConfig.getInstance().getFrUrl()); + NameValuePair codeNameValuePair = new BasicNameValuePair("code", oAuthCode); + NameValuePair grantTypeNameValuePair = new BasicNameValuePair("grant_type", "authorization_code"); + + params.add(clientIdNameValuePair); + params.add(clientSecretNameValuePair); + + params.add(codeNameValuePair); + params.add(grantTypeNameValuePair); + + httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8")); + httpPost.setHeader("Content-type", "application/x-www-form-urlencoded"); + + + httpPost.setConfig(requestConfig); + CloseableHttpClient httpClient = Utils.createHttpClient(accessTokenUrl); + CloseableHttpResponse response = httpClient.execute(httpPost); + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode != HttpStatus.SC_OK) { + response.close(); + httpClient.close(); + LogKit.info("登录集成,获取授权Token请求出错,http status:" + statusCode); + return ""; + } + + HttpEntity httpEntity = response.getEntity(); + if (httpEntity == null) { + response.close(); + httpClient.close(); + LogKit.info("登录集成,获取授权Token请求出错,http响应内容为空"); + return ""; + } + String responseContent = EntityUtils.toString(httpEntity, "UTF-8"); + response.close(); + if (StringKit.isEmpty(responseContent)) { + httpClient.close(); + LogKit.info("登录集成,获取授权Token请求出错,http响应内容为空1"); + return ""; + } + LogKit.info("登录集成,获取授权Token请求,http响应内容\n" + responseContent); + + String accessToken = getAccessToken(responseContent); + if (StringKit.isEmpty(accessToken)) { + httpClient.close(); + LogKit.info("登录集成,获取授权Token请求出错,授权Token为空"); + return ""; + } + LogKit.info("登录集成,授权Token:" + accessToken); + + + String userUrl = CustomDataConfig.getInstance().getProfileUrl() + "?access_token=" + accessToken + "&client_id=" + CustomDataConfig.getInstance().getClientId(); + HttpGet httpGet = new HttpGet(userUrl); + + httpGet.setConfig(requestConfig); + response = httpClient.execute(httpGet); + statusCode = response.getStatusLine().getStatusCode(); + if (statusCode != HttpStatus.SC_OK) { + response.close(); + httpClient.close(); + LogKit.info("登录集成,获取用户信息请求出错,http status:" + statusCode); + return ""; + } + + httpEntity = response.getEntity(); + if (httpEntity == null) { + response.close(); + httpClient.close(); + LogKit.info("登录集成,获取用户信息请求出错,http响应内容为空"); + return ""; + } + responseContent = EntityUtils.toString(httpEntity, "UTF-8"); + response.close(); + httpClient.close(); + if (StringKit.isEmpty(responseContent)) { + LogKit.info("登录集成,获取用户信息请求出错,http响应内容为空1"); + return ""; + } + + LogKit.info("登录集成,获取用户信息请求,http响应内容\n" + responseContent); + String uid = getUserId(responseContent); + if (StringKit.isEmpty(uid)) { + LogKit.info("登录集成,,获取用户信息请求出错,uid为空"); + return ""; + } + LogKit.info("登录集成,uid:" + uid); + + return uid; + } + + + public static String getSign(Map params, String secret) { + String sign = ""; + StringBuilder sb = new StringBuilder(); + //排序 + Set keyset = params.keySet(); + TreeSet sortSet = new TreeSet(); + sortSet.addAll(keyset); + Iterator it = sortSet.iterator(); + //加密字符串 + while (it.hasNext()) { + String key = it.next(); + String value = params.get(key); + sb.append(key).append(value); + } + sb.append("appkey").append(secret); + String md5Str = sb.toString(); + LogKit.info("登录集成,获取授权Token,sign 待加密字符串:" + md5Str); + try { + sign = DigestUtils.md5Hex(md5Str).toUpperCase(); + } catch (Exception e) { + } + return sign; + } + + + private String getAccessToken(String content) { + if (StringKit.isEmpty(content)) { + return ""; + } + + JSONObject contentJson = new JSONObject(content); + String token = contentJson.getString("access_token"); + return token; + } + + private String getUserId(String content) { + if (StringKit.isEmpty(content)) { + return ""; + } + + String loginName; + JSONObject contentJson = new JSONObject(content); + if (contentJson.containsKey("spRoleList")) { + JSONArray jsonArray = contentJson.getJSONArray("spRoleList"); + if ((jsonArray != null) && (jsonArray.size() >= 1)) { + return jsonArray.getString(0); + } + } + loginName = contentJson.getString("loginName"); + return loginName; + } + + + public synchronized static String getSysTime() { + SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd"); + Date date = new Date(); + String nowData = format.format(date); + return nowData; + } + + private void sendRedirect(HttpServletResponse res, String url) { + res.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); + res.setHeader("Location", url); + } + + private String getRequestUrl(HttpServletRequest req) { + String fullUrl = req.getRequestURL().toString(); + fullUrl = getRealUrl(fullUrl); + Map paraMap = req.getParameterMap(); + String paraName; + String[] paraValues; + String loginTypeParaName = CustomDataConfig.getInstance().getLoginTypeNameParameter(); + String queryStr = ""; + for (Map.Entry entry : paraMap.entrySet()) { + paraName = entry.getKey(); + if (ComparatorUtils.equals(paraName, loginTypeParaName)) { + continue; + } + if (ComparatorUtils.equals(paraName, "code")) { + continue; + } + paraValues = entry.getValue(); + queryStr = addParaToQuery(queryStr, paraName, paraValues); + } + if (StringKit.isEmpty(queryStr)) { + return fullUrl; + } + fullUrl = fullUrl + "?" + queryStr; + return fullUrl; + } + + private String getRealUrl(String url) { + if (StringKit.isEmpty(url)) { + return url; + } + int index = url.indexOf("/decision"); + if (index < 0) { + return url; + } + String tempUrl = CustomDataConfig.getInstance().getFrUrl() + url.substring(index + "/decision".length()); + return tempUrl; + } + + private String addParaToQuery(String query, String paraName, String[] paraValues) { + if (StringKit.isEmpty(paraName)) { + return query; + } + String fullQuery = query; + if ((paraValues == null) || (paraValues.length <= 0)) { + if (StringKit.isNotEmpty(fullQuery)) { + fullQuery = fullQuery + "&"; + } + fullQuery = paraName + "="; + return fullQuery; + } + for (int i = 0, max = paraValues.length - 1; i <= max; i++) { + if (StringKit.isNotEmpty(fullQuery)) { + fullQuery = fullQuery + "&"; + } + fullQuery = fullQuery + paraName + "=" + paraValues[i]; + } + return fullQuery; + } + + + private boolean isAllowLoginWithParameter(HttpServletRequest req) { + if (req == null) { + return false; + } + String loginTypeNameParameter = CustomDataConfig.getInstance().getLoginTypeNameParameter(); + String loginTypeConfigValue = CustomDataConfig.getInstance().getLoginTypeValue(); + if (StringKit.isEmpty(loginTypeNameParameter) || StringKit.isEmpty(loginTypeConfigValue)) { + return false; + } + String loginTypeValue = WebUtils.getHTTPRequestParameter(req, loginTypeNameParameter); + return ComparatorUtils.equalsIgnoreCase(loginTypeConfigValue, loginTypeValue); + } + + + private boolean isReportRequest(HttpServletRequest req) { + if (req == null) { + return false; + } + if (!"GET".equalsIgnoreCase(req.getMethod())) { + return false; + } + String url = req.getRequestURL().toString(); + if (url.endsWith("/decision") || url.endsWith("/decision/")) { + return true; + } + if ((url.indexOf("/decision/") >= 0) && (url.indexOf("/entry/access/") >= 0)) { + return true; + } + String viewlet = WebUtils.getHTTPRequestParameter(req, "viewlet"); + if ((url.indexOf("/decision/view/report") >= 0) && (StringKit.isNotEmpty(viewlet))) { + return true; + } + if ((url.indexOf("/decision/view/form") >= 0) && (StringKit.isNotEmpty(viewlet))) { + return true; + } + return false; + } + + private boolean isOauthCodeRequest(HttpServletRequest req) throws IOException { + if (req == null) { + return false; + } + if (!"GET".equalsIgnoreCase(req.getMethod())) { + return false; + } + String oAuthCode = WebUtils.getHTTPRequestParameter(req, "code"); + if (StringKit.isNotEmpty(oAuthCode)) { + return true; + } + return false; + } + + private boolean isLoginRequest(HttpServletRequest req) { + if (req == null) { + return false; + } + if (!"GET".equalsIgnoreCase(req.getMethod())) { + return false; + } + String url = req.getRequestURL().toString(); + if (url.endsWith("/decision/login") || url.endsWith("/decision/login/")) { + return true; + } + return false; + } + + private boolean isNoAuthRequest(HttpServletRequest req) throws IOException { + if (req == null) { + return false; + } + if (!"GET".equalsIgnoreCase(req.getMethod())) { + return false; + } + String oAuthCode = WebUtils.getHTTPRequestParameter(req, "loginType"); + if (StringKit.equalsIgnoreCase("noauth", oAuthCode)) { + return true; + } + return false; + } + +} diff --git a/src/main/java/com/fr/plugin/third/party/jsdbacbj/web/MainFilesComponent.java b/src/main/java/com/fr/plugin/third/party/jsdbacbj/web/MainFilesComponent.java new file mode 100644 index 0000000..ce7707d --- /dev/null +++ b/src/main/java/com/fr/plugin/third/party/jsdbacbj/web/MainFilesComponent.java @@ -0,0 +1,46 @@ +package com.fr.plugin.third.party.jsdbacbj.web; + +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; + +public class MainFilesComponent extends Component { + public static final MainFilesComponent KEY = new MainFilesComponent(); + private MainFilesComponent(){} + /** + * 返回需要引入的JS脚本路径 + * @param client 请求客户端描述 + * @return JS脚本路径 + */ + public ScriptPath script(RequestClient client ) { + //如果不需要就直接返回 ScriptPath.EMPTY + return ScriptPath.build("com/fr/plugin/third/party/jsdbacbj/main.js"); + } + + /** + * 返回需要引入的CSS样式路径 + * @param client 请求客户端描述 + * @return CSS样式路径 + */ + public StylePath style(RequestClient client ) { + //如果不需要就直接返回 StylePath.EMPTY; + //return StylePath.build("com/fr/plugin/jscssinput/demo/demo.css"); + return StylePath.EMPTY; + } + + /** + * 通过给定的资源过滤器控制是否加载这个资源 + * @return 资源过滤器 + */ + public Filter filter() { + return new Filter(){ + @Override + public boolean accept() { + //任何情况下我们都在平台组件加载时加载我们的组件 + return true; + } + }; + } +} \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/third/party/jsdbacbj/web/MainWebResourceProvider.java b/src/main/java/com/fr/plugin/third/party/jsdbacbj/web/MainWebResourceProvider.java new file mode 100644 index 0000000..0c8682f --- /dev/null +++ b/src/main/java/com/fr/plugin/third/party/jsdbacbj/web/MainWebResourceProvider.java @@ -0,0 +1,19 @@ +package com.fr.plugin.third.party.jsdbacbj.web; + +import com.fr.decision.fun.impl.AbstractWebResourceProvider; +import com.fr.decision.web.MainComponent; +import com.fr.web.struct.Atom; + +public class MainWebResourceProvider extends AbstractWebResourceProvider { + @Override + public Atom attach() { + //在平台主组件加载时添加我们自己的组件 + return MainComponent.KEY; + } + + @Override + public Atom client() { + //我们自己要引入的组件 + return MainFilesComponent.KEY; + } +} diff --git a/src/main/resources/com/fr/plugin/third/party/jsdbacbj/main.js b/src/main/resources/com/fr/plugin/third/party/jsdbacbj/main.js new file mode 100644 index 0000000..313da34 --- /dev/null +++ b/src/main/resources/com/fr/plugin/third/party/jsdbacbj/main.js @@ -0,0 +1,27 @@ +$(function () { + var url = Dec.fineServletURL + "/url/jsdbacbj/oauth/config"; + $.post(url, + function (data, status) { + if (status == "success") { + //debugger; + + var logoutUrl = data.logoutUrl; + var oAuthLoginUrl = data.oAuthLoginUrl; + + Dec.Logout = function () { + Dec.Utils.logout(function (e) { + Dec.Utils.clearLoginToken(); + window.location.href = oAuthLoginUrl; + }) + } + + var a = Dec.Logout; + + Dec.Logout = function () { + window.location.href = logoutUrl; + //$.get(logoutUrl); + a(); + } + } + }, "json"); +}); \ No newline at end of file