You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

461 lines
18 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package com.dsideal.gw.Handler;
import com.dsideal.gw.Bean.RetBean;
import com.dsideal.gw.GwApplication;
import com.dsideal.gw.Util.JwtUtil;
import com.dsideal.gw.Config.GatewayConfig;
import com.jfinal.handler.Handler;
import com.jfinal.kit.StrKit;
import com.jfinal.upload.MultipartRequest;
import com.jfinal.upload.UploadFile;
import com.jfinal.plugin.activerecord.Record;
import io.jsonwebtoken.Claims;
import okhttp3.*;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;
import java.util.concurrent.TimeUnit;
public class RouterHandler extends Handler {
private static final Logger logger = LoggerFactory.getLogger(RouterHandler.class);
// 常量定义
private static final class Constants {
static final String CONTENT_TYPE_JSON = "application/json";
static final String CONTENT_TYPE_MULTIPART = "multipart/form-data";
static final String HEADER_AUTHORIZATION = "Authorization";
static final String HEADER_COOKIE = "Cookie";
static final String HEADER_ACCEPT = "Accept";
static final String HEADER_CONTENT_TYPE = "Content-Type";
static final String ERROR_MESSAGE = "服务繁忙,请稍后重试";
}
// 静态资源扩展名
private static final Set<String> STATIC_EXTENSIONS = new HashSet<>(Arrays.asList(
"html", "js", "css", "png", "jpg", "jpeg", "gif", "ico", "svg",
"woff", "woff2", "ttf", "eot", "map", "json", "xml", "txt"
));
// OkHttp客户端配置
private static final OkHttpClient OK_HTTP_CLIENT = new OkHttpClient.Builder()
.connectTimeout(GatewayConfig.CONNECT_TIMEOUT, TimeUnit.MILLISECONDS)
.readTimeout(GatewayConfig.READ_TIMEOUT, TimeUnit.MILLISECONDS)
.writeTimeout(GatewayConfig.WRITE_TIMEOUT, TimeUnit.MILLISECONDS)
.connectionPool(new ConnectionPool(GatewayConfig.MAX_CONNECTIONS,
GatewayConfig.KEEP_ALIVE_DURATION,
TimeUnit.SECONDS))
.build();
@Override
public void handle(String target, HttpServletRequest req, HttpServletResponse res, boolean[] isHandled) {
try {
// 处理OPTIONS请求
if (req.getMethod().equals("OPTIONS")) {
handleOptionsRequest(res, isHandled);
return;
}
String servletPath = req.getServletPath();
String queryString = req.getQueryString();
// 处理静态资源
if (isStaticResource(servletPath)) {
next.handle(target, req, res, isHandled);
return;
}
// 处理内部调用
if (isInternalCall(servletPath, res, isHandled)) {
return;
}
// 处理认证
if (!isAuthenticated(req, res, isHandled)) {
return;
}
// 获取转发URL
String forwardUrl = getForwardUrl(servletPath);
if (forwardUrl == null) {
logger.error("无效的请求路径: {}", servletPath);
renderJson(res, new RetBean(RetBean.ERROR, Constants.ERROR_MESSAGE).toString());
isHandled[0] = true;
return;
}
// 记录请求日志
logRequest(req, forwardUrl);
// 根据请求方法处理
switch (req.getMethod()) {
case "GET":
handleGetRequest(req, res, isHandled, forwardUrl, queryString);
break;
case "POST":
if (isMultipartRequest(req)) {
handleFileUpload(req, res, isHandled, forwardUrl);
} else {
handlePostRequest(req, res, isHandled, forwardUrl, queryString);
}
break;
default:
logger.warn("不支持的请求方法: {}", req.getMethod());
renderJson(res, new RetBean(RetBean.ERROR, Constants.ERROR_MESSAGE).toString());
isHandled[0] = true;
}
} catch (Exception e) {
logger.error("请求处理异常", e);
renderJson(res, new RetBean(RetBean.ERROR, Constants.ERROR_MESSAGE).toString());
isHandled[0] = true;
}
}
// 处理OPTIONS请求
private void handleOptionsRequest(HttpServletResponse res, boolean[] isHandled) {
addCorsHeaders(res);
res.setStatus(HttpServletResponse.SC_OK);
isHandled[0] = true;
}
// 添加CORS头信息
private void addCorsHeaders(HttpServletResponse res) {
// 设置允许的源
for (String origin : GatewayConfig.ALLOWED_ORIGINS) {
if ("*".equals(origin)) {
res.setHeader("Access-Control-Allow-Origin", "*");
break;
}
}
// 设置允许的方法
res.setHeader("Access-Control-Allow-Methods", String.join(",", GatewayConfig.ALLOWED_METHODS));
// 设置允许的头信息
res.setHeader("Access-Control-Allow-Headers", String.join(",", GatewayConfig.ALLOWED_HEADERS));
// 设置是否允许发送Cookie
res.setHeader("Access-Control-Allow-Credentials", String.valueOf(GatewayConfig.ALLOW_CREDENTIALS));
// 设置预检请求的缓存时间
res.setHeader("Access-Control-Max-Age", String.valueOf(GatewayConfig.MAX_AGE));
}
// 处理GET请求
private void handleGetRequest(HttpServletRequest req, HttpServletResponse res,
boolean[] isHandled, String forwardUrl, String queryString) {
try {
Request request = buildGetRequest(req, forwardUrl, queryString);
executeRequest(request, res);
isHandled[0] = true;
} catch (Exception e) {
logger.error("GET请求处理失败: {}", forwardUrl, e);
renderJson(res, new RetBean(RetBean.ERROR, Constants.ERROR_MESSAGE).toString());
isHandled[0] = true;
}
}
// 处理POST请求
private void handlePostRequest(HttpServletRequest req, HttpServletResponse res,
boolean[] isHandled, String forwardUrl, String queryString) {
try {
Request request = buildPostRequest(req, forwardUrl, queryString);
executeRequest(request, res);
isHandled[0] = true;
} catch (Exception e) {
logger.error("POST请求处理失败: {}", forwardUrl, e);
renderJson(res, new RetBean(RetBean.ERROR, Constants.ERROR_MESSAGE).toString());
isHandled[0] = true;
}
}
// 处理文件上传
private void handleFileUpload(HttpServletRequest req, HttpServletResponse res,
boolean[] isHandled, String forwardUrl) {
try {
MultipartRequest mp = new MultipartRequest(req);
List<UploadFile> files = mp.getFiles();
if (files.isEmpty()) {
logger.error("文件上传失败: 未找到上传文件");
renderJson(res, new RetBean(RetBean.ERROR, Constants.ERROR_MESSAGE).toString());
isHandled[0] = true;
return;
}
Request request = buildFileUploadRequest(files.get(0), forwardUrl);
executeRequest(request, res);
isHandled[0] = true;
} catch (Exception e) {
logger.error("文件上传异常: {}", forwardUrl, e);
renderJson(res, new RetBean(RetBean.ERROR, Constants.ERROR_MESSAGE).toString());
isHandled[0] = true;
}
}
// 构建GET请求
private Request buildGetRequest(HttpServletRequest req, String forwardUrl, String queryString) {
Request.Builder builder = new Request.Builder()
.url(buildUrl(forwardUrl, queryString));
addCommonHeaders(req, builder);
return builder.build();
}
// 构建POST请求
private Request buildPostRequest(HttpServletRequest req, String forwardUrl, String queryString) {
Request.Builder builder = new Request.Builder()
.url(buildUrl(forwardUrl, queryString));
addCommonHeaders(req, builder);
return builder.post(createRequestBody(req)).build();
}
// 构建文件上传请求
private Request buildFileUploadRequest(UploadFile uploadFile, String forwardUrl) {
MediaType mediaType = MediaType.parse(Constants.CONTENT_TYPE_MULTIPART);
RequestBody requestBody = RequestBody.create(uploadFile.getFile(), mediaType);
MultipartBody body = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", uploadFile.getFileName(), requestBody)
.build();
return new Request.Builder()
.url(forwardUrl)
.post(body)
.build();
}
// 添加通用头信息
private void addCommonHeaders(HttpServletRequest req, Request.Builder builder) {
// 处理Authorization头
String authHeader = req.getHeader(Constants.HEADER_AUTHORIZATION);
if (!StrKit.isBlank(authHeader)) {
builder.addHeader(Constants.HEADER_AUTHORIZATION, authHeader);
builder.addHeader(Constants.HEADER_ACCEPT, "application/json;odata=verbose");
}
// 处理Cookie
javax.servlet.http.Cookie[] cookies = req.getCookies();
if (cookies != null && cookies.length > 0) {
StringBuilder cookieString = new StringBuilder();
for (Cookie cookie : cookies) {
if (cookieString.length() > 0) {
cookieString.append("; ");
}
cookieString.append(cookie.getName()).append("=").append(cookie.getValue());
}
builder.addHeader(Constants.HEADER_COOKIE, cookieString.toString());
}
// 如果还有Cookie头也添加进去
String cookieHeader = req.getHeader(Constants.HEADER_COOKIE);
if (!StrKit.isBlank(cookieHeader)) {
builder.addHeader(Constants.HEADER_COOKIE, cookieHeader);
}
// 添加其他重要头信息
addImportantHeaders(req, builder);
}
// 添加重要头信息
private void addImportantHeaders(HttpServletRequest req, Request.Builder builder) {
String[] importantHeaders = {
Constants.HEADER_ACCEPT,
Constants.HEADER_CONTENT_TYPE,
"User-Agent",
"Referer",
"Origin",
"Host"
};
for (String header : importantHeaders) {
String value = req.getHeader(header);
if (!StrKit.isBlank(value)) {
builder.addHeader(header, value);
}
}
}
// 创建请求体
private RequestBody createRequestBody(HttpServletRequest req) {
FormBody.Builder formBodyBuilder = new FormBody.Builder();
Enumeration<String> parameterNames = req.getParameterNames();
while (parameterNames.hasMoreElements()) {
String paramName = parameterNames.nextElement();
String paramValue = req.getParameter(paramName);
formBodyBuilder.add(paramName, paramValue);
}
return formBodyBuilder.build();
}
// 执行请求
private void executeRequest(Request request, HttpServletResponse res) throws IOException {
try {
Response response = OK_HTTP_CLIENT.newCall(request).execute();
if (response.isSuccessful()) {
// 处理Set-Cookie响应头
Headers headers = response.headers();
for (String name : headers.names()) {
if ("Set-Cookie".equalsIgnoreCase(name)) {
for (String value : headers.values(name)) {
res.addHeader("Set-Cookie", value);
}
}
}
ResponseBody body = response.body();
if (body != null) {
String contentType = response.header(Constants.HEADER_CONTENT_TYPE);
if (contentType != null && contentType.startsWith("image/")) {
handleImageResponse(res, body, contentType);
} else {
handleTextResponse(res, body);
}
} else {
logger.error("响应体为空: {}", request.url());
renderJson(res, new RetBean(RetBean.ERROR, Constants.ERROR_MESSAGE).toString());
}
} else {
logger.error("请求失败: {} - {}", request.url(), response.code());
renderJson(res, new RetBean(RetBean.ERROR, Constants.ERROR_MESSAGE).toString());
}
} catch (Exception e) {
logger.error("请求执行异常: {}", request.url(), e);
renderJson(res, new RetBean(RetBean.ERROR, Constants.ERROR_MESSAGE).toString());
}
}
// 处理图片响应
private void handleImageResponse(HttpServletResponse res, ResponseBody body, String contentType) throws IOException {
res.setContentType(contentType);
res.setHeader("Cache-Control", "no-store");
res.setHeader("Pragma", "no-cache");
res.setDateHeader("Expires", 0);
try (InputStream inputStream = body.byteStream()) {
IOUtils.copy(inputStream, res.getOutputStream());
}
}
// 处理文本响应
private void handleTextResponse(HttpServletResponse res, ResponseBody body) throws IOException {
String responseBody = body.string();
renderJson(res, responseBody);
}
// 渲染JSON响应
private void renderJson(HttpServletResponse res, String body) {
// 添加CORS头信息
addCorsHeaders(res);
res.setHeader("Cache-Control", "no-cache");
res.setCharacterEncoding("UTF-8");
res.setContentType(Constants.CONTENT_TYPE_JSON);
try {
res.getWriter().println(body);
res.getWriter().flush();
} catch (IOException e) {
logger.error("响应写入异常", e);
throw new RuntimeException(e);
}
}
// 检查认证
private boolean isAuthenticated(HttpServletRequest req, HttpServletResponse res, boolean[] isHandled) {
String servletPath = req.getServletPath();
if (GwApplication.whiteSet.contains(servletPath)) {
return true;
}
// 检查Session
Record rPerson = JwtUtil.getPersonInfo(req);
if (isValidPersonInfo(rPerson)) {
return true;
}
// 检查JWT
String jwtToken = req.getHeader(Constants.HEADER_AUTHORIZATION);
if (!StrKit.isBlank(jwtToken)) {
Claims claims = JwtUtil.getClaims(jwtToken);
if (claims != null) {
return true;
}
}
logger.warn("认证失败: {}", servletPath);
renderJson(res, new RetBean(RetBean.ERROR, Constants.ERROR_MESSAGE).toString());
isHandled[0] = true;
return false;
}
// 验证人员信息
private boolean isValidPersonInfo(Record rPerson) {
return rPerson != null &&
!StrKit.isBlank(rPerson.getStr("bureau_id")) &&
!StrKit.isBlank(rPerson.getStr("identity_id")) &&
!StrKit.isBlank(rPerson.getStr("person_id"));
}
// 获取转发URL
private String getForwardUrl(String servletPath) {
int xie1 = servletPath.indexOf('/');
int xie2 = servletPath.indexOf('/', xie1 + 1);
if (xie1 == -1 || xie2 == -1) {
return null;
}
String prefix = servletPath.substring(xie1 + 1, xie2);
String action = servletPath.substring(xie2 + 1);
if (!GwApplication.routeList.containsKey(prefix)) {
return null;
}
return GwApplication.routeList.get(prefix) + "/" + prefix + "/" + action;
}
// 检查是否为静态资源
private boolean isStaticResource(String servletPath) {
if (servletPath.equals("/")) {
return true;
}
int lastDotIndex = servletPath.lastIndexOf('.');
if (lastDotIndex == -1) {
return false;
}
String extension = servletPath.substring(lastDotIndex + 1).toLowerCase();
return STATIC_EXTENSIONS.contains(extension);
}
// 检查是否为内部调用
private boolean isInternalCall(String servletPath, HttpServletResponse res, boolean[] isHandled) {
if (servletPath.endsWith("/internal/")) {
logger.warn("内部接口调用: {}", servletPath);
renderJson(res, new RetBean(RetBean.ERROR, Constants.ERROR_MESSAGE).toString());
isHandled[0] = true;
return true;
}
return false;
}
// 检查是否为multipart请求
private boolean isMultipartRequest(HttpServletRequest req) {
String contentType = req.getContentType();
return contentType != null && contentType.startsWith(Constants.CONTENT_TYPE_MULTIPART);
}
// 构建URL
private String buildUrl(String baseUrl, String queryString) {
return queryString != null ? baseUrl + "?" + queryString : baseUrl;
}
// 记录请求日志
private void logRequest(HttpServletRequest req, String forwardUrl) {
logger.info("请求转发: {} {} -> {}", req.getMethod(), req.getRequestURI(), forwardUrl);
}
}