فهرست منبع

钉钉对接程序

yingp 5 سال پیش
والد
کامیت
d55cb16ffe
100فایلهای تغییر یافته به همراه5242 افزوده شده و 6 حذف شده
  1. 2 1
      README.md
  2. 1 0
      build.gradle
  3. 1 1
      dingtalk-sdk/build.gradle
  4. 326 0
      dingtalk-sdk/src/main/java/com/usoftchina/dingtalk/sdk/AddrBookSdk.java
  5. 97 0
      dingtalk-sdk/src/main/java/com/usoftchina/dingtalk/sdk/BaseSdk.java
  6. 40 0
      dingtalk-sdk/src/main/java/com/usoftchina/dingtalk/sdk/MessageSdk.java
  7. 196 0
      dingtalk-sdk/src/main/java/com/usoftchina/dingtalk/sdk/OaSdk.java
  8. 35 0
      dingtalk-sdk/src/main/java/com/usoftchina/dingtalk/sdk/ScheduleSdk.java
  9. 87 0
      dingtalk-sdk/src/main/java/com/usoftchina/dingtalk/sdk/WorkRecordSdk.java
  10. 43 0
      dingtalk-sdk/src/main/java/com/usoftchina/dingtalk/sdk/config/Agent.java
  11. 40 0
      dingtalk-sdk/src/main/java/com/usoftchina/dingtalk/sdk/config/DingTalkProperties.java
  12. 139 0
      dingtalk-sdk/src/main/java/com/usoftchina/dingtalk/sdk/dto/AddScheduleReq.java
  13. 263 0
      dingtalk-sdk/src/main/java/com/usoftchina/dingtalk/sdk/dto/SendMessageReq.java
  14. 25 0
      dingtalk-sdk/src/main/java/com/usoftchina/dingtalk/sdk/exception/DingTalkInvokeException.java
  15. 165 0
      dingtalk-sdk/src/main/java/com/usoftchina/dingtalk/sdk/util/DateUtils.java
  16. 44 0
      dingtalk-sdk/src/main/java/com/usoftchina/dingtalk/sdk/util/UrlUtils.java
  17. 14 0
      dingtalk-sdk/src/test/java/com/usoftchina/dingtalk/sdk/test/UrlTest.java
  18. 2 1
      settings.gradle
  19. 1 1
      uas-office-core/src/main/java/com/usoftchina/uas/office/dingtalk/config/RedisConfig.java
  20. 12 0
      uas-office-dingtalk-server/build.gradle
  21. 21 0
      uas-office-dingtalk-server/src/main/java/com/usoftchina/uas/office/dingtalk/UasOfficeServer.java
  22. 28 0
      uas-office-dingtalk-server/src/main/java/com/usoftchina/uas/office/dingtalk/config/ExceptionConfig.java
  23. 38 0
      uas-office-dingtalk-server/src/main/java/com/usoftchina/uas/office/dingtalk/config/WebMvcConfig.java
  24. 60 0
      uas-office-dingtalk-server/src/main/java/com/usoftchina/uas/office/dingtalk/config/WebSecurityConfig.java
  25. 0 0
      uas-office-dingtalk-server/src/main/resources/application.yaml
  26. 0 0
      uas-office-dingtalk-server/src/main/resources/banner.txt
  27. 4 0
      uas-office-dingtalk-server/src/main/resources/config/application-dev.yaml
  28. 80 0
      uas-office-dingtalk-server/src/main/resources/erp_schema.json
  29. 0 0
      uas-office-dingtalk-server/src/main/resources/icon.ico
  30. 0 0
      uas-office-dingtalk-server/src/main/resources/logback-spring.xml
  31. 0 0
      uas-office-dingtalk-server/src/main/resources/schema.sql
  32. 0 0
      uas-office-dingtalk-server/src/main/resources/static/addrbook.html
  33. 150 0
      uas-office-dingtalk-server/src/main/resources/static/agent.html
  34. 67 0
      uas-office-dingtalk-server/src/main/resources/static/authorize.html
  35. 49 0
      uas-office-dingtalk-server/src/main/resources/static/corp.html
  36. 0 0
      uas-office-dingtalk-server/src/main/resources/static/css/bootstrap.min.css
  37. 0 0
      uas-office-dingtalk-server/src/main/resources/static/css/iconfont.css
  38. 0 0
      uas-office-dingtalk-server/src/main/resources/static/css/iconfont.eot
  39. 0 0
      uas-office-dingtalk-server/src/main/resources/static/css/iconfont.svg
  40. 0 0
      uas-office-dingtalk-server/src/main/resources/static/css/iconfont.ttf
  41. 0 0
      uas-office-dingtalk-server/src/main/resources/static/css/iconfont.woff
  42. 0 0
      uas-office-dingtalk-server/src/main/resources/static/css/iconfont.woff2
  43. 0 0
      uas-office-dingtalk-server/src/main/resources/static/css/login.css
  44. 0 0
      uas-office-dingtalk-server/src/main/resources/static/dc.html
  45. 35 0
      uas-office-dingtalk-server/src/main/resources/static/js/addrbook.js
  46. 100 0
      uas-office-dingtalk-server/src/main/resources/static/js/agent.js
  47. 0 0
      uas-office-dingtalk-server/src/main/resources/static/js/bootstrap.min.js
  48. 0 0
      uas-office-dingtalk-server/src/main/resources/static/js/corp.js
  49. 0 0
      uas-office-dingtalk-server/src/main/resources/static/js/dc.js
  50. 0 0
      uas-office-dingtalk-server/src/main/resources/static/js/jquery.min.js
  51. 0 0
      uas-office-dingtalk-server/src/main/resources/static/js/template.min.js
  52. 0 0
      uas-office-dingtalk-server/src/main/resources/static/login.html
  53. 71 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/config/DingTalkConfig.java
  54. 80 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/controller/DingTalkAuthController.java
  55. 33 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/controller/DingTalkMessageController.java
  56. 82 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/entity/Agenda.java
  57. 222 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/entity/CardLog.java
  58. 118 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/entity/Employee.java
  59. 55 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/entity/HrOrg.java
  60. 107 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/entity/JProcess.java
  61. 161 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/entity/Meeting.java
  62. 201 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/entity/OutSign.java
  63. 70 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/listener/UasAgendaListener.java
  64. 51 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/listener/UasEmployeeListener.java
  65. 78 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/listener/UasMeetingListener.java
  66. 98 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/listener/UasOrgListener.java
  67. 112 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/listener/UasProcessListener.java
  68. 43 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/manage/controller/DingTalkAddrBookController.java
  69. 102 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/manage/controller/DingTalkAgentController.java
  70. 52 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/manage/controller/DingTalkSettingController.java
  71. 73 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/manage/entity/DingTalkAgent.java
  72. 34 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/manage/entity/DingTalkSetting.java
  73. 24 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/manage/event/DingTalkAgentEvent.java
  74. 20 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/manage/event/DingTalkCorpEvent.java
  75. 82 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/manage/service/DingTalkAgentService.java
  76. 66 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/manage/service/DingTalkSettingService.java
  77. 26 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/service/UasAgendaService.java
  78. 21 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/service/UasCardLogService.java
  79. 301 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/service/UasEmployeeService.java
  80. 17 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/service/UasJProcessService.java
  81. 37 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/service/UasMeetingService.java
  82. 255 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/service/UasOrgService.java
  83. 22 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/service/UasOutSignService.java
  84. 103 0
      uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/task/DingTalkCheckinTask.java
  85. 0 0
      uas-office-qywx-server/build.gradle
  86. 1 1
      uas-office-qywx-server/src/main/java/com/usoftchina/uas/office/qywx/UasOfficeServer.java
  87. 1 1
      uas-office-qywx-server/src/main/java/com/usoftchina/uas/office/qywx/config/ExceptionConfig.java
  88. 37 0
      uas-office-qywx-server/src/main/java/com/usoftchina/uas/office/qywx/config/WebMvcConfig.java
  89. 60 0
      uas-office-qywx-server/src/main/java/com/usoftchina/uas/office/qywx/config/WebSecurityConfig.java
  90. 36 0
      uas-office-qywx-server/src/main/resources/application.yaml
  91. 10 0
      uas-office-qywx-server/src/main/resources/banner.txt
  92. 4 0
      uas-office-qywx-server/src/main/resources/config/application-dev.yaml
  93. 0 0
      uas-office-qywx-server/src/main/resources/erp_schema.json
  94. BIN
      uas-office-qywx-server/src/main/resources/icon.ico
  95. 54 0
      uas-office-qywx-server/src/main/resources/logback-spring.xml
  96. 8 0
      uas-office-qywx-server/src/main/resources/schema.sql
  97. 44 0
      uas-office-qywx-server/src/main/resources/static/addrbook.html
  98. 0 0
      uas-office-qywx-server/src/main/resources/static/agent.html
  99. 0 0
      uas-office-qywx-server/src/main/resources/static/corp.html
  100. 5 0
      uas-office-qywx-server/src/main/resources/static/css/bootstrap.min.css

+ 2 - 1
README.md

@@ -9,8 +9,9 @@
 │  ├─qywx-sdk---------------------------------企业微信api封装sdk
 │  ├─uas-office-core--------------------------基础包
 │  ├─uas-office-dingtalk----------------------uas对接钉钉
+│  ├─uas-office-dingtalk-server---------------钉钉运行时服务
 │  ├─uas-office-qywx--------------------------uas对接企业微信
-│  ├─uas-office-server------------------------运行时服务
+│  ├─uas-office-qywx-server-------------------企业微信运行时服务
 │  │
 ```
 

+ 1 - 0
build.gradle

@@ -45,6 +45,7 @@ subprojects { Project subproject ->
         maven { url "http://maven.aliyun.com/nexus/content/groups/public/" }
         maven { url "http://maven.ubtob.com/artifactory/libs-snapshot-local" }
         maven { url "http://maven.ubtob.com/artifactory/libs-release-local" }
+        maven { url "http://maven.ubtob.com/artifactory/ext-release-local" }
     }
 
     dependencyManagement {

+ 1 - 1
dingtalk-sdk/build.gradle

@@ -1,6 +1,6 @@
 dependencies {
-    compile "org.springframework:spring-web"
     compile "org.springframework:spring-context"
     compile "$fastjson"
+    compile "com.dingtalk:dingtalk-sdk:1.0"
     testCompile "junit:junit"
 }

+ 326 - 0
dingtalk-sdk/src/main/java/com/usoftchina/dingtalk/sdk/AddrBookSdk.java

@@ -0,0 +1,326 @@
+package com.usoftchina.dingtalk.sdk;
+
+import com.dingtalk.api.DefaultDingTalkClient;
+import com.dingtalk.api.DingTalkClient;
+import com.dingtalk.api.request.*;
+import com.dingtalk.api.response.*;
+import com.usoftchina.dingtalk.sdk.config.DingTalkProperties;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 通讯录
+ *
+ * @author yingp
+ * @date 2020/2/19
+ */
+public class AddrBookSdk extends BaseSdk {
+
+    public AddrBookSdk(DingTalkProperties properties) {
+        super(properties);
+    }
+
+    /**
+     * 获取用户userid
+     *
+     * @param agentCode 应用code
+     * @param authCode  免登授权码
+     * @return
+     */
+    public OapiUserGetuserinfoResponse getUserInfo(String agentCode, String authCode) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/user/getuserinfo");
+        OapiUserGetuserinfoRequest request = new OapiUserGetuserinfoRequest();
+        request.setCode(authCode);
+        request.setHttpMethod("GET");
+        OapiUserGetuserinfoResponse resp = cached(() -> client.execute(request, getAccessToken(agentCode)));
+        assertOK(resp);
+        return resp;
+    }
+
+    /**
+     * 获取用户详情
+     *
+     * @param agentCode 应用code
+     * @param userId    员工id
+     * @return
+     */
+    public OapiUserGetResponse getUser(String agentCode, String userId) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/user/get");
+        OapiUserGetRequest req = new OapiUserGetRequest();
+        req.setUserid(userId);
+        req.setHttpMethod("GET");
+        OapiUserGetResponse resp = cached(() -> client.execute(req, getAccessToken(agentCode)));
+        assertOK(resp);
+        return resp;
+    }
+
+    /**
+     * 新增成员
+     *
+     * @param agentCode 应用code
+     * @param req
+     */
+    public String createUser(String agentCode, OapiUserCreateRequest req) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/user/create");
+        OapiUserCreateResponse resp = cached(() -> client.execute(req, getAccessToken(agentCode)));
+        assertOK(resp);
+        return resp.getUserid();
+    }
+
+    /**
+     * 更新成员
+     *
+     * @param agentCode 应用code
+     * @param req
+     */
+    public void updateUser(String agentCode, OapiUserUpdateRequest req) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/user/update");
+        OapiUserUpdateResponse resp = cached(() -> client.execute(req, getAccessToken(agentCode)));
+        assertOK(resp);
+    }
+
+    /**
+     * 删除成员
+     *
+     * @param agentCode 应用code
+     * @param userId    人员ID
+     */
+    public void deleteUser(String agentCode, String userId) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/user/delete");
+        OapiUserDeleteRequest req = new OapiUserDeleteRequest();
+        req.setUserid(userId);
+        req.setHttpMethod("GET");
+        OapiUserDeleteResponse resp = cached(() -> client.execute(req, getAccessToken(agentCode)));
+        assertOK(resp);
+    }
+
+    /**
+     * 获取部门用户userid列表
+     *
+     * @param agentCode    应用code
+     * @param departmentId 部门ID
+     * @return
+     */
+    public List<String> getUserIdList(String agentCode, long departmentId) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/user/getDeptMember");
+        OapiUserGetDeptMemberRequest req = new OapiUserGetDeptMemberRequest();
+        req.setDeptId(String.valueOf(departmentId));
+        req.setHttpMethod("GET");
+        OapiUserGetDeptMemberResponse resp = cached(() -> client.execute(req, getAccessToken(agentCode)));
+        assertOK(resp);
+        return resp.getUserIds();
+    }
+
+    /**
+     * 获取部门用户
+     *
+     * @param agentCode    应用code
+     * @param departmentId 部门ID
+     * @return
+     */
+    public List<OapiUserSimplelistResponse.Userlist> getSimpleUserList(String agentCode, long departmentId) {
+        List<OapiUserSimplelistResponse.Userlist> userList = new ArrayList<>();
+        long size = 100;
+        long offset = 0;
+        while (true) {
+            List<OapiUserSimplelistResponse.Userlist> tempList = getSimpleUserList(agentCode, departmentId, offset++, size);
+            if (null != tempList) {
+                userList.addAll(tempList);
+                if (tempList.size() < size) {
+                    break;
+                }
+            } else {
+                break;
+            }
+        }
+        return userList;
+    }
+
+    /**
+     * 获取部门用户
+     *
+     * @param agentCode
+     * @param departmentId
+     * @param offset       支持分页查询,与size参数同时设置时才生效,此参数代表偏移量,偏移量从0开始
+     * @param size
+     * @return
+     */
+    private List<OapiUserSimplelistResponse.Userlist> getSimpleUserList(String agentCode, long departmentId, long offset, long size) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/user/simplelist");
+        OapiUserSimplelistRequest req = new OapiUserSimplelistRequest();
+        req.setDepartmentId(departmentId);
+        req.setOffset(offset);
+        req.setSize(size);
+        req.setHttpMethod("GET");
+        OapiUserSimplelistResponse resp = cached(() -> client.execute(req, getAccessToken(agentCode)));
+        assertOK(resp);
+        return resp.getUserlist();
+    }
+
+    /**
+     * 获取部门用户详情
+     *
+     * @param agentCode    应用code
+     * @param departmentId 部门ID
+     * @return
+     */
+    public List<OapiUserListbypageResponse.Userlist> getUserList(String agentCode, long departmentId) {
+        List<OapiUserListbypageResponse.Userlist> userList = new ArrayList<>();
+        long size = 100;
+        int offset = 0;
+        while (true) {
+            List<OapiUserListbypageResponse.Userlist> tempList = getUserList(agentCode, departmentId, offset++ + size, size);
+            if (null != tempList) {
+                userList.addAll(tempList);
+                if (tempList.size() < size) {
+                    break;
+                }
+            } else {
+                break;
+            }
+        }
+        return userList;
+    }
+
+    /**
+     * 获取部门用户详情
+     *
+     * @param agentCode
+     * @param departmentId
+     * @param offset       支持分页查询,与size参数同时设置时才生效,此参数代表偏移量
+     * @param size
+     * @return
+     */
+    public List<OapiUserListbypageResponse.Userlist> getUserList(String agentCode, long departmentId, long offset, long size) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/user/listbypage");
+        OapiUserListbypageRequest req = new OapiUserListbypageRequest();
+        req.setDepartmentId(departmentId);
+        req.setOffset(offset);
+        req.setSize(size);
+        req.setHttpMethod("GET");
+        OapiUserListbypageResponse resp = cached(() -> client.execute(req, getAccessToken(agentCode)));
+        assertOK(resp);
+        return resp.getUserlist();
+    }
+
+    /**
+     * 根据手机号获取userid
+     *
+     * @param agentCode 应用code
+     * @param mobile    手机号
+     * @return
+     */
+    public String getUserIdByMobile(String agentCode, String mobile) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/user/get_by_mobile");
+        OapiUserGetByMobileRequest req = new OapiUserGetByMobileRequest();
+        req.setMobile(mobile);
+        OapiUserGetByMobileResponse resp = cached(() -> client.execute(req, getAccessToken(agentCode)));
+        assertOK(resp);
+        return resp.getUserid();
+    }
+
+    /**
+     * 创建部门
+     *
+     * @param agentCode 应用code
+     * @param req
+     * @return 部门ID
+     */
+    public Long createDepartment(String agentCode, OapiDepartmentCreateRequest req) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/department/create");
+        OapiDepartmentCreateResponse resp = cached(() -> client.execute(req, getAccessToken(agentCode)));
+        assertOK(resp);
+        return resp.getId();
+    }
+
+    /**
+     * 更新部门
+     *
+     * @param agentCode 应用code
+     * @param req
+     * @return
+     */
+    public void updateDepartment(String agentCode, OapiDepartmentUpdateRequest req) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/department/update");
+        OapiDepartmentUpdateResponse resp = cached(() -> client.execute(req, getAccessToken(agentCode)));
+        assertOK(resp);
+    }
+
+    /**
+     * 删除部门
+     *
+     * @param agentCode    应用code
+     * @param departmentId 部门ID
+     */
+    public void deleteDepartment(String agentCode, long departmentId) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/department/delete");
+        OapiDepartmentDeleteRequest req = new OapiDepartmentDeleteRequest();
+        req.setId(String.valueOf(departmentId));
+        req.setHttpMethod("GET");
+        OapiDepartmentDeleteResponse resp = cached(() -> client.execute(req, getAccessToken(agentCode)));
+        assertOK(resp);
+    }
+
+    /**
+     * 获取子部门ID列表
+     *
+     * @param agentCode    应用code
+     * @param departmentId 父部门ID
+     * @return
+     */
+    public List<Long> getSubDepartmentIdList(String agentCode, long departmentId) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/department/list_ids");
+        OapiDepartmentListIdsRequest req = new OapiDepartmentListIdsRequest();
+        req.setId(String.valueOf(departmentId));
+        req.setHttpMethod("GET");
+        OapiDepartmentListIdsResponse resp = cached(() -> client.execute(req, getAccessToken(agentCode)));
+        assertOK(resp);
+        return resp.getSubDeptIdList();
+    }
+
+    /**
+     * 获取部门,包括下级
+     *
+     * @param agentCode    应用code
+     * @param departmentId 父部门ID
+     * @return
+     */
+    public List<OapiDepartmentListResponse.Department> getDepartmentList(String agentCode, long departmentId) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/department/list");
+        OapiDepartmentListRequest req = new OapiDepartmentListRequest();
+        req.setId(String.valueOf(departmentId));
+        req.setFetchChild(true);
+        req.setHttpMethod("GET");
+        OapiDepartmentListResponse resp = cached(() -> client.execute(req, getAccessToken(agentCode)));
+        assertOK(resp);
+        return resp.getDepartment();
+    }
+
+    /**
+     * 获取全部部门
+     *
+     * @param agentCode 应用code
+     * @return
+     */
+    public List<OapiDepartmentListResponse.Department> getDepartmentList(String agentCode) {
+        return getDepartmentList(agentCode, 1);
+    }
+
+    /**
+     * 获取部门详情
+     *
+     * @param agentCode    应用code
+     * @param departmentId 部门ID
+     * @return
+     */
+    public OapiDepartmentGetResponse getDepartmentDetail(String agentCode, long departmentId) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/department/get");
+        OapiDepartmentGetRequest req = new OapiDepartmentGetRequest();
+        req.setId(String.valueOf(departmentId));
+        req.setHttpMethod("GET");
+        OapiDepartmentGetResponse resp = cached(() -> client.execute(req, getAccessToken(agentCode)));
+        assertOK(resp);
+        return resp;
+    }
+}

+ 97 - 0
dingtalk-sdk/src/main/java/com/usoftchina/dingtalk/sdk/BaseSdk.java

@@ -0,0 +1,97 @@
+package com.usoftchina.dingtalk.sdk;
+
+import com.alibaba.fastjson.JSON;
+import com.dingtalk.api.DefaultDingTalkClient;
+import com.dingtalk.api.DingTalkClient;
+import com.dingtalk.api.request.OapiGettokenRequest;
+import com.dingtalk.api.response.OapiGettokenResponse;
+import com.taobao.api.ApiException;
+import com.taobao.api.TaobaoResponse;
+import com.usoftchina.dingtalk.sdk.config.Agent;
+import com.usoftchina.dingtalk.sdk.config.DingTalkProperties;
+import com.usoftchina.dingtalk.sdk.exception.DingTalkInvokeException;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+/**
+ * @author yingp
+ * @date 2020/2/19
+ */
+public abstract class BaseSdk {
+    /**
+     * <私钥, Token>
+     */
+    private final Map<String, AccessToken> accessTokenMap = new HashMap<>();
+
+    private DingTalkProperties properties;
+
+    public BaseSdk(DingTalkProperties properties) {
+        this.properties = properties;
+    }
+
+    public String getCorpId() {
+        return properties.getCorpId();
+    }
+
+    protected Map<String, Agent> getAgentMap() {
+        return properties.getAgentMap();
+    }
+
+    protected void assertOK(TaobaoResponse resp) {
+        if (!"0".equals(resp.getErrorCode())) {
+            throw new DingTalkInvokeException(resp);
+        }
+    }
+
+    protected <R> R cached(Callable<R> callable) {
+        try {
+            return callable.call();
+        } catch (ApiException e) {
+            throw new DingTalkInvokeException(e);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * @param agentCode
+     * @return
+     */
+    protected synchronized String getAccessToken(String agentCode) {
+        Agent agent = getAgentMap().get(agentCode);
+        if (null == agent) {
+            throw new RuntimeException("没有找到应用" + agentCode);
+        }
+        if (!accessTokenMap.containsKey(agent.getAppSecret()) || accessTokenMap.get(agent.getAppSecret()).expired()) {
+            DefaultDingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/gettoken");
+            OapiGettokenRequest request = new OapiGettokenRequest();
+            request.setAppkey(agent.getAppKey());
+            request.setAppsecret(agent.getAppSecret());
+            request.setHttpMethod("GET");
+            OapiGettokenResponse resp = cached(() -> client.execute(request));
+            assertOK(resp);
+            accessTokenMap.put(agent.getAppSecret(), new AccessToken(resp));
+        }
+        return accessTokenMap.get(agent.getAppSecret()).getToken();
+    }
+
+    class AccessToken {
+        private final String token;
+        private final long endTime;
+
+        public AccessToken(OapiGettokenResponse resp) {
+            this.token = resp.getAccessToken();
+            this.endTime = System.currentTimeMillis() + resp.getExpiresIn() * 1000 - 5000;
+        }
+
+        public boolean expired() {
+            return System.currentTimeMillis() > endTime;
+        }
+
+        public String getToken() {
+            return token;
+        }
+    }
+}

+ 40 - 0
dingtalk-sdk/src/main/java/com/usoftchina/dingtalk/sdk/MessageSdk.java

@@ -0,0 +1,40 @@
+package com.usoftchina.dingtalk.sdk;
+
+import com.dingtalk.api.DefaultDingTalkClient;
+import com.dingtalk.api.DingTalkClient;
+import com.dingtalk.api.response.OapiMessageCorpconversationAsyncsendV2Response;
+import com.usoftchina.dingtalk.sdk.config.Agent;
+import com.usoftchina.dingtalk.sdk.config.DingTalkProperties;
+import com.usoftchina.dingtalk.sdk.dto.SendMessageReq;
+
+/**
+ * 消息
+ *
+ * @author yingp
+ * @date 2020/2/19
+ */
+public class MessageSdk extends BaseSdk {
+    public MessageSdk(DingTalkProperties properties) {
+        super(properties);
+    }
+
+    /**
+     * 发送消息
+     * 应用支持推送文本、图片、视频、文件、图文等类型
+     *
+     * @param agentCode 应用code
+     * @param req
+     * @return
+     */
+    public OapiMessageCorpconversationAsyncsendV2Response send(String agentCode, SendMessageReq req) {
+        Agent agent = getAgentMap().get(agentCode);
+        if (null == agent) {
+            throw new RuntimeException("没有找到应用" + agentCode);
+        }
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2");
+        req.agentId(agent.getId());
+        OapiMessageCorpconversationAsyncsendV2Response resp = cached(() -> client.execute(req.build(), getAccessToken(agentCode)));
+        assertOK(resp);
+        return resp;
+    }
+}

+ 196 - 0
dingtalk-sdk/src/main/java/com/usoftchina/dingtalk/sdk/OaSdk.java

@@ -0,0 +1,196 @@
+package com.usoftchina.dingtalk.sdk;
+
+import com.dingtalk.api.DefaultDingTalkClient;
+import com.dingtalk.api.DingTalkClient;
+import com.dingtalk.api.request.OapiAttendanceListRecordRequest;
+import com.dingtalk.api.request.OapiAttendanceListRequest;
+import com.dingtalk.api.request.OapiCheckinRecordGetRequest;
+import com.dingtalk.api.request.OapiCheckinRecordRequest;
+import com.dingtalk.api.response.OapiAttendanceListRecordResponse;
+import com.dingtalk.api.response.OapiAttendanceListResponse;
+import com.dingtalk.api.response.OapiCheckinRecordGetResponse;
+import com.dingtalk.api.response.OapiCheckinRecordResponse;
+import com.usoftchina.dingtalk.sdk.config.DingTalkProperties;
+import com.usoftchina.dingtalk.sdk.util.DateUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * oa
+ * 签到、考勤
+ *
+ * @author yingp
+ * @date 2020/2/19
+ */
+public class OaSdk extends BaseSdk {
+    public OaSdk(DingTalkProperties properties) {
+        super(properties);
+    }
+
+    /**
+     * 获取部门用户签到记录
+     * 企业使用此接口可获取部门人员的签到记录,进行统计分析,也可以基于高德地图(http://lbs.amap.com/)API接口开发人员分布图和热力图。
+     * <p>
+     * 注意:目前最多获取1000人以内的签到数据,如果所传部门ID及其子部门下的user超过1000,会报错。
+     * </p>
+     *
+     * @param agentCode
+     * @param departmentId
+     * @param startTime    起始时间。Unix时间戳,如:1520956800000
+     * @param endTime      结束时间。Unix时间戳,如:1520956800000。开始时间和结束时间的间隔不能大于45
+     * @return
+     */
+    public List<OapiCheckinRecordResponse.Data> getCheckinData(String agentCode, long departmentId, long startTime, long endTime) {
+        List<OapiCheckinRecordResponse.Data> dataList = new ArrayList<>();
+        long size = 100;
+        int i = 0;
+        while (true) {
+            List<OapiCheckinRecordResponse.Data> tempList = getCheckinData(agentCode, departmentId, startTime, endTime, i++, size);
+            if (null != tempList) {
+                dataList.addAll(tempList);
+                if (tempList.size() < size) {
+                    break;
+                }
+            } else {
+                break;
+            }
+        }
+        return dataList;
+    }
+
+    /**
+     * 分页获取部门用户签到记录
+     * 企业使用此接口可获取部门人员的签到记录,进行统计分析,也可以基于高德地图(http://lbs.amap.com/)API接口开发人员分布图和热力图。
+     * <p>
+     * 注意:目前最多获取1000人以内的签到数据,如果所传部门ID及其子部门下的user超过1000,会报错。
+     * </p>
+     *
+     * @param agentCode
+     * @param departmentId
+     * @param startTime
+     * @param endTime
+     * @param offset       支持分页查询,与size 参数同时设置时才生效,此参数代表偏移量,从012...依次递增
+     * @param size         支持分页查询,与offset 参数同时设置时才生效,此参数代表分页大小,最大100
+     * @return
+     */
+    public List<OapiCheckinRecordResponse.Data> getCheckinData(String agentCode, long departmentId, long startTime, long endTime, long offset, long size) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/checkin/record");
+        OapiCheckinRecordRequest req = new OapiCheckinRecordRequest();
+        req.setDepartmentId(String.valueOf(departmentId));
+        req.setStartTime(startTime);
+        req.setEndTime(endTime);
+        req.setOffset(offset);
+        req.setOrder("asc");
+        req.setSize(size);
+        req.setHttpMethod("GET");
+        OapiCheckinRecordResponse resp = cached(() -> client.execute(req, getAccessToken(agentCode)));
+        assertOK(resp);
+        return resp.getData();
+    }
+
+    /**
+     * 获取用户签到记录
+     * 企业使用此接口可获取指定人员的签到记录,进行统计分析,也可以基于高德地图(http://lbs.amap.com)API接口开发人员分布图和热力图。
+     *
+     * @param agentCode
+     * @param userIdList
+     * @param startTime  起始时间。Unix时间戳,如:1520956800000
+     * @param endTime    结束时间。Unix时间戳,如:1520956800000。如果是取1个人的数据,时间范围最大到10天,如果是取多个人的数据,时间范围最大1天。
+     * @return
+     */
+    public List<OapiCheckinRecordGetResponse.CheckinRecordVo> getCheckinData(String agentCode, List<String> userIdList, long startTime, long endTime) {
+        List<OapiCheckinRecordGetResponse.CheckinRecordVo> dataList = new ArrayList<>();
+        long size = 100;
+        int i = 0;
+        while (true) {
+            List<OapiCheckinRecordGetResponse.CheckinRecordVo> tempList = getCheckinData(agentCode, userIdList, startTime, endTime, i++, size);
+            if (null != tempList) {
+                dataList.addAll(tempList);
+                if (tempList.size() < size) {
+                    break;
+                }
+            } else {
+                break;
+            }
+        }
+        return dataList;
+    }
+
+    /**
+     * 分页获取用户签到记录
+     * 企业使用此接口可获取指定人员的签到记录,进行统计分析,也可以基于高德地图(http://lbs.amap.com)API接口开发人员分布图和热力图。
+     *
+     * @param agentCode
+     * @param userIdList
+     * @param startTime
+     * @param endTime
+     * @param cursor     分页查询的游标,最开始可以传0,然后以12依次递增
+     * @param size       分页查询的每页大小,最大100
+     * @return
+     */
+    public List<OapiCheckinRecordGetResponse.CheckinRecordVo> getCheckinData(String agentCode, List<String> userIdList, long startTime, long endTime, long cursor, long size) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/checkin/record/get");
+        OapiCheckinRecordGetRequest req = new OapiCheckinRecordGetRequest();
+        req.setStartTime(startTime);
+        req.setEndTime(endTime);
+        req.setSize(size);
+        req.setCursor(cursor);
+        req.setUseridList(String.join(",", userIdList));
+        OapiCheckinRecordGetResponse resp = cached(() -> client.execute(req, getAccessToken(agentCode)));
+        assertOK(resp);
+        return resp.getResult().getPageList();
+    }
+
+
+    /**
+     * 获取打卡详情
+     * 该接口用于返回企业内员工的实际打卡记录。比如,企业给一个员工设定的排班是上午9点和下午6点各打一次卡,但是员工在这期间打了多次,该接口会把所有的打卡记录都返回。
+     * <p>
+     * 如果只要获取打卡结果数据,不需要详情数据,可使用获取打卡结果接口。
+     * </p>
+     *
+     * @param agentCode
+     * @param userIdList 企业内的员工id列表,最多不能超过50
+     * @param startTime  查询考勤打卡记录的起始工作日。格式为“yyyy-MM-dd hh:mm:ss”。
+     * @param endTime    查询考勤打卡记录的结束工作日。格式为“yyyy-MM-dd hh:mm:ss”。注意,起始与结束工作日最多相隔7
+     */
+    public List<OapiAttendanceListRecordResponse.Recordresult> getAttendanceRecordList(String agentCode, List<String> userIdList, long startTime, long endTime) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/attendance/listRecord");
+        OapiAttendanceListRecordRequest req = new OapiAttendanceListRecordRequest();
+        req.setCheckDateFrom(DateUtils.formatDateTime(startTime));
+        req.setCheckDateTo(DateUtils.formatDateTime(endTime));
+        req.setUserIds(userIdList);
+        OapiAttendanceListRecordResponse resp = cached(() -> client.execute(req, getAccessToken(agentCode)));
+        assertOK(resp);
+        return resp.getRecordresult();
+    }
+
+    /**
+     * 获取打卡结果
+     * 该接口用于返回企业内员工的实际打卡结果。比如,企业给一个员工设定的排班是上午9点和下午6点各打一次卡,即使员工在这期间打了多次,该接口也只会返回两条记录,包括上午的打卡结果和下午的打卡结果。
+     * <p>
+     * 如果要获取打卡详细数据,比如打卡位置,可使用获取打卡详情接口。
+     * </p>
+     *
+     * @param agentCode
+     * @param userIdList
+     * @param startTime  查询考勤打卡记录的起始工作日。格式为“yyyy-MM-dd HH:mm:ss”,HH:mm:ss可以使用00:00:00,将返回此日期从0点到24点的结果
+     * @param endTime    查询考勤打卡记录的结束工作日。格式为“yyyy-MM-dd HH:mm:ss”,HH:mm:ss可以使用00:00:00,将返回此日期从0点到24点的结果。注意,起始与结束工作日最多相隔7
+     * @param offset     表示获取考勤数据的起始点,第一次传0,如果还有多余数据,下次获取传的offset值为之前的offset+limit,012...依次递增
+     * @param limit      表示获取考勤数据的条数,最大不能超过50
+     * @return
+     */
+    public List<OapiAttendanceListResponse.Recordresult> getAttendanceList(String agentCode, List<String> userIdList, long startTime, long endTime, long offset, long limit) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/attendance/list");
+        OapiAttendanceListRequest req = new OapiAttendanceListRequest();
+        req.setWorkDateFrom(DateUtils.formatDateTime(startTime));
+        req.setWorkDateTo(DateUtils.formatDateTime(endTime));
+        req.setUserIdList(userIdList);
+        req.setOffset(offset);
+        req.setLimit(limit);
+        OapiAttendanceListResponse resp = cached(() -> client.execute(req, getAccessToken(agentCode)));
+        assertOK(resp);
+        return resp.getRecordresult();
+    }
+}

+ 35 - 0
dingtalk-sdk/src/main/java/com/usoftchina/dingtalk/sdk/ScheduleSdk.java

@@ -0,0 +1,35 @@
+package com.usoftchina.dingtalk.sdk;
+
+import com.dingtalk.api.DefaultDingTalkClient;
+import com.dingtalk.api.DingTalkClient;
+import com.dingtalk.api.response.OapiCalendarCreateResponse;
+import com.usoftchina.dingtalk.sdk.config.DingTalkProperties;
+import com.usoftchina.dingtalk.sdk.dto.AddScheduleReq;
+
+/**
+ * DING日程
+ *
+ * @author yingp
+ * @date 2020/2/19
+ */
+public class ScheduleSdk extends BaseSdk {
+    public ScheduleSdk(DingTalkProperties properties) {
+        super(properties);
+    }
+
+    /**
+     * 创建日程
+     * 调用该接口可以将企业员工的待办事项写入到钉钉日历并在钉钉日历中展示。
+     * 企业可在应用的权限管理页面申请该接口权限
+     *
+     * @param agentCode
+     * @param req
+     * @return 日程id
+     */
+    public String addSchedule(String agentCode, AddScheduleReq req) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/calendar/create");
+        OapiCalendarCreateResponse resp = cached(() -> client.execute(req.build(), getAccessToken(agentCode)));
+        assertOK(resp);
+        return resp.getResult().getDingtalkCalendarId();
+    }
+}

+ 87 - 0
dingtalk-sdk/src/main/java/com/usoftchina/dingtalk/sdk/WorkRecordSdk.java

@@ -0,0 +1,87 @@
+package com.usoftchina.dingtalk.sdk;
+
+import com.dingtalk.api.DefaultDingTalkClient;
+import com.dingtalk.api.DingTalkClient;
+import com.dingtalk.api.request.OapiWorkrecordAddRequest;
+import com.dingtalk.api.request.OapiWorkrecordGetbyuseridRequest;
+import com.dingtalk.api.request.OapiWorkrecordUpdateRequest;
+import com.dingtalk.api.response.OapiWorkrecordAddResponse;
+import com.dingtalk.api.response.OapiWorkrecordGetbyuseridResponse;
+import com.dingtalk.api.response.OapiWorkrecordUpdateResponse;
+import com.usoftchina.dingtalk.sdk.config.DingTalkProperties;
+
+/**
+ * 待办任务
+ *
+ * @author yingp
+ * @date 2020/2/19
+ */
+public class WorkRecordSdk extends BaseSdk {
+    public WorkRecordSdk(DingTalkProperties properties) {
+        super(properties);
+    }
+
+    /**
+     * 发起待办
+     * <p>
+     * 企业可以调用该接口发起一个待办事项,该待办事项会出现在钉钉客户端“待办事项”页面,与钉钉审批待办事项并列。
+     * 需要注意的是,通过开放接口发起的待办,只会有一个“前往”按钮
+     * 目前待办事项有防骚扰控制,具体为:
+     * 1、每人每天最多收到一条表单内容相同的待办。触发这个限制,会返回错误码854001
+     * 2、每人每天最多收到100条待办。触发这个限制,会返回错误码854002
+     * </p>
+     *
+     * @param agentCode
+     * @param req
+     * @return 待办事项唯一id
+     */
+    public String addWorkRecord(String agentCode, OapiWorkrecordAddRequest req) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/workrecord/add");
+        OapiWorkrecordAddResponse resp = cached(() -> client.execute(req, getAccessToken(agentCode)));
+        assertOK(resp);
+        return resp.getBody();
+    }
+
+    /**
+     * 更新待办
+     * <p>
+     * 企业可以调用该接口更新待办事项状态,调用成功后,该待办事项在该用户的“待办事项”列表页面中消失。
+     * </p>
+     *
+     * @param agentCode
+     * @param userId    待办事项对应的用户id
+     * @param recordId  待办事项唯一id。该id可以使用创建待办接口中传入的biz_id,也可以使用创建待办接口返回结果中的record_id
+     */
+    public void updateWorkRecord(String agentCode, String userId, String recordId) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/workrecord/update");
+        OapiWorkrecordUpdateRequest req = new OapiWorkrecordUpdateRequest();
+        req.setUserid(userId);
+        req.setRecordId(recordId);
+        OapiWorkrecordUpdateResponse resp = cached(() -> client.execute(req, getAccessToken(agentCode)));
+        assertOK(resp);
+    }
+
+    /**
+     * 获取用户待办事项
+     * <p>
+     * 企业可以调用该接口分页获取用户的待办任务列表。
+     * </p>
+     *
+     * @param agentCode
+     * @param userId
+     * @param offset 分页游标,从0开始,如返回结果中has_more为true,则表示还有数据,offset再传上一次的offset+limit
+     * @param limit 分页大小,最多50
+     */
+    public OapiWorkrecordGetbyuseridResponse.PageResult getWorkRecordList(String agentCode, String userId, long offset, long limit) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/workrecord/getbyuserid");
+        OapiWorkrecordGetbyuseridRequest req = new OapiWorkrecordGetbyuseridRequest();
+        req.setUserid(userId);
+        req.setOffset(offset);
+        req.setLimit(limit);
+        // 待办事项状态,0表示未完成,1表示完成
+        req.setStatus(0L);
+        OapiWorkrecordGetbyuseridResponse resp = cached(() -> client.execute(req, getAccessToken(agentCode)));
+        assertOK(resp);
+        return resp.getRecords();
+    }
+}

+ 43 - 0
dingtalk-sdk/src/main/java/com/usoftchina/dingtalk/sdk/config/Agent.java

@@ -0,0 +1,43 @@
+package com.usoftchina.dingtalk.sdk.config;
+
+/**
+ * @author yingp
+ */
+public class Agent {
+    /**
+     * 编号,统一友好命名。代码里面直接使用
+     */
+    private final String code;
+    private final Long id;
+    /**
+     * 应用key
+     */
+    private final String appKey;
+    /**
+     * 应用私钥
+     */
+    private final String appSecret;
+
+    public Agent(String code, Long id, String appKey, String appSecret) {
+        this.code = code;
+        this.id = id;
+        this.appKey = appKey;
+        this.appSecret = appSecret;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public String getAppKey() {
+        return appKey;
+    }
+
+    public String getAppSecret() {
+        return appSecret;
+    }
+}

+ 40 - 0
dingtalk-sdk/src/main/java/com/usoftchina/dingtalk/sdk/config/DingTalkProperties.java

@@ -0,0 +1,40 @@
+package com.usoftchina.dingtalk.sdk.config;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author yingp
+ */
+public class DingTalkProperties {
+    /**
+     * 企业钉钉ID
+     */
+    private String corpId;
+    /**
+     * <应用CODE, 应用私钥>
+     */
+    private Map<String, Agent> agentMap;
+
+    public String getCorpId() {
+        return corpId;
+    }
+
+    public void setCorpId(String corpId) {
+        this.corpId = corpId;
+    }
+
+    public Map<String, Agent> getAgentMap() {
+        return agentMap;
+    }
+
+    public void setAgents(List<Agent> agents) {
+        if (null == agents) {
+            agentMap = new HashMap<>(0);
+        } else {
+            this.agentMap = agents.stream().collect(Collectors.toMap(Agent::getCode, a -> a));
+        }
+    }
+}

+ 139 - 0
dingtalk-sdk/src/main/java/com/usoftchina/dingtalk/sdk/dto/AddScheduleReq.java

@@ -0,0 +1,139 @@
+package com.usoftchina.dingtalk.sdk.dto;
+
+import com.dingtalk.api.request.OapiCalendarCreateRequest;
+import org.springframework.ui.ModelMap;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+/**
+ * @author yingp
+ */
+public class AddScheduleReq {
+    /**
+     * 组织者userId
+     */
+    private String organizer;
+    /**
+     * 业务方自己的主键
+     */
+    private String bizId;
+    /**
+     * 日程开始时间,Unix时间戳
+     */
+    private Long startTime;
+    /**
+     * 日程结束时间,Unix时间戳
+     */
+    private Long endTime;
+    /**
+     * 日程参与者列表。最多支持2000
+     */
+    private List<String> attendees;
+    /**
+     * 日程标题。0 ~ 128 字符。不填会默认显示为“新建事件”
+     */
+    private String summary;
+    /**
+     * 日程描述。0 ~ 512 字符
+     */
+    private String description;
+    /**
+     * 日程开始(start_time)前多少分提醒
+     */
+    private Long remindBeforeEventMins;
+    /**
+     * 日程地址。0 ~ 128 字符
+     */
+    private String location;
+    /**
+     * 日程来源标题
+     */
+    private String sourceTitle;
+    /**
+     * 日程来源链接,点击后跳转
+     */
+    private String sourceUrl;
+
+    public AddScheduleReq organizer(String organizer) {
+        this.organizer = organizer;
+        return this;
+    }
+
+    public AddScheduleReq bizId(String bizId) {
+        this.bizId = bizId;
+        return this;
+    }
+
+    public AddScheduleReq during(long startTime, long endTime) {
+        this.startTime = startTime;
+        this.endTime = endTime;
+        return this;
+    }
+
+    public AddScheduleReq attendees(List<String> attendees) {
+        this.attendees = attendees;
+        return this;
+    }
+
+    public AddScheduleReq summary(String summary) {
+        this.summary = summary;
+        return this;
+    }
+
+    public AddScheduleReq description(String description) {
+        this.description = description;
+        return this;
+    }
+
+    public AddScheduleReq source(String title, String url) {
+        this.sourceTitle = title;
+        this.sourceUrl = url;
+        return this;
+    }
+
+    public AddScheduleReq remindBefore(long minutes) {
+        this.remindBeforeEventMins = minutes;
+        return this;
+    }
+
+    public AddScheduleReq location(String location) {
+        this.location = location;
+        return this;
+    }
+
+    public OapiCalendarCreateRequest build() {
+        OapiCalendarCreateRequest req = new OapiCalendarCreateRequest();
+        OapiCalendarCreateRequest.OpenCalendarCreateVo creatVo = new OapiCalendarCreateRequest.OpenCalendarCreateVo();
+        creatVo.setUuid(UUID.randomUUID().toString());
+        creatVo.setBizId(bizId);
+        creatVo.setCreatorUserid(organizer);
+        creatVo.setSummary(summary);
+        creatVo.setDescription(description);
+        creatVo.setLocation(location);
+        creatVo.setReceiverUserids(attendees);
+        OapiCalendarCreateRequest.DatetimeVo start = new OapiCalendarCreateRequest.DatetimeVo();
+        start.setUnixTimestamp(startTime);
+        creatVo.setStartTime(start);
+        OapiCalendarCreateRequest.DatetimeVo end = new OapiCalendarCreateRequest.DatetimeVo();
+        end.setUnixTimestamp(endTime);
+        creatVo.setEndTime(end);
+        creatVo.setCalendarType("notification");
+        if (null != sourceTitle) {
+            OapiCalendarCreateRequest.OpenCalendarSourceVo source = new OapiCalendarCreateRequest.OpenCalendarSourceVo();
+            source.setTitle(sourceTitle);
+            source.setUrl(sourceUrl);
+            creatVo.setSource(source);
+        }
+        if (null != remindBeforeEventMins) {
+            OapiCalendarCreateRequest.OpenCalendarReminderVo reminderVo = new OapiCalendarCreateRequest.OpenCalendarReminderVo();
+            reminderVo.setMinutes(remindBeforeEventMins);
+            creatVo.setReminder(reminderVo);
+        }
+        req.setCreateVo(creatVo);
+        return req;
+    }
+}

+ 263 - 0
dingtalk-sdk/src/main/java/com/usoftchina/dingtalk/sdk/dto/SendMessageReq.java

@@ -0,0 +1,263 @@
+package com.usoftchina.dingtalk.sdk.dto;
+
+import com.dingtalk.api.request.OapiMessageCorpconversationAsyncsendV2Request;
+
+import java.util.*;
+
+/**
+ * @author yingp
+ */
+public class SendMessageReq {
+
+    private final static int MAX_USER_SIZE = 100;
+    private final static int MAX_PARTY_SIZE = 20;
+
+    private Set<String> userSet;
+    private Set<String> deptSet;
+    /**
+     * 消息内容
+     */
+    private AbstractMessageBody body;
+    /**
+     * 企业应用的id
+     */
+    private Long agentId;
+
+    public SendMessageReq toUser(String... userIds) {
+        if (null == userSet) {
+            userSet = new HashSet<>(userIds.length);
+        }
+        for (String userId : userIds) {
+            userSet.add(userId);
+        }
+        return this;
+    }
+
+    public SendMessageReq toDept(Long... deptIds) {
+        if (null == deptSet) {
+            deptSet = new HashSet<>(deptIds.length);
+        }
+        for (Long deptId : deptIds) {
+            deptSet.add(String.valueOf(deptId));
+        }
+        return this;
+    }
+
+    public SendMessageReq agentId(long agentId) {
+        this.agentId = agentId;
+        return this;
+    }
+
+    public Long getAgentId() {
+        return agentId;
+    }
+
+    /**
+     * 文本消息
+     *
+     * @param content
+     * @return
+     */
+    public SendMessageReq text(String content) {
+        this.body = new Text(content);
+        return this;
+    }
+
+    /**
+     * 图片消息
+     *
+     * @param mediaId
+     * @return
+     */
+    public SendMessageReq image(String mediaId) {
+        this.body = new Image(mediaId);
+        return this;
+    }
+
+    /**
+     * 文件消息
+     *
+     * @param mediaId
+     * @return
+     */
+    public SendMessageReq file(String mediaId) {
+        this.body = new File(mediaId);
+        return this;
+    }
+
+    /**
+     * 文本卡片消息
+     *
+     * @param title
+     * @param description
+     * @param url
+     * @return
+     */
+    public SendMessageReq actionCard(String title, String description, String url) {
+        this.body = new ActionCard(title, description, url);
+        return this;
+    }
+
+    /**
+     * markdown消息
+     *
+     * @param title
+     * @param content
+     * @return
+     */
+    public SendMessageReq markdown(String title, String content) {
+        this.body = new Markdown(title, content);
+        return this;
+    }
+
+    public OapiMessageCorpconversationAsyncsendV2Request build() {
+        OapiMessageCorpconversationAsyncsendV2Request request = new OapiMessageCorpconversationAsyncsendV2Request();
+        request.setAgentId(agentId);
+        if (null != userSet) {
+            if (userSet.size() > MAX_USER_SIZE) {
+                throw new RuntimeException(String.format("最多支持%s个用户", MAX_USER_SIZE));
+            }
+            request.setUseridList(String.join(",", userSet));
+        } else if (null != deptSet) {
+            if (deptSet.size() > MAX_PARTY_SIZE) {
+                throw new RuntimeException(String.format("最多支持%s个部门", MAX_PARTY_SIZE));
+            }
+            request.setDeptIdList(String.join(",", deptSet));
+        } else {
+            request.setToAllUser(true);
+        }
+        request.setMsg(body.build());
+        return request;
+    }
+
+    interface AbstractMessageBody {
+        OapiMessageCorpconversationAsyncsendV2Request.Msg build();
+    }
+
+    class Text implements AbstractMessageBody {
+        private final String content;
+
+        public Text(String content) {
+            this.content = content;
+        }
+
+        @Override
+        public OapiMessageCorpconversationAsyncsendV2Request.Msg build() {
+            OapiMessageCorpconversationAsyncsendV2Request.Msg msg = new OapiMessageCorpconversationAsyncsendV2Request.Msg();
+            msg.setMsgtype("text");
+            msg.setText(new OapiMessageCorpconversationAsyncsendV2Request.Text());
+            msg.getText().setContent(content);
+            return msg;
+        }
+    }
+
+    class Image implements AbstractMessageBody {
+        /**
+         * 图片媒体文件id,可以调用上传临时素材接口获取
+         */
+        private final String mediaId;
+
+        public Image(String mediaId) {
+            this.mediaId = mediaId;
+        }
+
+        @Override
+        public OapiMessageCorpconversationAsyncsendV2Request.Msg build() {
+            OapiMessageCorpconversationAsyncsendV2Request.Msg msg = new OapiMessageCorpconversationAsyncsendV2Request.Msg();
+            msg.setMsgtype("image");
+            msg.setImage(new OapiMessageCorpconversationAsyncsendV2Request.Image());
+            msg.getImage().setMediaId(mediaId);
+            return msg;
+        }
+    }
+
+    class File implements AbstractMessageBody {
+        /**
+         * 文件id,可以调用上传临时素材接口获取
+         */
+        private final String mediaId;
+
+        public File(String mediaId) {
+            this.mediaId = mediaId;
+        }
+
+        @Override
+        public OapiMessageCorpconversationAsyncsendV2Request.Msg build() {
+            OapiMessageCorpconversationAsyncsendV2Request.Msg msg = new OapiMessageCorpconversationAsyncsendV2Request.Msg();
+            msg.setMsgtype("file");
+            msg.setImage(new OapiMessageCorpconversationAsyncsendV2Request.Image());
+            msg.getImage().setMediaId(mediaId);
+            return msg;
+        }
+    }
+
+    class ActionCard implements AbstractMessageBody {
+        private final String title;
+        private final String description;
+        private final String url;
+
+        public ActionCard(String title, String description, String url) {
+            this.title = title;
+            this.description = description;
+            this.url = url;
+        }
+
+        @Override
+        public OapiMessageCorpconversationAsyncsendV2Request.Msg build() {
+            OapiMessageCorpconversationAsyncsendV2Request.Msg msg = new OapiMessageCorpconversationAsyncsendV2Request.Msg();
+            msg.setActionCard(new OapiMessageCorpconversationAsyncsendV2Request.ActionCard());
+            msg.getActionCard().setTitle(title);
+            msg.getActionCard().setMarkdown(description);
+            msg.getActionCard().setSingleTitle(title);
+            msg.getActionCard().setSingleUrl(url);
+            msg.setMsgtype("action_card");
+            return msg;
+        }
+    }
+
+    class Link implements AbstractMessageBody {
+        private final String title;
+        private final String text;
+        private final String messageUrl;
+        private final String picUrl;
+
+        public Link(String title, String text, String messageUrl, String picUrl) {
+            this.title = title;
+            this.text = text;
+            this.messageUrl = messageUrl;
+            this.picUrl = picUrl;
+        }
+
+        @Override
+        public OapiMessageCorpconversationAsyncsendV2Request.Msg build() {
+            OapiMessageCorpconversationAsyncsendV2Request.Msg msg = new OapiMessageCorpconversationAsyncsendV2Request.Msg();
+            msg.setMsgtype("link");
+            msg.setLink(new OapiMessageCorpconversationAsyncsendV2Request.Link());
+            msg.getLink().setTitle(title);
+            msg.getLink().setText(text);
+            msg.getLink().setMessageUrl(messageUrl);
+            msg.getLink().setPicUrl(picUrl);
+            return msg;
+        }
+    }
+
+    class Markdown implements AbstractMessageBody {
+        private final String title;
+        private final String text;
+
+        public Markdown(String title, String text) {
+            this.title = title;
+            this.text = text;
+        }
+
+        @Override
+        public OapiMessageCorpconversationAsyncsendV2Request.Msg build() {
+            OapiMessageCorpconversationAsyncsendV2Request.Msg msg = new OapiMessageCorpconversationAsyncsendV2Request.Msg();
+            msg.setMsgtype("markdown");
+            msg.setMarkdown(new OapiMessageCorpconversationAsyncsendV2Request.Markdown());
+            msg.getMarkdown().setText(text);
+            msg.getMarkdown().setTitle(title);
+            return msg;
+        }
+    }
+}

+ 25 - 0
dingtalk-sdk/src/main/java/com/usoftchina/dingtalk/sdk/exception/DingTalkInvokeException.java

@@ -0,0 +1,25 @@
+package com.usoftchina.dingtalk.sdk.exception;
+
+import com.taobao.api.ApiException;
+import com.taobao.api.TaobaoResponse;
+
+/**
+ * @author yingp
+ */
+public class DingTalkInvokeException extends RuntimeException {
+    private final String code;
+
+    public DingTalkInvokeException(TaobaoResponse resp) {
+        super(resp.getMsg());
+        this.code = resp.getErrorCode();
+    }
+
+    public DingTalkInvokeException(ApiException e) {
+        super(e.getErrMsg());
+        this.code = e.getErrCode();
+    }
+
+    public String getCode() {
+        return code;
+    }
+}

+ 165 - 0
dingtalk-sdk/src/main/java/com/usoftchina/dingtalk/sdk/util/DateUtils.java

@@ -0,0 +1,165 @@
+package com.usoftchina.dingtalk.sdk.util;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.*;
+import java.time.temporal.TemporalAdjusters;
+import java.time.temporal.TemporalField;
+import java.time.temporal.WeekFields;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Pattern;
+
+/**
+ * @author yingp
+ * @date 2019/9/11
+ */
+public class DateUtils {
+
+    private final static Map<String, DateFormat> formats = new ConcurrentHashMap<>();
+    private final static Pattern P_YMD = Pattern.compile("\\d{4}-\\d{2}-\\d{2}");
+    private final static Pattern P_YMD_HMS = Pattern.compile("\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}");
+    private final static Pattern P_YMD_HMS_TZ = Pattern.compile("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z");
+
+    private static DateFormat getFormat(String pattern, TimeZone timeZone) {
+        DateFormat format = formats.get(pattern + "_" + timeZone.getID());
+        if (null == format) {
+            format = new SimpleDateFormat(pattern);
+            format.setTimeZone(timeZone);
+            formats.put(pattern + "_" + timeZone.getID(), format);
+        }
+        return format;
+    }
+
+    public static String format(Date date, String pattern) {
+        return getFormat(pattern, TimeZone.getDefault()).format(date);
+    }
+
+    public static String format(Date date) {
+        return format(date, "yyyy-MM-dd");
+    }
+
+    public static String format(long time) {
+        return format(new Date(time));
+    }
+
+    public static String formatDateTime(Date date) {
+        return format(date, "yyyy-MM-dd HH:mm:ss");
+    }
+
+    public static String formatDateTime(long time) {
+        return formatDateTime(new Date(time));
+    }
+
+    public static Date parse(String date) {
+        try {
+            if (P_YMD.matcher(date).matches()) {
+                return getFormat("yyyy-MM-dd", TimeZone.getDefault()).parse(date);
+            } else if (P_YMD_HMS.matcher(date).matches()) {
+                return getFormat("yyyy-MM-dd HH:mm:ss", TimeZone.getDefault()).parse(date);
+            } else if (P_YMD_HMS_TZ.matcher(date).matches()) {
+                return getFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone("UTC")).parse(date);
+            }
+        } catch (ParseException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * 今天日期开始(00:00:00
+     *
+     * @return
+     */
+    public static Date nowDateStart() {
+        return Date.from(LocalDate.now().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    /**
+     * 今天日期截止(23:59:59
+     *
+     * @return
+     */
+    public static Date nowDateEnd() {
+        return Date.from(LocalDateTime.of(LocalDate.now(), LocalTime.MAX)
+                .atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    /**
+     * 今天(含时间)
+     *
+     * @return
+     */
+    public static Date nowDateTime() {
+        return new Date();
+    }
+
+    /**
+     * 日期起始(00:00:00
+     *
+     * @return
+     */
+    public static Date startOfDate(Date date) {
+        return Date.from(date.toInstant().atZone(ZoneId.systemDefault())
+                .toLocalDate().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    /**
+     * 日期截止(23:59:59
+     *
+     * @return
+     */
+    public static Date endOfDate(Date date) {
+        return Date.from(
+                LocalDateTime.of(date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), LocalTime.MAX)
+                        .atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    /**
+     * 周一
+     *
+     * @return
+     */
+    public static Date nowWeekStart() {
+        LocalDate now = LocalDate.now();
+        TemporalField fieldISO = WeekFields.of(Locale.FRANCE).dayOfWeek();
+        return Date.from(now.with(fieldISO, 1).atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    /**
+     * 周天
+     *
+     * @return
+     */
+    public static Date nowWeekEnd() {
+        LocalDate now = LocalDate.now();
+        TemporalField fieldISO = WeekFields.of(Locale.FRANCE).dayOfWeek();
+        return Date.from(
+                LocalDateTime.of(now.with(fieldISO, 7), LocalTime.MAX)
+                        .atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    /**
+     * 月初
+     *
+     * @return
+     */
+    public static Date nowMonthStart() {
+        LocalDate date = LocalDate.now().with(TemporalAdjusters.firstDayOfMonth());
+        return Date.from(date.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    /**
+     * 月末
+     *
+     * @return
+     */
+    public static Date nowMonthEnd() {
+        LocalDate date = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth());
+        return Date.from(LocalDateTime.of(date, LocalTime.MAX).atZone(ZoneId.systemDefault()).toInstant());
+    }
+}

+ 44 - 0
dingtalk-sdk/src/main/java/com/usoftchina/dingtalk/sdk/util/UrlUtils.java

@@ -0,0 +1,44 @@
+package com.usoftchina.dingtalk.sdk.util;
+
+import java.util.Base64;
+import java.util.regex.Pattern;
+
+/**
+ * @author yingp
+ */
+public class UrlUtils {
+
+    private final static Pattern urlPattern = Pattern.compile("https{0,1}://[^\\x{4e00}-\\x{9fa5}\\n\\r\\s]{3,}");
+
+    /**
+     * 生成获取授权,自动登录后跳转实际界面的auth链接
+     *
+     * @param corpId       公司ID
+     * @param master       账套
+     * @param agentCode    应用编号
+     * @param agentBaseUrl 应用主页
+     * @param authUrl      授权url
+     * @param redirectUrl  跳转页
+     * @return
+     * @throws Exception
+     */
+    public static String generateAuthUrl(String corpId, String master, String agentCode, String agentBaseUrl, String authUrl, String redirectUrl) throws Exception {
+        if (!urlPattern.matcher(redirectUrl).matches()) {
+            if (agentBaseUrl.endsWith("/")) {
+                if (redirectUrl.startsWith("/")) {
+                    redirectUrl = agentBaseUrl + redirectUrl.substring(1);
+                } else {
+                    redirectUrl = agentBaseUrl + redirectUrl;
+                }
+            } else {
+                if (redirectUrl.startsWith("/")) {
+                    redirectUrl = agentBaseUrl + redirectUrl;
+                } else {
+                    redirectUrl = agentBaseUrl + "/" + redirectUrl;
+                }
+            }
+        }
+        String base64Url = Base64.getEncoder().encodeToString(redirectUrl.getBytes());
+        return String.format("%s?corpId=%s&agent=%s&url=%s&master=%s", authUrl, corpId, agentCode, base64Url, master);
+    }
+}

+ 14 - 0
dingtalk-sdk/src/test/java/com/usoftchina/dingtalk/sdk/test/UrlTest.java

@@ -0,0 +1,14 @@
+package com.usoftchina.dingtalk.sdk.test;
+
+import com.usoftchina.dingtalk.sdk.util.UrlUtils;
+
+/**
+ * @author yingp
+ * @date 2020/2/18
+ */
+public class UrlTest {
+    public static void main(String[] args) throws Exception {
+        System.out.println(UrlUtils.generateAuthUrl("ding0fb1b0cd2510faf7ffe93478753d9884", "N_USOFTSYS", "Uas", "http://2788e5c924.qicp.vip/uas/",
+                "http://2788e5c924.qicp.vip/office/authorize", "/"));
+    }
+}

+ 2 - 1
settings.gradle

@@ -1,8 +1,9 @@
 rootProject.name = 'uas-office-integartion'
 include 'qywx-sdk'
-include 'uas-office-server'
+include 'uas-office-qywx-server'
 include 'uas-office-core'
 include 'uas-office-qywx'
 include 'dingtalk-sdk'
 include 'uas-office-dingtalk'
+include 'uas-office-dingtalk-server'
 

+ 1 - 1
uas-office-core/src/main/java/com/usoftchina/uas/office/config/RedisConfig.java → uas-office-core/src/main/java/com/usoftchina/uas/office/dingtalk/config/RedisConfig.java

@@ -1,4 +1,4 @@
-package com.usoftchina.uas.office.config;
+package com.usoftchina.uas.office.dingtalk.config;
 
 import com.usoftchina.uas.office.listener.UasEventListenerAdapter;
 import org.springframework.context.annotation.Bean;

+ 12 - 0
uas-office-dingtalk-server/build.gradle

@@ -0,0 +1,12 @@
+apply plugin: 'org.springframework.boot'
+
+dependencies {
+    compile project(':uas-office-dingtalk')
+    compile 'org.springframework.boot:spring-boot-starter-web'
+    compile 'org.springframework.boot:spring-boot-starter-jdbc'
+    compile 'org.springframework.boot:spring-boot-starter-security'
+    compile 'org.springframework.boot:spring-boot-starter-data-redis'
+    compile 'com.h2database:h2'
+    compile "$ojdbc"
+    testCompile 'org.springframework.boot:spring-boot-starter-test'
+}

+ 21 - 0
uas-office-dingtalk-server/src/main/java/com/usoftchina/uas/office/dingtalk/UasOfficeServer.java

@@ -0,0 +1,21 @@
+package com.usoftchina.uas.office.dingtalk;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+/**
+ * @author yingp
+ * @date 2020/2/18
+ */
+@SpringBootApplication(scanBasePackages = "com.usoftchina.uas.office")
+@EnableAsync
+@EnableScheduling
+@EnableTransactionManagement
+public class UasOfficeServer {
+    public static void main(String[] args) {
+        SpringApplication.run(UasOfficeServer.class, args);
+    }
+}

+ 28 - 0
uas-office-dingtalk-server/src/main/java/com/usoftchina/uas/office/dingtalk/config/ExceptionConfig.java

@@ -0,0 +1,28 @@
+package com.usoftchina.uas.office.dingtalk.config;
+
+import com.usoftchina.uas.office.dto.Result;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * @author Pro1
+ * @date 2017/6/22.
+ */
+@ControllerAdvice
+public class ExceptionConfig {
+
+    private final Logger logger = LoggerFactory.getLogger(ExceptionConfig.class);
+
+    @ExceptionHandler(value = Exception.class)
+    @ResponseBody
+    public Result defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
+        logger.error("request error, req: " + req.getRequestURI(), e);
+        return Result.error(e.getMessage());
+    }
+
+}

+ 38 - 0
uas-office-dingtalk-server/src/main/java/com/usoftchina/uas/office/dingtalk/config/WebMvcConfig.java

@@ -0,0 +1,38 @@
+package com.usoftchina.uas.office.dingtalk.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpHeaders;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * @author yingp
+ * @date 2018/11/7
+ */
+@Configuration
+public class WebMvcConfig implements WebMvcConfigurer {
+
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/**")
+                .allowedOrigins("*")
+                .allowedMethods("*")
+                .allowedHeaders("*")
+                .allowCredentials(true)
+                .exposedHeaders(HttpHeaders.SET_COOKIE).maxAge(3600L);
+    }
+
+    @Override
+    public void addViewControllers(ViewControllerRegistry registry) {
+        registry.addViewController("/index").setViewName("dc.html");
+        registry.addViewController("/").setViewName("dc.html");
+        registry.addViewController("/login").setViewName("/login.html");
+        registry.addViewController("/agent").setViewName("agent.html");
+        registry.addViewController("/addrbook").setViewName("addrbook.html");
+        registry.addViewController("/corp").setViewName("corp.html");
+        registry.addViewController("/authorize").setViewName("authorize.html");
+    }
+}
+
+

+ 60 - 0
uas-office-dingtalk-server/src/main/java/com/usoftchina/uas/office/dingtalk/config/WebSecurityConfig.java

@@ -0,0 +1,60 @@
+package com.usoftchina.uas.office.dingtalk.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+/**
+ * @author yingp
+ * @date 2019/3/11
+ */
+@Configuration
+@EnableWebSecurity
+public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
+    @Override
+    public void configure(WebSecurity web) throws Exception {
+        web.ignoring().antMatchers("/resources/**", "/static/**", "/public/**",
+                "/html/**", "/css/**", "/js/**", "**/*.css", "**/*.js", "/api/**", "/authorize");
+    }
+
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+        http.authorizeRequests()
+                .antMatchers("/login")
+                .permitAll()
+                .anyRequest()
+                .authenticated()
+                .and()
+                .formLogin()
+                .loginPage("/login")
+                .and()
+                .csrf()
+                .disable()
+                .sessionManagement()
+                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
+    }
+
+    @Autowired
+    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
+        auth.inMemoryAuthentication().passwordEncoder(new PlainTextPasswordEncoder())
+                .withUser("admin").password("select111***").roles("ADMIN");
+    }
+
+    class PlainTextPasswordEncoder implements PasswordEncoder {
+        @Override
+        public String encode(CharSequence rawPassword) {
+            return rawPassword.toString();
+        }
+
+        @Override
+        public boolean matches(CharSequence rawPassword, String encodedPassword) {
+            return encodedPassword.equals(rawPassword.toString());
+        }
+    }
+}

+ 0 - 0
uas-office-server/src/main/resources/application.yaml → uas-office-dingtalk-server/src/main/resources/application.yaml


+ 0 - 0
uas-office-server/src/main/resources/banner.txt → uas-office-dingtalk-server/src/main/resources/banner.txt


+ 4 - 0
uas-office-dingtalk-server/src/main/resources/config/application-dev.yaml

@@ -0,0 +1,4 @@
+spring:
+  redis:
+    host: 10.1.81.2
+    port: 6379

+ 80 - 0
uas-office-dingtalk-server/src/main/resources/erp_schema.json

@@ -0,0 +1,80 @@
+[
+  {
+    "name": "DingTalkSetting",
+    "columns": [
+      {
+        "name": "corp_id",
+        "type": "varchar2(100)"
+      },
+      {
+        "name": "checkin_time",
+        "type": "date"
+      }
+    ]
+  },
+  {
+    "name": "DingTalkAgent",
+    "columns": [
+      {
+        "name": "code",
+        "type": "varchar2(30)"
+      },
+      {
+        "name": "agent_id",
+        "type": "number(15)"
+      },
+      {
+        "name": "description",
+        "type": "varchar2(30)"
+      },
+      {
+        "name": "app_key",
+        "type": "varchar2(100)"
+      },
+      {
+        "name": "app_secret",
+        "type": "varchar2(100)"
+      },
+      {
+        "name": "outer_url",
+        "type": "varchar2(255)"
+      }
+    ]
+  },
+  {
+    "name": "employee",
+    "columns": [
+      {
+        "name": "em_ding",
+        "type": "varchar2(100)"
+      }
+    ]
+  },
+  {
+    "name": "hrorg",
+    "columns": [
+      {
+        "name": "or_ding",
+        "type": "number(15)"
+      }
+    ]
+  },
+  {
+    "name": "MeetingRoomApply",
+    "columns": [
+      {
+        "name": "ma_scheduleid",
+        "type": "varchar2(64)"
+      }
+    ]
+  },
+  {
+    "name": "Agenda",
+    "columns": [
+      {
+        "name": "ag_scheduleid",
+        "type": "varchar2(64)"
+      }
+    ]
+  }
+]

+ 0 - 0
uas-office-server/src/main/resources/icon.ico → uas-office-dingtalk-server/src/main/resources/icon.ico


+ 0 - 0
uas-office-server/src/main/resources/logback-spring.xml → uas-office-dingtalk-server/src/main/resources/logback-spring.xml


+ 0 - 0
uas-office-server/src/main/resources/schema.sql → uas-office-dingtalk-server/src/main/resources/schema.sql


+ 0 - 0
uas-office-server/src/main/resources/static/addrbook.html → uas-office-dingtalk-server/src/main/resources/static/addrbook.html


+ 150 - 0
uas-office-dingtalk-server/src/main/resources/static/agent.html

@@ -0,0 +1,150 @@
+<!DOCTYPE HTML>
+<html lang="zh-CN">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+    <title>UAS在线办公</title>
+    <link rel="stylesheet" href="css/bootstrap.min.css">
+    <link rel="stylesheet" href="css/iconfont.css">
+</head>
+<body>
+<nav class="navbar navbar-expand-lg navbar-light bg-light">
+    <a class="navbar-brand" href="#">UAS在线办公管理</a>
+
+    <div class="collapse navbar-collapse">
+        <ul class="navbar-nav mr-auto">
+            <li class="nav-item">
+                <a class="nav-link" href=".">数据中心</a>
+            </li>
+            <li class="nav-item active">
+                <a class="nav-link" href="agent">应用管理</a>
+            </li>
+            <li class="nav-item">
+                <a class="nav-link" href="addrbook">通讯录管理</a>
+            </li>
+            <li class="nav-item">
+                <a class="nav-link" href="corp">企业设置</a>
+            </li>
+        </ul>
+    </div>
+
+    <div>
+        <button class="btn btn-outline-success" id="addBtn"
+                data-toggle="modal" data-target="#formModal">添加应用</button>
+    </div>
+</nav>
+<!-- Modal -->
+<div class="modal fade" id="formModal" tabindex="-1" role="dialog" aria-labelledby="formModalTitle" aria-hidden="true">
+    <div class="modal-dialog modal-dialog-scrollable" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title" id="formModalTitle">应用参数</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <form id="form">
+                    <input type="hidden" id="idInput" name="id">
+                    <div class="form-group">
+                        <label for="codeInput">编号</label>
+                        <input type="text" class="form-control" id="codeInput" name="code" required
+                               aria-describedby="codeHelp">
+                    </div>
+                    <div class="form-group">
+                        <label for="descriptionInput">描述</label>
+                        <input type="text" class="form-control" id="descriptionInput" name="description" required
+                               aria-describedby="descriptionHelp">
+                    </div>
+                    <div class="form-group">
+                        <label for="agentIdInput">应用ID</label>
+                        <input type="text" class="form-control" id="agentIdInput" name="agentId" required
+                               aria-describedby="agentIdHelp">
+                    </div>
+                    <div class="form-group">
+                        <label for="appKeyInput">appKey</label>
+                        <input type="text" class="form-control" id="appKeyInput" name="appKey" required
+                               aria-describedby="appKeyHelp">
+                    </div>
+                    <div class="form-group">
+                        <label for="appSecretInput">appSecret</label>
+                        <input type="text" class="form-control" id="appSecretInput" name="appSecret"
+                               required aria-describedby="appSecretHelp">
+                    </div>
+                    <div class="form-group">
+                        <label for="outerUrlInput">外网地址</label>
+                        <input type="text" class="form-control" id="outerUrlInput" name="outerUrl"
+                               required aria-describedby="outerUrlHelp">
+                    </div>
+                </form>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-primary btn-save">保存</button>
+            </div>
+        </div>
+    </div>
+</div>
+<div class="modal fade" id="alertModal" tabindex="-1" role="dialog" aria-labelledby="alertModalTitle" aria-hidden="true">
+    <div class="modal-dialog modal-dialog-scrollable" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title" id="alertModalTitle">提示</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <textarea readonly class="form-control alert-content" rows="7" autofocus style="width: 100%"></textarea>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>
+
+<table class="table">
+    <thead class="thead-light">
+    <tr>
+        <th scope="col">编号</th>
+        <th scope="col">描述</th>
+        <th scope="col">应用ID</th>
+        <th scope="col">appKey</th>
+        <th scope="col">appSecret</th>
+        <th scope="col">外网地址</th>
+        <th scope="col">操作</th>
+    </tr>
+    </thead>
+    <tbody>
+    </tbody>
+</table>
+<script id="template-html" type="text/html">
+    <%for(var i in data){%>
+    <tr>
+        <td><%=data[i].code%></td>
+        <td><%=data[i].description%></td>
+        <td><%=data[i].agentId || ''%></td>
+        <td><%=data[i].appKey || ''%></td>
+        <td><%=data[i].appSecret || ''%></td>
+        <td><%=data[i].outerUrl || ''%></td>
+        <td>
+            <button type="button" class="btn btn-link btn-edit" data-index="<%=i%>" title="编辑"
+                    data-toggle="modal" data-target="#formModal">
+                <span class="iconfont icon-edit"></span>
+            </button>
+            <button type="button" class="btn btn-link btn-delete" data-index="<%=i%>" title="删除">
+                <span class="iconfont icon-delete"></span>
+            </button>
+            <button type="button" class="btn btn-link btn-auth" data-index="<%=i%>" title="创建授权链接">
+                <span class="iconfont icon-link"></span>
+            </button>
+        </td>
+    </tr>
+    <%}%>
+</script>
+<script src="js/jquery.min.js"></script>
+<script src="js/bootstrap.min.js"></script>
+<script src="js/template.min.js"></script>
+<script src="js/agent.js"></script>
+</body>

+ 67 - 0
uas-office-dingtalk-server/src/main/resources/static/authorize.html

@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html lang="zh-CN">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+    <title>UAS在线办公</title>
+    <style>
+        .loading {
+            position: absolute;
+            top: 50%;
+            left: 50%;
+            margin: -80px 0 0 -40px;
+            width: 80px;
+            height: 80px;
+            border: 3px solid #D0D3D4;
+            border-top-color: #626567;
+            border-radius: 50%;
+            animation: spin 1.5s linear infinite;
+        }
+
+        @keyframes spin {
+            0% {
+                transform: rotate(0deg);
+            }
+
+            100% {
+                transform: rotate(360deg);
+            }
+        }
+    </style>
+</head>
+<body>
+<div class="loading"></div>
+<script src="https://g.alicdn.com/dingding/dingtalk-jsapi/2.7.13/dingtalk.open.js"></script>
+<script type="application/javascript">
+    const param = function(variable) {
+        const query = window.location.search.substring(1);
+        const vars = query.split("&");
+        for (let i = 0; i < vars.length; i++) {
+            const pair = vars[i].split("=");
+            if (pair[0] == variable){
+                return pair[1];
+            }
+        }
+        return '';
+    };
+    const getAuthCode = function(corpId) {
+        return new Promise(function(resolve, reject){
+            dd.runtime.permission.requestAuthCode({
+                corpId: corpId,
+                onSuccess: function(result) {
+                    resolve(result.code);
+                },
+                onFail : function(err) {
+                    reject(err);
+                }
+            });
+        });
+    };
+    dd.ready(function() {
+        getAuthCode(param('corpId')).then((code) => {
+            window.location.href = `api/authorize?code=${code}&url=${param('url')}&agent=${param('agent')}&master=${param('master')}`;
+        });
+    });
+</script>
+</body>
+</html>

+ 49 - 0
uas-office-dingtalk-server/src/main/resources/static/corp.html

@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html lang="zh-CN">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+    <title>UAS在线办公</title>
+    <link rel="stylesheet" href="css/bootstrap.min.css">
+</head>
+<body>
+<nav class="navbar navbar-expand-lg navbar-light bg-light">
+    <a class="navbar-brand" href="#">UAS在线办公管理</a>
+
+    <div class="collapse navbar-collapse">
+        <ul class="navbar-nav mr-auto">
+            <li class="nav-item">
+                <a class="nav-link" href=".">数据中心</a>
+            </li>
+            <li class="nav-item">
+                <a class="nav-link" href="agent">应用管理</a>
+            </li>
+            <li class="nav-item">
+                <a class="nav-link" href="addrbook">通讯录管理</a>
+            </li>
+            <li class="nav-item active">
+                <a class="nav-link" href="corp">企业设置</a>
+            </li>
+        </ul>
+    </div>
+</nav>
+<div class="container">
+    <div class="row justify-content-md-center">
+        <div class="col-lg-auto" style="width: 800px;padding: 20px">
+            <form id="form">
+                <div class="form-group row">
+                    <label class="col-sm-2 col-form-label" for="corpIdInput">企业钉钉ID</label>
+                    <div class="col-sm-10">
+                        <input type="text" class="form-control" id="corpIdInput" name="corpId"
+                               required>
+                    </div>
+                </div>
+                <button type="button" class="btn btn-primary btn-save">保存</button>
+            </form>
+        </div>
+    </div>
+</div>
+<script src="js/jquery.min.js"></script>
+<script src="js/bootstrap.min.js"></script>
+<script src="js/corp.js"></script>
+</body>

+ 0 - 0
uas-office-server/src/main/resources/static/css/bootstrap.min.css → uas-office-dingtalk-server/src/main/resources/static/css/bootstrap.min.css


+ 0 - 0
uas-office-server/src/main/resources/static/css/iconfont.css → uas-office-dingtalk-server/src/main/resources/static/css/iconfont.css


+ 0 - 0
uas-office-server/src/main/resources/static/css/iconfont.eot → uas-office-dingtalk-server/src/main/resources/static/css/iconfont.eot


+ 0 - 0
uas-office-server/src/main/resources/static/css/iconfont.svg → uas-office-dingtalk-server/src/main/resources/static/css/iconfont.svg


+ 0 - 0
uas-office-server/src/main/resources/static/css/iconfont.ttf → uas-office-dingtalk-server/src/main/resources/static/css/iconfont.ttf


+ 0 - 0
uas-office-server/src/main/resources/static/css/iconfont.woff → uas-office-dingtalk-server/src/main/resources/static/css/iconfont.woff


+ 0 - 0
uas-office-server/src/main/resources/static/css/iconfont.woff2 → uas-office-dingtalk-server/src/main/resources/static/css/iconfont.woff2


+ 0 - 0
uas-office-server/src/main/resources/static/css/login.css → uas-office-dingtalk-server/src/main/resources/static/css/login.css


+ 0 - 0
uas-office-server/src/main/resources/static/dc.html → uas-office-dingtalk-server/src/main/resources/static/dc.html


+ 35 - 0
uas-office-dingtalk-server/src/main/resources/static/js/addrbook.js

@@ -0,0 +1,35 @@
+$(document).ready(function () {
+    var app = {
+        methods: {
+            setLoading: function (load) {
+                if (load) {
+                    $('#loadingModal').modal({backdrop: 'static', keyboard: false});
+                } else {
+                    $('#loadingModal').hide();
+                }
+            },
+            sync: function () {
+                app.methods.setLoading(true);
+                $.ajax({
+                    url: 'mgm/addrbook/sync',
+                    method: 'POST',
+                    success: function (content) {
+                        app.methods.setLoading(false);
+                        if (content.success) {
+                            alert('同步成功');
+                        } else {
+                            alert(content.message);
+                        }
+                    }
+                });
+            }
+        },
+        init: function () {
+            $('.btn-primary').click(function () {
+                app.methods.sync();
+            });
+        }
+    };
+
+    app.init();
+});

+ 100 - 0
uas-office-dingtalk-server/src/main/resources/static/js/agent.js

@@ -0,0 +1,100 @@
+$(document).ready(function () {
+    var app = {
+        data: {
+            agents: []
+        },
+        methods: {
+            getList: function () {
+                $.ajax({
+                    url: 'mgm/agent',
+                    method: 'GET',
+                    success: function (content) {
+                        if (content.success) {
+                            app.data.agents = content.data;
+                            $('.table tbody').html(template($('#template-html').text(), content));
+                            $('.btn-edit').click(function(){
+                                var index = $(this).data('index'),
+                                    agent = app.data.agents[index];
+                                $('#codeInput').val(agent.code);
+                                $('#descriptionInput').val(agent.description);
+                                $('#agentIdInput').val(agent.agentId);
+                                $('#appKeyInput').val(agent.appKey);
+                                $('#appSecretInput').val(agent.appSecret);
+                                $('#outerUrlInput').val(agent.outerUrl);
+                            });
+                            $('.btn-delete').click(function(){
+                                var index = $(this).data('index'),
+                                    agent = app.data.agents[index];
+                                app.methods.remove(agent.code);
+                            });
+                            $('.btn-auth').click(function(){
+                                var index = $(this).data('index'),
+                                    agent = app.data.agents[index];
+                                app.methods.getAuthUrl(agent.code);
+                            });
+                        } else {
+                            alert(content.message);
+                        }
+                    }
+                });
+            },
+            save: function () {
+                $.ajax({
+                    url: 'mgm/agent',
+                    method: 'POST',
+                    data: $('#form').serialize(),
+                    success: function (content) {
+                        if (content.success) {
+                            window.location.reload();
+                        } else {
+                            alert(content.message);
+                        }
+                    }
+                });
+            },
+            remove: function (code) {
+                $.ajax({
+                    url: 'mgm/agent',
+                    params: {
+                      code: code
+                    },
+                    method: 'DELETE',
+                    success: function (content) {
+                        if (content.success) {
+                            window.location.reload();
+                        } else {
+                            alert(content.message);
+                        }
+                    }
+                });
+            },
+            getAuthUrl: function (code) {
+                $.ajax({
+                    url: 'mgm/agent/auth_url?code=' + code,
+                    method: 'GET',
+                    success: function (content) {
+                        if (content.success) {
+                            app.methods.alert(code + '授权地址', content.data);
+                        } else {
+                            alert(content.message);
+                        }
+                    }
+                });
+            },
+            alert: function(title, content) {
+                $('#alertModal .modal-title').html(title);
+                $('#alertModal .alert-content').val(content);
+                $('#alertModal').modal();
+            }
+        },
+        init: function () {
+            $('.btn-save').click(function(){
+                app.methods.save();
+            });
+
+            app.methods.getList();
+        }
+    };
+
+    app.init();
+});

+ 0 - 0
uas-office-server/src/main/resources/static/js/bootstrap.min.js → uas-office-dingtalk-server/src/main/resources/static/js/bootstrap.min.js


+ 0 - 0
uas-office-server/src/main/resources/static/js/corp.js → uas-office-dingtalk-server/src/main/resources/static/js/corp.js


+ 0 - 0
uas-office-server/src/main/resources/static/js/dc.js → uas-office-dingtalk-server/src/main/resources/static/js/dc.js


+ 0 - 0
uas-office-server/src/main/resources/static/js/jquery.min.js → uas-office-dingtalk-server/src/main/resources/static/js/jquery.min.js


+ 0 - 0
uas-office-server/src/main/resources/static/js/template.min.js → uas-office-dingtalk-server/src/main/resources/static/js/template.min.js


+ 0 - 0
uas-office-server/src/main/resources/static/login.html → uas-office-dingtalk-server/src/main/resources/static/login.html


+ 71 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/config/DingTalkConfig.java

@@ -0,0 +1,71 @@
+package com.usoftchina.uas.office.dingtalk.config;
+
+import com.usoftchina.dingtalk.sdk.*;
+import com.usoftchina.dingtalk.sdk.config.Agent;
+import com.usoftchina.dingtalk.sdk.config.DingTalkProperties;
+import com.usoftchina.uas.office.dingtalk.manage.entity.DingTalkAgent;
+import com.usoftchina.uas.office.dingtalk.manage.event.DingTalkAgentEvent;
+import com.usoftchina.uas.office.dingtalk.manage.event.DingTalkCorpEvent;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.event.EventListener;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author yingp
+ * @date 2020/2/19
+ */
+@Configuration
+public class DingTalkConfig {
+    @Bean
+    public DingTalkProperties dingTalkProperties() {
+        return new DingTalkProperties();
+    }
+
+    @Bean
+    public AddrBookSdk addrBookSdk() {
+        return new AddrBookSdk(dingTalkProperties());
+    }
+
+    @Bean
+    public MessageSdk messageSdk() {
+        return new MessageSdk(dingTalkProperties());
+    }
+
+    @Bean
+    public OaSdk oaSdk() {
+        return new OaSdk(dingTalkProperties());
+    }
+
+    @Bean
+    public ScheduleSdk scheduleSdk() {
+        return new ScheduleSdk(dingTalkProperties());
+    }
+
+    @Bean
+    public WorkRecordSdk workRecordSdk() {
+        return new WorkRecordSdk(dingTalkProperties());
+    }
+
+    @Autowired
+    private DingTalkProperties dingTalkProperties;
+
+    @EventListener(DingTalkCorpEvent.class)
+    public void onDingTalkCorpEvent(DingTalkCorpEvent event) {
+        dingTalkProperties.setCorpId(event.getCorpId());
+    }
+
+    @EventListener(DingTalkAgentEvent.class)
+    public void onDingTalkAgentEvent(DingTalkAgentEvent event) {
+        List<DingTalkAgent> agents = event.getAgents();
+        if (null != agents) {
+            dingTalkProperties.setAgents(agents.stream().map(agent -> new Agent(agent.getCode(), agent.getAgentId(), agent.getAppKey(), agent.getAppSecret()))
+                    .collect(Collectors.toList()));
+        } else {
+            dingTalkProperties.setAgents(null);
+        }
+    }
+}

+ 80 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/controller/DingTalkAuthController.java

@@ -0,0 +1,80 @@
+package com.usoftchina.uas.office.dingtalk.controller;
+
+import com.dingtalk.api.response.OapiUserGetResponse;
+import com.dingtalk.api.response.OapiUserGetuserinfoResponse;
+import com.usoftchina.dingtalk.sdk.AddrBookSdk;
+import com.usoftchina.uas.office.dingtalk.entity.Employee;
+import com.usoftchina.uas.office.dingtalk.manage.entity.DingTalkAgent;
+import com.usoftchina.uas.office.dingtalk.manage.service.DingTalkAgentService;
+import com.usoftchina.uas.office.dingtalk.service.UasEmployeeService;
+import com.usoftchina.uas.office.entity.DataCenter;
+import com.usoftchina.uas.office.jdbc.DataSourceHolder;
+import com.usoftchina.uas.office.util.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.time.Duration;
+
+/**
+ * @author yingp
+ * @date 2020/2/18
+ */
+@Controller
+public class DingTalkAuthController {
+
+    @Autowired
+    private RedisTemplate redisTemplate;
+
+    @Autowired
+    private AddrBookSdk userSdk;
+
+    @Autowired
+    private UasEmployeeService employeeService;
+
+    @Autowired
+    private DingTalkAgentService agentService;
+
+    @RequestMapping(value = "/api/authorize")
+    public ModelAndView authorize(@RequestParam(value = "code") String code, @RequestParam(value = "agent") String agent,
+                                  @RequestParam(value = "master", required = false) String master,
+                                  @RequestParam(value = "url") String url, HttpServletResponse response) throws IOException {
+        DataCenter dataCenter = DataCenter.INSTANCE;
+        if (null == dataCenter) {
+            return new ModelAndView("/error");
+        }
+        OapiUserGetuserinfoResponse infoResp = userSdk.getUserInfo(agent, code);
+        try {
+            DataSourceHolder.set(dataCenter);
+            Employee employee = employeeService.getByDingUserId(infoResp.getUserid());
+            if (null == employee) {
+                OapiUserGetResponse userResp = userSdk.getUser(agent, infoResp.getUserid());
+                if (StringUtils.hasText(userResp.getMobile())) {
+                    employee = employeeService.getByMobile(userResp.getMobile());
+                }
+                if (null == employee && StringUtils.hasText(userResp.getEmail())) {
+                    employee = employeeService.getByEmail(userResp.getEmail());
+                }
+                if (null != employee) {
+                    employee.setEm_ding(infoResp.getUserid());
+                    employeeService.setDingUserId(employee);
+                }
+            }
+            if (null == employee) {
+                return new ModelAndView("/error");
+            }
+            DingTalkAgent uasAgent = agentService.findByCode("Uas");
+            redisTemplate.opsForValue().set(code, employee.getEm_id(), Duration.ofMinutes(1));
+            response.sendRedirect(String.format("%sopen/authorize.action?token=%s&master=%s&url=%s",
+                    uasAgent.getOuterUrl(), code, master == null ? "" : master, url));
+            return null;
+        } finally {
+            DataSourceHolder.clear();
+        }
+    }
+}

+ 33 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/controller/DingTalkMessageController.java

@@ -0,0 +1,33 @@
+package com.usoftchina.uas.office.dingtalk.controller;
+
+import com.usoftchina.dingtalk.sdk.MessageSdk;
+import com.usoftchina.dingtalk.sdk.dto.SendMessageReq;
+import com.usoftchina.uas.office.dto.Result;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author yingp
+ * @date 2020/2/19
+ */
+@RestController
+public class DingTalkMessageController {
+    @Autowired
+    private MessageSdk messageSdk;
+
+    /**
+     * 发送消息
+     *
+     * @param agent
+     * @param message
+     * @param user
+     * @return
+     */
+    @PostMapping(value = "/api/message/send")
+    public Result send(@RequestParam(value = "agent") String agent, String message, String user) {
+        messageSdk.send(agent, new SendMessageReq().text(message).toUser(user));
+        return Result.success();
+    }
+}

+ 82 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/entity/Agenda.java

@@ -0,0 +1,82 @@
+package com.usoftchina.uas.office.dingtalk.entity;
+
+import java.util.Date;
+
+/**
+ * @author yingp
+ * @date 2020/2/16
+ */
+public class Agenda {
+    private String ag_executor;
+    private Integer ag_id;
+    private String em_ding;
+    private Date ag_start;
+    private Date ag_end;
+    private String ag_title;
+    private String ag_content;
+    private String ag_scheduleid;
+
+    public String getAg_executor() {
+        return ag_executor;
+    }
+
+    public void setAg_executor(String ag_executor) {
+        this.ag_executor = ag_executor;
+    }
+
+    public Integer getAg_id() {
+        return ag_id;
+    }
+
+    public void setAg_id(Integer ag_id) {
+        this.ag_id = ag_id;
+    }
+
+    public String getEm_ding() {
+        return em_ding;
+    }
+
+    public void setEm_ding(String em_ding) {
+        this.em_ding = em_ding;
+    }
+
+    public Date getAg_start() {
+        return ag_start;
+    }
+
+    public void setAg_start(Date ag_start) {
+        this.ag_start = ag_start;
+    }
+
+    public Date getAg_end() {
+        return ag_end;
+    }
+
+    public void setAg_end(Date ag_end) {
+        this.ag_end = ag_end;
+    }
+
+    public String getAg_title() {
+        return ag_title;
+    }
+
+    public void setAg_title(String ag_title) {
+        this.ag_title = ag_title;
+    }
+
+    public String getAg_content() {
+        return ag_content;
+    }
+
+    public void setAg_content(String ag_content) {
+        this.ag_content = ag_content;
+    }
+
+    public String getAg_scheduleid() {
+        return ag_scheduleid;
+    }
+
+    public void setAg_scheduleid(String ag_scheduleid) {
+        this.ag_scheduleid = ag_scheduleid;
+    }
+}

+ 222 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/entity/CardLog.java

@@ -0,0 +1,222 @@
+package com.usoftchina.uas.office.dingtalk.entity;
+
+import com.dingtalk.api.response.OapiAttendanceListRecordResponse;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * @author yingp
+ * @date 2020/2/16
+ */
+public class CardLog {
+    private Integer cl_id;
+    private String cl_sourcetype;
+    private Date cl_time;
+    private String cl_cardcode;
+    private Integer cl_emid;
+    private String cl_emcode;
+    private String cl_emname;
+    private String cl_address;
+    private String cl_phone;
+    private String cl_location;
+    private String cl_groupname;
+    private String cl_checkintype;
+    private String cl_exceptiontype;
+    private String cl_wifiname;
+    private String cl_notes;
+    private String cl_wifimac;
+    private String cl_mediaids;
+    private Long cl_lat;
+    private Long cl_lng;
+
+    public Integer getCl_id() {
+        return cl_id;
+    }
+
+    public void setCl_id(Integer cl_id) {
+        this.cl_id = cl_id;
+    }
+
+    public String getCl_sourcetype() {
+        return cl_sourcetype;
+    }
+
+    public void setCl_sourcetype(String cl_sourcetype) {
+        this.cl_sourcetype = cl_sourcetype;
+    }
+
+    public Date getCl_time() {
+        return cl_time;
+    }
+
+    public void setCl_time(Date cl_time) {
+        this.cl_time = cl_time;
+    }
+
+    public String getCl_cardcode() {
+        return cl_cardcode;
+    }
+
+    public void setCl_cardcode(String cl_cardcode) {
+        this.cl_cardcode = cl_cardcode;
+    }
+
+    public Integer getCl_emid() {
+        return cl_emid;
+    }
+
+    public void setCl_emid(Integer cl_emid) {
+        this.cl_emid = cl_emid;
+    }
+
+    public String getCl_emcode() {
+        return cl_emcode;
+    }
+
+    public void setCl_emcode(String cl_emcode) {
+        this.cl_emcode = cl_emcode;
+    }
+
+    public String getCl_emname() {
+        return cl_emname;
+    }
+
+    public void setCl_emname(String cl_emname) {
+        this.cl_emname = cl_emname;
+    }
+
+    public String getCl_address() {
+        return cl_address;
+    }
+
+    public void setCl_address(String cl_address) {
+        this.cl_address = cl_address;
+    }
+
+    public String getCl_phone() {
+        return cl_phone;
+    }
+
+    public void setCl_phone(String cl_phone) {
+        this.cl_phone = cl_phone;
+    }
+
+    public String getCl_location() {
+        return cl_location;
+    }
+
+    public void setCl_location(String cl_location) {
+        this.cl_location = cl_location;
+    }
+
+    public String getCl_groupname() {
+        return cl_groupname;
+    }
+
+    public void setCl_groupname(String cl_groupname) {
+        this.cl_groupname = cl_groupname;
+    }
+
+    public String getCl_checkintype() {
+        return cl_checkintype;
+    }
+
+    public void setCl_checkintype(String cl_checkintype) {
+        this.cl_checkintype = cl_checkintype;
+    }
+
+    public String getCl_exceptiontype() {
+        return cl_exceptiontype;
+    }
+
+    public void setCl_exceptiontype(String cl_exceptiontype) {
+        this.cl_exceptiontype = cl_exceptiontype;
+    }
+
+    public String getCl_wifiname() {
+        return cl_wifiname;
+    }
+
+    public void setCl_wifiname(String cl_wifiname) {
+        this.cl_wifiname = cl_wifiname;
+    }
+
+    public String getCl_notes() {
+        return cl_notes;
+    }
+
+    public void setCl_notes(String cl_notes) {
+        this.cl_notes = cl_notes;
+    }
+
+    public String getCl_wifimac() {
+        return cl_wifimac;
+    }
+
+    public void setCl_wifimac(String cl_wifimac) {
+        this.cl_wifimac = cl_wifimac;
+    }
+
+    public String getCl_mediaids() {
+        return cl_mediaids;
+    }
+
+    public void setCl_mediaids(String cl_mediaids) {
+        this.cl_mediaids = cl_mediaids;
+    }
+
+    public Long getCl_lat() {
+        return cl_lat;
+    }
+
+    public void setCl_lat(Long cl_lat) {
+        this.cl_lat = cl_lat;
+    }
+
+    public Long getCl_lng() {
+        return cl_lng;
+    }
+
+    public void setCl_lng(Long cl_lng) {
+        this.cl_lng = cl_lng;
+    }
+
+    public CardLog(Employee employee, OapiAttendanceListRecordResponse.Recordresult record) {
+        this.cl_sourcetype = "DingTalk";
+        this.cl_time = record.getUserCheckTime();
+        this.cl_cardcode = employee.getEm_code();
+        this.cl_emid = employee.getEm_id();
+        this.cl_emcode = employee.getEm_code();
+        this.cl_emname = employee.getEm_name();
+        this.cl_address = record.getUserAddress();
+        this.cl_phone = employee.getEm_mobile();
+        this.cl_checkintype = "OnDuty".equals(record.getCheckType()) ? "上班打卡" : "下班打卡";
+        if (!"Normal".equals(record.getTimeResult())) {
+            switch (record.getTimeResult()) {
+                case "Early":
+                    this.cl_exceptiontype = "早退";
+                    break;
+                case "Late":
+                    this.cl_exceptiontype = "迟到";
+                    break;
+                case "SeriousLate":
+                    this.cl_exceptiontype = "严重迟到";
+                    break;
+                case "Absenteeism":
+                    this.cl_exceptiontype = "旷工迟到";
+                    break;
+                case "NotSigned":
+                    this.cl_exceptiontype = "未打卡";
+                    break;
+                default:
+                    break;
+            }
+        }
+        this.cl_wifiname = record.getUserSsid();
+        this.cl_notes = record.getOutsideRemark();
+        this.cl_wifimac = record.getUserMacAddr();
+        this.cl_lat = new BigDecimal(record.getUserLatitude()).multiply(BigDecimal.valueOf(1000000)).longValue();
+        this.cl_lng = new BigDecimal(record.getUserLongitude()).multiply(BigDecimal.valueOf(1000000)).longValue();
+    }
+}

+ 118 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/entity/Employee.java

@@ -0,0 +1,118 @@
+package com.usoftchina.uas.office.dingtalk.entity;
+
+/**
+ * UAS系统 人员资料表
+ *
+ * @author yingp
+ * @date 2020/2/15
+ */
+public class Employee {
+    private Integer em_id;
+    private String em_code;
+    private String em_name;
+    private String em_mobile;
+    private String em_email;
+    private String em_tel;
+    private String em_sex;
+    private Integer em_defaultorid;
+    private String em_position;
+    private String em_csshortname;
+    private String em_class;
+    private String em_ding;
+
+    public Integer getEm_id() {
+        return em_id;
+    }
+
+    public void setEm_id(Integer em_id) {
+        this.em_id = em_id;
+    }
+
+    public String getEm_code() {
+        return em_code;
+    }
+
+    public void setEm_code(String em_code) {
+        this.em_code = em_code;
+    }
+
+    public String getEm_name() {
+        return em_name;
+    }
+
+    public void setEm_name(String em_name) {
+        this.em_name = em_name;
+    }
+
+    public String getEm_mobile() {
+        return em_mobile;
+    }
+
+    public void setEm_mobile(String em_mobile) {
+        this.em_mobile = em_mobile;
+    }
+
+    public String getEm_email() {
+        return em_email;
+    }
+
+    public void setEm_email(String em_email) {
+        this.em_email = em_email;
+    }
+
+    public String getEm_tel() {
+        return em_tel;
+    }
+
+    public void setEm_tel(String em_tel) {
+        this.em_tel = em_tel;
+    }
+
+    public String getEm_sex() {
+        return em_sex;
+    }
+
+    public void setEm_sex(String em_sex) {
+        this.em_sex = em_sex;
+    }
+
+    public Integer getEm_defaultorid() {
+        return em_defaultorid;
+    }
+
+    public void setEm_defaultorid(Integer em_defaultorid) {
+        this.em_defaultorid = em_defaultorid;
+    }
+
+    public String getEm_position() {
+        return em_position;
+    }
+
+    public void setEm_position(String em_position) {
+        this.em_position = em_position;
+    }
+
+    public String getEm_csshortname() {
+        return em_csshortname;
+    }
+
+    public void setEm_csshortname(String em_csshortname) {
+        this.em_csshortname = em_csshortname;
+    }
+
+    public String getEm_class() {
+        return em_class;
+    }
+
+    public void setEm_class(String em_class) {
+        this.em_class = em_class;
+    }
+
+    public String getEm_ding() {
+        return em_ding;
+    }
+
+    public void setEm_ding(String em_ding) {
+        this.em_ding = em_ding;
+    }
+}

+ 55 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/entity/HrOrg.java

@@ -0,0 +1,55 @@
+package com.usoftchina.uas.office.dingtalk.entity;
+
+/**
+ * UAS系统 组织表
+ *
+ * @author yingp
+ * @date 2020/2/15
+ */
+public class HrOrg {
+    private Integer or_id;
+    private String or_name;
+    private Integer or_subof;
+    private String or_headmancode;
+    private Long or_ding;
+
+    public Integer getOr_id() {
+        return or_id;
+    }
+
+    public void setOr_id(Integer or_id) {
+        this.or_id = or_id;
+    }
+
+    public String getOr_name() {
+        return or_name;
+    }
+
+    public void setOr_name(String or_name) {
+        this.or_name = or_name;
+    }
+
+    public Integer getOr_subof() {
+        return or_subof;
+    }
+
+    public void setOr_subof(Integer or_subof) {
+        this.or_subof = or_subof;
+    }
+
+    public String getOr_headmancode() {
+        return or_headmancode;
+    }
+
+    public void setOr_headmancode(String or_headmancode) {
+        this.or_headmancode = or_headmancode;
+    }
+
+    public Long getOr_ding() {
+        return or_ding;
+    }
+
+    public void setOr_ding(Long or_ding) {
+        this.or_ding = or_ding;
+    }
+}

+ 107 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/entity/JProcess.java

@@ -0,0 +1,107 @@
+package com.usoftchina.uas.office.dingtalk.entity;
+
+/**
+ * @author yingp
+ * @date 2020/2/16
+ */
+public class JProcess {
+    private Integer jp_id;
+    private String jp_name;
+    private String jp_launcherid;
+    private String jp_launchername;
+    private String jp_nodedealman;
+    private String jp_caller;
+    private String jp_table;
+    private Integer jp_keyvalue;
+    private String jp_url;
+    private String jp_codevalue;
+    private String jp_nodeid;
+
+    public Integer getJp_id() {
+        return jp_id;
+    }
+
+    public void setJp_id(Integer jp_id) {
+        this.jp_id = jp_id;
+    }
+
+    public String getJp_name() {
+        return jp_name;
+    }
+
+    public void setJp_name(String jp_name) {
+        this.jp_name = jp_name;
+    }
+
+    public String getJp_nodedealman() {
+        return jp_nodedealman;
+    }
+
+    public void setJp_nodedealman(String jp_nodedealman) {
+        this.jp_nodedealman = jp_nodedealman;
+    }
+
+    public String getJp_launcherid() {
+        return jp_launcherid;
+    }
+
+    public void setJp_launcherid(String jp_launcherid) {
+        this.jp_launcherid = jp_launcherid;
+    }
+
+    public String getJp_launchername() {
+        return jp_launchername;
+    }
+
+    public void setJp_launchername(String jp_launchername) {
+        this.jp_launchername = jp_launchername;
+    }
+
+    public String getJp_caller() {
+        return jp_caller;
+    }
+
+    public void setJp_caller(String jp_caller) {
+        this.jp_caller = jp_caller;
+    }
+
+    public String getJp_table() {
+        return jp_table;
+    }
+
+    public void setJp_table(String jp_table) {
+        this.jp_table = jp_table;
+    }
+
+    public Integer getJp_keyvalue() {
+        return jp_keyvalue;
+    }
+
+    public void setJp_keyvalue(Integer jp_keyvalue) {
+        this.jp_keyvalue = jp_keyvalue;
+    }
+
+    public String getJp_url() {
+        return jp_url;
+    }
+
+    public void setJp_url(String jp_url) {
+        this.jp_url = jp_url;
+    }
+
+    public String getJp_codevalue() {
+        return jp_codevalue;
+    }
+
+    public void setJp_codevalue(String jp_codevalue) {
+        this.jp_codevalue = jp_codevalue;
+    }
+
+    public String getJp_nodeid() {
+        return jp_nodeid;
+    }
+
+    public void setJp_nodeid(String jp_nodeid) {
+        this.jp_nodeid = jp_nodeid;
+    }
+}

+ 161 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/entity/Meeting.java

@@ -0,0 +1,161 @@
+package com.usoftchina.uas.office.dingtalk.entity;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author yingp
+ * @date 2020/2/16
+ */
+public class Meeting {
+    private Integer ma_id;
+    private String ma_theme;
+    private String ma_recorder;
+    private String em_ding;
+    private Date ma_starttime;
+    private Date ma_endtime;
+    private String ma_remark;
+    private String ma_mrcode;
+    private String ma_scheduleid;
+    private MeetingRoom room;
+    private List<MeetingAttendee> attendees;
+
+    public Integer getMa_id() {
+        return ma_id;
+    }
+
+    public void setMa_id(Integer ma_id) {
+        this.ma_id = ma_id;
+    }
+
+    public String getEm_ding() {
+        return em_ding;
+    }
+
+    public void setEm_ding(String em_ding) {
+        this.em_ding = em_ding;
+    }
+
+    public String getMa_theme() {
+        return ma_theme;
+    }
+
+    public void setMa_theme(String ma_theme) {
+        this.ma_theme = ma_theme;
+    }
+
+    public String getMa_mrcode() {
+        return ma_mrcode;
+    }
+
+    public void setMa_mrcode(String ma_mrcode) {
+        this.ma_mrcode = ma_mrcode;
+    }
+
+    public String getMa_recorder() {
+        return ma_recorder;
+    }
+
+    public void setMa_recorder(String ma_recorder) {
+        this.ma_recorder = ma_recorder;
+    }
+
+    public Date getMa_starttime() {
+        return ma_starttime;
+    }
+
+    public void setMa_starttime(Date ma_starttime) {
+        this.ma_starttime = ma_starttime;
+    }
+
+    public Date getMa_endtime() {
+        return ma_endtime;
+    }
+
+    public void setMa_endtime(Date ma_endtime) {
+        this.ma_endtime = ma_endtime;
+    }
+
+    public String getMa_scheduleid() {
+        return ma_scheduleid;
+    }
+
+    public void setMa_scheduleid(String ma_scheduleid) {
+        this.ma_scheduleid = ma_scheduleid;
+    }
+
+    public String getMa_remark() {
+        return ma_remark;
+    }
+
+    public void setMa_remark(String ma_remark) {
+        this.ma_remark = ma_remark;
+    }
+
+    public MeetingRoom getRoom() {
+        return room;
+    }
+
+    public void setRoom(MeetingRoom room) {
+        this.room = room;
+    }
+
+    public List<MeetingAttendee> getAttendees() {
+        return attendees;
+    }
+
+    public void setAttendees(List<MeetingAttendee> attendees) {
+        this.attendees = attendees;
+    }
+
+    public static class MeetingRoom {
+        private String mr_site;
+        private String mr_name;
+
+        public String getMr_site() {
+            return mr_site;
+        }
+
+        public void setMr_site(String mr_site) {
+            this.mr_site = mr_site;
+        }
+
+        public String getMr_name() {
+            return mr_name;
+        }
+
+        public void setMr_name(String mr_name) {
+            this.mr_name = mr_name;
+        }
+    }
+
+    public static class MeetingAttendee {
+        private String md_emcode;
+        private String md_participants;
+        private String em_ding;
+
+        public String getMd_emcode() {
+            return md_emcode;
+        }
+
+        public String getEm_ding() {
+            return em_ding;
+        }
+
+        public void setEm_ding(String em_ding) {
+            this.em_ding = em_ding;
+        }
+
+        public void setMd_emcode(String md_emcode) {
+            this.md_emcode = md_emcode;
+        }
+
+        public String getMd_participants() {
+            return md_participants;
+        }
+
+        public void setMd_participants(String md_participants) {
+            this.md_participants = md_participants;
+        }
+    }
+}

+ 201 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/entity/OutSign.java

@@ -0,0 +1,201 @@
+package com.usoftchina.uas.office.dingtalk.entity;
+
+import com.dingtalk.api.response.OapiAttendanceListRecordResponse;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * @author yingp
+ * @date 2020/2/16
+ */
+public class OutSign {
+    private Integer mo_id;
+    private String mo_code;
+    private String mo_remark;
+    private Date mo_signtime;
+    private String mo_address;
+    private String mo_mancode;
+    private String mo_man;
+    private String mo_company;
+    private String mo_status;
+    private String mo_statuscode;
+    private String mo_groupname;
+    private String mo_checkintype;
+    private String mo_exceptiontype;
+    private String mo_wifiname;
+    private String mo_notes;
+    private String mo_wifimac;
+    private String mo_mediaids;
+    private Long mo_lat;
+    private Long mo_lng;
+
+    public Integer getMo_id() {
+        return mo_id;
+    }
+
+    public void setMo_id(Integer mo_id) {
+        this.mo_id = mo_id;
+    }
+
+    public String getMo_code() {
+        return mo_code;
+    }
+
+    public void setMo_code(String mo_code) {
+        this.mo_code = mo_code;
+    }
+
+    public String getMo_remark() {
+        return mo_remark;
+    }
+
+    public void setMo_remark(String mo_remark) {
+        this.mo_remark = mo_remark;
+    }
+
+    public Date getMo_signtime() {
+        return mo_signtime;
+    }
+
+    public void setMo_signtime(Date mo_signtime) {
+        this.mo_signtime = mo_signtime;
+    }
+
+    public String getMo_address() {
+        return mo_address;
+    }
+
+    public void setMo_address(String mo_address) {
+        this.mo_address = mo_address;
+    }
+
+    public String getMo_mancode() {
+        return mo_mancode;
+    }
+
+    public void setMo_mancode(String mo_mancode) {
+        this.mo_mancode = mo_mancode;
+    }
+
+    public String getMo_man() {
+        return mo_man;
+    }
+
+    public void setMo_man(String mo_man) {
+        this.mo_man = mo_man;
+    }
+
+    public String getMo_company() {
+        return mo_company;
+    }
+
+    public void setMo_company(String mo_company) {
+        this.mo_company = mo_company;
+    }
+
+    public String getMo_status() {
+        return mo_status;
+    }
+
+    public void setMo_status(String mo_status) {
+        this.mo_status = mo_status;
+    }
+
+    public String getMo_statuscode() {
+        return mo_statuscode;
+    }
+
+    public void setMo_statuscode(String mo_statuscode) {
+        this.mo_statuscode = mo_statuscode;
+    }
+
+    public String getMo_groupname() {
+        return mo_groupname;
+    }
+
+    public void setMo_groupname(String mo_groupname) {
+        this.mo_groupname = mo_groupname;
+    }
+
+    public String getMo_checkintype() {
+        return mo_checkintype;
+    }
+
+    public void setMo_checkintype(String mo_checkintype) {
+        this.mo_checkintype = mo_checkintype;
+    }
+
+    public String getMo_exceptiontype() {
+        return mo_exceptiontype;
+    }
+
+    public void setMo_exceptiontype(String mo_exceptiontype) {
+        this.mo_exceptiontype = mo_exceptiontype;
+    }
+
+    public String getMo_wifiname() {
+        return mo_wifiname;
+    }
+
+    public void setMo_wifiname(String mo_wifiname) {
+        this.mo_wifiname = mo_wifiname;
+    }
+
+    public String getMo_notes() {
+        return mo_notes;
+    }
+
+    public void setMo_notes(String mo_notes) {
+        this.mo_notes = mo_notes;
+    }
+
+    public String getMo_wifimac() {
+        return mo_wifimac;
+    }
+
+    public void setMo_wifimac(String mo_wifimac) {
+        this.mo_wifimac = mo_wifimac;
+    }
+
+    public String getMo_mediaids() {
+        return mo_mediaids;
+    }
+
+    public void setMo_mediaids(String mo_mediaids) {
+        this.mo_mediaids = mo_mediaids;
+    }
+
+    public Long getMo_lat() {
+        return mo_lat;
+    }
+
+    public void setMo_lat(Long mo_lat) {
+        this.mo_lat = mo_lat;
+    }
+
+    public Long getMo_lng() {
+        return mo_lng;
+    }
+
+    public void setMo_lng(Long mo_lng) {
+        this.mo_lng = mo_lng;
+    }
+
+    public OutSign(Employee employee, OapiAttendanceListRecordResponse.Recordresult record) {
+        this.mo_remark = "钉钉外出打卡";
+        this.mo_signtime = record.getUserCheckTime();
+        this.mo_address = record.getUserAddress();
+        this.mo_mancode = employee.getEm_code();
+        this.mo_man = employee.getEm_name();
+        this.mo_status = "已提交";
+        this.mo_statuscode = "COMMITED";
+        this.mo_checkintype = "外出打卡";
+        this.mo_wifiname = record.getBaseSsid();
+        this.mo_notes = record.getOutsideRemark();
+        this.mo_wifimac = record.getUserMacAddr();
+
+        this.mo_lat = new BigDecimal(record.getUserLatitude()).multiply(BigDecimal.valueOf(1000000)).longValue();
+        this.mo_lng = new BigDecimal(record.getUserLongitude()).multiply(BigDecimal.valueOf(1000000)).longValue();
+    }
+}

+ 70 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/listener/UasAgendaListener.java

@@ -0,0 +1,70 @@
+package com.usoftchina.uas.office.dingtalk.listener;
+
+import com.usoftchina.dingtalk.sdk.ScheduleSdk;
+import com.usoftchina.dingtalk.sdk.dto.AddScheduleReq;
+import com.usoftchina.dingtalk.sdk.util.UrlUtils;
+import com.usoftchina.uas.office.context.MasterHolder;
+import com.usoftchina.uas.office.dingtalk.entity.Agenda;
+import com.usoftchina.uas.office.dingtalk.manage.entity.DingTalkAgent;
+import com.usoftchina.uas.office.dingtalk.manage.service.DingTalkAgentService;
+import com.usoftchina.uas.office.dingtalk.service.UasAgendaService;
+import com.usoftchina.uas.office.dto.UasEvent;
+import com.usoftchina.uas.office.entity.DataCenter;
+import com.usoftchina.uas.office.jdbc.DataSourceHolder;
+import com.usoftchina.uas.office.listener.UasEventListener;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Arrays;
+
+/**
+ * @author yingp
+ * @date 2020/2/16
+ */
+@Component
+public class UasAgendaListener {
+    @Autowired
+    private ScheduleSdk scheduleSdk;
+
+    @Autowired
+    private UasAgendaService agendaService;
+
+    @Autowired
+    private DingTalkAgentService agentService;
+
+    /**
+     * uas日程审核通过,写入DING日程
+     *
+     * @param event
+     */
+    @UasEventListener(caller = "Agenda", operation = "AUDIT")
+    public void onAgendaAudit(UasEvent event) throws Exception{
+        Agenda agenda = agendaService.getById((int) event.getKey());
+        if (null != agenda) {
+            DingTalkAgent uasAgent = getUasAgent();
+            String sourceUrl = UrlUtils.generateAuthUrl(scheduleSdk.getCorpId(), event.getMaster(), uasAgent.getCode(), uasAgent.getOuterUrl(),
+                    DataCenter.INSTANCE.getOuterUrl() + "authorize", "jsps/common/commonpage.jsp?whoami=Agenda&formCondition=ag_idIS" + agenda.getAg_id());
+            AddScheduleReq req = new AddScheduleReq()
+                    .organizer(agenda.getEm_ding())
+                    .during(agenda.getAg_start().getTime(), agenda.getAg_end().getTime())
+                    .summary(agenda.getAg_title())
+                    .description(agenda.getAg_content())
+                    .bizId(String.valueOf(agenda.getAg_id()))
+                    .remindBefore(0)
+                    .attendees(Arrays.asList(agenda.getEm_ding()))
+                    .source("UAS系统", sourceUrl);
+            String scheduleId = scheduleSdk.addSchedule("Uas", req);
+            agenda.setAg_scheduleid(scheduleId);
+            agendaService.setScheduleId(agenda);
+        }
+    }
+
+    private DingTalkAgent getUasAgent() {
+        try {
+            DataSourceHolder.set(DataCenter.INSTANCE);
+            return agentService.findByCode("Uas");
+        } finally {
+            DataSourceHolder.set(MasterHolder.get());
+        }
+    }
+}

+ 51 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/listener/UasEmployeeListener.java

@@ -0,0 +1,51 @@
+package com.usoftchina.uas.office.dingtalk.listener;
+
+import com.usoftchina.dingtalk.sdk.AddrBookSdk;
+import com.usoftchina.uas.office.dingtalk.entity.Employee;
+import com.usoftchina.uas.office.dingtalk.service.UasEmployeeService;
+import com.usoftchina.uas.office.dto.UasEvent;
+import com.usoftchina.uas.office.listener.UasEventListener;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author yingp
+ * @date 2020/2/16
+ */
+@Component
+public class UasEmployeeListener {
+
+    @Autowired
+    private AddrBookSdk addrBookSdk;
+
+    @Autowired
+    private UasEmployeeService employeeService;
+
+    /**
+     * uas人员资料审核,修改钉钉通讯录人员
+     */
+    @UasEventListener(caller = "Employeemanager", operation = "AUDIT")
+    public void onEmployeeAudit(UasEvent event) {
+        Employee employee = employeeService.getById(Integer.parseInt(event.getKey().toString()));
+        if (null != employee) {
+            if ("离职".equals(employee.getEm_class())) {
+                if (null != employee.getEm_ding()) {
+                    addrBookSdk.deleteUser("Uas", employee.getEm_ding());
+                }
+            } else {
+                employeeService.sync(employee);
+            }
+        }
+    }
+
+    /**
+     * uas人员资料删除,删除企业微信通讯录人员
+     */
+    @UasEventListener(caller = "Employeemanager", operation = "DELETE")
+    public void onEmployeeDelete(UasEvent event) {
+        Employee employee = employeeService.getRemovedById(Integer.parseInt(event.getKey().toString()));
+        if (null != employee && null != employee.getEm_ding()) {
+            addrBookSdk.deleteUser("Uas", employee.getEm_ding());
+        }
+    }
+}

+ 78 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/listener/UasMeetingListener.java

@@ -0,0 +1,78 @@
+package com.usoftchina.uas.office.dingtalk.listener;
+
+import com.usoftchina.dingtalk.sdk.ScheduleSdk;
+import com.usoftchina.dingtalk.sdk.dto.AddScheduleReq;
+import com.usoftchina.dingtalk.sdk.util.UrlUtils;
+import com.usoftchina.uas.office.context.MasterHolder;
+import com.usoftchina.uas.office.dingtalk.entity.Meeting;
+import com.usoftchina.uas.office.dingtalk.manage.entity.DingTalkAgent;
+import com.usoftchina.uas.office.dingtalk.manage.service.DingTalkAgentService;
+import com.usoftchina.uas.office.dingtalk.service.UasMeetingService;
+import com.usoftchina.uas.office.dto.UasEvent;
+import com.usoftchina.uas.office.entity.DataCenter;
+import com.usoftchina.uas.office.jdbc.DataSourceHolder;
+import com.usoftchina.uas.office.listener.UasEventListener;
+import com.usoftchina.uas.office.util.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.util.stream.Collectors;
+
+/**
+ * @author yingp
+ * @date 2020/2/16
+ */
+@Component
+public class UasMeetingListener {
+    @Autowired
+    private ScheduleSdk scheduleSdk;
+    @Autowired
+    private UasMeetingService meetingService;
+    @Autowired
+    private DingTalkAgentService agentService;
+
+    /**
+     * uas会议申请审核通过,写入企业微信日程
+     *
+     * @param event
+     */
+    @UasEventListener(caller = "Meetingroomapply", operation = "AUDIT")
+    public void onMeetingAudit(UasEvent event) throws Exception{
+        Meeting meeting = meetingService.getById((int) event.getKey());
+        if (null != meeting) {
+            DingTalkAgent uasAgent = getUasAgent();
+            String sourceUrl = UrlUtils.generateAuthUrl(scheduleSdk.getCorpId(), event.getMaster(), uasAgent.getCode(), uasAgent.getOuterUrl(),
+                    DataCenter.INSTANCE.getOuterUrl() + "authorize",
+                    String.format("jsps/oa/meeting/meetingroomapply.jsp?formCondition=ma_idIS%s&gridCondition=md_maidIS%s", meeting.getMa_id(), meeting.getMa_id()));
+            AddScheduleReq req = new AddScheduleReq()
+                    .organizer(meeting.getEm_ding())
+                    .during(meeting.getMa_starttime().getTime(), meeting.getMa_endtime().getTime())
+                    .summary("会议-" + meeting.getMa_theme())
+                    .bizId(String.valueOf(meeting.getMa_id()))
+                    .remindBefore(900)
+                    .source("UAS系统", sourceUrl);
+            if (null != meeting.getRoom()) {
+                req.location(meeting.getRoom().getMr_site() + " " + meeting.getRoom().getMr_name());
+            }
+            if (!StringUtils.isEmpty(meeting.getMa_remark())) {
+                req.description(meeting.getMa_remark());
+            }
+            if (!CollectionUtils.isEmpty(meeting.getAttendees())) {
+                req.attendees(meeting.getAttendees().stream().map(Meeting.MeetingAttendee::getEm_ding).collect(Collectors.toList()));
+            }
+            String scheduleId = scheduleSdk.addSchedule("Uas", req);
+            meeting.setMa_scheduleid(scheduleId);
+            meetingService.setScheduleId(meeting);
+        }
+    }
+
+    private DingTalkAgent getUasAgent() {
+        try {
+            DataSourceHolder.set(DataCenter.INSTANCE);
+            return agentService.findByCode("Uas");
+        } finally {
+            DataSourceHolder.set(MasterHolder.get());
+        }
+    }
+}

+ 98 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/listener/UasOrgListener.java

@@ -0,0 +1,98 @@
+package com.usoftchina.uas.office.dingtalk.listener;
+
+import com.dingtalk.api.response.OapiDepartmentListResponse;
+import com.usoftchina.dingtalk.sdk.AddrBookSdk;
+import com.usoftchina.uas.office.dingtalk.entity.Employee;
+import com.usoftchina.uas.office.dingtalk.entity.HrOrg;
+import com.usoftchina.uas.office.dingtalk.service.UasEmployeeService;
+import com.usoftchina.uas.office.dingtalk.service.UasOrgService;
+import com.usoftchina.uas.office.dto.UasEvent;
+import com.usoftchina.uas.office.listener.UasEventListener;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.util.List;
+
+/**
+ * @author yingp
+ * @date 2020/2/16
+ */
+@Component
+public class UasOrgListener {
+
+    @Autowired
+    private AddrBookSdk addrBookSdk;
+
+    @Autowired
+    private UasOrgService orgService;
+
+    @Autowired
+    private UasEmployeeService employeeService;
+
+    @UasEventListener(caller = "HrOrg", operation = {"DELETE",  "BANNED"})
+    public void onOrgDelete(UasEvent event) {
+        HrOrg org = null;
+        if ("DELETE".equals(event.getOperation())) {
+            org = orgService.getRemovedById(Integer.parseInt(event.getKey().toString()));
+        } else {
+            org = orgService.getById(Integer.parseInt(event.getKey().toString()));
+        }
+        if (null != org && null != org.getOr_ding()) {
+            // 检查部门下面是否有人
+            checkUser(org);
+            // 删除,检查部门下面是否有子部门,一并删除
+            deleteWithChildren(org.getOr_ding());
+        }
+    }
+
+    @UasEventListener(caller = "HrOrg", operation = {"SAVE", "UPDATE", "RESBANNED"})
+    public void onOrgSave(UasEvent event) {
+        HrOrg org = orgService.getById(Integer.parseInt(event.getKey().toString()));
+        if (null != org) {
+            orgService.sync(org);
+        }
+    }
+
+    /**
+     * 检查部门下面是否有人
+     *
+     * @param org
+     */
+    private void checkUser(HrOrg org) {
+        List<String> userList = addrBookSdk.getUserIdList("Uas", org.getOr_ding());
+        if (!CollectionUtils.isEmpty(userList)) {
+            for (String userId : userList) {
+                Employee employee = employeeService.getByDingUserId(userId);
+                if (null == employee) {
+                    addrBookSdk.deleteUser("Uas", userId);
+                } else {
+                    if (!employeeService.isMemberOfOrg(org, employee)) {
+                        employeeService.sync(employee);
+                    } else {
+                        throw new RuntimeException("部门下面还有有效人员,请先删除人员后再继续!");
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 删除,检查部门下面是否有子部门,一并删除
+     *
+     * @param departmentId
+     */
+    private void deleteWithChildren(Long departmentId) {
+        List<OapiDepartmentListResponse.Department> departmentList = addrBookSdk.getDepartmentList("Uas", departmentId);
+        if (!CollectionUtils.isEmpty(departmentList)) {
+            if (departmentList.size() > 1) {
+                for (OapiDepartmentListResponse.Department department : departmentList) {
+                    if (!department.getId().equals(departmentId)) {
+                        deleteWithChildren(department.getId());
+                    }
+                }
+            }
+            addrBookSdk.deleteDepartment("Uas", departmentId);
+        }
+    }
+}

+ 112 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/listener/UasProcessListener.java

@@ -0,0 +1,112 @@
+package com.usoftchina.uas.office.dingtalk.listener;
+
+import com.usoftchina.dingtalk.sdk.MessageSdk;
+import com.usoftchina.dingtalk.sdk.dto.SendMessageReq;
+import com.usoftchina.dingtalk.sdk.util.UrlUtils;
+import com.usoftchina.uas.office.context.MasterHolder;
+import com.usoftchina.uas.office.dingtalk.entity.Employee;
+import com.usoftchina.uas.office.dingtalk.entity.JProcess;
+import com.usoftchina.uas.office.dingtalk.manage.entity.DingTalkAgent;
+import com.usoftchina.uas.office.dingtalk.manage.service.DingTalkAgentService;
+import com.usoftchina.uas.office.dingtalk.service.UasEmployeeService;
+import com.usoftchina.uas.office.dingtalk.service.UasJProcessService;
+import com.usoftchina.uas.office.dto.UasEvent;
+import com.usoftchina.uas.office.entity.DataCenter;
+import com.usoftchina.uas.office.jdbc.DataSourceHolder;
+import com.usoftchina.uas.office.listener.UasEventListener;
+import com.usoftchina.uas.office.util.Try;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.net.URLEncoder;
+import java.util.List;
+
+/**
+ * @author yingp
+ * @date 2020/2/16
+ */
+@Component
+public class UasProcessListener {
+
+    @Autowired
+    private MessageSdk messageSdk;
+
+    @Autowired
+    private UasJProcessService jProcessService;
+
+    private final Logger logger = LoggerFactory.getLogger(UasProcessListener.class);
+
+    @Autowired
+    private DingTalkAgentService agentService;
+
+    @Autowired
+    private UasEmployeeService employeeService;
+
+    /**
+     * uas保存审批任务,发送消息到钉钉
+     *
+     * @param event
+     */
+    @UasEventListener(caller = "JProcess", operation = "SAVE")
+    public void onProcessSave(UasEvent event) {
+        if (event.getKey() instanceof List) {
+            List<Integer> jpIdList = (List<Integer>) event.getKey();
+            jpIdList.forEach(jpId -> {
+                sendProcessMessage(event, jpId);
+            });
+        } else {
+            sendProcessMessage(event, (int) event.getKey());
+        }
+    }
+
+    private void sendProcessMessage(UasEvent event, Integer jpId) {
+        JProcess process = Try.call(() -> jProcessService.getById(jpId), 10);
+        if (null == process) {
+            logger.warn("can not find process {}, {}", jpId, event);
+            return;
+        }
+        Employee employee = getDealMan(process);
+        if (null == employee) {
+            return;
+        }
+        DataCenter dataCenter = DataCenter.INSTANCE;
+        String title = process.getJp_launchername() + "的" + process.getJp_name();
+        String description = "单据编号:" + process.getJp_codevalue();
+        try {
+            // 往uas应用发送
+            DingTalkAgent agent = getUasAgent();
+            String paramsStr = "{\"master\":\"" + event.getMaster() + "\",\"nodeId\":" + process.getJp_nodeid() + ",\"baseUrl\":\"" + URLEncoder.encode(agent.getOuterUrl(), "utf-8") + "\"}";
+            String msgUrl = UrlUtils.generateAuthUrl(messageSdk.getCorpId(), event.getMaster(), "Uas", agent.getOuterUrl(),
+                    dataCenter.getOuterUrl() + "authorize", "uas/approval/" + URLEncoder.encode(paramsStr, "utf-8"));
+            messageSdk.send("Uas", new SendMessageReq()
+                    .actionCard(title, description, msgUrl)
+                    .toUser(employee.getEm_ding()));
+        } catch (Exception e) {
+            logger.error("send process message error", e);
+        }
+    }
+
+    public Employee getDealMan(JProcess process) {
+        try {
+            DataSourceHolder.set(DataCenter.INSTANCE);
+            Employee employee = employeeService.getByCode(process.getJp_nodedealman());
+            if (null == employee || null == employee.getEm_ding()) {
+                return null;
+            }
+            return employee;
+        } finally {
+            DataSourceHolder.set(MasterHolder.get());
+        }
+    }
+
+    public DingTalkAgent getUasAgent() {
+        try {
+            DataSourceHolder.set(DataCenter.INSTANCE);
+            return agentService.findByCode("Uas");
+        } finally {
+            DataSourceHolder.set(MasterHolder.get());
+        }
+    }
+}

+ 43 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/manage/controller/DingTalkAddrBookController.java

@@ -0,0 +1,43 @@
+package com.usoftchina.uas.office.dingtalk.manage.controller;
+
+import com.usoftchina.uas.office.dingtalk.service.UasEmployeeService;
+import com.usoftchina.uas.office.dingtalk.service.UasOrgService;
+import com.usoftchina.uas.office.dto.Result;
+import com.usoftchina.uas.office.entity.DataCenter;
+import com.usoftchina.uas.office.jdbc.DataSourceHolder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author yingp
+ * @date 2020/2/16
+ */
+@RequestMapping(path = "/mgm")
+@RestController
+public class DingTalkAddrBookController {
+
+    @Autowired
+    private UasOrgService orgService;
+
+    @Autowired
+    private UasEmployeeService employeeService;
+
+    @PostMapping(path = "/addrbook/sync")
+    public Result syncAll() {
+        DataCenter dataCenter = DataCenter.INSTANCE;
+        if (null == dataCenter) {
+            return Result.error("数据中心未设置");
+        }
+        try {
+            DataSourceHolder.set(dataCenter);
+            orgService.sync();
+            employeeService.sync();
+            orgService.clearUseless();
+            return Result.success();
+        } finally {
+            DataSourceHolder.clear();
+        }
+    }
+}

+ 102 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/manage/controller/DingTalkAgentController.java

@@ -0,0 +1,102 @@
+package com.usoftchina.uas.office.dingtalk.manage.controller;
+
+import com.usoftchina.dingtalk.sdk.config.DingTalkProperties;
+import com.usoftchina.dingtalk.sdk.util.UrlUtils;
+import com.usoftchina.uas.office.dingtalk.manage.entity.DingTalkAgent;
+import com.usoftchina.uas.office.dingtalk.manage.service.DingTalkAgentService;
+import com.usoftchina.uas.office.dto.Result;
+import com.usoftchina.uas.office.entity.DataCenter;
+import com.usoftchina.uas.office.jdbc.DataSourceHolder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * @author yingp
+ * @date 2020/2/16
+ */
+@RequestMapping(path = "/mgm")
+@RestController
+public class DingTalkAgentController {
+
+    @Autowired
+    private DingTalkAgentService agentService;
+
+    @Autowired
+    private DingTalkProperties properties;
+
+    @GetMapping(path = "/agent")
+    public Result getAgentList() {
+        DataCenter dataCenter = DataCenter.INSTANCE;
+        if (null == dataCenter) {
+            return Result.error("数据中心未设置");
+        }
+        try {
+            DataSourceHolder.set(dataCenter);
+            return Result.success(agentService.findAll());
+        } finally {
+            DataSourceHolder.clear();
+        }
+    }
+
+    @PostMapping(path = "/agent")
+    public Result saveAgent(DingTalkAgent agent) {
+        DataCenter dataCenter = DataCenter.INSTANCE;
+        if (null == dataCenter) {
+            return Result.error("数据中心未设置");
+        }
+        try {
+            DataSourceHolder.set(dataCenter);
+            agentService.save(agent);
+            return Result.success();
+        } finally {
+            DataSourceHolder.clear();
+        }
+    }
+
+    @DeleteMapping(path = "/agent")
+    public Result deleteAgent(String code) {
+        DataCenter dataCenter = DataCenter.INSTANCE;
+        if (null == dataCenter) {
+            return Result.error("数据中心未设置");
+        }
+        try {
+            DataSourceHolder.set(dataCenter);
+            agentService.delete(code);
+            return Result.success();
+        } finally {
+            DataSourceHolder.clear();
+        }
+    }
+
+    /**
+     * 获取用于从钉钉跳转到具体应用的授权url
+     *
+     * @param code
+     * @return
+     */
+    @GetMapping(path = "/agent/auth_url")
+    public Result getAgentAuthUrl(String code) throws Exception{
+        DataCenter dataCenter = DataCenter.INSTANCE;
+        if (null == dataCenter) {
+            return Result.error("数据中心未设置");
+        }
+        if (null == dataCenter.getOuterUrl()) {
+            return Result.error("外网地址未设置");
+        }
+        if (null == properties.getCorpId()) {
+            return Result.error("企业微信ID未设置");
+        }
+        try {
+            DataSourceHolder.set(dataCenter);
+            DingTalkAgent agent = agentService.findByCode(code);
+            if (null == agent.getOuterUrl()) {
+                return Result.error("应用外网地址未设置");
+            }
+            String authUrl = UrlUtils.generateAuthUrl(properties.getCorpId(), dataCenter.getUsername(), code,
+                    agent.getOuterUrl(), dataCenter.getOuterUrl() + "authorize", "/");
+            return Result.success(authUrl);
+        } finally {
+            DataSourceHolder.clear();
+        }
+    }
+}

+ 52 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/manage/controller/DingTalkSettingController.java

@@ -0,0 +1,52 @@
+package com.usoftchina.uas.office.dingtalk.manage.controller;
+
+import com.usoftchina.uas.office.dingtalk.manage.service.DingTalkSettingService;
+import com.usoftchina.uas.office.dto.Result;
+import com.usoftchina.uas.office.entity.DataCenter;
+import com.usoftchina.uas.office.jdbc.DataSourceHolder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author yingp
+ * @date 2020/2/16
+ */
+@RequestMapping(path = "/mgm")
+@RestController
+public class DingTalkSettingController {
+
+    @Autowired
+    private DingTalkSettingService settingService;
+
+    @GetMapping(path = "/setting")
+    public Result getDingTalkSetting() {
+        DataCenter dataCenter = DataCenter.INSTANCE;
+        if (null == dataCenter) {
+            return Result.error("数据中心未设置");
+        }
+        try {
+            DataSourceHolder.set(dataCenter);
+            return Result.success(settingService.find());
+        } finally {
+            DataSourceHolder.clear();
+        }
+    }
+
+    @PostMapping(path = "/setting/corp")
+    public Result saveDingTalkCorp(String corpId) {
+        DataCenter dataCenter = DataCenter.INSTANCE;
+        if (null == dataCenter) {
+            return Result.error("数据中心未设置");
+        }
+        try {
+            DataSourceHolder.set(dataCenter);
+            settingService.setCorpId(corpId);
+            return Result.success();
+        } finally {
+            DataSourceHolder.clear();
+        }
+    }
+}

+ 73 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/manage/entity/DingTalkAgent.java

@@ -0,0 +1,73 @@
+package com.usoftchina.uas.office.dingtalk.manage.entity;
+
+/**
+ * @author yingp
+ * @date 2020/2/15
+ */
+public class DingTalkAgent {
+    private String code;
+    private Long agentId;
+    private String appKey;
+    private String description;
+    private String appSecret;
+    /**
+     * 应用外网地址
+     */
+    private String outerUrl;
+
+    public DingTalkAgent() {
+    }
+
+    public DingTalkAgent(String code, String description) {
+        this.code = code;
+        this.description = description;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public Long getAgentId() {
+        return agentId;
+    }
+
+    public void setAgentId(Long agentId) {
+        this.agentId = agentId;
+    }
+
+    public String getAppKey() {
+        return appKey;
+    }
+
+    public void setAppKey(String appKey) {
+        this.appKey = appKey;
+    }
+
+    public String getAppSecret() {
+        return appSecret;
+    }
+
+    public void setAppSecret(String appSecret) {
+        this.appSecret = appSecret;
+    }
+
+    public String getOuterUrl() {
+        return outerUrl;
+    }
+
+    public void setOuterUrl(String outerUrl) {
+        this.outerUrl = outerUrl;
+    }
+}

+ 34 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/manage/entity/DingTalkSetting.java

@@ -0,0 +1,34 @@
+package com.usoftchina.uas.office.dingtalk.manage.entity;
+
+import java.util.Date;
+
+/**
+ * @author yingp
+ * @date 2020/2/17
+ */
+public class DingTalkSetting {
+    /**
+     * 企业钉钉ID
+     */
+    private String corpId;
+    /**
+     * 打卡数据获取时间
+     */
+    private Date checkinTime;
+
+    public String getCorpId() {
+        return corpId;
+    }
+
+    public void setCorpId(String corpId) {
+        this.corpId = corpId;
+    }
+
+    public Date getCheckinTime() {
+        return checkinTime;
+    }
+
+    public void setCheckinTime(Date checkinTime) {
+        this.checkinTime = checkinTime;
+    }
+}

+ 24 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/manage/event/DingTalkAgentEvent.java

@@ -0,0 +1,24 @@
+package com.usoftchina.uas.office.dingtalk.manage.event;
+
+import com.usoftchina.uas.office.dingtalk.manage.entity.DingTalkAgent;
+import org.springframework.context.ApplicationEvent;
+
+import java.util.List;
+
+/**
+ * @author yingp
+ * @date 2020/2/15
+ */
+public class DingTalkAgentEvent extends ApplicationEvent {
+
+    private final List<DingTalkAgent> agents;
+
+    public DingTalkAgentEvent(Object source, List<DingTalkAgent> agents) {
+        super(source);
+        this.agents = agents;
+    }
+
+    public List<DingTalkAgent> getAgents() {
+        return agents;
+    }
+}

+ 20 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/manage/event/DingTalkCorpEvent.java

@@ -0,0 +1,20 @@
+package com.usoftchina.uas.office.dingtalk.manage.event;
+
+import org.springframework.context.ApplicationEvent;
+
+/**
+ * @author yingp
+ * @date 2020/2/17
+ */
+public class DingTalkCorpEvent extends ApplicationEvent {
+    private final String corpId;
+
+    public DingTalkCorpEvent(Object source, String corpId) {
+        super(source);
+        this.corpId = corpId;
+    }
+
+    public String getCorpId() {
+        return corpId;
+    }
+}

+ 82 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/manage/service/DingTalkAgentService.java

@@ -0,0 +1,82 @@
+package com.usoftchina.uas.office.dingtalk.manage.service;
+
+import com.usoftchina.uas.office.context.ContextHolder;
+import com.usoftchina.uas.office.dingtalk.manage.entity.DingTalkAgent;
+import com.usoftchina.uas.office.dingtalk.manage.event.DingTalkAgentEvent;
+import com.usoftchina.uas.office.event.DataCenterEvent;
+import com.usoftchina.uas.office.jdbc.DataSourceHolder;
+import com.usoftchina.uas.office.service.AbstractService;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author yingp
+ * @date 2020/2/15
+ */
+@Service
+public class DingTalkAgentService extends AbstractService {
+
+    @Cacheable(value = "DingTalkAgent", key = "#code")
+    public DingTalkAgent findByCode(String code) {
+        return queryForBean("select * from DingTalkAgent where code=?", DingTalkAgent.class, code);
+    }
+
+    public List<DingTalkAgent> findAll() {
+        return queryForBeanList("select * from DingTalkAgent order by code", DingTalkAgent.class);
+    }
+
+    @CacheEvict(value = "DingTalkAgent", key = "#agent.code")
+    public void save(DingTalkAgent agent) {
+        if (!StringUtils.isEmpty(agent.getOuterUrl())) {
+            if (!agent.getOuterUrl().endsWith("/")) {
+                agent.setOuterUrl(agent.getOuterUrl() + "/");
+            }
+        }
+        int count = queryForObject("select count(1) from DingTalkAgent where code=?", Integer.class, agent.getCode());
+        if (count == 0) {
+            jdbcTemplate.update("insert into DingTalkAgent(code,description,agent_id,app_key,app_secret,outer_url) values (?,?,?,?,?,?)",
+                    agent.getCode(), agent.getDescription(), agent.getAgentId(), agent.getAppKey(), agent.getAppSecret(), agent.getOuterUrl());
+        } else {
+            jdbcTemplate.update("update DingTalkAgent set description=?,agent_id=?,app_key=?,app_secret=?,outer_url=? where code=?",
+                    agent.getDescription(), agent.getAgentId(), agent.getAppKey(), agent.getAppSecret(), agent.getOuterUrl(), agent.getCode());
+        }
+
+        ContextHolder.getApplicationContext().publishEvent(new DingTalkAgentEvent(this, findAll()));
+    }
+
+    @CacheEvict(value = "DingTalkAgent", key = "#code")
+    public void delete(String code) {
+        jdbcTemplate.update("delete from DingTalkAgent where code=?", code);
+
+        ContextHolder.getApplicationContext().publishEvent(new DingTalkAgentEvent(this, findAll()));
+    }
+
+    @Async
+    @EventListener(DataCenterEvent.class)
+    public void onDataCenterEvent(DataCenterEvent event) throws Exception {
+        try {
+            DataSourceHolder.set(event.getDataCenter());
+            List<DingTalkAgent> agents = findAll();
+            if (CollectionUtils.isEmpty(agents)) {
+                agents = new ArrayList<>();
+                // 初始化
+                agents.add(new DingTalkAgent("Uas", "UAS系统"));
+                jdbcTemplate.batchUpdate("insert into DingTalkAgent(code,description) values (?,?)",
+                        agents.stream().map(agent -> new Object[]{agent.getCode(), agent.getDescription()})
+                                .collect(Collectors.toList()));
+            }
+            ContextHolder.getApplicationContext().publishEvent(new DingTalkAgentEvent(this, agents));
+        } finally {
+            DataSourceHolder.clear();
+        }
+    }
+}

+ 66 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/manage/service/DingTalkSettingService.java

@@ -0,0 +1,66 @@
+package com.usoftchina.uas.office.dingtalk.manage.service;
+
+import com.usoftchina.uas.office.context.ContextHolder;
+import com.usoftchina.uas.office.dingtalk.manage.entity.DingTalkSetting;
+import com.usoftchina.uas.office.dingtalk.manage.event.DingTalkCorpEvent;
+import com.usoftchina.uas.office.event.DataCenterEvent;
+import com.usoftchina.uas.office.jdbc.DataSourceHolder;
+import com.usoftchina.uas.office.service.AbstractService;
+import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+
+/**
+ * @author yingp
+ * @date 2020/2/17
+ */
+@Service
+public class DingTalkSettingService extends AbstractService {
+
+    public DingTalkSetting find() {
+        return queryForBean("select * from DingTalkSetting", DingTalkSetting.class);
+    }
+
+    public void setCorpId(String corpId) {
+        DingTalkSetting setting = find();
+        if (null == setting) {
+            jdbcTemplate.update("insert into DingTalkSetting(corp_id) values (?)", corpId);
+        } else {
+            jdbcTemplate.update("update DingTalkSetting set corp_id=?", corpId);
+        }
+        ContextHolder.getApplicationContext().publishEvent(new DingTalkCorpEvent(this, corpId));
+    }
+
+    public Long getCheckinTime() {
+        DingTalkSetting setting = find();
+        if (null != setting && null != setting.getCheckinTime()) {
+            return setting.getCheckinTime().getTime();
+        }
+        return null;
+    }
+
+    public void setCheckinTime(Date checkinTime) {
+        DingTalkSetting setting = find();
+        if (null == setting) {
+            jdbcTemplate.update("insert into DingTalkSetting(checkin_time) values (?)", checkinTime);
+        } else {
+            jdbcTemplate.update("update DingTalkSetting set checkin_time=?", checkinTime);
+        }
+    }
+
+    @Async
+    @EventListener(DataCenterEvent.class)
+    public void onDataCenterEvent(DataCenterEvent event) throws Exception {
+        try {
+            DataSourceHolder.set(event.getDataCenter());
+            DingTalkSetting setting = find();
+            if (null != setting && null != setting.getCorpId()) {
+                ContextHolder.getApplicationContext().publishEvent(new DingTalkCorpEvent(this, setting.getCorpId()));
+            }
+        } finally {
+            DataSourceHolder.clear();
+        }
+    }
+}

+ 26 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/service/UasAgendaService.java

@@ -0,0 +1,26 @@
+package com.usoftchina.uas.office.dingtalk.service;
+
+import com.usoftchina.uas.office.dingtalk.entity.Agenda;
+import com.usoftchina.uas.office.service.AbstractService;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author yingp
+ * @date 2020/2/16
+ */
+@Service
+public class UasAgendaService extends AbstractService {
+
+    public Agenda getById(Integer id) {
+        return queryForBean("select Agenda.*,em_ding from Agenda,employee where ag_executor=em_name and ag_id=? and em_ding is not null",
+                Agenda.class, id);
+    }
+
+    public String getScheduleId(Integer id) {
+        return queryForObject("select ag_scheduleid from Agenda where ag_id=?", String.class, id);
+    }
+
+    public void setScheduleId(Agenda agenda) {
+        jdbcTemplate.update("update Agenda set ag_scheduleid=? where ag_id=?", agenda.getAg_scheduleid(), agenda.getAg_id());
+    }
+}

+ 21 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/service/UasCardLogService.java

@@ -0,0 +1,21 @@
+package com.usoftchina.uas.office.dingtalk.service;
+
+import com.usoftchina.uas.office.dingtalk.entity.CardLog;
+import com.usoftchina.uas.office.service.AbstractService;
+import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
+import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author yingp
+ * @date 2020/2/17
+ */
+@Service
+public class UasCardLogService extends AbstractService {
+
+    public void save(CardLog log) {
+        log.setCl_id(generateId("CARDLOG_SEQ"));
+        SimpleJdbcInsert insert = new SimpleJdbcInsert(jdbcTemplate);
+        insert.withTableName("CardLog").execute(new BeanPropertySqlParameterSource(log));
+    }
+}

+ 301 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/service/UasEmployeeService.java

@@ -0,0 +1,301 @@
+package com.usoftchina.uas.office.dingtalk.service;
+
+import com.alibaba.fastjson.JSON;
+import com.dingtalk.api.request.OapiUserCreateRequest;
+import com.dingtalk.api.request.OapiUserUpdateRequest;
+import com.dingtalk.api.response.OapiDepartmentListResponse;
+import com.dingtalk.api.response.OapiUserListbypageResponse;
+import com.usoftchina.dingtalk.sdk.AddrBookSdk;
+import com.usoftchina.uas.office.dingtalk.entity.Employee;
+import com.usoftchina.uas.office.dingtalk.entity.HrOrg;
+import com.usoftchina.uas.office.service.AbstractService;
+import com.usoftchina.uas.office.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.ObjectUtils;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @author yingp
+ * @date 2020/2/16
+ */
+@Service
+public class UasEmployeeService extends AbstractService {
+
+    private final Logger logger = LoggerFactory.getLogger(UasEmployeeService.class);
+
+    @Autowired
+    private AddrBookSdk addrBookSdk;
+
+    public Employee getById(Integer id) {
+        return queryForBean("select * from employee where em_id=?", Employee.class, id);
+    }
+
+    public Employee getRemovedById(Integer id) {
+        Employee employee = queryFlashbackBean("employee", "where em_id=?", Employee.class, id);
+        if (null == employee) {
+            employee = queryRecycleBean("Employeemanager", Employee.class, id);
+        }
+        return employee;
+    }
+
+    public Employee getByDingUserId(String dingUserId) {
+        return queryForBean("select * from employee where em_ding=? and em_class<>'离职'", Employee.class, dingUserId);
+    }
+
+    public Employee getByMobile(String mobile) {
+        return queryForBean("select * from employee where em_mobile=? and em_class<>'离职'", Employee.class, mobile);
+    }
+
+    public Employee getByEmail(String email) {
+        return queryForBean("select * from employee where em_email=? and em_class<>'离职'", Employee.class, email);
+    }
+
+    public Employee getByCode(String code) {
+        return queryForBean("select * from employee where em_code=? and em_class<>'离职'", Employee.class, code);
+    }
+
+    public void setDingUserId(Employee employee) {
+        jdbcTemplate.update("update employee set em_ding=? where em_id=?", employee.getEm_ding(), employee.getEm_id());
+    }
+
+    public List<Employee> getAll() {
+        return queryForBeanList("select * from employee where em_class<>'离职' and em_ding is not null", Employee.class);
+    }
+
+    /**
+     * 是否组织内成员
+     *
+     * @param org
+     * @param employee
+     * @return
+     */
+    public boolean isMemberOfOrg(HrOrg org, Employee employee) {
+        if (org.getOr_id().equals(employee.getEm_defaultorid())) {
+            return true;
+        }
+        return queryForObject("select count(1) from empsjobs where org_id=? and emp_id=?",
+                Integer.class, org.getOr_id(), employee.getEm_id()) > 0;
+    }
+
+    /**
+     * 同步全部 uas人员资料 -> 钉钉通讯录成员资料
+     */
+    public void sync() {
+        List<Employee> employeeList = queryForBeanList("select * from employee where em_class<>'离职' and em_statuscode='AUDITED' and (em_mobile is not null or em_email is not null) " +
+                "and exists (select 1 from hrorg where or_id=em_defaultorid and or_ding is not null)", Employee.class);
+        if (CollectionUtils.isEmpty(employeeList)) {
+            return;
+        }
+        // 人员资料预处理
+        Iterator<Employee> iterator = employeeList.iterator();
+        while (iterator.hasNext()) {
+            Employee employee = iterator.next();
+            employee.setEm_mobile(StringUtils.getValidMobile(employee.getEm_mobile()));
+            employee.setEm_email(StringUtils.getValidEmail(employee.getEm_email()));
+            employee.setEm_tel(StringUtils.getValidTelephone(employee.getEm_tel()));
+            if (StringUtils.isEmpty(employee.getEm_mobile()) && StringUtils.isEmpty(employee.getEm_email())) {
+                iterator.remove();
+            }
+        }
+        DingTalkUserFactory userFactory = new DingTalkUserFactory(getRootOrg().getOr_ding());
+        for (Employee employee : employeeList) {
+            sync(employee, userFactory);
+        }
+    }
+
+    /**
+     * 同步单个 uas人员资料 -> 钉钉通讯录成员资料
+     */
+    public void sync(Employee employee) {
+        employee.setEm_mobile(StringUtils.getValidMobile(employee.getEm_mobile()));
+        employee.setEm_email(StringUtils.getValidEmail(employee.getEm_email()));
+        employee.setEm_tel(StringUtils.getValidTelephone(employee.getEm_tel()));
+        if (StringUtils.isEmpty(employee.getEm_mobile()) && StringUtils.isEmpty(employee.getEm_email())) {
+            if (null != employee.getEm_ding()) {
+                addrBookSdk.deleteUser("Uas", employee.getEm_ding());
+            }
+            return;
+        }
+
+        DingTalkUserFactory userFactory = new DingTalkUserFactory(getRootOrg().getOr_ding());
+        sync(employee, userFactory);
+    }
+
+    private void sync(Employee employee, DingTalkUserFactory userFactory) {
+        List<HrOrg> orgList = getOrgListByEmployee(employee);
+        OapiUserListbypageResponse.Userlist user = userFactory.getSimilarUser(employee);
+        if (null != user) {
+            checkUserChange(employee, user, orgList, userFactory);
+            if (!user.getUserid().equals(employee.getEm_ding())) {
+                employee.setEm_ding(user.getUserid());
+                setDingUserId(employee);
+            }
+        } else {
+            createUser(employee, orgList);
+            employee.setEm_ding(employee.getEm_code());
+            setDingUserId(employee);
+        }
+    }
+
+    private void checkUserChange(Employee employee, OapiUserListbypageResponse.Userlist user, List<HrOrg> orgList, DingTalkUserFactory userFactory) {
+        boolean changed = false;
+        OapiUserUpdateRequest req = new OapiUserUpdateRequest();
+        req.setUserid(user.getUserid());
+        if (!ObjectUtils.nullSafeEquals(employee.getEm_name(), user.getName())) {
+            changed = true;
+            req.setName(employee.getEm_name());
+        }
+        if (!ObjectUtils.nullSafeEquals(employee.getEm_mobile(), user.getMobile())) {
+            changed = true;
+            req.setMobile(employee.getEm_mobile());
+        }
+        if (!ObjectUtils.nullSafeEquals(employee.getEm_email(), user.getEmail())) {
+            userFactory.checkNewEmail(employee);
+            changed = true;
+            req.setEmail(employee.getEm_email());
+        }
+        if (!ObjectUtils.nullSafeEquals(employee.getEm_tel(), user.getTel())) {
+            changed = true;
+            req.setTel(employee.getEm_tel());
+        }
+        if (!ObjectUtils.nullSafeEquals(employee.getEm_csshortname(), user.getWorkPlace())) {
+            changed = true;
+            req.setWorkPlace(employee.getEm_csshortname());
+        }
+        if (!ObjectUtils.nullSafeEquals(employee.getEm_position(), user.getPosition())) {
+            changed = true;
+            req.setPosition(employee.getEm_position());
+        }
+
+        List<Long> oldDepList = JSON.parseArray(user.getDepartment(), Long.class);
+        oldDepList.sort((o1, o2) -> o1.compareTo(o2));
+        List<Long> newDepList = orgList.stream().map(HrOrg::getOr_ding).sorted().collect(Collectors.toList());
+        if (Objects.equals(oldDepList, newDepList)) {
+            changed = true;
+            req.setDepartment(newDepList);
+        }
+
+        if (changed) {
+            addrBookSdk.updateUser("Uas", req);
+        }
+    }
+
+    private HrOrg getRootOrg() {
+        return queryForBean("select * from hrorg where or_subof=0 and or_statuscode<>'DISABLE' and or_ding is not null",
+                HrOrg.class);
+    }
+
+    public List<HrOrg> getOrgListByEmployee(Employee employee) {
+        // 只需考虑所属组织已同步的情况
+        return queryForBeanList("select * from hrorg where or_statuscode<>'DISABLE' and or_ding is not null and (or_id=? or exists (select 1 from empsjobs where org_id=or_id and emp_id=?))",
+                HrOrg.class, employee.getEm_defaultorid(), employee.getEm_id());
+    }
+
+    private void createUser(Employee employee, List<HrOrg> orgList) {
+        List<Long> departmentList = orgList.stream().map(HrOrg::getOr_ding).collect(Collectors.toList());
+        OapiUserCreateRequest req = new OapiUserCreateRequest();
+        req.setUserid(employee.getEm_code());
+        req.setJobnumber(employee.getEm_code());
+        req.setWorkPlace(employee.getEm_csshortname());
+        req.setEmail(employee.getEm_email());
+        req.setMobile(employee.getEm_mobile());
+        req.setTel(employee.getEm_tel());
+        req.setPosition(employee.getEm_position());
+        req.setName(employee.getEm_name());
+        req.setDepartment(JSON.toJSONString(departmentList));
+        addrBookSdk.createUser("Uas", req);
+    }
+
+    class DingTalkUserFactory {
+        private Map<String, List<OapiUserListbypageResponse.Userlist>> mobileGroup;
+        private Map<String, List<OapiUserListbypageResponse.Userlist>> emailGroup;
+        private Map<String, List<OapiUserListbypageResponse.Userlist>> nameGroup;
+        private Map<String, OapiUserListbypageResponse.Userlist> idMap;
+
+        public DingTalkUserFactory(Long departmentId) {
+            List<OapiUserListbypageResponse.Userlist> userList = addrBookSdk.getUserList("Uas", departmentId);
+            mobileGroup = new HashMap<>();
+            emailGroup = new HashMap<>();
+            nameGroup = new HashMap<>();
+            idMap = new HashMap<>(userList.size());
+            for (OapiUserListbypageResponse.Userlist user : userList) {
+                if (StringUtils.hasText(user.getMobile())) {
+                    List<OapiUserListbypageResponse.Userlist> users = mobileGroup.get(user.getMobile());
+                    if (null == users) {
+                        users = new ArrayList<>(1);
+                        mobileGroup.put(user.getMobile(), users);
+                    }
+                    users.add(user);
+                }
+                if (StringUtils.hasText(user.getEmail())) {
+                    List<OapiUserListbypageResponse.Userlist> users = emailGroup.get(user.getEmail());
+                    if (null == users) {
+                        users = new ArrayList<>(1);
+                        emailGroup.put(user.getEmail(), users);
+                    }
+                    users.add(user);
+                }
+                List<OapiUserListbypageResponse.Userlist> users = nameGroup.get(user.getName());
+                if (null == users) {
+                    users = new ArrayList<>(1);
+                    nameGroup.put(user.getName(), users);
+                }
+                users.add(user);
+                idMap.put(user.getUserid(), user);
+            }
+        }
+
+        /**
+         * 用于人员资料更新邮箱
+         *
+         * @param employee
+         */
+        public void checkNewEmail(Employee employee) {
+            List<OapiUserListbypageResponse.Userlist> users = emailGroup.get(employee.getEm_email());
+            if (!CollectionUtils.isEmpty(users)) {
+                for (OapiUserListbypageResponse.Userlist user : users) {
+                    if (!user.getUserid().equals(employee.getEm_ding())) {
+                        Employee other = getByDingUserId(user.getUserid());
+                        if (null == other) {
+                            // 如果人员在uas不存在,删除通讯录人员
+                            addrBookSdk.deleteUser("Uas", user.getUserid());
+                            logger.info("delete dingtalk user: " + JSON.toJSONString(user));
+                        } else {
+                            throw new RuntimeException(String.format("邮箱 %s 已被 %s 使用,请修改 %s 的邮箱",
+                                    employee.getEm_email(), other.getEm_name(), employee.getEm_name()));
+                        }
+                    }
+                }
+            }
+        }
+
+        public OapiUserListbypageResponse.Userlist getSimilarUser(Employee employee) {
+            OapiUserListbypageResponse.Userlist user = null;
+            if (StringUtils.hasText(employee.getEm_ding()) && idMap.containsKey(employee.getEm_ding())) {
+                user = idMap.get(employee.getEm_ding());
+            } else if (StringUtils.hasText(employee.getEm_mobile()) && mobileGroup.containsKey(employee.getEm_mobile())) {
+                if (mobileGroup.get(employee.getEm_mobile()).size() > 1) {
+                    throw new RuntimeException("钉钉存在多个人绑定同一手机号的情况 " + employee.getEm_mobile());
+                }
+                user = mobileGroup.get(employee.getEm_mobile()).get(0);
+            } else if (StringUtils.hasText(employee.getEm_email()) && emailGroup.containsKey(employee.getEm_email())) {
+                if (emailGroup.get(employee.getEm_email()).size() > 1) {
+                    throw new RuntimeException("钉钉存在多个人绑定同一邮箱的情况 " + employee.getEm_email());
+                }
+                user = emailGroup.get(employee.getEm_email()).get(0);
+            } else if (nameGroup.containsKey(employee.getEm_name())) {
+                if (nameGroup.get(employee.getEm_name()).size() > 1) {
+                    throw new RuntimeException("钉钉人员存在同名的情况 " + employee.getEm_name());
+                }
+                user = nameGroup.get(employee.getEm_name()).get(0);
+            }
+            return user;
+        }
+    }
+}

+ 17 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/service/UasJProcessService.java

@@ -0,0 +1,17 @@
+package com.usoftchina.uas.office.dingtalk.service;
+
+import com.usoftchina.uas.office.dingtalk.entity.JProcess;
+import com.usoftchina.uas.office.service.AbstractService;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author yingp
+ * @date 2020/2/16
+ */
+@Service
+public class UasJProcessService extends AbstractService {
+
+    public JProcess getById(Integer id) {
+        return queryForBean("select * from JProcess where jp_id=?", JProcess.class, id);
+    }
+}

+ 37 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/service/UasMeetingService.java

@@ -0,0 +1,37 @@
+package com.usoftchina.uas.office.dingtalk.service;
+
+import com.usoftchina.uas.office.dingtalk.entity.Meeting;
+import com.usoftchina.uas.office.service.AbstractService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @author yingp
+ * @date 2020/2/16
+ */
+@Service
+public class UasMeetingService extends AbstractService {
+
+    public Meeting getById(Integer id) {
+        Meeting meeting = queryForBean("select Meetingroomapply.*,em_ding from Meetingroomapply,employee where ma_recorder=em_name and ma_id=? and em_ding is not null",
+                Meeting.class, id);
+        if (null != meeting) {
+            Meeting.MeetingRoom room = queryForBean("select * from Meetingroom where mr_code=?",
+                    Meeting.MeetingRoom.class, meeting.getMa_mrcode());
+            meeting.setRoom(room);
+            List<Meeting.MeetingAttendee> attendees = queryForBeanList("select MeetingDetail.*,em_ding from MeetingDetail,employee where md_emcode=em_code and md_maid=? and em_ding is not null and md_participants<>?",
+                    Meeting.MeetingAttendee.class, id, meeting.getMa_recorder());
+            meeting.setAttendees(attendees);
+        }
+        return meeting;
+    }
+
+    public String getScheduleId(Integer id) {
+        return queryForObject("select ma_scheduleid from Meetingroomapply where ma_id=?", String.class, id);
+    }
+
+    public void setScheduleId(Meeting meeting) {
+        jdbcTemplate.update("update Meetingroomapply set ma_scheduleid=? where ma_id=?", meeting.getMa_scheduleid(), meeting.getMa_id());
+    }
+}

+ 255 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/service/UasOrgService.java

@@ -0,0 +1,255 @@
+package com.usoftchina.uas.office.dingtalk.service;
+
+import com.dingtalk.api.request.OapiDepartmentCreateRequest;
+import com.dingtalk.api.request.OapiDepartmentUpdateRequest;
+import com.dingtalk.api.response.OapiDepartmentListResponse;
+import com.usoftchina.dingtalk.sdk.AddrBookSdk;
+import com.usoftchina.uas.office.dingtalk.entity.HrOrg;
+import com.usoftchina.uas.office.service.AbstractService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import java.util.*;
+
+/**
+ * @author yingp
+ * @date 2020/2/9
+ */
+@Service
+public class UasOrgService extends AbstractService {
+    @Autowired
+    private AddrBookSdk addrBookSdk;
+
+    public HrOrg getById(Integer id) {
+        return queryForBean("select * from HrOrg where or_statuscode<>'DISABLE' and or_ding is not null and or_id=?", HrOrg.class,
+                id);
+    }
+
+    public HrOrg getRemovedById(Integer id) {
+        HrOrg org = queryFlashbackBean("HrOrg", "where or_id=?", HrOrg.class, id);
+        if (null == org) {
+            org = queryRecycleBean("HrOrg", HrOrg.class, id);
+        }
+        return org;
+    }
+
+    /**
+     * 同步全部 uas组织资料 -> 企业微信部门资料
+     */
+    public void sync() {
+        List<HrOrg> HrOrgList = queryForBeanList("select * from HrOrg where or_statuscode<>'DISABLE'", HrOrg.class);
+        if (!CollectionUtils.isEmpty(HrOrgList)) {
+            List<OapiDepartmentListResponse.Department> departmentList = addrBookSdk.getDepartmentList("Uas");
+            checkOrg(groupOrgList(HrOrgList), 0, groupDepartmentList(departmentList), 0L);
+        }
+    }
+
+    /**
+     * 同步 uas组织资料 -> 企业微信部门资料
+     */
+    public void sync(HrOrg org) {
+        if (null != org.getOr_ding()) {
+            if (null == org.getOr_subof() || 0 == org.getOr_subof()) {
+                // 修改部门
+                OapiDepartmentUpdateRequest req = new OapiDepartmentUpdateRequest();
+                req.setId(org.getOr_ding());
+                req.setName(org.getOr_name());
+                req.setParentid("0");
+                addrBookSdk.updateDepartment("Uas", req);
+            } else {
+                HrOrg parentOrg = getById(org.getOr_subof());
+                if (null != parentOrg) {
+                    // 修改部门
+                    OapiDepartmentUpdateRequest req = new OapiDepartmentUpdateRequest();
+                    req.setId(org.getOr_ding());
+                    req.setName(org.getOr_name());
+                    req.setParentid(String.valueOf(parentOrg.getOr_ding()));
+                    addrBookSdk.updateDepartment("Uas", req);
+                }
+            }
+        } else {
+            List<OapiDepartmentListResponse.Department> departmentList = addrBookSdk.getDepartmentList("Uas");
+            if (null == org.getOr_subof() || 0 == org.getOr_subof()) {
+                OapiDepartmentListResponse.Department rootDept = null;
+                for (OapiDepartmentListResponse.Department department : departmentList) {
+                    if (department.getParentid() == 0) {
+                        rootDept = department;
+                        break;
+                    }
+                }
+                // 修改部门名称
+                OapiDepartmentUpdateRequest req = new OapiDepartmentUpdateRequest();
+                req.setId(rootDept.getId());
+                req.setName(org.getOr_name());
+                req.setParentid("0");
+                addrBookSdk.updateDepartment("Uas", req);
+                updateOrgDingId(org, rootDept.getId());
+            } else {
+                try {
+                    // 只需考虑父级组织已同步的情况
+                    HrOrg parentOrg = getById(org.getOr_subof());
+                    OapiDepartmentListResponse.Department dept = null;
+                    for (OapiDepartmentListResponse.Department department : departmentList) {
+                        if (department.getParentid().equals(parentOrg.getOr_ding()) && department.getName().equals(org.getOr_name())) {
+                            dept = department;
+                            break;
+                        }
+                    }
+                    if (null != dept) {
+                        // 修改部门名称
+                        OapiDepartmentUpdateRequest req = new OapiDepartmentUpdateRequest();
+                        req.setId(dept.getId());
+                        req.setName(org.getOr_name());
+                        addrBookSdk.updateDepartment("Uas", req);
+                        updateOrgDingId(org, dept.getId());
+                    } else {
+                        OapiDepartmentCreateRequest req = new OapiDepartmentCreateRequest();
+                        req.setName(org.getOr_name());
+                        req.setParentid(String.valueOf(parentOrg.getOr_ding()));
+                        req.setCreateDeptGroup(true);
+                        Long departmentId = addrBookSdk.createDepartment("Uas", req);
+                        updateOrgDingId(org, departmentId);
+                    }
+                } catch (EmptyResultDataAccessException e) {
+                }
+            }
+        }
+    }
+
+    private void updateOrgDingId(HrOrg org, long dingDepartmentId) {
+        org.setOr_ding(dingDepartmentId);
+        jdbcTemplate.update("update HrOrg set or_ding=? where or_id=?", dingDepartmentId, org.getOr_id());
+    }
+
+    private Map<Integer, List<HrOrg>> groupOrgList(List<HrOrg> orgList) {
+        Map<Integer, List<HrOrg>> orgMap = new HashMap<>();
+        if (null != orgList) {
+            for (HrOrg HrOrg : orgList) {
+                List<HrOrg> orgs = orgMap.get(HrOrg.getOr_subof());
+                if (null == orgs) {
+                    orgs = new ArrayList<>(1);
+                    orgMap.put(HrOrg.getOr_subof(), orgs);
+                }
+                orgs.add(HrOrg);
+            }
+        }
+        return orgMap;
+    }
+
+    private Map<Long, List<OapiDepartmentListResponse.Department>> groupDepartmentList(List<OapiDepartmentListResponse.Department> departmentList) {
+        Map<Long, List<OapiDepartmentListResponse.Department>> deptMap = new HashMap<>();
+        if (null != departmentList) {
+            for (OapiDepartmentListResponse.Department department : departmentList) {
+                List<OapiDepartmentListResponse.Department> departments = deptMap.get(department.getParentid());
+                if (null == departments) {
+                    departments = new ArrayList<>(1);
+                    deptMap.put(department.getParentid(), departments);
+                }
+                departments.add(department);
+            }
+        }
+        return deptMap;
+    }
+
+    private void checkOrg(Map<Integer, List<HrOrg>> orgMap, Integer parentOrgId,
+                          Map<Long, List<OapiDepartmentListResponse.Department>> deptMap, Long parentDeptId) {
+        List<HrOrg> orgList = orgMap.get(parentOrgId);
+        List<OapiDepartmentListResponse.Department> departmentList = deptMap.get(parentDeptId);
+        if (null != orgList) {
+            for (HrOrg org : orgList) {
+                OapiDepartmentListResponse.Department department = findDeptByName(departmentList, org.getOr_name());
+                if (null == department) {
+                    if (null != org.getOr_ding() && org.getOr_ding() > 0) {
+                        department = findDeptById(departmentList, org.getOr_ding());
+                    }
+                    if (null == department && parentDeptId == 0 && !CollectionUtils.isEmpty(departmentList)) {
+                        department = departmentList.get(0);
+                    }
+                    if (null == department) {
+                        OapiDepartmentCreateRequest req = new OapiDepartmentCreateRequest();
+                        req.setName(org.getOr_name());
+                        req.setParentid(String.valueOf(parentDeptId));
+                        req.setCreateDeptGroup(true);
+                        Long departmentId = addrBookSdk.createDepartment("Uas", req);
+                        updateOrgDingId(org, departmentId);
+                        checkOrg(orgMap, org.getOr_id(), deptMap, departmentId);
+                    } else {
+                        // 修改部门名称
+                        OapiDepartmentUpdateRequest req = new OapiDepartmentUpdateRequest();
+                        req.setId(department.getId());
+                        req.setName(org.getOr_name());
+                        addrBookSdk.updateDepartment("Uas", req);
+                        if (!department.getId().equals(org.getOr_ding())) {
+                            updateOrgDingId(org, department.getId());
+                        }
+                        checkOrg(orgMap, org.getOr_id(), deptMap, department.getId());
+                    }
+                } else {
+                    if (!department.getId().equals(org.getOr_ding())) {
+                        // 企业微信存在同级别同名的部门名称,还未绑定到uas系统
+                        updateOrgDingId(org, department.getId());
+                    }
+                    checkOrg(orgMap, org.getOr_id(), deptMap, department.getId());
+                }
+            }
+        }
+    }
+
+    private OapiDepartmentListResponse.Department findDeptById(List<OapiDepartmentListResponse.Department> departmentList, Long departmentId) {
+        if (null != departmentList) {
+            for (OapiDepartmentListResponse.Department department : departmentList) {
+                if (department.getId().equals(departmentId)) {
+                    return department;
+                }
+            }
+        }
+        return null;
+    }
+
+    private OapiDepartmentListResponse.Department findDeptByName(List<OapiDepartmentListResponse.Department> departmentList, String name) {
+        if (null != departmentList) {
+            for (OapiDepartmentListResponse.Department department : departmentList) {
+                if (department.getName().equals(name)) {
+                    return department;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 清除下面没有人员的部门
+     */
+    public void clearUseless() {
+        List<OapiDepartmentListResponse.Department> departmentList = addrBookSdk.getDepartmentList("Uas");
+        Map<Long, List<OapiDepartmentListResponse.Department>> group = groupDepartmentList(departmentList);
+        while (!departmentList.isEmpty()) {
+            Iterator<OapiDepartmentListResponse.Department> iterator = departmentList.iterator();
+            boolean changed = false;
+            while (iterator.hasNext()) {
+                OapiDepartmentListResponse.Department department = iterator.next();
+                // 必须先删末级部门
+                if (!group.containsKey(department.getId())) {
+                    int count = queryForObject("select count(1) from HrOrg where or_ding = ? and (exists (select 1 from employee where em_defaultorid=or_id) or exists (select 1 from empsjobs where org_id=or_id))", Integer.class, department.getId());
+                    if (count == 0) {
+                        List<String> users = addrBookSdk.getUserIdList("Uas", department.getId());
+                        if (CollectionUtils.isEmpty(users)) {
+                            addrBookSdk.deleteDepartment("Uas", department.getId());
+                            group.get(department.getParentid()).remove(department);
+                            if (group.get(department.getParentid()).isEmpty()) {
+                                group.remove(department.getParentid());
+                            }
+                            iterator.remove();
+                            changed = true;
+                        }
+                    }
+                }
+            }
+            if (!changed) {
+                break;
+            }
+        }
+    }
+}

+ 22 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/service/UasOutSignService.java

@@ -0,0 +1,22 @@
+package com.usoftchina.uas.office.dingtalk.service;
+
+import com.usoftchina.uas.office.dingtalk.entity.OutSign;
+import com.usoftchina.uas.office.service.AbstractService;
+import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
+import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author yingp
+ * @date 2020/2/17
+ */
+@Service
+public class UasOutSignService extends AbstractService {
+
+    public void save(OutSign sign) {
+        sign.setMo_id(generateId("MOBILE_OUTSIGN_SEQ"));
+        sign.setMo_code(generateCode("mobile_outsign", 2));
+        SimpleJdbcInsert insert = new SimpleJdbcInsert(jdbcTemplate);
+        insert.withTableName("MOBILE_OUTSIGN").execute(new BeanPropertySqlParameterSource(sign));
+    }
+}

+ 103 - 0
uas-office-dingtalk/src/main/java/com/usoftchina/uas/office/dingtalk/task/DingTalkCheckinTask.java

@@ -0,0 +1,103 @@
+package com.usoftchina.uas.office.dingtalk.task;
+
+import com.dingtalk.api.response.OapiAttendanceListRecordResponse;
+import com.usoftchina.dingtalk.sdk.OaSdk;
+import com.usoftchina.uas.office.dingtalk.entity.CardLog;
+import com.usoftchina.uas.office.dingtalk.entity.Employee;
+import com.usoftchina.uas.office.dingtalk.entity.OutSign;
+import com.usoftchina.uas.office.dingtalk.manage.service.DingTalkSettingService;
+import com.usoftchina.uas.office.dingtalk.service.UasCardLogService;
+import com.usoftchina.uas.office.dingtalk.service.UasEmployeeService;
+import com.usoftchina.uas.office.dingtalk.service.UasOutSignService;
+import com.usoftchina.uas.office.entity.DataCenter;
+import com.usoftchina.uas.office.jdbc.DataSourceHolder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * @author yingp
+ * @date 2020/2/16
+ */
+@Component
+public class DingTalkCheckinTask {
+
+    @Autowired
+    private OaSdk oaSdk;
+
+    @Autowired
+    private UasEmployeeService employeeService;
+
+    @Autowired
+    private DingTalkSettingService settingService;
+
+    @Autowired
+    private UasCardLogService cardLogService;
+
+    @Autowired
+    private UasOutSignService outSignService;
+
+    /**
+     * 每次最多条数据
+     */
+    private final int maxSeed = 100;
+
+    private final Logger logger = LoggerFactory.getLogger(DingTalkCheckinTask.class);
+
+    /**
+     * 抓取打卡数据
+     */
+    @Scheduled(fixedDelay = 300000, initialDelay = 30000)
+    public void pullCheckinData() {
+        DataCenter dataCenter = DataCenter.INSTANCE;
+        if (null != dataCenter.getUsername() && null != dataCenter.getPassword() && null != dataCenter.getUrl()) {
+            try {
+                DataSourceHolder.set(dataCenter);
+                List<Employee> employees = employeeService.getAll();
+                if (!CollectionUtils.isEmpty(employees)) {
+                    Map<String, Employee> employeeMap = employees.stream().collect(Collectors.toMap(Employee::getEm_ding, e -> e));
+                    Long lastTime = settingService.getCheckinTime();
+                    if (lastTime == null) {
+                        lastTime = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli();
+                    }
+                    Date now = new Date();
+                    long unixStartTime = lastTime + 1;
+                    long unixEndTime = now.getTime();
+                    Stream.iterate(0, n -> n + 1)
+                            .limit((int) Math.ceil((double) employees.size() / maxSeed)).forEach(page -> {
+                                List<String> userList = employees.stream().skip(page * maxSeed).limit(maxSeed).map(Employee::getEm_ding).collect(Collectors.toList());
+                        List<OapiAttendanceListRecordResponse.Recordresult> dataList = oaSdk.getAttendanceRecordList("Uas", userList, unixStartTime, unixEndTime);
+                        saveCheckinData(employeeMap, dataList);
+                    });
+                    settingService.setCheckinTime(now);
+                }
+            } finally {
+                DataSourceHolder.clear();
+            }
+        }
+    }
+
+    private void saveCheckinData(Map<String, Employee> employeeMap, List<OapiAttendanceListRecordResponse.Recordresult> dataList) {
+        dataList.forEach(data -> {
+            Employee employee = employeeMap.get(data.getUserId());
+            logger.debug("get checkin data, user {}, type {}", employee.getEm_name(), data.getLocationResult());
+            if ("Outside".equals(data.getLocationResult())) {
+                // 外勤签到 插入外勤打卡记录
+                outSignService.save(new OutSign(employee, data));
+            } else {
+                cardLogService.save(new CardLog(employee, data));
+            }
+        });
+    }
+}

+ 0 - 0
uas-office-server/build.gradle → uas-office-qywx-server/build.gradle


+ 1 - 1
uas-office-server/src/main/java/com/usoftchina/uas/office/UasOfficeServer.java → uas-office-qywx-server/src/main/java/com/usoftchina/uas/office/qywx/UasOfficeServer.java

@@ -1,4 +1,4 @@
-package com.usoftchina.uas.office;
+package com.usoftchina.uas.office.qywx;
 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;

+ 1 - 1
uas-office-server/src/main/java/com/usoftchina/uas/office/config/ExceptionConfig.java → uas-office-qywx-server/src/main/java/com/usoftchina/uas/office/qywx/config/ExceptionConfig.java

@@ -1,4 +1,4 @@
-package com.usoftchina.uas.office.config;
+package com.usoftchina.uas.office.qywx.config;
 
 import com.usoftchina.uas.office.dto.Result;
 import org.slf4j.Logger;

+ 37 - 0
uas-office-qywx-server/src/main/java/com/usoftchina/uas/office/qywx/config/WebMvcConfig.java

@@ -0,0 +1,37 @@
+package com.usoftchina.uas.office.qywx.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpHeaders;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * @author yingp
+ * @date 2018/11/7
+ */
+@Configuration
+public class WebMvcConfig implements WebMvcConfigurer {
+
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/**")
+                .allowedOrigins("*")
+                .allowedMethods("*")
+                .allowedHeaders("*")
+                .allowCredentials(true)
+                .exposedHeaders(HttpHeaders.SET_COOKIE).maxAge(3600L);
+    }
+
+    @Override
+    public void addViewControllers(ViewControllerRegistry registry) {
+        registry.addViewController("/index").setViewName("dc.html");
+        registry.addViewController("/").setViewName("dc.html");
+        registry.addViewController("/login").setViewName("/login.html");
+        registry.addViewController("/agent").setViewName("agent.html");
+        registry.addViewController("/addrbook").setViewName("addrbook.html");
+        registry.addViewController("/corp").setViewName("corp.html");
+    }
+}
+
+

+ 60 - 0
uas-office-qywx-server/src/main/java/com/usoftchina/uas/office/qywx/config/WebSecurityConfig.java

@@ -0,0 +1,60 @@
+package com.usoftchina.uas.office.qywx.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+/**
+ * @author yingp
+ * @date 2019/3/11
+ */
+@Configuration
+@EnableWebSecurity
+public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
+    @Override
+    public void configure(WebSecurity web) throws Exception {
+        web.ignoring().antMatchers("/resources/**", "/static/**", "/public/**",
+                "/html/**", "/css/**", "/js/**", "**/*.css", "**/*.js", "/api/**");
+    }
+
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+        http.authorizeRequests()
+                .antMatchers("/login")
+                .permitAll()
+                .anyRequest()
+                .authenticated()
+                .and()
+                .formLogin()
+                .loginPage("/login")
+                .and()
+                .csrf()
+                .disable()
+                .sessionManagement()
+                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
+    }
+
+    @Autowired
+    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
+        auth.inMemoryAuthentication().passwordEncoder(new PlainTextPasswordEncoder())
+                .withUser("admin").password("select111***").roles("ADMIN");
+    }
+
+    class PlainTextPasswordEncoder implements PasswordEncoder {
+        @Override
+        public String encode(CharSequence rawPassword) {
+            return rawPassword.toString();
+        }
+
+        @Override
+        public boolean matches(CharSequence rawPassword, String encodedPassword) {
+            return encodedPassword.equals(rawPassword.toString());
+        }
+    }
+}

+ 36 - 0
uas-office-qywx-server/src/main/resources/application.yaml

@@ -0,0 +1,36 @@
+server:
+  port: 9091
+  tomcat:
+    uri-encoding: UTF-8
+  servlet:
+    context-path: /office
+spring:
+  http:
+    encoding:
+      force: true
+      charset: utf-8
+      enabled: true
+  messages:
+    basename: i18n/messages
+  datasource:
+    primary:
+      driver-class-name: org.h2.Driver
+      jdbc-url: jdbc:h2:file:${user.home}/.office/data/server;FILE_LOCK=NO;DB_CLOSE_ON_EXIT=FALSE
+      username: admin
+      password: select111***
+    hikari:
+      minimum-idle: 5
+      maximum-pool-size: 50
+      idle-timeout: 30000
+      max-lifetime: 1800000
+      connection-timeout: 30000
+  redis:
+    host: 127.0.0.1
+    port: 6379
+  jackson:
+    serialization:
+      write-dates-as-timestamps: true
+logging:
+  path: ${user.home}/.office/logs/
+  level:
+    com.usoftchina.uas.office: debug

+ 10 - 0
uas-office-qywx-server/src/main/resources/banner.txt

@@ -0,0 +1,10 @@
+${AnsiColor.BRIGHT_RED}
+ _____  _____   _       ______
+|_   _||_   _| / \    .' ____ \
+  | |    | |  / _ \   | (___ \_|
+  | '    ' | / ___ \   _.____`.
+   \ \__/ /_/ /   \ \_| \____) |
+    `.__.'|____| |____|\______.'
+
+@Copyright 2020 Shenzhen Usoft Information Technology Co., Ltd.
+${AnsiColor.DEFAULT}

+ 4 - 0
uas-office-qywx-server/src/main/resources/config/application-dev.yaml

@@ -0,0 +1,4 @@
+spring:
+  redis:
+    host: 10.1.81.2
+    port: 6379

+ 0 - 0
uas-office-server/src/main/resources/erp_schema.json → uas-office-qywx-server/src/main/resources/erp_schema.json


BIN
uas-office-qywx-server/src/main/resources/icon.ico


+ 54 - 0
uas-office-qywx-server/src/main/resources/logback-spring.xml

@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <include resource="org/springframework/boot/logging/logback/base.xml" />
+    <jmxConfigurator/>
+
+    <!--
+    %m
+    输出代码中指定的消息
+    %p
+    输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL
+    %r
+    输出自应用启动到输出该log信息耗费的毫秒数
+    %c
+    输出所属的类目,通常就是所在类的全名
+    %t
+    输出产生该日志事件的线程名
+    %n
+    输出一个回车换行符,Windows平台为“\r\n”,Unix平台为“\n”
+    %d
+    输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},
+    输出类似:2002年10月18日 22:10:28,921
+    %l
+    输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java:10)
+    -->
+
+    <springProperty scope="context" name="log.path" source="logging.path" defaultValue="./logs"/>
+    <springProperty scope="context" name="spring.application.name" source="spring.application.name" defaultValue="uas-office-server"/>
+    <springProperty scope="context" name="spring.profiles.active" source="spring.profiles.active" defaultValue="prod"/>
+    <springProperty scope="context" name="common-pattern" source="logging.common-pattern" defaultValue="%d{yyyy-MM-dd HH:mm:ss.SSS}:[%5p] [%t:%r] [%C{1}:%M:%L] --> %m%n"/>
+    <springProperty scope="context" name="log.level.console" source="logging.level.console" defaultValue="INFO"/>
+
+    <contextName>${spring.application.name}-${spring.profiles.active}-logback</contextName>
+
+    <appender name="ROOT_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/root.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <fileNamePattern>${log.path}/%d{yyyy-MM}/root-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
+            <maxFileSize>20MB</maxFileSize>
+            <maxHistory>7</maxHistory>
+            <totalSizeCap>1GB</totalSizeCap>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${common-pattern}</pattern>
+        </encoder>
+    </appender>
+
+    <logger name="org.springframework" level="INFO"/>
+    <logger name="com.usoftchina.uas.office" level="DEBUG"/>
+
+    <root level="INFO">
+        <appender-ref ref="ROOT_APPENDER"/>
+    </root>
+
+</configuration>

+ 8 - 0
uas-office-qywx-server/src/main/resources/schema.sql

@@ -0,0 +1,8 @@
+create table if not exists DataCenter(
+    id int primary key,
+    url varchar(255),
+    username varchar(50),
+    password varchar(50),
+    driverClassName varchar(255),
+    outerUrl varchar(255)
+);

+ 44 - 0
uas-office-qywx-server/src/main/resources/static/addrbook.html

@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html lang="zh-CN">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+    <title>UAS在线办公</title>
+    <link rel="stylesheet" href="css/bootstrap.min.css">
+</head>
+<body>
+<nav class="navbar navbar-expand-lg navbar-light bg-light">
+    <a class="navbar-brand" href="#">UAS在线办公管理</a>
+
+    <div class="collapse navbar-collapse">
+        <ul class="navbar-nav mr-auto">
+            <li class="nav-item">
+                <a class="nav-link" href=".">数据中心</a>
+            </li>
+            <li class="nav-item">
+                <a class="nav-link" href="agent">应用管理</a>
+            </li>
+            <li class="nav-item active">
+                <a class="nav-link" href="addrbook">通讯录管理</a>
+            </li>
+            <li class="nav-item">
+                <a class="nav-link" href="corp">企业设置</a>
+            </li>
+        </ul>
+    </div>
+</nav>
+<div style="padding: 20px">
+    <button type="button" class="btn btn-primary">同步</button>
+</div>
+<div class="modal fade" id="loadingModal" backdrop="static" keyboard="false">
+    <div style="width: 200px;height:100px; z-index: 20000; position: absolute; text-align: center; left: 50%; top: 50%;margin-left:-100px;margin-top:-10px">
+        <div class="progress progress-striped active"
+             style="margin-bottom: 0;height:50px; text-align:center;line-height: 50px;font-size:large;">
+            同步中...   
+        </div>
+    </div>
+</div>
+<script src="js/jquery.min.js"></script>
+<script src="js/bootstrap.min.js"></script>
+<script src="js/addrbook.js"></script>
+</body>

+ 0 - 0
uas-office-server/src/main/resources/static/agent.html → uas-office-qywx-server/src/main/resources/static/agent.html


+ 0 - 0
uas-office-server/src/main/resources/static/corp.html → uas-office-qywx-server/src/main/resources/static/corp.html


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 5 - 0
uas-office-qywx-server/src/main/resources/static/css/bootstrap.min.css


برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است