diff --git a/JSD-8329-需求确认书.docx b/JSD-8329-需求确认书.docx new file mode 100644 index 0000000..be55394 Binary files /dev/null and b/JSD-8329-需求确认书.docx differ diff --git a/README.md b/README.md index 1368f52..ba43c29 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ # open-JSD-8329 -JSD-8329 开源任务材料 \ No newline at end of file +JSD-8329 开源任务材料\ +免责说明:该源码为第三方爱好者提供,不保证源码和方案的可靠性,也不提供任何形式的源码教学指导和协助!\ +仅作为开发者学习参考使用!禁止用于任何商业用途!\ +为保护开发者隐私,开发者信息已隐去!若原开发者希望公开自己的信息,可联系hugh处理。 \ No newline at end of file diff --git a/jsd-6890-配置使用文档.docx b/jsd-6890-配置使用文档.docx new file mode 100644 index 0000000..1dfd13b Binary files /dev/null and b/jsd-6890-配置使用文档.docx differ diff --git a/lib/finekit-10.0.jar b/lib/finekit-10.0.jar new file mode 100644 index 0000000..f4482fc Binary files /dev/null and b/lib/finekit-10.0.jar differ diff --git a/lib/taobao-sdk-java-auto_1479188381469-20210421.jar b/lib/taobao-sdk-java-auto_1479188381469-20210421.jar new file mode 100644 index 0000000..2978668 Binary files /dev/null and b/lib/taobao-sdk-java-auto_1479188381469-20210421.jar differ diff --git a/plugin.xml b/plugin.xml new file mode 100644 index 0000000..bbd3850 --- /dev/null +++ b/plugin.xml @@ -0,0 +1,24 @@ + + + com.fr.plugin.dingtalksyn.job + + yes + 1.2.6 + 10.0 + 2018-07-31 + fr.open + + ]]> + com.fr.plugin.dingtalksyn + + com.fanruan.api + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/dingtalksyn/bean/DingTalkJobConstructor.java b/src/main/java/com/fr/plugin/dingtalksyn/bean/DingTalkJobConstructor.java new file mode 100644 index 0000000..0f27c7c --- /dev/null +++ b/src/main/java/com/fr/plugin/dingtalksyn/bean/DingTalkJobConstructor.java @@ -0,0 +1,107 @@ + /* + * Copyright (C), 2018-2021 + * Project: starter + * FileName: DingTalkJobConstructor + * Author: Louis + * Date: 2021/4/21 15:58 + */ + package com.fr.plugin.dingtalksyn.bean; + + import com.fr.scheduler.job.FineScheduleJob; + + /** + *
+ * + * + * @author fr.open + * @since 1.0.0 + */ + public class DingTalkJobConstructor { + private String cron; + private String jobName; + private String jobGroup; + private String triggerName; + private String triggerGroup; + private Class jobClazz; + + public DingTalkJobConstructor() { + } + + public DingTalkJobConstructor cron(String var1) { + this.setCron(var1); + return this; + } + + public String getCron() { + return this.cron; + } + + public void setCron(String var1) { + this.cron = var1; + } + + public DingTalkJobConstructor jobName(String var1) { + this.setJobName(var1); + return this; + } + + public String getJobName() { + return this.jobName; + } + + public void setJobName(String var1) { + this.jobName = var1; + } + + public DingTalkJobConstructor jobGroup(String var1) { + this.setJobGroup(var1); + return this; + } + + public String getJobGroup() { + return this.jobGroup; + } + + public void setJobGroup(String var1) { + this.jobGroup = var1; + } + + public DingTalkJobConstructor triggerName(String var1) { + this.setTriggerName(var1); + return this; + } + + public String getTriggerName() { + return this.triggerName; + } + + public void setTriggerName(String var1) { + this.triggerName = var1; + } + + public DingTalkJobConstructor triggerGroup(String var1) { + this.setTriggerGroup(var1); + return this; + } + + public String getTriggerGroup() { + return this.triggerGroup; + } + + public void setTriggerGroup(String var1) { + this.triggerGroup = var1; + } + + public DingTalkJobConstructor jobClazz(Class var1) { + this.setJobClazz(var1); + return this; + } + + public Class getJobClazz() { + return this.jobClazz; + } + + public void setJobClazz(Class var1) { + this.jobClazz = var1; + } + } \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/dingtalksyn/config/DingSynConfig.java b/src/main/java/com/fr/plugin/dingtalksyn/config/DingSynConfig.java new file mode 100644 index 0000000..36765e7 --- /dev/null +++ b/src/main/java/com/fr/plugin/dingtalksyn/config/DingSynConfig.java @@ -0,0 +1,83 @@ + /* + * Copyright (C), 2018-2021 + * Project: starter + * FileName: OneAccessConfig + * Author: Louis + * Date: 2021/3/30 9:38 + */ + package com.fr.plugin.dingtalksyn.config; + + import com.fr.config.*; +import com.fr.config.holder.Conf; +import com.fr.config.holder.factory.Holders; +import com.fr.intelli.record.Focus; +import com.fr.intelli.record.Original; + +import static com.fr.plugin.dingtalksyn.provider.LocaleFinder.PLUGIN_ID; + + /** + *
+ * + * + * @author fr.open + * @since 1.0.0 + */ + @Visualization(category = "Plugin-dingtalksyn_Group") + public class DingSynConfig extends DefaultConfiguration { + + // 每天中午十二点触发 + public static final String CRON_CONDITION = "0 0 12 * * ?"; + public static final String APP_KEY = "xxxx"; + public static final String APP_SECRET = "xxxx"; + public static final long ROOT_DEP_ID = 1; + private static volatile DingSynConfig config = null; + + @Focus(id = PLUGIN_ID, text = "Plugin-dingtalksyn", source = Original.PLUGIN) + public static DingSynConfig getInstance() { + if (config == null) { + config = ConfigContext.getConfigInstance(DingSynConfig.class); + } + return config; + } + + @Identifier(value = "cronCondition", name = "Plugin-dingtalksyn_Config_CronCondition", description = "Plugin-dingtalksyn_Config_CronCondition_Description", status = Status.SHOW) + private Conf cronCondition = Holders.simple(CRON_CONDITION); + @Identifier(value = "appKey", name = "Plugin-dingtalksyn_Config_appKey", description = "Plugin-dingtalksyn_Config_appKey_Description", status = Status.SHOW) + private Conf appKey = Holders.simple(APP_KEY); + @Identifier(value = "appSecret", name = "Plugin-dingtalksyn_Config_appSecret", description = "Plugin-dingtalksyn_Config_appSecret_Description", status = Status.SHOW) + private Conf appSecret = Holders.simple(APP_SECRET); + @Identifier(value = "rootDepId", name = "Plugin-dingtalksyn_Config_rootDepId", description = "Plugin-dingtalksyn_Config_rootDepId_Description", status = Status.SHOW) + private Conf rootDepId = Holders.simple(ROOT_DEP_ID); + + public String getCronCondition() { + return cronCondition.get(); + } + + public void setCronCondition(String cronCondition) { + this.cronCondition.set(cronCondition); + } + + public String getAppKey() { + return appKey.get(); + } + + public void setAppKey(String appKey) { + this.appKey.set(appKey); + } + + public String getAppSecret() { + return appSecret.get(); + } + + public void setAppSecret(String appSecret) { + this.appSecret.set(appSecret); + } + + public long getRootDepId() { + return rootDepId.get(); + } + + public void setRootDepId(long rootDepId) { + this.rootDepId.set(rootDepId); + } + } \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/dingtalksyn/helper/DingTalkScheduleHelper.java b/src/main/java/com/fr/plugin/dingtalksyn/helper/DingTalkScheduleHelper.java new file mode 100644 index 0000000..e4bba04 --- /dev/null +++ b/src/main/java/com/fr/plugin/dingtalksyn/helper/DingTalkScheduleHelper.java @@ -0,0 +1,101 @@ + /* + * Copyright (C), 2018-2021 + * Project: starter + * FileName: DingTalkScheduleHelper + * Author: Louis + * Date: 2021/4/21 15:52 + */ + package com.fr.plugin.dingtalksyn.helper; + + import com.fr.plugin.dingtalksyn.bean.DingTalkJobConstructor; + import com.fr.plugin.dingtalksyn.job.DingTalkSyncMemberJob; + import com.fr.scheduler.ScheduleJobManager; + import com.fr.third.v2.org.quartz.CronScheduleBuilder; + import com.fr.third.v2.org.quartz.TriggerBuilder; + + import java.util.ArrayList; + import java.util.HashMap; + import java.util.TimeZone; + + /** + *
+ * + * + * @author fr.open + * @since 1.0.0 + */ + public class DingTalkScheduleHelper { + private static final String SECOND = "0"; + private static final String CRON_EXPRESSION_EVERY_DAY = "%s %s %s ? * *"; + private static final String CRON_EXPRESSION_EVERY_WEEK = "%s %s %s ? * %s"; + public static final String DINGTALK_SCHEDULE_SYN_MEMBER_JOB_NAME = "MQH_DingTalkSynDepMemberJob"; + public static final String DINGTALK_SCHEDULE_SYN_MEMBER_TRIGGER_NAME = "MQH_DingTalkSynDepMemberTrigger"; + public static final String DINGTALK_SCHEDULE_SYN_MEMBER_GROUP = "MQH_DingTalkSynDepMemberGroup"; + public static final String DINGTALK_SCHEDULE_SYN_MEMBER_TRIGGER_GROUP = "MQH_DingTalkSynDepMemberTriggerGroup"; + + private DingTalkScheduleHelper() { + } + + public static DingTalkScheduleHelper getInstance() { + return DingTalkScheduleHelper.HOLDER.INSTANCE; + } + +// private String getSynMemberJobCron(DingTalkTimingTaskConfig var1) { +// int var2 = var1.getPeriod(); +// String var3 = var1.getStartHour(); +// String var4 = var1.getStartMinute(); +// Week var5 = Week.getWeek(var1.getStartWeekDay()); +// if (var2 == 0) { +// return String.format("%s %s %s ? * *", "0", var4, var3); +// } else { +// return var2 == 1 ? String.format("%s %s %s ? * %s", "0", var4, var3, var5) : ""; +// } +// } + +// public void startSynMemberSchedule(DingTalkTimingTaskConfig var1) throws Exception { +// if (var1.isEnableAutoSync()) { +// String var2 = this.getSynMemberJobCron(var1); +// DingTalkJobConstructor var3 = (new DingTalkJobConstructor()).cron(var2).jobName("DingTalkSynDepMemberJob").jobGroup("DingTalkSynDepMemberGroup").triggerName("DingTalkSynDepMemberTrigger").triggerGroup("DingTalkSynDepMemberTriggerGroup").jobClazz(DingTalkSyncMemberJob.class); +// this.startSchedule(var3); +// } +// +// } + + public void startSynMemberSchedule(String cronCondition) throws Exception { + DingTalkJobConstructor jobConstructor = (new DingTalkJobConstructor()) + .cron(cronCondition).jobName(DINGTALK_SCHEDULE_SYN_MEMBER_JOB_NAME) + .jobGroup(DINGTALK_SCHEDULE_SYN_MEMBER_GROUP).triggerName(DINGTALK_SCHEDULE_SYN_MEMBER_TRIGGER_NAME) + .triggerGroup(DINGTALK_SCHEDULE_SYN_MEMBER_TRIGGER_GROUP).jobClazz(DingTalkSyncMemberJob.class); + this.startSchedule(jobConstructor); + } + + public void startSchedule(DingTalkJobConstructor var1) throws Exception { + if (var1 != null) { + String var2 = var1.getCron(); + String var3 = var1.getTriggerName(); + String var4 = var1.getTriggerGroup(); + String var5 = var1.getJobName(); + String var6 = var1.getJobGroup(); + Class var7 = var1.getJobClazz(); + TriggerBuilder var8 = TriggerBuilder.newTrigger(); + var8.withIdentity(var3, var4); + var8.withSchedule(CronScheduleBuilder.cronSchedule(var2).withMisfireHandlingInstructionFireAndProceed().inTimeZone(TimeZone.getTimeZone(TimeZone.getDefault().getID()))).startNow(); + var8.forJob(var5, var6); + ArrayList var9 = new ArrayList(); + var9.add(var8.build()); + ScheduleJobManager.getInstance().removeJob(var5, var6); + ScheduleJobManager.getInstance().addJob(var5, var6, "jobDescription", var7, var9, new HashMap()); + } + } + + public void stopSchedule(String var1, String var2) { + ScheduleJobManager.getInstance().removeJob(var1, var2); + } + + public static class HOLDER { + private static final DingTalkScheduleHelper INSTANCE = new DingTalkScheduleHelper(); + + public HOLDER() { + } + } + } diff --git a/src/main/java/com/fr/plugin/dingtalksyn/job/DingTalkSyncMemberJob.java b/src/main/java/com/fr/plugin/dingtalksyn/job/DingTalkSyncMemberJob.java new file mode 100644 index 0000000..115f9da --- /dev/null +++ b/src/main/java/com/fr/plugin/dingtalksyn/job/DingTalkSyncMemberJob.java @@ -0,0 +1,34 @@ + /* + * Copyright (C), 2018-2021 + * Project: starter + * FileName: DingTalkSyncMemberJob + * Author: Louis + * Date: 2021/4/21 16:02 + */ + package com.fr.plugin.dingtalksyn.job; + + import com.fanruan.api.log.LogKit; + import com.fr.cluster.core.ClusterNode; + import com.fr.plugin.dingtalksyn.user.DingTalkUserManager; + import com.fr.scheduler.job.FineScheduleJob; + import com.fr.third.v2.org.quartz.JobExecutionContext; + + /** + *
+ * + * + * @author fr.open + * @since 1.0.0 + */ + public class DingTalkSyncMemberJob extends FineScheduleJob { + public DingTalkSyncMemberJob() { + } + + public void run(JobExecutionContext jobExecutionContext, ClusterNode clusterNode) { + try { + DingTalkUserManager.getInstance().synDingTalkUsers(); + } catch (Exception e) { + LogKit.error(e.getMessage(), e); + } + } + } \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/dingtalksyn/provider/LifeCycleMonitorImpl.java b/src/main/java/com/fr/plugin/dingtalksyn/provider/LifeCycleMonitorImpl.java new file mode 100644 index 0000000..4575f4a --- /dev/null +++ b/src/main/java/com/fr/plugin/dingtalksyn/provider/LifeCycleMonitorImpl.java @@ -0,0 +1,49 @@ + /* + * Copyright (C), 2018-2021 + * Project: starter + * FileName: OneAccessLifeCycleMonitor + * Author: Louis + * Date: 2021/3/30 15:10 + */ + package com.fr.plugin.dingtalksyn.provider; + + import com.fanruan.api.log.LogKit; + import com.fr.plugin.context.PluginContext; + import com.fr.plugin.dingtalksyn.helper.DingTalkScheduleHelper; + import com.fr.plugin.observer.inner.AbstractPluginLifecycleMonitor; + import com.fr.plugin.dingtalksyn.config.DingSynConfig; + + import static com.fr.plugin.dingtalksyn.helper.DingTalkScheduleHelper.DINGTALK_SCHEDULE_SYN_MEMBER_GROUP; + import static com.fr.plugin.dingtalksyn.helper.DingTalkScheduleHelper.DINGTALK_SCHEDULE_SYN_MEMBER_JOB_NAME; + + /** + *
+ * + * + * @author fr.open + * @since 1.0.0 + */ + public class LifeCycleMonitorImpl extends AbstractPluginLifecycleMonitor { + public LifeCycleMonitorImpl() { + } + + @Override + public void afterRun(PluginContext pluginContext) { + DingSynConfig.getInstance(); + this.reStartSchedule(); + } + + @Override + public void beforeStop(PluginContext pluginContext) { + DingTalkScheduleHelper.getInstance().stopSchedule(DINGTALK_SCHEDULE_SYN_MEMBER_JOB_NAME, DINGTALK_SCHEDULE_SYN_MEMBER_GROUP); + } + + private void reStartSchedule() { + try { + String cronCondition = DingSynConfig.getInstance().getCronCondition(); + DingTalkScheduleHelper.getInstance().startSynMemberSchedule(cronCondition); + } catch (Exception e) { + LogKit.error(e.getMessage(), e); + } + } + } \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/dingtalksyn/provider/LocaleFinder.java b/src/main/java/com/fr/plugin/dingtalksyn/provider/LocaleFinder.java new file mode 100644 index 0000000..83ed14f --- /dev/null +++ b/src/main/java/com/fr/plugin/dingtalksyn/provider/LocaleFinder.java @@ -0,0 +1,36 @@ + /* + * Copyright (C), 2018-2020 + * Project: starter + * FileName: LocaleFinder + * Author: Louis + * Date: 2020/8/31 22:19 + */ + package com.fr.plugin.dingtalksyn.provider; + + import com.fr.intelli.record.Focus; +import com.fr.intelli.record.Original; +import com.fr.record.analyzer.EnableMetrics; +import com.fr.stable.fun.impl.AbstractLocaleFinder; + + /** + *
+ * + * + * @author fr.open + * @since 1.0.0 + */ + @EnableMetrics + public class LocaleFinder extends AbstractLocaleFinder { + public static final String PLUGIN_ID = "com.fr.plugin.dingtalksyn.job"; + + @Override + @Focus(id = PLUGIN_ID, text = "Plugin-dingtalksyn", source = Original.PLUGIN) + public String find() { + return "com/fr/plugin/dingtalksyn/locale/lang"; + } + + @Override + public int currentAPILevel() { + return CURRENT_LEVEL; + } + } \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/dingtalksyn/request/DelegatingServletInputStream.java b/src/main/java/com/fr/plugin/dingtalksyn/request/DelegatingServletInputStream.java new file mode 100644 index 0000000..b0ccecc --- /dev/null +++ b/src/main/java/com/fr/plugin/dingtalksyn/request/DelegatingServletInputStream.java @@ -0,0 +1,57 @@ + /* + * Copyright (C), 2018-2021 + * Project: starter + * FileName: DelegatingServletInputStream + * Author: Louis + * Date: 2021/7/16 9:21 + */ + package com.fr.plugin.dingtalksyn.request; + + import javax.servlet.ReadListener; + import javax.servlet.ServletInputStream; + import java.io.IOException; + import java.io.InputStream; + + /** + *
+ * + * + * @author fr.open + * @since 1.0.0 + */ + public class DelegatingServletInputStream extends ServletInputStream { + private final InputStream sourceStream; + + public DelegatingServletInputStream(InputStream stream) { + this.sourceStream = stream; + } + + public final InputStream getSourceStream() { + return this.sourceStream; + } + + @Override + public int read() throws IOException { + return this.sourceStream.read(); + } + + @Override + public void close() throws IOException { + super.close(); + this.sourceStream.close(); + } + + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener listener) { + } + } \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/dingtalksyn/request/GlobalRequestFilterBridge.java b/src/main/java/com/fr/plugin/dingtalksyn/request/GlobalRequestFilterBridge.java new file mode 100644 index 0000000..71c5d8e --- /dev/null +++ b/src/main/java/com/fr/plugin/dingtalksyn/request/GlobalRequestFilterBridge.java @@ -0,0 +1,78 @@ + /* + * Copyright (C), 2018-2021 + * Project: starter + * FileName: GlobalRequestFilterBridge + * Author: Louis + * Date: 2021/7/15 15:40 + */ + package com.fr.plugin.dingtalksyn.request; + + import com.fanruan.api.log.LogKit; +import com.fanruan.api.util.StringKit; +import com.fr.decision.fun.impl.AbstractGlobalRequestFilterProvider; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + /** + *
+ * + * + * @author fr.open + * @since 1.0.0 + */ + public class GlobalRequestFilterBridge extends AbstractGlobalRequestFilterProvider { + public static final String LOGIN_PATH = "/login"; + + /** + * 过滤器名称 + * + * @return + */ + @Override + public String filterName() { + return "DingTalkSynFilter"; + } + + /** + * 过滤规则 + * + * @return + */ + @Override + public String[] urlPatterns() { + return new String[]{"/decision/*"}; + } + + /** + * 过滤器初始化 + * + * @param filterConfig + */ + @Override + public void init(FilterConfig filterConfig) { + super.init(filterConfig); + } + + /** + * 过滤器处理 + * + * @param request + * @param response + * @param filterChain + */ + @Override + public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) { + String pathInfo = (request.getPathInfo() != null) ? request.getPathInfo() : StringKit.EMPTY; + if (StringKit.equals(LOGIN_PATH, pathInfo) && StringKit.equalsIgnoreCase(request.getMethod(), "POST")) { + request = new ModifyBodyRequestWrapper(request); + } + try { + filterChain.doFilter(request, response); + } catch (Exception e) { + LogKit.error(e.getMessage(), e); + } + } + } \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/dingtalksyn/request/ModifyBodyRequestWrapper.java b/src/main/java/com/fr/plugin/dingtalksyn/request/ModifyBodyRequestWrapper.java new file mode 100644 index 0000000..1851654 --- /dev/null +++ b/src/main/java/com/fr/plugin/dingtalksyn/request/ModifyBodyRequestWrapper.java @@ -0,0 +1,81 @@ + /* + * Copyright (C), 2018-2021 + * Project: starter + * FileName: UserNameTransferRequest + * Author: Louis + * Date: 2021/7/16 9:20 + */ + package com.fr.plugin.dingtalksyn.request; + + import com.fanruan.api.decision.user.UserKit; +import com.fanruan.api.util.StringKit; +import com.fr.decision.webservice.v10.user.UserService; +import com.fr.general.IOUtils; +import com.fr.json.JSONObject; + +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.InputStreamReader; +import java.util.List; + + /** + *
+ * + * + * @author fr.open + * @since 1.0.0 + */ + public class ModifyBodyRequestWrapper extends HttpServletRequestWrapper { + private byte[] data; + private String bodyContent; + + public ModifyBodyRequestWrapper(HttpServletRequest request) { + super(request); + this.bodyContent = modifyRequestBody(); + } + + /** + * 如登陆输入手机号,则替换为用户名 + * + * @return + */ + private String modifyRequestBody() { + try { + String orgContent = IOUtils.inputStream2String(this.getRequest().getInputStream()); + JSONObject bodyJo = new JSONObject(orgContent); + if (!bodyJo.has("username")) { + return orgContent; + } + String username = bodyJo.getString("username"); + if (StringKit.isBlank(username)) { + return orgContent; + } + if (!UserKit.existUsername(username)) { + List usernameList = UserService.getInstance().getUserNamesFromMobile(username); + if (!usernameList.isEmpty() && StringKit.isNotBlank(usernameList.get(0))) { + bodyJo.put("username", usernameList.get(0)); + } + } + return bodyJo.encode(); + } catch (Exception e) { + return StringKit.EMPTY; + } + } + + @Override + public ServletInputStream getInputStream() { + try { + return new DelegatingServletInputStream(new ByteArrayInputStream(this.bodyContent.getBytes())); + } catch (Exception e) { + return null; + } + } + + @Override + public BufferedReader getReader() { + return new BufferedReader(new InputStreamReader(new ByteArrayInputStream(this.bodyContent.getBytes()))); + } + } \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/dingtalksyn/user/DingTalkUserManager.java b/src/main/java/com/fr/plugin/dingtalksyn/user/DingTalkUserManager.java new file mode 100644 index 0000000..59cff43 --- /dev/null +++ b/src/main/java/com/fr/plugin/dingtalksyn/user/DingTalkUserManager.java @@ -0,0 +1,468 @@ + /* + * Copyright (C), 2018-2021 + * Project: starter + * FileName: DingTalkUserManager + * Author: Louis + * Date: 2021/4/21 16:18 + */ + package com.fr.plugin.dingtalksyn.user; + + import com.dingtalk.api.DefaultDingTalkClient; + import com.dingtalk.api.DingTalkClient; + import com.dingtalk.api.request.*; + import com.dingtalk.api.response.*; + import com.fanruan.api.decision.user.UserKit; + import com.fanruan.api.i18n.I18nKit; + import com.fanruan.api.log.LogKit; + import com.fanruan.api.util.StringKit; + import com.fr.decision.authority.AuthorityContext; + import com.fr.decision.authority.base.constant.SoftRoleType; + import com.fr.decision.authority.base.constant.type.operation.ManualOperationType; + import com.fr.decision.authority.data.*; + import com.fr.decision.authority.data.personnel.DepRole; + import com.fr.decision.privilege.TransmissionTool; + import com.fr.decision.record.OperateMessage; + import com.fr.decision.webservice.bean.user.DepartmentPostBean; + import com.fr.decision.webservice.bean.user.RoleBean; + import com.fr.decision.webservice.bean.user.UserBean; + import com.fr.decision.webservice.exception.general.DuplicatedNameException; + import com.fr.decision.webservice.utils.ControllerFactory; + import com.fr.decision.webservice.utils.UserSourceFactory; + import com.fr.decision.webservice.utils.WebServiceUtils; + import com.fr.decision.webservice.v10.user.CustomRoleService; + import com.fr.decision.webservice.v10.user.DepartmentService; + import com.fr.decision.webservice.v10.user.PositionService; + import com.fr.decision.webservice.v10.user.UserService; + import com.fr.general.ComparatorUtils; + import com.fr.intelli.record.MetricRegistry; + import com.fr.plugin.dingtalksyn.config.DingSynConfig; + import com.fr.stable.StableUtils; + import com.fr.stable.StringUtils; + 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 com.taobao.api.ApiException; + + import java.util.ArrayList; + import java.util.Collections; + import java.util.List; + + /** + *
+ * + * + * @author fr.open + * @since 1.0.0 + */ + public final class DingTalkUserManager { + public static final String DECISION_DEP_ROOT = "decision-dep-root"; + + public static final String GETTOKEN = "https://oapi.dingtalk.com/gettoken"; + public static final String DEPARTMENT_LIST = "https://oapi.dingtalk.com/topapi/v2/department/listsub"; + public static final String GET_DEPT_MEMBER = "https://oapi.dingtalk.com/topapi/user/listid"; + public static final String USER_GET = "https://oapi.dingtalk.com/topapi/v2/user/get"; + public static final String ROLE_LIST = "https://oapi.dingtalk.com/topapi/role/list"; + + private DingSynConfig config; + private String accessToken; + + public DingTalkUserManager() { + this.config = DingSynConfig.getInstance(); + } + + private static class HOLDER { + private static final DingTalkUserManager INSTANCE = new DingTalkUserManager(); + } + public static DingTalkUserManager getInstance() { + return HOLDER.INSTANCE; + } + + /** + * 同步更新的字段包括:用户、邮箱、手机、部门、职位、角色。 + * 用户唯一字段用户名,更新时也是基于用户名。 + * + * @throws Exception + */ + public synchronized void synDingTalkUsers() throws Exception { + LogKit.error("dingtalksyn-DingTalkUserManager-synDingTalkUsers-start"); + this.accessToken = getToken(); + // 同步用户角色 + List oapiRoleGroupList = getOapiRoleList(); + for (OapiRoleListResponse.OpenRoleGroup openRoleGroup : oapiRoleGroupList) { + List openRoleList = openRoleGroup.getRoles(); + for (OapiRoleListResponse.OpenRole openRole : openRoleList) { + roleSynOperation(openRole); + } + } + // 同步部门和用户信息 + departmentSynLoop(this.config.getRootDepId()); + LogKit.error("dingtalksyn-DingTalkUserManager-synDingTalkUsers-end"); + } + + /** + * 按部门遍历子部门并同步人员信息 + * + * @param deptId + * @throws Exception + */ + private void departmentSynLoop(long deptId) throws ApiException { + List departmentList = getDepartmentList(deptId); + if (departmentList == null || departmentList.isEmpty()) { + return; + } + // 同步部门信息 + for (OapiV2DepartmentListsubResponse.DeptBaseResponse oapiDepartment : departmentList) { + try { + departmentSynOperation(oapiDepartment); + } catch (Exception e) { + LogKit.error(e.getMessage(), e); + } + // 同步当前部门的用户信息 + List deptMemberList = getDeptMember(oapiDepartment.getDeptId()); + for (String userId : deptMemberList) { + try { + OapiV2UserGetResponse.UserGetResponse userGetResponse = getUserResponse(userId); + if(userGetResponse == null) { continue; } + userSynOperation(userGetResponse, oapiDepartment.getDeptId()); + } catch (Exception e) { + LogKit.error(e.getMessage(), e); + } + } + // 操作子部门遍历 + departmentSynLoop(oapiDepartment.getDeptId()); + } + } + + /** + * 角色的新增更新操作 + * + * @param openRole + * @throws Exception + */ + private void roleSynOperation(OapiRoleListResponse.OpenRole openRole) throws Exception { + RoleBean roleBean = CustomRoleService.getInstance().getCustomRole(String.valueOf(openRole.getId())); + if (roleBean == null) { + roleBean = new RoleBean(openRole.getName(), String.valueOf(openRole.getId()), openRole.getName(), ManualOperationType.KEY.toInteger()); + addCustomRole(getAdminUserId(), roleBean); + } else if(!StringKit.equals(roleBean.getText(), openRole.getName())) { + roleBean.setText(openRole.getName()); + roleBean.setDescription(openRole.getName()); + CustomRoleService.getInstance().editCustomRole(roleBean.getId(), roleBean); + } + } + + private void addCustomRole(String username, RoleBean roleBean) throws Exception { + this.checkDuplicatedCustomRole(roleBean.getText()); + CustomRole customRole = (new CustomRole()).id(roleBean.getId()).name(roleBean.getText()).description(roleBean.getDescription()).creationType(ManualOperationType.KEY).lastOperationType(ManualOperationType.KEY).enable(true); + ControllerFactory.getInstance().getCustomRoleController(username).addCustomRole(username, customRole); + this.deleteSoftData(customRole.getName()); + MetricRegistry.getMetric().submit(OperateMessage.build("Dec-Module-User_Manager", "Dec-Role", roleBean.getText(), "Dec-Log_Add")); + } + + private void checkDuplicatedCustomRole(String roleName) throws Exception { + QueryCondition queryCondition = QueryFactory.create().addRestriction(RestrictionFactory.eq("name", roleName)); + CustomRole customRole = (CustomRole) AuthorityContext.getInstance().getCustomRoleController().findOne(queryCondition); + if (customRole != null) { + throw new DuplicatedNameException(); + } + } + + private void deleteSoftData(String roleName) throws Exception { + QueryCondition queryCondition = QueryFactory.create().addRestriction(RestrictionFactory.and(new Restriction[]{RestrictionFactory.eq("deletedName", roleName), RestrictionFactory.eq("type", SoftRoleType.CUSTOM)})); + AuthorityContext.getInstance().getSoftDataController().remove(queryCondition); + } + + /** + * 部门组织的新增更新操作 + * + * @param oapiDepartment + * @throws Exception + */ + private void departmentSynOperation(OapiV2DepartmentListsubResponse.DeptBaseResponse oapiDepartment) throws Exception { + String departmentId = String.valueOf(oapiDepartment.getDeptId()); + String parentId = String.valueOf(oapiDepartment.getParentId()); + if (StringKit.equals(parentId, String.valueOf(this.config.getRootDepId()))) { parentId = DECISION_DEP_ROOT; } + String depName = oapiDepartment.getName(); + Department department = AuthorityContext.getInstance().getDepartmentController().getById(departmentId); + if (department == null) { + this.addDepartment(departmentId, parentId, depName); + } else { + this.editDepartment(department.getId(), depName, parentId); + } + } + + private 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 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); + } + + private 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)); + } + + /** + * 用户新增更新操作 + * + * @param userGetResponse + * @throws Exception + */ + private void userSynOperation(OapiV2UserGetResponse.UserGetResponse userGetResponse, Long currentDepId) throws Exception { + UserBean userBean; + if (UserKit.existUsername(userGetResponse.getUserid())) { + userBean = updateUserBean(userGetResponse, currentDepId); + UserService.getInstance().editUser(userBean); + addUserDepartment(userBean); + } else { + userBean = createUserBean(userGetResponse, currentDepId); + try { + UserService.getInstance().addUser(userBean); + } catch (Exception e) { + LogKit.error("dingtalksyn-DingTalkUserManager-userSynOperation-Username:{}, RealName:{}, Mobile:{}, Email:{}", + userBean.getUsername(), userBean.getRealName(), userBean.getMobile(), userBean.getEmail()); + LogKit.error(e.getMessage(), e); + } + } + } + + /** + * 增加用户部门关联 + * + * @param userBean + * @throws Exception + */ + private void addUserDepartment(UserBean userBean) throws Exception { + if (StringUtils.isNotEmpty(userBean.getDepartmentPostId())) { + User user = UserService.getInstance().getUserByUserName(userBean.getUsername()); + DepRole depRole = WebServiceUtils.parseUniqueDepartmentPostId(userBean.getDepartmentPostId()); + UserSourceFactory.getInstance().checkSource(user, AuthorityContext.getInstance().getDepartmentController().getById(depRole.getDepartmentId())); + AuthorityContext.getInstance().getUserController().addUserToDepartmentAndPost(user.getId(), depRole.getDepartmentId(), depRole.getPostId()); + } + } + + private UserBean updateUserBean(OapiV2UserGetResponse.UserGetResponse userGetResponse, Long currentDepId) throws Exception { + User user = UserService.getInstance().getUserByUserName(userGetResponse.getUserid()); + UserBean userBean = new UserBean(); + userBean.setId(user.getId()); + userBean.setUsername(user.getUserName()); + userBean.setRealName(userGetResponse.getName()); + userBean.setEmail(userGetResponse.getEmail()); + userBean.setMobile(userGetResponse.getMobile()); + if (userGetResponse.getRoleList() != null && !userGetResponse.getRoleList().isEmpty()) { + userBean.setRoleIds(oapiUserRoles2Ids(userGetResponse.getRoleList())); + } + if (currentDepId == null) { + return userBean; + } + String departmentPostId = String.valueOf(currentDepId); + if (StringKit.isBlank(departmentPostId) || StringKit.equals(departmentPostId, "null")) { + return userBean; + } + // 职务处理 + String positionId = positionSynOperation(userGetResponse, departmentPostId); + if (StringKit.isNotBlank(positionId)) { + departmentPostId = departmentPostId + "@@@" + positionId; + } + // 旧版本jar 20210119 + userBean.setDepartmentPostId(departmentPostId); + // 新版jar +// List departmentPostIds = userGetResponse.getDepartment().stream().map(String::valueOf).collect(Collectors.toList()); +// userBean.setDepartmentPostIds(departmentPostIds); + return userBean; + } + + private String[] oapiUserRoles2Ids(List oapiUserRoleList) { + String[] roleIds = new String[oapiUserRoleList.size()]; + for (int i = 0; i < oapiUserRoleList.size(); i++) { + roleIds[i] = String.valueOf(oapiUserRoleList.get(i).getId()); + } + return roleIds; + } + + private UserBean createUserBean(OapiV2UserGetResponse.UserGetResponse userGetResponse, Long currentDepId) throws Exception { + UserBean userBean = new UserBean(); + userBean.setUsername(userGetResponse.getUserid()); + userBean.setRealName(userGetResponse.getName()); + userBean.setEmail(userGetResponse.getEmail()); + userBean.setMobile(userGetResponse.getMobile()); + userBean.setPassword(TransmissionTool.defaultEncrypt("123456")); + if (userGetResponse.getRoleList() != null && !userGetResponse.getRoleList().isEmpty()) { + userBean.setRoleIds(oapiUserRoles2Ids(userGetResponse.getRoleList())); + } + if (currentDepId == null) { + return userBean; + } + String departmentPostId = String.valueOf(currentDepId); + if (StringKit.isBlank(departmentPostId) || StringKit.equals(departmentPostId, "null")) { + return userBean; + } + // 职务处理 + String positionId = positionSynOperation(userGetResponse, departmentPostId); + if (StringKit.isNotBlank(positionId)) { + departmentPostId = departmentPostId + "@@@" + positionId; + } + // 旧版本jar 20210119 + userBean.setDepartmentPostId(departmentPostId); + // 新版本jar +// List departmentPostIds = userGetResponse.getDepartment().stream().map(String::valueOf).collect(Collectors.toList()); +// userBean.setDepartmentPostIds(departmentPostIds); + return userBean; + } + + /** + * 职务同步操作 + * + * @param userGetResponse + * @param departmentId + * @return + * @throws Exception + */ + private String positionSynOperation(OapiV2UserGetResponse.UserGetResponse userGetResponse, String departmentId) throws Exception { + String position = StringKit.isNotBlank(userGetResponse.getTitle()) ? userGetResponse.getTitle() : "职员"; + 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 departmentPostBeanList = PositionService.getInstance().getPositionsUnderParentDepartment(getAdminUserId(), departmentId, position); + if (departmentPostBeanList == null || departmentPostBeanList.isEmpty()) { + try { + AuthorityContext.getInstance().getPostController().addPostToDepartment(positionId, departmentId); + } catch (Exception e) { + LogKit.error("dingtalksyn-DingTalkUserManager-positionSynOperation-addPostToDepartmentFailed-position:{}, departmentId:{}, userID:{}, userName:{}", + positionId + position, departmentId, userGetResponse.getUserid(), userGetResponse.getName()); + LogKit.error(e.getMessage(), e); + } + } + return positionId; + } + + /** + * 获取管理员id + * + * @return + * @throws Exception + */ + private String getAdminUserId() throws Exception { + List adminUserIdList = UserService.getInstance().getAdminUserIdList(); + if (adminUserIdList.isEmpty()) { + return "admin"; + } + return StringKit.isNotBlank(adminUserIdList.get(0)) ? adminUserIdList.get(0) : "admin"; + } + + /** + * 根据userid获取用户详情 + * + * @param userId + * @return + * @throws ApiException + */ + private OapiV2UserGetResponse.UserGetResponse getUserResponse(String userId) throws ApiException { + DingTalkClient client = new DefaultDingTalkClient(USER_GET); + OapiV2UserGetRequest req = new OapiV2UserGetRequest(); + req.setUserid(userId); + req.setLanguage("zh_CN"); + OapiV2UserGetResponse rsp = client.execute(req, this.accessToken); + return rsp.getResult(); + } + + /** + * 获取部门用户userid列表 + * + * @param deptId + * @return + * @throws ApiException + */ + private List getDeptMember(long deptId) throws ApiException { + DingTalkClient client = new DefaultDingTalkClient(GET_DEPT_MEMBER); + OapiUserListidRequest req = new OapiUserListidRequest(); + req.setDeptId(deptId); + OapiUserListidResponse rsp = client.execute(req, this.accessToken); + return rsp.getResult().getUseridList(); + } + + /** + * 获取部门列表 + * + * @return + * @throws ApiException + */ + private List getDepartmentList(long deptId) throws ApiException { + DingTalkClient client = new DefaultDingTalkClient(DEPARTMENT_LIST); + OapiV2DepartmentListsubRequest req = new OapiV2DepartmentListsubRequest(); + req.setDeptId(deptId); + req.setLanguage("zh_CN"); + OapiV2DepartmentListsubResponse rsp = client.execute(req, this.accessToken); + return rsp.getResult(); + } + + /** + * 获取角色列表 + * + * @return + * @throws ApiException + */ + private List getOapiRoleList() throws ApiException { + DingTalkClient client = new DefaultDingTalkClient(ROLE_LIST); + OapiRoleListRequest req = new OapiRoleListRequest(); + req.setSize(200L); + req.setOffset(0L); + OapiRoleListResponse rsp = client.execute(req, this.accessToken); + return rsp.getResult().getList(); + } + + /** + * 获取企业内部应用的access_token + * + * @return + * @throws ApiException + */ + private String getToken() throws ApiException { + DingTalkClient client = new DefaultDingTalkClient(GETTOKEN); + OapiGettokenRequest request = new OapiGettokenRequest(); + request.setAppkey(this.config.getAppKey()); + request.setAppsecret(this.config.getAppSecret()); + request.setHttpMethod("GET"); + OapiGettokenResponse response = client.execute(request); + return response.getAccessToken(); + } + } \ No newline at end of file diff --git a/src/main/resources/com/fr/plugin/dingtalksyn/locale/lang.properties b/src/main/resources/com/fr/plugin/dingtalksyn/locale/lang.properties new file mode 100644 index 0000000..ac5b0a2 --- /dev/null +++ b/src/main/resources/com/fr/plugin/dingtalksyn/locale/lang.properties @@ -0,0 +1,10 @@ +Plugin-dingtalksyn=DingTalk syn Plugin +Plugin-dingtalksyn_Group=DingTalk syn Plugin +Plugin-dingtalksyn_Config_CronCondition=Cron Condition +Plugin-dingtalksyn_Config_CronCondition_Description=Cron Condition +Plugin-dingtalksyn_Config_appKey=APPKEY +Plugin-dingtalksyn_Config_appKey_Description=APPKEY +Plugin-dingtalksyn_Config_appSecret=appSecret +Plugin-dingtalksyn_Config_appSecret_Description=appSecret +Plugin-dingtalksyn_Config_rootDepId=Root Department ID +Plugin-dingtalksyn_Config_rootDepId_Description=Root Department ID \ No newline at end of file diff --git a/src/main/resources/com/fr/plugin/dingtalksyn/locale/lang_zh_CN.properties b/src/main/resources/com/fr/plugin/dingtalksyn/locale/lang_zh_CN.properties new file mode 100644 index 0000000..22f4d98 --- /dev/null +++ b/src/main/resources/com/fr/plugin/dingtalksyn/locale/lang_zh_CN.properties @@ -0,0 +1,10 @@ +Plugin-dingtalksyn=\u9489\u9489\u7528\u6237\u540C\u6B65\u63D2\u4EF6 +Plugin-dingtalksyn_Group=\u9489\u9489\u7528\u6237\u540C\u6B65\u63D2\u4EF6 +Plugin-dingtalksyn_Config_CronCondition=Cron\u8868\u8FBE\u5F0F +Plugin-dingtalksyn_Config_CronCondition_Description=Cron\u8868\u8FBE\u5F0F +Plugin-dingtalksyn_Config_appKey=\u5E94\u7528\u7CFB\u7EDF\u7684APPKEY +Plugin-dingtalksyn_Config_appKey_Description=\u5E94\u7528\u7CFB\u7EDF\u7684APPKEY +Plugin-dingtalksyn_Config_appSecret=\u5E94\u7528\u7CFB\u7EDF\u7684appSecret +Plugin-dingtalksyn_Config_appSecret_Description=\u5E94\u7528\u7CFB\u7EDF\u7684appSecret +Plugin-dingtalksyn_Config_rootDepId=\u6839\u90E8\u95E8ID +Plugin-dingtalksyn_Config_rootDepId_Description=\u6839\u90E8\u95E8ID \ No newline at end of file