Java后台服务版本升级之更新功能开发(SpringBoot项目)

项目场景:

  部署环境:centos7系统、docker部署

  描述:公司产品后台服务版本升级之更新功能开发:资源打包后上传到云服务上,项目定时下载云服务器上最新版本对应的资源包解压至本地服务器,然后客户登录web点击更新按钮,进行服务的更新,更新功能对应的操作有:

①当前版本和最新版本标识调整;

②处理跨版本更新中间的增量sql和脚本的执行;

③重定向jar包自启动处理(保证当前项目运行的是最新版本);

④项目启动初始化时检测最新版本并启动最新的jar包;

  开发过程中的想法和挫折:更新后重定向jar包自启动的操作本来想通过修改操作系统的自启动文件(/etc/rc.d/rc.local)来实现,但是发现使用java修改该文件后再重启容器,容器启动不了了,如果是手动修改就可以,这个问题弄了一天无果后,再加上leader催任务催的紧,换个思路,首先目的是为了本地服务器断电,保证项目启动的是最新的版本。LZ想到可以项目初始化的时候就去检测最新版本然后通过脚本停止old版本的jar包,最后启动最新的jar包(以上说的第四步)

话不多说,上代码:


Service层:

更新功能如下:

    /**
     * @Author: yz 
     * @Date 14:40 2023/1/6
     * @Param []
     * @return 版本更新功能
    */
    @Override
    public ResponseResult updateLatestVersion() {
        log.info("开始获取最新的版本信息");
        VersionProjectVO vpInfo = versionMapper.getLatestProjectVersion();
        if (vpInfo == null) {
            throw new BusinessException(CommonExEnum.VERSION_PROJECT_ERR);
        }
        log.info("最新版本为:"+vpInfo.getCurrentEdition());
        if (vpInfo.getCurrentEdition()==1) {
            log.info("当前已是最新版本,无需更新。");
            throw new BusinessException("当前已是最新版本,无需更新。");
        }

        //不是最新版本,需要更新
        VersionProject cpVersion = versionMapper.getCurrentProjectDetailVersion();
        if (Objects.isNull(cpVersion)) {
            log.info("当前版本信息不存在");
            throw new BusinessException("当前版本信息不存在");
        }else{
            log.info("开始更新版本");
            //更新jar
            if (!cpVersion.getJar().equals(vpInfo.getJar())) {
                //修改st_version_jar表的execute字段前版本改为0,新版本改为1、st_version_project表的current_edition字段前版本改为0,新版本改为1
                /** 业务逻辑省略 **/
                //如果是跨版本,找到中间的所有版本
                List<VersionSqlShellExec> middleVersion = versionMapper.getMiddleVersion(cpVersion.getOrder(),vpInfo.getOrder());
                try {
                    String mysqlPwd = Constants.MYSQL_PWD;
                    String sqlShellUrl = Constants.SQL_SHELL_URL;
                    //执行脚本和sql
                    log.info("开始执行脚本和sql,中间版本数量为:"+middleVersion.size()+"个!");
                    for (int i = 0; i < middleVersion.size(); i++) {
                        String newJarVersion = middleVersion.get(i).getJar();
                        //String newJarOrder = middleVersion.get(i).getOrder();
                        String sqlCommond = "sh /root/runSql.sh "+mysqlPwd+" "+newJarVersion+"";
                        String shellCommond = "sh "+sqlShellUrl+"shell/"+newJarVersion+".sh";
                        //执行sql文件
                        log.info("sql脚本命令:"+sqlCommond);
                        log.info("shell脚本命令:"+shellCommond);
                        Process process = Runtime.getRuntime().exec(sqlCommond);
                        //写出脚本执行中的过程信息
                        String rightLine="";
                        StringBuilder result=new StringBuilder();
                        String errorLine="";
                        BufferedReader infoInput = new BufferedReader(new InputStreamReader(process.getInputStream()));
                        BufferedReader errorInput = new BufferedReader(new InputStreamReader(process.getErrorStream()));
                        log.debug("*****sql文件正确信息开始输出*****");
                        while ((rightLine = infoInput.readLine()) != null) {
                            result.append(rightLine);
                            log.info(rightLine);
                        }
                        log.debug("*****sql文件正确信息输出结束*****");
                        log.debug("*****sql文件错误信息开始输出*****");
                        while ((errorLine = errorInput.readLine()) != null) {
                            log.info(errorLine);
                        }
                        log.info("*****sql文件错误输出结束*****");
                        infoInput.close();
                        errorInput.close();
                        //阻塞执行线程直至脚本执行完成后返回
                        process.waitFor();
                        //Runtime.getRuntime().exec(sqlCommond2);
                        //执行脚本文件
                        Process exec = Runtime.getRuntime().exec(shellCommond);
                        String rightLine1="";
                        StringBuilder result1=new StringBuilder();
                        String errorLine1="";
                        BufferedReader infoInput1 = new BufferedReader(new InputStreamReader(exec.getInputStream()));
                        BufferedReader errorInput1 = new BufferedReader(new InputStreamReader(exec.getErrorStream()));
                        log.debug("*****shell脚本正确信息开始输出*****");
                        while ((rightLine1 = infoInput1.readLine()) != null) {
                            result1.append(rightLine1);
                            log.info(rightLine1);
                        }
                        log.debug("*****shell脚本正确信息输出结束*****");
                        log.debug("*****shell脚本错误信息开始输出*****");
                        while ((errorLine1 = errorInput1.readLine()) != null) {
                            log.info(errorLine1);
                        }
                        log.info("*****shell脚本错误输出结束*****");
                        infoInput1.close();
                        errorInput1.close();
                        //阻塞执行线程直至脚本执行完成后返回
                        exec.waitFor();
                    }
                    log.info("脚本和sql执行完成");
                    //获取jar版本对应的路径和截取到jar名
                    String jar = vpInfo.getJar();
                    String jarPath = versionMapper.getJarPath(jar);
                    if(StringUtils.isEmpty(jarPath)){
                        throw new BusinessException(CommonExEnum.NEW_VERSION_JAR_DELETE);
                    }
                    int i = jarPath.lastIndexOf("/");
                    String name = jarPath.substring(i + 1);
                    //jar包替换:备份自启动文件、尝试修改自启动文件重启容器
                    //复制shell文件
                    log.info("复制备份自启动文件...");
                    Runtime.getRuntime().exec("touch /etc/rc.d/rc.local.bak");
                    Runtime.getRuntime().exec("chmod 777 /etc/rc.d/rc.local.bak");
                    Runtime.getRuntime().exec("cp /etc/rc.d/rc.local/etc/rc.d/rc.local.bak");
                    String str = "ic_jar_path=\""+Constants.VERSION_JAR_PATH+name+"\"";
                    BufferedReader reader = new BufferedReader(new FileReader(new File("/etc/rc.d/rc.local.bak")));
                    //创建/etc/rc.d/rc.local文件和授权docker
                    Runtime.getRuntime().exec("touch /etc/rc.d/rc.local.test");
                    Runtime.getRuntime().exec("chmod 777 /etc/rc.d/rc.local.test");
                    BufferedWriter in = new BufferedWriter(new FileWriter(new File("/etc/rc.d/rc.local.test")));
                    String line=null;
                    //修改jar启动命令指向
                    while ((line=reader.readLine())!=null) {
                        //进入文件写入操作
                        log.info("进入文件写入操作...");
                        log.info("读取文件当前行数据:"+line);
                        if(line.startsWith("ic_jar_path=")){
                            log.info("第一行数据写入成功");
                            in.write(str+"\r\n");
                        }else{
                            in.write(line+"\r\n");
                        }
                    }
                    //关闭流,不可以少,否则数据在缓存,没有实际写入
                    reader.close();
                    in.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                log.info("jar包指向替换完成");
                log.info("版本信息已经更新完成,可以重启项目了!");
            }
        }
        return ResponseResult.success("项目已更新到版本V"+vpInfo.getEdition()+",请重新启动项目");
    }

项目新版本检测和重启功能如下:

    @Override
    public ResponseResult restartServer() {
        //判断当前系统环境是否为docker
        String osName = System.getProperty("os.name");
        if (!osName.startsWith("Windows")) {
            log.info("开始获取最新的版本信息");
            VersionProjectVO vpInfo = versionMapper.getLatestProjectVersion();
            if (vpInfo == null) {
                throw new BusinessException(CommonExEnum.VERSION_PROJECT_ERR);
            }
            //判断当前版本是否为最新版本
            boolean nowIsLast = versionMapper.getNowIsLast(vpInfo.getId());
            if (nowIsLast) {
                log.info("最新版本为:"+vpInfo.getCurrentEdition());
                //获取jar版本对应的路径和截取到jar名
                String jar = vpInfo.getJar();
                String jarPath = versionMapper.getJarPath(jar);
                if(StringUtils.isEmpty(jarPath)){
                    throw new BusinessException(CommonExEnum.NEW_VERSION_JAR_DELETE);
                }
                int i = jarPath.lastIndexOf("/");
                String name = jarPath.substring(i + 1);
                String commond = "sh /data/stop_ic.sh";
                String commond1 = "nohup java -jar "+Constants.VERSION_JAR_PATH+name+" --spring.profiles.active=pro >"+Constants.VERSION_JAR_PATH+"nohup.out 2>&1&";
                //判断新版本的jar是否存在
                File file = new File(Constants.VERSION_JAR_PATH + name);
                if (file.exists()) {
                    try {
                        //判断是否运行的是新版本的jar,是的话不需要继续启动从而造成无限循环了
                        String countCommond = "ps -ef|grep -c " + Constants.VERSION_JAR_PATH + name;
                        String[] command = { "/bin/sh", "-c", countCommond};
                        Process exec = Runtime.getRuntime().exec(command);
                        log.info(countCommond);
                        //写出脚本执行中的过程信息
                        String rightLine="";
                        String errorLine="";
                        BufferedReader infoInput = new BufferedReader(new InputStreamReader(exec.getInputStream()));
                        BufferedReader errorInput = new BufferedReader(new InputStreamReader(exec.getErrorStream()));
                        log.debug("*****sql文件正确信息开始输出*****");
                        while ((rightLine = infoInput.readLine()) != null) {
                            log.info("jar包运行行数:"+rightLine);
                            break;
                        }
                        log.debug("*****sql文件正确信息输出结束*****");
                        log.debug("*****sql文件错误信息开始输出*****");
                        while ((errorLine = errorInput.readLine()) != null) {
                            log.info(errorLine);
                        }
                        log.info("*****sql文件错误输出结束*****");
                        if(StringUtils.isNotEmpty(rightLine)&&Integer.parseInt(rightLine)<3){
                            //log.info("开始重启IC容器,执行命令:"+commond);
                            log.info("停止IC项目,执行命令:"+commond);
                            log.info("开始重启项目,执行命令:"+commond1);
                            Runtime.getRuntime().exec(commond);
                            Runtime.getRuntime().exec(commond1);
                            //processShell(commond);
                            log.info("重启IC容器成功");
                        }else{
                            log.info("当前版本jar包运行的是最新版本的jar,无需重启");
                        }
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }else{
                    log.info("当前版本jar包已删除或不存在");
                }
            }else{
                log.info("当前版本不是最新版本,请先更新");
            }
        }
        return ResponseResult.success(1);
    }

项目初始化添加检测如下: 

    @PostConstruct // 构造函数之后执行
    public void init(){
        log.info("检测最新版本,重新启动新版本");
        restartServer();
    }

结果:

总结:

虽然实现了功能,但是方案:更新后重定向jar包自启动的操作本来想通过修改操作系统的自启动文件(/etc/rc.d/rc.local)来实现(容器重启命令通过ssh连接宿主机:sshpass -p 'yourPwd' ssh -o StrictHostKeyChecking=no -p 22 root@yourIP docker restart containerName),容器启动不了的情况仍未解决,有同样困惑的伙伴解决后欢迎交流告知

结束语:

每天做好每天该做的事情,用做而不是用想,做该做的事情,并且接受它的事与愿违,不确定的事情越来越多,但是最后还得要选择接受,也许人生唯一确定的就是不确定的人生,人生唯一恐惧的就是恐惧本身。

springboot 是一种基于 Java 开发的框架,用于快速搭建、开发和管理企业级的应用程序。通过使用springboot开发人员可以轻松地创建可独立运行的、可执行的 Spring 应用程序,而不需要进行繁琐的配置。 在实战源码中,我们可以学习到如何使用 springboot 来管理项目。首先,我们需要了解如何搭建一个基本的 springboot 项目。我们可以通过 Maven 或者 Gradle 构建工具来初始化一个空的 springboot 项目,然后引入所需的依赖。 一旦项目搭建完毕,我们就可以开始开发springboot 提供了许多功能和组件,如:控制器、服务层、持久层、数据访问对象等。我们可以使用这些组件来构建一个完整的应用程序。 在源码中,我们可以学习到如何使用控制器来处理 HTTP 请求和响应,如何使用服务层来封装业务逻辑,如何使用持久层来进行数据库访问等。同时,我们还可以学习到如何优化项目的性能、如何进行单元测试和集成测试等。 通过实战源码,我们可以深入理解 springboot 的核心原理和用法。我们可以学习到如何使用依赖注入、自动配置、模板引擎等特性,以及如何使用 springboot 提供的各种插件和扩展来简化开发工作。 总而言之,实战源码可以帮助我们快速掌握 springboot开发技能和项目管理能力。通过学习实战源码,我们可以提高自己的开发水平,并能够更加高效地开发和管理 Java 项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

热心码民阿振

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值