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 extends FineScheduleJob> 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 extends FineScheduleJob> var1) {
+ this.setJobClazz(var1);
+ return this;
+ }
+
+ public Class extends FineScheduleJob> getJobClazz() {
+ return this.jobClazz;
+ }
+
+ public void setJobClazz(Class extends FineScheduleJob> 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