Browse Source

企业微信考勤打卡数据同步功能

fancy 5 years ago
parent
commit
aa76dedd02
16 changed files with 963 additions and 37 deletions
  1. 32 28
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/DingdingAttendanceQueue.java
  2. 339 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/QywxAttendanceSyncQueue.java
  3. 9 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/ThisApplication.java
  4. 9 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/exception/QywxFindNoArgumentError.java
  5. 43 7
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/factory/DingdingAttendanceFactory.java
  6. 2 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/ActionApplication.java
  7. 10 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/QywxJaxrsFilter.java
  8. 35 1
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingding/ActionListDingdingSyncRecord.java
  9. 45 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/ActionDeleteAllQywxAttendanceData.java
  10. 117 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/ActionListQywxAttendanceDetail.java
  11. 87 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/ActionListQywxSyncRecord.java
  12. 54 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/ActionSyncQywxData.java
  13. 6 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/BaseAction.java
  14. 103 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/QywxAttendanceAction.java
  15. 13 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/SyncDateException.java
  16. 59 1
      o2server/x_base_core_project/src/main/java/com/x/base/core/project/config/Qiyeweixin.java

+ 32 - 28
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/DingdingAttendanceQueue.java

@@ -18,7 +18,9 @@ import com.x.base.core.project.logger.LoggerFactory;
 import com.x.base.core.project.organization.Person;
 import com.x.base.core.project.queue.AbstractQueue;
 import com.x.base.core.project.tools.DateTools;
+import com.x.base.core.project.tools.ListTools;
 import com.x.base.core.project.x_organization_assemble_control;
+import org.apache.commons.lang3.StringUtils;
 
 import javax.persistence.EntityManager;
 import javax.persistence.criteria.CriteriaBuilder;
@@ -69,36 +71,38 @@ public class DingdingAttendanceQueue extends AbstractQueue<DingdingQywxSyncRecor
             List<Person> list = ThisApplication.context().applications().getQuery(false, app, uri).getDataAsList(Person.class);
             if (list != null && list.size() > 0) {
                 //钉钉用户id
-                List<String> ddUsers = list.stream().map(Person::getDingdingId).collect(Collectors.toList());
-                //分页查询
-                int page = 0;
-                boolean hasMoreResult = true;
-                while (hasMoreResult) {
-                    DingdingAttendancePost post = new DingdingAttendancePost();
-                    //post传入 时间(时间间隔不能超过7天) 人员(人员数量不能超过50个)
-                    post.setLimit(50L);
-                    post.setOffset(page * 50L);//从0开始翻页
-                    //这里的开始时间和结束时间 查询的是钉钉返回结果中的workDate
-                    //查询考勤打卡记录的起始工作日
-                    post.setWorkDateFrom(DateTools.format(fromDate));
-                    //查询考勤打卡记录的结束工作日
-                    post.setWorkDateTo(DateTools.format(toDate));
-                    post.setUserIdList(ddUsers);
-                    DingdingAttendanceResult result = HttpConnection.postAsObject(dingdingUrl, null, post.toString(), DingdingAttendanceResult.class);
-                    if (result.errcode != null && result.errcode == 0) {
-                        List<DingdingAttendanceResultItem> resultList = result.getRecordresult();
-                        saveDingdingAttendance(resultList);
-                        saveNumber += resultList.size();
-                        if (result.hasMore) {
-                            page++;
+                List<String> ddUsers = list.stream().filter(person -> StringUtils.isNotEmpty(person.getDingdingId()))
+                        .map(Person::getDingdingId).collect(Collectors.toList());
+                if (ListTools.isNotEmpty(ddUsers)) {
+                    //分页查询
+                    int page = 0;
+                    boolean hasMoreResult = true;
+                    while (hasMoreResult) {
+                        DingdingAttendancePost post = new DingdingAttendancePost();
+                        //post传入 时间(时间间隔不能超过7天) 人员(人员数量不能超过50个)
+                        post.setLimit(50L);
+                        post.setOffset(page * 50L);//从0开始翻页
+                        //这里的开始时间和结束时间 查询的是钉钉返回结果中的workDate
+                        //查询考勤打卡记录的起始工作日
+                        post.setWorkDateFrom(DateTools.format(fromDate));
+                        //查询考勤打卡记录的结束工作日
+                        post.setWorkDateTo(DateTools.format(toDate));
+                        post.setUserIdList(ddUsers);
+                        DingdingAttendanceResult result = HttpConnection.postAsObject(dingdingUrl, null, post.toString(), DingdingAttendanceResult.class);
+                        if (result.errcode != null && result.errcode == 0) {
+                            List<DingdingAttendanceResultItem> resultList = result.getRecordresult();
+                            saveDingdingAttendance(resultList);
+                            saveNumber += resultList.size();
+                            if (result.hasMore) {
+                                page++;
+                            } else {
+                                logger.info("同步钉钉考勤结束。。。。。。。。。。。。。。。。");
+                                hasMoreResult = false;
+                            }
                         } else {
-                            logger.info("同步钉钉考勤结束。。。。。。。。。。。。。。。。");
-                            hasMoreResult = false;
+                            //请求结果异常 结束
+                            throw new DingDingRequestException(result.errmsg);
                         }
-                    } else {
-                        //请求结果异常 结束
-                        logger.error(new DingDingRequestException(result.errmsg));
-                        hasMoreResult = false;
                     }
                 }
                 //是否还有更多用户

+ 339 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/QywxAttendanceSyncQueue.java

@@ -0,0 +1,339 @@
+package com.x.attendance.assemble.control;
+
+import com.x.attendance.assemble.control.exception.DingDingRequestException;
+import com.x.attendance.entity.*;
+import com.x.base.core.container.EntityManagerContainer;
+import com.x.base.core.container.factory.EntityManagerContainerFactory;
+import com.x.base.core.entity.JpaObject;
+import com.x.base.core.project.Application;
+import com.x.base.core.project.bean.WrapCopier;
+import com.x.base.core.project.bean.WrapCopierFactory;
+import com.x.base.core.project.config.Config;
+import com.x.base.core.project.connection.HttpConnection;
+import com.x.base.core.project.gson.GsonPropertyObject;
+import com.x.base.core.project.logger.Logger;
+import com.x.base.core.project.logger.LoggerFactory;
+import com.x.base.core.project.organization.Person;
+import com.x.base.core.project.queue.AbstractQueue;
+import com.x.base.core.project.tools.ListTools;
+import com.x.base.core.project.tools.StringTools;
+import com.x.base.core.project.x_organization_assemble_control;
+import org.apache.commons.lang3.BooleanUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.persistence.EntityManager;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class QywxAttendanceSyncQueue  extends AbstractQueue<DingdingQywxSyncRecord> {
+
+    private static final Logger logger = LoggerFactory.getLogger(QywxAttendanceSyncQueue.class);
+
+
+    @Override
+    protected void execute(DingdingQywxSyncRecord record) throws Exception {
+        logger.info("开始执行企业微信打卡数据同步,from:" + record.getDateFrom() + ", to:"+record.getDateTo());
+        if (DingdingQywxSyncRecord.syncType_qywx.equals(record.getType())) {
+            try {
+                qywxSync(record);
+            }catch (Exception e) {
+                logger.error(e);
+                updateSyncRecord(record, e.getMessage());
+            }
+        } else {
+            logger.info("不是企业微信同步任务。。。。。。。。。。。。。");
+        }
+    }
+
+
+    private void qywxSync(DingdingQywxSyncRecord record) throws Exception{
+        Application app = ThisApplication.context().applications().randomWithWeight(x_organization_assemble_control.class.getName());
+        //开始分页查询人员
+        boolean hasNextPerson = true;
+        int personPageSize = 50;
+        //开始时间和结束时间
+        Date fromDate = new Date();
+        fromDate.setTime(record.getDateFrom());
+        Date toDate =new Date();
+        toDate.setTime(record.getDateTo());
+        //先删除
+        deleteQywxAttendance(fromDate, toDate);
+        //人员查询地址
+        String uri = "person/list/(0)/next/50";
+        String qywxuri = "https://qyapi.weixin.qq.com/cgi-bin/checkin/getcheckindata?access_token="+ Config.qiyeweixin().attendanceAccessToken();
+        int saveNumber = 0;
+        while (hasNextPerson) {
+            List<Person> list = ThisApplication.context().applications().getQuery(false, app, uri).getDataAsList(Person.class);
+            if (list != null && list.size() > 0) {
+                //钉钉用户id
+                List<String> qywxUsers = list.stream().filter(person -> StringUtils.isNotEmpty(person.getQiyeweixinId()))
+                        .map(Person::getQiyeweixinId).collect(Collectors.toList());
+
+                if(ListTools.isNotEmpty(qywxUsers)){
+                    QywxPost post = new QywxPost();
+                    post.setStarttime(fromDate.getTime());
+                    post.setEndtime(toDate.getTime());
+                    post.setUseridlist(qywxUsers);
+                    post.setOpencheckindatatype(3);//全部打卡信息
+                    logger.info("企业微信 post :" + post.toString());
+                    QywxResult result = HttpConnection.postAsObject(qywxuri, null, post.toString(), QywxResult.class);
+                    logger.info("返回结果:"+result.toString());
+                    if (result.errcode == 0) {
+                        List<QywxResultItem> resultList = result.getCheckindata();
+                        saveQywxAttendance(resultList);
+                        saveNumber += resultList.size();
+                    } else {
+                        //请求结果异常 结束
+                        throw new DingDingRequestException(result.errmsg);
+                    }
+
+                }
+
+                //是否还有更多用户
+                if (list.size() < personPageSize) {
+                    logger.info("同步钉钉考勤 没有更多用户了,结束。。。。。。。。。。。。。。。");
+                    hasNextPerson = false;
+                    updateSyncRecord(record, null);
+                } else {
+                    //还有更多用户继续查询
+                    uri = "person/list/" + list.get(list.size() - 1).getDistinguishedName() + "/next/50";
+                }
+            } else {
+                //没有用户查询到结束
+                logger.info("同步钉钉考勤 查询不到用户了,结束。。。。。。。。。。。。。。。");
+                hasNextPerson = false;
+                updateSyncRecord(record, null);
+            }
+        }
+        logger.info("结束 插入:"+saveNumber+" 条");
+
+
+    }
+
+    private void saveQywxAttendance(List<QywxResultItem> resultList) throws Exception {
+        if (resultList != null && !resultList.isEmpty()) {
+            try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+                emc.beginTransaction(AttendanceQywxDetail.class);
+                for (int i = 0; i < resultList.size(); i++) {
+                    QywxResultItem item = resultList.get(i);
+                    AttendanceQywxDetail detail = QywxResultItem.copier.copy(item);
+                    emc.persist(detail);
+                }
+                emc.commit();
+            }
+        }
+    }
+
+    private void deleteQywxAttendance(Date fromDate, Date toDate) throws Exception {
+        try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+            //先删除 再同步
+            EntityManager em = emc.get(AttendanceQywxDetail.class);
+            CriteriaBuilder cb = em.getCriteriaBuilder();
+            CriteriaQuery<AttendanceQywxDetail> query = cb.createQuery(AttendanceQywxDetail.class);
+            Root<AttendanceQywxDetail> root = query.from(AttendanceQywxDetail.class);
+            long start = fromDate.getTime();
+            long end = toDate.getTime();
+            Predicate p = cb.between(root.get(AttendanceQywxDetail_.checkin_time), start, end);
+            query.select(root).where(p);
+            List<AttendanceQywxDetail> detailList = em.createQuery(query).getResultList();
+            //先删除
+            logger.info("删除");
+            if (detailList != null) {
+                logger.info("删除 list:"+detailList.size());
+                emc.beginTransaction(AttendanceDingtalkDetail.class);
+                for (int i = 0; i < detailList.size(); i++) {
+                    emc.remove(detailList.get(i));
+                }
+                emc.commit();
+            }
+            logger.info("删除结束");
+        }
+    }
+
+
+    private void updateSyncRecord(DingdingQywxSyncRecord record, String message) throws Exception {
+        try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+            emc.beginTransaction(DingdingQywxSyncRecord.class);
+            DingdingQywxSyncRecord entity = emc.find(record.getId(), DingdingQywxSyncRecord.class);
+            entity.setEndTime(new Date());
+            if (message == null || message.isEmpty()) {
+                entity.setStatus(DingdingQywxSyncRecord.status_end);
+            } else {
+                entity.setStatus(DingdingQywxSyncRecord.status_error);
+                entity.setExceptionMessage(message);
+            }
+            emc.commit();
+        }
+    }
+
+
+    public static class QywxPost extends GsonPropertyObject {
+        //{
+        //   "opencheckindatatype": 3,
+        //   "starttime": 1492617600,
+        //   "endtime": 1492790400,
+        //   "useridlist": ["james","paul"]
+        //}
+
+        //打卡类型。1:上下班打卡;2:外出打卡;3:全部打卡
+        private int opencheckindatatype;
+        private long starttime;
+        private long endtime;
+        private List<String> useridlist;
+
+        public int getOpencheckindatatype() {
+            return opencheckindatatype;
+        }
+
+        public void setOpencheckindatatype(int opencheckindatatype) {
+            this.opencheckindatatype = opencheckindatatype;
+        }
+
+        public long getStarttime() {
+            return starttime;
+        }
+
+        public void setStarttime(long starttime) {
+            this.starttime = starttime;
+        }
+
+        public long getEndtime() {
+            return endtime;
+        }
+
+        public void setEndtime(long endtime) {
+            this.endtime = endtime;
+        }
+
+        public List<String> getUseridlist() {
+            return useridlist;
+        }
+
+        public void setUseridlist(List<String> useridlist) {
+            this.useridlist = useridlist;
+        }
+    }
+
+    public static class QywxResult extends GsonPropertyObject {
+        private int errcode;
+        private String errmsg;
+        private List<QywxResultItem> checkindata;
+
+        public int getErrcode() {
+            return errcode;
+        }
+
+        public void setErrcode(int errcode) {
+            this.errcode = errcode;
+        }
+
+        public String getErrmsg() {
+            return errmsg;
+        }
+
+        public void setErrmsg(String errmsg) {
+            this.errmsg = errmsg;
+        }
+
+        public List<QywxResultItem> getCheckindata() {
+            return checkindata;
+        }
+
+        public void setCheckindata(List<QywxResultItem> checkindata) {
+            this.checkindata = checkindata;
+        }
+    }
+    public static class QywxResultItem extends GsonPropertyObject {
+
+
+        static WrapCopier<QywxResultItem, AttendanceQywxDetail> copier = WrapCopierFactory.wi(QywxResultItem.class,
+                AttendanceQywxDetail.class, null, JpaObject.FieldsUnmodify);
+
+        private String userid;
+        private String groupname;
+        private String checkin_type;
+        private long checkin_time;
+        private String exception_type;
+        private String location_title;
+        private String location_detail;
+        private String wifiname;
+        private String notes;
+
+        public String getUserid() {
+            return userid;
+        }
+
+        public void setUserid(String userid) {
+            this.userid = userid;
+        }
+
+        public String getGroupname() {
+            return groupname;
+        }
+
+        public void setGroupname(String groupname) {
+            this.groupname = groupname;
+        }
+
+        public String getCheckin_type() {
+            return checkin_type;
+        }
+
+        public void setCheckin_type(String checkin_type) {
+            this.checkin_type = checkin_type;
+        }
+
+        public long getCheckin_time() {
+            return checkin_time;
+        }
+
+        public void setCheckin_time(long checkin_time) {
+            this.checkin_time = checkin_time;
+        }
+
+        public String getException_type() {
+            return exception_type;
+        }
+
+        public void setException_type(String exception_type) {
+            this.exception_type = exception_type;
+        }
+
+        public String getLocation_title() {
+            return location_title;
+        }
+
+        public void setLocation_title(String location_title) {
+            this.location_title = location_title;
+        }
+
+        public String getLocation_detail() {
+            return location_detail;
+        }
+
+        public void setLocation_detail(String location_detail) {
+            this.location_detail = location_detail;
+        }
+
+        public String getWifiname() {
+            return wifiname;
+        }
+
+        public void setWifiname(String wifiname) {
+            this.wifiname = wifiname;
+        }
+
+        public String getNotes() {
+            return notes;
+        }
+
+        public void setNotes(String notes) {
+            this.notes = notes;
+        }
+    }
+}

+ 9 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/ThisApplication.java

@@ -18,6 +18,7 @@ public class ThisApplication {
 	}
 
 	public static DingdingAttendanceQueue dingdingQueue = new DingdingAttendanceQueue();
+	public static QywxAttendanceSyncQueue qywxQueue = new QywxAttendanceSyncQueue();
 
 	public static void init() throws Exception {
 		try {
@@ -27,6 +28,9 @@ public class ThisApplication {
 			if (BooleanUtils.isTrue(Config.dingding().getAttendanceSyncEnable())) {
 				dingdingQueue.start();
 			}
+			if (BooleanUtils.isTrue(Config.qiyeweixin().getAttendanceSyncEnable())) {
+				qywxQueue.start();
+			}
 		} catch (Exception e) {
 			e.printStackTrace();
 		}
@@ -48,5 +52,10 @@ public class ThisApplication {
 		} catch (Exception e) {
 			e.printStackTrace();
 		}
+		try {
+			qywxQueue.stop();
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
 	}
 }

+ 9 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/exception/QywxFindNoArgumentError.java

@@ -0,0 +1,9 @@
+package com.x.attendance.assemble.control.exception;
+
+import com.x.base.core.project.exception.PromptException;
+
+public class QywxFindNoArgumentError extends PromptException {
+    public QywxFindNoArgumentError() {
+        super("没有传入正确的参数");
+    }
+}

+ 43 - 7
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/factory/DingdingAttendanceFactory.java

@@ -3,10 +3,8 @@ package com.x.attendance.assemble.control.factory;
 import com.x.attendance.assemble.control.AbstractFactory;
 import com.x.attendance.assemble.control.Business;
 import com.x.attendance.assemble.control.exception.DingdingFindNoArgumentError;
-import com.x.attendance.entity.AttendanceDingtalkDetail;
-import com.x.attendance.entity.AttendanceDingtalkDetail_;
-import com.x.attendance.entity.DingdingQywxSyncRecord;
-import com.x.attendance.entity.DingdingQywxSyncRecord_;
+import com.x.attendance.assemble.control.exception.QywxFindNoArgumentError;
+import com.x.attendance.entity.*;
 import com.x.base.core.project.tools.StringTools;
 
 import javax.persistence.EntityManager;
@@ -23,17 +21,20 @@ public class DingdingAttendanceFactory extends AbstractFactory {
         super(business);
     }
 
+
+
     /**
-     * 查询所有钉钉同步记录
+     * 查询所有同步记录
+     * @param type  DingdingQywxSyncRecord.syncType_dingding DingdingQywxSyncRecord.syncType_qywx
      * @return
      * @throws Exception
      */
-    public List<DingdingQywxSyncRecord> findAllDingdingSyncRecord() throws Exception {
+    public List<DingdingQywxSyncRecord> findAllSyncRecordWithType(String type) throws Exception {
         EntityManager em = this.entityManagerContainer().get(DingdingQywxSyncRecord.class);
         CriteriaBuilder cb = em.getCriteriaBuilder();
         CriteriaQuery<DingdingQywxSyncRecord> query = cb.createQuery(DingdingQywxSyncRecord.class);
         Root<DingdingQywxSyncRecord> root = query.from(DingdingQywxSyncRecord.class);
-        Predicate p = cb.equal(root.get(DingdingQywxSyncRecord_.type), DingdingQywxSyncRecord.syncType_dingding);
+        Predicate p = cb.equal(root.get(DingdingQywxSyncRecord_.type), type);
         query.select(root).where(p).orderBy(cb.desc(root.get(DingdingQywxSyncRecord_.startTime)));
         return em.createQuery(query).getResultList();
     }
@@ -89,4 +90,39 @@ public class DingdingAttendanceFactory extends AbstractFactory {
         query.select(root).where(p).orderBy(cb.desc(root.get(AttendanceDingtalkDetail_.userCheckTime)));
         return em.createQuery(query).getResultList();
     }
+
+    /**
+     * 企业微信 打卡数据查询
+     * @param startTime
+     * @param endTime
+     * @param userId
+     * @return
+     * @throws Exception
+     */
+    public List<AttendanceQywxDetail> findQywxAttendanceDetail(Date startTime, Date endTime, String userId) throws Exception {
+        if (startTime == null && endTime == null && userId == null) {
+            throw new QywxFindNoArgumentError();
+        }
+        EntityManager em = this.entityManagerContainer().get(AttendanceQywxDetail.class);
+        CriteriaBuilder cb = em.getCriteriaBuilder();
+        CriteriaQuery<AttendanceQywxDetail> query = cb.createQuery(AttendanceQywxDetail.class);
+        Root<AttendanceQywxDetail> root = query.from(AttendanceQywxDetail.class);
+
+        Predicate p = null;
+        if (startTime != null && endTime != null) {
+            long start = startTime.getTime();
+            long end = endTime.getTime();
+            p = cb.between(root.get(AttendanceQywxDetail_.checkin_time), start, end);
+        }
+        if (userId != null && !userId.isEmpty()) {
+            if (p != null) {
+                p = cb.and(p, cb.equal(root.get(AttendanceQywxDetail_.userid), userId));
+            }else {
+                p = cb.equal(root.get(AttendanceQywxDetail_.userid), userId);
+            }
+        }
+        query.select(root).where(p).orderBy(cb.desc(root.get(AttendanceQywxDetail_.checkin_time)));
+        return em.createQuery(query).getResultList();
+
+    }
 }

+ 2 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/ActionApplication.java

@@ -20,6 +20,7 @@ import com.x.attendance.assemble.control.jaxrs.attendancestatisticrequirelog.Att
 import com.x.attendance.assemble.control.jaxrs.attendanceworkdayconfig.AttendanceWorkDayConfigAction;
 import com.x.attendance.assemble.control.jaxrs.dingding.DingdingAttendanceAction;
 import com.x.attendance.assemble.control.jaxrs.fileimport.AttendanceDetailFileImportAction;
+import com.x.attendance.assemble.control.jaxrs.qywx.QywxAttendanceAction;
 import com.x.attendance.assemble.control.jaxrs.selfholiday.AttendanceSelfHolidayAction;
 import com.x.attendance.assemble.control.jaxrs.selfholiday.AttendanceSelfHolidaySimpleAction;
 import com.x.attendance.assemble.control.jaxrs.uuid.UUIDAction;
@@ -50,6 +51,7 @@ public class ActionApplication extends AbstractActionApplication {
 		this.classes.add(AttendanceEmployeeConfigAction.class);
 		this.classes.add(AttendanceStatisticRequireLogAction.class);
 		this.classes.add(DingdingAttendanceAction.class);
+		this.classes.add(QywxAttendanceAction.class);
 		return this.classes;
 	}
 

+ 10 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/QywxJaxrsFilter.java

@@ -0,0 +1,10 @@
+package com.x.attendance.assemble.control.jaxrs;
+
+import com.x.base.core.project.jaxrs.CipherManagerJaxrsFilter;
+
+import javax.servlet.annotation.WebFilter;
+
+
+@WebFilter(urlPatterns = "/jaxrs/qywx/*", asyncSupported = true)
+public class QywxJaxrsFilter extends CipherManagerJaxrsFilter {
+}

+ 35 - 1
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingding/ActionListDingdingSyncRecord.java

@@ -5,12 +5,14 @@ import com.x.attendance.entity.DingdingQywxSyncRecord;
 import com.x.base.core.container.EntityManagerContainer;
 import com.x.base.core.container.factory.EntityManagerContainerFactory;
 import com.x.base.core.entity.JpaObject;
+import com.x.base.core.project.annotation.FieldDescribe;
 import com.x.base.core.project.bean.WrapCopier;
 import com.x.base.core.project.bean.WrapCopierFactory;
 import com.x.base.core.project.http.ActionResult;
 import com.x.base.core.project.logger.Logger;
 import com.x.base.core.project.logger.LoggerFactory;
 
+import java.util.Date;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -22,12 +24,13 @@ public class ActionListDingdingSyncRecord extends BaseAction {
         ActionResult<List<Wo>> result = new ActionResult<>();
         try ( EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
             Business business = new Business(emc);
-            List<DingdingQywxSyncRecord> list = business.dingdingAttendanceFactory().findAllDingdingSyncRecord();
+            List<DingdingQywxSyncRecord> list = business.dingdingAttendanceFactory().findAllSyncRecordWithType(DingdingQywxSyncRecord.syncType_dingding);
             if (list != null && !list.isEmpty()) {
                 List<Wo> wos = list.stream().map(record -> {
                     Wo wo = new Wo();
                     try {
                         wo = Wo.copier.copy(record, wo);
+                        wo.formatDate();
                     }catch (Exception e) {
                         logger.error(e);
                     }
@@ -43,5 +46,36 @@ public class ActionListDingdingSyncRecord extends BaseAction {
     public static class Wo extends DingdingQywxSyncRecord {
         static final WrapCopier<DingdingQywxSyncRecord, Wo> copier =
                 WrapCopierFactory.wo(DingdingQywxSyncRecord.class, Wo.class, null, JpaObject.FieldsInvisible);
+
+        @FieldDescribe("同步打卡记录的开始时间")
+        private Date dateFromFormat;
+        @FieldDescribe("同步打卡记录的结束时间")
+        private Date dateToFormat;
+
+        public void formatDate() {
+            Date date = new Date();
+            date.setTime(getDateFrom());
+            setDateFromFormat(date);
+            Date dateto = new Date();
+            dateto.setTime(getDateTo());
+            setDateToFormat(dateto);
+        }
+
+
+        public Date getDateFromFormat() {
+            return dateFromFormat;
+        }
+
+        public void setDateFromFormat(Date dateFromFormat) {
+            this.dateFromFormat = dateFromFormat;
+        }
+
+        public Date getDateToFormat() {
+            return dateToFormat;
+        }
+
+        public void setDateToFormat(Date dateToFormat) {
+            this.dateToFormat = dateToFormat;
+        }
     }
 }

+ 45 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/ActionDeleteAllQywxAttendanceData.java

@@ -0,0 +1,45 @@
+package com.x.attendance.assemble.control.jaxrs.qywx;
+
+
+
+import com.x.attendance.entity.AttendanceQywxDetail;
+import com.x.base.core.container.EntityManagerContainer;
+import com.x.base.core.container.factory.EntityManagerContainerFactory;
+import com.x.base.core.project.http.ActionResult;
+import com.x.base.core.project.http.EffectivePerson;
+import com.x.base.core.project.jaxrs.WrapBoolean;
+import com.x.base.core.project.logger.Logger;
+import com.x.base.core.project.logger.LoggerFactory;
+
+import java.util.List;
+
+public class ActionDeleteAllQywxAttendanceData extends BaseAction {
+
+    private static final Logger logger = LoggerFactory.getLogger(ActionDeleteAllQywxAttendanceData.class);
+
+
+    public ActionResult<WrapBoolean> execute(EffectivePerson effectivePerson) {
+        ActionResult<WrapBoolean> result = new ActionResult<>();
+
+        try ( EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+            List<AttendanceQywxDetail> details  = emc.listAll(AttendanceQywxDetail.class);
+            if ( null != details && !details.isEmpty() ) {
+                //进行数据库持久化操作
+                emc.beginTransaction( AttendanceQywxDetail.class );
+                for (AttendanceQywxDetail d : details) {
+                    emc.remove(d);
+                }
+                emc.commit();
+                logger.info( "成功删除所有的企业微信打卡数据信息"  );
+            }
+            result.setData(new WrapBoolean(true));
+        } catch ( Exception e ) {
+            result.error(e);
+            logger.error(e);
+        }
+
+        return result;
+    }
+
+
+}

+ 117 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/ActionListQywxAttendanceDetail.java

@@ -0,0 +1,117 @@
+package com.x.attendance.assemble.control.jaxrs.qywx;
+
+import com.google.gson.JsonElement;
+import com.x.attendance.assemble.control.Business;
+import com.x.attendance.assemble.control.jaxrs.dingding.BaseAction;
+import com.x.attendance.entity.AttendanceQywxDetail;
+import com.x.base.core.container.EntityManagerContainer;
+import com.x.base.core.container.factory.EntityManagerContainerFactory;
+import com.x.base.core.entity.JpaObject;
+import com.x.base.core.project.annotation.FieldDescribe;
+import com.x.base.core.project.bean.WrapCopier;
+import com.x.base.core.project.bean.WrapCopierFactory;
+import com.x.base.core.project.gson.GsonPropertyObject;
+import com.x.base.core.project.http.ActionResult;
+import com.x.base.core.project.logger.Logger;
+import com.x.base.core.project.logger.LoggerFactory;
+import com.x.base.core.project.organization.Person;
+import com.x.base.core.project.tools.DateTools;
+
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class ActionListQywxAttendanceDetail extends BaseAction {
+
+    private static final Logger logger = LoggerFactory.getLogger(ActionListQywxAttendanceDetail.class);
+
+    public ActionResult<List<Wo>> execute(JsonElement jsonElement) throws Exception {
+        ActionResult<List<Wo>> result = new ActionResult<>();
+        try ( EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+            Business business = new Business(emc);
+            Wi wi = this.convertToWrapIn(jsonElement , Wi.class);
+            Date start = DateTools.parseDateTime(wi.getStartTime());
+            Date end = DateTools.parseDateTime(wi.getEndTime());
+            String qywxUser = null;
+            //转化成企业微信的id
+            if (wi.getPerson() != null && !wi.getPerson().isEmpty()) {
+                Person person = business.organization().person().getObject(wi.getPerson());
+                qywxUser = person.getQiyeweixinId();
+            }
+            List<AttendanceQywxDetail> list = business.dingdingAttendanceFactory().findQywxAttendanceDetail(start, end, qywxUser);
+            if (list != null && !list.isEmpty()) {
+                List<Wo> wos = list.stream().map(detail -> {
+                    Wo wo = new Wo();
+                    try {
+                        wo = Wo.copier.copy(detail, wo);
+                        wo.formatDateTime();
+                    }catch (Exception e) {
+                        logger.error(e);
+                    }
+                    return wo;
+                }).collect(Collectors.toList());
+                result.setData(wos);
+            }
+        }
+        return result;
+    }
+
+    public static class Wi extends GsonPropertyObject {
+        @FieldDescribe("开始时间:yyyy-MM-dd HH:mm:ss")
+        private String startTime;
+        @FieldDescribe("结束时间:yyyy-MM-dd HH:mm:ss")
+        private String endTime;
+        @FieldDescribe("人员")
+        private String person;
+
+
+        public String getStartTime() {
+            return startTime;
+        }
+
+        public void setStartTime(String startTime) {
+            this.startTime = startTime;
+        }
+
+        public String getEndTime() {
+            return endTime;
+        }
+
+        public void setEndTime(String endTime) {
+            this.endTime = endTime;
+        }
+
+        public String getPerson() {
+            return person;
+        }
+
+        public void setPerson(String person) {
+            this.person = person;
+        }
+    }
+
+    public static class Wo extends AttendanceQywxDetail {
+        static final WrapCopier<AttendanceQywxDetail, Wo> copier =
+                WrapCopierFactory.wo(AttendanceQywxDetail.class, Wo.class, null, JpaObject.FieldsInvisible);
+
+        @FieldDescribe("实际打卡时间")
+        private Date checkTimeFormat;
+
+
+        public void formatDateTime() {
+            if (checkTimeFormat == null) {
+                Date date = new Date();
+                date.setTime(getCheckin_time());
+                setCheckTimeFormat(date);
+            }
+        }
+
+        public Date getCheckTimeFormat() {
+            return checkTimeFormat;
+        }
+
+        public void setCheckTimeFormat(Date checkTimeFormat) {
+            this.checkTimeFormat = checkTimeFormat;
+        }
+    }
+}

+ 87 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/ActionListQywxSyncRecord.java

@@ -0,0 +1,87 @@
+package com.x.attendance.assemble.control.jaxrs.qywx;
+
+import com.x.attendance.assemble.control.Business;
+import com.x.attendance.assemble.control.jaxrs.dingding.BaseAction;
+import com.x.attendance.entity.DingdingQywxSyncRecord;
+import com.x.base.core.container.EntityManagerContainer;
+import com.x.base.core.container.factory.EntityManagerContainerFactory;
+import com.x.base.core.entity.JpaObject;
+import com.x.base.core.project.annotation.FieldDescribe;
+import com.x.base.core.project.bean.WrapCopier;
+import com.x.base.core.project.bean.WrapCopierFactory;
+import com.x.base.core.project.http.ActionResult;
+import com.x.base.core.project.logger.Logger;
+import com.x.base.core.project.logger.LoggerFactory;
+
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class ActionListQywxSyncRecord extends BaseAction {
+
+    private static final Logger logger = LoggerFactory.getLogger(ActionListQywxSyncRecord.class);
+
+    public ActionResult<List<Wo>> execute() throws Exception {
+        ActionResult<List<Wo>> result = new ActionResult<>();
+        try ( EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+            Business business = new Business(emc);
+            List<DingdingQywxSyncRecord> list = business.dingdingAttendanceFactory().findAllSyncRecordWithType(DingdingQywxSyncRecord.syncType_qywx);
+            if (list != null && !list.isEmpty()) {
+                List<Wo> wos = list.stream().map(record -> {
+                    Wo wo = new Wo();
+                    try {
+                        wo = Wo.copier.copy(record, wo);
+                        wo.formatDate();
+                    }catch (Exception e) {
+                        logger.error(e);
+                    }
+                    return wo;
+                }).collect(Collectors.toList());
+                result.setData(wos);
+            }
+        }
+        return result;
+    }
+
+
+    public static class Wo extends DingdingQywxSyncRecord {
+        static final WrapCopier<DingdingQywxSyncRecord, Wo> copier =
+                WrapCopierFactory.wo(DingdingQywxSyncRecord.class, Wo.class, null, JpaObject.FieldsInvisible);
+
+
+        @FieldDescribe("同步打卡记录的开始时间")
+        private Date dateFromFormat;
+        @FieldDescribe("同步打卡记录的结束时间")
+        private Date dateToFormat;
+
+        public void formatDate() {
+            if (dateFromFormat == null) {
+                Date date = new Date();
+                date.setTime(getDateFrom());
+                setDateFromFormat(date);
+            }
+            if (dateToFormat == null) {
+                Date dateto = new Date();
+                dateto.setTime(getDateTo());
+                setDateToFormat(dateto);
+            }
+        }
+
+
+        public Date getDateFromFormat() {
+            return dateFromFormat;
+        }
+
+        public void setDateFromFormat(Date dateFromFormat) {
+            this.dateFromFormat = dateFromFormat;
+        }
+
+        public Date getDateToFormat() {
+            return dateToFormat;
+        }
+
+        public void setDateToFormat(Date dateToFormat) {
+            this.dateToFormat = dateToFormat;
+        }
+    }
+}

+ 54 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/ActionSyncQywxData.java

@@ -0,0 +1,54 @@
+package com.x.attendance.assemble.control.jaxrs.qywx;
+
+import com.x.attendance.assemble.control.ThisApplication;
+import com.x.attendance.assemble.control.jaxrs.dingding.MoreThanSevenDayException;
+import com.x.attendance.entity.DingdingQywxSyncRecord;
+import com.x.base.core.container.EntityManagerContainer;
+import com.x.base.core.container.factory.EntityManagerContainerFactory;
+import com.x.base.core.project.http.ActionResult;
+import com.x.base.core.project.http.EffectivePerson;
+import com.x.base.core.project.jaxrs.WrapBoolean;
+import com.x.base.core.project.logger.Logger;
+import com.x.base.core.project.logger.LoggerFactory;
+import com.x.base.core.project.tools.DateTools;
+
+import java.util.Date;
+
+
+public class ActionSyncQywxData extends BaseAction {
+
+    private static final Logger logger = LoggerFactory.getLogger(ActionSyncQywxData.class);
+
+    public ActionResult<WrapBoolean> execute(EffectivePerson effectivePerson, String dateFrom, String dateTo) throws Exception {
+        ActionResult<WrapBoolean> result = new ActionResult<>();
+        try ( EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+            if (null == dateFrom || null == dateTo) {
+                throw new SyncDateException();
+            }
+            Date from = DateTools.parse(dateFrom);
+            Date to = DateTools.parse(dateTo);
+            long gap = to.getTime() - from.getTime();
+            if (gap < 0) {
+                throw new SyncDateException();
+            }
+            if ((gap / (1000 * 60 * 60 * 24)) > 6 ) {
+                throw new MoreThanSevenDayException();
+            }
+            Date wxTo = DateTools.parse(dateTo+" 23:59:59");//企业微信和钉钉查询时间不一样
+            DingdingQywxSyncRecord record = new DingdingQywxSyncRecord();
+            record.setDateFrom(from.getTime());
+            record.setDateTo(wxTo.getTime());
+            record.setStartTime(new Date());
+            record.setType(DingdingQywxSyncRecord.syncType_qywx);
+            record.setStatus(DingdingQywxSyncRecord.status_loading);
+            emc.beginTransaction(DingdingQywxSyncRecord.class);
+            emc.persist(record);
+            emc.commit();
+            //企业微信的处理队列
+            ThisApplication.qywxQueue.send(record);
+            result.setData(new WrapBoolean(true));
+        }
+        return result;
+    }
+
+}

+ 6 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/BaseAction.java

@@ -0,0 +1,6 @@
+package com.x.attendance.assemble.control.jaxrs.qywx;
+
+import com.x.base.core.project.jaxrs.StandardJaxrsAction;
+
+public class BaseAction extends StandardJaxrsAction {
+}

+ 103 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/QywxAttendanceAction.java

@@ -0,0 +1,103 @@
+package com.x.attendance.assemble.control.jaxrs.qywx;
+
+import com.google.gson.JsonElement;
+import com.x.base.core.project.annotation.JaxrsDescribe;
+import com.x.base.core.project.annotation.JaxrsMethodDescribe;
+import com.x.base.core.project.annotation.JaxrsParameterDescribe;
+import com.x.base.core.project.http.ActionResult;
+import com.x.base.core.project.http.EffectivePerson;
+import com.x.base.core.project.http.HttpMediaType;
+import com.x.base.core.project.jaxrs.ResponseFactory;
+import com.x.base.core.project.jaxrs.StandardJaxrsAction;
+import com.x.base.core.project.jaxrs.WrapBoolean;
+import com.x.base.core.project.logger.Logger;
+import com.x.base.core.project.logger.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.*;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import java.util.List;
+
+@Path("qywx")
+@JaxrsDescribe("企业微信打卡数据管理")
+public class QywxAttendanceAction  extends StandardJaxrsAction {
+
+    private static final Logger logger = LoggerFactory.getLogger(QywxAttendanceAction.class);
+
+
+    //删除所有打卡数据
+    @JaxrsMethodDescribe(value = "删除所有打卡数据", action = ActionDeleteAllQywxAttendanceData.class)
+    @DELETE
+    @Path("all")
+    @Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void deleteAllData(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request) {
+        ActionResult<WrapBoolean> result = new ActionResult<>();
+        EffectivePerson effectivePerson = this.effectivePerson(request);
+        try {
+            result = new ActionDeleteAllQywxAttendanceData().execute(effectivePerson);
+        }catch (Exception e) {
+            logger.error(e, effectivePerson, request, null);
+            result.error(e);
+        }
+        asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
+    }
+
+
+    //
+    @JaxrsMethodDescribe(value = "同步企业微信考勤结果", action = ActionSyncQywxData.class)
+    @GET
+    @Path("sync/from/{dateFrom}/to/{dateTo}/start")
+    @Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void syncData(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request,
+                         @JaxrsParameterDescribe("开始时间: yyyy-MM-dd") @PathParam("dateFrom") String dateFrom,
+                         @JaxrsParameterDescribe("结束时间: yyyy-MM-dd") @PathParam("dateTo") String dateTo) {
+        ActionResult<WrapBoolean> result = new ActionResult<>();
+        EffectivePerson effectivePerson = this.effectivePerson(request);
+        try {
+            result = new ActionSyncQywxData().execute(effectivePerson, dateFrom, dateTo);
+        }catch (Exception e) {
+            logger.error(e, effectivePerson, request, null);
+            result.error(e);
+        }
+        asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
+    }
+
+    @JaxrsMethodDescribe(value = "查询企业微信同步记录信息", action = ActionListQywxSyncRecord.class)
+    @GET
+    @Path("sync/list")
+    @Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void listDingdingSyncRecord(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request) {
+        ActionResult<List<ActionListQywxSyncRecord.Wo>> result = new ActionResult<>();
+        EffectivePerson effectivePerson = this.effectivePerson(request);
+        try {
+            result = new ActionListQywxSyncRecord().execute();
+        }catch (Exception e) {
+            logger.error(e, effectivePerson, request, null);
+            result.error(e);
+        }
+        asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
+    }
+
+    @JaxrsMethodDescribe(value = "查询企业微信打卡结果", action = ActionListQywxAttendanceDetail.class)
+    @PUT
+    @Path("attendance/list")
+    @Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void listDingdingAttendance(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request, JsonElement jsonElement) {
+        ActionResult<List<ActionListQywxAttendanceDetail.Wo>> result = new ActionResult<>();
+        EffectivePerson effectivePerson = this.effectivePerson(request);
+        try {
+            result = new ActionListQywxAttendanceDetail().execute(jsonElement);
+        }catch (Exception e) {
+            logger.error(e, effectivePerson, request, null);
+            result.error(e);
+        }
+        asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
+    }
+}

+ 13 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/SyncDateException.java

@@ -0,0 +1,13 @@
+package com.x.attendance.assemble.control.jaxrs.qywx;
+
+import com.x.base.core.project.exception.PromptException;
+
+public class SyncDateException extends PromptException {
+
+
+    private static final long serialVersionUID = -6409463169780597687L;
+
+    public SyncDateException() {
+        super("传入的同步时间不正确!");
+    }
+}

+ 59 - 1
o2server/x_base_core_project/src/main/java/com/x/base/core/project/config/Qiyeweixin.java

@@ -44,6 +44,13 @@ public class Qiyeweixin extends ConfigObject {
 	private Boolean messageEnable;
 	@FieldDescribe("企业微信扫码登录")
 	private Boolean scanLoginEnable;
+	@FieldDescribe("是否启用考勤信息")
+	private Boolean attendanceSyncEnable;
+
+	@FieldDescribe("企业微信考勤打卡应用id")
+	private String attendanceSyncAgentId;
+	@FieldDescribe("企业微信考勤打卡应用secret")
+	private String attendanceSyncSecret;
 
 	public static Qiyeweixin defaultInstance() {
 		return new Qiyeweixin();
@@ -61,6 +68,7 @@ public class Qiyeweixin extends ConfigObject {
 	public static final String default_messageRedirectPortal = "";
 	public static final Boolean default_messageEanble = false;
 	public static final Boolean default_scanLoginEnable = false;
+	public static final Boolean default_attendanceSyncEnable = false;
 
 	public Qiyeweixin() {
 		this.enable = default_enable;
@@ -75,9 +83,14 @@ public class Qiyeweixin extends ConfigObject {
 		this.workUrl = default_workUrl;
 		this.messageRedirectPortal = default_messageRedirectPortal;
 		this.scanLoginEnable = default_scanLoginEnable;
-
+		this.attendanceSyncEnable = default_attendanceSyncEnable;
+		this.attendanceSyncAgentId = "";
+		this.attendanceSyncSecret = "";
 	}
 
+	private static String cacheAttendanceAccessToken;
+	private static Date cacheAttendanceAccessTokenDate;
+
 	private static String cachedCorpAccessToken;
 	private static Date cachedCorpAccessTokenDate;
 
@@ -142,6 +155,26 @@ public class Qiyeweixin extends ConfigObject {
 		}
 	}
 
+	public String attendanceAccessToken() throws Exception {
+		if ((StringUtils.isNotEmpty(cacheAttendanceAccessToken) && (null != cacheAttendanceAccessTokenDate))
+				&& (cacheAttendanceAccessTokenDate.after(new Date()))) {
+			return cacheAttendanceAccessToken;
+		} else {
+			String address = default_apiAddress + "/cgi-bin/gettoken?corpid=" + this.getCorpId() + "&corpsecret="
+					+ this.getAttendanceSyncSecret();
+			CorpAccessTokenResp resp = HttpConnection.getAsObject(address, null, CorpAccessTokenResp.class);
+			if (resp.getErrcode() != 0) {
+				throw new ExceptionQiyeweixinCorpAccessToken(resp.getErrcode(), resp.getErrmsg());
+			}
+			cacheAttendanceAccessToken = resp.getAccess_token();
+			Calendar cal = Calendar.getInstance();
+			cal.add(Calendar.MINUTE, 90);
+
+			cacheAttendanceAccessTokenDate = cal.getTime();
+			return cacheAttendanceAccessToken;
+		}
+	}
+
 	public String syncAccessToken() throws Exception {
 		if ((StringUtils.isNotEmpty(cachedSyncAccessToken) && (null != cachedSyncAccessTokenDate))
 				&& (cachedSyncAccessTokenDate.after(new Date()))) {
@@ -386,6 +419,31 @@ public class Qiyeweixin extends ConfigObject {
 		this.scanLoginEnable = scanLoginEnable;
 	}
 
+
+	public Boolean getAttendanceSyncEnable() {
+		return attendanceSyncEnable;
+	}
+
+	public void setAttendanceSyncEnable(Boolean attendanceSyncEnable) {
+		this.attendanceSyncEnable = attendanceSyncEnable;
+	}
+
+	public String getAttendanceSyncAgentId() {
+		return attendanceSyncAgentId;
+	}
+
+	public void setAttendanceSyncAgentId(String attendanceSyncAgentId) {
+		this.attendanceSyncAgentId = attendanceSyncAgentId;
+	}
+
+	public String getAttendanceSyncSecret() {
+		return attendanceSyncSecret;
+	}
+
+	public void setAttendanceSyncSecret(String attendanceSyncSecret) {
+		this.attendanceSyncSecret = attendanceSyncSecret;
+	}
+
 	public void save() throws Exception {
 		File file = new File(Config.base(), Config.PATH_CONFIG_QIYEWEIXIN);
 		FileUtils.write(file, XGsonBuilder.toJson(this), DefaultCharset.charset);