学习进度条

今日所花时间:一小时
今日代码量:100行
博客量:1篇
了解到的知识点:条件查询

科技政策一点通,高级检索功能
实体类代码:
policy

package com.example.search.pojo;

import jakarta.persistence.*;
import java.util.Date;

@Entity
@Table(name = "policy")
public class policy {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Long id;

    @Column(name = "name")
    private String name;

    @Column(name = "type")
    private String type;

    @Column(name = "category")
    private String category;

    @Column(name = "range")
    private String range;

    @Column(name = "document")
    private String document;

    @Column(name = "form")
    private String form;

    @Column(name = "organ")
    private String organ;

    @Column(name = "viadata")
    private Date viadata;

    @Column(name = "pubdata")
    private Date pubdata;

    @Column(name = "perdata")
    private Date perdata;

    @Column(name = "field")
    private String field;

    @Column(name = "theme")
    private String theme;

    @Column(name = "keyword")
    private String keyword;

    @Column(name = "superior")
    private String superior;

    @Column(name = "precursor")
    private String precursor;

    @Column(name = "succeed")
    private String succeed;

    @Column(name = "state")
    private String state;

    @Column(name = "text", columnDefinition = "longtext")
    private String text;

    @Column(name = "pdf")
    private String pdf;

    @Column(name = "redundancy")
    private String redundancy;

    @Column(name = "rank")
    private String rank;

    @Column(name = "policykey", columnDefinition = "text")
    private String policykey;

    @Column(name = "newrank")
    private String newrank;

    @Column(name = "year")
    private String year;

    @Column(name = "newkey")
    private String newkey;

    @Column(name = "secondtheme")
    private String secondtheme;

    @Column(name = "allsum")
    private Integer allsum;

    // Getters and Setters
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getCategory() {
        return category;
    }

    public void setCategory(String category) {
        this.category = category;
    }

    public String getRange() {
        return range;
    }

    public void setRange(String range) {
        this.range = range;
    }

    public String getDocument() {
        return document;
    }

    public void setDocument(String document) {
        this.document = document;
    }

    public String getForm() {
        return form;
    }

    public void setForm(String form) {
        this.form = form;
    }

    public String getOrgan() {
        return organ;
    }

    public void setOrgan(String organ) {
        this.organ = organ;
    }

    public Date getViadata() {
        return viadata;
    }

    public void setViadata(Date viadata) {
        this.viadata = viadata;
    }

    public Date getPubdata() {
        return pubdata;
    }

    public void setPubdata(Date pubdata) {
        this.pubdata = pubdata;
    }

    public Date getPerdata() {
        return perdata;
    }

    public void setPerdata(Date perdata) {
        this.perdata = perdata;
    }

    public String getField() {
        return field;
    }

    public void setField(String field) {
        this.field = field;
    }

    public String getTheme() {
        return theme;
    }

    public void setTheme(String theme) {
        this.theme = theme;
    }

    public String getKeyword() {
        return keyword;
    }

    public void setKeyword(String keyword) {
        this.keyword = keyword;
    }

    public String getSuperior() {
        return superior;
    }

    public void setSuperior(String superior) {
        this.superior = superior;
    }

    public String getPrecursor() {
        return precursor;
    }

    public void setPrecursor(String precursor) {
        this.precursor = precursor;
    }

    public String getSucceed() {
        return succeed;
    }

    public void setSucceed(String succeed) {
        this.succeed = succeed;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public String getPdf() {
        return pdf;
    }

    public void setPdf(String pdf) {
        this.pdf = pdf;
    }

    public String getRedundancy() {
        return redundancy;
    }

    public void setRedundancy(String redundancy) {
        this.redundancy = redundancy;
    }

    public String getRank() {
        return rank;
    }

    public void setRank(String rank) {
        this.rank = rank;
    }

    public String getPolicykey() {
        return policykey;
    }

    public void setPolicykey(String policykey) {
        this.policykey = policykey;
    }

    public String getNewrank() {
        return newrank;
    }

    public void setNewrank(String newrank) {
        this.newrank = newrank;
    }

    public String getYear() {
        return year;
    }

    public void setYear(String year) {
        this.year = year;
    }

    public String getNewkey() {
        return newkey;
    }

    public void setNewkey(String newkey) {
        this.newkey = newkey;
    }

    public String getSecondtheme() {
        return secondtheme;
    }

    public void setSecondtheme(String secondtheme) {
        this.secondtheme = secondtheme;
    }

    public Integer getAllsum() {
        return allsum;
    }

    public void setAllsum(Integer allsum) {
        this.allsum = allsum;
    }
}

PolicyType

package com.example.search.pojo;


import java.util.List;

public class PolicyType {
    private String typeId;
    private String typeName;
    private String parentId;
    private List<PolicyType> children;

    // Getters and Setters
    public String getTypeId() {
        return typeId;
    }

    public void setTypeId(String typeId) {
        this.typeId = typeId;
    }

    public String getTypeName() {
        return typeName;
    }

    public void setTypeName(String typeName) {
        this.typeName = typeName;
    }

    public String getParentId() {
        return parentId;
    }

    public void setParentId(String parentId) {
        this.parentId = parentId;
    }

    public List<PolicyType> getChildren() {
        return children;
    }

    public void setChildren(List<PolicyType> children) {
        this.children = children;
    }
}

controller层代码

package com.example.search.controller;

import com.example.search.mapper.SelectMapper;
import com.example.search.pojo.PolicyType;
import com.example.search.pojo.policy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;


import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/api")
public class SelectController {

    @Autowired
    private SelectMapper selectMapper;

    @GetMapping("/searchPolicies")
    @Transactional(readOnly = true)
    public ResponseEntity<List<policy>> searchPolicies(
            @RequestParam String keyword,
            @RequestParam(required = false) String startDate,
            @RequestParam(required = false) String organ,
            @RequestParam(required = false) String organNot) {

        try {
            List<policy> policies = selectMapper.findByNameContainingOrKeywordContaining(
                    keyword,
                    startDate,
                    organ,
                    organNot);
            return ResponseEntity.ok(policies);
        } catch (Exception e) {
            return ResponseEntity.internalServerError().build();
        }
    }

    @GetMapping("/policy/{id}")
    @Transactional(readOnly = true)
    public ResponseEntity<policy> showPolicyFullText(@PathVariable Long id) {
        try {
            policy policy = selectMapper.findById(id);
            if (policy != null) {
                return ResponseEntity.ok(policy);
            }
            return ResponseEntity.notFound().build();
        } catch (Exception e) {
            return ResponseEntity.internalServerError().build();
        }
    }

    @GetMapping("/policyTypes")
    @Transactional(readOnly = true)
    public ResponseEntity<List<PolicyType>> getAllPolicyTypes() {
        try {
            List<PolicyType> policyTypes = selectMapper.findAllPolicyTypes();
            return ResponseEntity.ok(policyTypes);
        } catch (Exception e) {
            return ResponseEntity.internalServerError().build();
        }
    }

    @GetMapping("/policiesByType")
    @Transactional(readOnly = true)
    public ResponseEntity<List<policy>> getPoliciesByType(
            @RequestParam(required = false) String typeNames, // 参数名改为typeNames
            @RequestParam(defaultValue = "1") int pageNum,
            @RequestParam(defaultValue = "10") int pageSize,
            @RequestParam(required = false) String startDate,
            @RequestParam(required = false) String organ,
            @RequestParam(required = false) String organNot) {

        try {
            int offset = (pageNum - 1) * pageSize;

            if (typeNames == null || typeNames.isEmpty()) {
                List<policy> policies = selectMapper.findAllPolicies(
                        offset,
                        pageSize,
                        startDate,
                        organ,
                        organNot
                );
                return ResponseEntity.ok(policies);
            } else {
                // 处理分类名称列表
                List<String> typeNameList = Arrays.stream(typeNames.split(","))
                        .map(String::trim)
                        .filter(name -> !name.isEmpty())
                        .collect(Collectors.toList());

                List<policy> policies = selectMapper.findByTypeNames(
                        typeNameList,
                        offset,
                        pageSize,
                        startDate,
                        organ,
                        organNot
                );
                return ResponseEntity.ok(policies);
            }
        } catch (Exception e) {
            return ResponseEntity.internalServerError().build();
        }
    }

    @GetMapping("/policyCountByType")
    @Transactional(readOnly = true)
    public ResponseEntity<Integer> getPolicyCountByType(
            @RequestParam(required = false) String typeNames, // 参数名改为typeNames
            @RequestParam(required = false) String startDate,
            @RequestParam(required = false) String organ,
            @RequestParam(required = false) String organNot) {

        try {
            if (typeNames == null || typeNames.isEmpty()) {
                int count = selectMapper.countAllPolicies(
                        startDate,
                        organ,
                        organNot
                );
                return ResponseEntity.ok(count);
            } else {
                // 处理分类名称列表
                List<String> typeNameList = Arrays.stream(typeNames.split(","))
                        .map(String::trim)
                        .filter(name -> !name.isEmpty())
                        .collect(Collectors.toList());

                int count = selectMapper.countByTypeNames(
                        typeNameList,
                        startDate,
                        organ,
                        organNot
                );
                return ResponseEntity.ok(count);
            }
        } catch (Exception e) {
            return ResponseEntity.internalServerError().build();
        }
    }
    @GetMapping("/advancedSearch")
    public ResponseEntity<List<policy>> advancedSearch(
            @RequestParam(required = false) String title,
            @RequestParam(required = false) String content,
            @RequestParam(required = false) String organ,
            @RequestParam(required = false) String policyType,
            @RequestParam(required = false) String documentNumber,
            @RequestParam(defaultValue = "1") int pageNum,
            @RequestParam(defaultValue = "10") int pageSize,
            @RequestParam(required = false) String startDate,
            @RequestParam(required = false) String typeNames) {

        try {
            int offset = (pageNum - 1) * pageSize;

            // 处理分类名称列表
            List<String> typeNameList = null;
            if (typeNames != null && !typeNames.isEmpty()) {
                typeNameList = Arrays.stream(typeNames.split(","))
                        .map(String::trim)
                        .filter(name -> !name.isEmpty())
                        .collect(Collectors.toList());
            }

            List<policy> policies = selectMapper.advancedSearch(
                    title, content, organ, policyType, documentNumber,
                    offset, pageSize, startDate, typeNameList);
            return ResponseEntity.ok(policies);
        } catch (Exception e) {
            return ResponseEntity.internalServerError().build();
        }
    }

    @GetMapping("/advancedSearchCount")
    public ResponseEntity<Integer> advancedSearchCount(
            @RequestParam(required = false) String title,
            @RequestParam(required = false) String content,
            @RequestParam(required = false) String organ,
            @RequestParam(required = false) String policyType,
            @RequestParam(required = false) String documentNumber,
            @RequestParam(required = false) String startDate,
            @RequestParam(required = false) String typeNames) {

        try {
            // 处理分类名称列表
            List<String> typeNameList = null;
            if (typeNames != null && !typeNames.isEmpty()) {
                typeNameList = Arrays.stream(typeNames.split(","))
                        .map(String::trim)
                        .filter(name -> !name.isEmpty())
                        .collect(Collectors.toList());
            }

            int count = selectMapper.advancedSearchCount(
                    title, content, organ, policyType, documentNumber,
                    startDate, typeNameList);
            return ResponseEntity.ok(count);
        } catch (Exception e) {
            return ResponseEntity.internalServerError().build();
        }
    }
}

mapper层代码(使用MyBatis映射)根据以下代码完成动态查询
selectMapper

package com.example.search.mapper;

import com.example.search.pojo.PolicyType;
import com.example.search.pojo.policy;
import org.apache.ibatis.annotations.*;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Mapper
public interface SelectMapper {
    @SelectProvider(type = PolicySqlProvider.class, method = "findByNameContainingOrKeywordContaining")
    @Transactional(readOnly = true)
    List<policy> findByNameContainingOrKeywordContaining(
            @Param("keyword") String keyword,
            @Param("startDate") String startDate,
            @Param("organ") String organ,
            @Param("organNot") String organNot);

    @Select("SELECT * FROM policy WHERE id = #{id}")
    @Transactional(readOnly = true)
    policy findById(Long id);

    @Select("SELECT type_id as typeId, type_name as typeName, parent_id as parentId FROM policy_type ORDER BY type_id")
    @Transactional(readOnly = true)
    List<PolicyType> findAllPolicyTypes();

    @SelectProvider(type = PolicySqlProvider.class, method = "findByTypeNames")
    @Transactional(readOnly = true)
    List<policy> findByTypeNames(
            @Param("typeNames") List<String> typeNames,
            @Param("offset") int offset,
            @Param("pageSize") int pageSize,
            @Param("startDate") String startDate,
            @Param("organ") String organ,
            @Param("organNot") String organNot);

    @SelectProvider(type = PolicySqlProvider.class, method = "countByTypeNames")
    @Transactional(readOnly = true)
    int countByTypeNames(
            @Param("typeNames") List<String> typeNames,
            @Param("startDate") String startDate,
            @Param("organ") String organ,
            @Param("organNot") String organNot);

    @SelectProvider(type = PolicySqlProvider.class, method = "findAllPolicies")
    @Transactional(readOnly = true)
    List<policy> findAllPolicies(
            @Param("offset") int offset,
            @Param("pageSize") int pageSize,
            @Param("startDate") String startDate,
            @Param("organ") String organ,
            @Param("organNot") String organNot);

    @SelectProvider(type = PolicySqlProvider.class, method = "countAllPolicies")
    @Transactional(readOnly = true)
    int countAllPolicies(
            @Param("startDate") String startDate,
            @Param("organ") String organ,
            @Param("organNot") String organNot);

    @SelectProvider(type = PolicySqlProvider.class, method = "advancedSearch")
    List<policy> advancedSearch(
            @Param("title") String title,
            @Param("content") String content,
            @Param("organ") String organ,
            @Param("policyType") String policyType,
            @Param("documentNumber") String documentNumber,
            @Param("offset") int offset,
            @Param("pageSize") int pageSize,
            @Param("startDate") String startDate,
            @Param("typeNames") List<String> typeNames);

    @SelectProvider(type = PolicySqlProvider.class, method = "advancedSearchCount")
    int advancedSearchCount(
            @Param("title") String title,
            @Param("content") String content,
            @Param("organ") String organ,
            @Param("policyType") String policyType,
            @Param("documentNumber") String documentNumber,
            @Param("startDate") String startDate,
            @Param("typeNames") List<String> typeNames);
}

PolicySqlProvider

package com.example.search.mapper;

import org.apache.ibatis.jdbc.SQL;
import org.junit.platform.commons.util.StringUtils;

import java.util.List;

public class PolicySqlProvider {
    public String findByTypeNames(
            List<String> typeNames,
            int offset,
            int pageSize,
            String startDate,
            String organ,
            String organNot) {

        return new SQL() {{
            SELECT("*");
            FROM("policy");
            if (typeNames != null && !typeNames.isEmpty()) {
                WHERE(buildTypeNameCondition(typeNames));
            }
            if (startDate != null && !startDate.trim().isEmpty()) {
                WHERE("pubdata >= #{startDate}");
            }
            if (organ != null && !organ.trim().isEmpty()) {
                WHERE("organ LIKE CONCAT('%', #{organ}, '%')");
            }
            if (organNot != null && !organNot.trim().isEmpty()) {
                WHERE("organ NOT LIKE CONCAT('%', #{organNot}, '%')");
            }
            ORDER_BY("pubdata DESC");
        }}.toString() + " LIMIT #{offset}, #{pageSize}";
    }

    public String countByTypeNames(
            List<String> typeNames,
            String startDate,
            String organ,
            String organNot) {

        return new SQL() {{
            SELECT("COUNT(*)");
            FROM("policy");
            if (typeNames != null && !typeNames.isEmpty()) {
                WHERE(buildTypeNameCondition(typeNames));
            }
            if (startDate != null && !startDate.trim().isEmpty()) {
                WHERE("pubdata >= #{startDate}");
            }
            if (organ != null && !organ.trim().isEmpty()) {
                WHERE("organ LIKE CONCAT('%', #{organ}, '%')");
            }
            if (organNot != null && !organNot.trim().isEmpty()) {
                WHERE("organ NOT LIKE CONCAT('%', #{organNot}, '%')");
            }
        }}.toString();
    }

    public String findAllPolicies(
            int offset,
            int pageSize,
            String startDate,
            String organ,
            String organNot) {

        return new SQL() {{
            SELECT("*");
            FROM("policy");
            if (startDate != null && !startDate.trim().isEmpty()) {
                WHERE("pubdata >= #{startDate}");
            }
            if (organ != null && !organ.trim().isEmpty()) {
                WHERE("organ LIKE CONCAT('%', #{organ}, '%')");
            }
            if (organNot != null && !organNot.trim().isEmpty()) {
                WHERE("organ NOT LIKE CONCAT('%', #{organNot}, '%')");
            }
            ORDER_BY("pubdata DESC");
        }}.toString() + " LIMIT #{offset}, #{pageSize}";
    }

    public String countAllPolicies(
            String startDate,
            String organ,
            String organNot) {

        return new SQL() {{
            SELECT("COUNT(*)");
            FROM("policy");
            if (startDate != null && !startDate.trim().isEmpty()) {
                WHERE("pubdata >= #{startDate}");
            }
            if (organ != null && !organ.trim().isEmpty()) {
                WHERE("organ LIKE CONCAT('%', #{organ}, '%')");
            }
            if (organNot != null && !organNot.trim().isEmpty()) {
                WHERE("organ NOT LIKE CONCAT('%', #{organNot}, '%')");
            }
        }}.toString();
    }

    public String findByNameContainingOrKeywordContaining(
            String keyword,
            String startDate,
            String organ,
            String organNot) {

        return new SQL() {{
            SELECT("*");
            FROM("policy");
            WHERE("(name LIKE CONCAT('%', #{keyword}, '%') OR keyword LIKE CONCAT('%', #{keyword}, '%'))");
            if (startDate != null && !startDate.trim().isEmpty()) {
                WHERE("pubdata >= #{startDate}");
            }
            if (organ != null && !organ.trim().isEmpty()) {
                WHERE("organ LIKE CONCAT('%', #{organ}, '%')");
            }
            if (organNot != null && !organNot.trim().isEmpty()) {
                WHERE("organ NOT LIKE CONCAT('%', #{organNot}, '%')");
            }
            ORDER_BY("pubdata DESC");
        }}.toString();
    }

    /**
     * 构建LIKE查询条件,用于匹配分类ID及其子分类
     * 例如:type LIKE '0100%' OR type LIKE '0200%'
     */
    private String buildLikeClause(List<String> typeIds) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < typeIds.size(); i++) {
            if (i > 0) {
                sb.append(" OR ");
            }
            sb.append("type LIKE '").append(typeIds.get(i)).append("%'");
        }
        return sb.toString();
    }

    /**
     * 原始方法,用于精确匹配分类ID
     * 保留但不使用,作为参考
     */
    private String buildInClause(List<String> typeIds) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < typeIds.size(); i++) {
            if (i > 0) {
                sb.append(",");
            }
            sb.append("'").append(typeIds.get(i)).append("'");
        }
        return sb.toString();
    }
    // 其他方法保持不变...

    /**
     * 构建分类名称查询条件
     */
    private String buildTypeNameCondition(List<String> typeNames) {
        StringBuilder sb = new StringBuilder("(");
        for (int i = 0; i < typeNames.size(); i++) {
            if (i > 0) {
                sb.append(" OR ");
            }
            sb.append("type = #{typeNames[").append(i).append("]}");
        }
        sb.append(")");
        return sb.toString();
    }

    public String advancedSearch(
            String title, String content, String organ,
            String policyType, String documentNumber,
            int offset, int pageSize, String startDate,
            List<String> typeNames) {
        return new SQL() {{
            SELECT("*");
            FROM("policy");
            if (StringUtils.isNotBlank(title)) {
                WHERE("name LIKE CONCAT('%', #{title}, '%')");
            }
            if (StringUtils.isNotBlank(content)) {
                WHERE("text LIKE CONCAT('%', #{content}, '%')");
            }
            if (StringUtils.isNotBlank(organ)) {
                WHERE("organ LIKE CONCAT('%', #{organ}, '%')");
            }
            if (StringUtils.isNotBlank(policyType)) {
                WHERE("type = #{policyType}");
            }
            if (StringUtils.isNotBlank(documentNumber)) {
                WHERE("document LIKE CONCAT('%', #{documentNumber}, '%')");
            }
            if (StringUtils.isNotBlank(startDate)) {
                WHERE("pubdata >= #{startDate}");
            }
            if (typeNames != null && !typeNames.isEmpty()) {
                WHERE(buildTypeNameCondition(typeNames));
            }
            ORDER_BY("pubdata DESC");
        }}.toString() + " LIMIT #{offset}, #{pageSize}";
    }

    public String advancedSearchCount(
            String title, String content, String organ,
            String policyType, String documentNumber,
            String startDate, List<String> typeNames) {
        return new SQL() {{
            SELECT("COUNT(*)");
            FROM("policy");
            if (StringUtils.isNotBlank(title)) {
                WHERE("name LIKE CONCAT('%', #{title}, '%')");
            }
            if (StringUtils.isNotBlank(content)) {
                WHERE("text LIKE CONCAT('%', #{content}, '%')");
            }
            if (StringUtils.isNotBlank(organ)) {
                WHERE("organ LIKE CONCAT('%', #{organ}, '%')");
            }
            if (StringUtils.isNotBlank(policyType)) {
                WHERE("type = #{policyType}");
            }
            if (StringUtils.isNotBlank(documentNumber)) {
                WHERE("document LIKE CONCAT('%', #{documentNumber}, '%')");
            }
            if (StringUtils.isNotBlank(startDate)) {
                WHERE("pubdata >= #{startDate}");
            }
            if (typeNames != null && !typeNames.isEmpty()) {
                WHERE(buildTypeNameCondition(typeNames));
            }
        }}.toString();
    }

}

PolicyMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.search.mapper.SelectMapper">
    <!-- 根据多个类型ID查询政策信息 -->
    <select id="findPoliciesByTypeIds" resultType="com.example.search.pojo.policy">
        SELECT name, pubdata FROM policy WHERE type IN
        <foreach item="typeId" collection="typeIds" open="(" separator="," close=")">
            #{typeId}
        </foreach>
    </select>

    <!-- 根据多个类型ID、分页、时间和机构筛选查询政策信息 -->
    <select id="findByTypeIds" resultType="com.example.search.pojo.policy">
        SELECT * FROM policy
        <where>
            <if test="typeIds != null and typeIds.size() > 0">
                type IN
                <foreach item="typeId" collection="typeIds" open="(" separator="," close=")">
                    #{typeId}
                </foreach>
            </if>
            <if test="startDate != null">
                AND pubdata >= #{startDate}
            </if>
            <if test="organ != null">
                AND organ LIKE CONCAT('%', #{organ}, '%')
            </if>
            <if test="organNot != null">
                AND organ NOT LIKE CONCAT('%', #{organNot}, '%')
            </if>
        </where>
        LIMIT #{offset}, #{pageSize}
    </select>

    <!-- 根据多个类型ID、时间和机构筛选统计政策数量 -->
    <select id="countByTypeIds" resultType="int">
        SELECT COUNT(*) FROM policy
        <where>
            <if test="typeIds != null and typeIds.size() > 0">
                type IN
                <foreach item="typeId" collection="typeIds" open="(" separator="," close=")">
                    #{typeId}
                </foreach>
            </if>
            <if test="startDate != null">
                AND pubdata >= #{startDate}
            </if>
            <if test="organ != null">
                AND organ LIKE CONCAT('%', #{organ}, '%')
            </if>
            <if test="organNot != null">
                AND organ NOT LIKE CONCAT('%', #{organNot}, '%')
            </if>
        </where>
    </select>
    <select id="findByTypeNames" resultType="com.example.search.pojo.policy">
        SELECT * FROM policy
        <where>
            <if test="typeNames != null and typeNames.size() > 0">
                type IN
                <foreach item="typeName" collection="typeNames" open="(" separator="," close=")">
                    #{typeName}
                </foreach>
            </if>
            <if test="startDate != null">
                AND pubdata >= #{startDate}
            </if>
            <if test="organ != null">
                AND organ LIKE CONCAT('%', #{organ}, '%')
            </if>
            <if test="organNot != null">
                AND organ NOT LIKE CONCAT('%', #{organNot}, '%')
            </if>
        </where>
        ORDER BY pubdata DESC
        LIMIT #{offset}, #{pageSize}
    </select>

    <!-- 根据分类名称统计政策数量 -->
    <select id="countByTypeNames" resultType="int">
        SELECT COUNT(*) FROM policy
        <where>
            <if test="typeNames != null and typeNames.size() > 0">
                type IN
                <foreach item="typeName" collection="typeNames" open="(" separator="," close=")">
                    #{typeName}
                </foreach>
            </if>
            <if test="startDate != null">
                AND pubdata >= #{startDate}
            </if>
            <if test="organ != null">
                AND organ LIKE CONCAT('%', #{organ}, '%')
            </if>
            <if test="organNot != null">
                AND organ NOT LIKE CONCAT('%', #{organNot}, '%')
            </if>
        </where>
    </select>
</mapper>

MyBatis
核心ORM框架:用于Java对象与数据库表之间的映射。
注解方式:使用@Select, @SelectProvider等注解直接在接口方法上定义SQL查询。
XML映射:通过XML文件定义复杂的SQL查询和结果映射(如findPoliciesByTypeIds等)。
动态SQL:通过, 等标签构建动态SQL查询条件。
2. MyBatis SQL Provider
@SelectProvider:动态生成SQL语句,通过PolicySqlProvider类中的方法返回SQL字符串。
SQL构建器:使用MyBatis提供的SQL类以编程方式构建SQL语句,支持条件判断和动态拼接。
3. Spring事务管理
@Transactional:标记方法为只读事务,确保查询操作在事务中执行(虽然只读事务对性能影响较小,但保持了一致性)。
4. 动态查询条件
参数校验:通过StringUtils.isNotBlank等方法检查参数是否有效。
条件拼接:根据参数动态添加WHERE子句,例如按时间范围、机构名称、政策类型等过滤。
5. 分页查询
LIMIT子句:通过offset和pageSize实现分页查询(MySQL语法)。
计数查询:配套的count*方法用于计算满足条件的总记录数,支持分页控件。
6. LIKE模糊查询
CONCAT函数:拼接%实现模糊匹配(如organ LIKE CONCAT('%', #{organ}, '%'))。
7. 集合参数处理
标签(XML中):遍历集合参数(如typeNames),生成IN (..., ...)条件。
动态构建OR条件(Provider中):通过buildTypeNameCondition方法为多个分类名称生成type = ? OR type = ?条件。
8. 结果映射
别名映射:如type_id as typeId,将数据库字段名映射到Java对象属性。
POJO映射:查询结果自动转换为policy或PolicyType对象。
9. 高级搜索功能
多字段组合查询:支持标题、内容、机构、文号、分类等字段的任意组合筛选。
时间范围过滤:通过pubdata >= #{startDate}筛选发布时间。
10. 代码组织
分离SQL逻辑:将复杂SQL拆分到Provider类或XML中,保持接口简洁。
复用代码:通过私有方法(如buildTypeNameCondition)复用条件构建逻辑。
前端html代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>科技政策一点通</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
    <style>
        :root {
            --primary-color: #2c7be5;
            --secondary-color: #6e84a3;
            --accent-color: #00d97e;
            --light-bg: #f9fbfd;
            --card-bg: #ffffff;
            --border-color: #e3ebf6;
            --text-color: #12263f;
            --text-muted: #95aac9;
            --shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
            --shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
            --radius: 0.375rem;
            --radius-lg: 0.5rem;
        }

        body {
            font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
            background-color: var(--light-bg);
            color: var(--text-color);
            margin: 0;
            padding: 0;
            line-height: 1.6;
        }

        .header {
            background: linear-gradient(135deg, var(--primary-color), #1a5cb8);
            color: white;
            padding: 1.25rem 0;
            margin-bottom: 1.5rem;
            box-shadow: var(--shadow);
        }

        .header h1 {
            font-size: 1.75rem;
            font-weight: 700;
            margin: 0;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 0.75rem;
        }

        .nav-tabs {
            background-color: var(--card-bg);
            padding: 0.75rem 1.5rem;
            border-bottom: 1px solid var(--border-color);
        }

        .nav-tabs .nav-link {
            color: var(--secondary-color);
            border: none;
            padding: 0.75rem 1.25rem;
            font-weight: 600;
            transition: all 0.2s ease;
            border-radius: var(--radius);
            margin-right: 0.5rem;
        }

        .nav-tabs .nav-link.active {
            color: var(--primary-color);
            background-color: rgba(44, 123, 229, 0.1);
            border-bottom: 3px solid var(--primary-color);
        }

        .nav-tabs .nav-link:hover:not(.active) {
            color: var(--primary-color);
            background-color: rgba(44, 123, 229, 0.05);
        }

        .sidebar {
            background-color: var(--card-bg);
            padding: 1.5rem;
            border-radius: var(--radius-lg);
            box-shadow: var(--shadow-sm);
            height: calc(100vh - 11rem);
            overflow-y: auto;
        }

        .sidebar h5 {
            color: var(--primary-color);
            font-weight: 600;
            margin-bottom: 1rem;
            display: flex;
            align-items: center;
            gap: 0.5rem;
        }

        .main-content {
            background-color: var(--card-bg);
            padding: 1.5rem;
            border-radius: var(--radius-lg);
            box-shadow: var(--shadow-sm);
            min-height: calc(100vh - 11rem);
        }

        .tree-item {
            padding: 0.75rem;
            cursor: pointer;
            display: flex;
            align-items: center;
            border-radius: var(--radius);
            transition: all 0.2s ease;
            margin-bottom: 0.25rem;
        }

        .tree-item:hover {
            background-color: rgba(44, 123, 229, 0.05);
        }

        .tree-checkbox {
            margin-right: 0.75rem;
            width: 1.1em;
            height: 1.1em;
            border: 2px solid var(--secondary-color);
            border-radius: 0.25rem;
        }

        .tree-checkbox:checked {
            background-color: var(--primary-color);
            border-color: var(--primary-color);
        }

        .tree-toggle {
            display: inline-flex;
            width: 1.5rem;
            height: 1.5rem;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            color: var(--secondary-color);
            transition: all 0.2s ease;
        }

        .tree-toggle:hover {
            color: var(--primary-color);
        }

        .tree-label {
            flex: 1;
            font-size: 0.95rem;
            color: var(--text-color);
            font-weight: 500;
        }

        .tree-children {
            padding-left: 1.5rem;
            display: none;
        }

        .tree-item.expanded > .tree-children {
            display: block;
        }

        .policy-item {
            padding: 1.25rem;
            border-radius: var(--radius);
            background-color: var(--card-bg);
            margin-bottom: 1rem;
            box-shadow: var(--shadow-sm);
            transition: all 0.2s ease;
            border-left: 4px solid var(--primary-color);
        }

        .policy-item:hover {
            transform: translateY(-2px);
            box-shadow: var(--shadow);
        }

        .policy-title {
            color: var(--primary-color);
            font-weight: 600;
            margin-bottom: 0.5rem;
            font-size: 1.05rem;
        }

        .policy-date {
            color: var(--text-muted);
            font-size: 0.85rem;
        }

        .search-box {
            margin-bottom: 1.5rem;
            background-color: var(--card-bg);
            padding: 1.25rem;
            border-radius: var(--radius-lg);
            box-shadow: var(--shadow-sm);
        }

        .filter-box {
            margin-bottom: 1.5rem;
            background-color: var(--card-bg);
            padding: 1.25rem;
            border-radius: var(--radius-lg);
            box-shadow: var(--shadow-sm);
        }

        .filter-group {
            margin-bottom: 1rem;
        }

        .filter-group-title {
            font-weight: 600;
            margin-bottom: 0.75rem;
            color: var(--text-color);
            font-size: 0.95rem;
        }

        .form-check-input:checked {
            background-color: var(--primary-color);
            border-color: var(--primary-color);
        }

        .pagination {
            justify-content: center;
            margin-top: 1.5rem;
        }

        .page-item.active .page-link {
            background-color: var(--primary-color);
            border-color: var(--primary-color);
        }

        .page-link {
            color: var(--primary-color);
        }

        .no-results {
            text-align: center;
            padding: 3rem;
            color: var(--text-muted);
            font-size: 1rem;
        }

        .loading-spinner {
            display: none;
            text-align: center;
            padding: 2rem;
        }

        .select-all-container {
            font-weight: 600;
            margin-bottom: 1rem;
            border-bottom: 1px solid var(--border-color);
            padding-bottom: 0.75rem;
            color: var(--primary-color);
        }

        .btn-primary {
            background-color: var(--primary-color);
            border-color: var(--primary-color);
            transition: all 0.2s ease;
        }

        .btn-primary:hover {
            background-color: #1a5cb8;
            border-color: #1a5cb8;
            transform: translateY(-1px);
        }

        .btn-outline-primary {
            color: var(--primary-color);
            border-color: var(--primary-color);
        }

        .btn-outline-primary:hover {
            background-color: var(--primary-color);
            border-color: var(--primary-color);
        }

        .alert-info {
            background-color: rgba(44, 123, 229, 0.1);
            border-color: rgba(44, 123, 229, 0.2);
            color: var(--primary-color);
        }

        /* 高级搜索模态框样式 */
        .modal-content {
            border-radius: var(--radius-lg);
            border: none;
            box-shadow: var(--shadow);
        }

        .modal-header {
            background-color: var(--primary-color);
            color: white;
            border-radius: var(--radius-lg) var(--radius-lg) 0 0;
            padding: 1.25rem 1.5rem;
        }

        .modal-title {
            font-weight: 600;
            font-size: 1.25rem;
        }

        #advancedSearchForm label {
            font-weight: 500;
            margin-bottom: 0.5rem;
            color: var(--text-color);
            font-size: 0.95rem;
        }

        #advancedSearchForm .form-control,
        #advancedSearchForm .form-select {
            border-radius: var(--radius);
            border: 1px solid var(--border-color);
            padding: 0.5rem 0.75rem;
            transition: all 0.2s ease;
        }

        #advancedSearchForm .form-control:focus,
        #advancedSearchForm .form-select:focus {
            border-color: var(--primary-color);
            box-shadow: 0 0 0 0.25rem rgba(44, 123, 229, 0.25);
        }

        #advancedSearchSubmit {
            background-color: var(--primary-color);
            border-color: var(--primary-color);
            padding: 0.5rem 1.25rem;
            font-weight: 500;
        }

        #advanced-search-button {
            background-color: var(--primary-color);
            border-color: var(--primary-color);
            color: #ffffff;
            padding: 0.5rem 1rem;
            border-radius: var(--radius);
            transition: all 0.2s ease;
            font-weight: 500;
        }

        #advanced-search-button:hover {
            background-color: #1a5cb8;
            border-color: #1a5cb8;
            transform: translateY(-1px);
        }

        /* 响应式调整 */
        @media (max-width: 768px) {
            .header h1 {
                font-size: 1.5rem;
            }

            .nav-tabs .nav-link {
                padding: 0.5rem 0.75rem;
                font-size: 0.9rem;
            }

            .sidebar {
                height: auto;
                margin-bottom: 1.5rem;
            }

            .main-content {
                min-height: auto;
            }
        }
    </style>
</head>
<body>
    <!-- 页面结构保持不变 -->
    <div class="header">
        <div class="container-fluid">
            <div class="row align-items-center">
                <div class="col-md-8">
                    <h1><i class="bi bi-journal-bookmark-fill"></i> 科技政策一点通</h1>
                </div>
                <div class="col-md-4 text-end">
                    <div class="input-group" style="width: 300px;">
                        <input type="text" id="search-input" class="form-control form-control-sm" placeholder="请输入关键字">
                        <button class="btn btn-primary btn-sm" type="button" id="search-button"><i class="bi bi-search"></i> 搜索</button>

                        <button class="btn btn-outline-primary btn-sm" type="button" id="advanced-search-button">
                            <i class="bi bi-gear"></i> 高级搜索
                        </button>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <!-- 添加高级搜索模态框 -->
    <div class="modal fade" id="advancedSearchModal" tabindex="-1" aria-hidden="true">
        <div class="modal-dialog modal-lg">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">高级搜索</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                </div>
                <div class="modal-body">
                    <form id="advancedSearchForm">
                        <div class="row mb-3">
                            <div class="col-md-6">
                                <label for="title" class="form-label">政策标题</label>
                                <input type="text" class="form-control" id="title" placeholder="请输入政策标题">
                            </div>
                            <div class="col-md-6">
                                <label for="content" class="form-label">政策内容</label>
                                <input type="text" class="form-control" id="content" placeholder="请输入政策内容关键词">
                            </div>
                        </div>
                        <div class="row mb-3">
                            <div class="col-md-6">
                                <label for="organ" class="form-label">发文机构</label>
                                <input type="text" class="form-control" id="organ" placeholder="请输入发文机构">
                            </div>
                            <div class="col-md-6">
                                <label for="policyType" class="form-label">政策分类</label>
                                <select class="form-select" id="policyType">
                                    <option value="">全部分类</option>
                                    <option value="综合">综合</option>
                                    <option value="科研机构改革">科研机构改革</option>
                                    <option value="科技计划管理">科技计划管理</option>
                                    <option value="科技经费与财务">科技经费与财务</option>
                                    <option value="基础研究与科研基地">基础研究与科研基地</option>
                                    <option value="基础研究">--基础研究</option>
                                    <option value="平台基地">--平台基地</option>
                                    <option value="企业技术进步与高新技术产业化">企业技术进步与高新技术产业化</option>
                                    <option value="企业">--企业</option>
                                    <option value="产业">--产业</option>
                                    <option value="创新载体">--创新载体</option>
                                    <option value="农村科技与社会发展">农村科技与社会发展</option>
                                    <option value="科技人才">科技人才</option>
                                    <option value="科技中介服务">科技中介服务</option>
                                    <option value="科技条件与标准">科技条件与标准</option>
                                    <option value="科技金融与税收">科技金融与税收</option>
                                    <option value="科技成果与知识产权">科技成果与知识产权</option>
                                    <option value="科技奖励">科技奖励</option>
                                    <option value="科学技术普及">科学技术普及</option>
                                </select>
                            </div>
                        </div>
                        <div class="row mb-3">
                            <div class="col-md-6">
                                <label for="documentNumber" class="form-label">政策文号</label>
                                <input type="text" class="form-control" id="documentNumber" placeholder="请输入政策文号">
                            </div>
                            <div class="col-md-6">
                                <label for="dateRange" class="form-label">发布时间</label>
                                <select class="form-select" id="dateRange">
                                    <option value="">全部时间</option>
                                    <option value="1">近一个月</option>
                                    <option value="6">近半年</option>
                                    <option value="12">近一年</option>
                                    <option value="custom">自定义时间</option>
                                </select>
                            </div>
                        </div>
                        <div class="row mb-3" id="customDateRange" style="display: none;">
                            <div class="col-md-6">
                                <label for="startDate" class="form-label">开始日期</label>
                                <input type="date" class="form-control" id="startDate">
                            </div>
                            <div class="col-md-6">
                                <label for="endDate" class="form-label">结束日期</label>
                                <input type="date" class="form-control" id="endDate">
                            </div>
                        </div>
                    </form>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
                    <button type="button" class="btn btn-primary" id="advancedSearchSubmit">搜索</button>
                </div>
            </div>
        </div>
    </div>

    <div class="nav-tabs">
        <ul class="nav nav-tabs">
            <li class="nav-item">
                <a class="nav-link active" href="#">首页</a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="#">政策动态</a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="#">科技政策</a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="#">政策解读</a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="#">政策问答</a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="#">政策培训</a>
            </li>
        </ul>
    </div>

    <div class="container-fluid">
        <div class="row">
            <div class="col-md-3">
                <div class="sidebar">
                    <h5><i class="bi bi-list-ul"></i> 政策分类</h5>
                    <hr>
                    <div id="policy-tree"></div>
                </div>
            </div>
            <div class="col-md-9">
                <div class="main-content">
                    <div class="filter-box">
                        <div class="row">
                            <div class="col-md-6">
                                <div class="filter-group">
                                    <div class="filter-group-title">时间:</div>
                                    <div class="form-check form-check-inline">
                                        <input class="form-check-input" type="radio" name="timeFilter" id="timeAll" value="all" checked>
                                        <label class="form-check-label" for="timeAll">全部时间</label>
                                    </div>
                                    <div class="form-check form-check-inline">
                                        <input class="form-check-input" type="radio" name="timeFilter" id="time1" value="1">
                                        <label class="form-check-label" for="time1">近一月</label>
                                    </div>
                                    <div class="form-check form-check-inline">
                                        <input class="form-check-input" type="radio" name="timeFilter" id="time6" value="6">
                                        <label class="form-check-label" for="time6">近半年</label>
                                    </div>
                                    <div class="form-check form-check-inline">
                                        <input class="form-check-input" type="radio" name="timeFilter" id="time12" value="12">
                                        <label class="form-check-label" for="time12">近一年</label>
                                    </div>
                                </div>
                            </div>
                            <div class="col-md-6">
                                <div class="filter-group">
                                    <div class="filter-group-title">制主主体:</div>
                                    <div class="form-check form-check-inline">
                                        <input class="form-check-input" type="radio" name="sourceFilter" id="sourceAll" value="all" checked>
                                        <label class="form-check-label" for="sourceAll">全部</label>
                                    </div>
                                    <div class="form-check form-check-inline">
                                        <input class="form-check-input" type="radio" name="sourceFilter" id="sourceNational" value="national">
                                        <label class="form-check-label" for="sourceNational">国家</label>
                                    </div>
                                    <div class="form-check form-check-inline">
                                        <input class="form-check-input" type="radio" name="sourceFilter" id="sourceHebei" value="hebei">
                                        <label class="form-check-label" for="sourceHebei">河北</label>
                                    </div>
                                    <div class="form-check form-check-inline">
                                        <input class="form-check-input" type="radio" name="sourceFilter" id="sourceOther" value="other">
                                        <label class="form-check-label" for="sourceOther">外省</label>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                    
                    <div id="current-category" class="alert alert-info py-2" style="display: none;">
                        <strong>当前分类:</strong> <span id="category-name"></span>
                    </div>
                    
                    <div id="loading-spinner" class="loading-spinner">
                        <div class="spinner-border text-primary" role="status">
                            <span class="visually-hidden">加载中...</span>
                        </div>
                        <p>正在加载数据,请稍候...</p>
                    </div>
                    
                    <div id="policy-list"></div>
                    
                    <nav aria-label="Page navigation">
                        <ul class="pagination" id="pagination">
                            <!-- 分页按钮将通过JavaScript动态生成 -->
                        </ul>
                    </nav>
                </div>
            </div>
        </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
    <script>
        document.addEventListener('DOMContentLoaded', function() {
            // 加载政策分类树
            loadPolicyTree();

            // 默认加载所有政策
            loadAllPolicies(1);

            // 搜索按钮点击事件
            document.getElementById('search-button').addEventListener('click', function() {
                const keyword = document.getElementById('search-input').value.trim();
                if (keyword) {
                    searchPolicies(keyword);
                }
            });

            // 回车键搜索
            document.getElementById('search-input').addEventListener('keypress', function(e) {
                if (e.key === 'Enter') {
                    const keyword = document.getElementById('search-input').value.trim();
                    if (keyword) {
                        searchPolicies(keyword);
                    }
                }
            });

            // 时间筛选变化事件
            document.querySelectorAll('input[name="timeFilter"]').forEach(radio => {
                radio.addEventListener('change', function() {
                    updateSelectedCategories();
                });
            });

            // 来源筛选变化事件
            document.querySelectorAll('input[name="sourceFilter"]').forEach(radio => {
                radio.addEventListener('change', function() {
                    updateSelectedCategories();
                });
            });
            // 在DOMContentLoaded事件监听器中添加
            document.getElementById('advanced-search-button').addEventListener('click', function() {
                const modal = new bootstrap.Modal(document.getElementById('advancedSearchModal'));
                modal.show();
            });

            document.getElementById('dateRange').addEventListener('change', function() {
                const customDateRange = document.getElementById('customDateRange');
                if (this.value === 'custom') {
                    customDateRange.style.display = 'flex';
                } else {
                    customDateRange.style.display = 'none';
                }
            });

            document.getElementById('advancedSearchSubmit').addEventListener('click', function() {
                const modal = bootstrap.Modal.getInstance(document.getElementById('advancedSearchModal'));
                modal.hide();

                // 获取搜索条件
                const title = document.getElementById('title').value.trim();
                const content = document.getElementById('content').value.trim();
                const organ = document.getElementById('organ').value.trim();
                const policyType = document.getElementById('policyType').value;
                const documentNumber = document.getElementById('documentNumber').value.trim();

                // 处理时间范围
                let startDate = '';
                const dateRange = document.getElementById('dateRange').value;
                if (dateRange === 'custom') {
                    startDate = document.getElementById('startDate').value;
                } else if (dateRange !== '') {
                    const months = parseInt(dateRange);
                    const date = new Date();
                    date.setMonth(date.getMonth() - months);
                    startDate = date.toISOString().split('T')[0];
                }

                // 执行高级搜索
                advancedSearch(title, content, organ, policyType, documentNumber, startDate, 1);
            });

            function advancedSearch(title, content, organ, policyType, documentNumber, startDate, pageNum) {
                document.getElementById('loading-spinner').style.display = 'block';
                document.getElementById('policy-list').innerHTML = '';
                document.getElementById('pagination').innerHTML = '';
                document.getElementById('current-category').style.display = 'none';

                // 构建查询参数
                let url = `http://localhost:8080/api/advancedSearch?pageNum=${pageNum}&pageSize=10`;
                if (title) url += `&title=${encodeURIComponent(title)}`;
                if (content) url += `&content=${encodeURIComponent(content)}`;
                if (organ) url += `&organ=${encodeURIComponent(organ)}`;
                if (policyType) url += `&policyType=${encodeURIComponent(policyType)}`;
                if (documentNumber) url += `&documentNumber=${encodeURIComponent(documentNumber)}`;
                if (startDate) url += `&startDate=${startDate}`;

                // 构建数量查询URL
                let countUrl = `http://localhost:8080/api/advancedSearchCount?`;
                if (title) countUrl += `&title=${encodeURIComponent(title)}`;
                if (content) countUrl += `&content=${encodeURIComponent(content)}`;
                if (organ) countUrl += `&organ=${encodeURIComponent(organ)}`;
                if (policyType) countUrl += `&policyType=${encodeURIComponent(policyType)}`;
                if (documentNumber) countUrl += `&documentNumber=${encodeURIComponent(documentNumber)}`;
                if (startDate) countUrl += `&startDate=${startDate}`;

                // 获取政策数量
                fetch(countUrl)
                    .then(response => response.json())
                    .then(count => {
                        const totalPages = Math.ceil(count / 10);

                        // 获取当前页的政策
                        fetch(url)
                            .then(response => response.json())
                            .then(policies => {
                                document.getElementById('loading-spinner').style.display = 'none';
                                renderPolicies(policies);
                                renderAdvancedSearchPagination(title, content, organ, policyType,
                                    documentNumber, startDate, pageNum, totalPages);
                            })
                            .catch(error => {
                                document.getElementById('loading-spinner').style.display = 'none';
                                console.error('高级搜索失败:', error);
                                document.getElementById('policy-list').innerHTML =
                                    '<div class="no-results"><i class="bi bi-exclamation-circle"></i> 高级搜索失败,请稍后再试</div>';
                            });
                    })
                    .catch(error => {
                        document.getElementById('loading-spinner').style.display = 'none';
                        console.error('获取政策数量失败:', error);
                    });
            }

            function renderAdvancedSearchPagination(title, content, organ, policyType,
                                                    documentNumber, startDate, currentPage, totalPages) {

                const pagination = document.getElementById('pagination');
                pagination.innerHTML = '';

                if (totalPages <= 1) return;

                // 上一页按钮
                const prevLi = document.createElement('li');
                prevLi.className = `page-item ${currentPage === 1 ? 'disabled' : ''}`;
                prevLi.innerHTML = `<a class="page-link" href="#" aria-label="Previous"><span aria-hidden="true">&laquo;</span> 上一页</a>`;
                prevLi.addEventListener('click', function(e) {
                    e.preventDefault();
                    if (currentPage > 1) {
                        advancedSearch(title, content, organ, policyType,
                            documentNumber, startDate, currentPage - 1);
                    }
                });
                pagination.appendChild(prevLi);

                // 页码按钮
                const startPage = Math.max(1, currentPage - 2);
                const endPage = Math.min(totalPages, currentPage + 2);

                // 第一页
                if (startPage > 1) {
                    const firstLi = document.createElement('li');
                    firstLi.className = 'page-item';
                    firstLi.innerHTML = `<a class="page-link" href="#">1</a>`;
                    firstLi.addEventListener('click', function(e) {
                        e.preventDefault();
                        advancedSearch(title, content, organ, policyType,
                            documentNumber, startDate, 1);
                    });
                    pagination.appendChild(firstLi);

                    if (startPage > 2) {
                        const ellipsisLi = document.createElement('li');
                        ellipsisLi.className = 'page-item disabled';
                        ellipsisLi.innerHTML = `<span class="page-link">...</span>`;
                        pagination.appendChild(ellipsisLi);
                    }
                }

                for (let i = startPage; i <= endPage; i++) {
                    const pageLi = document.createElement('li');
                    pageLi.className = `page-item ${i === currentPage ? 'active' : ''}`;
                    pageLi.innerHTML = `<a class="page-link" href="#">${i}</a>`;
                    pageLi.addEventListener('click', function(e) {
                        e.preventDefault();
                        advancedSearch(title, content, organ, policyType,
                            documentNumber, startDate, i);
                    });
                    pagination.appendChild(pageLi);
                }

                // 最后一页
                if (endPage < totalPages) {
                    if (endPage < totalPages - 1) {
                        const ellipsisLi = document.createElement('li');
                        ellipsisLi.className = 'page-item disabled';
                        ellipsisLi.innerHTML = `<span class="page-link">...</span>`;
                        pagination.appendChild(ellipsisLi);
                    }

                    const lastLi = document.createElement('li');
                    lastLi.className = 'page-item';
                    lastLi.innerHTML = `<a class="page-link" href="#">${totalPages}</a>`;
                    lastLi.addEventListener('click', function(e) {
                        e.preventDefault();
                        advancedSearch(title, content, organ, policyType,
                            documentNumber, startDate, totalPages);
                    });
                    pagination.appendChild(lastLi);
                }

                // 下一页按钮
                const nextLi = document.createElement('li');
                nextLi.className = `page-item ${currentPage === totalPages ? 'disabled' : ''}`;
                nextLi.innerHTML = `<a class="page-link" href="#" aria-label="Next">下一页 <span aria-hidden="true">&raquo;</span></a>`;
                nextLi.addEventListener('click', function(e) {
                    e.preventDefault();
                    if (currentPage < totalPages) {
                        advancedSearch(title, content, organ, policyType,
                            documentNumber, startDate, currentPage + 1);
                    }
                });
                pagination.appendChild(nextLi);
            }
        });

        // 加载所有政策(不带分类筛选)
        function loadAllPolicies(pageNum) {
            document.getElementById('loading-spinner').style.display = 'block';
            document.getElementById('policy-list').innerHTML = '';
            document.getElementById('pagination').innerHTML = '';
            document.getElementById('current-category').style.display = 'none';

            // 获取时间筛选条件
            const timeFilter = document.querySelector('input[name="timeFilter"]:checked').value;
            // 获取来源筛选条件
            const sourceFilter = document.querySelector('input[name="sourceFilter"]:checked').value;

            // 构建查询参数
            let url = `http://localhost:8080/api/policiesByType?pageNum=${pageNum}&pageSize=10`;

            // 添加时间筛选条件
            if (timeFilter !== 'all') {
                const months = parseInt(timeFilter);
                const date = new Date();
                date.setMonth(date.getMonth() - months);
                const dateStr = date.toISOString().split('T')[0];
                url += `&startDate=${dateStr}`;
            }

            // 添加来源筛选条件
            if (sourceFilter !== 'all') {
                if (sourceFilter === 'hebei') {
                    url += `&organ=河北省`;
                } else if (sourceFilter === 'national') {
                    url += `&organ=国务院`;
                } else if (sourceFilter === 'other') {
                    url += `&organNot=河北省&organNot=国务院`;
                }
            }

            // 构建数量查询URL
            let countUrl = `http://localhost:8080/api/policyCountByType?`;

            // 添加时间筛选条件到数量查询
            if (timeFilter !== 'all') {
                const months = parseInt(timeFilter);
                const date = new Date();
                date.setMonth(date.getMonth() - months);
                const dateStr = date.toISOString().split('T')[0];
                countUrl += `&startDate=${dateStr}`;
            }

            // 添加来源筛选条件到数量查询
            if (sourceFilter !== 'all') {
                if (sourceFilter === 'hebei') {
                    countUrl += `&organ=河北省`;
                } else if (sourceFilter === 'national') {
                    countUrl += `&organ=国务院`;
                } else if (sourceFilter === 'other') {
                    countUrl += `&organNot=河北省&organNot=国务院`;
                }
            }

            // 获取政策数量
            fetch(countUrl)
                .then(response => response.json())
                .then(count => {
                    const totalPages = Math.ceil(count / 10);

                    // 获取当前页的政策
                    fetch(url)
                        .then(response => response.json())
                        .then(policies => {
                            document.getElementById('loading-spinner').style.display = 'none';
                            renderPolicies(policies);
                            renderPagination('', pageNum, totalPages, true);
                        })
                        .catch(error => {
                            document.getElementById('loading-spinner').style.display = 'none';
                            console.error('加载政策失败:', error);
                            document.getElementById('policy-list').innerHTML =
                                '<div class="no-results"><i class="bi bi-exclamation-circle"></i> 加载政策失败,请稍后再试</div>';
                        });
                })
                .catch(error => {
                    document.getElementById('loading-spinner').style.display = 'none';
                    console.error('获取政策数量失败:', error);
                });
        }

        // 加载政策分类树
        function loadPolicyTree() {
            fetch('http://localhost:8080/api/policyTypes')
                .then(response => response.json())
                .then(data => {
                    const treeContainer = document.getElementById('policy-tree');

                    // 添加全选/全不选控制
                    const selectAllDiv = document.createElement('div');
                    selectAllDiv.className = 'select-all-container';

                    const selectAllCheckbox = document.createElement('input');
                    selectAllCheckbox.type = 'checkbox';
                    selectAllCheckbox.className = 'form-check-input tree-checkbox';
                    selectAllCheckbox.id = 'tree-checkbox-all';

                    const selectAllLabel = document.createElement('label');
                    selectAllLabel.className = 'tree-label';
                    selectAllLabel.htmlFor = 'tree-checkbox-all';
                    selectAllLabel.textContent = '全部分类';

                    selectAllCheckbox.addEventListener('change', function() {
                        const allCheckboxes = treeContainer.querySelectorAll('.tree-checkbox');
                        allCheckboxes.forEach(checkbox => {
                            if (checkbox.id !== 'tree-checkbox-all') {
                                checkbox.checked = this.checked;
                            }
                        });
                        updateSelectedCategories();
                    });

                    selectAllDiv.appendChild(selectAllCheckbox);
                    selectAllDiv.appendChild(selectAllLabel);
                    treeContainer.appendChild(selectAllDiv);

                    // 渲染树形结构
                    const treeData = buildTree(data);
                    renderTree(treeData, treeContainer);
                })
                .catch(error => {
                    console.error('加载分类树失败:', error);
                });
        }

        // 构建树形结构
        function buildTree(items) {
            const rootItems = [];
            const itemMap = {};

            // 首先将所有项存入映射表
            items.forEach(item => {
                itemMap[item.typeId] = {
                    ...item,
                    children: []
                };
            });

            // 构建树形结构
            items.forEach(item => {
                const mappedItem = itemMap[item.typeId];
                if (item.parentId) {
                    if (itemMap[item.parentId]) {
                        itemMap[item.parentId].children.push(mappedItem);
                    }
                } else {
                    rootItems.push(mappedItem);
                }
            });

            return rootItems;
        }

        // 渲染树形结构(带复选框版本)
        function renderTree(items, container) {
            items.forEach(item => {
                const hasChildren = item.children && item.children.length > 0;
                const itemElement = document.createElement('div');
                itemElement.className = 'tree-item';
                itemElement.dataset.typeId = item.typeId;

                // 创建复选框
                const checkbox = document.createElement('input');
                checkbox.type = 'checkbox';
                checkbox.className = 'form-check-input tree-checkbox';
                checkbox.id = `tree-checkbox-${item.typeId}`;
                checkbox.addEventListener('change', function() {
                    // 当父分类被选中/取消选中时,同步子分类的状态
                    if (hasChildren) {
                        const childrenContainer = itemElement.querySelector('.tree-children');
                        const childCheckboxes = childrenContainer.querySelectorAll('.tree-checkbox');
                        childCheckboxes.forEach(childCheckbox => {
                            childCheckbox.checked = this.checked;
                        });
                    }
                    updateSelectedCategories();
                });

                // 创建展开/折叠图标
                const toggle = document.createElement('span');
                toggle.className = 'tree-toggle';
                if (hasChildren) {
                    toggle.innerHTML = '<i class="bi bi-plus-square"></i>';
                    toggle.addEventListener('click', function(e) {
                        e.stopPropagation();
                        itemElement.classList.toggle('expanded');
                        const icon = this.querySelector('i');
                        if (itemElement.classList.contains('expanded')) {
                            icon.classList.remove('bi-plus-square');
                            icon.classList.add('bi-dash-square');
                        } else {
                            icon.classList.remove('bi-dash-square');
                            icon.classList.add('bi-plus-square');
                        }
                    });
                } else {
                    toggle.innerHTML = '&nbsp;&nbsp;';
                }

                // 创建标签
                const label = document.createElement('label');
                label.className = 'tree-label';
                label.htmlFor = `tree-checkbox-${item.typeId}`;
                label.textContent = item.typeName;
                label.addEventListener('click', function(e) {
                    e.stopPropagation();
                    checkbox.checked = !checkbox.checked;
                    // 触发change事件
                    const event = new Event('change');
                    checkbox.dispatchEvent(event);
                });

                // 组装元素
                itemElement.appendChild(checkbox);
                itemElement.appendChild(toggle);
                itemElement.appendChild(label);

                // 如果有子节点,递归渲染
                if (hasChildren) {
                    const childrenContainer = document.createElement('div');
                    childrenContainer.className = 'tree-children';
                    renderTree(item.children, childrenContainer);
                    itemElement.appendChild(childrenContainer);
                }

                container.appendChild(itemElement);
            });
        }

        // 更新选中的分类
        function updateSelectedCategories() {
            const checkedBoxes = document.querySelectorAll('.tree-checkbox:checked');

            // 排除全选复选框
            const filteredCheckboxes = Array.from(checkedBoxes).filter(
                checkbox => checkbox.id !== 'tree-checkbox-all'
            );

            if (filteredCheckboxes.length === 0) {
                // 如果没有选中任何分类,显示全部政策
                loadAllPolicies(1);
                return;
            }

            // 获取所有选中的分类名称(而不是ID)
            const allSelectedNames = [];
            filteredCheckboxes.forEach(checkbox => {
                const itemElement = checkbox.closest('.tree-item');
                const typeName = itemElement.querySelector('.tree-label').textContent;
                allSelectedNames.push(typeName);

                // 检查是否有子分类被选中
                const childrenContainer = itemElement.querySelector('.tree-children');
                if (childrenContainer) {
                    const childCheckboxes = childrenContainer.querySelectorAll('.tree-checkbox:checked');
                    childCheckboxes.forEach(childCheckbox => {
                        const childItemElement = childCheckbox.closest('.tree-item');
                        const childTypeName = childItemElement.querySelector('.tree-label').textContent;
                        allSelectedNames.push(childTypeName);
                    });
                }
            });

            // 去重
            const uniqueNames = [...new Set(allSelectedNames)];
            const typeNames = uniqueNames.join(',');

            // 获取分类名称显示
            const displayNames = filteredCheckboxes.map(checkbox => {
                const itemElement = checkbox.closest('.tree-item');
                return itemElement.querySelector('.tree-label').textContent;
            }).join('、');

            loadPoliciesByType(typeNames, displayNames, 1);
        }

        // 按分类加载政策(修改为使用typeNames)
        function loadPoliciesByType(typeNames, displayName, pageNum) {
            document.getElementById('loading-spinner').style.display = 'block';
            document.getElementById('policy-list').innerHTML = '';
            document.getElementById('pagination').innerHTML = '';

            if (displayName && displayName !== '全部政策') {
                document.getElementById('current-category').style.display = 'block';
                document.getElementById('category-name').textContent = displayName;
            } else {
                document.getElementById('current-category').style.display = 'none';
            }

            // 获取时间筛选条件
            const timeFilter = document.querySelector('input[name="timeFilter"]:checked').value;
            // 获取来源筛选条件
            const sourceFilter = document.querySelector('input[name="sourceFilter"]:checked').value;

            // 构建查询参数(使用typeNames而不是typeIds)
            let url = `http://localhost:8080/api/policiesByType?typeNames=${encodeURIComponent(typeNames)}&pageNum=${pageNum}&pageSize=10`;

            // 添加时间筛选条件
            if (timeFilter !== 'all') {
                const months = parseInt(timeFilter);
                const date = new Date();
                date.setMonth(date.getMonth() - months);
                const dateStr = date.toISOString().split('T')[0];
                url += `&startDate=${dateStr}`;
            }

            // 添加来源筛选条件
            if (sourceFilter !== 'all') {
                if (sourceFilter === 'hebei') {
                    url += `&organ=河北省`;
                } else if (sourceFilter === 'national') {
                    url += `&organ=国务院`;
                } else if (sourceFilter === 'other') {
                    url += `&organNot=河北省&organNot=国务院`;
                }
            }

            // 构建数量查询URL(使用typeNames而不是typeIds)
            let countUrl = `http://localhost:8080/api/policyCountByType?typeNames=${encodeURIComponent(typeNames)}`;

            // 添加时间筛选条件到数量查询
            if (timeFilter !== 'all') {
                const months = parseInt(timeFilter);
                const date = new Date();
                date.setMonth(date.getMonth() - months);
                const dateStr = date.toISOString().split('T')[0];
                countUrl += `&startDate=${dateStr}`;
            }

            // 添加来源筛选条件到数量查询
            if (sourceFilter !== 'all') {
                if (sourceFilter === 'hebei') {
                    countUrl += `&organ=河北省`;
                } else if (sourceFilter === 'national') {
                    countUrl += `&organ=国务院`;
                } else if (sourceFilter === 'other') {
                    countUrl += `&organNot=河北省&organNot=国务院`;
                }
            }

            // 获取政策数量
            fetch(countUrl)
                .then(response => response.json())
                .then(count => {
                    const totalPages = Math.ceil(count / 10);

                    // 获取当前页的政策
                    fetch(url)
                        .then(response => response.json())
                        .then(policies => {
                            document.getElementById('loading-spinner').style.display = 'none';
                            renderPolicies(policies);
                            renderPagination(typeNames, pageNum, totalPages, false);
                        })
                        .catch(error => {
                            document.getElementById('loading-spinner').style.display = 'none';
                            console.error('加载政策失败:', error);
                            document.getElementById('policy-list').innerHTML =
                                '<div class="no-results"><i class="bi bi-exclamation-circle"></i> 加载政策失败,请稍后再试</div>';
                        });
                })
                .catch(error => {
                    document.getElementById('loading-spinner').style.display = 'none';
                    console.error('获取政策数量失败:', error);
                });
        }

        // 渲染政策列表
        function renderPolicies(policies) {
            const policyList = document.getElementById('policy-list');

            if (policies.length === 0) {
                policyList.innerHTML = '<div class="no-results"><i class="bi bi-info-circle"></i> 没有找到符合条件的政策</div>';
                return;
            }

            policies.forEach(policy => {
                const policyItem = document.createElement('div');
                policyItem.className = 'policy-item';

                const title = document.createElement('div');
                title.className = 'policy-title';
                title.textContent = policy.name;

                const date = document.createElement('div');
                date.className = 'policy-date';
                date.textContent = formatDate(policy.pubdata);

                policyItem.appendChild(title);
                policyItem.appendChild(date);

                policyList.appendChild(policyItem);
            });
        }

        // 渲染分页控件
        function renderPagination(typeNames, currentPage, totalPages, isAllPolicies) {
            const pagination = document.getElementById('pagination');
            pagination.innerHTML = '';

            if (totalPages <= 1) return;

            // 上一页按钮
            const prevLi = document.createElement('li');
            prevLi.className = `page-item ${currentPage === 1 ? 'disabled' : ''}`;
            prevLi.innerHTML = `<a class="page-link" href="#" aria-label="Previous"><span aria-hidden="true">&laquo;</span> 上一页</a>`;
            prevLi.addEventListener('click', function(e) {
                e.preventDefault();
                if (currentPage > 1) {
                    if (isAllPolicies) {
                        loadAllPolicies(currentPage - 1);
                    } else {
                        loadPoliciesByType(typeNames, document.getElementById('category-name').textContent, currentPage - 1);
                    }
                }
            });
            pagination.appendChild(prevLi);

            // 页码按钮
            const startPage = Math.max(1, currentPage - 2);
            const endPage = Math.min(totalPages, currentPage + 2);

            // 第一页
            if (startPage > 1) {
                const firstLi = document.createElement('li');
                firstLi.className = 'page-item';
                firstLi.innerHTML = `<a class="page-link" href="#">1</a>`;
                firstLi.addEventListener('click', function(e) {
                    e.preventDefault();
                    if (isAllPolicies) {
                        loadAllPolicies(1);
                    } else {
                        loadPoliciesByType(typeNames, document.getElementById('category-name').textContent, 1);
                    }
                });
                pagination.appendChild(firstLi);

                if (startPage > 2) {
                    const ellipsisLi = document.createElement('li');
                    ellipsisLi.className = 'page-item disabled';
                    ellipsisLi.innerHTML = `<span class="page-link">...</span>`;
                    pagination.appendChild(ellipsisLi);
                }
            }

            for (let i = startPage; i <= endPage; i++) {
                const pageLi = document.createElement('li');
                pageLi.className = `page-item ${i === currentPage ? 'active' : ''}`;
                pageLi.innerHTML = `<a class="page-link" href="#">${i}</a>`;
                pageLi.addEventListener('click', function(e) {
                    e.preventDefault();
                    if (isAllPolicies) {
                        loadAllPolicies(i);
                    } else {
                        loadPoliciesByType(typeNames, document.getElementById('category-name').textContent, i);
                    }
                });
                pagination.appendChild(pageLi);
            }

            // 最后一页
            if (endPage < totalPages) {
                if (endPage < totalPages - 1) {
                    const ellipsisLi = document.createElement('li');
                    ellipsisLi.className = 'page-item disabled';
                    ellipsisLi.innerHTML = `<span class="page-link">...</span>`;
                    pagination.appendChild(ellipsisLi);
                }

                const lastLi = document.createElement('li');
                lastLi.className = 'page-item';
                lastLi.innerHTML = `<a class="page-link" href="#">${totalPages}</a>`;
                lastLi.addEventListener('click', function(e) {
                    e.preventDefault();
                    if (isAllPolicies) {
                        loadAllPolicies(totalPages);
                    } else {
                        loadPoliciesByType(typeNames, document.getElementById('category-name').textContent, totalPages);
                    }
                });
                pagination.appendChild(lastLi);
            }

            // 下一页按钮
            const nextLi = document.createElement('li');
            nextLi.className = `page-item ${currentPage === totalPages ? 'disabled' : ''}`;
            nextLi.innerHTML = `<a class="page-link" href="#" aria-label="Next">下一页 <span aria-hidden="true">&raquo;</span></a>`;
            nextLi.addEventListener('click', function(e) {
                e.preventDefault();
                if (currentPage < totalPages) {
                    if (isAllPolicies) {
                        loadAllPolicies(currentPage + 1);
                    } else {
                        loadPoliciesByType(typeNames, document.getElementById('category-name').textContent, currentPage + 1);
                    }
                }
            });
            pagination.appendChild(nextLi);
        }

        // 搜索政策
        function searchPolicies(keyword) {
            document.getElementById('loading-spinner').style.display = 'block';
            document.getElementById('policy-list').innerHTML = '';
            document.getElementById('pagination').innerHTML = '';
            document.getElementById('current-category').style.display = 'none';

            // 清除所有复选框的选中状态(排除全选复选框)
            document.querySelectorAll('.tree-checkbox').forEach(checkbox => {
                if (checkbox.id !== 'tree-checkbox-all') {
                    checkbox.checked = false;
                }
            });

            // 获取时间筛选条件
            const timeFilter = document.querySelector('input[name="timeFilter"]:checked').value;

            // 构建查询URL
            let url = `http://localhost:8080/api/searchPolicies?keyword=${encodeURIComponent(keyword)}`;

            // 添加时间筛选条件
            if (timeFilter !== 'all') {
                const months = parseInt(timeFilter);
                const date = new Date();
                date.setMonth(date.getMonth() - months);
                const dateStr = date.toISOString().split('T')[0];
                url += `&startDate=${dateStr}`;
            }

            fetch(url)
                .then(response => response.json())
                .then(policies => {
                    document.getElementById('loading-spinner').style.display = 'none';
                    if (policies.length === 0) {
                        document.getElementById('policy-list').innerHTML =
                            '<div class="no-results"><i class="bi bi-search"></i> 没有找到与 "' + keyword + '" 相关的政策</div>';
                    } else {
                        renderPolicies(policies);
                    }
                })
                .catch(error => {
                    document.getElementById('loading-spinner').style.display = 'none';
                    console.error('搜索政策失败:', error);
                    document.getElementById('policy-list').innerHTML =
                        '<div class="no-results"><i class="bi bi-exclamation-circle"></i> 搜索失败,请稍后再试</div>';
                });
        }

        // 格式化日期
        function formatDate(dateStr) {
            if (!dateStr) return '未知日期';
            const date = new Date(dateStr);
            return isNaN(date.getTime()) ? '未知日期' : date.toLocaleDateString('zh-CN');
        }
    </script>
</body>
</html>

配置类
CorsConfig

package com.example.search.controller;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        .allowedOrigins("*")
                        .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                        .allowedHeaders("*")
                        .allowCredentials(false)
                        .maxAge(3600);
            }
        };
    }
}

GlobalExceptionHandler

package com.example.search.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleException(Exception e) {
        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("服务器内部错误: " + e.getMessage());
    }
}
posted @ 2025-04-01 20:01  haoyinuo  阅读(21)  评论(0)    收藏  举报