package com.imyeyu.spring;
import com.imyeyu.io.IO;
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.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.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.InputStream;
import java.util.List;
import java.util.Locale;
/**
* TimiSpring
*
*
如果使用本依赖相关组件,务必让 SpringBoot 扫描本依赖的包,在 SpringApplication 上加上注解
*
* @ComponentScan({"自己扫描的包", "com.imyeyu.spring"})
*
*
* @author 夜雨
* @version 2021-11-20 17:16
*/
public class TimiSpring {
/**
* 工具类禁止实例化
*/
private TimiSpring() {
}
/**
* 获取 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 值类型
* @return 值
*/
public static T getSessionAttr(String key, Class clazz) {
return clazz.cast(getSessionAttr(key));
}
/**
* 设置会话数据
*
* @param key 键
* @param t 值
* @param 值类型
*/
public static 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 值类型
* @return 值
*/
public static T getRequestAttr(String key, Class clazz) {
return clazz.cast(getRequestAttr(key));
}
/**
* 设置请求数据
*
* @param key 键
* @param t 值
* @param 值类型
*/
public static 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 (Cookie cookie : cookies) {
if (cookie.getName().equals(key)) {
return cookie;
}
}
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 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 total 总数据量
* @return 请求范围
* @throws IOException IO 异常
*/
public static RequestRange getRequestRange(long total) 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() ? total - 1 : Long.parseLong(ranges[1]);
// 验证范围有效性
if (start < 0 || total <= end || end < start) {
resp.setHeader("Content-Range", "bytes */" + total);
resp.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
return null;
}
return new RequestRange(start, end, total);
}
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);
}
}
}
public static void responseRangeStream(InputStream stream, long total) throws IOException {
HttpServletResponse resp = getResponse();
RequestRange range = getRequestRange(total);
if (range == null) {
// 完整文件
resp.setContentLengthLong(total);
resp.setStatus(HttpServletResponse.SC_OK);
IO.toOutputStream(stream, resp.getOutputStream());
} else {
// 分片文件
resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
resp.setHeader("Content-Range", "bytes %s-%s/%s".formatted(range.getStart(), range.getEnd(), range.getTotal()));
resp.setContentLengthLong(range.getLength());
IO.toOutputStream(stream, resp.getOutputStream(), range.getStart(), range.getEnd());
resp.flushBuffer();
}
}
}