lfx 8 months ago
parent
commit
e676f24e34

+ 21 - 3
src/main/java/com/muzhi/tianhe/controller/TbController.java

@@ -1,5 +1,6 @@
 package com.muzhi.tianhe.controller;
 package com.muzhi.tianhe.controller;
 
 
+import com.alibaba.fastjson.JSONObject;
 import com.malk.server.common.McR;
 import com.malk.server.common.McR;
 import com.muzhi.tianhe.entity.Tianhe;
 import com.muzhi.tianhe.entity.Tianhe;
 import com.muzhi.tianhe.entity.vo.TianheDataVo;
 import com.muzhi.tianhe.entity.vo.TianheDataVo;
@@ -19,7 +20,10 @@ import org.springframework.web.bind.annotation.*;
 
 
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.io.IOException;
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
 import java.util.List;
 import java.util.List;
+import java.util.Map;
 
 
 /**
 /**
  * @Author ZhangKan
  * @Author ZhangKan
@@ -60,9 +64,11 @@ public class TbController {
         return McR.success();
         return McR.success();
     }
     }
 
 
-    @GetMapping("getCust")
-    public McR getCust(@RequestParam String projectId,@RequestParam String sfcId){
-        return McR.success(tbApiService.getProjectCustomfield(projectId,sfcId));
+    // 同步某个项目的信息
+    @GetMapping("syncStateByPid")
+    public McR getCust(@RequestParam String projectId){
+        thTbService.syncStateProject(projectId);
+        return McR.success();
     }
     }
 
 
     @GetMapping("syncProject")
     @GetMapping("syncProject")
@@ -109,4 +115,16 @@ public class TbController {
         TianheDataVo tianheTB = tbService.getTianheTB(1,Integer.MAX_VALUE,tianheQuery);
         TianheDataVo tianheTB = tbService.getTianheTB(1,Integer.MAX_VALUE,tianheQuery);
         UtilExcel.exportListByTemplate(response,tianheTB.getData(),Tianhe.class,"周报");
         UtilExcel.exportListByTemplate(response,tianheTB.getData(),Tianhe.class,"周报");
     }
     }
+
+    @PostMapping("taskLock")
+    public McR taskLock(@RequestBody JSONObject param){
+        thTbService.taskLock(param.getString("projectId"),param.getString("taskType"),param.getJSONArray("keys").toJavaList(String.class),param.getString("type").equals("add"));
+        return McR.success();
+    }
+
+    @PostMapping("taskUpdate")
+    public McR taskUpdate(@RequestBody JSONObject param){
+        return thTbService.taskUpdate(param);
+    }
+
 }
 }

+ 8 - 0
src/main/java/com/muzhi/tianhe/service/ThTbService.java

@@ -1,6 +1,10 @@
 package com.muzhi.tianhe.service;
 package com.muzhi.tianhe.service;
 
 
+import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.alibaba.fastjson.JSONObject;
+import com.malk.server.common.McR;
+
+import java.util.List;
 
 
 public interface ThTbService {
 public interface ThTbService {
 
 
@@ -12,4 +16,8 @@ public interface ThTbService {
 
 
     void syncStateProject(String projectId);
     void syncStateProject(String projectId);
 
 
+    void taskLock(String projectId, String taskType, List<String> keys, Boolean isIdd);
+
+    McR taskUpdate(JSONObject param);
+
 }
 }

+ 6 - 2
src/main/java/com/muzhi/tianhe/service/impl/AccessTokenServiceImpl.java

@@ -7,6 +7,7 @@ import com.dingtalk.api.request.OapiGettokenRequest;
 import com.dingtalk.api.response.OapiGettokenResponse;
 import com.dingtalk.api.response.OapiGettokenResponse;
 import com.muzhi.tianhe.service.AccessTokenService;
 import com.muzhi.tianhe.service.AccessTokenService;
 import com.taobao.api.ApiException;
 import com.taobao.api.ApiException;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 import org.springframework.util.StringUtils;
 import org.springframework.util.StringUtils;
 
 
@@ -54,8 +55,11 @@ public class AccessTokenServiceImpl implements AccessTokenService {
     public static final Long   EXPIRES_IN = 1 * 3600 * 1000L;
     public static final Long   EXPIRES_IN = 1 * 3600 * 1000L;
     public static final String TOKEN_APPID = "_appId";
     public static final String TOKEN_APPID = "_appId";
 
 
-    public static String appId = "65f92ac8046a09f810107919";
-    public static String appSecret = "fy9OcKys3ZMdPnj0jvATrni5LvEn8ksk";
+    @Value("${teambition.AppID}")
+    public String appId;
+
+    @Value("${teambition.AppSecret}")
+    public String appSecret;
     //获取access_token的方法(TB应用)
     //获取access_token的方法(TB应用)
     @Override
     @Override
     public String getAppToken() {
     public String getAppToken() {

+ 57 - 5
src/main/java/com/muzhi/tianhe/service/impl/TbApiService.java

@@ -4,15 +4,20 @@ import cn.hutool.http.HttpRequest;
 import cn.hutool.http.HttpUtil;
 import cn.hutool.http.HttpUtil;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.alibaba.fastjson.JSONObject;
+import com.malk.utils.UtilMap;
 import com.muzhi.tianhe.service.AccessTokenService;
 import com.muzhi.tianhe.service.AccessTokenService;
 import com.muzhi.tianhe.util.PublicUtil;
 import com.muzhi.tianhe.util.PublicUtil;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
 import java.util.Arrays;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 
 @Service
 @Service
 @Slf4j
 @Slf4j
@@ -21,12 +26,15 @@ public class TbApiService {
     @Autowired
     @Autowired
     private AccessTokenService accessTokenService;
     private AccessTokenService accessTokenService;
     //私有化
     //私有化
-    public static String PRIVATE_API_URL = "https://tb.trinasolar.com/gateway";
-//    public static String PRIVATE_API_URL = "https://tb.trinasolar.com/gateway";
 
 
-    public static String DING_CORP_ID = "dinga41ec6a58a4911d0f2c783f7214b6d69";
-    public static String tbOrganizationId = "65b1dca21ab0fa13be993595";
-    public static String tbOperatorId = "65b1dc8355d0f38026a1c3db"; // guhuchen
+    @Value("${teambition.domain}")
+    public String PRIVATE_API_URL;
+    @Value("${dingtalk.corpId}")
+    public String DING_CORP_ID;
+    @Value("${teambition.TenantId}")
+    public String tbOrganizationId;
+    @Value("${teambition.OperatorId}")
+    public String tbOperatorId; // guhuchen
 
 
     public JSONArray getProjectList(){
     public JSONArray getProjectList(){
         JSONObject result=new JSONObject();
         JSONObject result=new JSONObject();
@@ -58,6 +66,11 @@ public class TbApiService {
         return result.getJSONArray("result");
         return result.getJSONArray("result");
     }
     }
 
 
+    public JSONArray getTasksByStageId(String xmid,String stageId){
+        JSONObject result=header(HttpRequest.get(PRIVATE_API_URL + "/v3/project/" + xmid + "/task/query?q=stageId = " + stageId + "&pageSize=1000"));
+        return result.getJSONArray("result");
+    }
+
     public JSONArray getTasksInfo(String taskId){
     public JSONArray getTasksInfo(String taskId){
         JSONObject result=header(HttpRequest.get(PRIVATE_API_URL + "/v3/task/query?taskId="+taskId));
         JSONObject result=header(HttpRequest.get(PRIVATE_API_URL + "/v3/task/query?taskId="+taskId));
         return result.getJSONArray("result");
         return result.getJSONArray("result");
@@ -233,6 +246,15 @@ public class TbApiService {
         return result;
         return result;
     }
     }
 
 
+    public Object updateTaskLock(String taskId,Boolean isAdd,List<Map<String, String>> array){
+        Map map=new HashMap();
+        map.put(isAdd?"add":"del", array);
+        JSONObject result=header(HttpRequest.post(PRIVATE_API_URL + "/v3/task/{taskId}/access-policy/update".replace("{taskId}",taskId))
+                .header("x-operator-id",tbOperatorId)
+                .body(JSONObject.toJSONString(map)));
+        return result;
+    }
+
     public JSONObject addTask(JSONObject param){
     public JSONObject addTask(JSONObject param){
         JSONObject result=header(HttpRequest.post(PRIVATE_API_URL + "/v3/task/create")
         JSONObject result=header(HttpRequest.post(PRIVATE_API_URL + "/v3/task/create")
                 .header("x-operator-id",tbOperatorId)
                 .header("x-operator-id",tbOperatorId)
@@ -240,6 +262,36 @@ public class TbApiService {
         return result;
         return result;
     }
     }
 
 
+    public String getUserId(String id,Boolean isTB){
+        if(StringUtils.isBlank(id)){
+            return "";
+        }
+        JSONObject result=header(HttpUtil.createGet(PRIVATE_API_URL + "/idmap/dingtalk/"+(isTB?"getDingUserId?tbUserIds=":"getTbUserId?dingUserIds=")+id));
+        return result.getJSONArray("result").getJSONObject(0).getString(isTB?"dingtalkUserId":"tbUserId");
+    }
+
+    public List<String> getUserIds(String ids,Boolean isTB){
+        if(StringUtils.isBlank(ids)){
+            return Arrays.asList();
+        }
+        JSONObject result=header(HttpUtil.createGet(PRIVATE_API_URL + "/idmap/dingtalk/"+(isTB?"getDingUserId?tbUserIds=":"getTbUserId?dingUserIds=")+ids));
+        return result.getJSONArray("result").toJavaList(Map.class).stream().map(item->{
+            return UtilMap.getString(item,isTB?"dingtalkUserId":"tbUserId");
+        }).collect(Collectors.toList());
+    }
+
+    public JSONArray getStageId(String projectId,String q){
+        JSONObject result=header(HttpUtil.createGet(PRIVATE_API_URL + "/v3/project/{projectId}/stage/search?q=".replace("{projectId}",projectId)+q));
+        return result.getJSONArray("result");
+    }
+
+
+    public JSONObject addProjectState(String projectId,Map param){
+        JSONObject result=header(HttpRequest.post(PRIVATE_API_URL + "/v3/project/{projectId}/status/create".replace("{projectId}",projectId))
+                .header("x-operator-id",tbOperatorId)
+                .body(JSONObject.toJSONString(param)));
+        return result;
+    }
 
 
 
 
     private JSONObject header(HttpRequest request){
     private JSONObject header(HttpRequest request){

+ 86 - 5
src/main/java/com/muzhi/tianhe/service/impl/TbServiceImpl.java

@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.malk.utils.UtilMap;
 import com.muzhi.tianhe.entity.Tianhe;
 import com.muzhi.tianhe.entity.Tianhe;
 import com.muzhi.tianhe.entity.User;
 import com.muzhi.tianhe.entity.User;
 import com.muzhi.tianhe.entity.vo.TianheDataVo;
 import com.muzhi.tianhe.entity.vo.TianheDataVo;
@@ -20,7 +21,12 @@ import org.springframework.stereotype.Service;
 import org.springframework.util.StringUtils;
 import org.springframework.util.StringUtils;
 
 
 import java.text.SimpleDateFormat;
 import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
 import java.util.*;
 import java.util.*;
+import java.util.stream.Collectors;
 
 
 
 
 @Service
 @Service
@@ -75,6 +81,8 @@ public class TbServiceImpl extends ServiceImpl<TianheMapper, Tianhe> implements
 
 
     // 单个项目同步
     // 单个项目同步
     private void syncData(String xmid){
     private void syncData(String xmid){
+        // 先同步状态
+        synprojectState(xmid);
         /**数据库表*/
         /**数据库表*/
         JSONArray array = getXiangmuRenwuLeixing(xmid);
         JSONArray array = getXiangmuRenwuLeixing(xmid);
         List<String> list=new ArrayList<>();
         List<String> list=new ArrayList<>();
@@ -95,6 +103,51 @@ public class TbServiceImpl extends ServiceImpl<TianheMapper, Tianhe> implements
         }
         }
     }
     }
 
 
+    private void synprojectState(String projectId){
+        JSONObject project=tbApiService.getProjectInfo(projectId).getJSONObject(0);
+        boolean projectEnd=isFuture(project.getString("endDate")); // 项目是否未结束
+        JSONObject state=getProjectState(projectId);
+        String stageName=state.getString("name");
+        String degree=state.getString("degree");
+        if(!projectEnd&&degree.equals("normal")){
+            // 结束且绿灯
+            return;
+        }
+        // 判断项目是否截止
+        // 查询项目下任务列ID
+        String stageId=tbApiService.getStageId(projectId,stageName).getJSONObject(0).getString("id");
+        // 查询项目下任务
+        List<Map> list=tbApiService.getTasksByStageId(projectId,stageId).toJavaList(Map.class);
+        // 循环判断任务是否逾期
+        list=list.stream().filter(item -> UtilMap.getBoolean(item,"isDone") == false &&
+                isFuture(UtilMap.getString(item,"dueDate")) == false
+        ).collect(Collectors.toList());
+        if(list!=null&&list.size()>0){
+            // 任务有逾期情况
+            if(degree.equals("normal")){
+                if(projectEnd){
+                    // 项目未结束
+                    tbApiService.addProjectState(projectId,UtilMap.map("degree, content, name","risky","逾期",stageName)); // 状态绿灯修改为黄灯
+                }else{
+                    // 项目结束
+                    tbApiService.addProjectState(projectId,UtilMap.map("degree, content, name","risky","项目结束逾期",stageName)); // 状态绿灯修改为红灯
+                }
+            }else if(degree.equals("risky")){
+                if(projectEnd){
+
+                }else{
+                    // 项目结束
+                    tbApiService.addProjectState(projectId,UtilMap.map("degree, content, name","risky","项目结束逾期",stageName)); // 状态黄灯修改为红灯
+                }
+            }
+        }else{
+            // 任务没逾期情况
+            if(degree.equals("risky")){
+                tbApiService.addProjectState(projectId,UtilMap.map("degree, content, name","normal","正常",stageName)); // 状态黄灯修改为绿灯
+            }
+        }
+    }
+
     // 获取项目里程碑信息
     // 获取项目里程碑信息
     private void getLichengbei(String rwlxid,String xmid){
     private void getLichengbei(String rwlxid,String xmid){
         JSONArray array=tbApiService.getTasksByType(xmid,rwlxid);
         JSONArray array=tbApiService.getTasksByType(xmid,rwlxid);
@@ -141,9 +194,9 @@ public class TbServiceImpl extends ServiceImpl<TianheMapper, Tianhe> implements
     private String getColor(String degree){
     private String getColor(String degree){
         if(degree.equals("normal")){
         if(degree.equals("normal")){
             return "绿灯";
             return "绿灯";
-        }else if(degree.equals("normal")){
+        }else if(degree.equals("risky")){
             return "黄灯";
             return "黄灯";
-        }else if(degree.equals("normal")){
+        }else if(degree.equals("urgent")){
             return "红灯";
             return "红灯";
         }else{
         }else{
             return "";
             return "";
@@ -170,8 +223,10 @@ public class TbServiceImpl extends ServiceImpl<TianheMapper, Tianhe> implements
             tianhe.setXiangmubianhao(projectId);
             tianhe.setXiangmubianhao(projectId);
 
 
             JSONObject state=getProjectState(projectId);
             JSONObject state=getProjectState(projectId);
-            tianhe.setXiangmuzhuangtai(state.getString("name"));
-            tianhe.setXiangmuzhuangtaicolor(getColor(state.getString("degree")));
+            if(state!=null){
+                tianhe.setXiangmuzhuangtai(state.getString("name"));
+                tianhe.setXiangmuzhuangtaicolor(getColor(state.getString("degree")));
+            }
             JSONArray projects=tbApiService.getProjectInfo(projectId);
             JSONArray projects=tbApiService.getProjectInfo(projectId);
             JSONObject project = projects.getJSONObject(0);
             JSONObject project = projects.getJSONObject(0);
 //            JSONArray customfields = project.getJSONArray("customfields");
 //            JSONArray customfields = project.getJSONArray("customfields");
@@ -519,7 +574,7 @@ public class TbServiceImpl extends ServiceImpl<TianheMapper, Tianhe> implements
     @Override
     @Override
     public void test() {
     public void test() {
 //        getProjectState("65eefd95fc0ff5ef94bd7863");
 //        getProjectState("65eefd95fc0ff5ef94bd7863");
-        syncData("65efed344f2eb1fe4e88c81f");
+        syncData("66e3f8b0ebdbe2340552e91c");
 //        List<User> list=projectAllMember("65eefd4b5a93ac0240065a4e");
 //        List<User> list=projectAllMember("65eefd4b5a93ac0240065a4e");
 //        User user=getDingUserId("65c09f4155d0f38026a1e218");
 //        User user=getDingUserId("65c09f4155d0f38026a1e218");
 //        String list=userSearch("孙雯");
 //        String list=userSearch("孙雯");
@@ -729,4 +784,30 @@ public class TbServiceImpl extends ServiceImpl<TianheMapper, Tianhe> implements
         }
         }
         return result;
         return result;
     }
     }
+
+    /**
+     * 判断给定的时间字符串表示的时间是否在未来(即是否大于当前时间)。
+     *
+     * @param timeStr 时间字符串,格式为 "2024-09-11T10:00:00.000Z"
+     * @return 如果给定的时间在未来则返回 true,否则返回 false。
+     */
+    public static boolean isFuture(String timeStr) {
+        if(org.apache.commons.lang3.StringUtils.isBlank(timeStr)){
+            return true;
+        }
+        // 定义日期时间格式器
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
+        try {
+            // 解析时间字符串为 LocalDateTime 对象
+            LocalDateTime localDateTime = LocalDateTime.parse(timeStr, formatter);
+            // 获取当前时间的 LocalDateTime 对象(以 UTC 时区)
+            LocalDateTime now = LocalDateTime.now(ZoneId.of("UTC"));
+            // 比较给定的时间与当前时间
+            return localDateTime.isAfter(now);
+        } catch (DateTimeParseException e) {
+            // 如果时间字符串格式不正确,则抛出异常
+            throw new IllegalArgumentException("Invalid date format: " + timeStr, e);
+        }
+    }
+
 }
 }

+ 140 - 15
src/main/java/com/muzhi/tianhe/service/impl/ThTbServiceImpl.java

@@ -5,7 +5,9 @@ import com.alibaba.fastjson.JSONObject;
 import com.malk.server.aliwork.YDConf;
 import com.malk.server.aliwork.YDConf;
 import com.malk.server.aliwork.YDParam;
 import com.malk.server.aliwork.YDParam;
 import com.malk.server.aliwork.YDSearch;
 import com.malk.server.aliwork.YDSearch;
+import com.malk.server.common.McR;
 import com.malk.service.aliwork.YDClient;
 import com.malk.service.aliwork.YDClient;
+import com.malk.utils.UtilEnv;
 import com.malk.utils.UtilMap;
 import com.malk.utils.UtilMap;
 import com.muzhi.tianhe.service.ThTbService;
 import com.muzhi.tianhe.service.ThTbService;
 import com.muzhi.tianhe.util.PublicUtil;
 import com.muzhi.tianhe.util.PublicUtil;
@@ -15,6 +17,7 @@ import org.springframework.stereotype.Service;
 
 
 import java.lang.reflect.Array;
 import java.lang.reflect.Array;
 import java.util.*;
 import java.util.*;
+import java.util.stream.Collectors;
 
 
 @Slf4j
 @Slf4j
 @Service
 @Service
@@ -446,10 +449,33 @@ public class ThTbServiceImpl implements ThTbService {
         }
         }
     }
     }
 
 
+    private String _matchFormUuid(String code) {
+        Map<String, String> formUuid = UtilMap.empty();
+        if (UtilEnv.getActiveProfile().equals(UtilEnv.ENV_PROD)) {
+            formUuid.put("TASK", "FORM-B8C09F8900BD43EC8025068B3CB4C4F8SLAD");
+            formUuid.put("CUSTFIELD", "FORM-031FC90C7D3C49A394600D4EE6C037915QSI");
+        } else {
+            formUuid.put("TASK", "FORM-59A439BCB8194F10B8C862C579F285F5KWG6");                // 文件夹/文件版本记录
+            formUuid.put("CUSTFIELD", "FORM-C5B14D2EE4464AED835BA259388E829D9937");            // 文件夹权限
+        }
+        return formUuid.get(code);
+    }
+
     @Override
     @Override
     public void syncStateProject(String projectId) {
     public void syncStateProject(String projectId) {
+        // 同步项目下自定义字段
+        Map<String,String> projectCusts=new HashMap<>();
+        List<JSONObject> projectCoutList=tbApiService.getProjectCustomfield(projectId).toJavaList(JSONObject.class);
+        projectCoutList.forEach(item -> {
+            projectCusts.put(item.getString("name"),item.getString("id"));
+            ydClient.operateData(YDParam.builder().formUuid(_matchFormUuid("CUSTFIELD"))
+                    .formDataJson(JSONObject.toJSONString(UtilMap.map("textField_m0vvz8ni, textField_m0vvz8nj, textField_m0vvz8nk, textField_m0vvz8nl"
+                            ,projectId,"",item.getString("name"),item.getString("id"))))
+                    .searchCondition(JSONObject.toJSONString(Arrays.asList(new YDSearch("textField_m0vvz8nl",item.getString("id"),"自定义字段编号", YDSearch.Type.TEXT_FIELD,YDSearch.Operator.EQ))))
+                    .build(), YDConf.FORM_OPERATION.upsert);
+        });
         // 同步项目下任务状态
         // 同步项目下任务状态
-        JSONObject project = tbApiService.getProjectInfo(projectId).getJSONObject(0);;
+        JSONObject project = tbApiService.getProjectInfo(projectId).getJSONObject(0);
         Map<String,String> map=getProjectStateVule(projectId);
         Map<String,String> map=getProjectStateVule(projectId);
         JSONArray array = getXiangmuRenwuLeixing(projectId);
         JSONArray array = getXiangmuRenwuLeixing(projectId);
         for (int j = 0; j < array.size(); j++) {
         for (int j = 0; j < array.size(); j++) {
@@ -460,24 +486,123 @@ public class ThTbServiceImpl implements ThTbService {
             for (int i = 0; i < tasks.size(); i++) {
             for (int i = 0; i < tasks.size(); i++) {
                 JSONObject task = tasks.getJSONObject(i);
                 JSONObject task = tasks.getJSONObject(i);
                 log.info("同步任务,任务编号:[{}]进度:[{}/{}]",task.getString("id"),i+1,tasks.size());
                 log.info("同步任务,任务编号:[{}]进度:[{}/{}]",task.getString("id"),i+1,tasks.size());
-                ydClient.operateData(YDParam.builder().formUuid("FORM-59A439BCB8194F10B8C862C579F285F5KWG6")
-                        .formDataJson(JSONObject.toJSONString(UtilMap.map("textField_m0rkuewb, textField_m0rkuewd, textField_m0rkuewc, textField_m0rkuewe, selectField_m0rkuewk, textField_m0vtq84z, textField_m0vtq850"
-                                ,task.getString("id"),task.getString("content")
-                                ,projectId,project.getString("name"),map.get(task.getString("tfsId"))
-                                ,array.getJSONObject(j).getString("id"),array.getJSONObject(j).getString("name"))))
+                Map saveMap=UtilMap.map("textField_m0rkuewb, textField_m0rkuewd, textField_m0rkuewc, textField_m0rkuewe, selectField_m0rkuewk, textField_m0vtq84z, textField_m0vtq850"
+                        ,task.getString("id"),task.getString("content")
+                        ,projectId,project.getString("name"),map.get(task.getString("tfsId"))
+                        ,array.getJSONObject(j).getString("id"),array.getJSONObject(j).getString("name"));
+                saveMap.put("textField_m0xldyf3",task.getString("parentTaskId"));
+                JSONArray ancestorIds=task.getJSONArray("ancestorIds");
+                int size=ancestorIds.size();
+                if(size>0){
+                    saveMap.put("textField_m0xldyf8",ancestorIds.getString(size-1));
+                    if(size>1){
+                        saveMap.put("textField_m0xldyf9",ancestorIds.getString(size-2));
+                        if(size>2){
+                            saveMap.put("textField_m0xldyfa",ancestorIds.getString(size-3));
+                        }
+                    }
+                }
+                // 查询任务中的自定义字段值
+                Map<String,JSONArray> taskCustValues=new HashMap<>();
+                List<JSONObject> custs=task.getJSONArray("customfields").toJavaList(JSONObject.class);
+                custs.forEach(item -> taskCustValues.put(item.getString("cfId"),item.getJSONArray("value")));
+                saveMap.put("textField_m0xldyf7",getCustOne(taskCustValues.get(projectCusts.get("权重")),"title"));
+                saveMap.put("employeeField_m0xldyf4",Arrays.asList(tbApiService.getUserId(task.getString("executorId"),true)));
+                saveMap.put("employeeField_m0xldyf5",tbApiService.getUserIds(getCust(taskCustValues.get(projectCusts.get("联席责任人")),"id"),true));
+                saveMap.put("textField_m102rax6",getCustOne(taskCustValues.get(projectCusts.get("责任部门")),"title"));
+                saveMap.put("textField_m102rax9",getCustOne(taskCustValues.get(projectCusts.get("验收依据")),"title"));
+                saveMap.put("textField_m102raxm",getCustOne(taskCustValues.get(projectCusts.get("Q1目标")),"title"));
+                saveMap.put("textField_m10at4ej",getCustOne(taskCustValues.get(projectCusts.get("Q2目标")),"title"));
+                saveMap.put("textField_m10at4ek",getCustOne(taskCustValues.get(projectCusts.get("Q3目标")),"title"));
+                saveMap.put("textField_m10at4el",getCustOne(taskCustValues.get(projectCusts.get("Q4目标")),"title"));
+
+                ydClient.operateData(YDParam.builder().formUuid(_matchFormUuid("TASK"))
+                        .formDataJson(JSONObject.toJSONString(saveMap))
                         .searchCondition(JSONObject.toJSONString(Arrays.asList(new YDSearch("textField_m0rkuewb",task.getString("id"),"taskid", YDSearch.Type.TEXT_FIELD,YDSearch.Operator.EQ))))
                         .searchCondition(JSONObject.toJSONString(Arrays.asList(new YDSearch("textField_m0rkuewb",task.getString("id"),"taskid", YDSearch.Type.TEXT_FIELD,YDSearch.Operator.EQ))))
                         .build(), YDConf.FORM_OPERATION.upsert);
                         .build(), YDConf.FORM_OPERATION.upsert);
             }
             }
         }
         }
-//        // 同步项目下自定义字段
-//        List<JSONObject> projectCoutList=tbApiService.getProjectCustomfield(projectId).toJavaList(JSONObject.class);
-//        projectCoutList.forEach(item -> {
-//            ydClient.operateData(YDParam.builder().formUuid("FORM-C5B14D2EE4464AED835BA259388E829D9937")
-//                    .formDataJson(JSONObject.toJSONString(UtilMap.map("textField_m0vvz8ni, textField_m0vvz8nj, textField_m0vvz8nk, textField_m0vvz8nl"
-//                            ,projectId,item.getString("id"),item.getString("name"),item.getString("id"))))
-//                    .searchCondition(JSONObject.toJSONString(Arrays.asList(new YDSearch("textField_m0vvz8nl",item.getString("id"),"自定义字段编号", YDSearch.Type.TEXT_FIELD,YDSearch.Operator.EQ))))
-//                    .build(), YDConf.FORM_OPERATION.upsert);
-//        });
+    }
+
+    private String getCustOne(JSONArray array,String key){
+        if(array==null||array.size()<1){
+            return "";
+        }else {
+            return array.getJSONObject(0).getString(key);
+        }
+    }
+
+    private String getCust(JSONArray array,String key){
+        if(array==null||array.size()<1){
+            return "";
+        }else {
+            return String.join(",",array.toJavaList(Map.class).stream().map(item->{
+                return UtilMap.getString(item,key);
+            }).collect(Collectors.toList()));
+        }
+    }
+
+    @Override
+    public void taskLock(String projectId, String taskType, List<String> keys, Boolean isAdd) {
+        JSONArray tasks=tbApiService.getTasksByType(projectId,taskType);
+        // 使用流来转换List<String>到List<Map<String, String>>
+        List<Map<String, String>> mapList = keys.stream()
+                .map(name -> Collections.singletonMap("action", name))
+                .collect(Collectors.toList());
+
+        for (int i = 0; i < tasks.size(); i++) {
+            JSONObject task = tasks.getJSONObject(i);
+            log.info(isAdd?"锁定":"解锁"+"任务字段,任务编号:[{}]进度:[{}/{}]",task.getString("id"),i+1,tasks.size());
+            tbApiService.updateTaskLock(task.getString("id"),isAdd,mapList);
+        }
+    }
+
+    @Override
+    public McR taskUpdate(JSONObject param) {
+        String type=param.getString("type");
+        String taskId=param.getString("taskId");
+        if(type.equals("content")){
+            // 标题
+            tbApiService.updateTaskLock(taskId,false,Arrays.asList(UtilMap.map("action","content$update")));
+            tbApiService.updateTaskContent(taskId,param.getString("content"));
+            tbApiService.updateTaskLock(taskId,true,Arrays.asList(UtilMap.map("action","content$update")));
+        }else if(type.equals("executorId")){
+            // 执行人
+            tbApiService.updateTaskLock(taskId,false,Arrays.asList(UtilMap.map("action","executorId$update")));
+            tbApiService.updateTaskExecutor(taskId,tbApiService.getUserId(param.getString("executorId"),false));
+            tbApiService.updateTaskLock(taskId,true,Arrays.asList(UtilMap.map("action","executorId$update")));
+        }else if(type.equals("qz")){
+            // 权重
+            String[] taskIds=taskId.split(",");
+            String[] qzValues=param.getString("qzValues").split(",");
+            if(taskIds.length!=qzValues.length){
+                return McR.error("201","权重任务ID与数据长度不一致");
+            }
+            for (int i = 0; i < taskIds.length; i++) {
+                tbApiService.updateTaskLock(taskIds[i],false,Arrays.asList(UtilMap.map("action","cf:"+param.getString("cId")+"$update")));
+                tbApiService.updateTaskCustomfield(taskIds[i],param.getString("cId"),qzValues[i]);
+                tbApiService.updateTaskLock(taskIds[i],false,Arrays.asList(UtilMap.map("action","cf:"+param.getString("cId")+"$update")));
+            }
+        }else if(type.equals("lxzrr")){
+            // 联系责任人
+            List<String> ids=param.getJSONArray("userIds").toJavaList(String.class);
+            List<String> tbIds=tbApiService.getUserIds(String.join(",",ids),false);
+            JSONArray array=new JSONArray();
+            for (String id:tbIds){
+                array.add(UtilMap.map("id",id));
+            }
+            tbApiService.updateTaskLock(taskId,false,Arrays.asList(UtilMap.map("action","cf:"+param.getString("cId")+"$update")));
+            tbApiService.updateTaskCustomfield(taskId,param.getString("cId"),array);
+            tbApiService.updateTaskLock(taskId,true,Arrays.asList(UtilMap.map("action","cf:"+param.getString("cId")+"$update")));
+        }else if(type.equals("custValue")){
+            // 自定义字段
+            tbApiService.updateTaskLock(taskId,false,Arrays.asList(UtilMap.map("action","cf:"+param.getString("cId")+"$update")));
+            tbApiService.updateTaskCustomfield(taskId,param.getString("cId"),param.getString("cValue"));
+            tbApiService.updateTaskLock(taskId,true,Arrays.asList(UtilMap.map("action","cf:"+param.getString("cId")+"$update")));
+        }else{
+            return McR.error("201","type 错误");
+        }
+        return McR.success();
     }
     }
 
 
     //获取项目任务类型
     //获取项目任务类型

+ 42 - 0
src/main/resources/application-dev.yml

@@ -0,0 +1,42 @@
+server:
+  port: 9001
+  servlet:
+    context-path: /api/tianhe
+spring:
+  datasource:
+    url: jdbc:mysql://47.97.181.40:3306/dingtalk?useSSL=false&characterEncoding=UTF-8&serverTimezone=UTC
+    username: root
+    password: cp-root@2022++
+    driver-class-name: com.mysql.cj.jdbc.Driver
+  jackson:
+    date-format: yyyy-MM-dd HH:mm:ss
+    time-zone: GMT+8
+  thymeleaf:
+    cache: false
+enable:
+  scheduling: false
+logging:
+  config: classpath:logback-spring.xml
+  path: /home/server/tianhe/log/
+  level:
+    com.malk.*: debug
+
+# dingtalk
+dingtalk:
+  agentId: 2691784047
+  appKey: dinghbynhnd2dbgypmsa
+  appSecret: Kl5Xw8x0TlEIlvcJuUkYZD18UTTShJmfdKrAIpY8oX-Q_tazyUKA28nQh7dG5-mq
+  corpId: dingcc1b1ffad0d5ca1d
+  aesKey:
+  token:
+# teambition
+teambition:
+  AppID: 65956b5dd0ac095d62d0e592
+  AppSecret: gjQUoqKa1PHjTiyQFFuachfqKPyNeacA
+  TenantId: 6034c885e71842e1e5bb5218
+  OperatorId: 616fb6f78ad4104a10515809    # 公共账号, 需要有操作权限 [牧语]
+  domain: https://open.teambition.com/api
+# aliwork
+aliwork:
+  appType: APP_JMBVQ7C8A9X2PFBPU111
+  systemToken: RC966V717UBO2D0TERFZ37T0JMRM2MBVQKR0M8OG

+ 43 - 0
src/main/resources/application-prod.yml

@@ -0,0 +1,43 @@
+server:
+  port: 8112
+  servlet:
+    context-path: /tianhe
+spring:
+  datasource:
+#    url: jdbc:mysql://127.0.0.1:3306/dingtalk?useSSL=false&characterEncoding=UTF-8&serverTimezone=UTC
+    url: jdbc:mysql://47.97.181.40:3306/dingtalk?useSSL=false&characterEncoding=UTF-8&serverTimezone=UTC
+    username: root
+    password: cp-root@2022++
+    driver-class-name: com.mysql.cj.jdbc.Driver
+  jackson:
+    date-format: yyyy-MM-dd HH:mm:ss
+    time-zone: GMT+8
+  thymeleaf:
+    cache: false
+enable:
+  scheduling: true
+logging:
+  config: classpath:logback-spring.xml
+  path: /home/server/tianhe/log/
+  level:
+    com.malk.*: info
+
+# dingtalk
+dingtalk:
+  agentId: 2691784047
+  appKey: dinghbynhnd2dbgypmsa
+  appSecret: Kl5Xw8x0TlEIlvcJuUkYZD18UTTShJmfdKrAIpY8oX-Q_tazyUKA28nQh7dG5-mq
+  corpId: dinga41ec6a58a4911d0f2c783f7214b6d69
+  aesKey:
+  token:
+# teambition
+teambition:
+  AppID: 65f92ac8046a09f810107919
+  AppSecret: fy9OcKys3ZMdPnj0jvATrni5LvEn8ksk
+  TenantId: 65b1dca21ab0fa13be993595
+  OperatorId: 65b1dc8355d0f38026a1c3db    # 公共账号, 需要有操作权限 [牧语]
+  domain: https://tb.trinasolar.com:443/gateway
+# aliwork
+aliwork:
+  appType: APP_D5TQPJQBB21M7BCJUMO5
+  systemToken: VF666N9189VOKMBC6QY7B5K4IBR73I6U8BH1M0MB

+ 62 - 62
src/main/resources/application.properties

@@ -1,64 +1,64 @@
-server.port=8112
-server.servlet.context-path=/tianhe
-
-server.tomcat.uri-encoding=UTF-8
-
-## 服务名
-#spring.application.name=yikong-yd
-## 环境设置:dev、test、prod(开发环境、测试环境、生产环境)
-#spring.profiles.active=dev
-
-# mysql数据库连接
-# 本地测试数据库
+#server.port=8112
+#server.servlet.context-path=/tianhe
+#
+#server.tomcat.uri-encoding=UTF-8
+#
+### 服务名
+##spring.application.name=yikong-yd
+### 环境设置:dev、test、prod(开发环境、测试环境、生产环境)
+##spring.profiles.active=dev
+#
+## mysql数据库连接
+## 本地测试数据库
+##spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
+##spring.datasource.url=jdbc:mysql://localhost:3306/dingtalk?serverTimezone=GMT%2B8
+##spring.datasource.username=root
+##spring.datasource.password=123456
+#
 #spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
 #spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
-#spring.datasource.url=jdbc:mysql://localhost:3306/dingtalk?serverTimezone=GMT%2B8
-#spring.datasource.username=root
-#spring.datasource.password=123456
-
-spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
 #spring.datasource.url=jdbc:mysql://47.97.181.40:3306/dingtalk?serverTimezone=GMT%2B8
 #spring.datasource.url=jdbc:mysql://47.97.181.40:3306/dingtalk?serverTimezone=GMT%2B8
-spring.datasource.url=jdbc:mysql://127.0.0.1:3306/dingtalk?serverTimezone=GMT%2B8
-spring.datasource.username=root
-spring.datasource.password=cp-root@2022++
-
-#定时同步
-
-#配置日志,当前为默认的控制台输出,也可以用log4j
-#mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
-
-#控制台显示sql
-#spring.jpa.show-sql=true
-#更新或者创建数据表结构
-#spring.jpa.hibernate.ddl-auto=update
-
-#返回json的全局时间格式
-spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
-spring.jackson.time-zone=GMT+8
-#配置mapper xml文件的路径
-#mybatis-plus.mapper-locations=classpath:com/muzhi/lz/mapper/xml/*.xml
-
-#mybatis-plus.config-location=classpath:mybatis/mybatis-config.xml
-
-#禁用缓存
-spring.thymeleaf.cache=false
-#spring.thymeleaf.mode= LEGACYHTML5
-#spring.resources.chain.strategy.content.enabled=true
-#spring.resources.chain.strategy.content.paths=/**
-#spring.thymeleaf.check-template = true
-#spring.thymeleaf.servlet.content-type=text/html
-#spring.thymeleaf.enabled = true
-#spring.thymeleaf.encoding = UTF-8
-#spring.thymeleaf.prefix = classpath:/static/
-#spring.thymeleaf.suffix = .html
-
-#定义日期提交的格式(日期的格式化):SpingMVC将页面提交的值需要转换为指定类型;默认的是yyyy/MM/dd
-#spring.mvc.date-format=yyyy-MM-dd
-
-spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/templates/
-
-aliwork.appType=APP_JMBVQ7C8A9X2PFBPU111
-aliwork.systemToken=RC966V717UBO2D0TERFZ37T0JMRM2MBVQKR0M8OG
-dingtalk.agentId=2691784047
-dingtalk.appKey=dinghbynhnd2dbgypmsa
-dingtalk.appSecret=Kl5Xw8x0TlEIlvcJuUkYZD18UTTShJmfdKrAIpY8oX-Q_tazyUKA28nQh7dG5-mq
-
+##spring.datasource.url=jdbc:mysql://127.0.0.1:3306/dingtalk?serverTimezone=GMT%2B8
+#spring.datasource.username=root
+#spring.datasource.password=cp-root@2022++
+#
+##定时同步
+#
+##配置日志,当前为默认的控制台输出,也可以用log4j
+##mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
+#
+##控制台显示sql
+##spring.jpa.show-sql=true
+##更新或者创建数据表结构
+##spring.jpa.hibernate.ddl-auto=update
+#
+##返回json的全局时间格式
+#spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
+#spring.jackson.time-zone=GMT+8
+##配置mapper xml文件的路径
+##mybatis-plus.mapper-locations=classpath:com/muzhi/lz/mapper/xml/*.xml
+#
+##mybatis-plus.config-location=classpath:mybatis/mybatis-config.xml
+#
+##禁用缓存
+#spring.thymeleaf.cache=false
+##spring.thymeleaf.mode= LEGACYHTML5
+##spring.resources.chain.strategy.content.enabled=true
+##spring.resources.chain.strategy.content.paths=/**
+##spring.thymeleaf.check-template = true
+##spring.thymeleaf.servlet.content-type=text/html
+##spring.thymeleaf.enabled = true
+##spring.thymeleaf.encoding = UTF-8
+##spring.thymeleaf.prefix = classpath:/static/
+##spring.thymeleaf.suffix = .html
+#
+##定义日期提交的格式(日期的格式化):SpingMVC将页面提交的值需要转换为指定类型;默认的是yyyy/MM/dd
+##spring.mvc.date-format=yyyy-MM-dd
+#
+#spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/templates/
+#
+#aliwork.appType=APP_JMBVQ7C8A9X2PFBPU111
+#aliwork.systemToken=RC966V717UBO2D0TERFZ37T0JMRM2MBVQKR0M8OG
+#dingtalk.agentId=2691784047
+#dingtalk.appKey=dinghbynhnd2dbgypmsa
+#dingtalk.appSecret=Kl5Xw8x0TlEIlvcJuUkYZD18UTTShJmfdKrAIpY8oX-Q_tazyUKA28nQh7dG5-mq
+#

+ 17 - 0
src/main/resources/application.yml

@@ -0,0 +1,17 @@
+spring:
+  profiles:
+    active: dev
+  servlet:
+    multipart:
+      max-file-size: 100MB
+      max-request-size: 100MB
+  http:
+    enabled: false
+mybatis-plus:
+  mappers-locations: mapper/*.xml
+  type-aliases-package: com.muzhi.tianhe.entity
+#  configuration:
+#    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+  global-config:
+    db-config:
+      id-type: auto

+ 61 - 0
src/main/resources/logback-spring.xml

@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration debug="false" scan="false" scanPeriod="60 seconds">
+    <springProperty scope="context" name="LOG_HOME" source="logging.path" defaultValue="/home/server/log/"/>
+    <property name="FileNamePattern" value="${LOG_HOME}%d{yyyyMM}/%d{dd}"/>
+
+    <!-- 定义控制台输出 -->
+    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
+        <layout class="ch.qos.logback.classic.PatternLayout">
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} - [%thread] - %-5level - %logger{50} - %msg%n</pattern>
+        </layout>
+    </appender>
+
+    <appender name="appLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 指定日志文件的名称 -->
+        <!--<file>${FileNamePattern}/info.log</file>-->
+
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${FileNamePattern}/info-%i.log</fileNamePattern>
+            <MaxHistory>30</MaxHistory>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <MaxFileSize>30MB</MaxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+        </rollingPolicy>
+
+        <layout class="ch.qos.logback.classic.PatternLayout">
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%n</pattern>
+        </layout>
+    </appender>
+
+    <appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender">
+        <discriminator>
+            <Key>processid</Key>
+            <DefaultValue>sys</DefaultValue>
+        </discriminator>
+        <sift>
+            <appender name="FILE-${processid}"
+                      class="ch.qos.logback.core.rolling.RollingFileAppender">
+                <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+                    <FileNamePattern>
+                        ${FileNamePattern}/${processid}.log
+                    </FileNamePattern>
+                </rollingPolicy>
+                <layout class="ch.qos.logback.classic.PatternLayout">
+                    <Pattern>
+                        %d{yyyyMMdd:HH:mm:ss.SSS} [%thread] %-5level %msg%n
+                    </Pattern>
+                </layout>
+            </appender>
+        </sift>
+    </appender>
+
+
+    <!-- 日志输出级别 -->
+    <logger name="org.springframework" level="debug"  additivity="false"/>
+    <logger name="com.zitoo.connecter" level="debug"/>
+    <root level="INFO">
+        <appender-ref ref="stdout"/>
+        <appender-ref ref="appLogAppender"/>
+        <appender-ref ref="SIFT"/>
+    </root>
+</configuration>

+ 101 - 1
src/test/java/com/muzhi/tianhe/TbTest.java

@@ -19,12 +19,21 @@ import lombok.extern.slf4j.Slf4j;
 import okhttp3.OkHttpClient;
 import okhttp3.OkHttpClient;
 import okhttp3.Request;
 import okhttp3.Request;
 import okhttp3.Response;
 import okhttp3.Response;
+import org.apache.commons.lang3.StringUtils;
 import org.joda.time.DateTimeUtils;
 import org.joda.time.DateTimeUtils;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.context.SpringBootTest;
 
 
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
 import java.util.*;
 import java.util.*;
+import java.util.stream.Collectors;
 
 
 @Slf4j
 @Slf4j
 @SpringBootTest(classes = TianHeApplication.class)
 @SpringBootTest(classes = TianHeApplication.class)
@@ -46,7 +55,8 @@ public class TbTest {
 //            log.info("u:{}",users);
 //            log.info("u:{}",users);
 //            tbService.test();
 //            tbService.test();
 //            tbService.getDingUserId("");
 //            tbService.getDingUserId("");
-            tbService.asyncXm("661be43bfc0ff5ef94bdd073");
+            tbService.asyncXm("66e3f8b0ebdbe2340552e91c");
+//            Thread.sleep(1000*60);
 //            JSONArray array=tbApiService.getProjectState("65efed344f2eb1fe4e88c81f");
 //            JSONArray array=tbApiService.getProjectState("65efed344f2eb1fe4e88c81f");
 //            System.out.println(array);
 //            System.out.println(array);
         } catch (Exception e) {
         } catch (Exception e) {
@@ -87,7 +97,97 @@ public class TbTest {
     @Test
     @Test
     public void syncStatus(){
     public void syncStatus(){
         thTbService.syncState();
         thTbService.syncState();
+//        thTbService.syncStateProject("66651dd9abba8250461819d6");
+//        String a=tbApiService.getUserId("153620324221442254",false);
+//        System.out.println(a);
 
 
+//        String projectId="66e3f8b0ebdbe2340552e91c";
+//        JSONObject project=tbApiService.getProjectInfo(projectId).getJSONObject(0);
+//        boolean projectEnd=isFuture(project.getString("endDate")); // 项目是否未结束
+//        JSONObject state=getProjectState(projectId);
+//        String stageName=state.getString("name");
+//        String degree=state.getString("degree");
+//        if(!projectEnd&&degree.equals("normal")){
+//            // 结束且绿灯
+//            return;
+//        }
+//        // 判断项目是否截止
+//        // 查询项目下任务列ID
+//        String stageId=tbApiService.getStageId(projectId,stageName).getJSONObject(0).getString("id");
+//        // 查询项目下任务
+//        List<Map> list=tbApiService.getTasksByStageId(projectId,stageId).toJavaList(Map.class);
+//        // 循环判断任务是否逾期
+//        list=list.stream().filter(item -> UtilMap.getBoolean(item,"isDone") == false &&
+//                isFuture(UtilMap.getString(item,"dueDate")) == false
+//        ).collect(Collectors.toList());
+//        if(list!=null&&list.size()>0){
+//            // 任务有逾期情况
+//            if(degree.equals("normal")){
+//                if(projectEnd){
+//                    // 项目未结束
+//                    tbApiService.addProjectState(projectId,UtilMap.map("degree, content, name","risky","逾期",stageName)); // 状态绿灯修改为黄灯
+//                }else{
+//                    // 项目结束
+//                    tbApiService.addProjectState(projectId,UtilMap.map("degree, content, name","risky","项目结束逾期",stageName)); // 状态绿灯修改为红灯
+//                }
+//            }else if(degree.equals("risky")){
+//                if(projectEnd){
+//
+//                }else{
+//                    // 项目结束
+//                    tbApiService.addProjectState(projectId,UtilMap.map("degree, content, name","risky","项目结束逾期",stageName)); // 状态黄灯修改为红灯
+//                }
+//            }
+//        }else{
+//            // 任务没逾期情况
+//            if(degree.equals("risky")){
+//                tbApiService.addProjectState(projectId,UtilMap.map("degree, content, name","normal","正常",stageName)); // 状态黄灯修改为绿灯
+//            }
+//        }
     }
     }
 
 
+    /**
+     * 判断给定的时间字符串表示的时间是否在未来(即是否大于当前时间)。
+     *
+     * @param timeStr 时间字符串,格式为 "2024-09-11T10:00:00.000Z"
+     * @return 如果给定的时间在未来则返回 true,否则返回 false。
+     */
+    public static boolean isFuture(String timeStr) {
+        if(StringUtils.isBlank(timeStr)){
+            return true;
+        }
+        // 定义日期时间格式器
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
+        try {
+            // 解析时间字符串为 LocalDateTime 对象
+            LocalDateTime localDateTime = LocalDateTime.parse(timeStr, formatter);
+            // 获取当前时间的 LocalDateTime 对象(以 UTC 时区)
+            LocalDateTime now = LocalDateTime.now(ZoneId.of("UTC"));
+            // 比较给定的时间与当前时间
+            return localDateTime.isAfter(now);
+        } catch (DateTimeParseException e) {
+            // 如果时间字符串格式不正确,则抛出异常
+            throw new IllegalArgumentException("Invalid date format: " + timeStr, e);
+        }
+    }
+
+    /***
+     * 获取项目状态
+     * @param projectId
+     * @return
+     */
+    public JSONObject getProjectState(String projectId) {
+        try {
+            JSONArray array=tbApiService.getProjectState(projectId);
+            if(array!=null&&array.size()>0){
+                return array.getJSONObject(0);
+            }
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+
+
 }
 }