项目开发中的问题(7.8)

1、SpringMVC 接收参数的方式

方式一:@RequestParam(接收普通键值对)

适用场景
前端通过 URL 查询参数(?key=value)或普通表单提交数据。这是最基础、最常用的方式。

维度 内容
前端请求示例 GET /user/detail?userId=1001&token=abc123
后端注解 @RequestParam("参数名")
特点 参数名需与形参名一致(或通过注解指定映射),类型需相容。支持设置默认值和必填校验。

后端代码示例

1@GetMapping("/user/detail")
2public String getUser(@RequestParam("userId") Long id, 
3                      @RequestParam("token") String token) {
4    return "用户ID是:" + id + ",Token是:" + token;
5}

方式二:@PathVariable(接收路径参数)

适用场景
RESTful 风格接口,参数直接拼在 URL 路径中,通常用于标识某个具体资源(如 ID)。

维度 内容
前端请求示例 GET /user/1001
后端注解 @PathVariable("变量名")
特点 参数嵌入在 URL 路径中,URL 更加美观且语义化强。

后端代码示例

1@GetMapping("/user/{userId}")
2public String getUserById(@PathVariable("userId") Long id) {
3    return "查询到的用户ID是:" + id;
4}

方式三:@RequestBody(接收 JSON 数据)

适用场景
前端发送 POST/PUT 请求,且 Content-Typeapplication/json,传递复杂结构化数据。

维度 内容
前端请求示例 POST /user/add Body: { "username": "张三", "age": 25 }
后端注解 @RequestBody
特点 将 JSON 字符串自动反序列化为 Java 对象或集合。一个方法只能使用一次该注解。

后端代码示例

1@PostMapping("/user/add")
2public String addUser(@RequestBody User user) {
3    return "成功添加用户:" + user.getUsername();
4}

方式四:直接接收(不加注解)

适用场景
这是最基础的方式,适用于简单类型、数组以及自定义对象(JavaBean)。SpringMVC 会自动根据名称匹配并转换类型。

维度 内容
前端请求示例 GET /list?pageNum=1&pageSize=10 POST /delete?ids=1&ids=2
后端写法 方法参数直接写变量名,无需注解
特点 要求传递的参数名字与接收的形参名字一致,类型相容即可自动转换。

后端代码示例

1// 1. 简单类型 & 数组类型
2@GetMapping("/list")
3public String getList(int pageNum, int pageSize, Long[] ids) {
4    return "页码:" + pageNum;
5}
6
7// 2. 自定义 JavaBean (自动填充属性)
8@PostMapping("/save")
9public String save(User user) { // User类中需有对应的setter方法
10    return "保存用户:" + user.getUsername();
11}

注意事项

  • 框架关系:SpringBoot 本质上是快速构建框架的 Spring 框架,SpringMVC 是 Spring 的一部分。
  • 底层原理:SpringMVC 接收参数本质上是对原生 Servlet 方式的封装。
  • 原生对比:上述所有方式最终都对应原生的 String 变量名 = request.getParameter("形参名")

2、:key、:label、:value属性的含义

 <el-form-item label="状态" prop="status">
 	<el-select v-model="queryParams.status" placeholder="用户状态" clearable style="width: 240px">
    	<el-option 
        	v-for="dict in dict.type.sys_normal_disable" 
            :label="dict.label" 
            :value="dict.value"
            :key="dict.value" />
    </el-select>
  </el-form-item>
属性 对应的字典字段 “正常”时的值 “停用”时的值 作用通俗解释
:label dict.label "正常" "停用" 给用户看的(面子)
:value dict.value "0" "1" 给程序存的(里子)
:key dict.value "0" "1" 给 Vue 认人的(身份证)

3、prop=‘’ ‘’作用

<!-- 表格数据部分 -->
<el-table v-loading="loading" :data="postList" @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="55" align="center" />
      <el-table-column label="岗位编号" align="center" prop="postId" />
      <el-table-column label="岗位编码" align="center" prop="postCode" />
      <el-table-column label="岗位名称" align="center" prop="postName" />
      <el-table-column label="岗位排序" align="center" prop="postSort" />
      <el-table-column label="状态" align="center" prop="status">
        <template slot-scope="scope">
          <dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/>
        </template>
      </el-table-column>
      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
        <template slot-scope="scope">
          <span>{{ parseTime(scope.row.createTime) }}</span>
        </template>
      </el-table-column>
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template slot-scope="scope">
          <el-button
            size="mini"
            type="text"
            icon="el-icon-edit"
            @click="handleUpdate(scope.row)"
            v-hasPermi="['system:post:edit']"
          >修改</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-delete"
            @click="handleDelete(scope.row)"
            v-hasPermi="['system:post:remove']"
          >删除</el-button>
        </template>
      </el-table-column>
    </el-table>
<!-- 添加或修改岗位对话框 -->
    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="岗位名称" prop="postName">
          <el-input v-model="form.postName" placeholder="请输入岗位名称" />
        </el-form-item>
        <el-form-item label="岗位编码" prop="postCode">
          <el-input v-model="form.postCode" placeholder="请输入编码名称" />
        </el-form-item>
        <el-form-item label="岗位顺序" prop="postSort">
          <el-input-number v-model="form.postSort" controls-position="right" :min="0" />
        </el-form-item>
        <el-form-item label="岗位状态" prop="status">
          <el-radio-group v-model="form.status">
            <el-radio
              v-for="dict in dict.type.sys_normal_disable"
              :key="dict.value"
              :label="dict.value"
            >{{dict.label}}</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="submitForm">确 定</el-button>
        <el-button @click="cancel">取 消</el-button>
      </div>
    </el-dialog>
维度 表格 (el-table-column) 表单 (el-form-item)
核心作用 数据展示(从数据源取值显示) 数据校验(关联 rules 验证规则)
配合对象 配合 :data 属性 配合 :rules 属性和 :model 属性
能否省略 使用插槽自定义内容时可省略;纯文本展示时不可省略 不可省略(否则校验失效)
取值方式 自动提取或 scope.row.字段名 必须与 v-model 绑定的字段名一致
  • 含义propProperties(属性) 的缩写。它是自定义组件的“对外接口”。
  • 作用:专门用于父组件向子组件传递数据。它保证了数据的单向流动(父传子),防止子组件意外篡改父组件的数据,从而让代码更安全、组件更容易复用。

总结:prop 的本质是组件接收外部数据的接口;在表格里它负责“提取数据展示”,在表单里它负责“关联规则校验”

4、日期控件中的type属性

<el-col :span="24">
	<el-form-item label="到店时间" prop="actualArrivalTime">
		<el-date-picker clearable
            v-model="form.actualArrivalTime"
            style="width:100%"
            type="datetime"		//原为date,项目中带时分秒需改为datetime
            value-format="yyyy-MM-dd HH:mm:ss"
            placeholder="请选择实际到店时间">
		</el-date-picker>
	</el-form-item>
</el-col>

(1)type="date":这是日期选择器。它的逻辑是“选中即完成”,当你点击日历上的某一天(例如 9月15日),下拉框会自动关闭并赋值,因此它不需要“此刻”和“确定”按钮。

(2)type="datetime":这是日期时间选择器。因为它既需要选日期,又需要在下方滑动选择具体的时、分、秒,所以必须有一个“确定”按钮来确认最终的时间点,同时也会有“此刻”按钮快速填充当前时间。

5、数据表格中的时间显示和新增编辑模态框中的时间显示

(1)数据表格:

<el-table-column label="预约时间" align="center" prop="appointmentTime" width="200">
	<template slot-scope="scope">
    	<span>{{ parseTime(scope.row.appointmentTime,'{y}-{m}-{d} {h}:{i}:{s}') }}</span>
	</template>
</el-table-column>

(2)新增、编辑模态框:

注意格式为"yyyy-MM-dd HH:mm:ss"

<el-form-item label="预约时间" prop="appointmentTime">
	<el-date-picker clearable 
    	v-model="form.appointmentTime" 
        style="width: 380px;" 
        size="large" 			              
        type="datetime"
        value-format="yyyy-MM-dd HH:mm:ss" placeholder="请选择预约时间">
    </el-date-picker>
</el-form-item>

6、结算单页面列表时间显示问题

为什么新增数据后,列表中显示的只有年月日,时分秒为00:00:00?

因为前端通过新增添加数据,向后端传过去的时间是带时分秒的(日期控件中的type属性为"datetime")。

image-20260716182026444

后端在ControllerBusStatement接请求,但实际上是在domainBusStatement中的actualArrivalTime接。

@JsonFormat(pattern = "yyyy-MM-dd")注解中,只定义了年月日,所以要在注解中加入 HH:mm:ss

 /** 实际到店时间 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Excel(name = "实际到店时间", width = 30, dateFormat = "yyyy-MM-dd")
    private Date actualArrivalTime;

7、结算单页面高级搜索时间查询问题

问题:为什么搜索到店时间为当天的结算单数据却搜索不到?

例如:

搜索2026年7月15号的数据却搜索不到。

image-20260716184559050

原因:

这是因为前端发的请求只带年月日,没带时分秒。Java中接收数据若没带时分秒,则默认为00:00:00。

也就是开始时间2026-7-15 00:00:00,结束时间2026-7-15 00:00:00

image-20260716184745529

如何解决?

只需将结束时间改为当天的23:59:59即可。

解决步骤:

(1)分析前端数据是如何发送的?

开始日期结束日期"-"分割,就可以把actualArrivalTime的值会变成一个数组。

例如:2023-10-01 到 2023-10-31,拆分后变为["2023-10-01", "2023-10-31"]

高级搜索部分v-model,双向数据绑定了daterangeActualArrivalTime,那么在data中一定会有这个数据。

相当于先绑定到daterangeActualArrivalTime中,后续如果传递了时间,就按'-'分割开始时间和结束时间,后面再向params中添加开始时间和结束时间,供时间查询。

// 备注信息时间范围
daterangeActualArrivalTime: [],
<!-- 高级搜索部分 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="到店时间">
        <el-date-picker
          v-model="daterangeActualArrivalTime"
          style="width: 240px"
          value-format="yyyy-MM-dd"
          type="daterange"
          range-separator="-"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
        ></el-date-picker>
      </el-form-item>
 // 查询参数
 queryParams: {
 pageNum: 1,
 pageSize: 10,
 actualArrivalTime: null,
 isDelete: null
 },
到店时间绑定了"daterangeActualArrivalTime",但是在queryParams中只有actualArrivalTime,它是如何封装的?

首先,点击搜索时会调用getList()方法。

 /** 搜索按钮操作 */
 handleQuery() {
 	this.queryParams.pageNum = 1
    this.getList()
 },
 /** 查询结算单列表 */
    getList() {
      this.loading = true
	  // 相当于向 this.queryParams 中追加一个属性 params:{}
      this.queryParams.params = {}
	  // 当 daterangeActualArrivalTime数组 != null && 长度 != 0
	  // 说明我们填写了高级搜索中的时间
      if (null != this.daterangeActualArrivalTime && '' != this.daterangeActualArrivalTime) {
		// 相当于向this.queryParams.params追加了开始时间和结束时间两个属性,
		// 值为我们填写的 第一个(开始时间) 第二个(结束时间)
        this.queryParams.params["beginActualArrivalTime"] = this.daterangeActualArrivalTime[0]
        this.queryParams.params["endActualArrivalTime"] = this.daterangeActualArrivalTime[1]
      }

      listStatement(this.queryParams).then(response => {
        this.statementList = response.rows
        this.total = response.total
        this.loading = false
      })
    },

相当于

queryParams: {
    pageNum: 1,
    pageSize: 10,
    actualArrivalTime: null,
    isDelete: null,
    params:{
        beginActualArrivalTime:this.daterangeActualArrivalTime[0],
        endActualArrivalTime:this.daterangeActualArrivalTime[1]
    }
}

(2)后端是如何接收的?

那为什么一定要追加一个params,接着往里添加两组数据?为什么一定叫params?

实际上,我们的实体类BusStatement继承了BaseEntity

BaseEntity存在Map<String, Object>的请求参数params

相当于把前端的这个 params:{beginActualArrivalTime:this.daterangeActualArrivalTime[0], endActualArrivalTime:this.daterangeActualArrivalTime[1] }传到后端的Map<String, Object> params中去了。

public class BaseEntity implements Serializable
{
    private static final long serialVersionUID = 1L;

    /** 搜索值 */
    @JsonIgnore
    private String searchValue;

    /** 创建者 */
    private String createBy;

    /** 创建时间 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;

    /** 更新者 */
    private String updateBy;

    /** 更新时间 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date updateTime;

    /** 备注 */
    private String remark;

    /** 请求参数 */
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    private Map<String, Object> params;

所以前端是强制性用这种方式给后端传值,如果前端传了时间,才封装params;如果没传,就不封装,避免查询时出问题。

(3)处理高级搜索时间问题

解决方法:结束时间 + 1天 - 1秒
/**
     * 查询结算单列表
     *
     * @param busStatement 结算单
     * @return 结算单
     */
    @Override
    public List<BusStatement> selectBusStatementList(BusStatement busStatement) throws ParseException {
        // 处理高级搜索时间问题 结束时间 + 1天 - 1秒
        // 1. 接收结束时间 刚进入页面,前端没传时间,则获取 endActualArrivalTime 为 null
        String endActualArrivalTime = (String)busStatement.getParams().get("endActualArrivalTime");
        if(endActualArrivalTime != null){
            // 2. 转为 Date 类型
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            Date endDate = sdf.parse(endActualArrivalTime);
            // 3. 创建 Calendar 类来更新时间
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(endDate);
            // + 1 天
            calendar.add(Calendar.DAY_OF_YEAR,1);
            // - 1 秒
            calendar.add(Calendar.SECOND,-1);
            // 转换后,再将其转换为 Date 类型
            Date changeDate = calendar.getTime();
            // 再将无时分秒的,转换为 23:59:59 这种格式带时分秒的字符串
            SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String endTime = sdf1.format(changeDate);
            // 再设置回给结束时间
            busStatement.getParams().put("endActualArrivalTime",endTime);
        }

        return busStatementMapper.selectBusStatementList(busStatement);
    }
  1. 初始获取(String 类型)
    endActualArrivalTime = "2026-07-16"

    说明: 从 Map 中取出的原始字符串,只有日期。原来是Object类型,需强转为String类型。

  2. 转为 Date 对象(Date 类型)
    endDate = Wed Jul 16 00:00:00 CST 2026

    说明: 代码执行 sdf.parse()。因为原字符串没写时间,系统自动补全为当天的 0点0分0秒

  3. 日历运算后(Calendar -> Date 类型)
    changeDate = Thu Jul 17 23:59:59 CST 2026

    说明: 代码先 +1天 (变成17号0点),再 -1秒 (变成16号最后一秒)。目的是包含 "16号" 这一整天的数据。

  4. 最终格式化(String 类型)
    endTime = "2026-07-16 23:59:59"

    说明: 代码执行 sdf1.format()。将复杂的 Date 对象重新变回字符串,此时带上了时分秒,可以直接传给数据库进行范围查询。

String (日期) →→ Date (对象) →→ Calendar (日历工具) →→ Date (对象) →→ String (时间戳)

封装成工具类:

package cn.wolfcode.business.utils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class DateUtil {

    /**
     * 更新时间
     * @param date 需要更新的时间
     * @param type 要算什么单位
     * @param amount 要加多少
     * @return 更新后的时间
     */
    public static Date changeDate(Date date,Integer type,Integer amount){
        if(date == null || type == null || amount == null){
            return null;
        }
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(type,amount);
        // 转换回 Date 类型
        return calendar.getTime();
    }

    /**
     * String 转 Date
     * @param str 需要转换的字符串
     * @param regex 指定转换的格式
     * @return 转换后的 Date 日期类型
     */
    public static Date parseDate(String str,String regex){
        if(str == null || regex == null){
            return null;
        }
        SimpleDateFormat sdf = new SimpleDateFormat(regex);
        try {
            return sdf.parse(str);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 日期转换为字符串
     * @param date 需要转换的日期
     * @param regex 转换格式
     * @return 转换后的字符串
     */
    public static String parseString(Date date,String regex){
        if(date == null || regex == null){
            return null;
        }
        SimpleDateFormat sdf = new SimpleDateFormat(regex);
        return sdf.format(date);
    }

}

封装后的实现类:

/**
     * 查询结算单列表
     *
     * @param busStatement 结算单
     * @return 结算单
     */
    @Override
    public List<BusStatement> selectBusStatementList(BusStatement busStatement) throws ParseException {
        // 处理高级搜索时间问题 结束时间 + 1天 - 1秒
        // 1. 接收结束时间 刚进入页面,前端没传时间,则获取 endActualArrivalTime 为 null
        String endActualArrivalTime = (String)busStatement.getParams().get("endActualArrivalTime");
        // 转换为 Date 类型
        Date date = DateUtil.parseDate(endActualArrivalTime,"yyyy-MM-dd");
        // 时间 + 1天 - 1秒
        Date addDate = DateUtil.changeDate(date, Calendar.DAY_OF_YEAR,1);
        Date subDate = DateUtil.changeDate(addDate, Calendar.SECOND,-1);
        // 转回 String 类型
        String endTime = DateUtil.parseString(subDate,"yyyy-MM-dd HH:mm:ss");
        busStatement.getParams().put("endActualArrivalTime",endTime);
        return busStatementMapper.selectBusStatementList(busStatement);
    }

后端查询sql:

<select id="selectBusStatementList" parameterType="BusStatement" resultMap="BusStatementResult">
	<include refid="selectBusStatementVo"/>
        <where>  
            <if test="params.beginActualArrivalTime != null and params.beginActualArrivalTime != '' and params.endActualArrivalTime != null and params.endActualArrivalTime != ''"> and actual_arrival_time between #{params.beginActualArrivalTime} and #{params.endActualArrivalTime}</if>
            <if test="isDelete != null "> and is_delete = #{isDelete}</if>
        </where>
</select>

8、软删除问题

软删除在数据库层面本质是 Update 操作。但在 RESTful API 设计中,前端请求通常使用 DELETE 方法,部分场景下也会用 PATCH 或 POST,但绝不会用 PUT。

软删除带来的响应:

  • 对于查询:我们必须添加 is_delete = 0;

    • 设置默认只查询有效用户,若前端传了查询无效用户,则正常查询。
     // 设置只查询有效用户
    if(busStatement.getIsDelete() == null){
        busStatement.setIsDelete(BusStatement.IS_NOT_DELETE);
    }
    
     <if test="isDelete != null "> and is_delete = #{isDelete}</if>
    
  • 对于新增:我们必须保证添加的用户默认 is_delete = 0

    // 新录入的结算单 用户状态 --> 有效
    busStatement.setIsDelete(BusStatement.IS_NOT_DELETE);
    
  • 对于编辑:我们必须保证 is_delete=0 的有效用户才可以进行编辑操作。

      // 判断状态必须为有效用户才可以进行编辑操作
      if(BusStatement.IS_DELETE.equals(oldObj.getIsDelete())){
      throw new RuntimeException("无效用户禁止编辑");
      }
    
  • 对于删除:必须是有效用户才能执行删除。

    // 必须是有效用户才能执行删除
    if(BusStatement.IS_DELETE.equals(oldObj.getIsDelete())){
        throw new RuntimeException("无效用户禁止删除");
    }
    // 最好修改方法名称为 updateIsDeleteById
    return busStatementMapper.updateIsDeleteById(id,BusStatement.IS_DELETE);
    
    <update id="updateIsDeleteById">
        update bus_statement
        set is_delete = #{isDelete}
        where id = #{id}
    </update>
    

9、路由跳转

(1)明细按钮方法中:

/** 明细按钮操作 */
// statementId 形参 名字随意
handleGoToStatementItem(statementId){
    // 编程式导航 通过写代码的方式让页面跳转
    this.$router.push({
        // 这里定义了一个跳转的目标地址。它通过字符串拼接,把 statementId 的值拼接到 URL 的末尾。  
        path:'/business/statementItem/index/' + statementId
    })
}

(2)router下的index.js中:

{
  // 父级路由路径
  path: '/business/statementItem',
  // 使用的布局组件
  component: Layout,
  // 设置为 true,表示该路由不会在侧边栏菜单中显示
  hidden: true,
  // 访问此路由所需的权限标识
  permissions: ['business:statementItem:list'],
  // 子路由配置
  children: [
    {
      // 子路由路径,包含一个名为 statementId 的动态参数,且该参数必须是数字 (\\d+)
      path: 'index/:statementId(\\d+)',
      // 使用懒加载方式引入对应的页面组件
      component: () => import('@/views/business/statementItem'),
      // 路由的唯一名称,可用于编程式导航
      name: 'statementItem',
      // 路由元信息,用于存储额外数据
      meta: { 
        // 页面标题,可用于浏览器标签或面包屑导航
        title: '结算单明细', 
        // 当访问此隐藏页面时,让指定的菜单项保持高亮激活状态
        activeMenu: '/business/statement' 
      }
    }
  ]
},

10、添加或修改养修预约信息对话框中数据字典显示的问题

 <el-form-item label="服务类型" prop="serviceType">
     <el-select v-model="form.serviceType" placeholder="请选择服务类型" style="width: 100%">
         <el-option v-for="dict in dict.type.appoint_serv_type" 
                    :key="dict.value" 
                    :label="dict.label"
                    :value="parseInt(dict.value)"></el-option>
     </el-select>
</el-form-item>

(1)为什么要加parseInt()

这是为了解决 Vue 和 Element UI 中非常经典的“类型匹配”问题。

  • 字典数据的默认类型:在若依脚手架中,字典数据(dict.type.appoint_serv_type)从后端接口返回时,它的 value 默认都是字符串类型(例如 "1", "2")。
  • 表单数据的实际类型:由于后端 Java 实体类(如 BusAppointment)中,serviceType 字段定义为 Integer 类型。因此,当点击“新增”或“编辑”时,后端返回给前端的 form.serviceType 是一个数字类型(例如 1)。
  • 回显失败的根源:Element UI 的 <el-select> 组件在判断下拉框是否选中某一项时,底层使用的是 JavaScript 的严格相等(===)逻辑。如果不加 parseInt,前端会拿数字 1 去和下拉框选项的字符串 "1" 进行匹配,由于 1 === "1" 的结果是 false,导致下拉框无法正确回显,只能干巴巴地显示一串数字,而不是对应的文字标签。
  • 加上 parseInt 的效果:通过 parseInt(dict.value) 将字典的字符串 "1" 强制转换成了数字 1。这样前端拿数字 1 去匹配数字 11 === 1 结果为 true,下拉框就能完美回显了。

(2)“三等(===)”是什么意思?

“三等”指的是 JavaScript 中的严格相等运算符(===)

它和普通的两个等号(==)最大的区别在于:它不仅比较“值”是否相等,还要求“数据类型”必须完全一致。

  • 双等号(==):会进行隐式类型转换。例如 1 == "1",它会自动把字符串 "1" 转换成数字再进行比较,最终结果是 true
  • 三等号(===):绝对严格,不进行任何转换。例如 1 === "1",因为一个是数字(Number),一个是字符串(String),类型不同,直接返回 false

11、MyBatis 动态 SQL 更新策略对比:是否使用 <if> 判断

(1)不带 <if>

  • 传值时:正常更新为传入的值。
  • 不传值时(null)一定会更新。数据库字段会被强制覆盖为 null,导致原有数据丢失。
actual_arrival_time = #{actualArrivalTime}

(2)带 <if>

  • 传值时:正常更新为传入的值。
  • 不传值时(null)不会更新。SQL 会忽略该字段,数据库保持原来的值不变。
<if test="actualArrivalTime != null">actual_arrival_time = #{actualArrivalTime}</if>

12、Vue Router 传参与接参

发件人:this.$router(负责跳转)

  • 角色:全局路由实例,用于执行导航操作。

  • 动作:通过 push 将数据拼接到 URL 路径中。

  • 代码示例

    1this.$router.push({
    2  path: '/business/statementItem/index/' + statementId
    3})
    
  • 结果:URL 变为 /.../index/10086,数据作为路径参数传递。

收件人:this.$route(负责接收)

  • 角色:当前激活的路由信息对象,包含 URL、参数等状态。

  • 动作:从 params 中提取路径参数。

  • 代码示例

    1data() {
    2  return {
    3    // 安全获取参数,避免 params 为空时报错
    4    statementId: this.$route.params && this.$route.params.statementId
    5  }
    6}
    

13、数据库分析

  1. 所见即所得,页面上能看到的内容都需要添加字段。
  2. 是否存在关联表,需要存储关联表关键字段(id)。
  3. 考虑是否存在隐藏字段。
  4. 考虑是否存在小数位。该小数位应该使用什么表示(以金钱为主。decimal)
  5. 是否存在一些固定的值。考虑是否使用数据字典。

14、@JsonFormat@DateTimeFormat

特性 @JsonFormat @DateTimeFormat
作用方向 出参 (后端 -> 前端) 注:JSON入参也能管 入参 (前端 -> 后端) 专管表单/URL参数
所属框架 Jackson (JSON 处理库) Spring MVC (Web 数据绑定)
核心功能 将 Java Date 对象序列化为 JSON 字符串时的格式控制 将 HTTP 请求中的日期字符串解析为 Java Date 对象时的格式控制
典型场景 Controller 返回 JSON 数据给前端展示 接收 GET/POST 请求中的日期参数
时区支持 支持 (timezone 属性) 不支持
  • @JsonFormat"管出门" —— 后端返回数据时使用(同时也负责 JSON 格式的进门)。
  • @DateTimeFormat"管进门" —— 前端提交表单或 URL 传参时使用。

15、BaseEntityBaseController封装了哪些字段和方法?

(1)BaseEntity —— 实体层通用字段

所有业务实体类(如 SysUser, SysDept 等)通常继承此类,它封装了数据库表中几乎每个表都有的“审计字段”和“辅助字段”:

  • searchValue:搜索值,用于前端模糊查询时传递关键词。
  • createBy:创建者,记录数据创建人用户名。
  • createTime:创建时间,记录数据首次入库时间。
  • updateBy:更新者,记录最后修改人用户名。
  • updateTime:更新时间,记录数据最后一次被修改的时间。
  • remark:备注,用于存储额外说明信息。
  • params:请求参数映射,用于接收前端传来的非实体字段参数(如分页、排序等),常配合 MyBatis 动态 SQL 使用。

这些字段通过 MyBatis-Plus 或自定义拦截器实现自动填充(如 @TableField(fill = FieldFill.INSERT)),无需手动赋值。

(2)BaseController —— 控制层通用方法

所有 Controller 类继承此类,它提供了 Web 层常用的工具方法,简化接口开发:

分页相关

  • startPage():开启分页,自动从请求中提取页码和每页条数。
  • startOrderBy():设置排序规则,支持前端传入排序字段。
  • clearPage():清理分页线程变量,防止内存泄漏。

返回结果封装

  • success() / error():返回标准成功/失败响应。
  • toAjax(int rows):根据数据库操作影响行数自动返回成功或失败。
  • getDataTable(List<?> list):将查询结果封装为带总数和列表的分页响应对象(TableDataInfo)。

用户与权限获取

  • getUsername():获取当前登录用户名。
  • getUserId():获取当前登录用户 ID。
  • getLoginUser():获取完整登录用户对象(含角色、权限等)。

其他实用方法

  • initBinder():注册日期格式转换器,自动将前端字符串转为 Date 对象(替代部分 @DateTimeFormat 场景)。
  • getIpAddr():获取客户端 IP 地址。
  • getBrowser():获取浏览器类型。
posted on 2026-07-20 20:11  冬冬咚  阅读(13)  评论(0)    收藏  举报