Compare commits

...

5 Commits

Author SHA1 Message Date
69d847f337 support deploy nexus 2025-10-13 10:55:20 +08:00
443757f501 upper Page & PageResult base field to timi-java 2025-10-13 10:54:46 +08:00
2fc06e3851 add BaseMapper.listOrder 2025-10-13 10:53:55 +08:00
831d36e095 support list() and count() in SQLProvider, fix custom column mapper 2025-07-25 10:50:33 +08:00
39f628e71a add RequestRange 2025-07-15 11:44:27 +08:00
7 changed files with 188 additions and 101 deletions

57
pom.xml
View File

@ -28,17 +28,60 @@
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>21</source>
<target>21</target>
<encoding>UTF-8</encoding>
</configuration>
<artifactId>maven-deploy-plugin</artifactId>
<version>3.1.3</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<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>
</plugins>
</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>
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@ -7,6 +7,7 @@ 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;
@ -16,6 +17,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
@ -117,6 +119,12 @@ public class TimiSpring {
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 回调
*
@ -357,4 +365,26 @@ public class TimiSpring {
public static boolean isLocalIP() {
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);
}
}

View File

@ -1,8 +1,9 @@
package com.imyeyu.spring.bean;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.BasePage;
import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.utils.Text;
import java.util.LinkedHashMap;
@ -12,18 +13,7 @@ import java.util.LinkedHashMap;
* @author 夜雨
* @version 2023-06-02 14:47
*/
public class Page {
/** 下标 */
@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;
public class Page extends BasePage {
protected LinkedHashMap<String, BaseMapper.OrderType> orderMap;
@ -31,8 +21,7 @@ public class Page {
}
public Page(int index, int size) {
this.index = index;
this.size = size;
super(index, size);
}
public long getOffset() {
@ -43,30 +32,6 @@ public class Page {
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() {
return orderMap;
}
@ -75,8 +40,13 @@ public class Page {
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) {
result.setList(pageMapper.list(page.getOffset(), page.getLimit()));
result.setList(pageMapper.listOrder(page.getOffset(), page.getLimit(), page.getOrderMap()));
result.setTotal(pageMapper.count());
return result;
}

View File

@ -1,9 +1,6 @@
package com.imyeyu.spring.bean;
import com.imyeyu.java.TimiJava;
import com.imyeyu.utils.Calc;
import java.util.List;
import com.imyeyu.java.bean.BasePageResult;
/**
* 抽象页面查询结果
@ -11,49 +8,5 @@ import java.util.List;
* @author 夜雨
* @version 2023-06-02 14:47
*/
public class PageResult<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;
}
public class PageResult<T> extends BasePageResult<T> {
}

View 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;
}
}

View File

@ -8,6 +8,7 @@ import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.annotations.UpdateProvider;
import java.util.List;
import java.util.Map;
/**
* 基本 SQL 映射,子接口可以不实现
@ -43,8 +44,13 @@ public interface BaseMapper<T, P> {
*
* @return 数据量
*/
@SelectProvider(type = SQLProvider.class, method = "count")
long count();
default List<T> list(long offset, int limit) {
return listOrder(offset, limit, null);
}
/**
* 获取部分数据
*
@ -52,7 +58,8 @@ public interface BaseMapper<T, P> {
* @param limit 数据量
* @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);
/**
* 创建数据。默认自增主键为 id如需修改请重写此接口

View File

@ -40,6 +40,34 @@ public class SQLProvider {
/** 反射缓存 */
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);
}
return sql.append(" LIMIT %s, %s".formatted(offset, limit)).toString();
}
/**
* 插入
* <p><i>不实现 {@link Creatable} 也允许调用是合理的,某些数据属于关联数据,不参与主创建过程</i></p>
@ -82,7 +110,7 @@ public class SQLProvider {
TimiException.required(meta.idFieldColumn, "not found id field in %s".formatted(meta.entityClass));
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) {
sql.append(" AND (`deleted_at` IS NULL OR %s < `deleted_at`)".formatted(Time.now()));
}
@ -121,7 +149,7 @@ public class SQLProvider {
.collect(Collectors.joining(" AND "));
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 (TimiJava.isNotEmpty(conditionClause)) {
sql.append(" AND ");
@ -151,7 +179,7 @@ public class SQLProvider {
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);
return "UPDATE `%s` SET %s WHERE `%s` = #{%s}".formatted(meta.table, setClause, meta.idFieldColumn.columnName, meta.idFieldColumn.fieldName);
}
/**
@ -221,6 +249,9 @@ public class SQLProvider {
/** 表名 */
final String table;
/** 查询字段映射 */
final String selectAllClause;
/** ID 字段 */
final FieldColumn idFieldColumn;
@ -252,6 +283,7 @@ public class SQLProvider {
}
List<Field> allFieldList = Ref.listAllFields(entityClass);
StringBuilder selectAllClause = new StringBuilder();
FieldColumn idFieldColumn = null;
List<FieldColumn> fieldColumnList = new ArrayList<>();
for (int i = 0; i < allFieldList.size(); i++) {
@ -264,8 +296,21 @@ public class SQLProvider {
TimiException.requiredNull(idFieldColumn, String.format("multi id field for %s entity", entityClass.getName()));
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);
}
this.selectAllClause = selectAllClause.substring(0, selectAllClause.length() - 1);
this.idFieldColumn = idFieldColumn;
this.fieldColumnList = List.of(fieldColumnList.toArray(new FieldColumn[0])); // 转为只读
canUpdate = Updatable.class.isAssignableFrom(entityClass);