Compare commits
26 Commits
c27146aa91
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 08aab8d5a9 | |||
| f887079a62 | |||
| 3283c678db | |||
| 3eb6bd7df5 | |||
| 6a57d22366 | |||
| 007253f828 | |||
| d1728955aa | |||
| 1a81ac1c54 | |||
| 838c6cd6a4 | |||
| 39dd976820 | |||
| 2e67e4086d | |||
| 4de03cf60a | |||
| 9bcf17a118 | |||
| e08a50a9b2 | |||
| 945a2c5e9d | |||
| 1688666dca | |||
| 278bf7c59a | |||
| 8a7946ce01 | |||
| f2689ab812 | |||
| 3ae1ccedb7 | |||
| 8de027e0c7 | |||
| 69d847f337 | |||
| 443757f501 | |||
| 2fc06e3851 | |||
| 831d36e095 | |||
| 39f628e71a |
57
pom.xml
57
pom.xml
@ -28,17 +28,60 @@
|
|||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-deploy-plugin</artifactId>
|
||||||
<version>3.11.0</version>
|
<version>3.1.3</version>
|
||||||
<configuration>
|
</plugin>
|
||||||
<source>21</source>
|
<plugin>
|
||||||
<target>21</target>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<encoding>UTF-8</encoding>
|
<artifactId>maven-source-plugin</artifactId>
|
||||||
</configuration>
|
<version>3.3.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>attach-sources</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>jar-no-fork</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
|
<version>3.11.2</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>attach-javadocs</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>jar</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
<distributionManagement>
|
||||||
|
<repository>
|
||||||
|
<id>timi_nexus</id>
|
||||||
|
<url>https://nexus.imyeyu.com/repository/maven-releases/</url>
|
||||||
|
</repository>
|
||||||
|
</distributionManagement>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>timi_nexus</id>
|
||||||
|
<url>https://nexus.imyeyu.com/repository/maven-public/</url>
|
||||||
|
<releases>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</releases>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import com.imyeyu.java.bean.timi.TimiCode;
|
|||||||
import com.imyeyu.java.bean.timi.TimiException;
|
import com.imyeyu.java.bean.timi.TimiException;
|
||||||
import com.imyeyu.java.bean.timi.TimiResponse;
|
import com.imyeyu.java.bean.timi.TimiResponse;
|
||||||
import com.imyeyu.java.ref.Ref;
|
import com.imyeyu.java.ref.Ref;
|
||||||
|
import com.imyeyu.spring.bean.RequestRange;
|
||||||
import jakarta.servlet.http.Cookie;
|
import jakarta.servlet.http.Cookie;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
@ -16,8 +17,10 @@ import org.slf4j.LoggerFactory;
|
|||||||
import org.springframework.web.context.request.RequestContextHolder;
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -117,6 +120,12 @@ public class TimiSpring {
|
|||||||
return getRequest().getRequestURI();
|
return getRequest().getRequestURI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 回调
|
* 获取 HttpServlet 回调
|
||||||
*
|
*
|
||||||
@ -286,6 +295,26 @@ public class TimiSpring {
|
|||||||
getRequest().removeAttribute(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);
|
||||||
|
}
|
||||||
|
|
||||||
public static void addCookie(Cookie cookie) {
|
public static void addCookie(Cookie cookie) {
|
||||||
getResponse().addCookie(cookie);
|
getResponse().addCookie(cookie);
|
||||||
}
|
}
|
||||||
@ -308,12 +337,16 @@ public class TimiSpring {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取请求头的令牌,键为 Token
|
* 获取请求令牌,键为 Token 或 token,包括请求头和 URI
|
||||||
*
|
*
|
||||||
* @return 令牌
|
* @return 令牌
|
||||||
*/
|
*/
|
||||||
public static String getToken() {
|
public static String getToken() {
|
||||||
return getHeader("Token");
|
return TimiJava.firstNotEmpty(getHeader("Token"), getHeader("token"), getRequestArg("token"), getRequestArg("Token"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getLanguageRaw() {
|
||||||
|
return getHeader("Accept-Language");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -321,9 +354,18 @@ public class TimiSpring {
|
|||||||
* @return 客户端地区语言
|
* @return 客户端地区语言
|
||||||
*/
|
*/
|
||||||
public static Language getLanguage() {
|
public static Language getLanguage() {
|
||||||
String name = TimiSpring.getHeader("Language");
|
String name = getRequestArg("lang");
|
||||||
if (TimiJava.isEmpty(name)) {
|
if (TimiJava.isEmpty(name)) {
|
||||||
name = TimiSpring.getLocale().toString();
|
List<Locale.LanguageRange> rangeList = Locale.LanguageRange.parse(getLanguageRaw());
|
||||||
|
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
|
if (TimiJava.isEmpty(name)) { // use for not support
|
||||||
return Language.zh_CN;
|
return Language.zh_CN;
|
||||||
@ -357,4 +399,26 @@ public class TimiSpring {
|
|||||||
public static boolean isLocalIP() {
|
public static boolean isLocalIP() {
|
||||||
return getRequestIP().startsWith("127");
|
return getRequestIP().startsWith("127");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
src/main/java/com/imyeyu/spring/annotation/CaptchaValid.java
Normal file
17
src/main/java/com/imyeyu/spring/annotation/CaptchaValid.java
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package com.imyeyu.spring.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图形验证码校验注解
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2023-07-15 10:09
|
||||||
|
*/
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface CaptchaValid {
|
||||||
|
}
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
package com.imyeyu.spring.annotation;
|
||||||
|
|
||||||
|
import com.imyeyu.spring.bean.CaptchaData;
|
||||||
|
import org.aspectj.lang.JoinPoint;
|
||||||
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.aspectj.lang.annotation.Before;
|
||||||
|
import org.aspectj.lang.annotation.Pointcut;
|
||||||
|
import org.aspectj.lang.reflect.MethodSignature;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图形验证码校验注解处理器
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2023-07-15 10:01
|
||||||
|
*/
|
||||||
|
@Aspect
|
||||||
|
public abstract class CaptchaValidAbstractInterceptor {
|
||||||
|
|
||||||
|
private boolean enable = true;
|
||||||
|
|
||||||
|
/** 注入注解 */
|
||||||
|
@Pointcut("@annotation(com.imyeyu.spring.annotation.CaptchaValid)")
|
||||||
|
public void captchaPointCut() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行前
|
||||||
|
*
|
||||||
|
* @param joinPoint 切入点
|
||||||
|
*/
|
||||||
|
@Before("captchaPointCut()")
|
||||||
|
public void doBefore(JoinPoint joinPoint) {
|
||||||
|
if (!enable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (joinPoint.getSignature() instanceof MethodSignature ms) {
|
||||||
|
Object[] args = joinPoint.getArgs();
|
||||||
|
for (int i = 0; i < args.length; i++) {
|
||||||
|
if (args[i] instanceof CaptchaData<?> captchaData) {
|
||||||
|
// 校验请求参数的验证码
|
||||||
|
verify(captchaData.getCaptchaId(), captchaData.getCaptcha());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void verify(String captchaId, String captcha);
|
||||||
|
|
||||||
|
public void enable() {
|
||||||
|
enable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disable() {
|
||||||
|
enable = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +1,6 @@
|
|||||||
package com.imyeyu.spring.bean;
|
package com.imyeyu.spring.bean;
|
||||||
|
|
||||||
import jakarta.validation.Valid;
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.NotNull;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 含验证码数据实体
|
* 含验证码数据实体
|
||||||
@ -14,23 +12,21 @@ public class CaptchaData<T> {
|
|||||||
|
|
||||||
/** 来源 */
|
/** 来源 */
|
||||||
@NotBlank(message = "timijava.code.request_bad")
|
@NotBlank(message = "timijava.code.request_bad")
|
||||||
protected String from;
|
protected String captchaId;
|
||||||
|
|
||||||
/** 验证码 */
|
/** 验证码 */
|
||||||
@NotBlank(message = "captcha.miss")
|
@NotBlank(message = "captcha.miss")
|
||||||
protected String captcha;
|
protected String captcha;
|
||||||
|
|
||||||
/** 数据体 */
|
/** 数据体 */
|
||||||
@Valid
|
|
||||||
@NotNull
|
|
||||||
protected T data;
|
protected T data;
|
||||||
|
|
||||||
public String getFrom() {
|
public String getCaptchaId() {
|
||||||
return from;
|
return captchaId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFrom(String from) {
|
public void setCaptchaId(String captchaId) {
|
||||||
this.from = from;
|
this.captchaId = captchaId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCaptcha() {
|
public String getCaptcha() {
|
||||||
|
|||||||
105
src/main/java/com/imyeyu/spring/bean/Multilingual.java
Normal file
105
src/main/java/com/imyeyu/spring/bean/Multilingual.java
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package com.imyeyu.spring.bean;
|
||||||
|
|
||||||
|
import com.imyeyu.java.ref.Ref;
|
||||||
|
import com.imyeyu.spring.entity.UUIDEntity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2025-10-17 15:21
|
||||||
|
*/
|
||||||
|
public class Multilingual extends UUIDEntity {
|
||||||
|
|
||||||
|
protected String key;
|
||||||
|
|
||||||
|
protected String zhCN;
|
||||||
|
|
||||||
|
protected String zhTW;
|
||||||
|
|
||||||
|
protected String enUS;
|
||||||
|
|
||||||
|
protected String ruRU;
|
||||||
|
|
||||||
|
protected String koKR;
|
||||||
|
|
||||||
|
protected String jaJP;
|
||||||
|
|
||||||
|
protected String deDE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定语言值
|
||||||
|
*
|
||||||
|
* @param language 指定语言
|
||||||
|
* @return 值
|
||||||
|
*/
|
||||||
|
public String getValue(com.imyeyu.java.bean.Language language) {
|
||||||
|
try {
|
||||||
|
return Ref.getFieldValue(this, language.toString().replace("_", ""), String.class);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKey(String key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getZhCN() {
|
||||||
|
return zhCN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setZhCN(String zhCN) {
|
||||||
|
this.zhCN = zhCN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getZhTW() {
|
||||||
|
return zhTW;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setZhTW(String zhTW) {
|
||||||
|
this.zhTW = zhTW;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEnUS() {
|
||||||
|
return enUS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnUS(String enUS) {
|
||||||
|
this.enUS = enUS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRuRU() {
|
||||||
|
return ruRU;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRuRU(String ruRU) {
|
||||||
|
this.ruRU = ruRU;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKoKR() {
|
||||||
|
return koKR;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKoKR(String koKR) {
|
||||||
|
this.koKR = koKR;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getJaJP() {
|
||||||
|
return jaJP;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJaJP(String jaJP) {
|
||||||
|
this.jaJP = jaJP;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDeDE() {
|
||||||
|
return deDE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeDE(String deDE) {
|
||||||
|
this.deDE = deDE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +1,9 @@
|
|||||||
package com.imyeyu.spring.bean;
|
package com.imyeyu.spring.bean;
|
||||||
|
|
||||||
import jakarta.validation.constraints.Max;
|
import com.imyeyu.java.TimiJava;
|
||||||
import jakarta.validation.constraints.Min;
|
import com.imyeyu.java.bean.BasePage;
|
||||||
import com.imyeyu.spring.mapper.BaseMapper;
|
import com.imyeyu.spring.mapper.BaseMapper;
|
||||||
|
import com.imyeyu.utils.Text;
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
|
||||||
@ -12,18 +13,7 @@ import java.util.LinkedHashMap;
|
|||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2023-06-02 14:47
|
* @version 2023-06-02 14:47
|
||||||
*/
|
*/
|
||||||
public class Page {
|
public class Page extends BasePage {
|
||||||
|
|
||||||
/** 下标 */
|
|
||||||
@Min(value = 0, message = "page.min_index")
|
|
||||||
protected int index = 0;
|
|
||||||
|
|
||||||
/** 数据量 */
|
|
||||||
@Max(value = 64, message = "page.max_size")
|
|
||||||
protected int size = 16;
|
|
||||||
|
|
||||||
/** 关键字 */
|
|
||||||
protected String keyword;
|
|
||||||
|
|
||||||
protected LinkedHashMap<String, BaseMapper.OrderType> orderMap;
|
protected LinkedHashMap<String, BaseMapper.OrderType> orderMap;
|
||||||
|
|
||||||
@ -31,8 +21,7 @@ public class Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Page(int index, int size) {
|
public Page(int index, int size) {
|
||||||
this.index = index;
|
super(index, size);
|
||||||
this.size = size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getOffset() {
|
public long getOffset() {
|
||||||
@ -43,30 +32,6 @@ public class Page {
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getIndex() {
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIndex(int index) {
|
|
||||||
this.index = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSize() {
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSize(int size) {
|
|
||||||
this.size = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getKeyword() {
|
|
||||||
return keyword;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setKeyword(String keyword) {
|
|
||||||
this.keyword = keyword;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LinkedHashMap<String, BaseMapper.OrderType> getOrderMap() {
|
public LinkedHashMap<String, BaseMapper.OrderType> getOrderMap() {
|
||||||
return orderMap;
|
return orderMap;
|
||||||
}
|
}
|
||||||
@ -75,8 +40,13 @@ public class Page {
|
|||||||
this.orderMap = orderMap;
|
this.orderMap = orderMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addOrder(String field, BaseMapper.OrderType orderType) {
|
||||||
|
orderMap = TimiJava.firstNotNull(orderMap, new LinkedHashMap<>());
|
||||||
|
orderMap.put(Text.camelCase2underscore(field), orderType);
|
||||||
|
}
|
||||||
|
|
||||||
public static <T, P extends Page, R extends PageResult<T>> R toResult(BaseMapper<T, ?> pageMapper, P page, R result) {
|
public static <T, P extends Page, R extends PageResult<T>> R toResult(BaseMapper<T, ?> pageMapper, P page, R result) {
|
||||||
result.setList(pageMapper.list(page.getOffset(), page.getLimit()));
|
result.setList(pageMapper.listOrder(page.getOffset(), page.getLimit(), page.getOrderMap()));
|
||||||
result.setTotal(pageMapper.count());
|
result.setTotal(pageMapper.count());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
package com.imyeyu.spring.bean;
|
package com.imyeyu.spring.bean;
|
||||||
|
|
||||||
import com.imyeyu.java.TimiJava;
|
import com.imyeyu.java.bean.BasePageResult;
|
||||||
import com.imyeyu.utils.Calc;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 抽象页面查询结果
|
* 抽象页面查询结果
|
||||||
@ -11,49 +8,5 @@ import java.util.List;
|
|||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2023-06-02 14:47
|
* @version 2023-06-02 14:47
|
||||||
*/
|
*/
|
||||||
public class PageResult<T> {
|
public class PageResult<T> extends BasePageResult<T> {
|
||||||
|
|
||||||
/** 总数据量 */
|
|
||||||
protected long total;
|
|
||||||
|
|
||||||
/** 总页数 */
|
|
||||||
protected int pages;
|
|
||||||
|
|
||||||
protected List<T> list;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取总数据量
|
|
||||||
*
|
|
||||||
* @return 总数据量
|
|
||||||
*/
|
|
||||||
public long getTotal() {
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置总数据量
|
|
||||||
*
|
|
||||||
* @param total 总数据量
|
|
||||||
*/
|
|
||||||
public void setTotal(long total) {
|
|
||||||
this.total = total;
|
|
||||||
if (TimiJava.isNotEmpty(list)) {
|
|
||||||
pages = Calc.ceil(1D * total / list.size());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<T> getList() {
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setList(List<T> list) {
|
|
||||||
this.list = list;
|
|
||||||
if (TimiJava.isNotEmpty(list)) {
|
|
||||||
pages = Calc.ceil(1D * total / list.size());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPages() {
|
|
||||||
return pages;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
39
src/main/java/com/imyeyu/spring/bean/RequestRange.java
Normal file
39
src/main/java/com/imyeyu/spring/bean/RequestRange.java
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package com.imyeyu.spring.bean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2025-07-14 17:09
|
||||||
|
*/
|
||||||
|
public class RequestRange {
|
||||||
|
|
||||||
|
private long start;
|
||||||
|
|
||||||
|
private long end;
|
||||||
|
|
||||||
|
private long length;
|
||||||
|
|
||||||
|
public RequestRange(long start, long end) {
|
||||||
|
this.start = start;
|
||||||
|
this.end = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getStart() {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStart(long start) {
|
||||||
|
this.start = start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getEnd() {
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnd(long end) {
|
||||||
|
this.end = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLength() {
|
||||||
|
return end - start + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
57
src/main/java/com/imyeyu/spring/handler/GsonHandler.java
Normal file
57
src/main/java/com/imyeyu/spring/handler/GsonHandler.java
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package com.imyeyu.spring.handler;
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.imyeyu.java.TimiJava;
|
||||||
|
import org.apache.ibatis.type.BaseTypeHandler;
|
||||||
|
import org.apache.ibatis.type.JdbcType;
|
||||||
|
|
||||||
|
import java.sql.CallableStatement;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MySQL JSON 数据类型处理器
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2021-07-04 09:36
|
||||||
|
*/
|
||||||
|
public class GsonHandler extends BaseTypeHandler<JsonElement> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNonNullParameter(PreparedStatement ps, int i, JsonElement parameter, JdbcType jdbcType) throws SQLException {
|
||||||
|
ps.setString(i, parameter.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonElement getNullableResult(ResultSet rs, String columnName) throws SQLException {
|
||||||
|
return toElement(rs.getString(columnName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonElement getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
|
||||||
|
return toElement(rs.getString(columnIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonElement getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
|
||||||
|
return toElement(cs.getNString(columnIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonElement toElement(String json) {
|
||||||
|
if (TimiJava.isNotEmpty(json)) {
|
||||||
|
JsonElement el = JsonParser.parseString(json);
|
||||||
|
if (el.isJsonObject()) {
|
||||||
|
return el.getAsJsonObject();
|
||||||
|
}
|
||||||
|
if (el.isJsonArray()) {
|
||||||
|
return el.getAsJsonArray();
|
||||||
|
}
|
||||||
|
if (el.isJsonPrimitive()) {
|
||||||
|
return el.getAsJsonPrimitive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,6 +8,7 @@ import org.apache.ibatis.annotations.SelectProvider;
|
|||||||
import org.apache.ibatis.annotations.UpdateProvider;
|
import org.apache.ibatis.annotations.UpdateProvider;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基本 SQL 映射,子接口可以不实现
|
* 基本 SQL 映射,子接口可以不实现
|
||||||
@ -43,8 +44,13 @@ public interface BaseMapper<T, P> {
|
|||||||
*
|
*
|
||||||
* @return 数据量
|
* @return 数据量
|
||||||
*/
|
*/
|
||||||
|
@SelectProvider(type = SQLProvider.class, method = "count")
|
||||||
long count();
|
long count();
|
||||||
|
|
||||||
|
default List<T> list(long offset, int limit) {
|
||||||
|
return listOrder(offset, limit, null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取部分数据
|
* 获取部分数据
|
||||||
*
|
*
|
||||||
@ -52,7 +58,11 @@ public interface BaseMapper<T, P> {
|
|||||||
* @param limit 数据量
|
* @param limit 数据量
|
||||||
* @return 数据列表
|
* @return 数据列表
|
||||||
*/
|
*/
|
||||||
List<T> list(long offset, int limit);
|
@SelectProvider(type = SQLProvider.class, method = "listOrder")
|
||||||
|
List<T> listOrder(long offset, int limit, Map<String, OrderType> orderMap);
|
||||||
|
|
||||||
|
@SelectProvider(type = SQLProvider.class, method = "listAll")
|
||||||
|
List<T> listAll();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建数据。默认自增主键为 id,如需修改请重写此接口
|
* 创建数据。默认自增主键为 id,如需修改请重写此接口
|
||||||
@ -86,6 +96,9 @@ public interface BaseMapper<T, P> {
|
|||||||
@UpdateProvider(type = SQLProvider.class, method = "update")
|
@UpdateProvider(type = SQLProvider.class, method = "update")
|
||||||
void update(T t);
|
void update(T t);
|
||||||
|
|
||||||
|
@UpdateProvider(type = SQLProvider.class, method = "updateSelective")
|
||||||
|
void updateSelective(T t);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 软删除
|
* 软删除
|
||||||
*
|
*
|
||||||
|
|||||||
@ -47,7 +47,7 @@ public abstract class AbstractEntityService<T, P> implements BaseService<T, P> {
|
|||||||
|
|
||||||
public void update(T t) {
|
public void update(T t) {
|
||||||
checkMapper();
|
checkMapper();
|
||||||
baseMapper.update(t);
|
baseMapper.updateSelective(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void delete(P p) {
|
public void delete(P p) {
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
package com.imyeyu.spring.util;
|
package com.imyeyu.spring.util;
|
||||||
|
|
||||||
import jakarta.servlet.ServletException;
|
|
||||||
import jakarta.validation.ValidationException;
|
|
||||||
import com.imyeyu.java.bean.timi.TimiCode;
|
import com.imyeyu.java.bean.timi.TimiCode;
|
||||||
import com.imyeyu.java.bean.timi.TimiException;
|
import com.imyeyu.java.bean.timi.TimiException;
|
||||||
import com.imyeyu.java.bean.timi.TimiResponse;
|
import com.imyeyu.java.bean.timi.TimiResponse;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.validation.ValidationException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.TypeMismatchException;
|
import org.springframework.beans.TypeMismatchException;
|
||||||
@ -41,7 +41,7 @@ public class GlobalExceptionHandler {
|
|||||||
if (env.contains("dev") || log.isDebugEnabled()) {
|
if (env.contains("dev") || log.isDebugEnabled()) {
|
||||||
log.error("conversion error", e);
|
log.error("conversion error", e);
|
||||||
}
|
}
|
||||||
return new TimiResponse<>(TimiCode.ARG_BAD);
|
return new TimiResponse<>(TimiCode.ARG_BAD).msgKey("invalid.body");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,7 +56,7 @@ public class GlobalExceptionHandler {
|
|||||||
if (env.contains("dev") || log.isDebugEnabled()) {
|
if (env.contains("dev") || log.isDebugEnabled()) {
|
||||||
log.error("header error", e);
|
log.error("header error", e);
|
||||||
}
|
}
|
||||||
return new TimiResponse<>(TimiCode.REQUEST_BAD);
|
return new TimiResponse<>(TimiCode.REQUEST_BAD).msgKey("invalid.request");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -71,13 +71,13 @@ public class GlobalExceptionHandler {
|
|||||||
log.warn("request error", e);
|
log.warn("request error", e);
|
||||||
FieldError error = subE.getBindingResult().getFieldError();
|
FieldError error = subE.getBindingResult().getFieldError();
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
return new TimiResponse<>(TimiCode.ARG_BAD, error.getDefaultMessage());
|
return new TimiResponse<>(TimiCode.ARG_BAD, "[%s] %s".formatted(error.getField(), error.getDefaultMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (env.startsWith("dev") || log.isDebugEnabled()) {
|
if (env.startsWith("dev") || log.isDebugEnabled()) {
|
||||||
log.error("request error", e);
|
log.error("request error", e);
|
||||||
}
|
}
|
||||||
return new TimiResponse<>(TimiCode.REQUEST_BAD);
|
return new TimiResponse<>(TimiCode.REQUEST_BAD).msgKey("invalid.arg");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -89,8 +89,7 @@ public class GlobalExceptionHandler {
|
|||||||
@ExceptionHandler(Throwable.class)
|
@ExceptionHandler(Throwable.class)
|
||||||
public TimiResponse<?> error(Throwable e) {
|
public TimiResponse<?> error(Throwable e) {
|
||||||
if (e instanceof TimiException timiE) {
|
if (e instanceof TimiException timiE) {
|
||||||
// TODO 400 以下即使是开发环境也不算异常
|
if (!env.startsWith("prod") || log.isDebugEnabled()) {
|
||||||
if (env.startsWith("dev") || log.isDebugEnabled()) {
|
|
||||||
log.error(timiE.getMessage(), e);
|
log.error(timiE.getMessage(), e);
|
||||||
}
|
}
|
||||||
// 一般异常
|
// 一般异常
|
||||||
@ -98,6 +97,6 @@ public class GlobalExceptionHandler {
|
|||||||
}
|
}
|
||||||
// 致命异常
|
// 致命异常
|
||||||
log.error("fatal error", e);
|
log.error("fatal error", e);
|
||||||
return new TimiResponse<>(TimiCode.ERROR);
|
return new TimiResponse<>(TimiCode.ERROR).msgKey("service.error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package com.imyeyu.spring.util;
|
|||||||
|
|
||||||
import com.imyeyu.java.TimiJava;
|
import com.imyeyu.java.TimiJava;
|
||||||
import com.imyeyu.java.bean.CallbackArgReturn;
|
import com.imyeyu.java.bean.CallbackArgReturn;
|
||||||
|
import com.imyeyu.java.bean.LanguageMsgMapping;
|
||||||
import com.imyeyu.java.bean.timi.TimiCode;
|
import com.imyeyu.java.bean.timi.TimiCode;
|
||||||
import com.imyeyu.java.bean.timi.TimiResponse;
|
import com.imyeyu.java.bean.timi.TimiResponse;
|
||||||
import com.imyeyu.spring.TimiSpring;
|
import com.imyeyu.spring.TimiSpring;
|
||||||
@ -31,7 +32,7 @@ public class GlobalReturnHandler implements ResponseBodyAdvice<Object> {
|
|||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(GlobalReturnHandler.class);
|
private static final Logger log = LoggerFactory.getLogger(GlobalReturnHandler.class);
|
||||||
|
|
||||||
private CallbackArgReturn<String, String> multilingualHeader;
|
private CallbackArgReturn<LanguageMsgMapping<?>, String> multilingualHeader;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supports(@NonNull MethodParameter returnType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
|
public boolean supports(@NonNull MethodParameter returnType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
|
||||||
@ -54,22 +55,27 @@ public class GlobalReturnHandler implements ResponseBodyAdvice<Object> {
|
|||||||
} else {
|
} else {
|
||||||
result = new TimiResponse<>(TimiCode.SUCCESS, body);
|
result = new TimiResponse<>(TimiCode.SUCCESS, body);
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
if (multilingualHeader != null && TimiJava.isNotEmpty(result.getMsgKey())) {
|
if (multilingualHeader != null && TimiJava.isNotEmpty(result.getMsgKey())) {
|
||||||
result.setMsg(multilingualHeader.handler(result.getMsgKey()));
|
result.setMsg(multilingualHeader.handler(result));
|
||||||
} else if (TimiJava.isEmpty(result.getMsg())) {
|
} else if (TimiJava.isEmpty(result.getMsg())) {
|
||||||
result.setMsg(TimiCode.fromCode(result.getCode()).toString());
|
result.setMsg(TimiCode.fromCode(result.getCode()).toString());
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("multilingual response error", e);
|
||||||
|
result.setMsg(TimiCode.fromCode(result.getCode()).toString());
|
||||||
|
}
|
||||||
if (30000 < result.getCode()) {
|
if (30000 < result.getCode()) {
|
||||||
log.warn("ID: {} Response -> Exception.{}.{}", TimiSpring.getSessionAttr(AOPLogInterceptor.REQUEST_ID), result.getCode(), result.getMsg());
|
log.warn("ID: {} Response -> Exception.{}.{}", TimiSpring.getSessionAttr(AOPLogInterceptor.REQUEST_ID), result.getCode(), result.getMsg());
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CallbackArgReturn<String, String> getMultilingualHeader() {
|
public CallbackArgReturn<LanguageMsgMapping<?>, String> getMultilingualHeader() {
|
||||||
return multilingualHeader;
|
return multilingualHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMultilingualHeader(CallbackArgReturn<String, String> multilingualHeader) {
|
public void setMultilingualHeader(CallbackArgReturn<LanguageMsgMapping<?>, String> multilingualHeader) {
|
||||||
this.multilingualHeader = multilingualHeader;
|
this.multilingualHeader = multilingualHeader;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import com.imyeyu.spring.annotation.table.Column;
|
|||||||
import com.imyeyu.spring.annotation.table.Id;
|
import com.imyeyu.spring.annotation.table.Id;
|
||||||
import com.imyeyu.spring.annotation.table.Table;
|
import com.imyeyu.spring.annotation.table.Table;
|
||||||
import com.imyeyu.spring.annotation.table.Transient;
|
import com.imyeyu.spring.annotation.table.Transient;
|
||||||
|
import com.imyeyu.spring.entity.BaseEntity;
|
||||||
import com.imyeyu.spring.entity.Creatable;
|
import com.imyeyu.spring.entity.Creatable;
|
||||||
import com.imyeyu.spring.entity.Deletable;
|
import com.imyeyu.spring.entity.Deletable;
|
||||||
import com.imyeyu.spring.entity.Destroyable;
|
import com.imyeyu.spring.entity.Destroyable;
|
||||||
@ -40,6 +41,51 @@ public class SQLProvider {
|
|||||||
/** 反射缓存 */
|
/** 反射缓存 */
|
||||||
private static final Map<Class<?>, EntityMeta> ENTITY_META_CACHE = new ConcurrentHashMap<>();
|
private static final Map<Class<?>, EntityMeta> ENTITY_META_CACHE = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public String count(ProviderContext context) {
|
||||||
|
EntityMeta meta = getEntityMeta(context);
|
||||||
|
StringBuilder sql = new StringBuilder();
|
||||||
|
sql.append("SELECT COUNT(*) FROM `%s` WHERE 1 = 1".formatted(meta.table));
|
||||||
|
if (meta.canDelete) {
|
||||||
|
sql.append(" AND (`deleted_at` IS NULL OR %s < `deleted_at`)".formatted(Time.now()));
|
||||||
|
}
|
||||||
|
return sql.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String listOrder(ProviderContext context, @Param("offset") Long offset, @Param("limit") Integer limit, @Param("orderMap") Map<String, BaseMapper.OrderType> orderMap) {
|
||||||
|
EntityMeta meta = getEntityMeta(context);
|
||||||
|
StringBuilder sql = new StringBuilder();
|
||||||
|
sql.append("SELECT %s FROM `%s` WHERE 1 = 1".formatted(meta.selectAllClause, meta.table));
|
||||||
|
if (meta.canDelete) {
|
||||||
|
sql.append(" AND (`deleted_at` IS NULL OR %s < `deleted_at`)".formatted(Time.now()));
|
||||||
|
}
|
||||||
|
if (TimiJava.isNotEmpty(orderMap)) {
|
||||||
|
sql.append(" ORDER BY ");
|
||||||
|
for (Map.Entry<String, BaseMapper.OrderType> item : orderMap.entrySet()) {
|
||||||
|
sql.append(Text.camelCase2underscore(item.getKey())).append(' ').append(item.getValue().toString());
|
||||||
|
sql.append(", ");
|
||||||
|
}
|
||||||
|
sql.deleteCharAt(sql.length() - 2);
|
||||||
|
} else {
|
||||||
|
if (meta.canCreate && !meta.canUpdate) {
|
||||||
|
sql.append(" ORDER BY created_at DESC");
|
||||||
|
}
|
||||||
|
if (meta.canCreate && meta.canUpdate) {
|
||||||
|
sql.append(" ORDER BY COALESCE(updated_at, created_at) DESC");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sql.append(" LIMIT %s, %s".formatted(offset, limit)).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String listAll(ProviderContext context) {
|
||||||
|
EntityMeta meta = getEntityMeta(context);
|
||||||
|
StringBuilder sql = new StringBuilder();
|
||||||
|
sql.append("SELECT * FROM %s WHERE 1 = 1".formatted(meta.table));
|
||||||
|
if (meta.canDelete) {
|
||||||
|
sql.append(BaseMapper.NOT_DELETE);
|
||||||
|
}
|
||||||
|
return sql.toString();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 插入
|
* 插入
|
||||||
* <p><i>不实现 {@link Creatable} 也允许调用是合理的,某些数据属于关联数据,不参与主创建过程</i></p>
|
* <p><i>不实现 {@link Creatable} 也允许调用是合理的,某些数据属于关联数据,不参与主创建过程</i></p>
|
||||||
@ -82,7 +128,7 @@ public class SQLProvider {
|
|||||||
TimiException.required(meta.idFieldColumn, "not found id field in %s".formatted(meta.entityClass));
|
TimiException.required(meta.idFieldColumn, "not found id field in %s".formatted(meta.entityClass));
|
||||||
|
|
||||||
StringBuilder sql = new StringBuilder();
|
StringBuilder sql = new StringBuilder();
|
||||||
sql.append("SELECT * FROM `%s` WHERE `%s` = #{%s}".formatted(meta.table, meta.idFieldColumn.columnName, id));
|
sql.append("SELECT %s FROM `%s` WHERE `%s` = #{%s}".formatted(meta.selectAllClause, meta.table, meta.idFieldColumn.columnName, id));
|
||||||
if (meta.canDelete) {
|
if (meta.canDelete) {
|
||||||
sql.append(" AND (`deleted_at` IS NULL OR %s < `deleted_at`)".formatted(Time.now()));
|
sql.append(" AND (`deleted_at` IS NULL OR %s < `deleted_at`)".formatted(Time.now()));
|
||||||
}
|
}
|
||||||
@ -121,7 +167,7 @@ public class SQLProvider {
|
|||||||
.collect(Collectors.joining(" AND "));
|
.collect(Collectors.joining(" AND "));
|
||||||
|
|
||||||
StringBuilder sql = new StringBuilder();
|
StringBuilder sql = new StringBuilder();
|
||||||
sql.append("SELECT * FROM `%s` WHERE %s".formatted(meta.table, conditionClause));
|
sql.append("SELECT %s FROM `%s` WHERE %s".formatted(meta.selectAllClause, meta.table, conditionClause));
|
||||||
if (meta.canDelete) {
|
if (meta.canDelete) {
|
||||||
if (TimiJava.isNotEmpty(conditionClause)) {
|
if (TimiJava.isNotEmpty(conditionClause)) {
|
||||||
sql.append(" AND ");
|
sql.append(" AND ");
|
||||||
@ -151,7 +197,43 @@ public class SQLProvider {
|
|||||||
return "`%s` = #{%s}".formatted(fc.columnName, fc.fieldName);
|
return "`%s` = #{%s}".formatted(fc.columnName, fc.fieldName);
|
||||||
})
|
})
|
||||||
.collect(Collectors.joining(", "));
|
.collect(Collectors.joining(", "));
|
||||||
return "UPDATE `%s` SET `%s` WHERE `%s` = #{%s}".formatted(meta.table, setClause, meta.idFieldColumn.columnName, meta.idFieldColumn.fieldName);
|
return "UPDATE `%s` SET %s WHERE `%s` = #{%s}".formatted(meta.table, setClause, meta.idFieldColumn.columnName, meta.idFieldColumn.fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 ID 更新,选择性更新非空属性,需要实体实现 {@link Updatable}
|
||||||
|
*
|
||||||
|
* @param entity 实体
|
||||||
|
* @return SQL
|
||||||
|
*/
|
||||||
|
public String updateSelective(Object entity) {
|
||||||
|
EntityMeta meta = getEntityMeta(entity.getClass());
|
||||||
|
TimiException.required(meta.idFieldColumn, "not found id field in %s".formatted(meta.entityClass));
|
||||||
|
TimiException.required(meta.canUpdate, "not allow update for %s".formatted(meta.entityClass));
|
||||||
|
|
||||||
|
if (entity instanceof BaseEntity baseEntity) {
|
||||||
|
baseEntity.setCreatedAt(null);
|
||||||
|
baseEntity.setDeletedAt(null);
|
||||||
|
}
|
||||||
|
String setClause = meta.fieldColumnList.stream()
|
||||||
|
.filter(fc -> {
|
||||||
|
if (fc.isId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Ref.getFieldValue(entity, fc.field, Object.class) != null;
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(fc -> {
|
||||||
|
if (entity instanceof Updatable updatableEntity) {
|
||||||
|
updatableEntity.setUpdatedAt(Time.now());
|
||||||
|
}
|
||||||
|
return "`%s` = #{%s}".formatted(fc.columnName, fc.fieldName);
|
||||||
|
})
|
||||||
|
.collect(Collectors.joining(", "));
|
||||||
|
return "UPDATE `%s` SET %s WHERE `%s` = #{%s}".formatted(meta.table, setClause, meta.idFieldColumn.columnName, meta.idFieldColumn.fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -190,7 +272,7 @@ public class SQLProvider {
|
|||||||
* @param context 代理器上下文
|
* @param context 代理器上下文
|
||||||
* @return 实体类元数据
|
* @return 实体类元数据
|
||||||
*/
|
*/
|
||||||
private EntityMeta getEntityMeta(ProviderContext context) {
|
protected EntityMeta getEntityMeta(ProviderContext context) {
|
||||||
Type[] types = context.getMapperType().getGenericInterfaces();
|
Type[] types = context.getMapperType().getGenericInterfaces();
|
||||||
ParameterizedType type = (ParameterizedType) types[0];
|
ParameterizedType type = (ParameterizedType) types[0];
|
||||||
Class<?> entityClass = (Class<?>) type.getActualTypeArguments()[0];
|
Class<?> entityClass = (Class<?>) type.getActualTypeArguments()[0];
|
||||||
@ -203,7 +285,7 @@ public class SQLProvider {
|
|||||||
* @param entityClass 实体类
|
* @param entityClass 实体类
|
||||||
* @return 元数据
|
* @return 元数据
|
||||||
*/
|
*/
|
||||||
private EntityMeta getEntityMeta(Class<?> entityClass) {
|
protected EntityMeta getEntityMeta(Class<?> entityClass) {
|
||||||
return ENTITY_META_CACHE.computeIfAbsent(entityClass, EntityMeta::new);
|
return ENTITY_META_CACHE.computeIfAbsent(entityClass, EntityMeta::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,7 +295,7 @@ public class SQLProvider {
|
|||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @since 2025-02-05 23:47
|
* @since 2025-02-05 23:47
|
||||||
*/
|
*/
|
||||||
private static class EntityMeta {
|
protected static class EntityMeta {
|
||||||
|
|
||||||
/** 实体类 */
|
/** 实体类 */
|
||||||
final Class<?> entityClass;
|
final Class<?> entityClass;
|
||||||
@ -221,12 +303,18 @@ public class SQLProvider {
|
|||||||
/** 表名 */
|
/** 表名 */
|
||||||
final String table;
|
final String table;
|
||||||
|
|
||||||
|
/** 查询字段映射 */
|
||||||
|
final String selectAllClause;
|
||||||
|
|
||||||
/** ID 字段 */
|
/** ID 字段 */
|
||||||
final FieldColumn idFieldColumn;
|
final FieldColumn idFieldColumn;
|
||||||
|
|
||||||
/** 只读的列名字段名映射,Map<列名,字段名> */
|
/** 只读的列名字段名映射,Map<列名,字段名> */
|
||||||
final List<FieldColumn> fieldColumnList;
|
final List<FieldColumn> fieldColumnList;
|
||||||
|
|
||||||
|
/** true 为可创建 */
|
||||||
|
final boolean canCreate;
|
||||||
|
|
||||||
/** true 为可更新 */
|
/** true 为可更新 */
|
||||||
final boolean canUpdate;
|
final boolean canUpdate;
|
||||||
|
|
||||||
@ -252,6 +340,7 @@ public class SQLProvider {
|
|||||||
}
|
}
|
||||||
List<Field> allFieldList = Ref.listAllFields(entityClass);
|
List<Field> allFieldList = Ref.listAllFields(entityClass);
|
||||||
|
|
||||||
|
StringBuilder selectAllClause = new StringBuilder();
|
||||||
FieldColumn idFieldColumn = null;
|
FieldColumn idFieldColumn = null;
|
||||||
List<FieldColumn> fieldColumnList = new ArrayList<>();
|
List<FieldColumn> fieldColumnList = new ArrayList<>();
|
||||||
for (int i = 0; i < allFieldList.size(); i++) {
|
for (int i = 0; i < allFieldList.size(); i++) {
|
||||||
@ -264,14 +353,64 @@ public class SQLProvider {
|
|||||||
TimiException.requiredNull(idFieldColumn, String.format("multi id field for %s entity", entityClass.getName()));
|
TimiException.requiredNull(idFieldColumn, String.format("multi id field for %s entity", entityClass.getName()));
|
||||||
idFieldColumn = fieldColumn;
|
idFieldColumn = fieldColumn;
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
Column column = field.getAnnotation(Column.class);
|
||||||
|
if (column == null) {
|
||||||
|
selectAllClause.append('`').append(fieldColumn.columnName).append('`');
|
||||||
|
selectAllClause.append(',');
|
||||||
|
} else {
|
||||||
|
// 处理自定义映射列名
|
||||||
|
selectAllClause.append('`').append(column.value()).append('`');
|
||||||
|
selectAllClause.append(" AS `").append(fieldColumn.fieldName).append('`');
|
||||||
|
selectAllClause.append(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
fieldColumnList.add(fieldColumn);
|
fieldColumnList.add(fieldColumn);
|
||||||
}
|
}
|
||||||
|
this.selectAllClause = selectAllClause.substring(0, selectAllClause.length() - 1);
|
||||||
this.idFieldColumn = idFieldColumn;
|
this.idFieldColumn = idFieldColumn;
|
||||||
this.fieldColumnList = List.of(fieldColumnList.toArray(new FieldColumn[0])); // 转为只读
|
this.fieldColumnList = List.of(fieldColumnList.toArray(new FieldColumn[0])); // 转为只读
|
||||||
|
canCreate = Creatable.class.isAssignableFrom(entityClass);
|
||||||
canUpdate = Updatable.class.isAssignableFrom(entityClass);
|
canUpdate = Updatable.class.isAssignableFrom(entityClass);
|
||||||
canDelete = Deletable.class.isAssignableFrom(entityClass);
|
canDelete = Deletable.class.isAssignableFrom(entityClass);
|
||||||
canDestroy = Destroyable.class.isAssignableFrom(entityClass);
|
canDestroy = Destroyable.class.isAssignableFrom(entityClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Class<?> getEntityClass() {
|
||||||
|
return entityClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTable() {
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSelectAllClause() {
|
||||||
|
return selectAllClause;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FieldColumn getIdFieldColumn() {
|
||||||
|
return idFieldColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FieldColumn> getFieldColumnList() {
|
||||||
|
return fieldColumnList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canCreate() {
|
||||||
|
return canCreate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canUpdate() {
|
||||||
|
return canUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canDelete() {
|
||||||
|
return canDelete;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canDestroy() {
|
||||||
|
return canDestroy;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -280,7 +419,7 @@ public class SQLProvider {
|
|||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @since 2025-02-07 09:54
|
* @since 2025-02-07 09:54
|
||||||
*/
|
*/
|
||||||
private static class FieldColumn {
|
protected static class FieldColumn {
|
||||||
|
|
||||||
/** 字段 */
|
/** 字段 */
|
||||||
final Field field;
|
final Field field;
|
||||||
@ -318,5 +457,29 @@ public class SQLProvider {
|
|||||||
isAutoUpperUUID = false;
|
isAutoUpperUUID = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Field getField() {
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFieldName() {
|
||||||
|
return fieldName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColumnName() {
|
||||||
|
return columnName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isId() {
|
||||||
|
return isId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAutoUUID() {
|
||||||
|
return isAutoUUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAutoUpperUUID() {
|
||||||
|
return isAutoUpperUUID;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
package com.imyeyu.spring.util;
|
||||||
|
|
||||||
|
import org.springframework.boot.env.YamlPropertySourceLoader;
|
||||||
|
import org.springframework.core.env.PropertySource;
|
||||||
|
import org.springframework.core.io.support.EncodedResource;
|
||||||
|
import org.springframework.core.io.support.PropertySourceFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2025-10-13 16:29
|
||||||
|
*/
|
||||||
|
public class YamlPropertySourceFactory implements PropertySourceFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @org.springframework.lang.NonNull PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
|
||||||
|
List<PropertySource<?>> sources = new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource());
|
||||||
|
return sources.get(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/main/resources/lang/common.lang
Normal file
5
src/main/resources/lang/common.lang
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
invalid.arg=无效的参数
|
||||||
|
invalid.body=无效的请求体
|
||||||
|
invalid.header=无效的请求头
|
||||||
|
invalid.request=无效的请求
|
||||||
|
service.error=服务错误
|
||||||
Reference in New Issue
Block a user