最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java?Web開發(fā)中的分頁與參數(shù)校驗(yàn)舉例詳解

 更新時(shí)間:2025年02月27日 09:58:31   作者:sjsjsbbsbsn  
這篇文章主要介紹了JavaWeb開發(fā)中的分頁設(shè)計(jì)和參數(shù)校驗(yàn),分頁設(shè)計(jì)通過分頁查詢參數(shù)優(yōu)化查詢性能,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下

前言

在 Java Web 開發(fā)中,分頁和參數(shù)校驗(yàn)是兩個(gè)非常重要的功能。本文將圍繞 分頁設(shè)計(jì) 和 參數(shù)校驗(yàn) 進(jìn)行探討,包括如何設(shè)計(jì)合理的分頁查詢參數(shù),以及如何利用 Java 注解 實(shí)現(xiàn)參數(shù)校驗(yàn)。

分頁設(shè)計(jì)

為什么需要分頁?

當(dāng)數(shù)據(jù)庫表數(shù)據(jù)量較大時(shí),如果直接查詢所有數(shù)據(jù),可能會(huì)導(dǎo)致 查詢緩慢,甚至造成 內(nèi)存溢出(OOM)。分頁是一種常見的優(yōu)化方式,可以 減少數(shù)據(jù)庫負(fù)載 并 提升前端渲染速度。

如何設(shè)計(jì)分頁查詢參數(shù)?

分頁通常包含以下幾個(gè)核心參數(shù):

  • pageNo:當(dāng)前頁碼,默認(rèn)為 1。
  • pageSize:每頁返回的記錄數(shù),默認(rèn)為 20。
  • sortBy:排序字段,如 id、create_time
  • isAsc:是否升序,默認(rèn)為 true

我們可以設(shè)計(jì)一個(gè)公共的父類PageQuery來幫助提供默認(rèn)的參數(shù),同時(shí)我們在開發(fā)中也會(huì)用到mybatisplus,提供出轉(zhuǎn)成page對(duì)象的方法

@Data
@ApiModel(description = "分頁請(qǐng)求參數(shù)")
@Accessors(chain = true)
public class PageQuery {
    public static final Integer DEFAULT_PAGE_SIZE = 20;
    public static final Integer DEFAULT_PAGE_NUM = 1;

    @ApiModelProperty(value = "頁碼", example = "1")
    @Min(value = 1, message = "頁碼不能小于1")
    private Integer pageNo = DEFAULT_PAGE_NUM;

    @ApiModelProperty(value = "每頁大小", example = "5")
    @Min(value = 1, message = "每頁查詢數(shù)量不能小于1")
    private Integer pageSize = DEFAULT_PAGE_SIZE;

    @ApiModelProperty(value = "是否升序", example = "true")
    private Boolean isAsc = true;

    @ApiModelProperty(value = "排序字段", example = "id")
    private String sortBy;

    public int from(){
        return (pageNo - 1) * pageSize;
    }

    public <T> Page<T> toMpPage(OrderItem ... orderItems) {
        Page<T> page = new Page<>(pageNo, pageSize);
        // 是否手動(dòng)指定排序方式
        if (orderItems != null && orderItems.length > 0) {
            for (OrderItem orderItem : orderItems) {
                page.addOrder(orderItem);
            }
            return page;
        }
        // 前端是否有排序字段
        if (StringUtils.isNotEmpty(sortBy)){
            OrderItem orderItem = new OrderItem();
            orderItem.setAsc(isAsc);
            orderItem.setColumn(sortBy);
            page.addOrder(orderItem);
        }
        return page;
    }

    public <T> Page<T> toMpPage(String defaultSortBy, boolean isAsc) {
        if (StringUtils.isBlank(sortBy)){
            sortBy = defaultSortBy;
            this.isAsc = isAsc;
        }
        Page<T> page = new Page<>(pageNo, pageSize);
        OrderItem orderItem = new OrderItem();
        orderItem.setAsc(this.isAsc);
        orderItem.setColumn(sortBy);
        page.addOrder(orderItem);
        return page;
    }
    public <T> Page<T> toMpPageDefaultSortByCreateTimeDesc() {
        return toMpPage(Constant.DATA_FIELD_NAME_CREATE_TIME, false);
    }
}

設(shè)計(jì)優(yōu)點(diǎn)

  • 默認(rèn)分頁參數(shù),即使前端未傳分頁參數(shù),也不會(huì)報(bào)錯(cuò)。
  • 支持排序,可以根據(jù)前端傳遞的 sortBy 和 isAsc 進(jìn)行排序。
  • 與 MyBatis-Plus 兼容,直接轉(zhuǎn)換為 Page<T>,減少重復(fù)代碼。

使用方式

在實(shí)際開發(fā)中,我們可以在 Service 層調(diào)用 toMpPage() 方法,將 PageQuery 轉(zhuǎn)換為 MyBatis-Plus 的 Page<T> 對(duì)象。

public Page<User> getUserList(PageQuery query) {
    Page<User> page = query.toMpPage();
    return userMapper.selectPage(page, new QueryWrapper<>());
}

參數(shù)校驗(yàn)的藝術(shù):從基礎(chǔ)校驗(yàn)到深度防御

為什么參數(shù)校驗(yàn)是系統(tǒng)安全的第一道防線?

在實(shí)際開發(fā)中,我們常遇到這樣的問題:

  • 用戶輸入手機(jī)號(hào)為"1381234abcd"
  • 訂單金額出現(xiàn)負(fù)數(shù)
  • 狀態(tài)字段傳入非法數(shù)值
  • 接口被惡意構(gòu)造異常參數(shù)攻擊

參數(shù)校驗(yàn)如同系統(tǒng)的門衛(wèi),負(fù)責(zé):

  • 攔截80%以上的常規(guī)攻擊
  • 保證業(yè)務(wù)數(shù)據(jù)的有效性
  • 提高代碼可讀性和健壯性
  • 降低下游服務(wù)的校驗(yàn)壓力

JSR 380規(guī)范的核心武器庫

基礎(chǔ)校驗(yàn)實(shí)戰(zhàn)

@PostMapping("/create")
public Result createCoupon(@Valid @RequestBody CouponFormDTO dto) {
    // 業(yè)務(wù)邏輯
}

@Data
public class CouponFormDTO {
    @NotNull(message = "優(yōu)惠券類型不能為空")
    private Integer couponType;
    
    @Range(min=1, max=10, message="限領(lǐng)數(shù)量超出范圍")
    private Integer limitCount;
    
    @EnumValid(enumeration = {0,1}, message="領(lǐng)取方式非法")
    private ReceiveEnums receiveType;
}

常用注解矩陣:

注解適用類型適用場景
@NotNull任意對(duì)象確保字段不能為空
@NotBlankString確保字符串不能為空
@NotEmpty集合/數(shù)組確保列表有數(shù)據(jù)
@SizeString/集合限制長度或元素?cái)?shù)量
@Min/@Max數(shù)值類型限制最小/最大值
@PatternString正則表達(dá)式校驗(yàn)
@EmailString郵箱格式校驗(yàn)
@FutureDate時(shí)間必須是未來時(shí)間
@PastDate時(shí)間必須是過去時(shí)間
@Digits數(shù)值類型限制整數(shù)位數(shù)和小數(shù)位數(shù)

深度解析參數(shù)校驗(yàn)原理

JSR 380 校驗(yàn)流程

  • HTTP 請(qǐng)求進(jìn)入 Controller 層,綁定參數(shù)到 DTO 對(duì)象。
  • **@Valid** 觸發(fā)校驗(yàn)機(jī)制,調(diào)用 Hibernate Validator。
  • 執(zhí)行校驗(yàn)邏輯,遍歷 DTO 字段并檢查注解規(guī)則。
  • 校驗(yàn)失敗拋出 **MethodArgumentNotValidException**。
  • 全局異常處理器捕獲異常,封裝并返回錯(cuò)誤信息。

@Valid vs. @Validated 區(qū)別

特性@Valid@Validated
作用范圍單個(gè) DTODTO + 分組校驗(yàn)
分組支持不支持支持
適用場景基礎(chǔ)校驗(yàn)復(fù)雜業(yè)務(wù)場景
@PostMapping("/update")
public Result update(@Validated(UpdateGroup.class) @RequestBody UserDTO userDTO) {
    // 業(yè)務(wù)邏輯處理
}

自定義枚舉校驗(yàn)的黑科技

數(shù)據(jù)庫設(shè)計(jì)的隱痛

我們在設(shè)計(jì)數(shù)據(jù)庫時(shí)通常會(huì)使用某個(gè)數(shù)字來代表某個(gè)狀態(tài),如:

CREATE TABLE coupon (
    status TINYINT COMMENT '0-未激活 1-已生效 2-已過期'
)

傳統(tǒng)校驗(yàn)方式:

if (!Arrays.asList(0,1,2).contains(status)) {
    throw new IllegalArgumentException();
}

缺陷

  • 校驗(yàn)邏輯分散
  • 可維護(hù)性差
  • 無法復(fù)用

自定義注解解決方案

定義枚舉校驗(yàn)注解

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EnumValidator.class)
public @interface EnumValid {
    int[] value() default {};
    String message() default "非法枚舉值";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

實(shí)現(xiàn)校驗(yàn)邏輯

public class EnumValidator implements ConstraintValidator<EnumValid, Integer> {
    private Set<Integer> allowedValues = new HashSet<>();

    @Override
    public void initialize(EnumValid constraintAnnotation) {
        Arrays.stream(constraintAnnotation.value())
              .forEach(allowedValues::add);
    }

    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        if (value == null) return true;
        return allowedValues.contains(value);
    }
}

原理深度解析

ConstraintValidator生命周期

  • 初始化:讀取注解配置
  • 校驗(yàn)時(shí):執(zhí)行isValid方法
  • 結(jié)果處理:返回布爾值

JSR 380規(guī)范實(shí)現(xiàn)要點(diǎn)

  • 校驗(yàn)器發(fā)現(xiàn)機(jī)制:SPI方式加載
  • 級(jí)聯(lián)校驗(yàn):支持對(duì)象嵌套校驗(yàn)
  • 分組校驗(yàn):實(shí)現(xiàn)不同場景的校驗(yàn)規(guī)則

復(fù)雜校驗(yàn)的終極方案

有時(shí),僅僅靠我們的校驗(yàn)比較復(fù)雜,這時(shí)我們可能需要自己來編寫校驗(yàn)邏輯

我們可以通過自定義注解+AOP來幫我們實(shí)現(xiàn)

/**
 * 實(shí)現(xiàn)后在接口訪問時(shí)如果接口實(shí)現(xiàn)了這個(gè)接口
 * 會(huì)被自動(dòng)自行接口check進(jìn)行校驗(yàn)
 **/
public interface Checker<T> {

    /**
     * 用于實(shí)現(xiàn)validation不能校驗(yàn)的數(shù)據(jù)邏輯
     */
    default void check(){

    }

    default void check(T data){
    }
}

使用示例

Data
@ApiModel(description = "章節(jié)")
public class CataSaveDTO implements Checker {
    @ApiModelProperty("章、節(jié)、練習(xí)id")
    private Long id;
    @ApiModelProperty("目錄類型1:章,2:節(jié),3:測試")
    @NotNull(message = "")
    private Integer type;
    @ApiModelProperty("章節(jié)練習(xí)名稱")
    private String name;
    @ApiModelProperty("章排序,章一定要傳,小節(jié)和練習(xí)不需要傳")
    private Integer index;

    @ApiModelProperty("當(dāng)前章的小節(jié)或練習(xí)")
    @Size(min = 1, message = "不能出現(xiàn)空章")
    private List<CataSaveDTO> sections;

    @Override
    public void check() {
        //名稱為空校驗(yàn)
        if(type == CourseConstants.CataType.CHAPTER && StringUtils.isEmpty(name)) {
            throw new BadRequestException(CourseErrorInfo.Msg.COURSE_CATAS_SAVE_NAME_NULL);
        }else if(StringUtils.isEmpty(name)){
            throw new BadRequestException(CourseErrorInfo.Msg.COURSE_CATAS_SAVE_NAME_NULL2);
        }
        //名稱長度問題
        if (type == CourseConstants.CataType.CHAPTER && name.length() > 30){
            throw new BadRequestException(CourseErrorInfo.Msg.COURSE_CATAS_SAVE_NAME_SIZE);
        }else if(name.length() > 30) {
            throw new BadRequestException(CourseErrorInfo.Msg.COURSE_CATAS_SAVE_NAME_SIZE2);
        }
        if(CollUtils.isEmpty(sections)){
            throw new BadRequestException("不能出現(xiàn)空章");
        }

    }
}

接口方法參數(shù)校驗(yàn)器

/**
 * 接口方法參數(shù)校驗(yàn)器
 **/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ParamChecker {
}

定義切面類

@Aspect
@Slf4j
@SuppressWarnings("all")
public class CheckerAspect {

    @Before("@annotation(paramChecker)")
    public void before(JoinPoint joinPoint, ParamChecker paramChecker) {
        Object[] args = joinPoint.getArgs();
        if(ArrayUtils.isNotEmpty(args)){
            //遍歷方法參數(shù),參數(shù)是否實(shí)現(xiàn)了Checker接口
            for (Object arg : args){
                if(arg instanceof Checker) {
                    //調(diào)用check方法,校驗(yàn)業(yè)務(wù)邏輯
                    ((Checker)arg).check();
                }else if(arg instanceof List){
                    //如果參數(shù)是一個(gè)集合也要校驗(yàn)
                    CollUtils.check((List) arg);
                }
            }
        }
    }
}

工具方法

    /**
     * 集合校驗(yàn)邏輯
     *
     * @param data 要校驗(yàn)的集合
     * @param checker 校驗(yàn)器
     * @param <T> 集合元素類型
     */
    public static  <T> void  check(List<T> data, Checker<T> checker){
        if(data == null){
            return;
        }
        for (T t : data){
            checker.check(t);
        }
    }

    /**
     * 集合校驗(yàn)邏輯
     *
     * @param data 要校驗(yàn)的集合
     * @param <T> 集合元素類型
     */
    public static  <T extends Checker<T>> void  check(List<T> data){
        if(data == null){
            return;
        }
        for (T t : data){
            t.check();
        }
    }

注解使用

  @PostMapping("baseInfo/save")
    @ApiOperation("保存課程基本信息")
    @ParamChecker
    //校驗(yàn)非業(yè)務(wù)限制的字段
    public CourseSaveVO save(@RequestBody @Validated(CourseSaveBaseGroup.class) CourseBaseInfoSaveDTO courseBaseInfoSaveDTO) {
        return courseDraftService.save(courseBaseInfoSaveDTO);
    }

注意:切面類沒有納入ioc容器管理,如果是單體項(xiàng)目加上component注解即可,如果是多模塊項(xiàng)目,使用自動(dòng)裝配功能

總結(jié)

到此這篇關(guān)于Java Web開發(fā)中的分頁與參數(shù)校驗(yàn)舉例詳解的文章就介紹到這了,更多相關(guān)Java Web分頁與參數(shù)校驗(yàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

余庆县| 玛纳斯县| 石首市| 利辛县| 卓资县| 久治县| 泾川县| 赤水市| 马关县| 瓮安县| 民乐县| 吉木乃县| 瓮安县| 喀喇沁旗| 日照市| 读书| 桂东县| 深水埗区| 阳高县| 武宁县| 贵南县| 吉林省| 新兴县| 电白县| 武胜县| 壤塘县| 安阳县| 文登市| 中卫市| 长顺县| 哈尔滨市| 甘谷县| 龙山县| 三穗县| 卓尼县| 岳池县| 桦川县| 大名县| 盐亭县| 云安县| 喀喇沁旗|