|
|
@@ -1,49 +1,132 @@
|
|
1
|
1
|
package com.ruoyi.web.controller.oa;
|
|
2
|
2
|
|
|
3
|
|
-import com.ruoyi.common.core.controller.BaseController;
|
|
4
|
|
-import com.ruoyi.common.utils.http.HttpUtils;
|
|
5
|
|
-import org.springframework.http.HttpHeaders;
|
|
6
|
|
-import org.springframework.http.MediaType;
|
|
7
|
|
-import org.springframework.http.ResponseEntity;
|
|
8
|
|
-import org.springframework.web.bind.annotation.*;
|
|
9
|
|
-
|
|
10
|
|
-import java.io.UnsupportedEncodingException;
|
|
|
3
|
+import org.springframework.web.bind.annotation.GetMapping;
|
|
|
4
|
+import org.springframework.web.bind.annotation.RestController;
|
|
|
5
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
6
|
+import java.io.BufferedReader;
|
|
|
7
|
+import java.io.IOException;
|
|
|
8
|
+import java.io.InputStream;
|
|
|
9
|
+import java.io.InputStreamReader;
|
|
|
10
|
+import java.net.HttpURLConnection;
|
|
|
11
|
+import java.net.URL;
|
|
11
|
12
|
import java.net.URLEncoder;
|
|
12
|
13
|
import java.nio.charset.StandardCharsets;
|
|
13
|
|
-
|
|
|
14
|
+import java.util.zip.GZIPInputStream;
|
|
14
|
15
|
|
|
15
|
16
|
/**
|
|
16
|
|
- * cmc招标采购Controller
|
|
17
|
|
- *
|
|
18
|
|
- * @author cmc
|
|
19
|
|
- * @date 2024-04-07
|
|
|
17
|
+ * 招投标代理接口控制器(方案2优化版)
|
|
|
18
|
+ * 核心优化:自动资源关闭、参数校验、异常处理、响应流优化、连接复用
|
|
20
|
19
|
*/
|
|
21
|
20
|
@RestController
|
|
22
|
|
-@RequestMapping("/oa/bid")
|
|
23
|
|
-public class CmcBidController extends BaseController
|
|
24
|
|
-{
|
|
|
21
|
+public class CmcBidController {
|
|
|
22
|
+
|
|
|
23
|
+ // 连接超时时间(毫秒)
|
|
|
24
|
+ private static final int CONNECT_TIMEOUT = 5000;
|
|
|
25
|
+ // 读取超时时间(毫秒)
|
|
|
26
|
+ private static final int READ_TIMEOUT = 10000;
|
|
|
27
|
+
|
|
|
28
|
+ @GetMapping("/oa/bid/list")
|
|
|
29
|
+ public void proxyUrl(String url, HttpServletResponse response) {
|
|
|
30
|
+ // 1. 参数校验(方案2核心:防非法URL、空参数)
|
|
|
31
|
+ if (url == null || url.trim().isEmpty()) {
|
|
|
32
|
+ setErrorResponse(response, "请求参数异常:URL不能为空");
|
|
|
33
|
+ return;
|
|
|
34
|
+ }
|
|
|
35
|
+ // 基础URL合法性校验(可根据实际业务补充白名单)
|
|
|
36
|
+ if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
|
|
37
|
+ setErrorResponse(response, "请求参数异常:URL必须以http/https开头");
|
|
|
38
|
+ return;
|
|
|
39
|
+ }
|
|
|
40
|
+
|
|
|
41
|
+ // 2. 响应头基础配置(方案2:强化编码和缓存控制)
|
|
|
42
|
+ response.setContentType("text/html;charset=UTF-8");
|
|
|
43
|
+ response.setCharacterEncoding("UTF-8");
|
|
|
44
|
+ // 禁止缓存,避免前端获取旧数据
|
|
|
45
|
+ response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
|
46
|
+ response.setHeader("Pragma", "no-cache");
|
|
|
47
|
+ response.setHeader("Expires", "0");
|
|
|
48
|
+
|
|
|
49
|
+ HttpURLConnection conn = null;
|
|
|
50
|
+ // 3. 资源自动关闭(方案2核心:try-with-resources 语法)
|
|
|
51
|
+ try (
|
|
|
52
|
+ // 声明需要自动关闭的资源
|
|
|
53
|
+ InputStream inputStream = getTargetUrlInputStream(url);
|
|
|
54
|
+ BufferedReader reader = new BufferedReader(
|
|
|
55
|
+ new InputStreamReader(
|
|
|
56
|
+ inputStream,
|
|
|
57
|
+ StandardCharsets.UTF_8 // 强制UTF-8,避免编码乱码
|
|
|
58
|
+ )
|
|
|
59
|
+ )
|
|
|
60
|
+ ) {
|
|
|
61
|
+ // 4. 响应流优化(方案2:批量写入,减少IO次数)
|
|
|
62
|
+ StringBuilder responseContent = new StringBuilder();
|
|
|
63
|
+ String line;
|
|
|
64
|
+ while ((line = reader.readLine()) != null) {
|
|
|
65
|
+ responseContent.append(line).append("\n");
|
|
|
66
|
+ }
|
|
|
67
|
+ // 一次性写入响应流,提升性能
|
|
|
68
|
+ response.getWriter().write(responseContent.toString());
|
|
|
69
|
+ response.getWriter().flush();
|
|
|
70
|
+
|
|
|
71
|
+ } catch (IllegalArgumentException e) {
|
|
|
72
|
+ setErrorResponse(response, "URL格式非法:" + e.getMessage());
|
|
|
73
|
+ } catch (IOException e) {
|
|
|
74
|
+ setErrorResponse(response, "网络请求异常:" + e.getMessage());
|
|
|
75
|
+ } catch (Exception e) {
|
|
|
76
|
+ setErrorResponse(response, "服务内部异常:" + e.getMessage());
|
|
|
77
|
+ } finally {
|
|
|
78
|
+ // 5. 连接关闭(方案2:确保连接释放,避免连接泄漏)
|
|
|
79
|
+ if (conn != null) {
|
|
|
80
|
+ conn.disconnect();
|
|
|
81
|
+ }
|
|
|
82
|
+ }
|
|
|
83
|
+ }
|
|
|
84
|
+
|
|
25
|
85
|
/**
|
|
26
|
|
- * 查询cmc招标采购列表
|
|
|
86
|
+ * 获取目标URL的输入流(封装连接逻辑,方案2:解耦核心逻辑)
|
|
27
|
87
|
*/
|
|
28
|
|
- @GetMapping("/list")
|
|
29
|
|
- public ResponseEntity<String> list(@RequestParam(value = "keywords", required = false) String keywords,
|
|
30
|
|
- @RequestParam(value = "type", required = false) String type,
|
|
31
|
|
- @RequestParam(value = "time", required = false) String time,
|
|
32
|
|
- @RequestParam(value = "stime", required = false) String stime,
|
|
33
|
|
- @RequestParam(value = "endtime", required = false) String endtime,
|
|
34
|
|
- @RequestParam(value = "page", required = false) String page,
|
|
35
|
|
- @RequestParam(value = "mod", required = false) String mod) throws UnsupportedEncodingException {
|
|
36
|
|
- String encodedKeywords = URLEncoder.encode(keywords == null ? "" : keywords, StandardCharsets.UTF_8.name());
|
|
37
|
|
- String html = HttpUtils.sendGet("https://search.bidcenter.com.cn/search", "type=" + (type == null ? "" : type)
|
|
38
|
|
- + "&time=" + (time == null ? "" : time)
|
|
39
|
|
- + "&stime=" + (stime == null ? "" : stime)
|
|
40
|
|
- + "&endtime=" + (endtime == null ? "" : endtime)
|
|
41
|
|
- + "&page=" + (page == null ? "" : page)
|
|
42
|
|
- + "&mod=" + (mod == null ? "0" : mod)
|
|
43
|
|
- + "&keywords=" + encodedKeywords, StandardCharsets.UTF_8.name());
|
|
44
|
|
- return ResponseEntity.ok()
|
|
45
|
|
- .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_HTML_VALUE + ";charset=UTF-8")
|
|
46
|
|
- .body(html);
|
|
|
88
|
+ private InputStream getTargetUrlInputStream(String url) throws IOException {
|
|
|
89
|
+ URL targetUrl = new URL(url);
|
|
|
90
|
+ HttpURLConnection conn = (HttpURLConnection) targetUrl.openConnection();
|
|
|
91
|
+
|
|
|
92
|
+ // 3. 连接配置优化(方案2:连接复用、请求头强化)
|
|
|
93
|
+ conn.setRequestMethod("GET");
|
|
|
94
|
+ conn.setConnectTimeout(CONNECT_TIMEOUT);
|
|
|
95
|
+ conn.setReadTimeout(READ_TIMEOUT);
|
|
|
96
|
+
|
|
|
97
|
+ // 启用HTTP连接复用(方案2核心:减少TCP握手开销)
|
|
|
98
|
+ conn.setRequestProperty("Connection", "keep-alive");
|
|
|
99
|
+ conn.setRequestProperty("Keep-Alive", "timeout=10, max=100");
|
|
|
100
|
+
|
|
|
101
|
+ // 模拟浏览器请求头(强化版,降低拦截概率)
|
|
|
102
|
+ conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
|
|
|
103
|
+ conn.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,*/*;q=0.8");
|
|
|
104
|
+ conn.setRequestProperty("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8");
|
|
|
105
|
+ conn.setRequestProperty("Accept-Encoding", "gzip, deflate");
|
|
|
106
|
+
|
|
|
107
|
+ // Cookie配置(建议从配置文件/缓存获取,方案2:避免硬编码)
|
|
|
108
|
+ conn.setRequestProperty("Cookie", "bidguid=cad034bd-ad01-4dce-a4ca-1a1252f0dbf1; Hm_lvt_9954aa2d605277c3e24cb76809e2f856=1775527126,1775629118,1775700867; HMACCOUNT=68D47FA6AE336EBB; Hm_lpvt_9954aa2d605277c3e24cb76809e2f856=1775703005");
|
|
|
109
|
+
|
|
|
110
|
+ // 4. GZIP解压(方案2:兼容更多编码场景)
|
|
|
111
|
+ InputStream inputStream = conn.getInputStream();
|
|
|
112
|
+ String contentEncoding = conn.getContentEncoding();
|
|
|
113
|
+ if (contentEncoding != null && "gzip".equalsIgnoreCase(contentEncoding.trim())) {
|
|
|
114
|
+ inputStream = new GZIPInputStream(inputStream);
|
|
|
115
|
+ }
|
|
|
116
|
+ return inputStream;
|
|
47
|
117
|
}
|
|
48
|
118
|
|
|
49
|
|
-}
|
|
|
119
|
+ /**
|
|
|
120
|
+ * 统一异常响应处理(方案2核心:标准化错误返回)
|
|
|
121
|
+ */
|
|
|
122
|
+ private void setErrorResponse(HttpServletResponse response, String message) {
|
|
|
123
|
+ try {
|
|
|
124
|
+ response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
|
|
125
|
+ response.getWriter().write("<html><body><h3>请求失败</h3><p>" + message + "</p></body></html>");
|
|
|
126
|
+ response.getWriter().flush();
|
|
|
127
|
+ } catch (IOException e) {
|
|
|
128
|
+ // 记录日志(建议接入项目日志框架,如logback/log4j2)
|
|
|
129
|
+ System.err.println("异常响应写入失败:" + e.getMessage());
|
|
|
130
|
+ }
|
|
|
131
|
+ }
|
|
|
132
|
+}
|