SpringBoot/Java でリクエストのタイミングを調整し、サーバー応答ヘッダーの日付に応じてローカル Windows の変更時刻/時刻の同期を実現します (管理者権限の問題 - Bat 管理者が cmd を起動し、jar を実行します)

シーン

ビジネス シナリオでは、ローカル Windows サーバーとリモート Linux サーバーの間で時刻を同期する必要があります。

ただし、リモート サーバーは、ntp の構成や有効化などの操作を実行できません。

ただし、リモート ビジネス システムの IP やポートなど、リモート サーバーによって開かれているサービスを知ることはできます。

その後、リモート ビジネス システムのサービスをリクエストし、応答ヘッダーの日付フィールドに従ってリモート サーバーを取得できます。

ローカルサーバー時刻を変更する時刻。

フロントエンドとバックエンドの分離バージョンによれば、Nginx エージェントは Windows での展開に使用されます (完全なプロセス、グラフィック チュートリアル)。

フロントエンドとバックエンドの分離バージョンに従って、Nginx プロキシ方式を使用して Windows にデプロイします (完全なプロセス、グラフィック チュートリアル)

リモート導入サービスは、上記のローカルシミュレーションテストのみを参照し、フロントエンドとバックエンドを分離したシステムを導入します。

リソースの応答ヘッダーはサーバー時間を取得します。

ノート:

ブログ:
横柄なローグ気質 blog_CSDN ブログ - C#、アーキテクチャ ロード、SpringBoot のブロガー

達成

1. リモート サーバー上にサービスを構築し、アクセスできることを確認します。

 

次に、F12 を開いて応答ヘッダーの時刻を確認します。

 

サーバーの時刻であることを確認する方法は、現地時間を変更して再度リクエストしても、返される時刻は依然としてサーバーの時刻です。

 

 

返される時刻形式は次のとおりです。

2023年5月17日(水)01:14:20 GMT

返される日付と時刻の形式は RFC1123 プロトコル形式です

RFC1123プロトコル

このプロトコルの日時フォーマットの定義は RFC822 をバージョンアップしたものであり、RFC822 と比較して 2 桁の年が 4 桁に変更されています。

形式: DAY、DD MON YYYY hh:mm:ss GMT

RFC822プロトコル

RFC822: ARPA インターネット テキスト メッセージの標準 (ARPA インターネット テキスト メッセージ標準)、

これは、主に電子メール形式のメッセージに使用される電子メール情報規格です。

2. SpringBoot プロジェクトを構築して、一定の間隔で応答本文を取得するリクエストを開始する時間を実現します。

SpringBoot でリクエストを開始するための Http クライアントの実装は、以下を参照できます。

フォレスト宣言型 HTTP クライアント フレームワーク - SpringBoot に統合され、サードパーティの RESTful API の呼び出しを実装し、インターフェイス データ変換を実現します。

フォレスト宣言型 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> を通じて取得されます。

 

応答本文の日付フィールドは、ブレークポイントのデバッグを通じて明確にし、取得できます。

3. Java では、ZonedDateTime を通じて GMT 文字列を解析し、GMT タイム ゾーン変換を実行して、2 つの 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 時間を北京時間 (主にアジア/上海時間) に変換します。

ZonedId ソース コードで取得できます。

 

タイム ゾーンを東第 8 ゾーンに変換した後、時間の比較に秒単位の違いがある場合。

Java の 2 つの 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 は列挙クラスであり、ここで 2 番目は比較単位に使用されます。

他の単位も使用できます。時間が速いか遅いかによって差がマイナスになる場合があるので、ここで差を取ります。

の絶対値。

4. 上記を確認して差を秒単位で取得します。

Centos でシステム時間を表示するコマンド

date

Centos でシステム時間とタイムゾーンを表示するコマンド

date -R

 

ここで +0800 は東第 8 地区を意味していることがわかります。

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 コマンドを実行します。

ただし、1 つのことに注意してください。

Javaによるシステム時刻の変更がWindows上で反映されない問題の解決策。

上記のコードを直接実行し、現地時間を変更して上記の時間変更操作をトリガーした後、効果がないことがわかります。

これは、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. その他の実装方法に関する注意事項

インターネット上の情報を調べて他の実装方法を見つける

1 つは、ローカルで実行する場合、cmd を起動するには管理者権限が必要であることを直接プロンプトし、バット ファイルがローカルで生成されてバットに追加されるたびにプロンプ​​トが表示されることです。

コンテンツを記述し、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 を実行するたびに手動で確認する必要がなくなりますが、この操作の変更を有効にするにはサーバーを再起動する必要があります。

上記と同じ方法で時刻を変更する操作を実装します。

 

もう 1 つは、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
おすすめ