511 lines
11 KiB
Java
511 lines
11 KiB
Java
package com.imyeyu.spring;
|
||
|
||
import com.google.gson.Gson;
|
||
import com.imyeyu.java.TimiJava;
|
||
import com.imyeyu.java.bean.Language;
|
||
import com.imyeyu.java.bean.timi.TimiCode;
|
||
import com.imyeyu.java.bean.timi.TimiException;
|
||
import com.imyeyu.java.bean.timi.TimiResponse;
|
||
import com.imyeyu.java.ref.Ref;
|
||
import com.imyeyu.spring.bean.RequestRange;
|
||
import jakarta.servlet.http.Cookie;
|
||
import jakarta.servlet.http.HttpServletRequest;
|
||
import jakarta.servlet.http.HttpServletResponse;
|
||
import jakarta.servlet.http.HttpSession;
|
||
import org.slf4j.Logger;
|
||
import org.slf4j.LoggerFactory;
|
||
import org.springframework.beans.BeanWrapper;
|
||
import org.springframework.beans.BeanWrapperImpl;
|
||
import org.springframework.web.context.request.RequestContextHolder;
|
||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||
|
||
import java.beans.PropertyDescriptor;
|
||
import java.io.IOException;
|
||
import java.io.OutputStream;
|
||
import java.nio.charset.StandardCharsets;
|
||
import java.util.List;
|
||
import java.util.Locale;
|
||
|
||
/**
|
||
* TimiSpring
|
||
*
|
||
* <p>如果使用本依赖相关组件,务必让 SpringBoot 扫描本依赖的包,在 SpringApplication 上加上注解
|
||
* <pre>
|
||
* @ComponentScan({"自己扫描的包", "com.imyeyu.spring"})
|
||
* </pre>
|
||
*
|
||
* @author 夜雨
|
||
* @version 2021-11-20 17:16
|
||
*/
|
||
public class TimiSpring {
|
||
|
||
/** 版本号 */
|
||
public static final String VERSION = "0.0.1";
|
||
|
||
private static final Logger log = LoggerFactory.getLogger(TimiSpring.class);
|
||
private static final Gson GSON = new Gson();
|
||
|
||
/**
|
||
* 工具类禁止实例化
|
||
*/
|
||
private TimiSpring() {
|
||
}
|
||
|
||
/**
|
||
* 回调数据
|
||
*
|
||
* @param response 返回
|
||
* @param resp 返回结果
|
||
*/
|
||
public static void render(HttpServletResponse response, TimiResponse<?> resp) {
|
||
try {
|
||
HttpSession session = getSession();
|
||
response.setContentType("application/json;charset=UTF-8");
|
||
OutputStream out = response.getOutputStream();
|
||
out.write(GSON.toJson(resp).getBytes(StandardCharsets.UTF_8));
|
||
out.flush();
|
||
out.close();
|
||
} catch (Exception e) {
|
||
log.error("TimiSpring.render Error", e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 回调错误
|
||
*
|
||
* @param response 返回
|
||
* @param code 代码
|
||
* @param msgKey 消息映射键
|
||
*/
|
||
public static void renderError(HttpServletResponse response, TimiCode code, String msgKey) {
|
||
try {
|
||
response.setContentType("application/json;charset=UTF-8");
|
||
OutputStream out = response.getOutputStream();
|
||
out.write(GSON.toJson(code.toResponse().msg(msgKey)).getBytes(StandardCharsets.UTF_8));
|
||
out.flush();
|
||
out.close();
|
||
} catch (Exception e) {
|
||
log.error("TimiSpring.renderError error", e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取 Servlet 请求属性
|
||
*
|
||
* @return Servlet 请求属性
|
||
* @throws TimiException 请求异常
|
||
*/
|
||
public static ServletRequestAttributes getServletRequestAttributes() {
|
||
if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes sra) {
|
||
return sra;
|
||
}
|
||
throw new TimiException(TimiCode.ERROR_NPE_VARIABLE);
|
||
}
|
||
|
||
/**
|
||
* 获取 HttpServlet 请求
|
||
*
|
||
* @return HttpServlet 请求
|
||
*/
|
||
public static HttpServletRequest getRequest() {
|
||
return getServletRequestAttributes().getRequest();
|
||
}
|
||
|
||
/**
|
||
* 获取请求域名
|
||
*
|
||
* @return 请求域名
|
||
*/
|
||
public static String getDomain() {
|
||
return getRequest().getServerName();
|
||
}
|
||
|
||
/**
|
||
* 获取完整域名(含协议与端口)
|
||
*
|
||
* @return 完整域名
|
||
*/
|
||
public static String getFullDomain() {
|
||
HttpServletRequest req = getRequest();
|
||
String port = req.getServerPort() == 80 || req.getServerPort() == 443 ? "" : ":" + req.getServerPort();
|
||
return "%s://%s%s".formatted(req.getScheme(), getDomain(), port);
|
||
}
|
||
|
||
/**
|
||
* 获取请求 URL
|
||
*
|
||
* @return 请求 URL
|
||
*/
|
||
public static String getURL() {
|
||
return getRequest().getRequestURL().toString();
|
||
}
|
||
|
||
/**
|
||
* 获取请求 URI
|
||
*
|
||
* @return 请求 URI
|
||
*/
|
||
public static String getURI() {
|
||
return getRequest().getRequestURI();
|
||
}
|
||
|
||
/**
|
||
* 从 URI 指定标记开始截取
|
||
*
|
||
* @param flag 标记
|
||
* @return 截取后的 URI
|
||
*/
|
||
public static String cutURIStartAt(String flag) {
|
||
int indexOf = getURI().indexOf(flag);
|
||
TimiException.requiredTrue(-1 < indexOf, "not found flag: %s".formatted(flag));
|
||
return getURI().substring(indexOf + flag.length());
|
||
}
|
||
|
||
/**
|
||
* 获取 HttpServlet 回调
|
||
*
|
||
* @return HttpServlet 回调
|
||
*/
|
||
public static HttpServletResponse getResponse() {
|
||
return getServletRequestAttributes().getResponse();
|
||
}
|
||
|
||
/**
|
||
* 获取 Http 会话
|
||
*
|
||
* @return Http 会话
|
||
*/
|
||
public static HttpSession getSession() {
|
||
return getRequest().getSession();
|
||
}
|
||
|
||
/**
|
||
* 获取请求地理区域
|
||
*
|
||
* @return 地区
|
||
*/
|
||
public static Locale getLocale() {
|
||
return getRequest().getLocale();
|
||
}
|
||
|
||
/**
|
||
* 获取请求头属性
|
||
*
|
||
* @param key 属性键
|
||
* @return 属性值
|
||
*/
|
||
public static String getHeader(String key) {
|
||
return getRequest().getHeader(key);
|
||
}
|
||
|
||
/**
|
||
* 获取会话数据
|
||
*
|
||
* @param key 键
|
||
* @return 值
|
||
*/
|
||
public static Object getSessionAttr(String key) {
|
||
return getSession().getAttribute(key);
|
||
}
|
||
|
||
/**
|
||
* 获取会话数据
|
||
*
|
||
* @param key 键
|
||
* @return 值
|
||
*/
|
||
public static boolean hasSessionAttr(String key) {
|
||
return getSessionAttr(key) != null;
|
||
}
|
||
|
||
/**
|
||
* 获取会话数据(字符串)
|
||
*
|
||
* @param key 键
|
||
* @return 值
|
||
*/
|
||
public static String getSessionAttrAsString(String key) {
|
||
Object sessionAttr = getSessionAttr(key);
|
||
if (sessionAttr == null) {
|
||
return null;
|
||
}
|
||
return sessionAttr.toString();
|
||
}
|
||
|
||
/**
|
||
* 获取会话数据
|
||
*
|
||
* @param key 键
|
||
* @param clazz 值类型
|
||
* @param <T> 值类型
|
||
* @return 值
|
||
*/
|
||
public static <T> T getSessionAttr(String key, Class<T> clazz) {
|
||
return clazz.cast(getSessionAttr(key));
|
||
}
|
||
|
||
/**
|
||
* 设置会话数据
|
||
*
|
||
* @param key 键
|
||
* @param t 值
|
||
* @param <T> 值类型
|
||
*/
|
||
public static <T> void setSessionAttr(String key, T t) {
|
||
getSession().setAttribute(key, t);
|
||
}
|
||
|
||
/**
|
||
* 移除会话数据
|
||
*
|
||
* @param key 键
|
||
*/
|
||
public static void removeSessionAttr(String key) {
|
||
getSession().removeAttribute(key);
|
||
}
|
||
|
||
/**
|
||
* 获取请求数据
|
||
*
|
||
* @param key 键
|
||
* @return 值
|
||
*/
|
||
public static Object getRequestAttr(String key) {
|
||
return getRequest().getAttribute(key);
|
||
}
|
||
|
||
/**
|
||
* 获取请求数据
|
||
*
|
||
* @param key 键
|
||
* @return 值
|
||
*/
|
||
public static boolean hasRequestAttr(String key) {
|
||
return getRequestAttr(key) != null;
|
||
}
|
||
|
||
/**
|
||
* 获取请求数据(字符串)
|
||
*
|
||
* @param key 键
|
||
* @return 值
|
||
*/
|
||
public static String getRequestAttrAsString(String key) {
|
||
Object reqAttr = getRequestAttr(key);
|
||
if (reqAttr == null) {
|
||
return null;
|
||
}
|
||
return reqAttr.toString();
|
||
}
|
||
|
||
/**
|
||
* 获取请求数据
|
||
*
|
||
* @param key 键
|
||
* @param clazz 值类型
|
||
* @param <T> 值类型
|
||
* @return 值
|
||
*/
|
||
public static <T> T getRequestAttr(String key, Class<T> clazz) {
|
||
return clazz.cast(getRequestAttr(key));
|
||
}
|
||
|
||
/**
|
||
* 设置请求数据
|
||
*
|
||
* @param key 键
|
||
* @param t 值
|
||
* @param <T> 值类型
|
||
*/
|
||
public static <T> void setRequestAttr(String key, T t) {
|
||
getRequest().setAttribute(key, t);
|
||
}
|
||
|
||
/**
|
||
* 移除请求数据
|
||
*
|
||
* @param key 键
|
||
*/
|
||
public static void removeRequestAttr(String key) {
|
||
getRequest().removeAttribute(key);
|
||
}
|
||
|
||
/**
|
||
* 获取请求 URL 参数
|
||
*
|
||
* @param key 键
|
||
* @return 参数值
|
||
*/
|
||
public static String getRequestArg(String key) {
|
||
return getRequest().getParameter(key);
|
||
}
|
||
|
||
/**
|
||
* 获取请求 URL 参数(多值)
|
||
*
|
||
* @param key 键
|
||
* @return 参数值
|
||
*/
|
||
public static String[] getRequestArgs(String key) {
|
||
return getRequest().getParameterValues(key);
|
||
}
|
||
|
||
/**
|
||
* 添加 Cookie
|
||
*
|
||
* @param cookie Cookie
|
||
*/
|
||
public static void addCookie(Cookie cookie) {
|
||
getResponse().addCookie(cookie);
|
||
}
|
||
|
||
/**
|
||
* 添加 Cookie
|
||
*
|
||
* @param key 键
|
||
* @param value 值
|
||
*/
|
||
public static void addCookie(String key, String value) {
|
||
addCookie(new Cookie(key, value));
|
||
}
|
||
|
||
/**
|
||
* 获取 Cookie
|
||
*
|
||
* @param key 键
|
||
* @return Cookie
|
||
*/
|
||
public static Cookie getCookie(String key) {
|
||
Cookie[] cookies = getRequest().getCookies();
|
||
if (cookies == null) {
|
||
return null;
|
||
}
|
||
for (int i = 0; i < cookies.length; i++) {
|
||
if (cookies[i].getName().equals(key)) {
|
||
return cookies[i];
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 获取请求令牌,键为 Token 或 token,包括请求头和 URI
|
||
*
|
||
* @return 令牌
|
||
*/
|
||
public static String getToken() {
|
||
return TimiJava.firstNotEmpty(getHeader("Token"), getHeader("token"), getRequestArg("token"), getRequestArg("Token"));
|
||
}
|
||
|
||
/**
|
||
* 获取原始语言头
|
||
*
|
||
* @return 语言头
|
||
*/
|
||
public static String getLanguageRaw() {
|
||
return getHeader("Accept-Language");
|
||
}
|
||
|
||
/**
|
||
* 获取客户端地区语言
|
||
*
|
||
* @return 客户端地区语言
|
||
*/
|
||
public static Language.Enum getLanguage() {
|
||
String name = getRequestArg("lang");
|
||
if (TimiJava.isEmpty(name)) {
|
||
name = getLanguageRaw();
|
||
}
|
||
if (TimiJava.isNotEmpty(name)) {
|
||
List<Locale.LanguageRange> rangeList = Locale.LanguageRange.parse(name);
|
||
for (Locale.LanguageRange item : rangeList) {
|
||
if (item.getRange().contains("-")) {
|
||
name = item.getRange();
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
if (TimiJava.isNotEmpty(name)) {
|
||
name = name.replace("-", "_");
|
||
}
|
||
if (TimiJava.isEmpty(name)) { // use for not support
|
||
return Language.Enum.zh_CN;
|
||
}
|
||
return Ref.toType(Language.Enum.class, name);
|
||
}
|
||
|
||
/**
|
||
* 获取请求 IP
|
||
*
|
||
* @return 请求 IP
|
||
* @throws TimiException 服务异常
|
||
*/
|
||
public static String getRequestIP() {
|
||
String ip = getHeader("x-forwarded-for");
|
||
// nginx 转发
|
||
if (TimiJava.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
|
||
ip = getHeader("X-Forwarded-For");
|
||
}
|
||
if (TimiJava.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
|
||
ip = getHeader("X-Real-IP");
|
||
}
|
||
// 默认获取
|
||
if (TimiJava.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
|
||
ip = getRequest().getRemoteAddr();
|
||
}
|
||
// 本地 IP
|
||
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
|
||
}
|
||
|
||
/**
|
||
* 是否本地 IP
|
||
*
|
||
* @return true 为本地 IP
|
||
*/
|
||
public static boolean isLocalIP() {
|
||
return getRequestIP().startsWith("127");
|
||
}
|
||
|
||
/**
|
||
* 解析 Range 请求范围
|
||
*
|
||
* @param fileLength 文件长度
|
||
* @return 请求范围
|
||
* @throws IOException IO 异常
|
||
*/
|
||
public static RequestRange requestRange(long fileLength) throws IOException {
|
||
HttpServletResponse resp = getResponse();
|
||
|
||
String range = getRequestAttrAsString("Range");
|
||
if (range == null || !range.startsWith("bytes=")) {
|
||
return null;
|
||
}
|
||
// 处理 bytes=0-999 格式
|
||
String rangeValue = range.substring("bytes=".length());
|
||
String[] ranges = rangeValue.split("-");
|
||
TimiException.requiredTrue(2 == ranges.length, "Invalid Range format");
|
||
long start = Long.parseLong(ranges[0]);
|
||
long end = ranges[1].isEmpty() ? fileLength - 1 : Long.parseLong(ranges[1]);
|
||
// 验证范围有效性
|
||
if (start < 0 || fileLength <= end || end < start) {
|
||
resp.setHeader("Content-Range", "bytes */" + fileLength);
|
||
resp.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
|
||
return null;
|
||
}
|
||
return new RequestRange(start, end);
|
||
}
|
||
|
||
public static void copyPropertiesNotNull(Object source, Object target) {
|
||
BeanWrapper srcBean = new BeanWrapperImpl(source);
|
||
BeanWrapper targetBean = new BeanWrapperImpl(target);
|
||
|
||
for (PropertyDescriptor pd : srcBean.getPropertyDescriptors()) {
|
||
String propertyName = pd.getName();
|
||
Object srcValue = srcBean.getPropertyValue(propertyName);
|
||
if (srcValue != null && targetBean.isWritableProperty(propertyName)) {
|
||
targetBean.setPropertyValue(propertyName, srcValue);
|
||
}
|
||
}
|
||
}
|
||
}
|