1 Commits

Author SHA1 Message Date
d6681e133a refactor: 将构建过程放在 Builder 中以避免重复校验 2025-08-10 16:50:21 +08:00
4 changed files with 75 additions and 195 deletions

View File

@@ -27,7 +27,6 @@ import javax.annotation.Nullable;
import com.google.common.collect.ImmutableMap;
import xyz.zhouxy.plusone.commons.annotation.Virtual;
import xyz.zhouxy.plusone.commons.collection.CollectionTools;
import xyz.zhouxy.plusone.commons.util.RegexTools;
import xyz.zhouxy.plusone.commons.util.StringTools;
@@ -45,30 +44,10 @@ import xyz.zhouxy.plusone.commons.util.StringTools;
*/
public class PagingAndSortingQueryParams {
private static final int DEFAULT_PAGE_SIZE = 15;
private Integer size;
private Long pageNum;
private List<String> orderBy;
private static final Pattern SORT_STR_PATTERN = Pattern.compile("^[a-zA-Z][\\w-]{0,63}-(desc|asc|DESC|ASC)$");
private final Map<String, String> sortableProperties;
/**
* 构造分页排序查询参数
*
* @param sortableProperties 可排序的属性。不可为空。
*/
public PagingAndSortingQueryParams(Map<String, String> sortableProperties) {
checkArgument(CollectionTools.isNotEmpty(sortableProperties),
"Sortable properties can not be empty.");
sortableProperties.forEach((k, v) ->
checkArgument(StringTools.isNotBlank(k) && StringTools.isNotBlank(v),
"Property name must not be blank."));
this.sortableProperties = ImmutableMap.copyOf(sortableProperties);
}
// Setters
/**
@@ -100,56 +79,18 @@ public class PagingAndSortingQueryParams {
// Setters end
/**
* 构建分页参数
*
* @return {@code PagingParams} 对象
*/
public final PagingParams buildPagingParams() {
final int sizeValue = this.size != null ? this.size : defaultSizeInternal();
final long pageNumValue = this.pageNum != null ? this.pageNum : 1L;
checkArgument(CollectionTools.isNotEmpty(this.orderBy),
"The 'orderBy' cannot be empty");
final List<SortableProperty> propertiesToSort = this.orderBy.stream()
.map(this::generateSortableProperty)
.collect(Collectors.toList());
return new PagingParams(sizeValue, pageNumValue, propertiesToSort);
}
/**
* 默认每页大小
*
* <p>NOTE: 可覆写此方法</p>
*
* @return 默认每页大小
*/
@Virtual
protected int defaultSizeInternal() {
return DEFAULT_PAGE_SIZE;
}
@Override
public String toString() {
return "PagingAndSortingQueryParams ["
+ "size=" + size
+ ", pageNum=" + pageNum
+ ", orderBy=" + orderBy
+ ", sortableProperties=" + sortableProperties
+ "]";
}
private SortableProperty generateSortableProperty(String orderByStr) {
checkArgument(StringTools.isNotBlank(orderByStr));
checkArgument(RegexTools.matches(orderByStr, SORT_STR_PATTERN));
String[] propertyNameAndOrderType = orderByStr.split("-");
checkArgument(propertyNameAndOrderType.length == 2);
String propertyName = propertyNameAndOrderType[0];
checkArgument(sortableProperties.containsKey(propertyName),
"The property name must be in the set of sortable properties.");
String columnName = sortableProperties.get(propertyName);
String orderType = propertyNameAndOrderType[1];
return new SortableProperty(propertyName, columnName, orderType);
protected static PagingParamsBuilder pagingParamsBuilder(
int defaultSize, int maxSize, Map<String, String> sortableProperties) {
return new PagingParamsBuilder(defaultSize, maxSize, sortableProperties);
}
/**
@@ -217,4 +158,47 @@ public class PagingAndSortingQueryParams {
}
}
protected static final class PagingParamsBuilder {
private static final Pattern SORT_STR_PATTERN = Pattern.compile("^[a-zA-Z][\\w-]{0,63}-(desc|asc|DESC|ASC)$");
private final Map<String, String> sortableProperties;
protected final int defaultSize;
protected final int maxSize;
private PagingParamsBuilder(int defaultSize, int maxSize, Map<String, String> sortableProperties) {
this.defaultSize = defaultSize;
this.maxSize = maxSize;
checkArgument(CollectionTools.isNotEmpty(sortableProperties),
"Sortable properties can not be empty.");
sortableProperties.forEach((k, v) ->
checkArgument(StringTools.isNotBlank(k) && StringTools.isNotBlank(v),
"Property name must not be blank."));
this.sortableProperties = ImmutableMap.copyOf(sortableProperties);
}
public PagingParams buildPagingParams(PagingAndSortingQueryParams params) {
final int sizeValue = params.size != null ? params.size : this.defaultSize;
final long pageNumValue = params.pageNum != null ? params.pageNum : 1L;
checkArgument(CollectionTools.isNotEmpty(params.orderBy),
"The 'orderBy' cannot be empty");
final List<SortableProperty> propertiesToSort = params.orderBy.stream()
.map(this::generateSortableProperty)
.collect(Collectors.toList());
return new PagingParams(sizeValue, pageNumValue, propertiesToSort);
}
private SortableProperty generateSortableProperty(String orderByStr) {
checkArgument(StringTools.isNotBlank(orderByStr));
checkArgument(RegexTools.matches(orderByStr, SORT_STR_PATTERN));
String[] propertyNameAndOrderType = orderByStr.split("-");
checkArgument(propertyNameAndOrderType.length == 2);
String propertyName = propertyNameAndOrderType[0];
checkArgument(sortableProperties.containsKey(propertyName),
"The property name must be in the set of sortable properties.");
String columnName = sortableProperties.get(propertyName);
String orderType = propertyNameAndOrderType[1];
return new SortableProperty(propertyName, columnName, orderType);
}
}
}

View File

@@ -27,11 +27,11 @@ import java.time.Year;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.chrono.IsoChronology;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import com.google.common.collect.BoundType;
import com.google.common.collect.Range;
import xyz.zhouxy.plusone.commons.time.Quarter;
@@ -673,44 +673,6 @@ public class DateTimeTools {
return Range.closedOpen(date.atStartOfDay(zone), startOfNextDate(date, zone));
}
/**
* 将指定日期范围转为日期时间范围
*
* @param dateRange 日期范围
* @return 对应的日期时间范围
*/
public static Range<LocalDateTime> toDateTimeRange(Range<LocalDate> dateRange) {
BoundType lowerBoundType = dateRange.lowerBoundType();
LocalDateTime lowerEndpoint = lowerBoundType == BoundType.CLOSED
? dateRange.lowerEndpoint().atStartOfDay()
: dateRange.lowerEndpoint().plusDays(1).atStartOfDay();
BoundType upperBoundType = dateRange.upperBoundType();
LocalDateTime upperEndpoint = upperBoundType == BoundType.CLOSED
? dateRange.upperEndpoint().plusDays(1).atStartOfDay()
: dateRange.upperEndpoint().atStartOfDay();
return Range.closedOpen(lowerEndpoint, upperEndpoint);
}
/**
* 将指定日期范围转为日期时间范围
*
* @param dateRange 日期范围
* @return 对应的日期时间范围
*/
public static Range<ZonedDateTime> toDateTimeRange(Range<LocalDate> dateRange, ZoneId zone) {
BoundType lowerBoundType = dateRange.lowerBoundType();
ZonedDateTime lowerEndpoint = lowerBoundType == BoundType.CLOSED
? dateRange.lowerEndpoint().atStartOfDay(zone)
: dateRange.lowerEndpoint().plusDays(1).atStartOfDay(zone);
BoundType upperBoundType = dateRange.upperBoundType();
ZonedDateTime upperEndpoint = upperBoundType == BoundType.CLOSED
? dateRange.upperEndpoint().plusDays(1).atStartOfDay(zone)
: dateRange.upperEndpoint().atStartOfDay(zone);
return Range.closedOpen(lowerEndpoint, upperEndpoint);
}
/**
* 判断指定年份是否为闰年
*
@@ -718,7 +680,7 @@ public class DateTimeTools {
* @return 指定年份是否为闰年
*/
public static boolean isLeapYear(int year) {
return Year.isLeap(year);
return IsoChronology.INSTANCE.isLeapYear(year);
}
// ================================

View File

@@ -231,9 +231,8 @@ class AccountQueryParams extends PagingAndSortingQueryParams {
.put("createTime", "create_time")
.build();
public AccountQueryParams() {
super(PROPERTY_COLUMN_MAP);
}
private static final PagingParamsBuilder PAGING_PARAMS_BUILDER = PagingAndSortingQueryParams
.pagingParamsBuilder(20, 100, PROPERTY_COLUMN_MAP);
private @Getter @Setter Long id;
private @Getter @Setter String username;
@@ -249,6 +248,10 @@ class AccountQueryParams extends PagingAndSortingQueryParams {
}
return this.createTimeEnd.plusDays(1);
}
public PagingParams buildPagingParams() {
return PAGING_PARAMS_BUILDER.buildPagingParams(this);
}
}
@Data

View File

@@ -43,7 +43,6 @@ import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.BoundType;
import com.google.common.collect.Range;
import xyz.zhouxy.plusone.commons.time.Quarter;
@@ -84,6 +83,24 @@ class DateTimeToolsTests {
CALENDAR.setTime(DATE);
}
// Joda
static final org.joda.time.LocalDateTime JODA_LOCAL_DATE_TIME
= new org.joda.time.LocalDateTime(2024, 12, 29, 12, 58, 30, 333);
static final org.joda.time.LocalDate JODA_LOCAL_DATE = JODA_LOCAL_DATE_TIME.toLocalDate();
static final org.joda.time.LocalTime JODA_LOCAL_TIME = JODA_LOCAL_DATE_TIME.toLocalTime();
// Joda - 2024-12-29 12:58:30.333 SystemDefaultZone
static final org.joda.time.DateTimeZone JODA_SYS_ZONE = org.joda.time.DateTimeZone.getDefault();
static final org.joda.time.DateTime JODA_DATE_TIME_WITH_SYS_ZONE = JODA_LOCAL_DATE_TIME.toDateTime(JODA_SYS_ZONE);
static final org.joda.time.Instant JODA_INSTANT_WITH_SYS_ZONE = JODA_DATE_TIME_WITH_SYS_ZONE.toInstant();
static final long JODA_INSTANT_MILLIS = JODA_INSTANT_WITH_SYS_ZONE.getMillis();
// Joda - 2024-12-29 12:58:30.333 GMT+04:00
static final org.joda.time.DateTimeZone JODA_ZONE = org.joda.time.DateTimeZone.forID("GMT+04:00");
static final org.joda.time.DateTime JODA_DATE_TIME = JODA_LOCAL_DATE_TIME.toDateTime(JODA_ZONE);
static final org.joda.time.Instant JODA_INSTANT = JODA_DATE_TIME.toInstant();
static final long JODA_MILLIS = JODA_INSTANT.getMillis();
// ================================
// #region - toDate
// ================================
@@ -92,7 +109,9 @@ class DateTimeToolsTests {
void toDate_timeMillis() {
assertNotEquals(SYS_DATE, DATE);
log.info("SYS_DATE: {}, DATE: {}", SYS_DATE, DATE);
assertEquals(SYS_DATE, JODA_DATE_TIME_WITH_SYS_ZONE.toDate());
assertEquals(SYS_DATE, DateTimeTools.toDate(INSTANT_MILLIS));
assertEquals(DATE, JODA_DATE_TIME.toDate());
assertEquals(DATE, DateTimeTools.toDate(MILLIS));
}
@@ -378,7 +397,7 @@ class DateTimeToolsTests {
// ================================
@Test
void toDateTimeRange_specifiedDate() {
void startDateTimeRange() {
Range<LocalDateTime> localDateTimeRange = DateTimeTools.toDateTimeRange(LOCAL_DATE);
assertEquals(LOCAL_DATE.atStartOfDay(), localDateTimeRange.lowerEndpoint());
assertEquals(LocalDate.of(2024, 12, 30).atStartOfDay(), localDateTimeRange.upperEndpoint());
@@ -392,94 +411,6 @@ class DateTimeToolsTests {
assertFalse(zonedDateTimeRange.contains(LocalDate.of(2024, 12, 30).atStartOfDay().atZone(SYS_ZONE_ID)));
}
@Test
void toDateTimeRange_dateRange_openRange() {
// (2000-01-01..2025-10-01) -> [2000-01-02T00:00..2025-10-01T00:00)
Range<LocalDate> dateRange = Range.open(
LocalDate.of(2000, 1, 1),
LocalDate.of(2025, 10, 1));
Range<LocalDateTime> localDateTimeRange = DateTimeTools.toDateTimeRange(dateRange);
assertEquals(BoundType.CLOSED, localDateTimeRange.lowerBoundType());
assertEquals(LocalDateTime.of(2000, 1, 2, 0, 0), localDateTimeRange.lowerEndpoint());
assertEquals(BoundType.OPEN, localDateTimeRange.upperBoundType());
assertEquals(LocalDateTime.of(2025, 10, 1, 0, 0), localDateTimeRange.upperEndpoint());
log.info(localDateTimeRange.toString());
Range<ZonedDateTime> zonedDateTimeRange = DateTimeTools.toDateTimeRange(dateRange, ZONE_ID);
assertEquals(BoundType.CLOSED, zonedDateTimeRange.lowerBoundType());
assertEquals(LocalDateTime.of(2000, 1, 2, 0, 0).atZone(ZONE_ID), zonedDateTimeRange.lowerEndpoint());
assertEquals(BoundType.OPEN, zonedDateTimeRange.upperBoundType());
assertEquals(LocalDateTime.of(2025, 10, 1, 0, 0).atZone(ZONE_ID), zonedDateTimeRange.upperEndpoint());
log.info(zonedDateTimeRange.toString());
}
@Test
void toDateTimeRange_dateRange_openClosedRange() {
// (2000-01-01..2025-10-01] -> [2025-01-02T00-00..2025-10-02 00:00)
Range<LocalDate> dateRange = Range.openClosed(
LocalDate.of(2000, 1, 1),
LocalDate.of(2025, 10, 1));
Range<LocalDateTime> localDateTimeRange = DateTimeTools.toDateTimeRange(dateRange);
assertEquals(BoundType.CLOSED, localDateTimeRange.lowerBoundType());
assertEquals(LocalDateTime.of(2000, 1, 2, 0, 0), localDateTimeRange.lowerEndpoint());
assertEquals(BoundType.OPEN, localDateTimeRange.upperBoundType());
assertEquals(LocalDateTime.of(2025, 10, 2, 0, 0), localDateTimeRange.upperEndpoint());
log.info(localDateTimeRange.toString());
Range<ZonedDateTime> zonedDateTimeRange = DateTimeTools.toDateTimeRange(dateRange, ZONE_ID);
assertEquals(BoundType.CLOSED, zonedDateTimeRange.lowerBoundType());
assertEquals(LocalDateTime.of(2000, 1, 2, 0, 0).atZone(ZONE_ID), zonedDateTimeRange.lowerEndpoint());
assertEquals(BoundType.OPEN, zonedDateTimeRange.upperBoundType());
assertEquals(LocalDateTime.of(2025, 10, 2, 0, 0).atZone(ZONE_ID), zonedDateTimeRange.upperEndpoint());
log.info(zonedDateTimeRange.toString());
}
@Test
void toDateTimeRange_dateRange_closedRange() {
// [2000-01-01..2025-10-01] -> [2025-01-01T00-00..2025-10-02 00:00)
Range<LocalDate> dateRange = Range.closed(
LocalDate.of(2000, 1, 1),
LocalDate.of(2025, 10, 1));
Range<LocalDateTime> localDateTimeRange = DateTimeTools.toDateTimeRange(dateRange);
assertEquals(BoundType.CLOSED, localDateTimeRange.lowerBoundType());
assertEquals(LocalDateTime.of(2000, 1, 1, 0, 0), localDateTimeRange.lowerEndpoint());
assertEquals(BoundType.OPEN, localDateTimeRange.upperBoundType());
assertEquals(LocalDateTime.of(2025, 10, 2, 0, 0), localDateTimeRange.upperEndpoint());
log.info(localDateTimeRange.toString());
Range<ZonedDateTime> zonedDateTimeRange = DateTimeTools.toDateTimeRange(dateRange, ZONE_ID);
assertEquals(BoundType.CLOSED, zonedDateTimeRange.lowerBoundType());
assertEquals(LocalDateTime.of(2000, 1, 1, 0, 0).atZone(ZONE_ID), zonedDateTimeRange.lowerEndpoint());
assertEquals(BoundType.OPEN, zonedDateTimeRange.upperBoundType());
assertEquals(LocalDateTime.of(2025, 10, 2, 0, 0).atZone(ZONE_ID), zonedDateTimeRange.upperEndpoint());
log.info(zonedDateTimeRange.toString());
}
@Test
void toDateTimeRange_dateRange_closedOpenRange() {
// [2025-01-01..2025-10-01) -> [2025-01-01T00-00..2025-10-01 00:00)
Range<LocalDate> dateRange = Range.closedOpen(
LocalDate.of(2000, 1, 1),
LocalDate.of(2025, 10, 1));
Range<LocalDateTime> localDateTimeRange = DateTimeTools.toDateTimeRange(dateRange);
assertEquals(BoundType.CLOSED, localDateTimeRange.lowerBoundType());
assertEquals(LocalDateTime.of(2000, 1, 1, 0, 0), localDateTimeRange.lowerEndpoint());
assertEquals(BoundType.OPEN, localDateTimeRange.upperBoundType());
assertEquals(LocalDateTime.of(2025, 10, 1, 0, 0), localDateTimeRange.upperEndpoint());
log.info(localDateTimeRange.toString());
Range<ZonedDateTime> zonedDateTimeRange = DateTimeTools.toDateTimeRange(dateRange, ZONE_ID);
assertEquals(BoundType.CLOSED, zonedDateTimeRange.lowerBoundType());
assertEquals(LocalDateTime.of(2000, 1, 1, 0, 0).atZone(ZONE_ID), zonedDateTimeRange.lowerEndpoint());
assertEquals(BoundType.OPEN, zonedDateTimeRange.upperBoundType());
assertEquals(LocalDateTime.of(2025, 10, 1, 0, 0).atZone(ZONE_ID), zonedDateTimeRange.upperEndpoint());
log.info(zonedDateTimeRange.toString());
}
// ================================
// #endregion - others
// ================================