Explorar o código

Merge branch 'server-165' of uskycloud/usky-modules into master

gez hai 10 meses
pai
achega
173dd1a906
Modificáronse 49 ficheiros con 2885 adicións e 147 borrados
  1. 3 0
      pom.xml
  2. 46 0
      service-agbox/service-agbox-biz/src/main/java/com/usky/agbox/service/job/patrolAgbox.java
  3. 6 1
      service-fire/service-fire-biz/src/main/java/com/usky/fire/service/impl/PatrolInspectionAreaServiceImpl.java
  4. 6 1
      service-fire/service-fire-biz/src/main/java/com/usky/fire/service/impl/PatrolInspectionPersonnelServiceImpl.java
  5. 6 1
      service-fire/service-fire-biz/src/main/java/com/usky/fire/service/impl/PatrolInspectionSiteServiceImpl.java
  6. 14 0
      service-iot/service-iot-biz/pom.xml
  7. 54 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/constant/dingTalkConstant.java
  8. 3 2
      service-iot/service-iot-biz/src/main/java/com/usky/iot/controller/web/PmProjectController.java
  9. 2 8
      service-iot/service-iot-biz/src/main/java/com/usky/iot/controller/web/PmWorkReportController.java
  10. 57 40
      service-iot/service-iot-biz/src/main/java/com/usky/iot/controller/web/WeChatController.java
  11. 5 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/domain/MceMbuser.java
  12. 5 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/domain/PmWorkReport.java
  13. 2 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/mapper/MceMbuserMapper.java
  14. 1 1
      service-iot/service-iot-biz/src/main/java/com/usky/iot/service/PmProjectService.java
  15. 8 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/service/PmWorkReportService.java
  16. 1 1
      service-iot/service-iot-biz/src/main/java/com/usky/iot/service/config/ExecutorConfig.java
  17. 0 34
      service-iot/service-iot-biz/src/main/java/com/usky/iot/service/impl/BaseAppInfoServiceImpl.java
  18. 16 11
      service-iot/service-iot-biz/src/main/java/com/usky/iot/service/impl/PmProjectServiceImpl.java
  19. 262 35
      service-iot/service-iot-biz/src/main/java/com/usky/iot/service/impl/PmWorkReportServiceImpl.java
  20. 4 4
      service-iot/service-iot-biz/src/main/java/com/usky/iot/service/vo/WorkTimeExportVO.java
  21. 1 0
      service-iot/service-iot-biz/src/main/resources/mapper/iot/MceMbuserMapper.xml
  22. 1 0
      service-iot/service-iot-biz/src/main/resources/mapper/iot/PmWorkReportMapper.xml
  23. 81 0
      service-job/pom.xml
  24. 48 0
      service-job/src/main/java/com/ruoyi/job/RuoYiJobApplication.java
  25. 57 0
      service-job/src/main/java/com/ruoyi/job/config/ScheduleConfig.java
  26. 187 0
      service-job/src/main/java/com/ruoyi/job/controller/SysJobController.java
  27. 89 0
      service-job/src/main/java/com/ruoyi/job/controller/SysJobLogController.java
  28. 171 0
      service-job/src/main/java/com/ruoyi/job/domain/SysJob.java
  29. 155 0
      service-job/src/main/java/com/ruoyi/job/domain/SysJobLog.java
  30. 64 0
      service-job/src/main/java/com/ruoyi/job/mapper/SysJobLogMapper.java
  31. 69 0
      service-job/src/main/java/com/ruoyi/job/mapper/SysJobMapper.java
  32. 56 0
      service-job/src/main/java/com/ruoyi/job/service/ISysJobLogService.java
  33. 102 0
      service-job/src/main/java/com/ruoyi/job/service/ISysJobService.java
  34. 86 0
      service-job/src/main/java/com/ruoyi/job/service/SysJobLogServiceImpl.java
  35. 253 0
      service-job/src/main/java/com/ruoyi/job/service/SysJobServiceImpl.java
  36. 39 0
      service-job/src/main/java/com/ruoyi/job/task/RyTask.java
  37. 106 0
      service-job/src/main/java/com/ruoyi/job/util/AbstractQuartzJob.java
  38. 63 0
      service-job/src/main/java/com/ruoyi/job/util/CronUtils.java
  39. 182 0
      service-job/src/main/java/com/ruoyi/job/util/JobInvokeUtil.java
  40. 22 0
      service-job/src/main/java/com/ruoyi/job/util/QuartzDisallowConcurrentExecution.java
  41. 20 0
      service-job/src/main/java/com/ruoyi/job/util/QuartzJobExecution.java
  42. 132 0
      service-job/src/main/java/com/ruoyi/job/util/ScheduleUtils.java
  43. 10 0
      service-job/src/main/resources/banner.txt
  44. 47 0
      service-job/src/main/resources/bootstrap.yaml.bak
  45. 23 0
      service-job/src/main/resources/bootstrap.yml
  46. 94 0
      service-job/src/main/resources/logback.xml
  47. 93 0
      service-job/src/main/resources/mapper/job/SysJobLogMapper.xml
  48. 111 0
      service-job/src/main/resources/mapper/job/SysJobMapper.xml
  49. 22 8
      service-park/service-park-biz/src/main/resources/logback.xml

+ 3 - 0
pom.xml

@@ -81,6 +81,9 @@
 
     <module>service-agbox</module>
 
+
+    <module>service-job</module>
+
   </modules>
           
   

+ 46 - 0
service-agbox/service-agbox-biz/src/main/java/com/usky/agbox/service/job/patrolAgbox.java

@@ -9,6 +9,14 @@ import com.usky.agbox.service.util.HttpClientUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.time.LocalDateTime;
 import java.util.HashMap;
 import java.util.List;
 
@@ -23,6 +31,29 @@ public class patrolAgbox {
     @Autowired
     private PatrolInspectionAgboxService patrolInspectionAgboxService;
 
+    public static void sendMessage(String host, int port, String xmlData) {
+        try (Socket socket = new Socket(host, port);
+             OutputStream os = socket.getOutputStream();
+             Writer writer = new OutputStreamWriter(os)) {
+            String str = "QWCMD:";
+            byte[] byteArray = str.getBytes();
+            // 定义消息头的长度为4个字节
+            byte[] header = new byte[20];
+            ByteBuffer buffer = ByteBuffer.wrap(header);
+            buffer.put(byteArray);
+            // 将XML数据的长度转换为字节,并填充到消息头中
+            buffer.putInt(xmlData.getBytes().length);
+            // 发送消息头
+            os.write(header);
+            // 发送XML数据
+            writer.write(xmlData);
+            writer.flush();
+
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
     /**
      * 获取推送配置信息
      * @return
@@ -75,6 +106,21 @@ public class patrolAgbox {
                 "\":\""+eventVO.get("name").toString()+"\",\"certifiedNo\":\""+eventVO.get("certifiedNo").toString()+"\"}}");
         String resultString = HttpClientUtils.doPost(list.get(0).getAgboxUrl(),map);
         JSONObject resultVO = JSONObject.parseObject(resultString);
+        LocalDateTime now = LocalDateTime.now();
+//        String host = "127.0.0.1";
+        int port = 5901;
+        String xmlData = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>  \n" +
+                "<Agent ID=\"SGT100006088\" Type=\"SG\" Ver=\"1.2.0.0\">\n" +
+                "<DVRHeart State=\"0\" TotalSpace=\"1000\" FreeSpace=\"500\">\n" +
+                "System,smss.exe,csrss.exe\n" +
+                "</DVRHeart>\n" +
+                "<GetTicks/>\n" +
+                "<OperationCmd Type=\"18\" Channel=\"2\" TriggerTime=\""+now+"\" Note=\"5rWL6K+V\" " +
+                "GUID=\"7b726e15-4d45-53e7-f0e0-0abb91156fd7\">\n" +
+                "/9j/4AAQSkZJRgABAQEASABIAAD/2wBDABsSFBcUERsXFhceHBsgKEIrKCUlKFE6PTBCYFVlZF9V\n" +
+                "</OperationCmd>\n" +
+                "</Agent>";
+        sendMessage(URL, port, xmlData);
         return resultVO;
     }
 

+ 6 - 1
service-fire/service-fire-biz/src/main/java/com/usky/fire/service/impl/PatrolInspectionAreaServiceImpl.java

@@ -3,6 +3,8 @@ package com.usky.fire.service.impl;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.ruoyi.common.datascope.annotation.DataScope;
+import com.ruoyi.common.datascope.context.DataScopeContextHolder;
 import com.usky.common.core.exception.BusinessException;
 import com.usky.common.mybatis.core.AbstractCrudService;
 import com.usky.common.security.utils.SecurityUtils;
@@ -21,6 +23,7 @@ import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * <p>
@@ -180,6 +183,7 @@ public class PatrolInspectionAreaServiceImpl extends AbstractCrudService<PatrolI
      * @return
      */
     @Override
+    @DataScope
     public List<PatrolInspectionArea> patrolInspectionAreaList(Integer id, String areaName) {
         LoginUser loginUser = SecurityUtils.getLoginUser();
         String userType = null;
@@ -188,7 +192,8 @@ public class PatrolInspectionAreaServiceImpl extends AbstractCrudService<PatrolI
         }
         LambdaQueryWrapper<PatrolInspectionArea> queryWrapper = Wrappers.lambdaQuery();
         queryWrapper.eq(PatrolInspectionArea::getEnable, 1)
-                .eq(PatrolInspectionArea::getTenantId, SecurityUtils.getTenantId());
+                .eq(PatrolInspectionArea::getTenantId, SecurityUtils.getTenantId())
+                .apply(Objects.nonNull(DataScopeContextHolder.getDataScopeSql()),DataScopeContextHolder.getDataScopeSql());
         if ("00".equals(userType)) {
             queryWrapper.eq(PatrolInspectionArea::getCreator, SecurityUtils.getUsername());
         }

+ 6 - 1
service-fire/service-fire-biz/src/main/java/com/usky/fire/service/impl/PatrolInspectionPersonnelServiceImpl.java

@@ -3,6 +3,8 @@ package com.usky.fire.service.impl;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.ruoyi.common.datascope.annotation.DataScope;
+import com.ruoyi.common.datascope.context.DataScopeContextHolder;
 import com.usky.common.core.bean.ApiResult;
 import com.usky.common.core.bean.CommonPage;
 import com.usky.common.core.exception.BusinessException;
@@ -29,6 +31,7 @@ import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.stream.Collectors;
 
 /**
@@ -174,6 +177,7 @@ public class PatrolInspectionPersonnelServiceImpl extends AbstractCrudService<Pa
 
 
     @Override
+    @DataScope
     public List<PatrolInspectionAreaVo> personnelLeftList() {
         LoginUser loginUser = SecurityUtils.getLoginUser();
         String userType = null;
@@ -182,7 +186,8 @@ public class PatrolInspectionPersonnelServiceImpl extends AbstractCrudService<Pa
         }
         LambdaQueryWrapper<PatrolInspectionArea> queryWrapper = Wrappers.lambdaQuery();
         queryWrapper.eq(PatrolInspectionArea::getEnable, 1)
-                .eq(PatrolInspectionArea::getTenantId, SecurityUtils.getTenantId());
+                .eq(PatrolInspectionArea::getTenantId, SecurityUtils.getTenantId())
+                .apply(Objects.nonNull(DataScopeContextHolder.getDataScopeSql()),DataScopeContextHolder.getDataScopeSql());
         if ("00".equals(userType)) {
             queryWrapper.eq(PatrolInspectionArea::getCreator, SecurityUtils.getUsername());
         }

+ 6 - 1
service-fire/service-fire-biz/src/main/java/com/usky/fire/service/impl/PatrolInspectionSiteServiceImpl.java

@@ -3,6 +3,8 @@ package com.usky.fire.service.impl;
 import com.alibaba.nacos.common.utils.CollectionUtils;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.ruoyi.common.datascope.annotation.DataScope;
+import com.ruoyi.common.datascope.context.DataScopeContextHolder;
 import com.usky.common.core.bean.CommonPage;
 import com.usky.common.core.exception.BusinessException;
 import com.usky.common.mybatis.core.AbstractCrudService;
@@ -27,6 +29,7 @@ import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.stream.Collectors;
 
 /**
@@ -50,6 +53,7 @@ public class PatrolInspectionSiteServiceImpl extends AbstractCrudService<PatrolI
     private PatrolInspectionPlanSiteService planSiteService;
 
     @Override
+    @DataScope
     public List<PatrolInspectionAreaVo> areaLeftList() {
         DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
         LoginUser loginUser = SecurityUtils.getLoginUser();
@@ -59,7 +63,8 @@ public class PatrolInspectionSiteServiceImpl extends AbstractCrudService<PatrolI
         }
         LambdaQueryWrapper<PatrolInspectionArea> queryWrapper = Wrappers.lambdaQuery();
         queryWrapper.eq(PatrolInspectionArea::getEnable, 1)
-                .eq(PatrolInspectionArea::getTenantId, SecurityUtils.getTenantId());
+                .eq(PatrolInspectionArea::getTenantId, SecurityUtils.getTenantId())
+                .apply(Objects.nonNull(DataScopeContextHolder.getDataScopeSql()),DataScopeContextHolder.getDataScopeSql());
         if ("00".equals(userType)) {
             queryWrapper.eq(PatrolInspectionArea::getCreator, SecurityUtils.getUsername());
         }

+ 14 - 0
service-iot/service-iot-biz/pom.xml

@@ -94,6 +94,20 @@
             <artifactId>ruoyi-common-core</artifactId>
         </dependency>
 
+        <!--钉钉-->
+        <!--获取企业accessToken(企业内部应用) 新版SDK-->
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>dingtalk</artifactId>
+            <version>2.1.34</version>
+        </dependency>
+        <!--旧版SDK-->
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>alibaba-dingtalk-service-sdk</artifactId>
+            <version>2.0.0</version>
+        </dependency>
+
     </dependencies>
 
     <build>

+ 54 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/constant/dingTalkConstant.java

@@ -0,0 +1,54 @@
+package com.usky.iot.constant;
+
+public class dingTalkConstant {
+
+    // 钉钉组织id CorpId
+    public static final String DING_TALK_CORP_ID = "dingefbc6eb33c7f3d80";
+
+    // App Id(应用id)
+    public static final String DING_TALK_APP_ID = "e9908c06-c9cf-49a9-b8ae-a80c4a298782";
+
+    // 原企业内部应用AgentId
+    public static final String DING_TALK_AGENT_ID = "3130849582";
+
+    // Client ID (原 AppKey 和 SuiteKey)
+    public static final String DING_TALK_CLIENT_ID = "dingbrwslwgx0bykrjbc";
+
+    // Client Secret (原 AppSecret 和 SuiteSecret)
+    public static final String DING_TALK_CLIENT_SECRET = "G77mhy4tOJJxoBctvmRUIFpGphV39h8dv9hlVI9kJHrq3sLIDBzggwi22mTpLjpa";
+
+    // 企业内部 access_token 的获取URL(2个参数:Client ID 和 Client Secret)
+    public static final String DING_TALK_ACCESS_TOKEN_URL = "https://oapi.dingtalk.com/gettoken";
+
+    // 用户 userId 获取URL      根据手机号获取(2个参数:手机号 mobile ,access_token)
+    public static final String DING_TALK_USERID_URL = "https://oapi.dingtalk.com/topapi/v2/user/getbymobile";
+
+    // 日报模板id
+    public static final String DING_TALK_DAILY_REPORT_TEMPLATE_ID = "153cb1ea26eee62842e14fa40fa92b95";
+
+    // 钉钉日报模板名
+    public static final String DING_TALK_DAILY_REPORT_TEMPLATE_NAME = "日报";
+
+    // 日报是否发送聊天消息到抄送人
+    public static final Boolean DING_TALK_DAILY_REPORT_TO_CHAT = false;
+
+    // 日报内容类型
+    public static final String DING_TALK_DAILY_REPORT_CONTENT_TYPE = "markdown";
+
+    //日报内容key 今日完成工作
+    public static final String DING_TALK_DAILY_REPORT_COMPLETED_WORK = "今日完成工作";
+
+    //日报内容key 明日工作计划
+    public static final String DING_TALK_DAILY_REPORT_TOMORROW_PLAN = "明日工作计划";
+
+    //日报内容key 需协调工作
+    public static final String DING_TALK_DAILY_REPORT_COORDINATE_WORK = "需协调工作";
+
+    // 日志发送到的群id
+    public static final String DING_TALK_DAILY_REPORT_TO_CIDS = "";
+
+    // 钉钉日报发送地址
+    public static final String DING_TALK_CREATE_DAILY_REPORT_URL = "https://oapi.dingtalk.com/topapi/report/create";
+
+
+}

+ 3 - 2
service-iot/service-iot-biz/src/main/java/com/usky/iot/controller/web/PmProjectController.java

@@ -56,8 +56,9 @@ public class PmProjectController {
                                                      @RequestParam(value = "projectStatus", required = false, defaultValue = "0") Integer projectStatus,
                                                      @RequestParam(value = "projectAscription", required = false, defaultValue = "0") Integer projectAscription,
                                                      @RequestParam(value = "pageNum", required = false, defaultValue = "1") Integer pageNum,
-                                                     @RequestParam(value = "pageSize", required = false, defaultValue = "10") Integer pageSize) {
-        return ApiResult.success(pmProjectService.projectList(projectName, projectType, projectStatus, projectAscription, pageNum, pageSize));
+                                                     @RequestParam(value = "pageSize", required = false, defaultValue = "10") Integer pageSize,
+                                                     @RequestParam(value = "projectId", required = false)Integer projectId) {
+        return ApiResult.success(pmProjectService.projectList(projectName, projectType, projectStatus, projectAscription, pageNum, pageSize, projectId));
     }
 
     /**

+ 2 - 8
service-iot/service-iot-biz/src/main/java/com/usky/iot/controller/web/PmWorkReportController.java

@@ -2,18 +2,12 @@ package com.usky.iot.controller.web;
 
 
 import com.usky.common.core.bean.ApiResult;
-import com.usky.iot.domain.PmProject;
 import com.usky.iot.domain.PmWorkReport;
 import com.usky.iot.service.PmProjectService;
 import com.usky.iot.service.PmWorkReportService;
-import com.usky.iot.service.vo.PmProjectWorkTimeVo;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
-import java.time.DayOfWeek;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.temporal.TemporalAdjusters;
 import java.util.List;
 import java.util.Map;
 
@@ -44,8 +38,8 @@ public class PmWorkReportController {
      */
     @GetMapping("/week")
     public ApiResult<List<Map<String, List<PmWorkReport>>>> weekWork(@RequestParam(required = false) String startDate,
-                                                  @RequestParam(required = false) String endDate,
-                                                  @RequestParam(required = false, defaultValue = "0") Integer id) {
+                                                                     @RequestParam(required = false) String endDate,
+                                                                     @RequestParam(required = false, defaultValue = "0") Integer id) {
         return ApiResult.success(pmWorkReportService.weekWork(startDate, endDate, id));
     }
 

+ 57 - 40
service-iot/service-iot-biz/src/main/java/com/usky/iot/controller/web/WeChatController.java

@@ -25,6 +25,8 @@ import me.chanjar.weixin.common.api.WxConsts;
 import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
 import me.chanjar.weixin.mp.api.WxMpService;
 import me.chanjar.weixin.mp.enums.WxMpApiUrl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.ExitCodeEvent;
 import org.springframework.http.ResponseEntity;
@@ -64,6 +66,8 @@ public class WeChatController {
     @Autowired
     private RedisHelper redisHelper;
 
+    private static final Logger LOGGER = LoggerFactory.getLogger(WeChatController.class);
+
     /**
      * 调用的第一个接口,获取微信公众号CODE,获取openid
      */
@@ -114,21 +118,24 @@ public class WeChatController {
      * 跳转首页权限
      */
     @GetMapping("/getPageAuthorization")
-    public void getPageAuthorization() throws UnsupportedEncodingException {
+    public void getPageAuthorization(@RequestParam(value = "openId",required = false) String openId) throws UnsupportedEncodingException {
         String http = "https://";
         String code = request.getParameter("code");
-        String userAgent = request.getHeader("User-Agent");
-        String openId = request.getParameter("openId");
-        if((openId != null) && (StringUtils.isNotBlank(openId))){
+        System.out.println("String openId:  "+openId);
+        LOGGER.info("String openId:  "+openId);
+
+        if((openId != null) && (openId != "")){
             try{
                 LambdaQueryWrapper<MceMbuser> queryWrapper = Wrappers.lambdaQuery();
                 queryWrapper.eq(MceMbuser::getOpenid,openId).last("LIMIT 1");
                 MceMbuser one = mceMbuserService.getOne(queryWrapper);
                 if(one != null){
-                    String sendUrl = http+constant.call_back_domain+constant.WE_CHAT_INDEX+"?openId="+one.getOpenid();
+                    //String sendUrl = http+constant.call_back_domain+constant.WE_CHAT_INDEX+"?openId="+one.getOpenid();
+                    String sendUrl = http+constant.call_back_domain+constant.WE_CHAT_INDEX+"?flag=true";
                     response.sendRedirect(URLDecoder.decode(sendUrl, "UTF-8"));
                 }else{
-                    String sendUrl = http+constant.call_back_domain+constant.WE_CHAT_LOGIN+"?openId="+openId;
+                    //String sendUrl = http+constant.call_back_domain+constant.WE_CHAT_LOGIN+"?openId="+openId;
+                    String sendUrl = http+constant.call_back_domain+constant.WE_CHAT_LOGIN+"?flag=true";
                     response.sendRedirect(URLDecoder.decode(sendUrl, "UTF-8"));
                 }
             }catch (IOException e){
@@ -136,41 +143,51 @@ public class WeChatController {
             }
 
         }else{
-            if(StringUtils.isBlank(code)){
-                String temp = http+constant.call_back_domain+"/prod-api/service-iot"+request.getRequestURI();
-                String url = URLDecoder.decode(temp, "UTF-8");
-                String sendUrl = "https://open.weixin.qq.com/connect/oauth2/authorize?appid="+constant.WE_CHAT_APP_ID+"&redirect_uri="+url+"&response_type=code&scope=snsapi_base&state=abc123#wechat_redirect";
-                try{
-                    System.out.println("sendUrl: "+sendUrl);
-                    response.sendRedirect(sendUrl);
-                }catch (IOException e){
-                    throw new BusinessException(e.getMessage());
-                }
-            }else{
-                System.out.println("code:  "+code);
-                try {
-                    WxOAuth2AccessToken wxOAuth2AccessToken = wxMpService.getOAuth2Service().getAccessToken(code);
-
-                    String openid = wxOAuth2AccessToken.getOpenId();
-                    String access_token = wxOAuth2AccessToken.getAccessToken();
-                    System.out.println("openid:  "+openid);
-                    LambdaQueryWrapper<MceMbuser> queryWrapper = Wrappers.lambdaQuery();
-                    queryWrapper.eq(MceMbuser::getOpenid,openid).last("LIMIT 1");
-                    MceMbuser one = mceMbuserService.getOne(queryWrapper);
-                    if(one != null){
-
-                        String sendUrl = http+constant.call_back_domain+constant.WE_CHAT_INDEX+"?openId="+one.getOpenid();
-                        response.sendRedirect(URLDecoder.decode(sendUrl, "UTF-8"));
-                    }else{
-                        String sendUrl = http+constant.call_back_domain+constant.WE_CHAT_LOGIN+"?openId="+openid;
-                        response.sendRedirect(URLDecoder.decode(sendUrl, "UTF-8"));
-                    }
-
-
-                } catch (Exception e) {
-                    throw new BusinessException(e.getMessage());
-                }
+            try{
+                String sendUrl = http+constant.call_back_domain+constant.WE_CHAT_LOGIN+"?flag=true";
+                response.sendRedirect(URLDecoder.decode(sendUrl, "UTF-8"));
+            }catch (IOException e){
+                throw new BusinessException(e.getMessage());
             }
+//            if(StringUtils.isBlank(code)){
+//                String temp = http+constant.call_back_domain+"/prod-api/service-iot"+request.getRequestURI();
+//                String url = URLDecoder.decode(temp, "UTF-8");
+//                String sendUrl = "https://open.weixin.qq.com/connect/oauth2/authorize?appid="+constant.WE_CHAT_APP_ID+"&redirect_uri="+url+"&response_type=code&scope=snsapi_base&state=abc123#wechat_redirect";
+//                try{
+//                    System.out.println("sendUrl: "+sendUrl);
+//                    LOGGER.info("sendUrl: "+sendUrl);
+//                    response.sendRedirect(sendUrl);
+//                }catch (IOException e){
+//                    throw new BusinessException(e.getMessage());
+//                }
+//            }else{
+//                System.out.println("code:  "+code);
+//                LOGGER.info("code:  "+code);
+//                try {
+//                    WxOAuth2AccessToken wxOAuth2AccessToken = wxMpService.getOAuth2Service().getAccessToken(code);
+//
+//                    String openid = wxOAuth2AccessToken.getOpenId();
+//                    String access_token = wxOAuth2AccessToken.getAccessToken();
+//                    System.out.println("openid:  "+openid);
+//                    LOGGER.info("openid:  "+openid);
+//                    LambdaQueryWrapper<MceMbuser> queryWrapper = Wrappers.lambdaQuery();
+//                    queryWrapper.eq(MceMbuser::getOpenid,openid).last("LIMIT 1");
+//                    MceMbuser one = mceMbuserService.getOne(queryWrapper);
+//                    if(one != null){
+//                        //String sendUrl = http+constant.call_back_domain+constant.WE_CHAT_INDEX+"?openId="+one.getOpenid();
+//                        String sendUrl = http+constant.call_back_domain+constant.WE_CHAT_INDEX;
+//                        response.sendRedirect(URLDecoder.decode(sendUrl, "UTF-8"));
+//                    }else{
+//                        //String sendUrl = http+constant.call_back_domain+constant.WE_CHAT_LOGIN+"?openId="+openid;
+//                        String sendUrl = http+constant.call_back_domain+constant.WE_CHAT_LOGIN;
+//                        response.sendRedirect(URLDecoder.decode(sendUrl, "UTF-8"));
+//                    }
+//
+//
+//                } catch (Exception e) {
+//                    throw new BusinessException(e.getMessage());
+//                }
+//            }
 
         }
 

+ 5 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/domain/MceMbuser.java

@@ -44,6 +44,11 @@ public class MceMbuser implements Serializable {
      */
     private String cids;
 
+    /**
+     * 钉钉用户id
+     */
+    private String dingTalkId;
+
     /**
      * 创建人
      */

+ 5 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/domain/PmWorkReport.java

@@ -110,4 +110,9 @@ public class PmWorkReport implements Serializable {
      */
     @TableField(exist = false)
     private Integer readFlag;
+
+    /**
+     * 是否发送钉钉 默认:0,否;1:是
+     */
+    private Integer sendDingTalk;
 }

+ 2 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/mapper/MceMbuserMapper.java

@@ -2,6 +2,7 @@ package com.usky.iot.mapper;
 
 import com.usky.iot.domain.MceMbuser;
 import com.usky.common.mybatis.core.CrudMapper;
+import org.springframework.stereotype.Repository;
 
 /**
  * <p>
@@ -11,6 +12,7 @@ import com.usky.common.mybatis.core.CrudMapper;
  * @author han
  * @since 2024-05-10
  */
+@Repository
 public interface MceMbuserMapper extends CrudMapper<MceMbuser> {
 
 }

+ 1 - 1
service-iot/service-iot-biz/src/main/java/com/usky/iot/service/PmProjectService.java

@@ -45,7 +45,7 @@ public interface PmProjectService extends CrudService<PmProject> {
      * @param pageSize          页大小
      * @return
      */
-    CommonPage<PmProject> projectList(String projectName, Integer projectType, Integer projectStatus, Integer projectAscription, Integer pageNum, Integer pageSize);
+    CommonPage<PmProject> projectList(String projectName, Integer projectType, Integer projectStatus, Integer projectAscription, Integer pageNum, Integer pageSize, Integer projectId);
 
     /**
      * 查询项目名

+ 8 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/service/PmWorkReportService.java

@@ -1,5 +1,6 @@
 package com.usky.iot.service;
 
+import com.usky.iot.domain.PmWorkContent;
 import com.usky.iot.domain.PmWorkReport;
 import com.usky.common.mybatis.core.CrudService;
 
@@ -31,9 +32,16 @@ public interface PmWorkReportService extends CrudService<PmWorkReport> {
      */
     void addReport(PmWorkReport pmWorkReport);
 
+
     /**
      * 工时计算
      * @return
      */
     List<Map<String, Object>> countTime();
+
+    /**
+     * 同步钉钉
+     * @param workReport 工作报告
+     */
+    void sendDingTalkDailyReport(PmWorkReport workReport, List<PmWorkContent> workContents);
 }

+ 1 - 1
service-iot/service-iot-biz/src/main/java/com/usky/iot/service/config/ExecutorConfig.java

@@ -23,7 +23,7 @@ public class ExecutorConfig {
     /** 缓冲队列大小 */
     private int queueCapacity = 10;
 
-    @Bean
+    @Bean(name = "asyncServiceExecutor")
     public Executor asyncServiceExecutor(){
         log.info("start asyncServiceExecutor++++++++++++++++++++++");
         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

+ 0 - 34
service-iot/service-iot-biz/src/main/java/com/usky/iot/service/impl/BaseAppInfoServiceImpl.java

@@ -105,40 +105,6 @@ public class BaseAppInfoServiceImpl extends AbstractCrudService<BaseAppInfoMappe
         rabbitTemplate.convertAndSend(rabbitMQConfig.patrolFInfoExchange,"",JSONObject.toJSONString(map));
 
         this.save(baseAppInfo);
-        String url = "http://sgdzpic.3322.org:5901";
-        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>  \n" +
-                "<Agent ID=\"SSJCZHQY0001\" Type=\"18\" Ver=\"1.2.0.0\">\n" +
-                "<DVRHeart State=\"1\" TotalSpace=\"XXXXX\" FreeSpace=\"XXXXXX\">\n" +
-                "System,smss.exe,csrss.exe…\n" +
-                "</DVRHeart>\n" +
-                "<GetTicks/>\n" +
-                "<OperationCmd Type=\"18\" Channel=\"0\" TriggerTime=\"2024-06-21 11:19:40\" Note=\"XXXXXX\" GUID=\"XXXXX…\">\n" +
-                "/9j/4AAQSkZJRgABAQEASABIAAD/2wBDABsSFBcUERsXFhceHBsgKEIrKCUlKFE6PTBCYFVlZF9V…\n" +
-                "</OperationCmd>\n" +
-                "</Agent>";
-        CloseableHttpClient httpClient = HttpClients.createDefault();
-        HttpPost httpPost = new HttpPost(url);
-        try {
-            // 设置请求的内容为XML
-            StringEntity entity = new StringEntity(xml);
-            entity.setContentType("application/xml");
-            httpPost.setEntity(entity);
-
-            // 发送请求
-            HttpResponse response = httpClient.execute(httpPost);
-
-            // 打印响应状态和内容
-            System.out.println("Response Status: " + response.getStatusLine().getStatusCode());
-            System.out.println("Response Content: " + EntityUtils.toString(response.getEntity()));
-        } catch (Exception e) {
-            e.printStackTrace();
-        } finally {
-            try {
-                httpClient.close();
-            } catch (IOException e) {
-                e.printStackTrace();
-            }
-        }
 //        if (pushFlag.equals(1)){
 //            JSONObject a = remotePatrolAgboxService.updateHeart(jsonObj.toJSONString());
 //        }

+ 16 - 11
service-iot/service-iot-biz/src/main/java/com/usky/iot/service/impl/PmProjectServiceImpl.java

@@ -120,20 +120,24 @@ public class PmProjectServiceImpl extends AbstractCrudService<PmProjectMapper, P
      * 分页
      *
      * @param projectName       项目名
-     * @param projectType       类型
-     * @param projectStatus     状态
-     * @param projectAscription 项目归属类型
+     * @param projectType       类型(1:人力外包,2:项目研发,3:采购项目,4:过标项目,5:集成项目,6:其他)
+     * @param projectStatus     状态(1:未开始;2;进行中;3:已完成;4:已暂停;5:已作废)
+     * @param projectAscription 项目归属类型(0:全部;1:我负责;2:我参与)
      * @param pageNum           页码
      * @param pageSize          页大小
+     * @param projectId         项目id
      * @return
      */
     @Override
-    public CommonPage<PmProject> projectList(String projectName, Integer projectType, Integer projectStatus, Integer projectAscription, Integer pageNum, Integer pageSize) {
+    public CommonPage<PmProject> projectList(String projectName, Integer projectType, Integer projectStatus, Integer projectAscription, Integer pageNum, Integer pageSize, Integer projectId) {
         IPage<PmProject> page = new Page<>(pageNum, pageSize);
         LambdaQueryWrapper<PmProject> lambdaQuery = Wrappers.lambdaQuery();
-        lambdaQuery.eq(PmProject::getTenantId, SecurityUtils.getTenantId())
-                .eq(PmProject::getDelFlag, 0)
-                .orderByDesc(PmProject::getCreateTime);
+        lambdaQuery.eq(PmProject::getTenantId, SecurityUtils.getTenantId()).eq(PmProject::getDelFlag, 0);
+        if (projectId != null && projectId!= 0){
+            lambdaQuery.eq(PmProject::getId, projectId);
+            page = this.page(page, lambdaQuery);
+            return new CommonPage<>(page.getRecords(), page.getTotal(), pageSize, pageNum);
+        }
         if (projectAscription == 0) {
             lambdaQuery.and(q -> q.eq(PmProject::getProjectHead, SecurityUtils.getUserId())
                     .or().apply("FIND_IN_SET('" + SecurityUtils.getUserId() + "', project_member) > 0")
@@ -152,6 +156,7 @@ public class PmProjectServiceImpl extends AbstractCrudService<PmProjectMapper, P
         if (projectStatus != 0) {
             lambdaQuery.eq(PmProject::getProjectStatus, projectStatus);
         }
+        lambdaQuery.orderByDesc(PmProject::getCreateTime);
         page = this.page(page, lambdaQuery);
         if (page.getRecords() == null || page.getRecords().isEmpty()) {
             return new CommonPage<>(Collections.emptyList(), 0, pageSize, pageNum);
@@ -219,10 +224,10 @@ public class PmProjectServiceImpl extends AbstractCrudService<PmProjectMapper, P
         List<SysUser> userList = new ArrayList<>();
         if (userId != null) {
             LambdaQueryWrapper<SysUser> queryNameId = Wrappers.lambdaQuery();
-            queryNameId.select(SysUser::getUserId,SysUser::getNickName)
+            queryNameId.select(SysUser::getUserId, SysUser::getNickName)
                     .eq(SysUser::getUserId, userId);
             userList = sysUserMapper.selectList(queryNameId);
-        }else{
+        } else {
             LambdaQueryWrapper<PmProject> wrapper = Wrappers.lambdaQuery();
             wrapper.select(PmProject::getProjectHead, PmProject::getProjectMember)
                     .eq(PmProject::getTenantId, SecurityUtils.getTenantId())
@@ -295,10 +300,10 @@ public class PmProjectServiceImpl extends AbstractCrudService<PmProjectMapper, P
     }
 
     @Override
-    public void exitProject(Integer projectId){
+    public void exitProject(Integer projectId) {
         Long userId = SecurityUtils.getUserId();
         PmProject project = pmProjectMapper.selectById(projectId);
-        String projectMember =  project.getProjectMember();
+        String projectMember = project.getProjectMember();
         String[] parts = projectMember.split(",");
         String result = String.join(",", java.util.stream.Stream.of(parts)
                 .filter(part -> !part.equals(String.valueOf(userId)))

+ 262 - 35
service-iot/service-iot-biz/src/main/java/com/usky/iot/service/impl/PmWorkReportServiceImpl.java

@@ -1,17 +1,24 @@
 package com.usky.iot.service.impl;
 
 import com.alibaba.fastjson.JSONObject;
+import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenResponse;
+import com.aliyun.tea.TeaException;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.dingtalk.api.DefaultDingTalkClient;
+import com.dingtalk.api.DingTalkClient;
+import com.dingtalk.api.request.OapiReportCreateRequest;
+import com.dingtalk.api.request.OapiV2UserGetbymobileRequest;
+import com.dingtalk.api.response.OapiReportCreateResponse;
+import com.dingtalk.api.response.OapiV2UserGetbymobileResponse;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.usky.common.core.bean.ApiResult;
 import com.usky.common.core.exception.BusinessException;
 import com.usky.common.security.utils.SecurityUtils;
-import com.usky.iot.domain.PmProject;
-import com.usky.iot.domain.PmReceive;
-import com.usky.iot.domain.PmWorkContent;
-import com.usky.iot.domain.PmWorkReport;
-import com.usky.iot.mapper.PmReceiveMapper;
-import com.usky.iot.mapper.PmWorkContentMapper;
-import com.usky.iot.mapper.PmWorkReportMapper;
+import com.usky.iot.constant.dingTalkConstant;
+import com.usky.iot.domain.*;
+import com.usky.iot.mapper.*;
 import com.usky.iot.service.PmProjectService;
 import com.usky.iot.service.PmWorkContentService;
 import com.usky.iot.service.PmWorkReportService;
@@ -19,7 +26,9 @@ import com.usky.common.mybatis.core.AbstractCrudService;
 import com.usky.iot.service.vo.PmProjectWorkTimeVo;
 import com.usky.system.RemoteMceService;
 import com.usky.system.domain.SysUser;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -42,6 +51,7 @@ import java.util.stream.Collectors;
  * @author fu
  * @since 2024-05-20
  */
+@Slf4j
 @Service
 public class PmWorkReportServiceImpl extends AbstractCrudService<PmWorkReportMapper, PmWorkReport> implements PmWorkReportService {
 
@@ -67,6 +77,12 @@ public class PmWorkReportServiceImpl extends AbstractCrudService<PmWorkReportMap
     @Autowired
     private PmReceiveMapper pmReceiveMapper;
 
+    @Autowired
+    private MceMbuserMapper mceMbuserMapper;
+
+    @Autowired
+    private SysUserMapper sysUserMapper;
+
     /**
      * 获取时间内工作报告
      *
@@ -111,7 +127,8 @@ public class PmWorkReportServiceImpl extends AbstractCrudService<PmWorkReportMap
         }
         //固定返回七条数据,没有内容也要设置时间给前端渲染
         LambdaQueryWrapper<PmWorkReport> queryWrapperR = Wrappers.lambdaQuery();
-        queryWrapperR.select(PmWorkReport::getId, PmWorkReport::getReportDate, PmWorkReport::getSubmitDate, PmWorkReport::getUpdateTime, PmWorkReport::getTomorrowPlan, PmWorkReport::getCoordinateWork, PmWorkReport::getCcTo)
+        queryWrapperR.select(PmWorkReport::getId, PmWorkReport::getReportDate, PmWorkReport::getSubmitDate, PmWorkReport::getUpdateTime,
+                PmWorkReport::getTomorrowPlan, PmWorkReport::getCoordinateWork, PmWorkReport::getCcTo, PmWorkReport::getSendDingTalk)
                 .eq(PmWorkReport::getSubmitterId, SecurityUtils.getUserId())
                 .between(PmWorkReport::getReportDate, startDate1, endDate1)
                 .orderByAsc(PmWorkReport::getReportDate)
@@ -248,7 +265,14 @@ public class PmWorkReportServiceImpl extends AbstractCrudService<PmWorkReportMap
             newReport.setDeptId(deptId);
             newReport.setTenantId(tenantId);
             newReport.setTotalHours(totalWorkTime);
-            pmWorkReportMapper.insert(newReport);
+            newReport.setSendDingTalk(pmWorkReport.getSendDingTalk());
+            try {
+                pmWorkReportMapper.insert(newReport);
+
+            } catch (Exception e) {
+                e.printStackTrace();
+                throw new BusinessException("已存在重复数据,一人一天一篇报告");
+            }
 
             //获取报告中所有项目id
             List<Integer> projectIds = new ArrayList<>();
@@ -287,24 +311,18 @@ public class PmWorkReportServiceImpl extends AbstractCrudService<PmWorkReportMap
                                 .collect(Collectors.toList()))
                         .orElse(Collections.emptyList());
             }
-            sendAsyncMessage(newReport, ids);
-            //查username
-            if (ids.size() > 0) {
-                List<SysUser> usersName = pmWorkContentService.nickNames(ids);
-                for (Long id : ids) {
-                    PmReceive pmReceive = new PmReceive();
-                    pmReceive.setReceiverId(id);
-                    pmReceive.setReportId(newReport.getId());
-                    pmReceive.setTenantId(tenantId);
-                    pmReceive.setDeptId(deptId);
-                    pmReceive.setCreateBy(userName);
-                    pmReceive.setCreateTime(dateTime);
-                    pmReceive.setReceiverName(userName);
-                    pmReceive.setReadFlag(0);
-                    pmReceiveMapper.insert(pmReceive);
-                }
+            //sendAsyncMessage(newReport, ids);
+            List<Long> finalIds = ids;
+            CompletableFuture.runAsync(() -> {sendAsyncMessage(newReport, finalIds);});
+            //存入消息接收表
+            receiveMessages(ids, newReport.getId());
+            //是否同步钉钉
+            if (pmWorkReport.getSendDingTalk() == 1) {
+                //sendDingTalkDailyReport(pmWorkReport);
+                CompletableFuture.runAsync(() -> {
+                    sendDingTalkDailyReport(newReport, pmWorkReport.getWorkContents());
+                });
             }
-
         } else if (repeat.size() > 0) {
             PmWorkReport rp = new PmWorkReport();
             rp.setId(pmWorkReport.getId());
@@ -344,7 +362,30 @@ public class PmWorkReportServiceImpl extends AbstractCrudService<PmWorkReportMap
         }
     }
 
-    private void sendAsyncMessage(PmWorkReport newReport, List<Long> userId) {
+    private void receiveMessages(List<Long> ids, Integer reportId) {
+        List<SysUser> users = pmWorkContentService.nickNames(ids);
+        if (ids.size() > 0) {
+            for (Long id : ids) {
+                PmReceive pmReceive = new PmReceive();
+                pmReceive.setReceiverId(id);
+                pmReceive.setReportId(reportId);
+                pmReceive.setTenantId(SecurityUtils.getTenantId());
+                pmReceive.setDeptId(SecurityUtils.getLoginUser().getSysUser().getDeptId());
+                pmReceive.setCreateBy(SecurityUtils.getUsername());
+                pmReceive.setCreateTime(LocalDateTime.now());
+                for (SysUser user : users) {
+                    if (user.getUserId().equals(id)) {
+                        pmReceive.setReceiverName(user.getUserName());
+                    }
+                }
+                pmReceive.setReadFlag(0);
+                pmReceiveMapper.insert(pmReceive);
+            }
+        }
+    }
+
+    @Async("asyncServiceExecutor")
+    public void sendAsyncMessage(PmWorkReport newReport, List<Long> userId) {
         JSONObject jsonObject = new JSONObject();
         jsonObject.put("infoTitle", INFO_TITLE);
         jsonObject.put("infoContent", SecurityUtils.getLoginUser().getSysUser().getNickName() + INFO_CONTENT);
@@ -354,15 +395,18 @@ public class PmWorkReportServiceImpl extends AbstractCrudService<PmWorkReportMap
         if (userId != null && !userId.isEmpty()) {
             jsonObject.put("userIds", userId);
         }
-        // 异步发送消息
-        CompletableFuture.runAsync(() -> {
-            try {
-                // 推送消息中心
-                remoteMceService.addMce(jsonObject.toString());
-            } catch (Exception e) {
-                e.printStackTrace();
+        // 推送消息中心
+        try {
+            ApiResult<Void> voidApiResult = remoteMceService.addMce(jsonObject.toString());
+
+            if (voidApiResult.isSuccess()) {
+                log.info("报告消息发送成功!");
+            } else {
+                log.error("报告消息发送失败!");
             }
-        });
+        } catch (Exception e) {
+            log.error("发送消息时发生异常", e);
+        }
     }
 
     @Override
@@ -448,4 +492,187 @@ public class PmWorkReportServiceImpl extends AbstractCrudService<PmWorkReportMap
         returnList.add(monthMap);
         return returnList;
     }
+
+    /**
+     * 指定请求
+     *
+     * @return
+     * @throws Exception
+     */
+    public static com.aliyun.dingtalkoauth2_1_0.Client createClient() throws Exception {
+        com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config();
+        config.protocol = "https";
+        config.regionId = "central";
+        config.method = "POST";
+        return new com.aliyun.dingtalkoauth2_1_0.Client(config);
+    }
+
+    /**
+     * 获取钉钉企业内部AccessToken
+     *
+     * @return
+     * @throws Exception
+     */
+    public String getDingTalkToken() {
+        GetAccessTokenResponse responseBody = new GetAccessTokenResponse();
+        com.aliyun.dingtalkoauth2_1_0.Client client = null;
+        try {
+            client = createClient();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenRequest getAccessTokenRequest = new com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenRequest()
+                .setAppKey(dingTalkConstant.DING_TALK_CLIENT_ID)
+                .setAppSecret(dingTalkConstant.DING_TALK_CLIENT_SECRET);
+        try {
+            responseBody = client.getAccessToken(getAccessTokenRequest);
+        } catch (TeaException err) {
+            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
+                // err 中含有 code 和 message 属性,可帮助开发定位问题
+            }
+        } catch (Exception _err) {
+            TeaException err = new TeaException(_err.getMessage(), _err);
+            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
+                // err 中含有 code 和 message 属性,可帮助开发定位问题
+            }
+        }
+        return responseBody.getBody().accessToken;
+    }
+
+    /**
+     * 获取钉钉userId
+     *
+     * @param userId 平台userid
+     * @return
+     */
+    public String getDingTalkUserId(Long userId) {
+        String dingTalkId = "";
+        LambdaQueryWrapper<MceMbuser> dingTalkQuery = Wrappers.lambdaQuery();
+        dingTalkQuery.select(MceMbuser::getDingTalkId, MceMbuser::getUserId)
+                .eq(MceMbuser::getUserId, userId);
+        MceMbuser mceMbuser = mceMbuserMapper.selectOne(dingTalkQuery);
+        if (mceMbuser != null) {
+            String dingId = mceMbuser.getDingTalkId();
+            if (dingId != null && dingId != "") {
+                return mceMbuser.getDingTalkId();
+            }
+        }
+        LambdaQueryWrapper<SysUser> phoneQuery = Wrappers.lambdaQuery();
+        phoneQuery.select(SysUser::getPhonenumber, SysUser::getUserName)
+                .eq(SysUser::getUserId, userId);
+        SysUser user = sysUserMapper.selectOne(phoneQuery);
+        String mobile = user.getPhonenumber();
+        try {
+            String accessToken = getDingTalkToken();
+            DingTalkClient client = new DefaultDingTalkClient(dingTalkConstant.DING_TALK_USERID_URL);
+            OapiV2UserGetbymobileRequest req = new OapiV2UserGetbymobileRequest();
+            req.setMobile(mobile);
+            OapiV2UserGetbymobileResponse rsp = client.execute(req, accessToken);
+            ObjectMapper objectMapper = new ObjectMapper();
+            JsonNode rootNode = objectMapper.readTree(rsp.getBody());
+            JsonNode resultNode = rootNode.get("result");
+            dingTalkId = resultNode.get("userid").asText();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        if (mceMbuser != null) {
+            mceMbuser.setDingTalkId(dingTalkId);
+            mceMbuser.setUpdateBy(SecurityUtils.getUsername());
+            mceMbuser.setUpdateTime(LocalDateTime.now());
+            mceMbuserMapper.update(mceMbuser, dingTalkQuery);
+        } else {
+            MceMbuser mbuser = new MceMbuser();
+            mbuser.setDingTalkId(dingTalkId);
+            mbuser.setPhone(mobile);
+            mbuser.setUserId(userId);
+            mbuser.setCreateBy(user.getUserName());
+            mbuser.setCreateTime(LocalDateTime.now());
+            mceMbuserMapper.insert(mbuser);
+        }
+        return dingTalkId;
+    }
+
+    @Async("asyncServiceExecutor")//异步发送
+    @Override
+    public void sendDingTalkDailyReport(PmWorkReport workReport, List<PmWorkContent> workContents) {
+        String ccToStr = workReport.getCcTo();
+        String[] items = ccToStr.split(",");
+        List<Long> userIds = new ArrayList<>();
+        for (String item : items) {
+            long number = Long.parseLong(item.trim());
+            userIds.add(number);
+        }
+        List<String> ccTo = new ArrayList<>();
+        for (Long userId : userIds) {
+            String dingTalkUserId = getDingTalkUserId(userId);
+            ccTo.add(dingTalkUserId);
+        }
+        String reportCoordinateWork = workReport.getCoordinateWork();
+        String reportTomorrowPlan = workReport.getTomorrowPlan();
+        String coordinateWork1 = "-";
+        String tomorrowPlan1 = "-";
+        if (reportCoordinateWork != null && reportCoordinateWork != "") {
+            coordinateWork1 = reportCoordinateWork;
+        }
+        if (reportTomorrowPlan != null && reportTomorrowPlan != "") {
+            tomorrowPlan1 = reportTomorrowPlan;
+        }
+
+        StringBuilder contentBuilder = new StringBuilder();
+        for (PmWorkContent content : workContents) {
+            String projectName = content.getProjectName();
+            BigDecimal workTime = content.getWorkTime();
+            String workContent = content.getWorkContent();
+            StringBuilder markdown = new StringBuilder();
+            markdown.append("#### ").append(projectName).append(" ").append(workTime).append("h\n\n");
+            markdown.append("<span style=\"font-size: 18px;\">").append(workContent).append("\n").append("</span>");
+            contentBuilder.append(markdown);
+        }
+        String completedWork = contentBuilder.toString();
+        try {
+            DingTalkClient client = new DefaultDingTalkClient(dingTalkConstant.DING_TALK_CREATE_DAILY_REPORT_URL);
+            OapiReportCreateRequest req = new OapiReportCreateRequest();
+            OapiReportCreateRequest.OapiCreateReportParam obj1 = new OapiReportCreateRequest.OapiCreateReportParam();
+            List<OapiReportCreateRequest.OapiReportContentVo> list3 = new ArrayList<>();
+            OapiReportCreateRequest.OapiReportContentVo completedWorkD = new OapiReportCreateRequest.OapiReportContentVo();
+            completedWorkD.setSort(0L);
+            completedWorkD.setType(1L);
+            completedWorkD.setContentType(dingTalkConstant.DING_TALK_DAILY_REPORT_CONTENT_TYPE);
+            completedWorkD.setContent(completedWork);
+            completedWorkD.setKey(dingTalkConstant.DING_TALK_DAILY_REPORT_COMPLETED_WORK);
+            list3.add(completedWorkD);
+            OapiReportCreateRequest.OapiReportContentVo tomorrowPlan = new OapiReportCreateRequest.OapiReportContentVo();
+            tomorrowPlan.setSort(1L);
+            tomorrowPlan.setType(1L);
+            tomorrowPlan.setContentType(dingTalkConstant.DING_TALK_DAILY_REPORT_CONTENT_TYPE);
+            tomorrowPlan.setContent(tomorrowPlan1);
+            tomorrowPlan.setKey(dingTalkConstant.DING_TALK_DAILY_REPORT_TOMORROW_PLAN);
+            list3.add(tomorrowPlan);
+            OapiReportCreateRequest.OapiReportContentVo coordinateWork = new OapiReportCreateRequest.OapiReportContentVo();
+            coordinateWork.setSort(2L);
+            coordinateWork.setType(1L);
+            coordinateWork.setContentType(dingTalkConstant.DING_TALK_DAILY_REPORT_CONTENT_TYPE);
+            coordinateWork.setContent(coordinateWork1);
+            coordinateWork.setKey(dingTalkConstant.DING_TALK_DAILY_REPORT_COORDINATE_WORK);
+            list3.add(coordinateWork);
+            obj1.setContents(list3);
+            obj1.setToUserids(ccTo);
+            obj1.setTemplateId(dingTalkConstant.DING_TALK_DAILY_REPORT_TEMPLATE_ID);
+            obj1.setToChat(dingTalkConstant.DING_TALK_DAILY_REPORT_TO_CHAT);
+            obj1.setDdFrom(dingTalkConstant.DING_TALK_CORP_ID);
+            obj1.setUserid(getDingTalkUserId(SecurityUtils.getUserId()));
+            //obj1.setToCids(dingTalkConstant.DING_TALK_DAILY_REPORT_TO_CIDS);//发送到群,群id
+            req.setCreateReportParam(obj1);
+            OapiReportCreateResponse rsp = client.execute(req, getDingTalkToken());
+            if (rsp.isSuccess()) {
+                log.info("钉钉报告发送成功");
+            } else {
+                log.error("钉钉报告发送失败: " + rsp.getErrmsg());
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+
 }

+ 4 - 4
service-iot/service-iot-biz/src/main/java/com/usky/iot/service/vo/WorkTimeExportVO.java

@@ -36,25 +36,25 @@ public class WorkTimeExportVO implements Serializable {
     /**
      *
      */
-    @Excel(name = "账号名")
+    @Excel(name = "账号名", align = Excel.Align.LEFT)
     private String userName;
 
     /**
      *
      */
-    @Excel(name = "员工名")
+    @Excel(name = "员工名", align = Excel.Align.LEFT)
     private String fullName;
 
     /**
      * 项目名
      */
-    @Excel(name = "项目名")
+    @Excel(name = "项目名", align = Excel.Align.LEFT)
     private String projectName;
 
     /**
      * 工时
      */
-    @Excel(name = "耗时")
+    @Excel(name = "耗时", align = Excel.Align.LEFT)
     private BigDecimal workTime;
 
     /**

+ 1 - 0
service-iot/service-iot-biz/src/main/resources/mapper/iot/MceMbuserMapper.xml

@@ -13,6 +13,7 @@
         <result column="create_time" property="createTime" />
         <result column="update_by" property="updateBy" />
         <result column="update_time" property="updateTime" />
+        <result column="ding_talk_id" property="dingTalkId" />
     </resultMap>
 
 </mapper>

+ 1 - 0
service-iot/service-iot-biz/src/main/resources/mapper/iot/PmWorkReportMapper.xml

@@ -18,6 +18,7 @@
         <result column="update_time" property="updateTime" />
         <result column="dept_id" property="deptId" />
         <result column="tenant_id" property="tenantId" />
+        <result column="send_ding_talk" property="sendDingTalk" />
     </resultMap>
 
 </mapper>

+ 81 - 0
service-job/pom.xml

@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>com.usky</groupId>
+        <artifactId>usky-modules</artifactId>
+        <version>0.0.1</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>service-job</artifactId>
+
+    <description>
+        service-job定时任务
+    </description>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>com.usky</groupId>
+            <artifactId>common-cloud-starter</artifactId>
+        </dependency>
+		
+        <!-- Swagger UI -->
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger-ui</artifactId>
+            <version>${swagger.fox.version}</version>
+        </dependency>
+		
+        <!-- Quartz -->
+        <dependency>
+            <groupId>org.quartz-scheduler</groupId>
+            <artifactId>quartz</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.mchange</groupId>
+                    <artifactId>c3p0</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>javax.xml.bind</groupId>
+            <artifactId>jaxb-api</artifactId>
+            <version>2.3.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.usky</groupId>
+            <artifactId>service-fire-api</artifactId>
+            <version>0.0.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.usky</groupId>
+            <artifactId>ruoyi-common-swagger</artifactId>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <finalName>${project.artifactId}</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.2.6.RELEASE</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+   
+</project>

+ 48 - 0
service-job/src/main/java/com/ruoyi/job/RuoYiJobApplication.java

@@ -0,0 +1,48 @@
+package com.ruoyi.job;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+//import com.ruoyi.common.security.annotation.EnableCustomConfig;
+import com.usky.common.security.annotation.EnableRyFeignClients;
+import com.ruoyi.common.swagger.annotation.EnableCustomSwagger2;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.core.env.Environment;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/*
+ * 定时任务
+ * 
+ * @author ruoyi
+ */
+//@EnableCustomConfig
+@EnableCustomSwagger2
+@EnableFeignClients(basePackages = {"com.usky"})
+@MapperScan(value = "com.ruoyi.job.mapper")
+@ComponentScan(basePackages = {"com.usky","com.ruoyi"})
+@SpringBootApplication
+public class RuoYiJobApplication
+{
+    private static final Logger LOGGER = LoggerFactory.getLogger(RuoYiJobApplication.class);
+
+    public static void main(String[] args) throws UnknownHostException
+    {
+        ConfigurableApplicationContext application = SpringApplication.run(RuoYiJobApplication.class, args);
+        Environment env = application.getEnvironment();
+        String ip = InetAddress.getLocalHost().getHostAddress();
+        String port = env.getProperty("server.port");
+        String path = env.getProperty("server.servlet.context-path");
+        LOGGER.info("\n----------------------------------------------------------\n\t" +
+                "Application is running! Access URLs:\n\t" +
+                "Local: \t\thttp://localhost:" + port + (null==path?"":path) + "/\n\t" +
+                "External: \thttp://" + ip + ":" + port + (null==path?"":path) + "/\n\t" +
+                "Api: \t\thttp://" + ip + ":" + port + (null==path?"":path) + "/swagger-ui/index.html\n\t" +
+                "----------------------------------------------------------");
+    }
+}

+ 57 - 0
service-job/src/main/java/com/ruoyi/job/config/ScheduleConfig.java

@@ -0,0 +1,57 @@
+package com.ruoyi.job.config;
+
+import java.util.Properties;
+import javax.sql.DataSource;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.quartz.SchedulerFactoryBean;
+
+/**
+ * 定时任务配置(单机部署建议删除此类和qrtz数据库表,默认走内存会最高效)
+ * 
+ * @author ruoyi
+ */
+//@Configuration
+//public class ScheduleConfig
+//{
+//    @Bean
+//    public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource)
+//    {
+//        SchedulerFactoryBean factory = new SchedulerFactoryBean();
+//        factory.setDataSource(dataSource);
+//
+//        // quartz参数
+//        Properties prop = new Properties();
+//        prop.put("org.quartz.scheduler.instanceName", "RuoyiScheduler");
+//        prop.put("org.quartz.scheduler.instanceId", "AUTO");
+//        // 线程池配置
+//        prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
+//        prop.put("org.quartz.threadPool.threadCount", "20");
+//        prop.put("org.quartz.threadPool.threadPriority", "5");
+//        // JobStore配置
+//        prop.put("org.quartz.jobStore.class", "org.springframework.scheduling.quartz.LocalDataSourceJobStore");
+//        // 集群配置
+//        prop.put("org.quartz.jobStore.isClustered", "true");
+//        prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
+//        prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");
+//        prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true");
+//
+//        // sqlserver 启用
+//        // prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");
+//        prop.put("org.quartz.jobStore.misfireThreshold", "12000");
+//        prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
+//        factory.setQuartzProperties(prop);
+//
+//        factory.setSchedulerName("RuoyiScheduler");
+//        // 延时启动
+//        factory.setStartupDelay(1);
+//        factory.setApplicationContextSchedulerContextKey("applicationContextKey");
+//        // 可选,QuartzScheduler
+//        // 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
+//        factory.setOverwriteExistingJobs(true);
+//        // 设置自动启动,默认为true
+//        factory.setAutoStartup(true);
+//
+//        return factory;
+//    }
+//}

+ 187 - 0
service-job/src/main/java/com/ruoyi/job/controller/SysJobController.java

@@ -0,0 +1,187 @@
+package com.ruoyi.job.controller;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.common.core.utils.StringUtils;
+import com.usky.common.log.annotation.Log;
+import com.usky.common.log.enums.BusinessType;
+import com.usky.common.security.annotation.RequiresPermissions;
+import com.usky.common.security.utils.SecurityUtils;
+import org.quartz.SchedulerException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.usky.common.core.constant.Constants;
+import com.usky.common.core.exception.job.TaskException;
+import com.usky.common.core.utils.poi.ExcelUtil;
+import com.usky.common.core.web.controller.BaseController;
+import com.usky.common.core.web.domain.AjaxResult;
+import com.usky.common.core.web.page.TableDataInfo;
+
+import com.ruoyi.job.domain.SysJob;
+import com.ruoyi.job.service.ISysJobService;
+import com.ruoyi.job.util.CronUtils;
+import com.ruoyi.job.util.ScheduleUtils;
+
+/**
+ * 调度任务信息操作处理
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/job")
+public class SysJobController extends BaseController
+{
+    @Autowired
+    private ISysJobService jobService;
+
+    /**
+     * 查询定时任务列表
+     */
+//    @RequiresPermissions("monitor:job:list")
+    @GetMapping("/list")
+    public ApiResult<List<SysJob>> list(SysJob sysJob)
+    {
+        startPage();
+        List<SysJob> list = jobService.selectJobList(sysJob);
+        return ApiResult.success(list);
+    }
+
+    /**
+     * 导出定时任务列表
+     */
+//    @RequiresPermissions("monitor:job:export")
+    @Log(title = "定时任务", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, SysJob sysJob)
+    {
+        List<SysJob> list = jobService.selectJobList(sysJob);
+        ExcelUtil<SysJob> util = new ExcelUtil<SysJob>(SysJob.class);
+        util.exportExcel(response, list, "定时任务");
+    }
+
+    /**
+     * 获取定时任务详细信息
+     */
+//    @RequiresPermissions("monitor:job:query")
+    @GetMapping(value = "/{jobId}")
+    public AjaxResult getInfo(@PathVariable("jobId") Long jobId)
+    {
+        return AjaxResult.success(jobService.selectJobById(jobId));
+    }
+
+    /**
+     * 新增定时任务
+     */
+//    @RequiresPermissions("monitor:job:add")
+    @Log(title = "定时任务", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody SysJob job) throws SchedulerException,TaskException {
+        if (!CronUtils.isValid(job.getCronExpression()))
+        {
+            return error("新增任务'" + job.getJobName() + "'失败,Cron表达式不正确");
+        }
+        else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI))
+        {
+            return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用");
+        }
+        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS }))
+        {
+            return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用");
+        }
+        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS }))
+        {
+            return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用");
+        }
+        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR))
+        {
+            return error("新增任务'" + job.getJobName() + "'失败,目标字符串存在违规");
+        }
+        else if (!ScheduleUtils.whiteList(job.getInvokeTarget()))
+        {
+            return error("新增任务'" + job.getJobName() + "'失败,目标字符串不在白名单内");
+        }
+        job.setCreateBy(SecurityUtils.getUsername());
+        return toAjax(jobService.insertJob(job));
+    }
+
+    /**
+     * 修改定时任务
+     */
+//    @RequiresPermissions("monitor:job:edit")
+    @Log(title = "定时任务", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody SysJob job) throws SchedulerException,TaskException {
+        if (!CronUtils.isValid(job.getCronExpression()))
+        {
+            return error("修改任务'" + job.getJobName() + "'失败,Cron表达式不正确");
+        }
+        else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI))
+        {
+            return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用");
+        }
+        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS }))
+        {
+            return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用");
+        }
+        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS }))
+        {
+            return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用");
+        }
+        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR))
+        {
+            return error("修改任务'" + job.getJobName() + "'失败,目标字符串存在违规");
+        }
+        else if (!ScheduleUtils.whiteList(job.getInvokeTarget()))
+        {
+            return error("修改任务'" + job.getJobName() + "'失败,目标字符串不在白名单内");
+        }
+        job.setUpdateBy(SecurityUtils.getUsername());
+        return toAjax(jobService.updateJob(job));
+    }
+
+    /**
+     * 定时任务状态修改
+     */
+//    @RequiresPermissions("monitor:job:changeStatus")
+    @Log(title = "定时任务", businessType = BusinessType.UPDATE)
+    @PutMapping("/changeStatus")
+    public AjaxResult changeStatus(@RequestBody SysJob job) throws SchedulerException
+    {
+        SysJob newJob = jobService.selectJobById(job.getJobId());
+        newJob.setStatus(job.getStatus());
+        return toAjax(jobService.changeStatus(newJob));
+    }
+
+    /**
+     * 定时任务立即执行一次
+     */
+//    @RequiresPermissions("monitor:job:changeStatus")
+    @Log(title = "定时任务", businessType = BusinessType.UPDATE)
+    @PutMapping("/run")
+    public AjaxResult run(@RequestBody SysJob job) throws SchedulerException
+    {
+        jobService.run(job);
+        return AjaxResult.success();
+    }
+
+    /**
+     * 删除定时任务
+     */
+//    @RequiresPermissions("monitor:job:remove")
+    @Log(title = "定时任务", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{jobIds}")
+    public AjaxResult remove(@PathVariable Long[] jobIds) throws SchedulerException, TaskException
+    {
+        jobService.deleteJobByIds(jobIds);
+        return AjaxResult.success();
+    }
+}

+ 89 - 0
service-job/src/main/java/com/ruoyi/job/controller/SysJobLogController.java

@@ -0,0 +1,89 @@
+package com.ruoyi.job.controller;
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.usky.common.core.utils.poi.ExcelUtil;
+import com.usky.common.core.web.controller.BaseController;
+import com.usky.common.core.web.domain.AjaxResult;
+import com.usky.common.core.web.page.TableDataInfo;
+import com.usky.common.log.annotation.Log;
+import com.usky.common.log.enums.BusinessType;
+import com.usky.common.security.annotation.RequiresPermissions;
+import com.ruoyi.job.domain.SysJobLog;
+import com.ruoyi.job.service.ISysJobLogService;
+
+/**
+ * 调度日志操作处理
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/job/log")
+public class SysJobLogController extends BaseController
+{
+    @Autowired
+    private ISysJobLogService jobLogService;
+
+    /**
+     * 查询定时任务调度日志列表
+     */
+//    @RequiresPermissions("monitor:job:list")
+    @GetMapping("/list")
+    public TableDataInfo list(SysJobLog sysJobLog)
+    {
+        startPage();
+        List<SysJobLog> list = jobLogService.selectJobLogList(sysJobLog);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出定时任务调度日志列表
+     */
+//    @RequiresPermissions("monitor:job:export")
+    @Log(title = "任务调度日志", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, SysJobLog sysJobLog)
+    {
+        List<SysJobLog> list = jobLogService.selectJobLogList(sysJobLog);
+        ExcelUtil<SysJobLog> util = new ExcelUtil<SysJobLog>(SysJobLog.class);
+        util.exportExcel(response, list, "调度日志");
+    }
+
+    /**
+     * 根据调度编号获取详细信息
+     */
+//    @RequiresPermissions("monitor:job:query")
+    @GetMapping(value = "/{configId}")
+    public AjaxResult getInfo(@PathVariable Long jobLogId)
+    {
+        return AjaxResult.success(jobLogService.selectJobLogById(jobLogId));
+    }
+
+    /**
+     * 删除定时任务调度日志
+     */
+//    @RequiresPermissions("monitor:job:remove")
+    @Log(title = "定时任务调度日志", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{jobLogIds}")
+    public AjaxResult remove(@PathVariable Long[] jobLogIds)
+    {
+        return toAjax(jobLogService.deleteJobLogByIds(jobLogIds));
+    }
+
+    /**
+     * 清空定时任务调度日志
+     */
+//    @RequiresPermissions("monitor:job:remove")
+    @Log(title = "调度日志", businessType = BusinessType.CLEAN)
+    @DeleteMapping("/clean")
+    public AjaxResult clean()
+    {
+        jobLogService.cleanJobLog();
+        return AjaxResult.success();
+    }
+}

+ 171 - 0
service-job/src/main/java/com/ruoyi/job/domain/SysJob.java

@@ -0,0 +1,171 @@
+package com.ruoyi.job.domain;
+
+import java.util.Date;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.usky.common.core.annotation.Excel;
+import com.usky.common.core.annotation.Excel.ColumnType;
+import com.usky.common.core.constant.ScheduleConstants;
+import com.usky.common.core.utils.StringUtils;
+import com.usky.common.core.web.domain.BaseEntity;
+import com.ruoyi.job.util.CronUtils;
+
+/**
+ * 定时任务调度表 sys_job
+ * 
+ * @author ruoyi
+ */
+public class SysJob extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 任务ID */
+    @Excel(name = "任务序号", cellType = ColumnType.NUMERIC)
+    private Long jobId;
+
+    /** 任务名称 */
+    @Excel(name = "任务名称")
+    private String jobName;
+
+    /** 任务组名 */
+    @Excel(name = "任务组名")
+    private String jobGroup;
+
+    /** 调用目标字符串 */
+    @Excel(name = "调用目标字符串")
+    private String invokeTarget;
+
+    /** cron执行表达式 */
+    @Excel(name = "执行表达式 ")
+    private String cronExpression;
+
+    /** cron计划策略 */
+    @Excel(name = "计划策略 ", readConverterExp = "0=默认,1=立即触发执行,2=触发一次执行,3=不触发立即执行")
+    private String misfirePolicy = ScheduleConstants.MISFIRE_DEFAULT;
+
+    /** 是否并发执行(0允许 1禁止) */
+    @Excel(name = "并发执行", readConverterExp = "0=允许,1=禁止")
+    private String concurrent;
+
+    /** 任务状态(0正常 1暂停) */
+    @Excel(name = "任务状态", readConverterExp = "0=正常,1=暂停")
+    private String status;
+
+    public Long getJobId()
+    {
+        return jobId;
+    }
+
+    public void setJobId(Long jobId)
+    {
+        this.jobId = jobId;
+    }
+
+    @NotBlank(message = "任务名称不能为空")
+    @Size(min = 0, max = 64, message = "任务名称不能超过64个字符")
+    public String getJobName()
+    {
+        return jobName;
+    }
+
+    public void setJobName(String jobName)
+    {
+        this.jobName = jobName;
+    }
+
+    public String getJobGroup()
+    {
+        return jobGroup;
+    }
+
+    public void setJobGroup(String jobGroup)
+    {
+        this.jobGroup = jobGroup;
+    }
+
+    @NotBlank(message = "调用目标字符串不能为空")
+    @Size(min = 0, max = 500, message = "调用目标字符串长度不能超过500个字符")
+    public String getInvokeTarget()
+    {
+        return invokeTarget;
+    }
+
+    public void setInvokeTarget(String invokeTarget)
+    {
+        this.invokeTarget = invokeTarget;
+    }
+
+    @NotBlank(message = "Cron执行表达式不能为空")
+    @Size(min = 0, max = 255, message = "Cron执行表达式不能超过255个字符")
+    public String getCronExpression()
+    {
+        return cronExpression;
+    }
+
+    public void setCronExpression(String cronExpression)
+    {
+        this.cronExpression = cronExpression;
+    }
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    public Date getNextValidTime()
+    {
+        if (StringUtils.isNotEmpty(cronExpression))
+        {
+            return CronUtils.getNextExecution(cronExpression);
+        }
+        return null;
+    }
+
+    public String getMisfirePolicy()
+    {
+        return misfirePolicy;
+    }
+
+    public void setMisfirePolicy(String misfirePolicy)
+    {
+        this.misfirePolicy = misfirePolicy;
+    }
+
+    public String getConcurrent()
+    {
+        return concurrent;
+    }
+
+    public void setConcurrent(String concurrent)
+    {
+        this.concurrent = concurrent;
+    }
+
+    public String getStatus()
+    {
+        return status;
+    }
+
+    public void setStatus(String status)
+    {
+        this.status = status;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("jobId", getJobId())
+            .append("jobName", getJobName())
+            .append("jobGroup", getJobGroup())
+            .append("cronExpression", getCronExpression())
+            .append("nextValidTime", getNextValidTime())
+            .append("misfirePolicy", getMisfirePolicy())
+            .append("concurrent", getConcurrent())
+            .append("status", getStatus())
+            .append("createBy", getCreateBy())
+            .append("createTime", getCreateTime())
+            .append("updateBy", getUpdateBy())
+            .append("updateTime", getUpdateTime())
+            .append("remark", getRemark())
+            .toString();
+    }
+}

+ 155 - 0
service-job/src/main/java/com/ruoyi/job/domain/SysJobLog.java

@@ -0,0 +1,155 @@
+package com.ruoyi.job.domain;
+
+import java.util.Date;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.usky.common.core.annotation.Excel;
+import com.usky.common.core.web.domain.BaseEntity;
+
+/**
+ * 定时任务调度日志表 sys_job_log
+ * 
+ * @author ruoyi
+ */
+public class SysJobLog extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** ID */
+    @Excel(name = "日志序号")
+    private Long jobLogId;
+
+    /** 任务名称 */
+    @Excel(name = "任务名称")
+    private String jobName;
+
+    /** 任务组名 */
+    @Excel(name = "任务组名")
+    private String jobGroup;
+
+    /** 调用目标字符串 */
+    @Excel(name = "调用目标字符串")
+    private String invokeTarget;
+
+    /** 日志信息 */
+    @Excel(name = "日志信息")
+    private String jobMessage;
+
+    /** 执行状态(0正常 1失败) */
+    @Excel(name = "执行状态", readConverterExp = "0=正常,1=失败")
+    private String status;
+
+    /** 异常信息 */
+    @Excel(name = "异常信息")
+    private String exceptionInfo;
+
+    /** 开始时间 */
+    private Date startTime;
+
+    /** 停止时间 */
+    private Date stopTime;
+
+    public Long getJobLogId()
+    {
+        return jobLogId;
+    }
+
+    public void setJobLogId(Long jobLogId)
+    {
+        this.jobLogId = jobLogId;
+    }
+
+    public String getJobName()
+    {
+        return jobName;
+    }
+
+    public void setJobName(String jobName)
+    {
+        this.jobName = jobName;
+    }
+
+    public String getJobGroup()
+    {
+        return jobGroup;
+    }
+
+    public void setJobGroup(String jobGroup)
+    {
+        this.jobGroup = jobGroup;
+    }
+
+    public String getInvokeTarget()
+    {
+        return invokeTarget;
+    }
+
+    public void setInvokeTarget(String invokeTarget)
+    {
+        this.invokeTarget = invokeTarget;
+    }
+
+    public String getJobMessage()
+    {
+        return jobMessage;
+    }
+
+    public void setJobMessage(String jobMessage)
+    {
+        this.jobMessage = jobMessage;
+    }
+
+    public String getStatus()
+    {
+        return status;
+    }
+
+    public void setStatus(String status)
+    {
+        this.status = status;
+    }
+
+    public String getExceptionInfo()
+    {
+        return exceptionInfo;
+    }
+
+    public void setExceptionInfo(String exceptionInfo)
+    {
+        this.exceptionInfo = exceptionInfo;
+    }
+
+    public Date getStartTime()
+    {
+        return startTime;
+    }
+
+    public void setStartTime(Date startTime)
+    {
+        this.startTime = startTime;
+    }
+    
+    public Date getStopTime()
+    {
+        return stopTime;
+    }
+
+    public void setStopTime(Date stopTime)
+    {
+        this.stopTime = stopTime;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("jobLogId", getJobLogId())
+            .append("jobName", getJobName())
+            .append("jobGroup", getJobGroup())
+            .append("jobMessage", getJobMessage())
+            .append("status", getStatus())
+            .append("exceptionInfo", getExceptionInfo())
+            .append("startTime", getStartTime())
+            .append("stopTime", getStopTime())
+            .toString();
+    }
+}

+ 64 - 0
service-job/src/main/java/com/ruoyi/job/mapper/SysJobLogMapper.java

@@ -0,0 +1,64 @@
+package com.ruoyi.job.mapper;
+
+import java.util.List;
+import com.ruoyi.job.domain.SysJobLog;
+
+/**
+ * 调度任务日志信息 数据层
+ * 
+ * @author ruoyi
+ */
+public interface SysJobLogMapper
+{
+    /**
+     * 获取quartz调度器日志的计划任务
+     * 
+     * @param jobLog 调度日志信息
+     * @return 调度任务日志集合
+     */
+    public List<SysJobLog> selectJobLogList(SysJobLog jobLog);
+
+    /**
+     * 查询所有调度任务日志
+     *
+     * @return 调度任务日志列表
+     */
+    public List<SysJobLog> selectJobLogAll();
+
+    /**
+     * 通过调度任务日志ID查询调度信息
+     * 
+     * @param jobLogId 调度任务日志ID
+     * @return 调度任务日志对象信息
+     */
+    public SysJobLog selectJobLogById(Long jobLogId);
+
+    /**
+     * 新增任务日志
+     * 
+     * @param jobLog 调度日志信息
+     * @return 结果
+     */
+    public int insertJobLog(SysJobLog jobLog);
+
+    /**
+     * 批量删除调度日志信息
+     * 
+     * @param logIds 需要删除的数据ID
+     * @return 结果
+     */
+    public int deleteJobLogByIds(Long[] logIds);
+
+    /**
+     * 删除任务日志
+     * 
+     * @param jobId 调度日志ID
+     * @return 结果
+     */
+    public int deleteJobLogById(Long jobId);
+
+    /**
+     * 清空任务日志
+     */
+    public void cleanJobLog();
+}

+ 69 - 0
service-job/src/main/java/com/ruoyi/job/mapper/SysJobMapper.java

@@ -0,0 +1,69 @@
+package com.ruoyi.job.mapper;
+
+import java.util.List;
+import com.ruoyi.job.domain.SysJob;
+import org.springframework.stereotype.Repository;
+
+/**
+ * 调度任务信息 数据层
+ * 
+ * @author ruoyi
+ */
+@Repository
+public interface SysJobMapper
+{
+    /**
+     * 查询调度任务日志集合
+     * 
+     * @param job 调度信息
+     * @return 操作日志集合
+     */
+    public List<SysJob> selectJobList(SysJob job);
+
+    /**
+     * 查询所有调度任务
+     * 
+     * @return 调度任务列表
+     */
+    public List<SysJob> selectJobAll();
+
+    /**
+     * 通过调度ID查询调度任务信息
+     * 
+     * @param jobId 调度ID
+     * @return 角色对象信息
+     */
+    public SysJob selectJobById(Long jobId);
+
+    /**
+     * 通过调度ID删除调度任务信息
+     * 
+     * @param jobId 调度ID
+     * @return 结果
+     */
+    public int deleteJobById(Long jobId);
+
+    /**
+     * 批量删除调度任务信息
+     * 
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+    public int deleteJobByIds(Long[] ids);
+
+    /**
+     * 修改调度任务信息
+     * 
+     * @param job 调度任务信息
+     * @return 结果
+     */
+    public int updateJob(SysJob job);
+
+    /**
+     * 新增调度任务信息
+     * 
+     * @param job 调度任务信息
+     * @return 结果
+     */
+    public int insertJob(SysJob job);
+}

+ 56 - 0
service-job/src/main/java/com/ruoyi/job/service/ISysJobLogService.java

@@ -0,0 +1,56 @@
+package com.ruoyi.job.service;
+
+import java.util.List;
+import com.ruoyi.job.domain.SysJobLog;
+
+/**
+ * 定时任务调度日志信息信息 服务层
+ * 
+ * @author ruoyi
+ */
+public interface ISysJobLogService
+{
+    /**
+     * 获取quartz调度器日志的计划任务
+     * 
+     * @param jobLog 调度日志信息
+     * @return 调度任务日志集合
+     */
+    public List<SysJobLog> selectJobLogList(SysJobLog jobLog);
+
+    /**
+     * 通过调度任务日志ID查询调度信息
+     * 
+     * @param jobLogId 调度任务日志ID
+     * @return 调度任务日志对象信息
+     */
+    public SysJobLog selectJobLogById(Long jobLogId);
+
+    /**
+     * 新增任务日志
+     * 
+     * @param jobLog 调度日志信息
+     */
+    public void addJobLog(SysJobLog jobLog);
+
+    /**
+     * 批量删除调度日志信息
+     * 
+     * @param logIds 需要删除的日志ID
+     * @return 结果
+     */
+    public int deleteJobLogByIds(Long[] logIds);
+
+    /**
+     * 删除任务日志
+     * 
+     * @param jobId 调度日志ID
+     * @return 结果
+     */
+    public int deleteJobLogById(Long jobId);
+
+    /**
+     * 清空任务日志
+     */
+    public void cleanJobLog();
+}

+ 102 - 0
service-job/src/main/java/com/ruoyi/job/service/ISysJobService.java

@@ -0,0 +1,102 @@
+package com.ruoyi.job.service;
+
+import java.util.List;
+import org.quartz.SchedulerException;
+import com.usky.common.core.exception.job.TaskException;
+import com.ruoyi.job.domain.SysJob;
+
+/**
+ * 定时任务调度信息信息 服务层
+ * 
+ * @author ruoyi
+ */
+public interface ISysJobService
+{
+    /**
+     * 获取quartz调度器的计划任务
+     * 
+     * @param job 调度信息
+     * @return 调度任务集合
+     */
+    public List<SysJob> selectJobList(SysJob job);
+
+    /**
+     * 通过调度任务ID查询调度信息
+     * 
+     * @param jobId 调度任务ID
+     * @return 调度任务对象信息
+     */
+    public SysJob selectJobById(Long jobId);
+
+    /**
+     * 暂停任务
+     * 
+     * @param job 调度信息
+     * @return 结果
+     */
+    public int pauseJob(SysJob job) throws SchedulerException;
+
+    /**
+     * 恢复任务
+     * 
+     * @param job 调度信息
+     * @return 结果
+     */
+    public int resumeJob(SysJob job) throws SchedulerException;
+
+    /**
+     * 删除任务后,所对应的trigger也将被删除
+     * 
+     * @param job 调度信息
+     * @return 结果
+     */
+    public int deleteJob(SysJob job) throws SchedulerException;
+
+    /**
+     * 批量删除调度信息
+     * 
+     * @param jobIds 需要删除的任务ID
+     * @return 结果
+     */
+    public void deleteJobByIds(Long[] jobIds) throws SchedulerException;
+
+    /**
+     * 任务调度状态修改
+     * 
+     * @param job 调度信息
+     * @return 结果
+     */
+    public int changeStatus(SysJob job) throws SchedulerException;
+
+    /**
+     * 立即运行任务
+     * 
+     * @param job 调度信息
+     * @return 结果
+     */
+    public void run(SysJob job) throws SchedulerException;
+
+    /**
+     * 新增任务
+     * 
+     * @param job 调度信息
+     * @return 结果
+     */
+    public int insertJob(SysJob job) throws SchedulerException, TaskException;
+
+    /**
+     * 更新任务
+     * 
+     * @param job 调度信息
+     * @return 结果
+     */
+    public int updateJob(SysJob job) throws SchedulerException, TaskException;
+
+    /**
+     * 校验cron表达式是否有效
+     * 
+     * @param cronExpression 表达式
+     * @return 结果
+     */
+    public boolean checkCronExpressionIsValid(String cronExpression);
+}

+ 86 - 0
service-job/src/main/java/com/ruoyi/job/service/SysJobLogServiceImpl.java

@@ -0,0 +1,86 @@
+package com.ruoyi.job.service;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.job.domain.SysJobLog;
+import com.ruoyi.job.mapper.SysJobLogMapper;
+
+/**
+ * 定时任务调度日志信息 服务层
+ * 
+ * @author ruoyi
+ */
+@Service
+public class SysJobLogServiceImpl implements ISysJobLogService
+{
+    @Autowired
+    private SysJobLogMapper jobLogMapper;
+
+    /**
+     * 获取quartz调度器日志的计划任务
+     * 
+     * @param jobLog 调度日志信息
+     * @return 调度任务日志集合
+     */
+    @Override
+    public List<SysJobLog> selectJobLogList(SysJobLog jobLog)
+    {
+        return jobLogMapper.selectJobLogList(jobLog);
+    }
+
+    /**
+     * 通过调度任务日志ID查询调度信息
+     * 
+     * @param jobLogId 调度任务日志ID
+     * @return 调度任务日志对象信息
+     */
+    @Override
+    public SysJobLog selectJobLogById(Long jobLogId)
+    {
+        return jobLogMapper.selectJobLogById(jobLogId);
+    }
+
+    /**
+     * 新增任务日志
+     * 
+     * @param jobLog 调度日志信息
+     */
+    @Override
+    public void addJobLog(SysJobLog jobLog)
+    {
+        jobLogMapper.insertJobLog(jobLog);
+    }
+
+    /**
+     * 批量删除调度日志信息
+     * 
+     * @param logIds 需要删除的数据ID
+     * @return 结果
+     */
+    @Override
+    public int deleteJobLogByIds(Long[] logIds)
+    {
+        return jobLogMapper.deleteJobLogByIds(logIds);
+    }
+
+    /**
+     * 删除任务日志
+     * 
+     * @param jobId 调度日志ID
+     */
+    @Override
+    public int deleteJobLogById(Long jobId)
+    {
+        return jobLogMapper.deleteJobLogById(jobId);
+    }
+
+    /**
+     * 清空任务日志
+     */
+    @Override
+    public void cleanJobLog()
+    {
+        jobLogMapper.cleanJobLog();
+    }
+}

+ 253 - 0
service-job/src/main/java/com/ruoyi/job/service/SysJobServiceImpl.java

@@ -0,0 +1,253 @@
+package com.ruoyi.job.service;
+
+import java.util.List;
+import javax.annotation.PostConstruct;
+import org.quartz.JobDataMap;
+import org.quartz.JobKey;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import com.usky.common.core.constant.ScheduleConstants;
+import com.usky.common.core.exception.job.TaskException;
+import com.ruoyi.job.domain.SysJob;
+import com.ruoyi.job.mapper.SysJobMapper;
+import com.ruoyi.job.util.CronUtils;
+import com.ruoyi.job.util.ScheduleUtils;
+
+/**
+ * 定时任务调度信息 服务层
+ * 
+ * @author ruoyi
+ */
+@Service
+public class SysJobServiceImpl implements ISysJobService
+{
+    @Autowired
+    private Scheduler scheduler;
+
+    @Autowired
+    private SysJobMapper jobMapper;
+
+    /**
+     * 项目启动时,初始化定时器 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据)
+     */
+    @PostConstruct
+    public void init() throws SchedulerException, TaskException
+    {
+        scheduler.clear();
+        List<SysJob> jobList = jobMapper.selectJobAll();
+        for (SysJob job : jobList)
+        {
+            ScheduleUtils.createScheduleJob(scheduler, job);
+        }
+    }
+
+    /**
+     * 获取quartz调度器的计划任务列表
+     * 
+     * @param job 调度信息
+     * @return
+     */
+    @Override
+    public List<SysJob> selectJobList(SysJob job)
+    {
+        return jobMapper.selectJobList(job);
+    }
+
+    /**
+     * 通过调度任务ID查询调度信息
+     * 
+     * @param jobId 调度任务ID
+     * @return 调度任务对象信息
+     */
+    @Override
+    public SysJob selectJobById(Long jobId)
+    {
+        return jobMapper.selectJobById(jobId);
+    }
+
+    /**
+     * 暂停任务
+     * 
+     * @param job 调度信息
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int pauseJob(SysJob job) throws SchedulerException
+    {
+        Long jobId = job.getJobId();
+        String jobGroup = job.getJobGroup();
+        job.setStatus(ScheduleConstants.Status.PAUSE.getValue());
+        int rows = jobMapper.updateJob(job);
+        if (rows > 0)
+        {
+            scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
+        }
+        return rows;
+    }
+
+    /**
+     * 恢复任务
+     * 
+     * @param job 调度信息
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int resumeJob(SysJob job) throws SchedulerException
+    {
+        Long jobId = job.getJobId();
+        String jobGroup = job.getJobGroup();
+        job.setStatus(ScheduleConstants.Status.NORMAL.getValue());
+        int rows = jobMapper.updateJob(job);
+        if (rows > 0)
+        {
+            scheduler.resumeJob(ScheduleUtils.getJobKey(jobId, jobGroup));
+        }
+        return rows;
+    }
+
+    /**
+     * 删除任务后,所对应的trigger也将被删除
+     * 
+     * @param job 调度信息
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int deleteJob(SysJob job) throws SchedulerException
+    {
+        Long jobId = job.getJobId();
+        String jobGroup = job.getJobGroup();
+        int rows = jobMapper.deleteJobById(jobId);
+        if (rows > 0)
+        {
+            scheduler.deleteJob(ScheduleUtils.getJobKey(jobId, jobGroup));
+        }
+        return rows;
+    }
+
+    /**
+     * 批量删除调度信息
+     * 
+     * @param jobIds 需要删除的任务ID
+     * @return 结果
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteJobByIds(Long[] jobIds) throws SchedulerException
+    {
+        for (Long jobId : jobIds)
+        {
+            SysJob job = jobMapper.selectJobById(jobId);
+            deleteJob(job);
+        }
+    }
+
+    /**
+     * 任务调度状态修改
+     * 
+     * @param job 调度信息
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int changeStatus(SysJob job) throws SchedulerException
+    {
+        int rows = 0;
+        String status = job.getStatus();
+        if (ScheduleConstants.Status.NORMAL.getValue().equals(status))
+        {
+            rows = resumeJob(job);
+        }
+        else if (ScheduleConstants.Status.PAUSE.getValue().equals(status))
+        {
+            rows = pauseJob(job);
+        }
+        return rows;
+    }
+
+    /**
+     * 立即运行任务
+     * 
+     * @param job 调度信息
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void run(SysJob job) throws SchedulerException
+    {
+        Long jobId = job.getJobId();
+        String jobGroup = job.getJobGroup();
+        SysJob properties = selectJobById(job.getJobId());
+        // 参数
+        JobDataMap dataMap = new JobDataMap();
+        dataMap.put(ScheduleConstants.TASK_PROPERTIES, properties);
+        scheduler.triggerJob(ScheduleUtils.getJobKey(jobId, jobGroup), dataMap);
+    }
+
+    /**
+     * 新增任务
+     * 
+     * @param job 调度信息 调度信息
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int insertJob(SysJob job) throws SchedulerException, TaskException
+    {
+        job.setStatus(ScheduleConstants.Status.PAUSE.getValue());
+        int rows = jobMapper.insertJob(job);
+        if (rows > 0)
+        {
+            ScheduleUtils.createScheduleJob(scheduler, job);
+        }
+        return rows;
+    }
+
+    /**
+     * 更新任务的时间表达式
+     * 
+     * @param job 调度信息
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int updateJob(SysJob job) throws SchedulerException, TaskException
+    {
+        SysJob properties = selectJobById(job.getJobId());
+        int rows = jobMapper.updateJob(job);
+        if (rows > 0)
+        {
+            updateSchedulerJob(job, properties.getJobGroup());
+        }
+        return rows;
+    }
+
+    /**
+     * 更新任务
+     * 
+     * @param job 任务对象
+     * @param jobGroup 任务组名
+     */
+    public void updateSchedulerJob(SysJob job, String jobGroup) throws SchedulerException, com.usky.common.core.exception.job.TaskException
+    {
+        Long jobId = job.getJobId();
+        // 判断是否存在
+        JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup);
+        if (scheduler.checkExists(jobKey))
+        {
+            // 防止创建时存在数据问题 先移除,然后在执行创建操作
+            scheduler.deleteJob(jobKey);
+        }
+        ScheduleUtils.createScheduleJob(scheduler, job);
+    }
+
+    /**
+     * 校验cron表达式是否有效
+     * 
+     * @param cronExpression 表达式
+     * @return 结果
+     */
+    @Override
+    public boolean checkCronExpressionIsValid(String cronExpression)
+    {
+        return CronUtils.isValid(cronExpression);
+    }
+}

+ 39 - 0
service-job/src/main/java/com/ruoyi/job/task/RyTask.java

@@ -0,0 +1,39 @@
+package com.ruoyi.job.task;
+
+import com.usky.common.core.utils.SpringUtils;
+import com.usky.fire.RemoteFireService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import com.usky.common.core.utils.StringUtils;
+
+/**
+ * 定时任务调度测试
+ * 
+ * @author ruoyi
+ */
+@Component("ryTask")
+public class RyTask
+{
+    @Autowired
+    private RemoteFireService remoteFireService;
+
+    public void ryMultipleParams(String s, Boolean b, Long l, Double d, Integer i)
+    {
+        System.out.println(StringUtils.format("执行多参方法: 字符串类型{},布尔类型{},长整型{},浮点型{},整形{}", s, b, l, d, i));
+    }
+
+    public void ryParams(String params)
+    {
+        System.out.println("执行有参方法:" + params);
+    }
+
+    public void ryNoParams()
+    {
+        System.out.println("执行无参方法");
+    }
+
+    public void addPatrolInspectionAbnormalData(){
+        remoteFireService.addPatrolInspectionAbnormalData();
+        System.out.println("addPatrolInspectionAbnormalData");
+    }
+}

+ 106 - 0
service-job/src/main/java/com/ruoyi/job/util/AbstractQuartzJob.java

@@ -0,0 +1,106 @@
+package com.ruoyi.job.util;
+
+import java.util.Date;
+import org.quartz.Job;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.usky.common.core.constant.ScheduleConstants;
+import com.usky.common.core.utils.ExceptionUtil;
+import com.usky.common.core.utils.SpringUtils;
+import com.usky.common.core.utils.StringUtils;
+import com.usky.common.core.utils.bean.BeanUtils;
+import com.ruoyi.job.domain.SysJob;
+import com.ruoyi.job.domain.SysJobLog;
+import com.ruoyi.job.service.ISysJobLogService;
+
+/**
+ * 抽象quartz调用
+ *
+ * @author ruoyi
+ */
+public abstract class AbstractQuartzJob implements Job
+{
+    private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class);
+
+    /**
+     * 线程本地变量
+     */
+    private static ThreadLocal<Date> threadLocal = new ThreadLocal<>();
+
+    @Override
+    public void execute(JobExecutionContext context) throws JobExecutionException
+    {
+        SysJob sysJob = new SysJob();
+        BeanUtils.copyBeanProp(sysJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES));
+        try
+        {
+            before(context, sysJob);
+            if (sysJob != null)
+            {
+                doExecute(context, sysJob);
+            }
+            after(context, sysJob, null);
+        }
+        catch (Exception e)
+        {
+            log.error("任务执行异常  - :", e);
+            after(context, sysJob, e);
+        }
+    }
+
+    /**
+     * 执行前
+     *
+     * @param context 工作执行上下文对象
+     * @param sysJob 系统计划任务
+     */
+    protected void before(JobExecutionContext context, SysJob sysJob)
+    {
+        threadLocal.set(new Date());
+    }
+
+    /**
+     * 执行后
+     *
+     * @param context 工作执行上下文对象
+     * @param sysJob 系统计划任务
+     */
+    protected void after(JobExecutionContext context, SysJob sysJob, Exception e)
+    {
+        Date startTime = threadLocal.get();
+        threadLocal.remove();
+
+        final SysJobLog sysJobLog = new SysJobLog();
+        sysJobLog.setJobName(sysJob.getJobName());
+        sysJobLog.setJobGroup(sysJob.getJobGroup());
+        sysJobLog.setInvokeTarget(sysJob.getInvokeTarget());
+        sysJobLog.setStartTime(startTime);
+        sysJobLog.setStopTime(new Date());
+        long runMs = sysJobLog.getStopTime().getTime() - sysJobLog.getStartTime().getTime();
+        sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒");
+        if (e != null)
+        {
+            sysJobLog.setStatus("1");
+            String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000);
+            sysJobLog.setExceptionInfo(errorMsg);
+        }
+        else
+        {
+            sysJobLog.setStatus("0");
+        }
+
+        // 写入数据库当中
+        SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog);
+    }
+
+    /**
+     * 执行方法,由子类重载
+     *
+     * @param context 工作执行上下文对象
+     * @param sysJob 系统计划任务
+     * @throws Exception 执行过程中的异常
+     */
+    protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception;
+}

+ 63 - 0
service-job/src/main/java/com/ruoyi/job/util/CronUtils.java

@@ -0,0 +1,63 @@
+package com.ruoyi.job.util;
+
+import java.text.ParseException;
+import java.util.Date;
+import org.quartz.CronExpression;
+
+/**
+ * cron表达式工具类
+ * 
+ * @author ruoyi
+ *
+ */
+public class CronUtils
+{
+    /**
+     * 返回一个布尔值代表一个给定的Cron表达式的有效性
+     *
+     * @param cronExpression Cron表达式
+     * @return boolean 表达式是否有效
+     */
+    public static boolean isValid(String cronExpression)
+    {
+        return CronExpression.isValidExpression(cronExpression);
+    }
+
+    /**
+     * 返回一个字符串值,表示该消息无效Cron表达式给出有效性
+     *
+     * @param cronExpression Cron表达式
+     * @return String 无效时返回表达式错误描述,如果有效返回null
+     */
+    public static String getInvalidMessage(String cronExpression)
+    {
+        try
+        {
+            new CronExpression(cronExpression);
+            return null;
+        }
+        catch (ParseException pe)
+        {
+            return pe.getMessage();
+        }
+    }
+
+    /**
+     * 返回下一个执行时间根据给定的Cron表达式
+     *
+     * @param cronExpression Cron表达式
+     * @return Date 下次Cron表达式执行时间
+     */
+    public static Date getNextExecution(String cronExpression)
+    {
+        try
+        {
+            CronExpression cron = new CronExpression(cronExpression);
+            return cron.getNextValidTimeAfter(new Date(System.currentTimeMillis()));
+        }
+        catch (ParseException e)
+        {
+            throw new IllegalArgumentException(e.getMessage());
+        }
+    }
+}

+ 182 - 0
service-job/src/main/java/com/ruoyi/job/util/JobInvokeUtil.java

@@ -0,0 +1,182 @@
+package com.ruoyi.job.util;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.LinkedList;
+import java.util.List;
+import com.usky.common.core.utils.SpringUtils;
+import com.usky.common.core.utils.StringUtils;
+import com.ruoyi.job.domain.SysJob;
+
+/**
+ * 任务执行工具
+ *
+ * @author ruoyi
+ */
+public class JobInvokeUtil
+{
+    /**
+     * 执行方法
+     *
+     * @param sysJob 系统任务
+     */
+    public static void invokeMethod(SysJob sysJob) throws Exception
+    {
+        String invokeTarget = sysJob.getInvokeTarget();
+        String beanName = getBeanName(invokeTarget);
+        String methodName = getMethodName(invokeTarget);
+        List<Object[]> methodParams = getMethodParams(invokeTarget);
+
+        if (!isValidClassName(beanName))
+        {
+            Object bean = SpringUtils.getBean(beanName);
+            invokeMethod(bean, methodName, methodParams);
+        }
+        else
+        {
+            Object bean = Class.forName(beanName).newInstance();
+            invokeMethod(bean, methodName, methodParams);
+        }
+    }
+
+    /**
+     * 调用任务方法
+     *
+     * @param bean 目标对象
+     * @param methodName 方法名称
+     * @param methodParams 方法参数
+     */
+    private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams)
+            throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
+            InvocationTargetException
+    {
+        if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0)
+        {
+            Method method = bean.getClass().getDeclaredMethod(methodName, getMethodParamsType(methodParams));
+            method.invoke(bean, getMethodParamsValue(methodParams));
+        }
+        else
+        {
+            Method method = bean.getClass().getDeclaredMethod(methodName);
+            method.invoke(bean);
+        }
+    }
+
+    /**
+     * 校验是否为为class包名
+     * 
+     * @param invokeTarget 名称
+     * @return true是 false否
+     */
+    public static boolean isValidClassName(String invokeTarget)
+    {
+        return StringUtils.countMatches(invokeTarget, ".") > 1;
+    }
+
+    /**
+     * 获取bean名称
+     * 
+     * @param invokeTarget 目标字符串
+     * @return bean名称
+     */
+    public static String getBeanName(String invokeTarget)
+    {
+        String beanName = StringUtils.substringBefore(invokeTarget, "(");
+        return StringUtils.substringBeforeLast(beanName, ".");
+    }
+
+    /**
+     * 获取bean方法
+     * 
+     * @param invokeTarget 目标字符串
+     * @return method方法
+     */
+    public static String getMethodName(String invokeTarget)
+    {
+        String methodName = StringUtils.substringBefore(invokeTarget, "(");
+        return StringUtils.substringAfterLast(methodName, ".");
+    }
+
+    /**
+     * 获取method方法参数相关列表
+     * 
+     * @param invokeTarget 目标字符串
+     * @return method方法相关参数列表
+     */
+    public static List<Object[]> getMethodParams(String invokeTarget)
+    {
+        String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")");
+        if (StringUtils.isEmpty(methodStr))
+        {
+            return null;
+        }
+        String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)");
+        List<Object[]> classs = new LinkedList<>();
+        for (int i = 0; i < methodParams.length; i++)
+        {
+            String str = StringUtils.trimToEmpty(methodParams[i]);
+            // String字符串类型,以'或"开头
+            if (StringUtils.startsWithAny(str, "'", "\""))
+            {
+                classs.add(new Object[] { StringUtils.substring(str, 1, str.length() - 1), String.class });
+            }
+            // boolean布尔类型,等于true或者false
+            else if ("true".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str))
+            {
+                classs.add(new Object[] { Boolean.valueOf(str), Boolean.class });
+            }
+            // long长整形,以L结尾
+            else if (StringUtils.endsWith(str, "L"))
+            {
+                classs.add(new Object[] { Long.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Long.class });
+            }
+            // double浮点类型,以D结尾
+            else if (StringUtils.endsWith(str, "D"))
+            {
+                classs.add(new Object[] { Double.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Double.class });
+            }
+            // 其他类型归类为整形
+            else
+            {
+                classs.add(new Object[] { Integer.valueOf(str), Integer.class });
+            }
+        }
+        return classs;
+    }
+
+    /**
+     * 获取参数类型
+     * 
+     * @param methodParams 参数相关列表
+     * @return 参数类型列表
+     */
+    public static Class<?>[] getMethodParamsType(List<Object[]> methodParams)
+    {
+        Class<?>[] classs = new Class<?>[methodParams.size()];
+        int index = 0;
+        for (Object[] os : methodParams)
+        {
+            classs[index] = (Class<?>) os[1];
+            index++;
+        }
+        return classs;
+    }
+
+    /**
+     * 获取参数值
+     * 
+     * @param methodParams 参数相关列表
+     * @return 参数值列表
+     */
+    public static Object[] getMethodParamsValue(List<Object[]> methodParams)
+    {
+        Object[] classs = new Object[methodParams.size()];
+        int index = 0;
+        for (Object[] os : methodParams)
+        {
+            classs[index] = (Object) os[0];
+            index++;
+        }
+        return classs;
+    }
+}

+ 22 - 0
service-job/src/main/java/com/ruoyi/job/util/QuartzDisallowConcurrentExecution.java

@@ -0,0 +1,22 @@
+package com.ruoyi.job.util;
+
+import org.quartz.DisallowConcurrentExecution;
+import org.quartz.JobExecutionContext;
+
+import com.ruoyi.job.domain.SysJob;
+
+/**
+ * 定时任务处理(禁止并发执行)
+ * 
+ * @author ruoyi
+ *
+ */
+@DisallowConcurrentExecution
+public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob
+{
+    @Override
+    protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception
+    {
+        JobInvokeUtil.invokeMethod(sysJob);
+    }
+}

+ 20 - 0
service-job/src/main/java/com/ruoyi/job/util/QuartzJobExecution.java

@@ -0,0 +1,20 @@
+package com.ruoyi.job.util;
+
+import org.quartz.JobExecutionContext;
+
+import com.ruoyi.job.domain.SysJob;
+
+/**
+ * 定时任务处理(允许并发执行)
+ * 
+ * @author ruoyi
+ *
+ */
+public class QuartzJobExecution extends AbstractQuartzJob
+{
+    @Override
+    protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception
+    {
+        JobInvokeUtil.invokeMethod(sysJob);
+    }
+}

+ 132 - 0
service-job/src/main/java/com/ruoyi/job/util/ScheduleUtils.java

@@ -0,0 +1,132 @@
+package com.ruoyi.job.util;
+
+import org.quartz.CronScheduleBuilder;
+import org.quartz.CronTrigger;
+import org.quartz.Job;
+import org.quartz.JobBuilder;
+import org.quartz.JobDetail;
+import org.quartz.JobKey;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.TriggerBuilder;
+import org.quartz.TriggerKey;
+import com.usky.common.core.constant.Constants;
+import com.usky.common.core.constant.ScheduleConstants;
+import com.usky.common.core.exception.job.TaskException;
+import com.usky.common.core.exception.job.TaskException.Code;
+import com.usky.common.core.utils.StringUtils;
+import com.ruoyi.job.domain.SysJob;
+
+/**
+ * 定时任务工具类
+ * 
+ * @author ruoyi
+ *
+ */
+public class ScheduleUtils
+{
+    /**
+     * 得到quartz任务类
+     *
+     * @param sysJob 执行计划
+     * @return 具体执行任务类
+     */
+    private static Class<? extends Job> getQuartzJobClass(SysJob sysJob)
+    {
+        boolean isConcurrent = "0".equals(sysJob.getConcurrent());
+        return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;
+    }
+
+    /**
+     * 构建任务触发对象
+     */
+    public static TriggerKey getTriggerKey(Long jobId, String jobGroup)
+    {
+        return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
+    }
+
+    /**
+     * 构建任务键对象
+     */
+    public static JobKey getJobKey(Long jobId, String jobGroup)
+    {
+        return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
+    }
+
+    /**
+     * 创建定时任务
+     */
+    public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException
+    {
+        Class<? extends Job> jobClass = getQuartzJobClass(job);
+        // 构建job信息
+        Long jobId = job.getJobId();
+        String jobGroup = job.getJobGroup();
+        JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build();
+
+        // 表达式调度构建器
+        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
+        cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder);
+
+        // 按新的cronExpression表达式构建一个新的trigger
+        CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup))
+                .withSchedule(cronScheduleBuilder).build();
+
+        // 放入参数,运行时的方法可以获取
+        jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);
+
+        // 判断是否存在
+        if (scheduler.checkExists(getJobKey(jobId, jobGroup)))
+        {
+            // 防止创建时存在数据问题 先移除,然后在执行创建操作
+            scheduler.deleteJob(getJobKey(jobId, jobGroup));
+        }
+
+        scheduler.scheduleJob(jobDetail, trigger);
+
+        // 暂停任务
+        if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue()))
+        {
+            scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
+        }
+    }
+
+    /**
+     * 设置定时任务策略
+     */
+    public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb)
+            throws TaskException
+    {
+        switch (job.getMisfirePolicy())
+        {
+            case ScheduleConstants.MISFIRE_DEFAULT:
+                return cb;
+            case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
+                return cb.withMisfireHandlingInstructionIgnoreMisfires();
+            case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
+                return cb.withMisfireHandlingInstructionFireAndProceed();
+            case ScheduleConstants.MISFIRE_DO_NOTHING:
+                return cb.withMisfireHandlingInstructionDoNothing();
+            default:
+                throw new TaskException("The task misfire policy '" + job.getMisfirePolicy()
+                        + "' cannot be used in cron schedule tasks", Code.CONFIG_ERROR);
+        }
+    }
+
+    /**
+     * 检查包名是否为白名单配置
+     * 
+     * @param invokeTarget 目标字符串
+     * @return 结果
+     */
+    public static boolean whiteList(String invokeTarget)
+    {
+        String packageName = StringUtils.substringBefore(invokeTarget, "(");
+        int count = StringUtils.countMatches(packageName, ".");
+        if (count > 1)
+        {
+            return StringUtils.containsAnyIgnoreCase(invokeTarget, Constants.JOB_WHITELIST_STR);
+        }
+        return true;
+    }
+}

+ 10 - 0
service-job/src/main/resources/banner.txt

@@ -0,0 +1,10 @@
+Spring Boot Version: ${spring-boot.version}
+Spring Application Name: ${spring.application.name}
+                            _            _         _     
+                           (_)          (_)       | |    
+ _ __  _   _   ___   _   _  _  ______    _   ___  | |__  
+| '__|| | | | / _ \ | | | || ||______|  | | / _ \ | '_ \ 
+| |   | |_| || (_) || |_| || |          | || (_) || |_) |
+|_|    \__,_| \___/  \__, ||_|          | | \___/ |_.__/ 
+                      __/ |            _/ |              
+                     |___/            |__/               

+ 47 - 0
service-job/src/main/resources/bootstrap.yaml.bak

@@ -0,0 +1,47 @@
+# Tomcat
+server:
+  port: 9203
+# spring配置
+spring:
+  application:
+    # 应用名称
+    name: service-job
+  profiles:
+    # 环境配置
+    active: dev
+  cloud:
+    nacos:
+      discovery:
+        # 服务注册地址
+        server-addr: usky-cloud-nacos:8848
+#      config:
+#        # 配置中心地址
+#        server-addr: usky-cloud-nacos:8848
+#        # 配置文件格式
+#        file-extension: yml
+#        # 共享配置
+#        shared-configs:
+#          - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
+  redis:
+    host: usky-cloud-redis
+    port: 6379
+    password:
+  datasource:
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    url: jdbc:mysql://usky-cloud-mysql:3306/ry-cloud?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+    username: usky
+    password: Yt#75Usky
+  main:
+    allow-circular-references: true
+# mybatis配置
+mybatis:
+  # 搜索指定包别名
+  typeAliasesPackage: com.ruoyi.job.domain
+  # 配置mapper的扫描,找到所有的mapper.xml映射文件
+  mapperLocations: classpath:mapper/**/*.xml
+
+# swagger配置
+swagger:
+  title: 定时任务接口文档
+  license: Powered By ruoyi
+  licenseUrl: https://ruoyi.vip

+ 23 - 0
service-job/src/main/resources/bootstrap.yml

@@ -0,0 +1,23 @@
+# Tomcat
+server:
+  port: 9203
+spring:
+  application:
+    # 应用名称
+    name: service-job
+  profiles:
+    # 环境配置
+    active: dev
+  cloud:
+    nacos:
+      discovery:
+        # 服务注册地址
+        server-addr: usky-cloud-nacos:8848
+      config:
+        # 配置中心地址
+        server-addr: usky-cloud-nacos:8848
+        # 配置文件格式
+        file-extension: yml
+        # 共享配置
+        shared-configs:
+          - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

+ 94 - 0
service-job/src/main/resources/logback.xml

@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration scan="true" scanPeriod="60 seconds" debug="false">
+    <!-- 日志存放路径 -->
+    <property name="log.path" value="/var/log/uskycloud/service-job" />
+    <!-- 日志输出格式 -->
+    <property name="log.pattern" value="%d{MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{26}:%line: %msg%n" />
+    <!--    	<property name="log.pattern" value="%gray(%d{MM-dd HH:mm:ss.SSS}) %highlight(%-5level) &#45;&#45; [%gray(%thread)] %cyan(%logger{26}:%line): %msg%n" />-->
+
+
+    <property name="SQL_PACKAGE" value="com.ruoyi.job.mapper"/>
+
+    <!-- 控制台输出 -->
+    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+    </appender>
+
+    <appender name="file_sql" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/sql.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/sql.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 日志最大的历史 60天 -->
+            <maxHistory>3</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+    </appender>
+
+    <!-- 系统日志输出 -->
+    <appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/info.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/info.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 日志最大的历史 60天 -->
+            <maxHistory>3</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>INFO</level>
+            <!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/error.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 日志最大的历史 60天 -->
+            <maxHistory>60</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>ERROR</level>
+            <!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <!-- 系统模块日志级别控制  -->
+    <!--	<logger name="com.usky" level="info" />-->
+    <!-- Spring日志级别控制  -->
+    <!--	<logger name="org.springframework" level="warn" />-->
+
+    <logger name="${SQL_PACKAGE}" additivity="false" level="debug">
+        <appender-ref ref="console"/>
+        <appender-ref ref="file_sql"/>
+    </logger>
+
+    <!--系统操作日志-->
+    <root level="info">
+        <appender-ref ref="file_info" />
+        <appender-ref ref="file_error" />
+        <appender-ref ref="console" />
+    </root>
+</configuration>

+ 93 - 0
service-job/src/main/resources/mapper/job/SysJobLogMapper.xml

@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.job.mapper.SysJobLogMapper">
+
+	<resultMap type="com.ruoyi.job.domain.SysJobLog" id="SysJobLogResult">
+		<id     property="jobLogId"       column="job_log_id"      />
+		<result property="jobName"        column="job_name"        />
+		<result property="jobGroup"       column="job_group"       />
+		<result property="invokeTarget"   column="invoke_target"   />
+		<result property="jobMessage"     column="job_message"     />
+		<result property="status"         column="status"          />
+		<result property="exceptionInfo"  column="exception_info"  />
+		<result property="createTime"     column="create_time"     />
+	</resultMap>
+	
+	<sql id="selectJobLogVo">
+        select job_log_id, job_name, job_group, invoke_target, job_message, status, exception_info, create_time 
+		from sys_job_log
+    </sql>
+	
+	<select id="selectJobLogList" parameterType="com.ruoyi.job.domain.SysJobLog" resultMap="SysJobLogResult">
+		<include refid="selectJobLogVo"/>
+		<where>
+			<if test="jobName != null and jobName != ''">
+				AND job_name like concat('%', #{jobName}, '%')
+			</if>
+			<if test="jobGroup != null and jobGroup != ''">
+				AND job_group = #{jobGroup}
+			</if>
+			<if test="status != null and status != ''">
+				AND status = #{status}
+			</if>
+			<if test="invokeTarget != null and invokeTarget != ''">
+				AND invoke_target like concat('%', #{invokeTarget}, '%')
+			</if>
+			<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
+				and date_format(create_time,'%y%m%d') &gt;= date_format(#{params.beginTime},'%y%m%d')
+			</if>
+			<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
+				and date_format(create_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')
+			</if>
+		</where>
+	</select>
+	
+	<select id="selectJobLogAll" resultMap="SysJobLogResult">
+		<include refid="selectJobLogVo"/>
+	</select>
+	
+	<select id="selectJobLogById" parameterType="Long" resultMap="SysJobLogResult">
+		<include refid="selectJobLogVo"/>
+		where job_log_id = #{jobLogId}
+	</select>
+	
+	<delete id="deleteJobLogById" parameterType="Long">
+ 		delete from sys_job_log where job_log_id = #{jobLogId}
+ 	</delete>
+ 	
+ 	<delete id="deleteJobLogByIds" parameterType="Long">
+ 		delete from sys_job_log where job_log_id in
+ 		<foreach collection="array" item="jobLogId" open="(" separator="," close=")">
+ 			#{jobLogId}
+        </foreach> 
+ 	</delete>
+ 	
+ 	<update id="cleanJobLog">
+        truncate table sys_job_log
+    </update>
+ 	
+ 	<insert id="insertJobLog" parameterType="com.ruoyi.job.domain.SysJobLog">
+ 		insert into sys_job_log(
+ 			<if test="jobLogId != null and jobLogId != 0">job_log_id,</if>
+ 			<if test="jobName != null and jobName != ''">job_name,</if>
+ 			<if test="jobGroup != null and jobGroup != ''">job_group,</if>
+ 			<if test="invokeTarget != null and invokeTarget != ''">invoke_target,</if>
+ 			<if test="jobMessage != null and jobMessage != ''">job_message,</if>
+ 			<if test="status != null and status != ''">status,</if>
+ 			<if test="exceptionInfo != null and exceptionInfo != ''">exception_info,</if>
+ 			create_time
+ 		)values(
+ 			<if test="jobLogId != null and jobLogId != 0">#{jobLogId},</if>
+ 			<if test="jobName != null and jobName != ''">#{jobName},</if>
+ 			<if test="jobGroup != null and jobGroup != ''">#{jobGroup},</if>
+ 			<if test="invokeTarget != null and invokeTarget != ''">#{invokeTarget},</if>
+ 			<if test="jobMessage != null and jobMessage != ''">#{jobMessage},</if>
+ 			<if test="status != null and status != ''">#{status},</if>
+ 			<if test="exceptionInfo != null and exceptionInfo != ''">#{exceptionInfo},</if>
+ 			sysdate()
+ 		)
+	</insert>
+
+</mapper> 

+ 111 - 0
service-job/src/main/resources/mapper/job/SysJobMapper.xml

@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.job.mapper.SysJobMapper">
+
+	<resultMap type="com.ruoyi.job.domain.SysJob" id="SysJobResult">
+		<id     property="jobId"          column="job_id"          />
+		<result property="jobName"        column="job_name"        />
+		<result property="jobGroup"       column="job_group"       />
+		<result property="invokeTarget"   column="invoke_target"   />
+		<result property="cronExpression" column="cron_expression" />
+		<result property="misfirePolicy"  column="misfire_policy"  />
+		<result property="concurrent"     column="concurrent"      />
+		<result property="status"         column="status"          />
+		<result property="createBy"       column="create_by"       />
+		<result property="createTime"     column="create_time"     />
+		<result property="updateBy"       column="update_by"       />
+		<result property="updateTime"     column="update_time"     />
+		<result property="remark"         column="remark"          />
+	</resultMap>
+	
+	<sql id="selectJobVo">
+        select job_id, job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark 
+		from sys_job
+    </sql>
+	
+	<select id="selectJobList" parameterType="com.ruoyi.job.domain.SysJob" resultMap="SysJobResult">
+		<include refid="selectJobVo"/>
+		<where>
+			<if test="jobName != null and jobName != ''">
+				AND job_name like concat('%', #{jobName}, '%')
+			</if>
+			<if test="jobGroup != null and jobGroup != ''">
+				AND job_group = #{jobGroup}
+			</if>
+			<if test="status != null and status != ''">
+				AND status = #{status}
+			</if>
+			<if test="invokeTarget != null and invokeTarget != ''">
+				AND invoke_target like concat('%', #{invokeTarget}, '%')
+			</if>
+		</where>
+	</select>
+	
+	<select id="selectJobAll" resultMap="SysJobResult">
+		<include refid="selectJobVo"/>
+	</select>
+	
+	<select id="selectJobById" parameterType="Long" resultMap="SysJobResult">
+		<include refid="selectJobVo"/>
+		where job_id = #{jobId}
+	</select>
+	
+	<delete id="deleteJobById" parameterType="Long">
+ 		delete from sys_job where job_id = #{jobId}
+ 	</delete>
+ 	
+ 	<delete id="deleteJobByIds" parameterType="Long">
+ 		delete from sys_job where job_id in
+ 		<foreach collection="array" item="jobId" open="(" separator="," close=")">
+ 			#{jobId}
+        </foreach> 
+ 	</delete>
+ 	
+ 	<update id="updateJob" parameterType="com.ruoyi.job.domain.SysJob">
+ 		update sys_job
+ 		<set>
+ 			<if test="jobName != null and jobName != ''">job_name = #{jobName},</if>
+ 			<if test="jobGroup != null and jobGroup != ''">job_group = #{jobGroup},</if>
+ 			<if test="invokeTarget != null and invokeTarget != ''">invoke_target = #{invokeTarget},</if>
+ 			<if test="cronExpression != null and cronExpression != ''">cron_expression = #{cronExpression},</if>
+ 			<if test="misfirePolicy != null and misfirePolicy != ''">misfire_policy = #{misfirePolicy},</if>
+ 			<if test="concurrent != null and concurrent != ''">concurrent = #{concurrent},</if>
+ 			<if test="status !=null">status = #{status},</if>
+ 			<if test="remark != null and remark != ''">remark = #{remark},</if>
+ 			<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
+ 			update_time = sysdate()
+ 		</set>
+ 		where job_id = #{jobId}
+	</update>
+ 	
+ 	<insert id="insertJob" parameterType="com.ruoyi.job.domain.SysJob" useGeneratedKeys="true" keyProperty="jobId">
+ 		insert into sys_job(
+ 			<if test="jobId != null and jobId != 0">job_id,</if>
+ 			<if test="jobName != null and jobName != ''">job_name,</if>
+ 			<if test="jobGroup != null and jobGroup != ''">job_group,</if>
+ 			<if test="invokeTarget != null and invokeTarget != ''">invoke_target,</if>
+ 			<if test="cronExpression != null and cronExpression != ''">cron_expression,</if>
+ 			<if test="misfirePolicy != null and misfirePolicy != ''">misfire_policy,</if>
+ 			<if test="concurrent != null and concurrent != ''">concurrent,</if>
+ 			<if test="status != null and status != ''">status,</if>
+ 			<if test="remark != null and remark != ''">remark,</if>
+ 			<if test="createBy != null and createBy != ''">create_by,</if>
+ 			create_time
+ 		)values(
+ 			<if test="jobId != null and jobId != 0">#{jobId},</if>
+ 			<if test="jobName != null and jobName != ''">#{jobName},</if>
+ 			<if test="jobGroup != null and jobGroup != ''">#{jobGroup},</if>
+ 			<if test="invokeTarget != null and invokeTarget != ''">#{invokeTarget},</if>
+ 			<if test="cronExpression != null and cronExpression != ''">#{cronExpression},</if>
+ 			<if test="misfirePolicy != null and misfirePolicy != ''">#{misfirePolicy},</if>
+ 			<if test="concurrent != null and concurrent != ''">#{concurrent},</if>
+ 			<if test="status != null and status != ''">#{status},</if>
+ 			<if test="remark != null and remark != ''">#{remark},</if>
+ 			<if test="createBy != null and createBy != ''">#{createBy},</if>
+ 			sysdate()
+ 		)
+	</insert>
+
+</mapper> 

+ 22 - 8
service-park/service-park-biz/src/main/resources/logback.xml

@@ -3,7 +3,9 @@
     <!-- 日志存放路径 -->
 	<property name="log.path" value="/var/log/uskycloud/service-park" />
    <!-- 日志输出格式 -->
-	<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
+    <property name="log.pattern" value="%d{MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{26}:%line: %msg%n" />
+
+    <property name="SQL_PACKAGE" value="com.usky.park.mapper"/>
 
     <!-- 控制台输出 -->
 	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
@@ -12,6 +14,20 @@
 		</encoder>
 	</appender>
 
+    <appender name="file_sql" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/sql.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/sql.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 日志最大的历史 60天 -->
+            <maxHistory>3</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+    </appender>
+
     <!-- 系统日志输出 -->
 	<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
 	    <file>${log.path}/info.log</file>
@@ -58,17 +74,15 @@
     </appender>
 
     <!-- 系统模块日志级别控制  -->
-	<logger name="com.usky.park" level="info" />
-	<!-- Spring日志级别控制  -->
-	<logger name="org.springframework" level="warn" />
-
-	<root level="info">
-		<appender-ref ref="console" />
-	</root>
+    <logger name="${SQL_PACKAGE}" additivity="false" level="debug">
+        <appender-ref ref="console"/>
+        <appender-ref ref="file_sql"/>
+    </logger>
 	
 	<!--系统操作日志-->
     <root level="info">
         <appender-ref ref="file_info" />
         <appender-ref ref="file_error" />
+        <appender-ref ref="console" />
     </root>
 </configuration>