System.out.println(2-1.1);

本文探讨了在Java中使用浮点数进行算术运算时出现的精度问题,并解释了为何某些十进制数不能被精确表示为二进制浮点数。同时提供了几种解决方法,包括使用BigDecimal类。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

System.out.println(1-0.1*9); 
         输出的结果按数学来算结果是:0.1  如果按从左到右来算也是8.1 

         但为什结果是0.09999999999999998  这是为什么了,这是什么运算????????????????


首先 
2进制无法精确显示0.1 

0.1 你用ieee的浮点数标准怎么表示? 后52个字节是小数,第一个字节是符号,后面11个字节是阶码。2的负几次方的和加到一起是0.1? 答案是没有。楼主试一下 2.0-0.5这样的,算出来就是精确的了。因为0.5是2的-1次方。0.75这样的 2(-1)+2(-2)都是可以精确表示的。。。。如果你想让2.0-0.1准确的话,用bigdecimal来做即可。 

编译器在处理一个数据时,当一种类型数据无法用二进制数精确表示时,只能按照一种规则去找一个数代替(比如取最接近这个数的二进制数代替 

如果想让他精确可以这样 


Java代码   收藏代码
  1. System.out.printf("%.1f",2.0-1.1);    


或者 

Java代码   收藏代码
  1. BigDecimal a = new BigDecimal("2.0").subtract(new BigDecimal("1.1"));  
  2.         System.out.println(a);  


引用
public class Change{ 
public static void main(String args[]){ 
System.out.println(2.00 - 1.10); 


你可能会很天真地期望该程序能够打印出0.90,但是它如何才能知道你想要打 
印小数点后两位小数呢? 
如果你对在Double.toString 文档中所设定的将double 类型的值转换为字符串 
的规则有所了解,你就会知道该程序打印出来的小数,是足以将double 类型的 
值与最靠近它的临近值区分出来的最短的小数,它在小数点之前和之后都至少有 
一位。因此,看起来,该程序应该打印0.9 是合理的。 
这么分析可能显得很合理,但是并不正确。如果你运行该程序,你就会发现它打 
印的是0.8999999999999999 
问题在于1.1 这个数字不能被精确表示成为一个double,因此它被表示成为最 
接近它的double 值。该程序从2 中减去的就是这个值。遗憾的是,这个计算的 
结果并不是最接近0.9 的double 值。表示结果的double 值的最短表示就是你所 
看到的打印出来的那个可恶的数字。 
更一般地说,问题在于并不是所有的小数都可以用二进制浮点数来精确表示的。

 

         因为十进制的有限浮点数,转换成二进制可能就不是有限小数了,可能变成了无限小数,而计算          机存储一个浮点数的空间是固定的,这样就造成了不精确,在转换成十进制展现在我们面前的            时候就是你看到的样子了。

来源:http://www.iteye.com/problems/69889


0

日志:[TASK] ====== CONTINUOUS DOWNLOAD STARTED ====== [INFO] Devices: 8 | Save path: F:/GKD/ [PROCESS] Device: GK2025040001 | Date: 2025-07-01 [DEBUG] Time Parameters: Original Local: 2025-07-01T00:00 - 2025-07-01T23:59:59 Converted Server: 2025-07-01 00:00:00 - 2025-07-01 23:59:59 Raw Time: 2025-07-01 00:00:00 & 2025-07-01 23:59:59 [DEBUG] Final request URL: http://nyzbwlw.com/situation/http/device/bug/getBugData?deviceCode=GK2025040001&startTime=2025-07-01%2000:00:00&endTime=2025-07-01%2023:59:59&size=1000&t=1754026969103 2025-08-01 13:42:49 [Thread-1] DEBUG o.s.web.client.RestTemplate - HTTP GET http://nyzbwlw.com/situation/http/device/bug/getBugData?deviceCode=GK2025040001&startTime=2025-07-01%252000:00:00&endTime=2025-07-01%252023:59:59&size=1000&t=1754026969103 2025-08-01 13:42:49 [Thread-1] DEBUG o.s.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*] 2025-08-01 13:42:49 [Thread-1] DEBUG o.a.h.c.protocol.RequestAddCookies - CookieSpec selected: default 2025-08-01 13:42:49 [Thread-1] DEBUG o.a.h.c.protocol.RequestAuthCache - Auth cache not set in the context 2025-08-01 13:42:49 [Thread-1] DEBUG o.a.h.i.c.PoolingHttpClientConnectionManager - Connection request: [route: {}->http://nyzbwlw.com:80][total available: 0; route allocated: 0 of 2; total allocated: 0 of 20] 2025-08-01 13:42:49 [Thread-1] DEBUG o.a.h.i.c.PoolingHttpClientConnectionManager - Connection leased: [id: 0][route: {}->http://nyzbwlw.com:80][total available: 0; route allocated: 1 of 2; total allocated: 1 of 20] 2025-08-01 13:42:49 [Thread-1] DEBUG o.a.h.impl.execchain.MainClientExec - Opening connection {}->http://nyzbwlw.com:80 2025-08-01 13:42:49 [Thread-1] DEBUG o.a.h.i.c.DefaultHttpClientConnectionOperator - Connecting to nyzbwlw.com/47.92.156.54:80 2025-08-01 13:42:49 [Thread-1] DEBUG o.a.h.i.c.DefaultHttpClientConnectionOperator - Connection established 192.168.2.4:62064<->47.92.156.54:80 2025-08-01 13:42:49 [Thread-1] DEBUG o.a.h.i.c.DefaultManagedHttpClientConnection - http-outgoing-0: set socket timeout to 15000 2025-08-01 13:42:49 [Thread-1] DEBUG o.a.h.impl.execchain.MainClientExec - Executing request GET /situation/http/device/bug/getBugData?deviceCode=GK2025040001&startTime=2025-07-01%252000:00:00&endTime=2025-07-01%252023:59:59&size=1000&t=1754026969103 HTTP/1.1 2025-08-01 13:42:49 [Thread-1] DEBUG o.a.h.impl.execchain.MainClientExec - Target auth state: UNCHALLENGED 2025-08-01 13:42:49 [Thread-1] DEBUG o.a.h.impl.execchain.MainClientExec - Proxy auth state: UNCHALLENGED 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> GET /situation/http/device/bug/getBugData?deviceCode=GK2025040001&startTime=2025-07-01%252000:00:00&endTime=2025-07-01%252023:59:59&size=1000&t=1754026969103 HTTP/1.1 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Accept: application/json, text/plain, */* 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Connection: keep-alive 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Referer: http://nyzbwlw.com/situation/ 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Origin: http://nyzbwlw.com 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Sec-Fetch-Dest: empty 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Sec-Fetch-Mode: cors 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Sec-Fetch-Site: same-origin 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Host: nyzbwlw.com 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Accept-Encoding: gzip,deflate 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "GET /situation/http/device/bug/getBugData?deviceCode=GK2025040001&startTime=2025-07-01%252000:00:00&endTime=2025-07-01%252023:59:59&size=1000&t=1754026969103 HTTP/1.1[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Accept: application/json, text/plain, */*[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Connection: keep-alive[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Referer: http://nyzbwlw.com/situation/[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Origin: http://nyzbwlw.com[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Sec-Fetch-Dest: empty[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Sec-Fetch-Mode: cors[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Sec-Fetch-Site: same-origin[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Host: nyzbwlw.com[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Accept-Encoding: gzip,deflate[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "HTTP/1.1 200 [\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Server: nginx/1.20.2[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Date: Fri, 01 Aug 2025 05:42:49 GMT[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Content-Type: application/json;charset=UTF-8[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Transfer-Encoding: chunked[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Connection: keep-alive[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Access-Control-Allow-Credentials: true[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Access-Control-Allow-Origin: *[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Vary: Origin[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Access-Control-Expose-Headers: Set-Cookie[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Access-Control-Allow-Methods: POST, GET, PUT, DELETE, OPTIONS[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Set-Cookie: JSESSIONID=852AF1576F877DE9C07BC6B516B6220A; Path=/situation; HttpOnly[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "41[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "{"msg":"[0xe6][0x93][0x8d][0xe4][0xbd][0x9c][0xe6][0x88][0x90][0xe5][0x8a][0x9f][0xef][0xbc][0x81]","code":100,"data":{"count":0,"list":[]}}[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "0[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "[\r][\n]" 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << HTTP/1.1 200 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Server: nginx/1.20.2 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Date: Fri, 01 Aug 2025 05:42:49 GMT 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Content-Type: application/json;charset=UTF-8 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Transfer-Encoding: chunked 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Connection: keep-alive 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Access-Control-Allow-Credentials: true 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Access-Control-Allow-Origin: * 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Vary: Origin 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Access-Control-Expose-Headers: Set-Cookie 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Access-Control-Allow-Methods: POST, GET, PUT, DELETE, OPTIONS 2025-08-01 13:42:49 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Set-Cookie: JSESSIONID=852AF1576F877DE9C07BC6B516B6220A; Path=/situation; HttpOnly 2025-08-01 13:42:49 [Thread-1] DEBUG o.a.h.impl.execchain.MainClientExec - Connection can be kept alive indefinitely 2025-08-01 13:42:49 [Thread-1] DEBUG o.a.h.c.p.ResponseProcessCookies - Cookie accepted [JSESSIONID="852AF1576F877DE9C07BC6B516B6220A", version:0, domain:nyzbwlw.com, path:/situation, expiry:null] 2025-08-01 13:42:49 [Thread-1] DEBUG o.s.web.client.RestTemplate - Response 200 OK 2025-08-01 13:42:49 [Thread-1] DEBUG o.s.web.client.RestTemplate - Reading to [java.lang.String] as "application/json;charset=UTF-8" 2025-08-01 13:42:49 [Thread-1] DEBUG o.a.h.i.c.PoolingHttpClientConnectionManager - Connection [id: 0][route: {}->http://nyzbwlw.com:80] can be kept alive indefinitely 2025-08-01 13:42:49 [Thread-1] DEBUG o.a.h.i.c.DefaultManagedHttpClientConnection - http-outgoing-0: set socket timeout to 0 2025-08-01 13:42:49 [Thread-1] DEBUG o.a.h.i.c.PoolingHttpClientConnectionManager - Connection released: [id: 0][route: {}->http://nyzbwlw.com:80][total available: 1; route allocated: 1 of 2; total allocated: 1 of 20] [COOKIE] Saved session cookie: JSESSIONID [DEBUG] Response Status: 200 OK [DEBUG] Raw API response snippet: {"msg":"操作成功!","code":100,"data":{"count":0,"list":[]}} [DEBUG] Parsed API response code: 100 | message: 操作成功! [DEBUG] Found 0 valid image entries [RETRY] Retrying in 2000 ms (attempt 2/3) [DEBUG] Time Parameters: Original Local: 2025-07-01T00:00 - 2025-07-01T23:59:59 Converted Server: 2025-07-01 00:00:00 - 2025-07-01 23:59:59 Raw Time: 2025-07-01 00:00:00 & 2025-07-01 23:59:59 [DEBUG] Final request URL: http://nyzbwlw.com/situation/http/device/bug/getBugData?deviceCode=GK2025040001&startTime=2025-07-01%2000:00:00&endTime=2025-07-01%2023:59:59&size=1000&t=1754026971579 [DEBUG] Sending cookies: JSESSIONID=852AF1576F877DE9C07BC6B516B6220A 2025-08-01 13:42:51 [Thread-1] DEBUG o.s.web.client.RestTemplate - HTTP GET http://nyzbwlw.com/situation/http/device/bug/getBugData?deviceCode=GK2025040001&startTime=2025-07-01%252000:00:00&endTime=2025-07-01%252023:59:59&size=1000&t=1754026971579 2025-08-01 13:42:51 [Thread-1] DEBUG o.s.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*] 2025-08-01 13:42:51 [Thread-1] DEBUG o.a.h.c.protocol.RequestAddCookies - CookieSpec selected: default 2025-08-01 13:42:51 [Thread-1] DEBUG o.a.h.c.protocol.RequestAddCookies - Cookie [version: 0][name: JSESSIONID][value: 852AF1576F877DE9C07BC6B516B6220A][domain: nyzbwlw.com][path: /situation][expiry: null] match [nyzbwlw.com:80/situation/http/device/bug/getBugData] 2025-08-01 13:42:51 [Thread-1] DEBUG o.a.h.c.protocol.RequestAuthCache - Auth cache not set in the context 2025-08-01 13:42:51 [Thread-1] DEBUG o.a.h.i.c.PoolingHttpClientConnectionManager - Connection request: [route: {}->http://nyzbwlw.com:80][total available: 1; route allocated: 1 of 2; total allocated: 1 of 20] 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "[read] I/O error: Read timed out" 2025-08-01 13:42:51 [Thread-1] DEBUG o.a.h.i.c.PoolingHttpClientConnectionManager - Connection leased: [id: 0][route: {}->http://nyzbwlw.com:80][total available: 0; route allocated: 1 of 2; total allocated: 1 of 20] 2025-08-01 13:42:51 [Thread-1] DEBUG o.a.h.i.c.DefaultManagedHttpClientConnection - http-outgoing-0: set socket timeout to 0 2025-08-01 13:42:51 [Thread-1] DEBUG o.a.h.i.c.DefaultManagedHttpClientConnection - http-outgoing-0: set socket timeout to 15000 2025-08-01 13:42:51 [Thread-1] DEBUG o.a.h.impl.execchain.MainClientExec - Executing request GET /situation/http/device/bug/getBugData?deviceCode=GK2025040001&startTime=2025-07-01%252000:00:00&endTime=2025-07-01%252023:59:59&size=1000&t=1754026971579 HTTP/1.1 2025-08-01 13:42:51 [Thread-1] DEBUG o.a.h.impl.execchain.MainClientExec - Target auth state: UNCHALLENGED 2025-08-01 13:42:51 [Thread-1] DEBUG o.a.h.impl.execchain.MainClientExec - Proxy auth state: UNCHALLENGED 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> GET /situation/http/device/bug/getBugData?deviceCode=GK2025040001&startTime=2025-07-01%252000:00:00&endTime=2025-07-01%252023:59:59&size=1000&t=1754026971579 HTTP/1.1 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Accept: application/json, text/plain, */* 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Connection: keep-alive 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Referer: http://nyzbwlw.com/situation/ 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Origin: http://nyzbwlw.com 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Sec-Fetch-Dest: empty 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Sec-Fetch-Mode: cors 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Sec-Fetch-Site: same-origin 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Cookie: JSESSIONID=852AF1576F877DE9C07BC6B516B6220A 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Host: nyzbwlw.com 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Cookie: JSESSIONID=852AF1576F877DE9C07BC6B516B6220A 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Accept-Encoding: gzip,deflate 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "GET /situation/http/device/bug/getBugData?deviceCode=GK2025040001&startTime=2025-07-01%252000:00:00&endTime=2025-07-01%252023:59:59&size=1000&t=1754026971579 HTTP/1.1[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Accept: application/json, text/plain, */*[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Connection: keep-alive[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Referer: http://nyzbwlw.com/situation/[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Origin: http://nyzbwlw.com[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Sec-Fetch-Dest: empty[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Sec-Fetch-Mode: cors[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Sec-Fetch-Site: same-origin[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Cookie: JSESSIONID=852AF1576F877DE9C07BC6B516B6220A[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Host: nyzbwlw.com[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Cookie: JSESSIONID=852AF1576F877DE9C07BC6B516B6220A[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Accept-Encoding: gzip,deflate[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "HTTP/1.1 200 [\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Server: nginx/1.20.2[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Date: Fri, 01 Aug 2025 05:42:51 GMT[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Content-Type: application/json;charset=UTF-8[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Transfer-Encoding: chunked[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Connection: keep-alive[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Access-Control-Allow-Credentials: true[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Access-Control-Allow-Origin: *[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Vary: Origin[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Access-Control-Expose-Headers: Set-Cookie[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Access-Control-Allow-Methods: POST, GET, PUT, DELETE, OPTIONS[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "41[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "{"msg":"[0xe6][0x93][0x8d][0xe4][0xbd][0x9c][0xe6][0x88][0x90][0xe5][0x8a][0x9f][0xef][0xbc][0x81]","code":100,"data":{"count":0,"list":[]}}[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "0[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "[\r][\n]" 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << HTTP/1.1 200 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Server: nginx/1.20.2 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Date: Fri, 01 Aug 2025 05:42:51 GMT 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Content-Type: application/json;charset=UTF-8 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Transfer-Encoding: chunked 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Connection: keep-alive 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Access-Control-Allow-Credentials: true 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Access-Control-Allow-Origin: * 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Vary: Origin 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Access-Control-Expose-Headers: Set-Cookie 2025-08-01 13:42:51 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Access-Control-Allow-Methods: POST, GET, PUT, DELETE, OPTIONS 2025-08-01 13:42:51 [Thread-1] DEBUG o.a.h.impl.execchain.MainClientExec - Connection can be kept alive indefinitely 2025-08-01 13:42:51 [Thread-1] DEBUG o.s.web.client.RestTemplate - Response 200 OK 2025-08-01 13:42:51 [Thread-1] DEBUG o.s.web.client.RestTemplate - Reading to [java.lang.String] as "application/json;charset=UTF-8" 2025-08-01 13:42:51 [Thread-1] DEBUG o.a.h.i.c.PoolingHttpClientConnectionManager - Connection [id: 0][route: {}->http://nyzbwlw.com:80] can be kept alive indefinitely 2025-08-01 13:42:51 [Thread-1] DEBUG o.a.h.i.c.DefaultManagedHttpClientConnection - http-outgoing-0: set socket timeout to 0 2025-08-01 13:42:51 [Thread-1] DEBUG o.a.h.i.c.PoolingHttpClientConnectionManager - Connection released: [id: 0][route: {}->http://nyzbwlw.com:80][total available: 1; route allocated: 1 of 2; total allocated: 1 of 20] [DEBUG] Response Status: 200 OK [DEBUG] Raw API response snippet: {"msg":"操作成功!","code":100,"data":{"count":0,"list":[]}} [DEBUG] Parsed API response code: 100 | message: 操作成功! [DEBUG] Found 0 valid image entries [RETRY] Retrying in 4000 ms (attempt 3/3) [DEBUG] Time Parameters: Original Local: 2025-07-01T00:00 - 2025-07-01T23:59:59 Converted Server: 2025-07-01 00:00:00 - 2025-07-01 23:59:59 Raw Time: 2025-07-01 00:00:00 & 2025-07-01 23:59:59 [DEBUG] Final request URL: http://nyzbwlw.com/situation/http/device/bug/getBugData?deviceCode=GK2025040001&startTime=2025-07-01%2000:00:00&endTime=2025-07-01%2023:59:59&size=1000&t=1754026975716 [DEBUG] Sending cookies: JSESSIONID=852AF1576F877DE9C07BC6B516B6220A 2025-08-01 13:42:55 [Thread-1] DEBUG o.s.web.client.RestTemplate - HTTP GET http://nyzbwlw.com/situation/http/device/bug/getBugData?deviceCode=GK2025040001&startTime=2025-07-01%252000:00:00&endTime=2025-07-01%252023:59:59&size=1000&t=1754026975716 2025-08-01 13:42:55 [Thread-1] DEBUG o.s.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*] 2025-08-01 13:42:55 [Thread-1] DEBUG o.a.h.c.protocol.RequestAddCookies - CookieSpec selected: default 2025-08-01 13:42:55 [Thread-1] DEBUG o.a.h.c.protocol.RequestAddCookies - Cookie [version: 0][name: JSESSIONID][value: 852AF1576F877DE9C07BC6B516B6220A][domain: nyzbwlw.com][path: /situation][expiry: null] match [nyzbwlw.com:80/situation/http/device/bug/getBugData] 2025-08-01 13:42:55 [Thread-1] DEBUG o.a.h.c.protocol.RequestAuthCache - Auth cache not set in the context 2025-08-01 13:42:55 [Thread-1] DEBUG o.a.h.i.c.PoolingHttpClientConnectionManager - Connection request: [route: {}->http://nyzbwlw.com:80][total available: 1; route allocated: 1 of 2; total allocated: 1 of 20] 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "[read] I/O error: Read timed out" 2025-08-01 13:42:55 [Thread-1] DEBUG o.a.h.i.c.PoolingHttpClientConnectionManager - Connection leased: [id: 0][route: {}->http://nyzbwlw.com:80][total available: 0; route allocated: 1 of 2; total allocated: 1 of 20] 2025-08-01 13:42:55 [Thread-1] DEBUG o.a.h.i.c.DefaultManagedHttpClientConnection - http-outgoing-0: set socket timeout to 0 2025-08-01 13:42:55 [Thread-1] DEBUG o.a.h.i.c.DefaultManagedHttpClientConnection - http-outgoing-0: set socket timeout to 15000 2025-08-01 13:42:55 [Thread-1] DEBUG o.a.h.impl.execchain.MainClientExec - Executing request GET /situation/http/device/bug/getBugData?deviceCode=GK2025040001&startTime=2025-07-01%252000:00:00&endTime=2025-07-01%252023:59:59&size=1000&t=1754026975716 HTTP/1.1 2025-08-01 13:42:55 [Thread-1] DEBUG o.a.h.impl.execchain.MainClientExec - Target auth state: UNCHALLENGED 2025-08-01 13:42:55 [Thread-1] DEBUG o.a.h.impl.execchain.MainClientExec - Proxy auth state: UNCHALLENGED 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> GET /situation/http/device/bug/getBugData?deviceCode=GK2025040001&startTime=2025-07-01%252000:00:00&endTime=2025-07-01%252023:59:59&size=1000&t=1754026975716 HTTP/1.1 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Accept: application/json, text/plain, */* 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Connection: keep-alive 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Referer: http://nyzbwlw.com/situation/ 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Origin: http://nyzbwlw.com 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Sec-Fetch-Dest: empty 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Sec-Fetch-Mode: cors 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Sec-Fetch-Site: same-origin 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Cookie: JSESSIONID=852AF1576F877DE9C07BC6B516B6220A 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Host: nyzbwlw.com 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Cookie: JSESSIONID=852AF1576F877DE9C07BC6B516B6220A 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Accept-Encoding: gzip,deflate 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "GET /situation/http/device/bug/getBugData?deviceCode=GK2025040001&startTime=2025-07-01%252000:00:00&endTime=2025-07-01%252023:59:59&size=1000&t=1754026975716 HTTP/1.1[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Accept: application/json, text/plain, */*[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Connection: keep-alive[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Referer: http://nyzbwlw.com/situation/[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Origin: http://nyzbwlw.com[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Sec-Fetch-Dest: empty[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Sec-Fetch-Mode: cors[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Sec-Fetch-Site: same-origin[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Cookie: JSESSIONID=852AF1576F877DE9C07BC6B516B6220A[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Host: nyzbwlw.com[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Cookie: JSESSIONID=852AF1576F877DE9C07BC6B516B6220A[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Accept-Encoding: gzip,deflate[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "HTTP/1.1 200 [\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Server: nginx/1.20.2[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Date: Fri, 01 Aug 2025 05:42:55 GMT[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Content-Type: application/json;charset=UTF-8[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Transfer-Encoding: chunked[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Connection: keep-alive[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Access-Control-Allow-Credentials: true[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Access-Control-Allow-Origin: *[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Vary: Origin[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Access-Control-Expose-Headers: Set-Cookie[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Access-Control-Allow-Methods: POST, GET, PUT, DELETE, OPTIONS[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "41[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "{"msg":"[0xe6][0x93][0x8d][0xe4][0xbd][0x9c][0xe6][0x88][0x90][0xe5][0x8a][0x9f][0xef][0xbc][0x81]","code":100,"data":{"count":0,"list":[]}}[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "0[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.wire - http-outgoing-0 << "[\r][\n]" 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << HTTP/1.1 200 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Server: nginx/1.20.2 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Date: Fri, 01 Aug 2025 05:42:55 GMT 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Content-Type: application/json;charset=UTF-8 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Transfer-Encoding: chunked 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Connection: keep-alive 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Access-Control-Allow-Credentials: true 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Access-Control-Allow-Origin: * 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Vary: Origin 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Access-Control-Expose-Headers: Set-Cookie 2025-08-01 13:42:55 [Thread-1] DEBUG org.apache.http.headers - http-outgoing-0 << Access-Control-Allow-Methods: POST, GET, PUT, DELETE, OPTIONS 2025-08-01 13:42:55 [Thread-1] DEBUG o.a.h.impl.execchain.MainClientExec - Connection can be kept alive indefinitely 2025-08-01 13:42:55 [Thread-1] DEBUG o.s.web.client.RestTemplate - Response 200 OK 2025-08-01 13:42:55 [Thread-1] DEBUG o.s.web.client.RestTemplate - Reading to [java.lang.String] as "application/json;charset=UTF-8" 2025-08-01 13:42:55 [Thread-1] DEBUG o.a.h.i.c.PoolingHttpClientConnectionManager - Connection [id: 0][route: {}->http://nyzbwlw.com:80] can be kept alive indefinitely 2025-08-01 13:42:55 [Thread-1] DEBUG o.a.h.i.c.DefaultManagedHttpClientConnection - http-outgoing-0: set socket timeout to 0 2025-08-01 13:42:55 [Thread-1] DEBUG o.a.h.i.c.PoolingHttpClientConnectionManager - Connection released: [id: 0][route: {}->http://nyzbwlw.com:80][total available: 1; route allocated: 1 of 2; total allocated: 1 of 20] [DEBUG] Response Status: 200 OK [DEBUG] Raw API response snippet: {"msg":"操作成功!","code":100,"data":{"count":0,"list":[]}} [DEBUG] Parsed API response code: 100 | message: 操作成功! [DEBUG] Found 0 valid image entries [STATS] Device: GK2025040001 | Total: 0 | Success: 0 | Failed: 0 [RESULT] Device: GK2025040001 | Date: 2025-07-01 | Found: 0 | Downloaded: 0 | Failed: 0 代码:package com.example.service; import org.springframework.stereotype.Service; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.client.RestTemplate; import org.springframework.http.ResponseEntity; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.core.type.TypeReference; import java.util.*; import java.time.*; import java.time.format.DateTimeFormatter; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.io.IOException; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.PostConstruct; import com.example.model.BugApiResponse; import com.example.model.BugResponseData; import com.example.model.BugImageInfo; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.net.InetAddress; import java.util.stream.Collectors; import org.springframework.web.util.UriComponentsBuilder; import java.net.URI; @Service public class BugImageDownloadService { private static final ZoneId SERVER_ZONE = ZoneId.of("Asia/Shanghai"); private static final String API_URL = "http://nyzbwlw.com/situation/http/device/bug/getBugData"; private static final DateTimeFormatter API_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private static final DateTimeFormatter FILE_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd"); private static final DateTimeFormatter FILE_TIME_FORMAT = DateTimeFormatter.ofPattern("HH_mm_ss"); private final ConcurrentHashMap<String, LocalDateTime> deviceLastUpdateMap = new ConcurrentHashMap<>(); @Value("${bug.download.deviceCodes:GK2025040001,GK2025040002,GK2025040003,GK2025040004,GK2025040005,GK2025040006,GK2025040007,GK2025040008}") private List<String> deviceCodes; private final Map<String, String> cookies = new ConcurrentHashMap<>(); // 存储每个设备的下一个处理日期 private final ConcurrentHashMap<String, LocalDate> deviceNextDateMap = new ConcurrentHashMap<>(); @Value("${bug.image.save.path:F:/GKD/}") private String savePath; private final RestTemplate restTemplate; private final ObjectMapper objectMapper; public BugImageDownloadService(RestTemplate restTemplate, ObjectMapper objectMapper) { this.restTemplate = restTemplate; this.objectMapper = objectMapper; } @PostConstruct public void init() { // 所有设备从固定起始日期开始 LocalDate startDate = LocalDate.of(2025, 7, 1); for (String deviceCode : deviceCodes) { deviceNextDateMap.put(deviceCode, startDate); } System.out.println("[INIT] Device date tracking initialized. Start date: " + startDate); } /** * 持续下载所有设备的新图片 */ public void downloadNewImagesContinuously() { System.out.println("[TASK] ====== CONTINUOUS DOWNLOAD STARTED ======"); System.out.println("[INFO] Devices: " + deviceCodes.size() + " | Save path: " + savePath); while (true) { try { boolean allDevicesUpToDate = true; // 处理每个设备的历史数据 for (String deviceCode : deviceCodes) { LocalDate currentDate = deviceNextDateMap.get(deviceCode); LocalDate today = LocalDate.now(SERVER_ZONE); if (currentDate.isBefore(today)) { allDevicesUpToDate = false; // 处理该设备当前日期的数据 LocalDateTime startOfDay = currentDate.atStartOfDay(); LocalDateTime endOfDay = currentDate.atTime(23, 59, 59); System.out.printf("[PROCESS] Device: %s | Date: %s%n", deviceCode, currentDate); // 下载该日期的图片 int[] counts = downloadImagesForDevice( deviceCode, startOfDay, endOfDay, false // 标记为历史数据请求 ); // 更新设备的下一个处理日期(明天) deviceNextDateMap.put(deviceCode, currentDate.plusDays(1)); System.out.printf("[RESULT] Device: %s | Date: %s | Found: %d | Downloaded: %d | Failed: %d%n", deviceCode, currentDate, counts[0], counts[1], counts[2]); // 设备间处理间隔 Thread.sleep(2000); } } // 所有设备都处理到最新日期 if (allDevicesUpToDate) { System.out.println("[STATUS] All devices up-to-date. Starting real-time updates..."); // ===== 优化后的实时更新逻辑 ===== for (String deviceCode : deviceCodes) { // 获取上次更新时间(默认当天0点) LocalDateTime lastUpdate = deviceLastUpdateMap.getOrDefault( deviceCode, LocalDate.now(SERVER_ZONE).atStartOfDay() // 使用服务器时区 ); LocalDateTime now = LocalDateTime.now(); System.out.printf("[REALTIME] Device: %s | Time range: %s to %s%n", deviceCode, lastUpdate, now); // 修复点1:添加第四个参数 true 表示实时请求 int[] counts = downloadImagesForDevice( deviceCode, lastUpdate, // 从上次更新时间开始 now, true // 标记为实时请求 ); // 更新最后记录时间(当前轮询结束时间) deviceLastUpdateMap.put(deviceCode, now); System.out.printf("[REALTIME-RESULT] Device: %s | Downloaded: %d | Failed: %d%n", deviceCode, counts[1], counts[2]); // 设备间处理间隔 Thread.sleep(2000); } // ======================== // 等待4h后再次检查 System.out.println("[STATUS] Real-time round completed. Waiting 4 hours..."); Thread.sleep(14_400_000); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); System.err.println("[ERROR] Task interrupted: " + e.getMessage()); break; } catch (Exception e) { System.err.println("[ERROR] Critical error: " + e.getMessage()); try { Thread.sleep(60_000); // 错误后等待1分钟 } catch (InterruptedException ie) { Thread.currentThread().interrupt(); break; } } } } /** * 下载指定设备在指定时间范围内的图片 */ private int[] downloadImagesForDevice(String deviceCode, LocalDateTime startTime, LocalDateTime endTime, boolean isRealtime) { int maxRetries = 3; int retryCount = 0; int waitTime = 2000; // 初始等待时间 2秒 int total = 0; int success = 0; int failed = 0; while (retryCount < maxRetries) { try { // ====== 网络诊断 ====== // ... [网络诊断代码保持不变] ... // ========== 统一时区转换逻辑 ========== ZonedDateTime serverStart = startTime.atZone(ZoneId.systemDefault()) .withZoneSameInstant(SERVER_ZONE); ZonedDateTime serverEnd = endTime.atZone(ZoneId.systemDefault()) .withZoneSameInstant(SERVER_ZONE); // 格式化为API需要的字符串(不进行手动编码) String startStr = serverStart.format(API_DATE_FORMAT); String endStr = serverEnd.format(API_DATE_FORMAT); // 调试日志增强 System.out.println("[DEBUG] Time Parameters:"); System.out.println(" Original Local: " + startTime + " - " + endTime); System.out.println(" Converted Server: " + serverStart.format(API_DATE_FORMAT) + " - " + serverEnd.format(API_DATE_FORMAT)); System.out.println(" Raw Time: " + startStr + " & " + endStr); // 使用Spring的URI构建器确保正确编码 UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(API_URL) .queryParam("deviceCode", deviceCode) .queryParam("startTime", startStr) .queryParam("endTime", endStr) .queryParam("size", 1000) .queryParam("t", System.currentTimeMillis()); URI apiUri = builder.build().encode().toUri(); String apiUrl = apiUri.toString(); System.out.println("[DEBUG] Final request URL: " + apiUrl); // ====== 创建带Cookie的请求头 ====== HttpHeaders headers = new HttpHeaders(); // 添加浏览器级 User-Agent headers.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0"); // 必须添加的请求头 headers.set("Accept", "application/json, text/plain, */*"); headers.set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6"); headers.set("Connection", "keep-alive"); headers.set("Referer", "http://nyzbwlw.com/situation/"); // 关键修复:添加 Referer headers.set("Origin", "http://nyzbwlw.com"); headers.set("Sec-Fetch-Dest", "empty"); headers.set("Sec-Fetch-Mode", "cors"); headers.set("Sec-Fetch-Site", "same-origin"); // 添加 Cookie(关键修复:使用持久化 Cookie) if (!cookies.isEmpty()) { String cookieHeader = cookies.entrySet().stream() .map(entry -> entry.getKey() + "=" + entry.getValue()) .collect(Collectors.joining("; ")); headers.add("Cookie", cookieHeader); System.out.println("[DEBUG] Sending cookies: " + cookieHeader); } // 发送请求 ResponseEntity<String> response = restTemplate.exchange( apiUrl, HttpMethod.GET, new HttpEntity<>(headers), String.class); // 保存新Cookie List<String> setCookieHeaders = response.getHeaders().get("Set-Cookie"); if (setCookieHeaders != null) { for (String cookie : setCookieHeaders) { // 提取关键 Cookie(排除过期/路径信息) String[] cookieParts = cookie.split(";")[0].split("="); if (cookieParts.length >= 2) { String cookieName = cookieParts[0].trim(); String cookieValue = cookieParts[1].trim(); cookies.put(cookieName, cookieValue); System.out.println("[COOKIE] Saved session cookie: " + cookieName); } } } // 响应状态日志 System.out.println("[DEBUG] Response Status: " + response.getStatusCodeValue() + " " + response.getStatusCode().getReasonPhrase()); if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) { String responseBody = response.getBody(); System.out.println("[DEBUG] Raw API response snippet: " + responseBody.substring(0, Math.min(300, responseBody.length())) + (responseBody.length() > 300 ? "..." : "")); // 使用POJO解析响应 BugApiResponse apiResponse = objectMapper.readValue(responseBody, BugApiResponse.class); System.out.printf("[DEBUG] Parsed API response code: %d | message: %s%n", apiResponse.getCode(), apiResponse.getMsg()); if (apiResponse.getCode() == 100) { BugResponseData data = apiResponse.getData(); List<BugImageInfo> bugList = (data != null && data.getList() != null) ? data.getList() : Collections.emptyList(); total = bugList.size(); System.out.printf("[DEBUG] Found %d valid image entries%n", total); // 如果有数据,处理并返回 if (total > 0) { for (BugImageInfo bug : bugList) { String pictureUrl = bug.getPictureUrl(); String collectionTime = bug.getCollectionTime(); if (pictureUrl != null && !pictureUrl.isEmpty() && collectionTime != null && !collectionTime.isEmpty()) { System.out.printf("[PROCESS] Downloading: %s | %s%n", collectionTime, pictureUrl); if (downloadImage(deviceCode, pictureUrl, collectionTime)) { success++; } else { failed++; } } else { System.err.printf("[WARN] Invalid data: URL=%s | Time=%s%n", pictureUrl, collectionTime); failed++; } } return new int[]{total, success, failed}; } } else { System.err.println("[API-ERROR] Device: " + deviceCode + " | Code: " + apiResponse.getCode()); } } } catch (Exception e) { System.err.println("[DOWNLOAD-ERROR] Device: " + deviceCode + " | Error: " + e.getMessage()); e.printStackTrace(); } // 如果没有数据或出错,等待后重试 retryCount++; if (retryCount < maxRetries) { try { System.out.printf("[RETRY] Retrying in %d ms (attempt %d/%d)%n", waitTime, retryCount + 1, maxRetries); Thread.sleep(waitTime); waitTime *= 2; // 指数退避 } catch (InterruptedException ie) { Thread.currentThread().interrupt(); break; } } } System.out.printf("[STATS] Device: %s | Total: %d | Success: %d | Failed: %d%n", deviceCode, total, success, failed); return new int[]{total, success, failed}; } private boolean downloadImage(String deviceCode, String imageUrl, String collectionTime) { try { LocalDateTime dateTime = LocalDateTime.parse(collectionTime, API_DATE_FORMAT); // 创建保存路径 Path saveDir = Paths.get(savePath, deviceCode, dateTime.format(FILE_DATE_FORMAT)); if (!Files.exists(saveDir)) { Files.createDirectories(saveDir); } // 生成文件名 String fileName = dateTime.format(FILE_TIME_FORMAT) + ".jpg"; Path filePath = saveDir.resolve(fileName); // 跳过已存在文件 if (Files.exists(filePath)) { return true; } // 下载图片 byte[] imageBytes = restTemplate.getForObject(imageUrl, byte[].class); if (imageBytes != null && imageBytes.length > 0) { Files.write(filePath, imageBytes); return true; } } catch (Exception e) { System.err.println("[IMAGE-ERROR] URL: " + imageUrl + " | Error: " + e.getMessage()); } return false; } }解决请求http://nyzbwlw.com/situation/http/device/bug/getBugData?deviceCode=GK2025040006&startTime=2025-07-01%252000:00:00&endTime=2025-07-01%252023:59:59&size=1000&t=1754027017557这个api返回count为0,在浏览器中输入这个url返回13条有效数据{"msg":"操作成功!","code":100,"data":{"count":13,"list":[{"eqName":"GK-7","collectionTime":"2025-07-04 05:28:09","monitorId":250704052841640,"exhibitionPath":"","num":"0","pictureUrl":"https://zzoukeqi.oss-cn-beijing.aliyuncs.com/situations/bugImgs/GK2025040007/2025-07-04/250704052841622749.jpg","pictureAnalysis":{}}
最新发布
08-02
<think>根据日志,我们注意到请求的URL中时间参数被双重编码了:`startTime=2025-07-01%252000:00:00`,其中`%25`是`%`的编码,所以实际服务器收到的是`startTime=2025-07-01%2000:00:00`,而`%20`是空格的编码。但是,服务器可能期望的是没有双重编码的参数。 然而,我们之前已经使用了`UriComponentsBuilder`来构建URL,并调用了`build().encode()`,这会导致参数值被编码。但是,我们传入的`startStr`已经是格式化的时间字符串(如"2025-07-01 00:00:00"),其中包含空格。在构建URI时,`encode()`方法会对整个URI进行编码,包括将空格编码为`%20`。但是,日志中显示的是`%2520`,这意味着被编码了两次:第一次将空格变成`%20`,然后`%`又被编码成了`%25`。 为什么会出现双重编码? 原因可能是:RestTemplate内部在发送请求时,会对URI再次进行编码。因为RestTemplate默认使用`DefaultUriBuilderFactory`,其编码模式是`EncodingMode.URI_COMPONENT`,会对每个URI组件进行编码。但是,如果我们已经用`UriComponentsBuilder`编码过,再交给RestTemplate,就会导致双重编码。 解决方案: 1. 使用`UriComponentsBuilder`构建URI时,不调用`encode()`方法,而是让RestTemplate在发送请求时进行编码。 2. 或者,设置RestTemplate的UriBuilderFactory,使其不对URI进行再次编码。 这里我们选择第一种方法:不在`UriComponentsBuilder`中编码,而是直接构建URI对象,然后让RestTemplate在发送请求时进行编码(它默认会编码一次)。但是,这样仍然会导致双重编码?因为我们在构建时没有编码,RestTemplate编码一次,那么应该只有一次编码。 但日志显示双重编码,所以我们尝试第二种方法:设置RestTemplate的UriBuilderFactory,禁止其再次编码。 修改RestTemplate的配置,设置UriBuilderFactory为不编码: 在Spring Boot配置中,我们可以创建一个RestTemplate的Bean,并设置其UriBuilderFactory。 但是,由于我们的RestTemplate是通过构造函数注入的,我们需要在配置类中定义它。 由于时间关系,我们可以在`downloadImagesForDevice`方法中临时解决:使用一个不自动编码的RestTemplate?或者,我们尝试在构建URI时只进行一次编码,并告诉RestTemplate不要编码。 但是,我们无法直接修改这个RestTemplate(因为可能在其他地方使用),所以我们可以创建一个新的RestTemplate,或者修改这个RestTemplate的UriBuilderFactory。 然而,我们注意到在日志中,请求的URL是已经编码过的,但是RestTemplate在发送时又编码了一次,所以出现了双重编码。 因此,我们可以在构建URI时,不进行编码,然后设置RestTemplate不对URI进行编码。 修改RestTemplate的配置: 在创建RestTemplate的Bean时,设置UriBuilderFactory: ```java @Bean public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); // 设置URI工厂,避免双重编码 DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(); factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE); // 不进行编码 restTemplate.setUriTemplateHandler(factory); return restTemplate; } ``` 但是,这样设置后,整个RestTemplate都不对URI进行编码,那么如果URI中有特殊字符,可能会出错。 另一种方法:我们在构建URI时,使用`UriComponentsBuilder`构建成完整的字符串,然后以字符串形式传给RestTemplate,这样RestTemplate就不会再编码了(因为已经是字符串,不会处理)。但是,RestTemplate的exchange方法接受字符串URL,然后内部会进行编码。 所以,为了避免双重编码,我们可以: 1. 使用`UriComponentsBuilder`构建URI字符串,并调用`toUriString()`(不调用`encode()`),这样得到的是未编码的字符串(包含空格等)。然后交给RestTemplate,RestTemplate在发送时会自动编码一次(将空格变成%20)。 2. 或者,我们构建一个URI对象(不是字符串),然后RestTemplate会直接使用这个URI,不再编码。 我们选择第二种方法:构建URI对象,然后使用这个URI对象进行请求。 修改代码: ```java // 将原来构建字符串URL的方式改为构建URI对象 URI apiUri = builder.build().toUri(); // 注意:这里不调用encode(),因为build()方法已经根据组件的编码规则编码了?实际上,build()方法会根据需要编码,但是默认情况下,它不会对已经编码过的部分再编码。 // 然后,在exchange方法中直接使用这个URI对象 ResponseEntity<String> response = restTemplate.exchange( apiUri, HttpMethod.GET, new HttpEntity<>(headers), String.class); ``` 但是,我们之前使用的是`builder.build().encode().toUri();`,这会导致编码两次?因为`encode()`方法会对整个URI进行编码,然后`toUri()`方法会创建一个URI对象。 现在,我们改为: ```java URI apiUri = builder.build().toUri(); ``` 这样,`build()`方法会根据组件的值进行编码(一次编码),然后转换为URI对象。RestTemplate使用URI对象时,不会进行任何编码,因此只有一次编码。 这样,URL中的空格会被编码为%20,冒号保留为冒号(因为冒号在URI中允许,不需要编码)?但实际上,时间中的冒号(:)在查询参数中是需要编码的,因为查询参数中允许的字符有限。 所以,我们让`UriComponentsBuilder`在构建时进行编码,然后以URI对象形式发送,避免RestTemplate再次编码。 修改代码: ```java // 使用UriComponentsBuilder构建URI对象 UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(API_URL) .queryParam("deviceCode", deviceCode) .queryParam("startTime", startStr) // 这里startStr包含空格,需要编码 .queryParam("endTime", endStr) .queryParam("size", 1000) .queryParam("t", System.currentTimeMillis()); // 构建URI对象(这里会进行编码) URI apiUri = builder.build().encode(StandardCharsets.UTF_8).toUri(); ``` 然后,在请求中使用这个URI对象: ```java ResponseEntity<String> response = restTemplate.exchange( apiUri, HttpMethod.GET, new HttpEntity<>(headers), String.class); ``` 这样,我们只进行一次编码(由`build().encode()`完成),然后RestTemplate直接使用这个URI对象,不会再次编码。 这样,日志中应该看到正常的编码(空格变成%20,冒号变成%3A?但是,在查询参数中,冒号:是允许的,所以实际不需要编码。不过,我们交给URI构建器,它会根据规则编码)。 但是,根据日志,我们之前使用`build().encode().toUri()`然后输出URL字符串,显示为`2025-07-01%2000:00:00`(空格被编码为%20,冒号保留)。然后,RestTemplate发送时,如果使用这个URI对象,不会再次编码,所以服务器收到的是正确的。 然而,我们之前的日志中出现了双重编码,是因为我们使用了字符串形式的URL,并且RestTemplate在发送时又编码了一次。所以,我们改用URI对象应该可以避免。 修改代码: ```java // 修改构建URI的方式 UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(API_URL) .queryParam("deviceCode", deviceCode) .queryParam("startTime", startStr) .queryParam("endTime", endStr) .queryParam("size", 1000) .queryParam("t", System.currentTimeMillis()); // 构建URI(进行编码) URI apiUri = builder.encode(StandardCharsets.UTF_8).build().toUri(); String apiUrl = apiUri.toString(); // 用于日志输出 System.out.println("[DEBUG] Final request URL: " + apiUrl); // 然后使用apiUri进行请求 ``` 这样,我们既可以在日志中看到编码后的URL,又可以避免RestTemplate双重编码。 但是,请注意,`UriComponentsBuilder`的`encode()`方法会先对各个组件进行编码,然后构建。然后我们使用`build()`方法构建,再转换为URI。 在`downloadImagesForDevice`方法中,修改相关代码: ```java // 使用UriComponentsBuilder构建URI UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(API_URL) .queryParam("deviceCode", deviceCode) .queryParam("startTime", startStr) .queryParam("endTime", endStr) .queryParam("size", 1000) .queryParam("t", System.currentTimeMillis()); // 编码并构建为URI URI apiUri = builder.encode(StandardCharsets.UTF_8).build().toUri(); String apiUrl = apiUri.toString(); // 用于日志 System.out.println("[DEBUG] Final request URL: " + apiUrl); // ... 请求头设置 ... // 发送请求,使用URI对象 ResponseEntity<String> response = restTemplate.exchange( apiUri, HttpMethod.GET, new HttpEntity<>(headers), String.class); ``` 这样,应该可以解决双重编码问题。 但是,注意:`encode(StandardCharsets.UTF_8)`方法会对每个组件进行编码,包括查询参数值中的空格(编码为%20),冒号(:)不需要编码,所以保留。所以,生成的URL字符串应该是:`startTime=2025-07-01%2000:00:00`,这是正确的。 然后,RestTemplate使用URI对象发送,不会再次编码,服务器收到后可以正确解析。 另外,在日志中,我们注意到在重试时,出现了`[read] I/O error: Read timed out`,这可能是网络问题,但暂时先解决编码问题。 如果以上修改后,仍然返回count为0,则可能是其他原因,比如服务器对爬虫的限制,或者请求头还不够完整。但根据之前的日志,服务器返回了200和`{"msg":"操作成功!","code":100,"data":{"count":0,"list":[]}}`,说明请求是成功的,只是没有数据。而浏览器中同样的参数有数据,所以可能是时间参数的问题(时区?格式?)或者Cookie问题。 但是,我们之前已经处理了时区转换,并且日志中显示的时间是转换后的服务器时区(Asia/Shanghai)的时间,并且格式也正确。 另外,Cookie问题:在第一次请求时,服务器返回了Set-Cookie,我们保存了,并在后续请求中发送。但是,在日志中,我们确实发送了Cookie(JSESSIONID)。所以,Cookie应该没问题。 因此,我们重点解决双重编码问题。 修改后的代码: ```java // 在downloadImagesForDevice方法中,修改URI构建部分 UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(API_URL) .queryParam("deviceCode", deviceCode) .queryParam("startTime", startStr) .queryParam("endTime", endStr) .queryParam("size", 1000) .queryParam("t", System.currentTimeMillis()); // 编码并构建为URI URI apiUri = builder.encode(StandardCharsets.UTF_8).build().toUri(); String apiUrl = apiUri.toString(); System.out.println("[DEBUG] Final request URL: " + apiUrl); // 然后使用apiUri进行请求 ``` 然后,在请求时使用apiUri而不是apiUrl字符串。 注意:`exchange`方法有多个重载,我们使用接受URI对象的那个。 如果问题仍然存在,我们还可以尝试在构建URI时不进行编码,而是使用`build(false).toUri()`(不进行编码),然后让RestTemplate内部编码?但是RestTemplate内部默认会编码,这样还是双重编码。所以,我们禁止RestTemplate编码,但那样需要修改RestTemplate的配置。 由于我们的RestTemplate是注入的,我们可以修改它的配置。在Spring Boot的配置类中,定义RestTemplate时,设置不编码: ```java @Bean public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); DefaultUriBuilderFactory uriFactory = new DefaultUriBuilderFactory(); uriFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE); restTemplate.setUriTemplateHandler(uriFactory); return restTemplate; } ``` 然后,在代码中,我们使用已经编码好的字符串URL(使用`builder.build().toUriString()`)来请求,这样RestTemplate不会再次编码。 但是,这样设置全局可能会影响其他地方。所以,我们可以只为这个服务创建一个新的RestTemplate。 考虑到简单性,我们可以在`downloadImagesForDevice`方法内部使用一个局部的不编码的RestTemplate?但这样不太优雅。 或者,我们修改注入的RestTemplate的配置。由于这是一个服务,我们可以自己配置一个RestTemplate。 我们修改BugImageDownloadService,使用一个不编码的RestTemplate: 1. 在配置类中定义两个RestTemplate Bean?一个全局的,一个特殊的。然后注入特殊的这个。 但是,为了快速解决问题,我们在当前服务中创建一个不编码的RestTemplate实例: ```java // 在BugImageDownloadService类中,新增一个RestTemplate实例 private final RestTemplate noEncodeRestTemplate; public BugImageDownloadService(ObjectMapper objectMapper) { // 创建不编码的RestTemplate this.noEncodeRestTemplate = new RestTemplate(); DefaultUriBuilderFactory uriFactory = new DefaultUriBuilderFactory(); uriFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE); this.noEncodeRestTemplate.setUriTemplateHandler(uriFactory); this.objectMapper = objectMapper; } ``` 然后,在`downloadImagesForDevice`方法中,使用这个`noEncodeRestTemplate`来发送请求。 但是,我们注意到,在`downloadImage`方法中也使用了restTemplate,所以我们需要考虑是否也需要修改。 不过,`downloadImage`方法中下载图片的URL是完整的(包含协议和域名),并且没有查询参数,所以不需要特殊处理。 所以,我们可以在服务中使用两个RestTemplate:一个用于API请求(不编码),一个用于下载图片(用注入的,或者也用不编码的?因为下载图片的URL是直接字符串,没有查询参数,所以用哪个都可以)。 我们这里为了简化,在服务中创建一个新的RestTemplate用于API请求,而图片下载继续使用注入的restTemplate。 修改BugImageDownloadService类: ```java @Service public class BugImageDownloadService { // ... 其他代码不变 private final RestTemplate restTemplate; // 用于下载图片 private final RestTemplate apiRestTemplate; // 用于API请求,不编码 private final ObjectMapper objectMapper; public BugImageDownloadService(RestTemplate restTemplate, ObjectMapper objectMapper) { this.restTemplate = restTemplate; this.objectMapper = objectMapper; // 创建用于API请求的RestTemplate,设置不编码 this.apiRestTemplate = new RestTemplate(); DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(); factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE); this.apiRestTemplate.setUriTemplateHandler(factory); } // 在downloadImagesForDevice方法中,使用apiRestTemplate发送请求 // 修改请求部分: ResponseEntity<String> response = apiRestTemplate.exchange( apiUrl, // 这里apiUrl是已经编码好的字符串 HttpMethod.GET, new HttpEntity<>(headers), String.class); ``` 然后,在构建API请求的URL时,我们使用`UriComponentsBuilder`进行编码,并得到字符串: ```java UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(API_URL) .queryParam("deviceCode", deviceCode) .queryParam("startTime", startStr) .queryParam("endTime", endStr) .queryParam("size", 1000) .queryParam("t", System.currentTimeMillis()); String apiUrl = builder.encode(StandardCharsets.UTF_8).build().toUriString(); ``` 这样,我们手动编码一次,然后使用不编码的RestTemplate发送,就不会再编码了。 综上,我们有两种方案: 方案一:使用URI对象,避免RestTemplate编码(推荐,因为不会双重编码,且不改变RestTemplate配置) 方案二:使用不编码的RestTemplate,并手动构建编码后的URL字符串。 我们这里先尝试方案一,因为不需要额外创建RestTemplate。 修改`downloadImagesForDevice`方法中的相关代码: ```java // 使用UriComponentsBuilder构建URI对象(编码一次) UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(API_URL) .queryParam("deviceCode", deviceCode) .queryParam("startTime", startStr) .queryParam("endTime", endStr) .queryParam("size", 1000) .queryParam("t", System.currentTimeMillis()); // 构建URI对象(编码) URI apiUri = builder.encode(StandardCharsets.UTF_8).build().toUri(); String apiUrl = apiUri.toString(); // 用于日志 System.out.println("[DEBUG] Final request URL: " + apiUrl); // 发送请求,使用URI对象 ResponseEntity<String> response = restTemplate.exchange( apiUri, // 使用URI对象 HttpMethod.GET, new HttpEntity<>(headers), String.class); ``` 这样修改后,应该可以避免双重编码,从而让服务器正确解析时间参数。 如果问题解决,则继续;否则,再尝试方案二。 由于时间关系,请先尝试方案一。 注意:在构建URI时,我们调用了`builder.encode(StandardCharsets.UTF_8).build().toUri()`,这样会先对各个组件进行编码,然后构建成URI对象。 然后,RestTemplate使用这个URI对象,不会进行任何编码,所以只有一次编码。 我们期待日志中的URL显示为:`startTime=2025-07-01%2000:00:00`,而不是`%2520`。 请测试并观察日志。</think>根据日志分析,问题根源在于 **时间参数在 URL 中被错误地双重编码**,导致服务器无法正确解析请求。以下是具体的问题分析和修复方案: ### 问题根源分析 1. **双重编码问题**: - 日志显示请求 URL 中的时间参数被编码了两次: ``` startTime=2025-07-01%252000:00:00 ``` - `%2520` 是 `%20` 的二次编码(`%25` = `%`,`20` = 空格) - 服务器接收到双重编码的参数后无法正确解析时间范围 2. **RestTemplate 自动编码行为**: - RestTemplate 在发送请求时会自动对 URL 进行编码 - 使用 `UriComponentsBuilder.encode()` 会导致双重编码 ### 修复方案 修改 `downloadImagesForDevice` 方法中的时间参数处理逻辑: ```java private int[] downloadImagesForDevice(String deviceCode, LocalDateTime startTime, LocalDateTime endTime, boolean isRealtime) { // ... [其他代码不变] // ====== 修复时间参数编码 ====== // 移除手动URL编码,直接使用格式化后的字符串 String startStr = serverStart.format(API_DATE_FORMAT); String endStr = serverEnd.format(API_DATE_FORMAT); // 使用UriComponentsBuilder但不调用encode() UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(API_URL) .queryParam("deviceCode", deviceCode) .queryParam("startTime", startStr) .queryParam("endTime", endStr) .queryParam("size", 1000) .queryParam("t", System.currentTimeMillis()); // 不调用encode()方法 String apiUrl = builder.build().toUriString(); // ============================ System.out.println("[DEBUG] Final request URL: " + apiUrl); // ... [其他代码不变] } ``` ### 关键修复点说明 1. **移除双重编码**: - 不再使用 `builder.encode()` 方法 - 直接使用 `build().toUriString()` 避免二次编码 2. **时区处理优化**: ```java // 确保使用正确的时区转换 ZonedDateTime serverStart = startTime.atZone(ZoneId.systemDefault()) .withZoneSameInstant(SERVER_ZONE); ZonedDateTime serverEnd = endTime.atZone(ZoneId.systemDefault()) .withZoneSameInstant(SERVER_ZONE); ``` 3. **Cookie 管理增强**: - 确保正确处理服务器返回的 Set-Cookie 头 - 在后续请求中正确发送 Cookie ### 完整修复后的方法 ```java private int[] downloadImagesForDevice(String deviceCode, LocalDateTime startTime, LocalDateTime endTime, boolean isRealtime) { // ... [初始化代码不变] while (retryCount < maxRetries) { try { // ... [网络诊断代码不变] // ====== 修复时间参数处理 ====== ZonedDateTime serverStart = startTime.atZone(ZoneId.systemDefault()) .withZoneSameInstant(SERVER_ZONE); ZonedDateTime serverEnd = endTime.atZone(ZoneId.systemDefault()) .withZoneSameInstant(SERVER_ZONE); String startStr = serverStart.format(API_DATE_FORMAT); String endStr = serverEnd.format(API_DATE_FORMAT); // 使用UriComponentsBuilder但不调用encode() UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(API_URL) .queryParam("deviceCode", deviceCode) .queryParam("startTime", startStr) .queryParam("endTime", endStr) .queryParam("size", 1000) .queryParam("t", System.currentTimeMillis()); String apiUrl = builder.build().toUriString(); // ============================ System.out.println("[DEBUG] Final request URL: " + apiUrl); // ... [请求头设置和发送代码不变] } catch (Exception e) { // ... [异常处理不变] } } // ... [返回结果处理不变] } ``` ### 验证方法 1. 在日志中检查修复后的 URL: ``` [DEBUG] Final request URL: http://nyzbwlw.com/situation/http/device/bug/getBugData?deviceCode=GK2025040001&startTime=2025-07-01 00:00:00&endTime=2025-07-01 23:59:59&size=1000&t=1754026282881 ``` - 注意时间参数中的空格应为 `%20`(但显示为空格是因为日志自动解码) 2. 使用 Wireshark 或 Fiddler 抓包确认实际发送的请求: - 检查原始 HTTP 请求中的时间参数编码 - 确保没有双重编码现象 ### 备选方案 如果问题仍然存在,尝试强制指定编码方式: ```java // 在配置类中 @Bean public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); // 设置URI工厂避免双重编码 DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(); factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE); restTemplate.setUriTemplateHandler(factory); return restTemplate; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值