SpringBoot/Java中定时请求并根据服务端响应头的date实现本地Windows修改时间/时间同步(管理员权限问题-bat管理员启动cmd并运行jar)

场景

业务场景需要将本地Windows服务器与远端Linux服务器进行时间同步。

但是远端服务器无法进行任何操作,不能进行配置开启ntp等操作。

但是可以知道远端服务器开放的服务,比如远端的业务系统的ip和端口。

那么可以通过请求远端业务系统的服务,并根据响应头的date字段获取远端服务器

的时间,进行更改本地服务器的时间。

若依前后端分离版本,Windows下使用Nginx代理的方式进行部署(全流程,图文教程):

若依前后端分离版本,Windows下使用Nginx代理的方式进行部署(全流程,图文教程)_前后端分离jar包部署_霸道流氓气质的博客-CSDN博客

远端部署服务才参考如上进行本地模拟测试,部署前后端分离的系统,通过请求前端

资源的响应头获取服务端时间。

注:

博客:
霸道流氓气质的博客_CSDN博客-C#,架构之路,SpringBoot领域博主

实现

1、远端服务器搭建一个服务,并确保可访问

 

然后打开F12找到响应头的时间

 

如何验证是服务端的时间,可将本地时间修改下再次请求下,返回仍然是服务端时间。

 

返回的时间格式是

Wed, 17 May 2023 01:14:20 GMT

返回的日期时间格式为RFC1123协议格式

RFC1123协议

该协议日期时间格式的定义是RFC822的升级;相对于RFC822,将2位数年份改成4位数。

格式:DAY, DD MON YYYY hh:mm:ss GMT

RFC822协议

RFC822: Standard for ARPA Internet Text Messages​​(ARPA 互联网文本消息的标准),

即电子邮件信息标准,主要是用于电子邮件格式的报文。

2、搭建SpringBoot项目实现定时发起请求获取响应体的时间

SpringBoot中实现Http客户端端发起请求可参考如下

Forest-声明式HTTP客户端框架-集成到SpringBoot实现调用第三方restful api并实现接口数据转换:

Forest-声明式HTTP客户端框架-集成到SpringBoot实现调用第三方restful api并实现接口数据转换_api框架_霸道流氓气质的博客-CSDN博客

参考上面流程,新增调用服务端服务的接口

​
import com.dtflys.forest.annotation.Get;
import com.dtflys.forest.http.ForestResponse;
import org.springframework.stereotype.Service;

@Service
public interface IBusThirdApiService {

    @Get("http://ip:250")
    ForestResponse<String> getResponseDate();

}

​

新建定时任务

@Component
@EnableScheduling
public class GetApiDataTask {

    private static final Logger log = LoggerFactory.getLogger(GetApiDataTask.class);

    @Autowired
    private IBusThirdApiService iBusThirdApiService;

    @Scheduled(fixedRateString = "${task.getResponseDate}")
    public void  getResponseDate(){
        try {
            ForestResponse<String> forestResponse = iBusThirdApiService.getResponseDate();
            String responTimeString = forestResponse.getHeaderValue("date");
        }catch (Exception exception){
        }
    }
}

这里定时对上面的接口发起调用,并通过ForestResponse<String>获取forest发起请求的响应体

通过断点调试可以明确和获取响应体的date字段。

3、Java中通过ZonedDateTime解析GMT字符串并进行GMT时区转换以及比较两个ZonedDateTime大小、

相差秒数。

Java对获取的GMT字符串/RFC1123字符串进行解析转换为ZonedDateTime

ZonedDateTime responseDateTime = ZonedDateTime.parse(responTimeString, DateTimeFormatter.RFC_1123_DATE_TIME);

获取的RFC1123的时间的时区即GTM时间,是格林威治这个时候的时间,是要比北京时间慢8小时。

所以需要进行时区转换。

Java中对ZonedDateTime/RFC1123/GMT进行时区转换

            ZoneId z = ZoneId.of("Asia/Shanghai");
            ZonedDateTime serverDateTime = responseDateTime.withZoneSameInstant(z);

将上面的RFC1123的时间转换为北京时间,大部分是Asia/Shanghai

可以进ZonedId源码中获取

 

将时区都转换为东八区之后,如果比较时间相差秒数。

Java中比较两个ZonedDateTime的大小/差值/秒数

            ZonedDateTime responseDateTime = ZonedDateTime.parse(responTimeString, DateTimeFormatter.RFC_1123_DATE_TIME);

            ZonedDateTime nowDateTime = ZonedDateTime.now();

            //GMT 时区转换
            ZoneId z = ZoneId.of("Asia/Shanghai");
            ZonedDateTime serverDateTime = responseDateTime.withZoneSameInstant(z);

            //时间大小比较
            long seconds = ChronoUnit.SECONDS.between(nowDateTime, serverDateTime);
            System.out.println("相差秒数:"+seconds);

            long absSeconds = Math.abs(seconds);

通过ChronoUnit.SECONDS.between实现,这里ChronoUnit是枚举类,比较单位这里用的秒,

还可以使用其他任意单位。因为时间有快有慢,所以差值有可能出现负数,所以这里取差值

的绝对值。

4、验证以上获取秒数差值

Centos中查看系统时间的命令

date

Centos中查看系统时间以及时区的命令

date -R

 

可以看到这里的+0800表示东八区

Centos中修改系统时间的命令

date  -s  "2023-5-15 09:50:00"

Centos中设置自动Ntp时间同步的命令

ntpdate ntp.aliyun.com

后面是国内的NTP服务器,首先确保服务端与之相同,不然更换其他NTP服务器

ping ntp.aliyun.com

首先将服务端的时间调整快

 

再将远端时间修改正确,进行ntp网络时间同步之后

再将远端时间修改慢了之后

 

5、Java修改Windows系统时间

            if(absSeconds>secondsThreadHoldValue){
                DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
                DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss");
                String formatDate = serverDateTime.format(dateFormatter);
                String formatTime = serverDateTime.format(timeFormatter);
                //修改本地时间对应为远程时间
                //方式1-直接通过命令的方式修改
                String cmd = " cmd /c time "+formatTime;
                Runtime.getRuntime().exec(cmd); // 修改时间
                cmd = " cmd /c date "+formatDate;
                Runtime.getRuntime().exec(cmd); // 修改日期
                log.info("触发修改系统时间,本地时间:{},服务器时间:{}",nowDateTime,serverDateTime);
            }

以上代码判断如果相差的秒数大于指定的数,则触发修改本地时间为远端时间的操作。

即通过格式化远端的时间获取日期和时间并执行cmd的命令实现。

但是注意一点:

Windows上Java修改系统时间不生效的解决。

直接运行上面的代码,并修改本地的时间触发以上修改时间的操作之后,发现并不生效。

这是因为Java中修改系统时间需要管理员权限,所以需要在运行jar包时以管理员权限启动cmd。

bat中以管理员模式启动cmd,进入到bat所属目录中,并启动jar包

将上面打成jar包,并新建一个bat文件,修改内容如下

@ echo off
%1 %2
ver|find "5.">nul&&goto :Admin
mshta vbscript:createobject("shell.application").shellexecute("%~s0","goto :Admin","","runas",1)(window.close)&goto :eof
:Admin

::下面是自己的代码
cd /d %~dp0
java -jar scheduleForestTimeSync.jar
pause

上面是以管理员启动cmd的命令,

下面cd /d %~dp0是进入到bat所属目录的命令,

然后将jar包与bat放在同一目录下,双击bat启动

启动时会进行管理员模式启动cmd的提示,点击是。

6、完善以上时间同步定时任务的代码

@Component
@EnableScheduling
public class GetApiDataTask {

    private static final Logger log = LoggerFactory.getLogger(GetApiDataTask.class);

    @Value("${task.secondsThreadHoldValue}")
    private Integer secondsThreadHoldValue;

    @Autowired
    private IBusThirdApiService iBusThirdApiService;

    @Scheduled(fixedRateString = "${task.getResponseDate}")
    public void  getResponseDate(){
        try {
            ForestResponse<String> forestResponse = iBusThirdApiService.getResponseDate();
            String responTimeString = forestResponse.getHeaderValue("date");

            ZonedDateTime responseDateTime = ZonedDateTime.parse(responTimeString, DateTimeFormatter.RFC_1123_DATE_TIME);

            ZonedDateTime nowDateTime = ZonedDateTime.now();

            //GMT 时区转换
            ZoneId z = ZoneId.of("Asia/Shanghai");
            ZonedDateTime serverDateTime = responseDateTime.withZoneSameInstant(z);

            //时间大小比较
            long seconds = ChronoUnit.SECONDS.between(nowDateTime, serverDateTime);
            System.out.println("相差秒数:"+seconds);

            long absSeconds = Math.abs(seconds);
            //相差时间秒数阈值
            if(absSeconds>secondsThreadHoldValue){
                DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
                DateTimeFormatter dateTimeFormatter1 = DateTimeFormatter.ofPattern("HH:mm:ss");
                String format = serverDateTime.format(dateTimeFormatter);
                String format1 = serverDateTime.format(dateTimeFormatter1);
                //修改本地时间对应为远程时间
                //方式1-直接通过命令的方式修改
                String cmd = " cmd /c time "+format1;
                Runtime.getRuntime().exec(cmd); // 修改时间
                cmd = " cmd /c date "+format;
                Runtime.getRuntime().exec(cmd); // 修改日期
                log.info("触发修改系统时间,本地时间:{},服务器时间:{}",nowDateTime,serverDateTime);
            }
        }catch (Exception exception){
            StackTraceElement stackTrace = exception.getStackTrace()[0];
            String methodName = stackTrace.getMethodName();
            String fileName = stackTrace.getFileName();
            String className = stackTrace.getClassName();
            int lineNumber = stackTrace.getLineNumber();
            log.error("异常发生在文件{}的类{}中的方法{}的第{}行',异常信息:{}", fileName,className,methodName,lineNumber,exception.getMessage());
        }
    }
}

测试以上时间同步的效果

7、关于其它实现方式的说明

通过调研网络上资料发现其他的实现方式

一种是在本地运行时会直接提示需要管理员权限启动cmd,每次通过在本地生成bat文件,并向bat中

写入内容,并执行bat,成功之后删除的逻辑实现。

此种方式实现参考如下

            File temDir = new File("/");
            String filePath = "setDateTime.bat";
            File batFile = new File(temDir.getPath() + "/setDateTime.bat" + filePath);

            if (!temDir.exists()) {
                temDir.mkdir();
                batFile.createNewFile();
            }

            FileWriter fw = new FileWriter(filePath);
            BufferedWriter bw = new BufferedWriter(fw);
            bw.write("@echo off\n");
            bw.write("%1 mshta vbscript:CreateObject(\"Shell.Application\").ShellExecute(\"cmd.exe\",\"/c %~s0 ::\",\"\",\"runas\",1)(window.close)&&exit\n");
            bw.write("time "+format1);
            bw.newLine();
            bw.write("date "+format);
            bw.close();
            fw.close();
            Process process = Runtime.getRuntime().exec(filePath);
            process.waitFor();
            //等上面的执行完毕后再删除文件
            batFile.delete();

这种操作,在每次触发修改时间操作时都会弹窗提示是否允许以管理员模式启动

但是可以通过修改Windows的本地安全策略,使该提示关闭。

控制面板-管理工具-本地安全策略-安全选项-用户账号控制:以管理员批准模式运行所有管理员

将此禁用后,则每次执行bat时不再需要手动确认,但是修改此项操作需要重启服务器才能生效。

使用如上方式一样能实现修改时间的操作

另一种是通过jna调用Windows的Kernel32 DLL的方式修改时间。

添加依赖

        <dependency>
            <groupId>net.java.dev.jna</groupId>
            <artifactId>jna-platform</artifactId>
            <version>4.4.0</version>
        </dependency>
         <dependency>
            <groupId>net.java.dev.jna</groupId>
             <artifactId>jna</artifactId>
            <version>4.3.0</version>
    </dependency>

新建修改工具类

package com.badao.demo.api;

import com.sun.jna.Native;
import com.sun.jna.platform.win32.WinBase;
import com.sun.jna.win32.StdCallLibrary;

public class WindowsSetSystemTime {

    /**
     * Kernel32 DLL Interface. kernel32.dll uses the __stdcall calling
     * convention (check the function declaration for "WINAPI" or "PASCAL"), so
     * extend StdCallLibrary Most C libraries will just extend
     * com.sun.jna.Library,
     */
    public interface Kernel32 extends StdCallLibrary {

        boolean SetLocalTime(WinBase.SYSTEMTIME st);

        Kernel32 instance = (Kernel32) Native.loadLibrary("kernel32.dll", Kernel32.class);

    }

    public boolean SetLocalTime(WinBase.SYSTEMTIME st) {
        return Kernel32.instance.SetLocalTime(st);
    }

    public boolean SetLocalTime(short wYear, short wMonth, short wDay, short wHour, short wMinute, short wSecond , short wMilliseconds) {
        WinBase.SYSTEMTIME st = new WinBase.SYSTEMTIME();
        st.wYear = wYear;
        st.wMonth = wMonth;
        st.wDay = wDay;
        st.wHour = wHour;
        st.wMinute = wMinute;
        st.wSecond = wSecond;
        st.wMilliseconds=wMilliseconds;
        return SetLocalTime(st);
    }
}

修改时间测试

            String dateStr1= "2023-05-16 16:00:00.000";
            WindowsSetSystemTime w=new WindowsSetSystemTime();
            w.SetLocalTime(
                    Short.parseShort(dateStr1.substring(0, 4).trim()),
                    Short.parseShort(dateStr1.substring(5, 7).trim()),
                    Short.parseShort(dateStr1.substring(8, 10).trim()),
                    Short.parseShort(dateStr1.substring(11, 13).trim()),
                    Short.parseShort(dateStr1.substring(14, 16).trim()),
                    Short.parseShort(dateStr1.substring(17, 19).trim()),
                    Short.parseShort(dateStr1.substring(20, 23).trim())
            );

注意这种方式,仍然修改运行jar包时以管理员模式启动cmd,不然也不会生效。

猜你喜欢

转载自blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/130720097
今日推荐