Browse Source

提交开源任务材料

10.0
LAPTOP-SB56SG4Q\86185 2 years ago
parent
commit
ba1b029590
  1. BIN
      JSD-9377-配置使用文档.pdf
  2. 5
      README.md
  3. BIN
      lib/finekit-10.0.jar
  4. 29
      plugin.xml
  5. 40
      src/main/java/com/fr/plugin/icgq/LocaleFinder.java
  6. 34
      src/main/java/com/fr/plugin/icgq/PluginMonitor.java
  7. 52
      src/main/java/com/fr/plugin/icgq/config/IcgqConfig.java
  8. 57
      src/main/java/com/fr/plugin/icgq/data/UsersTableData.java
  9. 131
      src/main/java/com/fr/plugin/icgq/data/UsersTableDataModel.java
  10. 75
      src/main/java/com/fr/plugin/icgq/data/WebTableDataModel.java
  11. 115
      src/main/java/com/fr/plugin/icgq/kit/DepartmentServiceKit.java
  12. 730
      src/main/java/com/fr/plugin/icgq/kit/HttpKit.java
  13. 173
      src/main/java/com/fr/plugin/icgq/kit/UserServiceKit.java
  14. 57
      src/main/java/com/fr/plugin/icgq/provider/UsersTableDataDefine.java
  15. 181
      src/main/java/com/fr/plugin/icgq/service/ReportDataHandler.java
  16. 33
      src/main/java/com/fr/plugin/icgq/service/RequestHandlerBridge.java
  17. 35
      src/main/java/com/fr/plugin/icgq/service/URLAliasBridge.java
  18. 52
      src/main/java/com/fr/plugin/icgq/ui/UsersTableDataPane.java
  19. 136
      src/main/java/com/fr/plugin/icgq/ui/WebBaseTableDataPane.java
  20. 60
      src/main/java/com/fr/plugin/icgq/ui/WebQueryPane.java
  21. BIN
      src/main/resources/com/fr/plugin/icgq/images/help.png
  22. BIN
      src/main/resources/com/fr/plugin/icgq/images/logo16.png
  23. 15
      src/main/resources/com/fr/plugin/icgq/locale/lang.properties
  24. 15
      src/main/resources/com/fr/plugin/icgq/locale/lang_zh_CN.properties

BIN
JSD-9377-配置使用文档.pdf

Binary file not shown.

5
README.md

@ -1,3 +1,6 @@
# open-JSD-9377
JSD-9377 IAM用户数据集插件(数据集、用户集成)
JSD-9377 IAM用户数据集插件(数据集、用户集成)\
免责说明:该源码为第三方爱好者提供,不保证源码和方案的可靠性,也不提供任何形式的源码教学指导和协助!\
仅作为开发者学习参考使用!禁止用于任何商业用途!\
为保护开发者隐私,开发者信息已隐去!若原开发者希望公开自己的信息,可联系hugh处理。

BIN
lib/finekit-10.0.jar

Binary file not shown.

29
plugin.xml

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<plugin>
<id>com.fr.plugin.icgq.sso</id>
<name><![CDATA[用户同步]]></name>
<active>yes</active>
<version>1.1.8</version>
<env-version>10.0</env-version>
<jartime>2018-07-31</jartime>
<vendor>fr.open</vendor>
<description><![CDATA[用户同步]]></description>
<change-notes><![CDATA[用户同步]]></change-notes>
<main-package>com.fr.plugin.icgq</main-package>
<prefer-packages>
<prefer-package>com.fanruan.api</prefer-package>
</prefer-packages>
<lifecycle-monitor class="com.fr.plugin.icgq.PluginMonitor"/>
<extra-core>
<LocaleFinder class="com.fr.plugin.icgq.LocaleFinder"/>
</extra-core>
<extra-decision>
<HttpHandlerProvider class="com.fr.plugin.icgq.service.RequestHandlerBridge"/>
<URLAliasProvider class="com.fr.plugin.icgq.service.URLAliasBridge"/>
</extra-decision>
<extra-designer>
<TableDataDefineProvider class="com.fr.plugin.icgq.provider.UsersTableDataDefine"/>
<ServerTableDataDefineProvider class="com.fr.plugin.icgq.provider.UsersTableDataDefine"/>
</extra-designer>
<function-recorder class="com.fr.plugin.icgq.LocaleFinder"/>
</plugin>

40
src/main/java/com/fr/plugin/icgq/LocaleFinder.java

@ -0,0 +1,40 @@
/*
* Copyright (C), 2018-2020
* Project: starter
* FileName: LocaleFinder
* Author: Louis
* Date: 2020/8/31 22:19
*/
package com.fr.plugin.icgq;
import com.fr.intelli.record.Focus;
import com.fr.intelli.record.Original;
import com.fr.record.analyzer.EnableMetrics;
import com.fr.stable.fun.Authorize;
import com.fr.stable.fun.impl.AbstractLocaleFinder;
import static com.fr.plugin.icgq.LocaleFinder.PLUGIN_ID;
/**
* <Function Description><br>
* <LocaleFinder>
*
* @author fr.open
* @since 1.0.0
*/
@EnableMetrics
@Authorize(callSignKey = PLUGIN_ID)
public class LocaleFinder extends AbstractLocaleFinder {
public static final String PLUGIN_ID = "com.fr.plugin.icgq.sso";
@Override
@Focus(id = PLUGIN_ID, text = "Plugin-icgq", source = Original.PLUGIN)
public String find() {
return "com/fr/plugin/icgq/locale/lang";
}
@Override
public int currentAPILevel() {
return CURRENT_LEVEL;
}
}

34
src/main/java/com/fr/plugin/icgq/PluginMonitor.java

@ -0,0 +1,34 @@
/*
* Copyright (C), 2018-2021
* Project: starter
* FileName: PluginMonitor
* Author: Louis
* Date: 2021/3/30 15:10
*/
package com.fr.plugin.icgq;
import com.fr.plugin.context.PluginContext;
import com.fr.plugin.icgq.config.IcgqConfig;
import com.fr.plugin.observer.inner.AbstractPluginLifecycleMonitor;
/**
* <Function Description><br>
* <PluginMonitor>
*
* @author fr.open
* @since 1.0.0
*/
public class PluginMonitor extends AbstractPluginLifecycleMonitor {
public PluginMonitor() {
}
@Override
public void afterRun(PluginContext pluginContext) {
IcgqConfig.getInstance();
}
@Override
public void beforeStop(PluginContext pluginContext) {
}
}

52
src/main/java/com/fr/plugin/icgq/config/IcgqConfig.java

@ -0,0 +1,52 @@
/*
* Copyright (C), 2018-2021
* Project: starter
* FileName: IcgqConfig
* Author: Louis
* Date: 2021/3/30 9:38
*/
package com.fr.plugin.icgq.config;
import com.fanruan.api.util.StringKit;
import com.fr.config.*;
import com.fr.config.holder.Conf;
import com.fr.config.holder.factory.Holders;
/**
* <Function Description><br>
* <IcgqConfig>
*
* @author fr.open
* @since 1.0.0
*/
@Visualization(category = "Plugin-icgq_Group")
public class IcgqConfig extends DefaultConfiguration {
private static volatile IcgqConfig config = null;
@Identifier(value = "clientId", name = "Plugin-icgq_Config_ClientId", description = "Plugin-icgq_Config_ClientId_Description", status = Status.SHOW)
private final Conf<String> clientId = Holders.simple(StringKit.EMPTY);
@Identifier(value = "clientSecret", name = "Plugin-icgq_Config_ClientSecret", description = "Plugin-icgq_Config_ClientSecret_Description", status = Status.SHOW)
private final Conf<String> clientSecret = Holders.simple(StringKit.EMPTY);
public static IcgqConfig getInstance() {
if (config == null) {
config = ConfigContext.getConfigInstance(IcgqConfig.class);
}
return config;
}
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);
}
}

57
src/main/java/com/fr/plugin/icgq/data/UsersTableData.java

@ -0,0 +1,57 @@
/*
* Copyright (C), 2018-2021
* Project: starter
* FileName: WechatTableData
* Author: Louis
* Date: 2021/12/8 16:55
*/
package com.fr.plugin.icgq.data;
import com.fanruan.api.data.open.BaseTableData;
import com.fr.base.TableData;
import com.fr.general.data.DataModel;
import com.fr.script.Calculator;
import com.fr.stable.xml.XMLPrintWriter;
import com.fr.stable.xml.XMLableReader;
/**
* <Function Description><br>
* <UsersTableData>
*
* @author fr.open
* @since 1.0.0
*/
public class UsersTableData extends BaseTableData {
private static final long serialVersionUID = -7456007025209900779L;
@Override
public DataModel createDataModel(Calculator calculator) {
return createDataModel(calculator, TableData.RESULT_ALL);
}
@Override
public DataModel createDataModel(Calculator calculator, int rowCount) {
return new UsersTableDataModel(Calculator.processParameters(calculator, super.getParameters(calculator)));
}
@Override
public void readXML(XMLableReader reader) {
super.readXML(reader);
}
@Override
public void writeXML(XMLPrintWriter writer) {
super.writeXML(writer);
}
@Override
public Object clone() throws CloneNotSupportedException {
UsersTableData cloned = (UsersTableData) super.clone();
return cloned;
}
@Override
public boolean equals(Object obj) {
return obj instanceof UsersTableData;
}
}

131
src/main/java/com/fr/plugin/icgq/data/UsersTableDataModel.java

@ -0,0 +1,131 @@
/*
* Copyright (C), 2018-2021
* Project: starter
* FileName: WechatTableDataModel
* Author: Louis
* Date: 2021/12/8 19:09
*/
package com.fr.plugin.icgq.data;
import com.fanruan.api.design.DesignKit;
import com.fanruan.api.log.LogKit;
import com.fanruan.api.util.StringKit;
import com.fr.json.JSONArray;
import com.fr.json.JSONObject;
import com.fr.log.FineLoggerFactory;
import com.fr.plugin.context.PluginContexts;
import com.fr.plugin.icgq.kit.HttpKit;
import com.fr.stable.ParameterProvider;
import com.fr.third.org.apache.http.entity.StringEntity;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* <Function Description><br>
* <UsersTableDataModel>
*
* @author fr.open
* @since 1.0.0
*/
public class UsersTableDataModel extends WebTableDataModel {
private static final long serialVersionUID = 8191435706966886501L;
public static final String ACCOUNT_LIST = "/esc-idm/api/v1/account/list";
public static final String APP_ACCOUNT_ACCOUNT_UUID = "app_account__account_uuid";
public static final String APP_ACCOUNT_ACCOUNT_NO = "app_account__account_no";
public static final String APP_ACCOUNT_ACCOUNT_NAME = "app_account__account_name";
public static final String REQUEST_LOG_ACTION_FLAG = "request_log__action_flag";
public static final String REQUEST_LOG_ID = "request_log__id";
public static final String IDT_USER_USER_NAME = "idt_user__user_name";
public static final String IDT_USER_EMAIL = "idt_user__email";
public static final String IDT_USER_USER_EMAIL = "idt_user__user_email";
public static final String IDT_USER_WORK_NO = "idt_user__work_no";
public static final String IDT_USER_MOBILE = "idt_user__mobile";
public static final String IDT_ORG_NAME = "idt_org__name";
public static final String IDT_JOB_NAME = "idt_job__name";
public UsersTableDataModel(ParameterProvider[] parameters) {
super(parameters);
this.columnNames = new String[]{APP_ACCOUNT_ACCOUNT_UUID, APP_ACCOUNT_ACCOUNT_NO, APP_ACCOUNT_ACCOUNT_NAME, REQUEST_LOG_ACTION_FLAG, REQUEST_LOG_ID, IDT_USER_USER_NAME, IDT_USER_EMAIL, IDT_USER_USER_EMAIL, IDT_USER_MOBILE, IDT_USER_WORK_NO, IDT_ORG_NAME, IDT_JOB_NAME};
}
@Override
protected void init() {
if (!PluginContexts.currentContext().isAvailable()) {
LogKit.error(DesignKit.i18nText("Plugin-icgq_Licence_Expired"));
return;
}
if (this.valueList != null) {
return;
}
this.valueList = new ArrayList();
// 域名
this.host = this.parameters[0].getValue().toString();
// 登录名(盟拓方提供)
String clientId = this.parameters[1].getValue().toString();
// 密钥固定分配)
String secret = this.parameters[2].getValue().toString();
// 开始日期
String startTime = this.parameters[3].getValue().toString();
try {
String accessToken = this.getAccessToken(clientId, secret);
JSONArray usersInfo = getUserInfo(accessToken, startTime);
addFRUserInfo2List(usersInfo);
} catch (IOException e) {
FineLoggerFactory.getLogger().error(e.getMessage(), e);
}
}
/**
* 人员获取
*
* @param accessToken
* @param startTime
* @return
* @throws IOException
*/
private JSONArray getUserInfo(String accessToken, String startTime) throws IOException {
Map<String, String> header = new HashMap<>();
header.put("Content-Type", "application/json;charset=UTF-8");
header.put("Authorization", accessToken);
JSONObject params = JSONObject.create();
params.put("size", "3000");
params.put("page", "1");
if (StringKit.isNotBlank(startTime)) {
params.put("startTime", startTime);
}
StringEntity stringEntity = new StringEntity(params.encode(), "UTF-8");
String response = HttpKit.executeAndParse(com.fanruan.api.net.http.rs.HttpRequest.custom()
.url(this.host + ACCOUNT_LIST).post(stringEntity).headers(header).build());
FineLoggerFactory.getLogger().info("icgq-UsersTableDataModel-getUserInfo-response:{}", response);
JSONObject responseJo = new JSONObject(response);
if (StringKit.equals(responseJo.getString("code"), "0")) {
return responseJo.getJSONObject("data").getJSONArray("list");
}
return JSONArray.create();
}
private void addFRUserInfo2List(JSONArray usersInfo) {
ArrayList<String> frUserInfo;
for (Object o : usersInfo) {
JSONObject userInfo = (JSONObject) o;
frUserInfo = new ArrayList<>();
frUserInfo.add(userInfo.getString(APP_ACCOUNT_ACCOUNT_UUID));
frUserInfo.add(userInfo.getString(APP_ACCOUNT_ACCOUNT_NO));
frUserInfo.add(userInfo.getString(APP_ACCOUNT_ACCOUNT_NAME));
frUserInfo.add(userInfo.getString(REQUEST_LOG_ACTION_FLAG));
frUserInfo.add(userInfo.getString(REQUEST_LOG_ID));
frUserInfo.add(userInfo.getString(IDT_USER_USER_NAME));
frUserInfo.add(userInfo.getString(IDT_USER_EMAIL));
frUserInfo.add(userInfo.getString(IDT_USER_USER_EMAIL));
frUserInfo.add(userInfo.getString(IDT_USER_WORK_NO));
frUserInfo.add(userInfo.getString(IDT_USER_MOBILE));
frUserInfo.add(userInfo.getString(IDT_ORG_NAME));
frUserInfo.add(userInfo.getString(IDT_JOB_NAME));
this.valueList.add(frUserInfo.toArray());
}
}
}

75
src/main/java/com/fr/plugin/icgq/data/WebTableDataModel.java

@ -0,0 +1,75 @@
/*
* Copyright (C), 2018-2021
* Project: starter
* FileName: WebTableDataModel
* Author: Louis
* Date: 2021/12/16 8:17
*/
package com.fr.plugin.icgq.data;
import com.fanruan.api.data.open.BaseDataModel;
import com.fanruan.api.err.TableDataException;
import com.fr.base.Base64;
import com.fr.stable.ParameterProvider;
import java.util.ArrayList;
/**
* <Function Description><br>
* <WebTableDataModel>
*
* @author fr.open
* @since 1.0.0
*/
public abstract class WebTableDataModel extends BaseDataModel {
private static final long serialVersionUID = 5010918328453603247L;
protected ParameterProvider[] parameters;
protected String[] columnNames;
protected ArrayList valueList = null;
protected String host;
public WebTableDataModel(ParameterProvider[] parameters) {
this.parameters = parameters;
}
@Override
public int getColumnCount() throws TableDataException {
return this.columnNames.length;
}
@Override
public String getColumnName(int i) throws TableDataException {
return columnNames[i];
}
@Override
public int getRowCount() throws TableDataException {
init();
return valueList.size();
}
@Override
public Object getValueAt(int row, int col) throws TableDataException {
init();
return ((Object[]) valueList.get(row))[col];
}
@Override
public void release() throws Exception {
this.parameters = null;
}
protected abstract void init();
/**
* 获取访问令牌接口Token
*
* @param clientId
* @param secret
* @return
*/
protected String getAccessToken(String clientId, String secret) {
String auth = clientId + ":" + secret;
return "Basic " + Base64.encode(auth.getBytes());
}
}

115
src/main/java/com/fr/plugin/icgq/kit/DepartmentServiceKit.java

@ -0,0 +1,115 @@
/*
* Copyright (C), 2018-2021
* Project: starter
* FileName: DepartmentServiceKit
* Author: Louis
* Date: 2021/5/14 9:38
*/
package com.fr.plugin.icgq.kit;
import com.fanruan.api.i18n.I18nKit;
import com.fanruan.api.util.StringKit;
import com.fr.decision.authority.AuthorityContext;
import com.fr.decision.authority.base.constant.type.operation.ManualOperationType;
import com.fr.decision.authority.data.Department;
import com.fr.decision.record.OperateMessage;
import com.fr.decision.webservice.exception.general.DuplicatedNameException;
import com.fr.decision.webservice.v10.user.DepartmentService;
import com.fr.general.ComparatorUtils;
import com.fr.intelli.record.MetricRegistry;
import com.fr.stable.StableUtils;
import com.fr.stable.query.QueryFactory;
import com.fr.stable.query.condition.QueryCondition;
import com.fr.stable.query.restriction.Restriction;
import com.fr.stable.query.restriction.RestrictionFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.fr.decision.authority.base.AuthorityConstants.DECISION_DEP_ROOT;
/**
* <Function Description><br>
* <DepartmentServiceKit>
*
* @author fr.open
* @since 1.0.0
*/
public class DepartmentServiceKit extends DepartmentService {
public static final String IDT_ORG__STATUS = "idt_org__status";
public static final String IDT_ORG__ORG_CODE = "idt_org__org_code";
public static final String IDT_ORG__SUP_ORG_CODE = "idt_org__sup_org_code";
public static final String IDT_ORG__NAME = "idt_org__name";
public static final String ROOT_DEP_ID = "10000000";
private static volatile DepartmentServiceKit departmentServiceKit = null;
public DepartmentServiceKit() {
}
public static DepartmentServiceKit getInstance() {
if (departmentServiceKit == null) {
departmentServiceKit = new DepartmentServiceKit();
}
return departmentServiceKit;
}
/**
* 根部门与FR根部门转换
*
* @param parentId
* @return
*/
public String changeRootId(String parentId) {
if (StringKit.isBlank(parentId) || StringKit.equals(parentId, ROOT_DEP_ID)) {
return DECISION_DEP_ROOT;
}
return parentId;
}
public void addDepartment(String id, String pId, String depName) throws Exception {
if (StringKit.equals(pId, DECISION_DEP_ROOT)) {
pId = null;
}
this.checkDuplicatedDepartmentName(pId, depName);
Department department = (new Department()).id(id).name(depName).parentId(pId).creationType(ManualOperationType.KEY).lastOperationType(ManualOperationType.KEY).enable(true);
AuthorityContext.getInstance().getDepartmentController().add(department);
MetricRegistry.getMetric().submit(OperateMessage.build("Dec-Module-User_Manager", "Dec-Department", this.getDepartmentFullPath(pId, depName, "/"), "Dec-Log_Add"));
}
private void checkDuplicatedDepartmentName(String parentId, String depName) throws Exception {
QueryCondition condition = QueryFactory.create().addRestriction(RestrictionFactory.and(new Restriction[]{RestrictionFactory.eq("name", depName), RestrictionFactory.eq("parentId", parentId)}));
Department sameNameDep = AuthorityContext.getInstance().getDepartmentController().findOne(condition);
if (sameNameDep != null) {
throw new DuplicatedNameException();
}
}
private String getDepartmentFullPath(String pId, String depName, String splitter) throws Exception {
List<String> paths = new ArrayList<>();
paths.add(depName);
while (!ComparatorUtils.equals(pId, DECISION_DEP_ROOT) && pId != null) {
Department parentDepartment = AuthorityContext.getInstance().getDepartmentController().getById(pId);
paths.add(parentDepartment.getName());
pId = parentDepartment.getParentId();
}
Collections.reverse(paths);
return StableUtils.join(paths.toArray(new String[0]), splitter);
}
public void editDepartment(String departmentId, String depName, String pId) throws Exception {
if (StringKit.equals(pId, DECISION_DEP_ROOT)) {
pId = null;
}
Department department = AuthorityContext.getInstance().getDepartmentController().getById(departmentId);
String departmentFullPath = DepartmentService.getInstance().getDepartmentFullPath(departmentId);
if (!ComparatorUtils.equals(department.getName(), depName)) {
this.checkDuplicatedDepartmentName(department.getParentId(), depName);
department.setName(depName);
department.setParentId(pId);
AuthorityContext.getInstance().getDepartmentController().update(department);
}
MetricRegistry.getMetric().submit(OperateMessage.build("Dec-Module-User_Manager", "Dec-Department", DepartmentService.getInstance().getDepartmentFullPath(departmentId), "Dec-Log_Update", I18nKit.getLocText("Fine-Dec_Department") + ":" + departmentFullPath));
}
}

730
src/main/java/com/fr/plugin/icgq/kit/HttpKit.java

@ -0,0 +1,730 @@
/*
* Copyright (C), 2018-2021
* Project: starter
* FileName: HttpKit
* Author: Louis
* Date: 2021/3/17 14:19
*/
package com.fr.plugin.icgq.kit;
import com.fanruan.api.log.LogKit;
import com.fanruan.api.macro.EncodeConstants;
import com.fanruan.api.net.http.rs.HttpRequest;
import com.fanruan.api.net.http.rs.*;
import com.fr.third.guava.collect.Maps;
import com.fr.third.org.apache.http.*;
import com.fr.third.org.apache.http.client.HttpRequestRetryHandler;
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.HttpEntityEnclosingRequestBase;
import com.fr.third.org.apache.http.client.methods.HttpRequestBase;
import com.fr.third.org.apache.http.client.protocol.HttpClientContext;
import com.fr.third.org.apache.http.client.utils.URIBuilder;
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.routing.HttpRoute;
import com.fr.third.org.apache.http.conn.socket.ConnectionSocketFactory;
import com.fr.third.org.apache.http.conn.socket.LayeredConnectionSocketFactory;
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.conn.ssl.SSLSocketFactory;
import com.fr.third.org.apache.http.conn.ssl.TrustStrategy;
import com.fr.third.org.apache.http.entity.FileEntity;
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.impl.client.CloseableHttpClient;
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.message.BasicNameValuePair;
import com.fr.third.org.apache.http.protocol.HttpContext;
import com.fr.third.org.apache.http.ssl.SSLContextBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static com.fanruan.api.net.http.rs.HttpRequestType.POST;
/**
* @author richie
* @version 10.0
* Created by richie on 2019-08-29
* <p>
* http请求工具类封装了用于http请求的各种方法
* 新增https忽略证书功能 Update By louis on 2021-03-17
* </p>
*/
public class HttpKit {
private static final int RETRY_TIMES = 5;
private final static Object SYNC_LOCK = new Object();
private static CloseableHttpClient httpClient = null;
/**
* 根据请求地址创建HttpClient对象
*
* @param url 请求地址
* @return HttpClient对象
*/
public static CloseableHttpClient getHttpClient(String url) {
String hostname = url.split("/")[2];
int port = 80;
if (hostname.contains(":")) {
String[] arr = hostname.split(":");
hostname = arr[0];
port = Integer.parseInt(arr[1]);
}
if (httpClient == null) {
synchronized (SYNC_LOCK) {
if (httpClient == null) {
// httpClient = createHttpClient(hostname, port, SSLContexts.createDefault());
try {
httpClient = createHttpClient(hostname, port, createIgnoreVerifySSL());
} catch (NoSuchAlgorithmException e) {
LogKit.error(e.getMessage(), e);
} catch (KeyManagementException e) {
LogKit.error(e.getMessage(), e);
}
}
}
}
return httpClient;
}
/**
* 新增了https 请求绕过证书认证
*
* @return
* @throws NoSuchAlgorithmException
* @throws KeyManagementException
*/
public static SSLContext createIgnoreVerifySSL() throws NoSuchAlgorithmException, KeyManagementException {
// 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;
try {
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
//信任所有
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
}).build();
return sslContext;
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
LogKit.error(e.getMessage(), e);
}
return null;
}
public static CloseableHttpClient createHttpClient(String hostname, int port, SSLContext sslContext) {
return createHttpClient(200, 40, 100, hostname, port, sslContext);
}
private static CloseableHttpClient createHttpClient(int maxTotal,
int maxPerRoute,
int maxRoute,
String hostname,
int port,
SSLContext sslContext) {
ConnectionSocketFactory socketFactory = PlainConnectionSocketFactory.getSocketFactory();
LayeredConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
Registry<ConnectionSocketFactory> registry = RegistryBuilder
.<ConnectionSocketFactory>create()
.register("http", socketFactory)
.register("https", sslConnectionSocketFactory)
.build();
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
// 将最大连接数增加
cm.setMaxTotal(maxTotal);
// 将每个路由基础的连接增加
cm.setDefaultMaxPerRoute(maxPerRoute);
HttpHost httpHost = new HttpHost(hostname, port);
// 将目标主机的最大连接数增加
cm.setMaxPerRoute(new HttpRoute(httpHost), maxRoute);
// 请求重试处理
HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {
@Override
public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
if (executionCount >= RETRY_TIMES) {// 如果已经重试了5次,就放弃
return false;
}
if (exception instanceof NoHttpResponseException) {// 如果服务器丢掉了连接,那么就重试
return true;
}
if (exception instanceof SSLHandshakeException) {// 不要重试SSL握手异常
return false;
}
if (exception instanceof InterruptedIOException) {// 超时
return false;
}
if (exception instanceof UnknownHostException) {// 目标服务器不可达
return false;
}
if (exception instanceof SSLException) {// SSL握手异常
return false;
}
HttpClientContext clientContext = HttpClientContext.adapt(context);
com.fr.third.org.apache.http.HttpRequest request = clientContext.getRequest();
// 如果请求是幂等的,就再次尝试
return !(request instanceof HttpEntityEnclosingRequest);
}
};
return HttpClients.custom()
.setConnectionManager(cm)
.setRetryHandler(httpRequestRetryHandler)
.build();
}
/**
* 设置 httpEntity
*
* @param requestBase 请求体
* @param httpRequest 请求
*/
private static void setHttpEntity(@NotNull HttpEntityEnclosingRequestBase requestBase, @NotNull HttpRequest httpRequest) {
HttpEntity httpEntity = httpRequest.getHttpEntity();
if (httpEntity != null) {
// 如果存在 httpEntity 直接设置
requestBase.setEntity(httpEntity);
return;
}
Map<String, String> params = httpRequest.getParams();
if (params == null || params.isEmpty()) {
return;
}
List<NameValuePair> pairs = new ArrayList<NameValuePair>();
for (Map.Entry<String, String> entry : params.entrySet()) {
pairs.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
try {
requestBase.setEntity(new UrlEncodedFormEntity(pairs, httpRequest.getEncoding()));
} catch (UnsupportedEncodingException e) {
LogKit.error(e.getMessage(), e);
}
}
private static <V> Map<String, String> transformMap(Map<String, V> oldMap) {
if (oldMap == null) {
return null;
}
return Maps.transformEntries(oldMap, new Maps.EntryTransformer<String, V, String>() {
@Override
public String transformEntry(@Nullable String key, @Nullable V value) {
return value == null ? null : value.toString();
}
});
}
/**
* 发起POST请求并获取返回的文本
*
* @param url 响应请求的的服务器地址
* @param params POST请求的参数
* @return 服务器返回的文本内容
*/
public static <V> String post(String url, Map<String, V> params) throws IOException {
return executeAndParse(HttpRequest
.custom()
.url(url)
.post(transformMap(params))
.build());
}
/**
* 发起POST请求并获取返回的文本
*
* @param url 响应请求的的服务器地址
* @param params POST请求的参数
* @param responseType 返回类型
* @return 服务器返回的文本内容
* @see com.fanruan.api.net.http.HttpKit#execute(HttpRequest)
*/
@Deprecated
public static <T, V> T post(String url, Map<String, V> params, HttpResponseType<T> responseType) throws IOException {
CloseableHttpResponse response = execute(HttpRequest
.custom()
.url(url)
.post(transformMap(params))
.build());
return responseType.result(response, null);
}
/**
* 发起POST请求并获取返回的文本
*
* @param url 响应请求的的服务器地址
* @param params POST请求的参数
* @param headers 请求头
* @return 服务器返回的文本内容
* @see com.fanruan.api.net.http.HttpKit#executeAndParse(HttpRequest)
*/
@Deprecated
public static <V> String post(String url, Map<String, V> params, Map<String, String> headers) throws IOException {
return executeAndParse(HttpRequest
.custom()
.url(url)
.post(transformMap(params))
.headers(headers)
.build());
}
/**
* 发起POST请求并获取返回的文本
*
* @param url 响应请求的的服务器地址
* @param params POST请求的参数
* @param headers 请求头
* @param responseType 返回类型
* @return 服务器返回的文本内容
* @see com.fanruan.api.net.http.HttpKit#execute(HttpRequest)
*/
@Deprecated
public static <T, V> T post(String url, Map<String, V> params, Map<String, String> headers, HttpResponseType<T> responseType) throws IOException {
CloseableHttpResponse response = execute(HttpRequest
.custom()
.url(url)
.post(transformMap(params))
.headers(headers)
.build());
return responseType.result(response, null);
}
/**
* 发起POST请求并获取返回的文本
*
* @param url 响应请求的的服务器地址
* @param params POST请求的参数
* @param responseEncoding 响应的文本的编码
* @return 服务器返回的文本内容
* @see com.fanruan.api.net.http.HttpKit#executeAndParse(HttpRequest, BaseHttpResponseHandle)
*/
@Deprecated
public static <V> String post(String url, Map<String, V> params, String responseEncoding) throws IOException {
return executeAndParse(HttpRequest
.custom()
.url(url)
.post(transformMap(params))
.build(),
new TextResponseHandle(responseEncoding));
}
/**
* 发起POST请求并获取返回的文本
*
* @param url 响应请求的的服务器地址
* @param params POST请求的参数
* @param responseEncoding 响应的文本的编码
* @param headers 请求头
* @return 服务器返回的文本内容
* @see com.fanruan.api.net.http.HttpKit#executeAndParse(HttpRequest, BaseHttpResponseHandle)
*/
@Deprecated
public static <V> String post(String url, Map<String, V> params, String responseEncoding, Map<String, String> headers) throws IOException {
return executeAndParse(HttpRequest
.custom()
.url(url)
.post(transformMap(params))
.headers(headers)
.build(),
new TextResponseHandle(responseEncoding));
}
/**
* 发起POST请求并获取返回的文本
*
* @param url 响应请求的的服务器地址
* @param params POST请求的参数
* @param responseEncoding 响应的文本的编码
* @param paramsEncoding 参数编码
* @param headers 请求头
* @return 服务器返回的文本内容
* @see com.fanruan.api.net.http.HttpKit#executeAndParse(HttpRequest, BaseHttpResponseHandle)
*/
public static <V> String post(String url, Map<String, V> params, String responseEncoding, String paramsEncoding, Map<String, String> headers) throws IOException {
return executeAndParse(HttpRequest
.custom()
.url(url)
.post(transformMap(params))
.encoding(paramsEncoding)
.headers(headers)
.build(),
new TextResponseHandle(responseEncoding));
}
/**
* 发起POST请求并获取返回的文本
*
* @param url 响应请求的的服务器地址
* @param params POST请求的参数
* @param responseEncoding 响应的文本的编码
* @param paramsEncoding 参数编码
* @param headers 请求头
* @param responseType 返回值类型
* @return 服务器返回的文本内容
* @see com.fanruan.api.net.http.HttpKit#execute(HttpRequest)
*/
public static <T, V> T post(String url, Map<String, V> params, String responseEncoding, String paramsEncoding, Map<String, String> headers, HttpResponseType<T> responseType) throws IOException {
CloseableHttpResponse response = execute(HttpRequest
.custom()
.url(url)
.post(transformMap(params))
.encoding(paramsEncoding)
.headers(headers)
.build());
return responseType.result(response, responseEncoding);
}
/**
* 发起GET请求并获取返回的文本
*
* @param url 响应请求的的服务器地址
* @return 服务器返回的文本内容
*/
public static String get(String url) throws IOException {
return executeAndParse(HttpRequest.custom().url(url).build());
}
/**
* 发起GET请求并获取返回的文本
*
* @param url 响应请求的的服务器地址
* @param params 参数
* @return 服务器返回的文本内容
*/
public static String get(String url, Map<String, String> params) throws IOException {
return executeAndParse(HttpRequest.custom().url(url).params(params).build());
}
/**
* 发起GET请求并获取返回的文本
*
* @param url 响应请求的的服务器地址
* @param params 参数
* @param headers 请求头
* @return 服务器返回的文本内容
* @see com.fanruan.api.net.http.HttpKit#executeAndParse(HttpRequest, BaseHttpResponseHandle)
*/
public static String get(String url, Map<String, String> params, Map<String, String> headers) throws IOException {
return executeAndParse(HttpRequest.custom().url(url).params(params).headers(headers).build());
}
/**
* 发起GET请求并获取返回的文本
*
* @param url 响应请求的的服务器地址
* @param params 参数
* @param responseEncoding 返回的文本的编码
* @return 服务器返回的文本内容
* @see com.fanruan.api.net.http.HttpKit#executeAndParse(HttpRequest, BaseHttpResponseHandle)
*/
public static String get(String url, Map<String, String> params, String responseEncoding) throws IOException {
return executeAndParse(HttpRequest
.custom()
.url(url)
.params(params)
.build(),
new TextResponseHandle(responseEncoding));
}
/**
* 发起GET请求并获取返回的文本
*
* @param url 响应请求的的服务器地址
* @param params 参数
* @param responseEncoding 返回的文本的编码
* @return 服务器返回的文本内容
* @see com.fanruan.api.net.http.HttpKit#executeAndParse(HttpRequest, BaseHttpResponseHandle)
*/
public static String get(String url, Map<String, String> params, String responseEncoding, Map<String, String> headers) throws IOException {
return executeAndParse(HttpRequest
.custom()
.url(url)
.params(params)
.headers(headers)
.build(),
new TextResponseHandle(responseEncoding));
}
/**
* 从指定的地址下载文件
*
* @param url 文件下载地址
* @return 文件的字节流
* @throws IOException 下载过程中出现错误则抛出此异常
*/
public static ByteArrayInputStream download(String url) throws IOException {
return executeAndParse(HttpRequest.custom().url(url).build(), StreamResponseHandle.DEFAULT);
}
/**
* 从指定的地址下载文件
*
* @param url 文件下载地址
* @param params 参数对
* @param responseEncoding 响应的文件编码
* @param headers 请求头
* @return 文件的字节流
* @throws IOException 下载过程中出现错误则抛出此异常
*/
public static ByteArrayInputStream download(String url, Map<String, String> params, String responseEncoding, Map<String, String> headers) throws IOException {
return executeAndParse(HttpRequest
.custom()
.url(url)
.params(params)
.headers(headers)
.build(),
new StreamResponseHandle(responseEncoding));
}
/**
* 上传文件到指定的服务器
*
* @param url 接收文件的服务器地址
* @param file 要上传的文件默认的文件编码为utf-8
* @throws IOException 上传中出现错误则抛出此异常
*/
public static void upload(String url, File file) throws IOException {
upload(url, file, Charset.forName("utf-8"));
}
/**
* 上传文件到指定的服务器
*
* @param url 接收文件的服务器地址
* @param file 要上传的文件
* @param charset 文件的编码
* @throws IOException 上传中出现错误则抛出此异常
*/
public static void upload(String url, File file, Charset charset) throws IOException {
upload(url, new FileEntity(file), charset);
}
/**
* 上传文件到指定的服务器
*
* @param url 接收文件的服务器地址
* @param builder 附件构造器
* @param charset 文件的编码
* @throws IOException 上传中出现错误则抛出此异常
*/
public static void upload(String url, MultipartEntityBuilder builder, Charset charset) throws IOException {
upload(url, builder, charset, Collections.<String, String>emptyMap(), POST);
}
/**
* 上传文件到指定的服务器
*
* @param url 接收文件的服务器地址
* @param fileEntity 文件实体
* @param charset 文件的编码
* @throws IOException 上传中出现错误则抛出此异常
*/
public static void upload(String url, FileEntity fileEntity, Charset charset) throws IOException {
upload(url, fileEntity, charset, Collections.<String, String>emptyMap(), POST);
}
/**
* 上传多文件到指定的服务器
*
* @param url 接收文件的服务器地址
* @param builder 附件构造器
* @param charset 文件的编码
* @param headers 请求头
* @param httpRequestType 请求类型
* @throws IOException 上传中出现错误则抛出此异常
*/
public static void upload(String url, MultipartEntityBuilder builder, Charset charset, Map<String, String> headers, HttpRequestType httpRequestType) throws IOException {
// richie:采用浏览器模式,防止出现乱码
builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
HttpEntity reqEntity = builder.setCharset(charset).build();
upload(url, reqEntity, charset, headers, httpRequestType);
}
/**
* 上传文件到指定的服务器
*
* @param url 接收文件的服务器地址
* @param reqEntity 请求实体
* @param charset 文件的编码
* @param headers 请求头
* @param httpRequestType 请求类型
* @throws IOException 上传中出现错误则抛出此异常
*/
public static void upload(String url, HttpEntity reqEntity, Charset charset, Map<String, String> headers, HttpRequestType httpRequestType) throws IOException {
executeAndParse(HttpRequest
.custom()
.url(url)
.headers(headers)
.method(httpRequestType)
.httpEntity(reqEntity)
.encoding(charset.toString())
.build(),
UploadResponseHandle.DEFAULT);
}
/**
* 请求资源或服务使用默认文本http解析器UTF-8编码
*
* @param httpRequest httpRequest
* @return 返回处理结果
*/
public static String executeAndParse(HttpRequest httpRequest) throws IOException {
return executeAndParse(httpRequest, TextResponseHandle.DEFAULT);
}
/**
* 请求资源或服务自请求参数并指定 http 响应处理器
*
* <pre>
* String res = HttpToolbox.executeAndParse(HttpRequest
* .custom()
* .url("")
* .build(),
* TextResponseHandle.DEFAULT);
* </pre>
*
* @param httpRequest httpRequest
* @param handle http 解析器
* @return 返回处理结果
*/
public static <T> T executeAndParse(HttpRequest httpRequest, BaseHttpResponseHandle<T> handle) throws IOException {
return handle.parse(execute(httpRequest));
}
/**
* 请求资源或服务传入请求参数
*
* @param httpRequest httpRequest
* @return 返回处理结果
*/
public static CloseableHttpResponse execute(HttpRequest httpRequest) throws IOException {
return execute(getHttpClient(httpRequest.getUrl()), httpRequest);
}
/**
* 请求资源或服务自定义client对象传入请求参数
*
* @param httpClient http客户端
* @param httpRequest httpRequest
* @return 返回处理结果
*/
public static CloseableHttpResponse execute(CloseableHttpClient httpClient, HttpRequest httpRequest) throws IOException {
String url = httpRequest.getUrl();
// 创建请求对象
HttpRequestBase httpRequestBase = httpRequest.getMethod().createHttpRequest(url);
// 设置header信息
httpRequestBase.setHeader("User-Agent", "Mozilla/5.0");
Map<String, String> headers = httpRequest.getHeaders();
if (headers != null && !headers.isEmpty()) {
for (Map.Entry<String, String> entry : headers.entrySet()) {
httpRequestBase.setHeader(entry.getKey(), entry.getValue());
}
}
// 配置请求的设置
RequestConfig requestConfig = httpRequest.getConfig();
if (requestConfig != null) {
httpRequestBase.setConfig(requestConfig);
}
// 判断是否支持设置entity(仅HttpPost、HttpPut、HttpPatch支持)
if (HttpEntityEnclosingRequestBase.class.isAssignableFrom(httpRequestBase.getClass())) {
setHttpEntity((HttpEntityEnclosingRequestBase) httpRequestBase, httpRequest);
} else {
Map<String, String> params = httpRequest.getParams();
if (params != null && !params.isEmpty()) {
// 注意get等不支持设置entity需要更新拼接之后的URL,但是url变量没有更新
httpRequestBase.setURI(URI.create(buildUrl(url, params, httpRequest.getEncoding())));
}
}
return httpClient.execute(httpRequestBase);
}
/**
* 构建 Url
*
* @param url 请求地址
* @param params 参数
* @return 拼接之后的地址
*/
public static String buildUrl(String url, Map<String, String> params) {
try {
return buildUrl(url, params, EncodeConstants.ENCODING_UTF_8);
} catch (UnsupportedEncodingException ignore) {
}
return url;
}
/**
* 构建 Url
*
* @param url 请求地址
* @param params 参数
* @return 拼接之后的地址
* @throws UnsupportedEncodingException 不支持的编码
*/
private static String buildUrl(String url, Map<String, String> params, String paramsEncoding) throws UnsupportedEncodingException {
if (params == null || params.isEmpty()) {
return url;
}
URIBuilder builder;
try {
builder = new URIBuilder(url);
for (Map.Entry<String, String> entry : params.entrySet()) {
String key = URLEncoder.encode(entry.getKey(), paramsEncoding);
String value = URLEncoder.encode(entry.getValue(), paramsEncoding);
builder.setParameter(key, value);
}
return builder.build().toString();
} catch (URISyntaxException e) {
LogKit.debug("Error to build url, please check the arguments.");
}
return url;
}
}

173
src/main/java/com/fr/plugin/icgq/kit/UserServiceKit.java

@ -0,0 +1,173 @@
/*
* Copyright (C), 2018-2021
* Project: starter
* FileName: UserServiceKit
* Author: Louis
* Date: 2021/5/14 8:28
*/
package com.fr.plugin.icgq.kit;
import com.fanruan.api.log.LogKit;
import com.fanruan.api.util.StringKit;
import com.fr.decision.authority.AuthorityContext;
import com.fr.decision.authority.data.Department;
import com.fr.decision.authority.data.Post;
import com.fr.decision.authority.data.User;
import com.fr.decision.privilege.TransmissionTool;
import com.fr.decision.webservice.bean.user.DepartmentPostBean;
import com.fr.decision.webservice.bean.user.UserBean;
import com.fr.decision.webservice.v10.user.PositionService;
import com.fr.decision.webservice.v10.user.UserService;
import com.fr.json.JSONObject;
import com.fr.stable.query.QueryFactory;
import com.fr.stable.query.condition.QueryCondition;
import com.fr.stable.query.restriction.RestrictionFactory;
import java.util.ArrayList;
import java.util.List;
import static com.fr.plugin.icgq.kit.DepartmentServiceKit.IDT_ORG__NAME;
/**
* <Function Description><br>
* <UserServiceKit>
*
* @author fr.open
* @since 1.0.0
*/
public class UserServiceKit extends UserService {
public static final String USER_NAME = "app_account__account_no";
public static final String ACCOUNT_NAME = "app_account__account_name";
public static final String ACCOUNT_NO = "app_account__account_no";
public static final String ENABLE = "app_account__status";
public static final String EMAIL = "idt_user__email";
public static final String MOBILE = "idt_user__mobile";
public static final String POSITION = "idt_job__name";
public static final String PASSWORD = "idt_user__pwd";
private static volatile UserServiceKit userServiceKit = null;
public UserServiceKit() {
}
public static UserServiceKit getInstance() {
if (userServiceKit == null) {
userServiceKit = new UserServiceKit();
}
return userServiceKit;
}
public UserBean createUserBean(JSONObject account) throws Exception {
UserBean userBean = new UserBean();
userBean.setUsername(account.getString(USER_NAME));
userBean.setRealName(account.getString(ACCOUNT_NAME));
userBean.setEnable(true);
userBean.setEmail(account.getString(EMAIL));
userBean.setMobile(account.getString(MOBILE));
userBean.setPassword(TransmissionTool.defaultEncrypt(account.getString(USER_NAME) + "123456"));
String departmentId;
String position = StringKit.EMPTY;
try {
String depName = account.getJSONArray("orgs").getJSONObject(0).getString(IDT_ORG__NAME);
QueryCondition condition = QueryFactory.create().addRestriction(RestrictionFactory.and(RestrictionFactory.eq("name", depName), RestrictionFactory.eq("parentId", null)));
Department department = AuthorityContext.getInstance().getDepartmentController().findOne(condition);
departmentId = department.getId();
} catch (Exception e) {
departmentId = StringKit.EMPTY;
}
if (StringKit.isNotBlank(departmentId)) {
List<String> departmentPostIds = UserServiceKit.getInstance().createDepartmentPostIds(departmentId, position);
userBean.setDepartmentPostIds(departmentPostIds);
}
return userBean;
}
/**
* 部门id转为部门职务组合list
*
* @param departmentPostId
* @param title
* @return
* @throws Exception
*/
public List<String> createDepartmentPostIds(String departmentPostId, String title) throws Exception {
List<String> departmentPostIds = new ArrayList<>();
// 职务处理
// String positionId = positionSynOperation(title, departmentPostId);
// if (StringKit.isNotBlank(positionId)) {
// departmentPostId = departmentPostId + "@@@" + positionId;
// }
departmentPostIds.add(departmentPostId);
return departmentPostIds;
}
/**
* 职务同步操作
*
* @param title
* @return
* @throws Exception
*/
private String positionSynOperation(String title, String departmentId) throws Exception {
String position = StringKit.isNotBlank(title) ? title : "职员";
Post post = AuthorityContext.getInstance().getPostController().findOne(QueryFactory.create().addRestriction(RestrictionFactory.eq("name", position)));
String positionId;
if (post == null) {
positionId = PositionService.getInstance().addPosition(position, position);
} else {
positionId = post.getId();
}
List<DepartmentPostBean> departmentPostBeanList = PositionService.getInstance().getPositionsUnderParentDepartment(getAdminUserId(), departmentId, position);
if (departmentPostBeanList == null || departmentPostBeanList.isEmpty()) {
try {
AuthorityContext.getInstance().getPostController().addPostToDepartment(positionId, departmentId);
} catch (Exception e) {
LogKit.info("icgq-UserServiceKit-positionSynOperation-addPostToDepartmentFailed-position:{}, departmentId:{}", positionId + position, departmentId);
LogKit.error(e.getMessage(), e);
}
}
return positionId;
}
/**
* 获取管理员id
*
* @return
* @throws Exception
*/
public String getAdminUserId() throws Exception {
List<String> adminUserIdList = UserService.getInstance().getAdminUserIdList();
if (adminUserIdList.isEmpty()) {
return "admin";
}
return StringKit.isNotBlank(adminUserIdList.get(0)) ? adminUserIdList.get(0) : "admin";
}
public UserBean updateUserBean(JSONObject account) throws Exception {
User user = UserService.getInstance().getUserByUserName(account.getString(USER_NAME));
if (user == null) {
return null;
}
UserBean userBean = new UserBean();
userBean.setId(user.getId());
userBean.setUsername(user.getUserName());
userBean.setRealName(account.getString(ACCOUNT_NAME));
userBean.setEnable(true);
userBean.setEmail(account.getString(EMAIL));
userBean.setMobile(account.getString(MOBILE));
String departmentId;
String position = StringKit.EMPTY;
try {
String depName = account.getJSONArray("orgs").getJSONObject(0).getString(IDT_ORG__NAME);
QueryCondition condition = QueryFactory.create().addRestriction(RestrictionFactory.and(RestrictionFactory.eq("name", depName), RestrictionFactory.eq("parentId", null)));
Department department = AuthorityContext.getInstance().getDepartmentController().findOne(condition);
departmentId = department.getId();
} catch (Exception e) {
departmentId = StringKit.EMPTY;
}
if (StringKit.isNotBlank(departmentId)) {
List<String> departmentPostIds = UserServiceKit.getInstance().createDepartmentPostIds(departmentId, position);
userBean.setDepartmentPostIds(departmentPostIds);
}
return userBean;
}
}

57
src/main/java/com/fr/plugin/icgq/provider/UsersTableDataDefine.java

@ -0,0 +1,57 @@
/*
* Copyright (C), 2018-2021
* Project: starter
* FileName: WechatTableDataDefine
* Author: Louis
* Date: 2021/12/8 16:41
*/
package com.fr.plugin.icgq.provider;
import com.fanruan.api.design.DesignKit;
import com.fr.base.TableData;
import com.fr.design.data.tabledata.tabledatapane.AbstractTableDataPane;
import com.fr.design.fun.ServerTableDataDefineProvider;
import com.fr.design.fun.impl.AbstractTableDataDefineProvider;
import com.fr.plugin.icgq.data.UsersTableData;
import com.fr.plugin.icgq.ui.UsersTableDataPane;
/**
* <Function Description><br>
* <WechatTableDataDefine>
*
* @author fr.open
* @since 1.0.0
*/
public class UsersTableDataDefine extends AbstractTableDataDefineProvider implements ServerTableDataDefineProvider {
public static final String ICON_PATH = "/com/fr/plugin/icgq/images/logo16.png";
@Override
public Class<? extends TableData> classForTableData() {
return UsersTableData.class;
}
@Override
public Class<? extends TableData> classForInitTableData() {
return UsersTableData.class;
}
@Override
public Class<? extends AbstractTableDataPane> appearanceForTableData() {
return UsersTableDataPane.class;
}
@Override
public String nameForTableData() {
return DesignKit.i18nText("Plugin-icgq_Users_Table_Data");
}
@Override
public String prefixForTableData() {
return DesignKit.i18nText("Plugin-icgq_Users_Table_Data");
}
@Override
public String iconPathForTableData() {
return ICON_PATH;
}
}

181
src/main/java/com/fr/plugin/icgq/service/ReportDataHandler.java

@ -0,0 +1,181 @@
/*
* Copyright (C), 2015-2019
* FileName: ReportDataHandler
* Author: Louis
* Date: 2019/8/22 8:34
* Description: ReportDataHandler
* History:
* <author> <time> <version> <desc>
*/
package com.fr.plugin.icgq.service;
import com.fanruan.api.i18n.I18nKit;
import com.fanruan.api.log.LogKit;
import com.fanruan.api.util.StringKit;
import com.fr.base.Base64;
import com.fr.data.NetworkHelper;
import com.fr.decision.authority.AuthorityContext;
import com.fr.decision.authority.data.Department;
import com.fr.decision.authority.data.User;
import com.fr.decision.fun.impl.BaseHttpHandler;
import com.fr.decision.webservice.bean.user.UserBean;
import com.fr.decision.webservice.bean.user.UserUpdateBean;
import com.fr.decision.webservice.v10.user.DepartmentService;
import com.fr.decision.webservice.v10.user.UserService;
import com.fr.general.ComparatorUtils;
import com.fr.intelli.record.Focus;
import com.fr.intelli.record.Original;
import com.fr.json.JSONObject;
import com.fr.plugin.context.PluginContexts;
import com.fr.plugin.icgq.config.IcgqConfig;
import com.fr.plugin.icgq.kit.UserServiceKit;
import com.fr.record.analyzer.EnableMetrics;
import com.fr.stable.query.QueryFactory;
import com.fr.stable.query.condition.QueryCondition;
import com.fr.stable.query.restriction.RestrictionFactory;
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;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import static com.fr.decision.authority.base.AuthorityConstants.DECISION_DEP_ROOT;
import static com.fr.plugin.icgq.LocaleFinder.PLUGIN_ID;
import static com.fr.plugin.icgq.data.UsersTableDataModel.REQUEST_LOG_ACTION_FLAG;
import static com.fr.plugin.icgq.kit.DepartmentServiceKit.IDT_ORG__NAME;
import static com.fr.plugin.icgq.kit.DepartmentServiceKit.IDT_ORG__ORG_CODE;
import static com.fr.plugin.icgq.kit.UserServiceKit.USER_NAME;
/**
* Function Description<br>
* ReportDataHandler
*
* @author fr.open
* @since 1.0.0
*/
@EnableMetrics
public class ReportDataHandler extends BaseHttpHandler {
public static final String IAM_USERS = "/iam/data";
public static final String BASIC_AUTH = "Basic ";
@Override
public RequestMethod getMethod() {
return RequestMethod.POST;
}
@Override
public String getPath() {
return IAM_USERS;
}
@Override
public boolean isPublic() {
return true;
}
@Override
@Focus(id = PLUGIN_ID, text = "Plugin-icgq", source = Original.PLUGIN)
public void handle(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (!PluginContexts.currentContext().isAvailable()) {
LogKit.error(I18nKit.getLocText("Plugin-icgq_Licence_Expired"));
printErrorJSON(response, I18nKit.getLocText("Plugin-icgq_Licence_Expired"));
return;
}
String authorization = request.getHeader("Authorization");
LogKit.info("icgq-ReportDataHandler-authorization:{}", authorization);
if (StringKit.isEmpty(authorization) || !authorization.startsWith(BASIC_AUTH) || !StringKit.equals(authorization, getAuth())) {
printErrorJSON(response, "认证错误");
return;
}
try {
JSONObject userInfo = this.getParams(request);
if (userInfo.getJSONArray("orgs") != null && !userInfo.getJSONArray("orgs").isEmpty()) {
departmentSynOperation(userInfo.getJSONArray("orgs").getJSONObject(0));
}
userSynOperation(userInfo);
printResultJSON(response, userInfo.getString("request_log__id"));
} catch (Exception e) {
LogKit.error(e.getMessage(), e);
printErrorJSON(response, e.getMessage());
}
}
private String getAuth() {
String auth = IcgqConfig.getInstance().getClientId() + ":" + IcgqConfig.getInstance().getClientSecret();
return "Basic " + Base64.encode(auth.getBytes());
}
/**
* 部门组织的新增更新操作
*
* @param departmentJo
* @throws Exception
*/
private void departmentSynOperation(JSONObject departmentJo) throws Exception {
LogKit.info("icgq-ReportDataHandler-departmentSynOperation-departmentJo:{}", departmentJo.encode());
String departmentId = departmentJo.getString(IDT_ORG__ORG_CODE);
// String parentId = departmentJo.getString(IDT_ORG__SUP_ORG_CODE);
// parentId = DepartmentServiceKit.getInstance().changeRootId(parentId);
String parentId = DECISION_DEP_ROOT;
String depName = departmentJo.getString(IDT_ORG__NAME);
QueryCondition condition = QueryFactory.create().addRestriction(RestrictionFactory.and(RestrictionFactory.eq("name", depName), RestrictionFactory.eq("parentId", null)));
Department sameNameDep = AuthorityContext.getInstance().getDepartmentController().findOne(condition);
if (sameNameDep == null) {
DepartmentService.getInstance().addDepartment(parentId, depName);
}
}
/**
* 用户新增和更新操作
*
* @param userJo
*/
private void userSynOperation(JSONObject userJo) throws Exception {
LogKit.info("icgq-ReportDataHandler-userSynOperation-userJo:{}", userJo.encode());
UserBean userBean;
if (ComparatorUtils.equals(userJo.getInt(REQUEST_LOG_ACTION_FLAG), 0)) {
userBean = UserServiceKit.getInstance().createUserBean(userJo);
UserService.getInstance().addUser(userBean);
} else if (ComparatorUtils.equals(userJo.getInt(REQUEST_LOG_ACTION_FLAG), 1)) {
userBean = UserServiceKit.getInstance().updateUserBean(userJo);
if (userBean == null) {
return;
}
UserServiceKit.getInstance().editUser(userBean, UserServiceKit.getInstance().getAdminUserId());
} else if (ComparatorUtils.equals(userJo.getInt(REQUEST_LOG_ACTION_FLAG), 2)) {
User user = UserService.getInstance().getUserByUserName(userJo.getString(USER_NAME));
String[] removeUserIds = new String[]{user.getId()};
UserUpdateBean userUpdateBean = new UserUpdateBean();
userUpdateBean.setRemoveUserIds(removeUserIds);
UserService.getInstance().deleteUsers(userUpdateBean);
}
}
private JSONObject getParams(HttpServletRequest req) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(NetworkHelper.getRequestInputStream(req), StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
String temp;
while ((temp = br.readLine()) != null) {
sb.append(temp);
}
br.close();
return new JSONObject(sb.toString());
}
private void printResultJSON(HttpServletResponse res, String data) throws Exception {
JSONObject result = JSONObject.create();
result.put("code", "0").put("timestamp", String.valueOf(System.currentTimeMillis()))
.put("data", data).put("msg", "操作成功");
WebUtils.printAsJSON(res, result);
}
private void printErrorJSON(HttpServletResponse res, String msg) throws Exception {
JSONObject errorJSON = JSONObject.create();
errorJSON.put("code", "1").put("msg", msg);
WebUtils.printAsJSON(res, errorJSON);
}
}

33
src/main/java/com/fr/plugin/icgq/service/RequestHandlerBridge.java

@ -0,0 +1,33 @@
/*
* Copyright (C), 2015-2019
* FileName: RequestHandlerBridge
* Author: Louis
* Date: 2019/8/17 22:20
* Description: RequestHandlerBridge
* History:
* <author> <time> <version> <desc>
*/
package com.fr.plugin.icgq.service;
import com.fr.decision.fun.impl.AbstractHttpHandlerProvider;
import com.fr.decision.fun.impl.BaseHttpHandler;
import com.fr.stable.fun.Authorize;
import static com.fr.plugin.icgq.LocaleFinder.PLUGIN_ID;
/**
* Function Description<br>
* RequestHandlerBridge
*
* @author fr.open
* @since 1.0.0
*/
@Authorize(callSignKey = PLUGIN_ID)
public class RequestHandlerBridge extends AbstractHttpHandlerProvider {
@Override
public BaseHttpHandler[] registerHandlers() {
return new BaseHttpHandler[]{
new ReportDataHandler()
};
}
}

35
src/main/java/com/fr/plugin/icgq/service/URLAliasBridge.java

@ -0,0 +1,35 @@
/*
* Copyright (C), 2015-2019
* FileName: URLAliasBridge
* Author: Louis
* Date: 2019/8/17 22:21
* Description: URLAliasBridge
* History:
* <author> <time> <version> <desc>
*/
package com.fr.plugin.icgq.service;
import com.fr.decision.fun.impl.AbstractURLAliasProvider;
import com.fr.decision.webservice.url.alias.URLAlias;
import com.fr.decision.webservice.url.alias.URLAliasFactory;
import com.fr.stable.fun.Authorize;
import static com.fr.plugin.icgq.LocaleFinder.PLUGIN_ID;
import static com.fr.plugin.icgq.service.ReportDataHandler.IAM_USERS;
/**
* Function Description<br>
* URLAliasBridge
*
* @author fr.open
* @since 1.0.0
*/
@Authorize(callSignKey = PLUGIN_ID)
public class URLAliasBridge extends AbstractURLAliasProvider {
@Override
public URLAlias[] registerAlias() {
return new URLAlias[]{
URLAliasFactory.createPluginAlias(IAM_USERS, IAM_USERS, true),
};
}
}

52
src/main/java/com/fr/plugin/icgq/ui/UsersTableDataPane.java

@ -0,0 +1,52 @@
/*
* Copyright (C), 2018-2021
* Project: starter
* FileName: WechatTableDataPane
* Author: Louis
* Date: 2021/12/8 19:19
*/
package com.fr.plugin.icgq.ui;
import com.fanruan.api.design.DesignKit;
import com.fr.plugin.icgq.data.UsersTableData;
import com.fr.script.Calculator;
import com.fr.stable.ParameterProvider;
import java.util.List;
/**
* <Function Description><br>
* <UsersTableDataPane>
*
* @author fr.open
* @since 1.0.0
*/
public class UsersTableDataPane extends WebBaseTableDataPane<UsersTableData> {
public UsersTableDataPane() {
super();
}
@Override
public void populateBean(UsersTableData data) {
if (data == null) {
return;
}
Calculator c = Calculator.createCalculator();
this.editorPane.populate(data.getParameters(c));
}
@Override
public UsersTableData updateBean() {
UsersTableData data = new UsersTableData();
List<ParameterProvider> parameterProviderList = this.editorPane.update();
ParameterProvider[] parameters = parameterProviderList.toArray(new ParameterProvider[0]);
data.setParameters(parameters);
return data;
}
@Override
protected String title4PopupWindow() {
return DesignKit.i18nText("Plugin-icgq_Query");
}
}

136
src/main/java/com/fr/plugin/icgq/ui/WebBaseTableDataPane.java

@ -0,0 +1,136 @@
/*
* Copyright (C), 2018-2021
* Project: starter
* FileName: WebBaseTableDatapane
* Author: Louis
* Date: 2021/12/10 8:49
*/
package com.fr.plugin.icgq.ui;
import com.fanruan.api.design.DesignKit;
import com.fanruan.api.design.ui.action.UpdateAction;
import com.fanruan.api.design.ui.component.UIToolbar;
import com.fanruan.api.design.ui.component.table.UITableEditorPane;
import com.fanruan.api.design.ui.component.table.action.UITableEditAction;
import com.fanruan.api.design.ui.component.table.model.ParameterTableModel;
import com.fanruan.api.design.ui.component.table.model.UITableModelAdapter;
import com.fanruan.api.design.ui.toolbar.ToolBarDef;
import com.fanruan.api.design.work.BaseTableDataPane;
import com.fanruan.api.log.LogKit;
import com.fanruan.api.util.IOKit;
import com.fr.base.TableData;
import com.fr.stable.ParameterProvider;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.net.URI;
/**
* <Function Description><br>
* <WebBaseTableDatapane>
*
* @author fr.open
* @since 1.0.0
*/
public abstract class WebBaseTableDataPane<T extends TableData> extends BaseTableDataPane<T> {
public static final String ICON_HELP = "/com/fr/plugin/icgq/images/help.png";
public static final String ICON_PREVIEW = "/com/fr/design/images/m_file/preview.png";
public static final String ICON_REFRESH = "/com/fr/design/images/control/refresh.png";
public static final String HELP_URL = "https://help.finereport.com/index.php";
private static final String PREVIEW_BUTTON = DesignKit.i18nText("Plugin-icgq_Preview");
private static final String REFRESH_BUTTON = DesignKit.i18nText("Plugin-icgq_Refresh");
private static final String HELP_BUTTON = DesignKit.i18nText("Plugin-icgq_Help");
protected UITableEditorPane<ParameterProvider> editorPane;
public WebBaseTableDataPane() {
this.setLayout(new BorderLayout(4, 4));
Box box = new Box(BoxLayout.Y_AXIS);
JPanel northPane = new JPanel(new BorderLayout(4, 4));
JToolBar editToolBar = createToolBar();
northPane.add(editToolBar, BorderLayout.CENTER);
JToolBar editHelpBar = createHelpBar();
northPane.add(editHelpBar, BorderLayout.EAST);
northPane.setBorder(BorderFactory.createEmptyBorder(0, 0, 6, 0));
UITableModelAdapter<ParameterProvider> model = new ParameterTableModel();
editorPane = new UITableEditorPane<ParameterProvider>(model);
box.add(northPane);
box.add(editorPane);
JPanel sqlSplitPane = new JPanel(new BorderLayout(4, 4));
sqlSplitPane.add(box, BorderLayout.CENTER);
this.add(sqlSplitPane, BorderLayout.CENTER);
}
private JToolBar createToolBar() {
ToolBarDef toolBarDef = new ToolBarDef();
toolBarDef.addShortCut(new PreviewAction());
UIToolbar editToolBar = ToolBarDef.createJToolBar();
toolBarDef.updateToolBar(editToolBar);
return editToolBar;
}
private JToolBar createHelpBar() {
ToolBarDef helpBarDef = new ToolBarDef();
helpBarDef.addShortCut(new HelpAction());
UIToolbar editHelpBar = ToolBarDef.createJToolBar();
helpBarDef.updateToolBar(editHelpBar);
return editHelpBar;
}
@Override
protected String title4PopupWindow() {
return DesignKit.i18nText("Plugin-icgq_Query");
}
private void refresh() {
java.util.List<ParameterProvider> existParameterList = editorPane.update();
ParameterProvider[] ps = existParameterList == null ? new ParameterProvider[0] : existParameterList.toArray(new ParameterProvider[0]);
editorPane.populate(ps);
}
private class PreviewAction extends UpdateAction {
public PreviewAction() {
this.setName(PREVIEW_BUTTON);
this.setMnemonic('P');
this.setSmallIcon(IOKit.readIcon(ICON_PREVIEW));
}
public void actionPerformed(ActionEvent evt) {
DesignKit.previewTableData(WebBaseTableDataPane.this.updateBean());
}
}
private class HelpAction extends UpdateAction {
public HelpAction() {
this.setName(HELP_BUTTON);
this.setMnemonic('P');
this.setSmallIcon(IOKit.readIcon(ICON_HELP));
}
public void actionPerformed(ActionEvent evt) {
try {
Desktop.getDesktop().browse(URI.create(HELP_URL));
} catch (IOException e1) {
LogKit.error(e1.getMessage(), e1);
}
}
}
protected class RefreshAction extends UITableEditAction {
public RefreshAction() {
this.setName(REFRESH_BUTTON);
this.setSmallIcon(IOKit.readIcon(ICON_REFRESH));
}
public void actionPerformed(ActionEvent e) {
refresh();
}
@Override
public void checkEnabled() {
}
}
}

60
src/main/java/com/fr/plugin/icgq/ui/WebQueryPane.java

@ -0,0 +1,60 @@
/*
* Copyright (C), 2018-2021
* Project: starter
* FileName: WechatQueryPane
* Author: Louis
* Date: 2021/12/14 15:32
*/
package com.fr.plugin.icgq.ui;
import com.fanruan.api.design.DesignKit;
import com.fanruan.api.design.ui.component.UIButtonGroup;
import com.fanruan.api.design.ui.component.UILabel;
import com.fanruan.api.design.ui.container.BasicPane;
import com.fanruan.api.design.ui.layout.TableLayoutKit;
import java.awt.*;
/**
* <Function Description><br>
* <WebQueryPane>
*
* @author fr.open
* @since 1.0.0
*/
public class WebQueryPane extends BasicPane {
public static final String[] SEARCH_OPTIONS = new String[]{
DesignKit.i18nText("Plugin-icgq_Search_User"),
DesignKit.i18nText("Plugin-icgq_Search_Department")
};
private UIButtonGroup searchType;
public WebQueryPane() {
setLayout(new BorderLayout());
searchType = new UIButtonGroup<String[]>(SEARCH_OPTIONS);
searchType.setSelectedIndex(0);
Component[][] coms = new Component[][]{
{new UILabel(DesignKit.i18nText("Plugin-icgq_Search_Type") + ":"), searchType}
};
double p = TableLayoutKit.PREFERRED;
double f = TableLayoutKit.FILL;
double[] rowSize = {p};
double[] columnSize = {p, f};
add(TableLayoutKit.createTableLayoutPane(coms, rowSize, columnSize));
}
@Override
protected String title4PopupWindow() {
return DesignKit.i18nText("Plugin-icgq_Query");
}
public int getSearchType() {
return searchType.getSelectedIndex();
}
public void setSearchType(int searchType) {
this.searchType.setSelectedIndex(searchType);
}
}

BIN
src/main/resources/com/fr/plugin/icgq/images/help.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 817 B

BIN
src/main/resources/com/fr/plugin/icgq/images/logo16.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

15
src/main/resources/com/fr/plugin/icgq/locale/lang.properties

@ -0,0 +1,15 @@
Plugin-icgq=Sso Plugin
Plugin-icgq_Group=Sso Plugin
Plugin-icgq_Config_ClientId=Client Id
Plugin-icgq_Config_ClientId_Description=Client Id
Plugin-icgq_Config_ClientSecret=Client Secret
Plugin-icgq_Config_ClientSecret_Description=Client Secret
Plugin-icgq_Licence_Expired=Sso Plugin Licence Expired
Plugin-icgq_Users_Table_Data=Users Table Data
Plugin-icgq_Query=Query
Plugin-icgq_Help=Help
Plugin-icgq_Preview=Preview
Plugin-icgq_Refresh=Refresh
Plugin-icgq_Search_Type=Search Type
Plugin-icgq_Search_User=User
Plugin-icgq_Search_Department=Department

15
src/main/resources/com/fr/plugin/icgq/locale/lang_zh_CN.properties

@ -0,0 +1,15 @@
Plugin-icgq=\u7528\u6237\u540C\u6B65\u63D2\u4EF6
Plugin-icgq_Group=\u7528\u6237\u540C\u6B65\u63D2\u4EF6
Plugin-icgq_Config_ClientId=\u5E94\u7528ID
Plugin-icgq_Config_ClientId_Description=\u5E94\u7528ID
Plugin-icgq_Config_ClientSecret=\u5E94\u7528\u79D8\u94A5
Plugin-icgq_Config_ClientSecret_Description=\u5E94\u7528\u79D8\u94A5
Plugin-icgq_Licence_Expired=\u7528\u6237\u540C\u6B65\u63D2\u4EF6\u8BB8\u53EF\u8FC7\u671F
Plugin-icgq_Users_Table_Data=\u7528\u6237\u6570\u636E\u96C6
Plugin-icgq_Query=\u67E5\u8BE2
Plugin-icgq_Help=\u5E2E\u52A9\u6587\u6863
Plugin-icgq_Preview=\u9884\u89C8
Plugin-icgq_Refresh=\u5237\u65B0
Plugin-icgq_Search_Type=\u64CD\u4F5C\u79CD\u7C7B
Plugin-icgq_Search_User=\u7528\u6237
Plugin-icgq_Search_Department=\u90E8\u95E8
Loading…
Cancel
Save