Kaynağa Gözat

soc-modules-deploy模块后台代码迁移-白令海token、心跳及应用清单接口

humingshi-7@163.com 1 yıl önce
ebeveyn
işleme
8aabcfbe06
22 değiştirilmiş dosya ile 1565 ekleme ve 5 silme
  1. 319 0
      soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/controller/BeringController.java
  2. 11 0
      soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/dao/SysConfDao.java
  3. 29 0
      soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/domain/SysConf.java
  4. 17 0
      soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/job/HeartBeatJob.java
  5. 8 0
      soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/service/AppRunInfoExtendService.java
  6. 8 0
      soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/service/AsyncService.java
  7. 24 0
      soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/service/BeringService.java
  8. 4 1
      soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/service/FrontTaskService.java
  9. 13 0
      soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/service/impl/AppRunInfoServiceExtendImpl.java
  10. 265 0
      soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/service/impl/AsyncServiceImpl.java
  11. 452 0
      soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/service/impl/BeringServiceImpl.java
  12. 101 4
      soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/service/impl/FrontTaskServiceImpl.java
  13. 19 0
      soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/util/CommonUtils.java
  14. 34 0
      soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/vo/MachineInfo.java
  15. 24 0
      soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/vo/TokenCache.java
  16. 23 0
      soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/vo/TokenVo.java
  17. 50 0
      soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/vo/heart/AppRunningInfo.java
  18. 46 0
      soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/vo/heart/HeartBeat.java
  19. 29 0
      soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/vo/heart/HeartResponse.java
  20. 25 0
      soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/vo/heart/HeartTimeVo.java
  21. 55 0
      soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/vo/heart/ResponeError.java
  22. 9 0
      soc-modules/soc-modules-deploy/src/main/resources/mapper/SysConfDao.xml

+ 319 - 0
soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/controller/BeringController.java

@@ -0,0 +1,319 @@
+package com.xunmei.deploy.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.xunmei.common.core.utils.StringUtils;
+import com.xunmei.deploy.domain.AppInfoExtend;
+import com.xunmei.deploy.domain.HostInfo;
+import com.xunmei.deploy.domain.UpgradeTask;
+import com.xunmei.deploy.service.*;
+import com.xunmei.deploy.util.CommonUtils;
+import com.xunmei.deploy.util.RedisPrefix;
+import com.xunmei.deploy.util.RedisTemplateUtil;
+import com.xunmei.deploy.util.UTCTimeUtils;
+import com.xunmei.deploy.vo.TokenCache;
+import com.xunmei.deploy.vo.TokenVo;
+import com.xunmei.deploy.vo.appinfo.AppInfoVo;
+import com.xunmei.deploy.vo.appinfo.HotfixVo;
+import com.xunmei.deploy.vo.appinfo.PutAppInfoVo;
+import com.xunmei.deploy.vo.heart.HeartBeat;
+import com.xunmei.deploy.vo.heart.HeartResponse;
+import com.xunmei.deploy.vo.heart.ResponeError;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.rmi.ServerException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+public class BeringController {
+
+    private Logger logger = LoggerFactory.getLogger(getClass());
+    @Autowired
+    private BeringService beringService;
+
+    @Autowired
+    private AppInfoService appInfoService;
+    @Autowired
+    private HostInfoService hostInfoService;
+    @Autowired
+    private UpgradeTaskService upgradeTaskService;
+    @Autowired
+    private AppInfoExtendService extendService;
+
+    @Resource
+    private RedisTemplateUtil redisTemplateUtil;
+
+    /**
+     * 白令海Rest接口-获取token
+     */
+    @PostMapping("/api/deploy/accesstoken")
+    public Object register(@RequestBody TokenVo tokenVo, HttpServletResponse response){
+        Map<String,Object> map = new HashMap<>();
+        try {
+            HostInfo hostInfo = beringService.generateAccessToken(tokenVo);
+            //注册成功返回信息
+            map.put("access_token",hostInfo.getAccessToken());
+            map.put("expires_in",7200);
+            map.put("token_type","Bearer");
+            map.put("scope","* identity");
+        }catch (ServerException e) {
+            logger.error("白令海Rest接口-获取token出现异常:{}",e);
+            response.setStatus(400);
+            map.put("message",e.getMessage());
+        } catch (Exception e) {
+            logger.error("白令海Rest接口-获取token出现异常:{}",e);
+            map.put("message",e.getMessage());
+        }
+        return map;
+    }
+    /**
+     * 白令海Rest接口-上送心跳
+     */
+    @PostMapping("/api/deploy/heartbeat")
+    public Object heartBeat(HttpServletRequest request, @RequestBody HeartBeat heartBeat){
+        //获取令牌
+        String authorization = request.getHeader("Authorization");
+        authorization = StringUtils.replace(authorization,"Bearer ","");
+
+        try {
+            HeartResponse heartResponse = beringService.heartBeat(authorization, heartBeat);
+            return heartResponse;
+        } catch (Exception e) {
+            ResponseEntity responseEntity = ResponeError.Info(e.getMessage(), null);
+            logger.error("上送心跳出现异常:{}",e);
+            return responseEntity;
+        }
+    }
+
+    /**
+     * 白令海Rest接口-推送应用清单
+     */
+    @PostMapping("/api/deploy/list")
+    public Object putList(@RequestBody PutAppInfoVo putAppInfoVo, HttpServletResponse response, HttpServletRequest request){
+        Map<String,Object> map = new HashMap<>();
+        logger.info("白令海推送应用清单:{}",JSONArray.toJSON(putAppInfoVo).toString());
+        try {
+            //获取令牌
+            String authorization = request.getHeader("Authorization");
+            String token = StringUtils.replace(authorization,"Bearer ","");
+
+            //根据token获取主机信息
+            TokenCache tokenCache = null;
+            String result = redisTemplateUtil.get(RedisPrefix.CACHE_TOKEN_TIMES + ":" + token);
+            if (StringUtils.isNotBlank(result)){
+                tokenCache =  JSON.parseObject(result, TokenCache.class);
+            }
+
+            //获取秘钥(前置机id、注册码)
+            String clientId = tokenCache.getClientId();
+
+            logger.info("白令海本次推送应用清单注册码:{}",clientId);
+
+            if (!this.checkParam(putAppInfoVo)){
+                logger.error("白令海推送应用清单,参数校验不通过!");
+                response.setStatus(400);
+                map.put("message","参数校验不通过!");
+                return map;
+            }
+            appInfoService.saveAppInfo(clientId,putAppInfoVo);
+            logger.info("保存白令海推送应用清单成功!");
+        }catch (Exception e) {
+            logger.error("推送应用清单出现异常:{}",e);
+            response.setStatus(400);
+            map.put("message",e.getMessage());
+        }
+        return map;
+    }
+
+
+    /**
+     * 白令海Rest接口-获取应用清单
+     */
+    @GetMapping("/api/deploy/list")
+    public Object getAppList(HttpServletResponse response, HttpServletRequest request){
+
+        try {
+            //获取当前项目运行的ip
+            String serverName = request.getHeader("X-Local-Ip");
+            //nginx 监听默认端口为8080
+            int serverPort = 8080;
+            String header_port = request.getHeader("X-Local-Port");
+            if (StringUtils.isNotEmpty(header_port)){
+                serverPort = Integer.valueOf(header_port);
+            }
+
+            logger.info("获取应用清单,当前服务请求下载地址ip:{},端口:{}",serverName,serverPort);
+
+            Map<String,Object> map = new HashMap<>();
+            String authorization = request.getHeader("Authorization");
+            authorization = StringUtils.replace(authorization,"Bearer ","");
+            String deployTimeStamp = request.getParameter("deployTimeStamp");
+            //根据token获取主机信息
+            TokenCache tokenCache = null;
+            String result = redisTemplateUtil.get(RedisPrefix.CACHE_TOKEN_TIMES + ":" + authorization);
+            if (org.apache.commons.lang3.StringUtils.isNotBlank(result)){
+                tokenCache =  JSON.parseObject(result, TokenCache.class);
+            }
+            if(tokenCache == null){
+                logger.error(authorization + "令牌不合法!");
+                throw new RuntimeException(authorization + "令牌不合法!");
+            }
+
+            //获取秘钥
+            String clientId = tokenCache.getClientId();
+
+            HostInfo hostInfo = hostInfoService.getById(clientId);
+
+            if(!hostInfo.getCoreTimeStamp().equals(UTCTimeUtils.getUtcStr2Long(deployTimeStamp))){
+                logger.error("部署中心时间戳和前置机时间戳不一致,部署中心时间:{},前置机请求时间{}",hostInfo.getCoreTimeStamp(),UTCTimeUtils.getUtcStr2Long(deployTimeStamp));
+                response.setStatus(304);
+                return "Not Modified";
+            }
+
+            long coreTime = UTCTimeUtils.getUtcStr2Long(UTCTimeUtils.getUTCTimeStr());
+
+            hostInfo.setCoreTimeStamp(coreTime);
+            hostInfoService.updateById(hostInfo);
+
+            List<AppInfoExtend> infoList = extendService.getByHostInfoId(clientId);
+            List<AppInfoVo> avs = new ArrayList<>();
+            AppInfoVo av = null;
+            /**
+             * 白代理解析的是json,需要把json字符串转换为 json对象
+             */
+            for (AppInfoExtend extend : infoList) {
+                av = new AppInfoVo();
+                av.setAppId(extend.getAppId());
+                av.setAppName(extend.getAppName());
+                av.setAppType(extend.getAppType());
+                av.setCertificateAuthority(extend.getCertificateAuthority());
+                av.setCode(extend.getCode());
+                av.setHash(extend.getHash());
+                av.setHashAlgorithm(extend.getHashAlgorithm());
+                av.setLogPath(extend.getLogPath());
+                String osArchitectures = extend.getOsArchitectures();
+                List<String> oss = JSONArray.parseArray(osArchitectures, String.class);
+                av.setOsArchitectures(oss);
+                List<String> ops = JSONArray.parseArray(extend.getOsPlatforms(), String.class);
+                av.setOsPlatforms(ops);
+                av.setProductName(extend.getProductName());
+                Object starts = JSON.parse(extend.getStart());
+                av.setStart(starts);
+                Object stops = JSON.parse(extend.getStop());
+                av.setStop(stops);
+                av.setVersion(extend.getVersion());
+                av.setRunAtStartup(extend.isRunAtStartup());
+                av.setGuardEnabled(extend.isRunAtStartup());
+                if ("http://localhost:33385".equals(extend.getDownloadUrl()) || CommonUtils.isURL(extend.getDownloadUrl())){
+                    av.setDownloadUrl(extend.getDownloadUrl());
+                }else {
+                    av.setDownloadUrl("http://"+serverName+":"+serverPort+extend.getDownloadUrl());
+                }
+                UpgradeTask task = upgradeTaskService.getTask(extend.getAppId(), extend.getHostInfoId());
+                if(task != null){
+                    av.setVersion(task.getTargeVersion());
+                }
+
+                Object parse = JSON.parse(extend.getLivenessProbe());
+                av.setLivenessProbe(parse);
+                av.setWaitForReady(extend.getWaitForReady());
+                // 设置补丁相关数据
+                String hotfixesStr = extend.getHotfixes();
+                if (StringUtils.isEmpty(hotfixesStr)) {
+                    hotfixesStr = "[]";
+                }
+                List<HotfixVo> hotfixVos = JSONObject.parseArray(hotfixesStr, HotfixVo.class);
+                if (!hotfixVos.isEmpty()) {
+                    //处理hotfixVos 下载路径
+                    for (HotfixVo hotfixVo : hotfixVos) {
+                        hotfixVo.setDownloadUrl("http://" + serverName + ":" + serverPort + hotfixVo.getDownloadUrl());
+                    }
+                    av.setHotfixes(hotfixVos);
+                }
+                avs.add(av);
+
+            }
+            map.put("timeStamp",UTCTimeUtils.getLong2UtcStr(coreTime));
+            map.put("apps",avs);
+
+            logger.info("前置机:{},本次获取应用清单返回数据:{}",clientId,JSON.toJSONString(map));
+            return map;
+        }catch (Exception e){
+            logger.error("获取应用清单出现异常",e);
+        }finally {
+            logger.info("获取应用清单逻辑结束!");
+        }
+
+        return null;
+    }
+
+    //推送应用清单:验证请求参数
+    private boolean checkParam(PutAppInfoVo putAppInfoVo){
+        List<AppInfoVo> apps = putAppInfoVo.getApps();
+        if (!(null != apps && apps.size() >0)){
+            return false;
+        }
+        for (int i = 0; i < apps.size(); i++) {
+            AppInfoVo infoVo = apps.get(i);
+            if (StringUtils.isEmpty(infoVo.getProductName())){
+                return false;
+            }
+            if (StringUtils.isEmpty(infoVo.getCode())){
+                return false;
+            }
+            if (StringUtils.isEmpty(infoVo.getAppId())){
+                return false;
+            }
+            if (StringUtils.isEmpty(infoVo.getAppType())){
+                return false;
+            }
+            if (StringUtils.isEmpty(infoVo.getVersion())){
+                return false;
+            }
+            if (StringUtils.isEmpty(infoVo.getAppName())){
+                return false;
+            }
+            if (StringUtils.isEmpty(infoVo.getDownloadUrl())){
+                return false;
+            }
+            if (StringUtils.isEmpty(infoVo.getCertificateAuthority())){
+                return false;
+            }
+            if (StringUtils.isEmpty(infoVo.getHashAlgorithm())){
+                return false;
+            }
+            if (StringUtils.isEmpty(infoVo.getHash())){
+                return false;
+            }
+            if (null == infoVo.getOsArchitectures()){
+                return false;
+            }
+            if (null == infoVo.getOsPlatforms()){
+                return false;
+            }
+            if (null == infoVo.getStart()){
+                return false;
+            }
+            if (null == infoVo.getStop()){
+                return false;
+            }
+            if (null == infoVo.getLivenessProbe()){
+                return false;
+            }
+        }
+        return true;
+    }
+}

+ 11 - 0
soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/dao/SysConfDao.java

@@ -0,0 +1,11 @@
+package com.xunmei.deploy.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.xunmei.deploy.domain.SysConf;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface SysConfDao extends BaseMapper<SysConf> {
+
+    SysConf getByCode(String code);
+}

+ 29 - 0
soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/domain/SysConf.java

@@ -0,0 +1,29 @@
+package com.xunmei.deploy.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("t_sys_conf")
+@ApiModel(value="SysConf对象", description="")
+public class SysConf implements Serializable {
+
+    private static final long serialVersionUID=1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    private String code;
+
+    private String value;
+
+    private Date createTime;
+}

+ 17 - 0
soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/job/HeartBeatJob.java

@@ -0,0 +1,17 @@
+package com.xunmei.deploy.job;
+
+import com.xunmei.deploy.service.BeringService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Component
+public class HeartBeatJob {
+    @Autowired
+    private BeringService beringService;
+
+    @Scheduled(fixedDelay = 1000 * 30)
+    public void scheduleUpgradeTask() {
+        beringService.scheduleHearts();
+    }
+}

+ 8 - 0
soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/service/AppRunInfoExtendService.java

@@ -0,0 +1,8 @@
+package com.xunmei.deploy.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.xunmei.deploy.domain.AppRunInfoExtend;
+
+public interface AppRunInfoExtendService extends IService<AppRunInfoExtend> {
+
+}

+ 8 - 0
soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/service/AsyncService.java

@@ -0,0 +1,8 @@
+package com.xunmei.deploy.service;
+
+import com.xunmei.deploy.vo.heart.AppRunningInfo;
+import java.util.*;
+
+public interface AsyncService {
+    void asyncSaveOrUpdateBatch(List<AppRunningInfo> appRunningInfos, String hostId);
+}

+ 24 - 0
soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/service/BeringService.java

@@ -0,0 +1,24 @@
+package com.xunmei.deploy.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.xunmei.deploy.domain.HostInfo;
+import com.xunmei.deploy.vo.TokenVo;
+import com.xunmei.deploy.vo.heart.HeartBeat;
+import com.xunmei.deploy.vo.heart.HeartResponse;
+
+public interface BeringService  extends IService<HostInfo> {
+    /**
+     * 获取令牌
+     */
+    HostInfo generateAccessToken(TokenVo tokenVo)throws Exception;
+
+    /**
+     * 上送心跳
+     */
+    HeartResponse heartBeat(String authorization, HeartBeat heartBeat)throws Exception;
+
+    /**
+     * 定时任务消费心跳
+     */
+    void scheduleHearts();
+}

+ 4 - 1
soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/service/FrontTaskService.java

@@ -7,7 +7,10 @@ public interface FrontTaskService  extends IService<FrontTask> {
 
     /**
      * 创建重启应用任务
-     * @param hostId
      */
     void createRestartAppTask(String hostId) throws Exception;
+    /**
+     * 验证是否需要生成清单任务,如果需要则在数据库添加记录
+     **/
+    void validateAppInfoList(String hostId,String version)throws Exception;
 }

+ 13 - 0
soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/service/impl/AppRunInfoServiceExtendImpl.java

@@ -0,0 +1,13 @@
+package com.xunmei.deploy.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.xunmei.deploy.dao.AppRunInfoExtendDao;
+import com.xunmei.deploy.domain.AppRunInfoExtend;
+import com.xunmei.deploy.service.AppRunInfoExtendService;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AppRunInfoServiceExtendImpl extends ServiceImpl<AppRunInfoExtendDao, AppRunInfoExtend> implements AppRunInfoExtendService {
+
+
+}

+ 265 - 0
soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/service/impl/AsyncServiceImpl.java

@@ -0,0 +1,265 @@
+package com.xunmei.deploy.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.xunmei.common.core.utils.StringUtils;
+import com.xunmei.deploy.dao.AppRunInfoExtendDao;
+import com.xunmei.deploy.domain.AppRunInfo;
+import com.xunmei.deploy.domain.AppRunInfoExtend;
+import com.xunmei.deploy.domain.UpgradeTask;
+import com.xunmei.deploy.enums.DeployStages;
+import com.xunmei.deploy.service.AppRunInfoExtendService;
+import com.xunmei.deploy.service.AppRunInfoService;
+import com.xunmei.deploy.service.AsyncService;
+import com.xunmei.deploy.service.UpgradeTaskService;
+import com.xunmei.deploy.util.CommonConstraint;
+import com.xunmei.deploy.util.CommonUtils;
+import com.xunmei.deploy.util.RedisPrefix;
+import com.xunmei.deploy.util.RedisTemplateUtil;
+import com.xunmei.deploy.vo.heart.AppRunningInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import javax.annotation.Resource;
+import java.util.*;
+
+@Service
+public class AsyncServiceImpl implements AsyncService {
+
+    @Autowired
+    private AppRunInfoService appRunInfoService;
+    @Autowired
+    private AppRunInfoExtendService appRunInfoExtendService;
+    @Autowired
+    private UpgradeTaskService upgradeTaskService;
+
+    @Autowired
+    private AppRunInfoExtendDao appRunInfoExtendDao;
+
+    @Resource
+    private RedisTemplateUtil redisTemplateUtil;
+
+    @Override
+    @Async
+    public void asyncSaveOrUpdateBatch(List<AppRunningInfo> appRunningInfos, String hostId){
+        //缓存中查数据
+        List<AppRunInfo> runInfoList = null;
+
+        String result = redisTemplateUtil.get(RedisPrefix.CACHE_APP_RUN_INFO + ":" + hostId);
+        if (StringUtils.isNotBlank(result)){
+            runInfoList = JSON.parseArray(result,AppRunInfo.class);
+        }
+        if (null == runInfoList || runInfoList.size() == 0){
+            QueryWrapper<AppRunInfo> wrapper = new QueryWrapper<>();
+            wrapper.eq("host_id",hostId);
+            runInfoList = appRunInfoService.list(wrapper);
+            redisTemplateUtil.set(RedisPrefix.CACHE_APP_RUN_INFO + ":" + hostId,JSON.toJSONString(runInfoList),2 * 60 * 60);
+        }
+
+
+        Map<String,List<AppRunningInfo>> maps = new HashMap<>();
+        for (AppRunningInfo app : appRunningInfos) {
+            List<AppRunningInfo> infos = maps.get(app.getCode());
+            if(infos == null){
+                infos = new ArrayList<>();
+            }
+            infos.add(app);
+            maps.put(app.getCode(),infos);
+        }
+
+        ArrayList<AppRunningInfo> newList = new ArrayList<>();
+
+        //第二步所有同名app进行排序
+        Iterator<Map.Entry<String,List<AppRunningInfo>>> entries = maps.entrySet().iterator();
+        while (entries.hasNext()) {
+            Map.Entry<String, List<AppRunningInfo>> infos = entries.next();
+            List<AppRunningInfo> appInfos = infos.getValue();
+            /**
+             * 当前app处理
+             */
+            if (appInfos.size() > 0) {
+                if(appInfos.size() > 1){
+                    //给list做排序
+                    Collections.sort(appInfos, new Comparator<AppRunningInfo>() {
+                        @Override
+                        public int compare(AppRunningInfo o1, AppRunningInfo o2) {
+                            String one = o1.getVersion();
+                            String two = o2.getVersion();
+
+                            String version = CommonUtils.compareToVersion(one, two);
+                            if (version.equals(o1.getVersion())) {
+                                return -1;
+                            } else {
+                                return 1;
+                            }
+                        }
+                    });
+                }
+                //排序完成,保存数据
+                //第一条数据是版本号最大的一条
+                AppRunningInfo runningInfo = appInfos.get(0);
+                newList.add(runningInfo);
+            }
+        }
+
+        ArrayList<AppRunInfo> infos = new ArrayList<>();
+        for (AppRunningInfo appRunningInfo:newList) {
+            //修改任务执行步骤
+            this.updateTaskStage(appRunningInfo,hostId);
+            AppRunInfo info = new AppRunInfo();
+            info.setAppId(appRunningInfo.getAppId());
+            info.setAppName(appRunningInfo.getAppName());
+            info.setAppType(appRunningInfo.getAppType());
+            info.setCode(appRunningInfo.getCode());
+            info.setDescription(appRunningInfo.getDescription());
+            info.setProcessId(appRunningInfo.getProcessId());
+            info.setRunning(appRunningInfo.isRunning());
+            info.setStage(appRunningInfo.getStage());
+            info.setStartTime(null);
+            info.setStatus(appRunningInfo.getStatus());
+            info.setVersion(appRunningInfo.getVersion());
+            info.setHostId(hostId);
+            info.setHotfixes(appRunningInfo.getHotfixes());
+            infos.add(info);
+        }
+
+        if (runInfoList != null && runInfoList.size() > 0){
+            if (null != infos && infos.size() > 0){
+                Iterator<AppRunInfo> infoIterator = infos.iterator();
+                while (infoIterator.hasNext()){
+                    AppRunInfo next = infoIterator.next();
+                    for (AppRunInfo runInfo : runInfoList) {
+                        if (next.getCode().equals(runInfo.getCode())){
+                            //工具类型:版本号相同不需要修改
+                            boolean toolAndSameVersion = "Tool".equals(runInfo.getAppType()) && next.getVersion().equals(runInfo.getVersion());
+                            //非工具类型:版本号相同且状态运行不需要修改
+                            boolean sameVersionAndRunning = next.getVersion().equals(runInfo.getVersion()) && next.isRunning() && runInfo.isRunning();
+                            if (toolAndSameVersion || sameVersionAndRunning){
+                                infoIterator.remove();
+                            }else {
+                                next.setId(runInfo.getId());
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        //数据库有数据更新时更新缓存数据
+        if (null != infos && infos.size() > 0){
+            appRunInfoService.saveOrUpdateBatch(infos);
+
+            QueryWrapper<AppRunInfo> wrapper = new QueryWrapper<>();
+            wrapper.eq("host_id",hostId);
+            List<AppRunInfo> list = appRunInfoService.list(wrapper);
+            //放入缓存中
+            redisTemplateUtil.del(RedisPrefix.CACHE_APP_RUN_INFO + ":" + hostId);
+            redisTemplateUtil.set(RedisPrefix.CACHE_APP_RUN_INFO + ":" + hostId,JSON.toJSONString(list),2 * 60 * 60);
+
+        }
+
+        this.saveAppRunInfoExtend(newList,hostId);
+    }
+
+
+
+    /**
+     * 保存app_run_info_extend表数据
+     */
+    private void saveAppRunInfoExtend(List<AppRunningInfo> appRunningInfos, String hostId){
+        //从缓存中取数据
+        List<AppRunInfoExtend> extendList = null;
+        String result = redisTemplateUtil.get(RedisPrefix.CACHE_APP_RUN_INFO_EXTEND + ":" + hostId);
+        if (StringUtils.isNotBlank(result)){
+            extendList = JSON.parseArray(result,AppRunInfoExtend.class);
+        }
+        if (null == extendList || extendList.size() == 0){
+            QueryWrapper<AppRunInfoExtend> wrapper = new QueryWrapper<>();
+            wrapper.eq("host_id",hostId);
+            extendList = appRunInfoExtendDao.selectList(wrapper);
+            redisTemplateUtil.set(RedisPrefix.CACHE_APP_RUN_INFO_EXTEND + ":" + hostId,JSON.toJSONString(extendList),2 * 60 * 60);
+        }
+
+        //是否更新缓存
+        List<AppRunInfoExtend> dataList = new ArrayList<>();
+
+        OUT:for (AppRunningInfo runningInfo : appRunningInfos) {
+            AppRunInfoExtend extend = null;
+            //判断缓存中是否有相同的数据
+            for (AppRunInfoExtend infoExtend : extendList){
+                if (runningInfo.getCode().equals(infoExtend.getCode()) && hostId.equals(infoExtend.getHostId())){
+                    //工具类型:版本号query状态为ready相同不需要修改
+                    boolean toolFlag = "Tool".equals(infoExtend.getAppType()) && runningInfo.getVersion().equals(infoExtend.getVersion())
+                            && DeployStages.READY.getStatus().equals(runningInfo.getStage()) && DeployStages.READY.getStatus().equals(infoExtend.getStage());
+                    //非工具类型:版本号相同、状态运行、安装步骤相同不需要修改
+                    boolean sameStatusFlag = runningInfo.getStage().equals(infoExtend.getStage()) && runningInfo.getStatus().equals(infoExtend.getStatus())
+                            && runningInfo.getVersion().equals(infoExtend.getVersion()) && runningInfo.isRunning() && infoExtend.isRunning();
+                    //如果状态和版本相同且状态为运行则不做修改
+                    if (toolFlag || sameStatusFlag){
+                        continue OUT;
+                    }else {
+                        extend = infoExtend;
+                        break;
+                    }
+                }
+            }
+
+            if(null != extend){
+                extend.setStage(runningInfo.getStage());
+                extend.setStatus(runningInfo.getStatus());
+                extend.setVersion(runningInfo.getVersion());
+            }else {
+                extend = new AppRunInfoExtend();
+                extend.setAppId(runningInfo.getAppId());
+                extend.setAppName(runningInfo.getAppName());
+                extend.setAppType(runningInfo.getAppType());
+                extend.setCode(runningInfo.getCode());
+                extend.setDescription(runningInfo.getDescription());
+                extend.setProcessId(runningInfo.getProcessId());
+                extend.setRunning(runningInfo.isRunning());
+                extend.setStage(runningInfo.getStage());
+                extend.setStartTime(null);
+                extend.setStatus(runningInfo.getStatus());
+                extend.setVersion(runningInfo.getVersion());
+                extend.setHostId(hostId);
+                extend.setHotfixes(runningInfo.getHotfixes());
+            }
+            dataList.add(extend);
+        }
+
+        if (null != dataList && dataList.size() > 0){
+            appRunInfoExtendService.saveOrUpdateBatch(dataList);
+
+            QueryWrapper<AppRunInfoExtend> wrapper = new QueryWrapper<>();
+            wrapper.eq("host_id",hostId);
+            List<AppRunInfoExtend> list = appRunInfoExtendDao.selectList(wrapper);
+            //放入缓存中
+            redisTemplateUtil.del(RedisPrefix.CACHE_APP_RUN_INFO_EXTEND + ":" + hostId);
+            redisTemplateUtil.set(RedisPrefix.CACHE_APP_RUN_INFO_EXTEND + ":" + hostId,JSON.toJSONString(list),2 * 60 * 60);
+        }
+    }
+
+    /**
+     * 修改任务状态
+     *
+     */
+    private void updateTaskStage(AppRunningInfo app, String hostId){
+        QueryWrapper<UpgradeTask> qw = new QueryWrapper<>();
+        qw.eq("app_code",app.getCode());
+        qw.eq("host_id",hostId);
+        qw.eq("targe_version",app.getVersion());
+        UpgradeTask task = upgradeTaskService.getOne(qw);
+        if (null == task){
+            return;
+        }
+
+        String stage = app.getStage();
+        String status = app.getStatus();
+
+        task.setDeployDescription(stage + CommonConstraint.UpgradeDescriptionConstant.SPLIT + status);
+        task.setDeployStages(stage);
+        task.setDeployStatus(status);
+        upgradeTaskService.updateById(task);
+    }
+}

+ 452 - 0
soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/service/impl/BeringServiceImpl.java

@@ -0,0 +1,452 @@
+package com.xunmei.deploy.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.github.zafarkhaja.semver.Version;
+import com.xunmei.deploy.dao.*;
+import com.xunmei.deploy.domain.FrontTask;
+import com.xunmei.deploy.domain.HostInfo;
+import com.xunmei.deploy.domain.HostZipInfo;
+import com.xunmei.deploy.domain.SysConf;
+import com.xunmei.deploy.service.AsyncService;
+import com.xunmei.deploy.service.BeringService;
+import com.xunmei.deploy.service.FrontTaskService;
+import com.xunmei.deploy.util.RedisPrefix;
+import com.xunmei.deploy.util.RedisTemplateUtil;
+import com.xunmei.deploy.util.UTCTimeUtils;
+import com.xunmei.deploy.vo.MachineInfo;
+import com.xunmei.deploy.vo.OrgVo;
+import com.xunmei.deploy.vo.TokenCache;
+import com.xunmei.deploy.vo.TokenVo;
+import com.xunmei.deploy.vo.heart.AppRunningInfo;
+import com.xunmei.deploy.vo.heart.HeartBeat;
+import com.xunmei.deploy.vo.heart.HeartResponse;
+import com.xunmei.deploy.vo.heart.HeartTimeVo;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.DigestUtils;
+import javax.annotation.Resource;
+import java.rmi.ServerException;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+@Service
+public class BeringServiceImpl  extends ServiceImpl<HostInfoDao, HostInfo> implements BeringService {
+    private Logger logger = LoggerFactory.getLogger(getClass());
+    @Value("${apps.version}")
+    private String defaultVersion;
+    private static final long MAX_HEART_TIME = 5 * 60 * 1000;
+    @Resource
+    private RedisTemplateUtil redisTemplateUtil;
+
+    @Autowired
+    private SysConfDao sysConfDao;
+    @Autowired
+    private FrontTaskDao frontTaskDao;
+
+    @Autowired
+    private HostInfoDao hostInfoDao;
+    @Autowired
+    private HostZipInfoDao hostZipInfoDao;
+    @Autowired
+    private UpgradeBatchInfoDao upgradeBatchInfoDao;
+    @Autowired
+    private OrgVoDao orgVoDao;
+
+    @Autowired
+    private FrontTaskService frontTaskService;
+    @Autowired
+    private AsyncService asyncService;
+
+    /**
+     * 获取令牌
+     */
+    @Override
+    public HostInfo generateAccessToken(TokenVo tokenVo) throws Exception {
+
+        //1.先判断请求参数是否合法
+        if(tokenVo == null){
+            logger.error("主机获取令牌失败:参数不能为空!");
+            throw new ServerException("主机获取令牌失败:参数不能为空!");
+        }
+
+        if(StringUtils.isEmpty(tokenVo.getClient_id())){
+            logger.error("主机获取令牌失败:主机ID不能为空!");
+            throw new ServerException("主机获取令牌失败:主机ID不能为空!");
+        }
+
+        MachineInfo machineInfo = tokenVo.getMachineInfo();
+        if(machineInfo == null){
+            logger.error("主机获取令牌失败:计算机信息不能为空!");
+            throw new ServerException("主机获取令牌失败:计算机信息不能为空!");
+        }
+
+        if(StringUtils.isEmpty(tokenVo.getGrant_type())){
+            logger.error("主机获取令牌失败:认证类型不能为空!");
+            throw new ServerException("主机获取令牌失败:认证类型不能为空!");
+        }
+        if(!tokenVo.getGrant_type().equals("client_credentials")){
+            logger.error("主机获取令牌失败:请求参数认证类型错误!");
+            throw new ServerException("主机获取令牌失败:请求参数认证类型错误!");
+        }
+
+        //验证秘钥是否合法
+        List<String> macs = machineInfo.getMacs();
+        if (null == macs || macs.size() <= 0){
+            logger.error("主机获取令牌失败:计算机macs信息不能为空!");
+            throw new ServerException("主机获取令牌失败:计算机macs信息不能为空!");
+        }
+
+        Collections.sort(macs);
+        String macss = StringUtils.join(macs.toArray(), "");
+
+        String str = macss + "zmoon";
+
+        //md5加密--->按照生成规则生成后的秘钥
+        String code = DigestUtils.md5DigestAsHex(str.getBytes());
+        if(!code.equals(tokenVo.getClient_secret())){
+            logger.error("主机获取令牌失败:主机秘钥认证失败!");
+            throw new ServerException("主机获取令牌失败:主机秘钥认证失败!");
+        }
+
+        //2.验证该秘钥是否在数据库中已经存在
+        TokenCache tokenCache = null;
+        String result = redisTemplateUtil.get(RedisPrefix.CACHE_TOKENS + ":" + tokenVo.getClient_secret());
+        if (StringUtils.isNotBlank(result)){
+            tokenCache =  JSON.parseObject(result, TokenCache.class);
+        }
+
+
+        HostInfo hostInfo = baseMapper.selectById(tokenVo.getClient_secret());
+        //秘钥验证成功且数据库无该秘钥,判断为数据库丢失数据,需要重新注册
+        if (hostInfo == null){
+            //判断是否允许重新注册
+            SysConf sysConf = sysConfDao.getByCode("register_again");
+            if ("1".equals(sysConf.getValue())){
+                logger.info("数据库注册数据丢失,程序允许主机重新注册!\n"+ JSON.toJSONString(machineInfo));
+                hostInfo = new HostInfo();
+                hostInfo.setId(code);
+                hostInfo.setHostName(machineInfo.getMachineName());
+                hostInfo.setHostIp(StringUtils.join(machineInfo.getIpAddresses().toArray(),","));
+                hostInfo.setHostSystem(machineInfo.getOsPlatform());
+                hostInfo.setHostFrame(machineInfo.getOsArchitecture());
+                hostInfo.setHostMac(StringUtils.join(macs.toArray(),","));
+                hostInfo.setHostOrg(null);
+                hostInfo.setHostStatus(1);
+                hostInfo.setIsPush(0);
+                baseMapper.insert(hostInfo);
+
+                //删除frontTask中数据
+                QueryWrapper<FrontTask> wrapper = new QueryWrapper<>();
+                wrapper.eq("host_id",code);
+                frontTaskDao.delete(wrapper);
+
+                //添加缓存信息
+                TokenCache tc = new TokenCache();
+                tc.setClientId(hostInfo.getId());
+                tc.setClientSecret(hostInfo.getId());
+                redisTemplateUtil.set(RedisPrefix.CACHE_TOKENS + ":" +hostInfo.getId(),tc,2 * 60 * 60);
+            }else {
+                logger.error("数据库注册数据丢失,程序不允许主机重新注册!\n"+ JSON.toJSONString(machineInfo));
+            }
+        }
+
+        long time = System.currentTimeMillis();
+        //返回有效的token
+        //判断缓存中令牌是否存在
+        if(null != tokenCache && StringUtils.isNotEmpty(tokenCache.getAccessToken())){
+            //判断令牌的有效期是否过期
+            //验证该token是否在系统中存在
+            Long tokenCreateTime = tokenCache.getTokenDate();
+            int expiresIn = tokenCache.getExpiresIn();
+            //与当前时间判断 是否超过9授权时间,如果超过,则重新生成token
+            long timeDiff = (time - tokenCreateTime) / 1000;
+            //令牌时间过期
+            if(timeDiff > expiresIn){
+                //先删除之前tokentimes的缓存
+                redisTemplateUtil.del(RedisPrefix.CACHE_TOKEN_TIMES + ":" +tokenCache.getAccessToken());
+                //重新生成token 并更新数据库与缓存
+                String key = tokenCache.getClientSecret();
+                key = key + time;
+                String newMd5 = DigestUtils.md5DigestAsHex(key.getBytes());
+                tokenCache.setAccessToken(newMd5);
+                tokenCache.setExpiresIn(7200);
+                tokenCache.setTokenDate(time);
+                redisTemplateUtil.set(RedisPrefix.CACHE_TOKENS + ":" + tokenCache.getClientId(),tokenCache,2 * 60 * 60);
+                redisTemplateUtil.set(RedisPrefix.CACHE_TOKEN_TIMES + ":" + tokenCache.getAccessToken(),tokenCache,2 * 60 * 60);
+
+                hostInfo.setTokenCreateTime(time);
+                hostInfo.setExpiresIn(7200);
+                hostInfo.setAccessToken(newMd5);
+                hostInfo.setHostName(tokenVo.getMachineInfo().getMachineName());
+                logger.info("token不为空,时效超时,请求里面的ip 为:"+tokenVo.getMachineInfo().getIpAddresses().toString());
+                hostInfo.setHostIp(tokenVo.getMachineInfo().getIpAddresses().toString());
+                baseMapper.updateById(hostInfo);
+            }
+        }else{
+            //生成新的令牌 更新数据库与缓存
+            String t = tokenVo.getClient_secret()+time;
+            String md5Token = DigestUtils.md5DigestAsHex(t.getBytes());
+            hostInfo.setAccessToken(md5Token);
+            hostInfo.setExpiresIn(7200);
+            hostInfo.setScope("* identity");
+            hostInfo.setTokenType("Bearer");
+            hostInfo.setTokenCreateTime(System.currentTimeMillis());
+            logger.info("token为空,请求里面的ip 为:"+tokenVo.getMachineInfo().getIpAddresses().toString());
+            hostInfo.setHostName(tokenVo.getMachineInfo().getMachineName());
+            hostInfo.setHostIp(tokenVo.getMachineInfo().getIpAddresses().toString());
+            baseMapper.updateById(hostInfo);
+            //tokenTimes 缓存新增一条
+            TokenCache tc = new TokenCache();
+            tc.setClientId(hostInfo.getId());
+            tc.setClientSecret(hostInfo.getId());
+            tc.setAccessToken(hostInfo.getAccessToken());
+            tc.setExpiresIn(hostInfo.getExpiresIn());
+            tc.setTokenDate(hostInfo.getTokenCreateTime());
+
+            redisTemplateUtil.set(RedisPrefix.CACHE_TOKENS + ":" + tokenVo.getClient_id(),tc,2 * 60 * 60);
+            redisTemplateUtil.set(RedisPrefix.CACHE_TOKEN_TIMES + ":" + md5Token,tc,2 * 60 * 60);
+        }
+        return hostInfo;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public HeartResponse heartBeat(String authorization, HeartBeat heartBeat) throws Exception {
+
+        logger.info("心跳携带令牌:{}", authorization);
+        HeartResponse response = new HeartResponse();
+        //参数严重
+        String time = heartBeat.getTime();
+        if(StringUtils.isBlank(time)){
+            logger.error("白令海节点计算机的UTC时间不能为空!");
+            throw new RuntimeException("白令海节点计算机的UTC时间不能为空!");
+        }
+
+        String version = heartBeat.getVersion();
+        if(StringUtils.isBlank(version)){
+            logger.error("白令海自身版本号不能为空!");
+            throw new RuntimeException("白令海自身版本号不能为空!");
+        }
+
+        Boolean allowPrerelsease = heartBeat.getAllowPrerelease();
+        if(allowPrerelsease == null){
+            logger.error("白令海允许升级到预发布版本不能为空!");
+            throw new RuntimeException("白令海允许升级到预发布版本不能为空!");
+
+        }
+
+        String description = heartBeat.getDescription();
+        if(StringUtils.isBlank(description)){
+            logger.error("白令海节点描述不能为空!");
+            throw new RuntimeException("白令海节点描述不能为空!");
+        }
+
+        //根据token获取主机信息
+        TokenCache tokenCache = null;
+        String result = redisTemplateUtil.get(RedisPrefix.CACHE_TOKEN_TIMES + ":" + authorization);
+        if (StringUtils.isNotBlank(result)){
+            tokenCache =  JSON.parseObject(result, TokenCache.class);
+        }
+
+        if(tokenCache == null){
+            logger.error(authorization + "令牌不合法!");
+            throw new RuntimeException(authorization + "令牌不合法!");
+        }
+
+        //获取秘钥
+        String clientId = tokenCache.getClientId();
+
+        HostInfo hostInfo = hostInfoDao.selectById(clientId);
+        if (null == hostInfo){
+            logger.error("主机{}在数据库不存在", clientId);
+        }
+        logger.info("主机{},心跳获取参数:{}", hostInfo.getId(), JSON.toJSONString(heartBeat));
+        //查询hostZipInfo信息是否存在,如果不存在,按照默认新建
+        QueryWrapper<HostZipInfo> qw = new QueryWrapper<>();
+        qw.eq("host_id",hostInfo.getId());
+
+        HostZipInfo hostZipInfo = hostZipInfoDao.selectOne(qw);
+        if(hostZipInfo == null){
+            String orgId = upgradeBatchInfoDao.getOrgId(hostInfo.getId());
+
+            if(StringUtils.isNotEmpty(orgId) || "YCAF".equals(hostInfo.getHostName())){
+                OrgVo org = orgVoDao.getByOrgId(orgId);
+                if(org == null && !"YCAF".equals(hostInfo.getHostName())){
+                    throw new RuntimeException("心跳异常:组织机构不存在,该机器组织机构id"+orgId);
+                }
+                hostZipInfo = new HostZipInfo();
+                hostZipInfo.setStatus(1);
+                hostZipInfo.setZipVersion(defaultVersion);
+                hostZipInfo.setTargetVersion(defaultVersion);
+                hostZipInfo.setHostId(hostInfo.getId());
+
+                hostZipInfo.setHostIp(hostInfo.getHostIp());
+                if (org != null){
+                    hostZipInfo.setOrgId(org.getOrgId()+"");
+                    hostZipInfo.setOrgName(org.getOrgName());
+                }else {
+                    hostZipInfo.setOrgId(null);
+                    hostZipInfo.setOrgName(null);
+                }
+                hostZipInfo.setHostName(hostInfo.getHostName());
+
+
+                hostZipInfoDao.insert(hostZipInfo);
+            }
+        }else{
+            hostZipInfo.setHostName(hostInfo.getHostName());
+            hostZipInfo.setHostIp(hostInfo.getHostIp());
+            hostZipInfo.setHostId(hostInfo.getId());
+            String orgId = upgradeBatchInfoDao.getOrgId(hostInfo.getId());
+            if(StringUtils.isNotEmpty(orgId)){
+                OrgVo org = orgVoDao.getByOrgId(orgId);
+                if(org == null){
+                    throw new RuntimeException("心跳异常:组织机构不存在,该机器组织机构id"+orgId);
+                }
+                hostZipInfo.setOrgId(orgId);
+                hostZipInfo.setOrgName(org.getOrgName());
+            }
+            hostZipInfoDao.updateById(hostZipInfo);
+        }
+
+        //获取到心跳信息,获取缓存中是否有心跳信息。
+        HeartTimeVo heartTimeVo = null;
+        String heartResult = redisTemplateUtil.get(RedisPrefix.CACHE_HEARTS + ":" + clientId);
+        if (org.apache.commons.lang3.StringUtils.isNotBlank(heartResult)){
+            heartTimeVo =  JSON.parseObject(heartResult, HeartTimeVo.class);
+        }
+        if(heartTimeVo == null){
+            //缓存中不存在该token信息,缓存中添加心跳信息
+            heartTimeVo = new HeartTimeVo();
+
+            heartTimeVo.setClientId(tokenCache.getClientId());
+            heartTimeVo.setHeartTime(System.currentTimeMillis());
+            logger.info("第一次心跳向心跳缓存中添加信息:key:{},value:{}",clientId,JSON.toJSONString(heartTimeVo));
+            //缓存中添加
+            redisTemplateUtil.set(RedisPrefix.CACHE_HEARTS + ":" + clientId,heartTimeVo,2 * 60 * 60);
+        }else{
+            //存在该心跳,1.更新心态缓存
+            long now = System.currentTimeMillis();
+            heartTimeVo.setHeartTime(now);
+            if(hostInfo.getHostStatus() == 2){
+                //如果是离线状态,改为在线状态
+                hostInfo.setHostStatus(1);
+                hostInfoDao.updateById(hostInfo);
+                heartTimeVo.setHostStatus(1);
+            }
+            redisTemplateUtil.set(RedisPrefix.CACHE_HEARTS + ":" + clientId,heartTimeVo,2 * 60 * 60);
+        }
+
+        //调用是否生成任务清单判断,线程池开启线程
+        frontTaskService.validateAppInfoList(clientId,heartBeat.getVersion());
+        //获取缓存中数据,告知白代理需要来弄一波任务清单推送了哦
+        List<FrontTask> hasTask = null;
+        String frontTaskResult = redisTemplateUtil.get(RedisPrefix.CACHE_FRONT_TASKS + ":" + clientId);
+        if (StringUtils.isNotBlank(frontTaskResult)){
+            hasTask = JSON.parseArray(frontTaskResult,FrontTask.class);
+        }
+        response.setHasTask(false);
+        if(hasTask != null && hasTask.size() > 0 ){
+            logger.info("判断{}需要推送任务清单:{}",clientId,JSON.toJSONString(hasTask));
+            response.setHasTask(true);
+        }
+        response.setName("部署中心");
+        response.setVersion("1.0.0");
+        if(hostInfo.getCoreTimeStamp() == 0){
+            response.setDeployTimeStamp(heartBeat.getDeployTimeStamp());
+        }else{
+            response.setDeployTimeStamp(UTCTimeUtils.getLong2UtcStr(hostInfo.getCoreTimeStamp()));
+        }
+        response.setTime(UTCTimeUtils.getUTCTimeStr());
+        response.setAgentLastVersion(getNewlyAgentVersion(hostInfo));
+
+        List<AppRunningInfo> appRunningInfos = heartBeat.getApps();
+        if (null != appRunningInfos && appRunningInfos.size() > 0){
+            asyncService.asyncSaveOrUpdateBatch(appRunningInfos,hostInfo.getId());
+        }
+
+        logger.info("心跳返回结果:{}",JSON.toJSONString(response));
+        return response;
+    }
+
+
+    /**
+     * 定时任务消费心跳
+     */
+    public void scheduleHearts(){
+        String lockKey = RedisPrefix.HEART_LOCK_PREFIX;
+        boolean isLock = !redisTemplateUtil.hasKey(lockKey) && redisTemplateUtil.set(lockKey, lockKey, 10);
+        if(!isLock) {
+            return;
+        }
+
+        Set<String> hallKeys = redisTemplateUtil.getKeys(RedisPrefix.CACHE_HEARTS + ":*");
+
+        Iterator<String> iterator = hallKeys.iterator();
+
+        while (iterator.hasNext()){
+            String key = iterator.next();
+            String heartResult = redisTemplateUtil.get(key);
+            HeartTimeVo hv = null;
+            if (org.apache.commons.lang3.StringUtils.isNotBlank(heartResult)){
+                hv =  JSON.parseObject(heartResult, HeartTimeVo.class);
+            }
+            if (null == hv){
+                continue;
+            }
+
+            Long heartTime = hv.getHeartTime();
+
+            long newTime = System.currentTimeMillis();
+
+            long mid = newTime - heartTime;
+
+            //log.info("主机:{},30秒一次查看心跳时间,上一次心跳时间:{},当前时间:{},时间差:{}秒",hv.getClientId(),heartTime,newTime,mid/1000);
+
+            if(mid > MAX_HEART_TIME){
+                //时间已经超过最大心跳时间,我们认为主机已经离线了
+                String clientId = hv.getClientId();
+
+                HostInfo hostInfo = hostInfoDao.selectById(clientId);
+
+                hostInfo.setHostStatus(2);
+
+                hostInfoDao.updateById(hostInfo);
+
+                hv.setHostStatus(2);
+                //离线后删除心跳缓存
+                redisTemplateUtil.del(key);
+                logger.info("主机:{},上一次心跳时间:{},当前时间:{},时间差:{}秒,修改主机为离线",hv.getClientId(),heartTime,newTime,mid/1000);
+                //log.info("主机:{},分析结果:心跳超过5分钟,时间差:{}秒,修改主机状态为离线且删除心跳缓存",mid/1000,hv.getClientId());
+            }
+        }
+    }
+
+    /**
+     * 获取白令海的最新版本号
+     */
+    private String getNewlyAgentVersion(HostInfo hostInfo){
+        //需要升级的版本为空  直接返回agent自身版本
+        if (StringUtils.isEmpty(hostInfo.getUploadVersion())){
+            return hostInfo.getAgentVersion();
+        }
+
+        //对比agent自身版本 和 要升级的版本
+        Version agentVersion = Version.valueOf(hostInfo.getAgentVersion());
+        Version uploadVersion = Version.valueOf(hostInfo.getUploadVersion());
+        if (agentVersion.compareTo(uploadVersion) < 0){
+            return hostInfo.getUploadVersion();
+        }
+        return hostInfo.getAgentVersion();
+    }
+}

+ 101 - 4
soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/service/impl/FrontTaskServiceImpl.java

@@ -5,26 +5,32 @@ import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.xunmei.deploy.dao.AppInfoDao;
+import com.xunmei.deploy.dao.HostInfoDao;
 import com.xunmei.deploy.domain.AppInfo;
 import com.xunmei.deploy.domain.FrontTask;
+import com.xunmei.deploy.domain.HostInfo;
 import com.xunmei.deploy.enums.TaskType;
 import com.xunmei.deploy.dao.FrontTaskDao;
 import com.xunmei.deploy.service.FrontTaskService;
 import com.xunmei.deploy.util.RedisPrefix;
 import com.xunmei.deploy.util.RedisTemplateUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.UUID;
+import java.util.*;
 
 @Service
 public class FrontTaskServiceImpl  extends ServiceImpl<FrontTaskDao, FrontTask> implements FrontTaskService {
+    private Logger logger = LoggerFactory.getLogger(getClass());
+
     @Autowired
     private AppInfoDao appInfoDao;
+    @Autowired
+    private HostInfoDao hostInfoDao;
     @Resource
     private RedisTemplateUtil redisTemplateUtil;
 
@@ -68,4 +74,95 @@ public class FrontTaskServiceImpl  extends ServiceImpl<FrontTaskDao, FrontTask>
         newCache.addAll(saveList);
         redisTemplateUtil.set(RedisPrefix.CACHE_FRONT_TASKS + ":" + hostId,JSON.toJSONString(newCache),2 * 60 * 60);
     }
+
+
+    @Override
+    @Async("asyncServiceExecutor")
+    public void validateAppInfoList(String hostId,String version) throws Exception {
+
+        //获取主机信息
+        HostInfo hostInfo = hostInfoDao.selectById(hostId);
+        //判断两个字段是否为0
+        Long coreTimeStamp = hostInfo.getCoreTimeStamp();
+        Long frontTimeStamp = hostInfo.getFrontTimeStamp();
+        //获取缓存中的任务
+        List<FrontTask> taskCaches = null;
+        String result = redisTemplateUtil.get(RedisPrefix.CACHE_FRONT_TASKS + ":" + hostId);
+        if (org.apache.commons.lang3.StringUtils.isNotBlank(result)){
+            taskCaches = JSON.parseArray(result,FrontTask.class);
+        }
+        if (null == taskCaches){
+            taskCaches = new ArrayList<>();
+        }
+
+        if(coreTimeStamp == 0 && frontTimeStamp == 0){
+            logger.info("当前心跳为新注册的前置机:{}",hostId);
+            boolean cacheFlag = true;
+            if (null != taskCaches && taskCaches.size() > 0){
+                for (int i = 0; i < taskCaches.size(); i++) {
+                    if (taskCaches.get(i).getTaskType().equals(TaskType.UPLOAD_APP_LIST.getStatus())){
+                        cacheFlag = false;
+                        break;
+                    }
+                }
+            }
+            //缓存list为空或者缓存list中没有推送应用清单任务-->生成
+            if (null == taskCaches || cacheFlag){
+                QueryWrapper<FrontTask> qw = new QueryWrapper<>();
+                qw.eq("host_id",hostId);
+                qw.eq("task_type",TaskType.UPLOAD_APP_LIST.getStatus());
+                qw.ne("task_status",2);
+                FrontTask frontTask = baseMapper.selectOne(qw);
+                if(frontTask == null){
+                    logger.info("数据库中不存在该前置机的任务清单任务,开始生成任务:{}",hostId);
+                    //生成任务
+                    frontTask = new FrontTask();
+                    frontTask.setHostId(hostId);
+                    frontTask.setTaskStatus(0);
+                    frontTask.setTaskType(TaskType.UPLOAD_APP_LIST.getStatus());
+                    frontTask.setId(UUID.randomUUID().toString());
+                    baseMapper.insert(frontTask);
+                }
+                //状态为4时修改为0
+                if (frontTask.getTaskStatus() == 4){
+                    frontTask.setTaskStatus(0);
+                    baseMapper.updateById(frontTask);
+                }
+                //新装机器,需要进行逻辑处理呢。
+                taskCaches.add(frontTask);
+                redisTemplateUtil.set(RedisPrefix.CACHE_FRONT_TASKS + ":" + hostId,JSON.toJSONString(taskCaches),2 * 60 * 60);
+                logger.info("缓存中开始添加该前置机存在任务清单任务缓存:{}",hostId);
+            }else {
+                //删除缓存中推送应用清单的任务--状态为4
+                taskCaches = handleTaskCaches(hostId, taskCaches);
+                redisTemplateUtil.set(RedisPrefix.CACHE_FRONT_TASKS + ":" + hostId,JSON.toJSONString(taskCaches),2 * 60 * 60);
+            }
+        }else{
+            taskCaches = handleTaskCaches(hostId, taskCaches);
+            redisTemplateUtil.set(RedisPrefix.CACHE_FRONT_TASKS + ":" + hostId,JSON.toJSONString(taskCaches),2 * 60 * 60);
+        }
+
+        /**
+         * 添加逻辑:
+         * 判断主机对应白令海版本是否有变化,如果有则更新
+         */
+        if(hostInfo.getAgentVersion() == null || !version.equals(hostInfo.getAgentVersion())){
+            hostInfo.setAgentVersion(version);
+            hostInfoDao.updateById(hostInfo);
+        }
+    }
+
+    private List<FrontTask> handleTaskCaches(String hostId, List<FrontTask> taskCaches) {
+        if (null != taskCaches && taskCaches.size() > 0){
+            Iterator<FrontTask> iterator = taskCaches.iterator();
+            while (iterator.hasNext()){
+                FrontTask next = iterator.next();
+                if (next.getTaskType().equals(TaskType.UPLOAD_APP_LIST.getStatus()) && next.getTaskStatus() == 4){
+                    logger.info("删除该前置机存在缓存中upload_app_list的任务:{},任务状态:{}",hostId,next.getTaskStatus());
+                    iterator.remove();
+                }
+            }
+        }
+        return taskCaches;
+    }
 }

+ 19 - 0
soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/util/CommonUtils.java

@@ -95,4 +95,23 @@ public class CommonUtils {
         return version_one;
     }
 
+
+    /**
+     * 判断一个字符串是否为url
+     */
+    public static boolean isURL(String str){
+        //转换为小写
+        str = str.toLowerCase();
+        String regex = "^((https|http|ftp|rtsp|mms)?://)"  //https、http、ftp、rtsp、mms
+                + "?(([0-9a-z_!~*'().&=+$%-]+: )?[0-9a-z_!~*'().&=+$%-]+@)?" //ftp的user@
+                + "(([0-9]{1,3}\\.){3}[0-9]{1,3}" // IP形式的URL- 例如:199.194.52.184
+                + "|" // 允许IP和DOMAIN(域名)
+                + "([0-9a-z_!~*'()-]+\\.)*" // 域名- www.
+                + "([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]\\." // 二级域名
+                + "[a-z]{2,6})" // first level domain- .com or .museum
+                + "(:[0-9]{1,5})?" // 端口号最大为65535,5位数
+                + "((/?)|" // a slash isn't required if there is no file name
+                + "(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+/?)$";
+        return  str.matches(regex);
+    }
 }

+ 34 - 0
soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/vo/MachineInfo.java

@@ -0,0 +1,34 @@
+package com.xunmei.deploy.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 物联网主机信息
+ */
+@Data
+public class MachineInfo {
+
+    /**计算机名称**/
+    private String machineName;
+
+    /**操作系统类型,Windows|Linux|macOS等**/
+    private String osPlatform;
+
+    /**操作系统架构,x86|x64|Arm|Arm64**/
+    private String osArchitecture;
+
+    /**操作系统版本**/
+    private String osVersion;
+
+    /**Agent自身的版本,需遵守语义化版本 2.0.0规范。**/
+    private String version;
+
+    /**MAC地址集合**/
+    private List<String> macs;
+
+    /**IP地址集合**/
+    private List<String> ipAddresses;
+
+}

+ 24 - 0
soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/vo/TokenCache.java

@@ -0,0 +1,24 @@
+package com.xunmei.deploy.vo;
+
+import lombok.Data;
+
+
+@Data
+public class TokenCache {
+
+    /**注册码**/
+    private String clientId;
+
+    /**密钥**/
+    private String clientSecret;
+
+    /**令牌**/
+    private String accessToken;
+
+    /**令牌有效期**/
+    private Integer expiresIn;
+
+    /**获取令牌当前时间戳**/
+    private Long tokenDate;
+
+}

+ 23 - 0
soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/vo/TokenVo.java

@@ -0,0 +1,23 @@
+package com.xunmei.deploy.vo;
+
+import lombok.Data;
+
+/**
+ * 获取accessToken 请求参数
+ */
+
+@Data
+public class TokenVo {
+
+    /**注册码**/
+    private String client_id;
+
+    /**密钥**/
+    private String client_secret;
+
+    /**安装Agent的计算机信息**/
+    private MachineInfo machineInfo;
+
+    /** 认证类型 **/
+    private String grant_type;
+}

+ 50 - 0
soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/vo/heart/AppRunningInfo.java

@@ -0,0 +1,50 @@
+package com.xunmei.deploy.vo.heart;
+
+import lombok.Data;
+
+/**
+ * 应用运行信息
+ */
+@Data
+public class AppRunningInfo {
+
+    /**部署平台分配的标识,编码在Agent中应确保唯
+     一。每次部署都应该不同。 code 只能由数字
+     [0-9] 构成。**/
+    private String code;
+
+    /**应用的唯一ID,和部署多少次无关。**/
+    private String appId;
+
+    /**应用类型BackgroundServices,DesktopApp, SystemServices,Tool**/
+    private String appType;
+
+    /**应用名称,建议使用中文名称**/
+    private String appName;
+
+    /**应用版本号,需遵守语义化版本 2.0.0 规范**/
+    private String version;
+
+    /**应用正在运行中**/
+    private boolean running;
+
+    /**进程ID**/
+    private String processId;
+
+    /**应用的启动时间**/
+    private String startTime;
+
+    /**应用部署阶段**/
+    private String stage;
+
+    /**应用部署该阶段的状态**/
+    private String status;
+
+    /**运行过程的描述信息,主要用于显示错误信息**/
+    private String description;
+
+    /**补丁包**/
+    private String hotfixes;
+
+
+}

+ 46 - 0
soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/vo/heart/HeartBeat.java

@@ -0,0 +1,46 @@
+package com.xunmei.deploy.vo.heart;
+
+import lombok.Data;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 心跳实体
+ */
+@Data
+public class HeartBeat {
+
+    /**Agent节点计算机的UTC时间。*/
+    private String time;
+
+    /**Agent自身版本号**/
+    private String version;
+
+    /***ture 表示该Agent允许升级到
+     预发布版本,部署中心根据该值
+     返回对应的最新版本号。**/
+    private Boolean allowPrerelease;
+
+    /**Agent节点描述,用于识别节点。**/
+    private String description;
+
+    /**Agent的重要错误信息,比如验证获取的应用清单无效**/
+    private String errorMessage;
+
+    /**节点上的应用清单的时间戳。
+     * 部署中心应该检测节点与中心的时戳是否一致,
+     * 若不一致应发出推送应用清单任务,
+     * 使得清单保持一致。
+     * **/
+    private String deployTimeStamp;
+
+
+    /**节点部署的应用运行状态,将来
+     也许会由配置决定是否附带app
+     运行状态,所以并非必填。**/
+    private List<AppRunningInfo> apps = new ArrayList<>();
+
+
+}
+
+

+ 29 - 0
soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/vo/heart/HeartResponse.java

@@ -0,0 +1,29 @@
+package com.xunmei.deploy.vo.heart;
+
+import lombok.Data;
+
+@Data
+public class HeartResponse {
+
+    /**部署中心的名称**/
+    private String name;
+
+    /**部署中心的版本号**/
+    private String version;
+
+    /**部署中心计算机的UTC时间。**/
+    private String time;
+
+    /**系统安装的服务及版本清单**/
+    private String agentLastVersion;
+
+    /**部署中心应用清单的最后更新时戳**/
+    private String deployTimeStamp;
+
+    /**部署中心有待执行的下发任务**/
+    private boolean hasTask;
+
+
+
+
+}

+ 25 - 0
soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/vo/heart/HeartTimeVo.java

@@ -0,0 +1,25 @@
+package com.xunmei.deploy.vo.heart;
+
+import lombok.Data;
+
+/**
+ * @author gaoxiong
+ * @Title: 心跳信息
+ * @Package
+ * @Description:
+ * @date 2021/4/1410:56
+ */
+@Data
+public class HeartTimeVo {
+
+    /**心跳主机id**/
+    private String clientId;
+
+    /**最后一次心跳时间**/
+    private Long heartTime;
+
+    /**主机状态,默认在线**/
+    private Integer hostStatus = 1;
+
+
+}

+ 55 - 0
soc-modules/soc-modules-deploy/src/main/java/com/xunmei/deploy/vo/heart/ResponeError.java

@@ -0,0 +1,55 @@
+package com.xunmei.deploy.vo.heart;
+
+import lombok.Data;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+
+/**
+ * @author gaoxiong
+ * @Title: 错误对象
+ * @Package
+ * @Description:
+ * @date 2021/6/1010:04
+ */
+@Data
+public class ResponeError {
+    /**
+     *  状态码
+     */
+    private int code;
+    /**
+     * 消息
+     */
+    private String message;
+
+    /**
+     * 详细描述
+     */
+    private String details;
+    /**
+     * 其他信息
+     */
+    private Object any;
+
+
+    public ResponeError(int code, String message){
+        this.code = code;
+        this.message = message;
+        this.details = message;
+    }
+
+    public ResponeError(int code, String message, Object any){
+        this.code = code;
+        this.message = message;
+        this.details = message;
+        this.any = any;
+    }
+
+    public static ResponseEntity Info(String message, Object any){
+        ResponeError error = new ResponeError(HttpStatus.BAD_REQUEST.value(),message,any);
+        return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
+    }
+
+
+
+}

+ 9 - 0
soc-modules/soc-modules-deploy/src/main/resources/mapper/SysConfDao.xml

@@ -0,0 +1,9 @@
+<?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.xunmei.deploy.dao.SysConfDao">
+
+    <select id="getByCode" resultType="com.xunmei.deploy.domain.SysConf">
+        select id,code,`value`,create_time from t_sys_conf where code = #{code}
+    </select>
+
+</mapper>