若依【(前后端分离版)SpringBoot+Vue3】

文章目录


什么是若依

提示:这里可以添加本文要记录的大概内容:

若依是开源项目,便于二次开发,百度直接搜索:若依官网

1、减少了自己的代码量

2、学习优秀开源项目底层的编程思想,设计思路,提高自己的编程能力

使用若依

使用开源项目的步骤:

1、下载并运行

启动后端:

启动前端:VS CODE 插件GitHub Copilot ChatAgent模式下

项目成功启动页面:

2、看懂业务流程

3、进行二次开发

验证码的前端实现

后端生成一个表达式

javascript 复制代码
1+1=2
1+1=?@2

1+1=?转成图片,传到前端展示,答案2存入Redis

输入答案点击登录,就把表单存入后台了,这时候从Redis中把正确答案拿出来,两个答案对比

📌 前后端验证码流程说明文档

1、前端初始化验证码

在 Vue 的 <script setup> 中,组件挂载时会调用 getCode() 函数初始化验证码。

getCode() 函数逻辑:

js 复制代码
getCode().then(res => {
  captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled

  if (captchaEnabled.value) {
    codeUrl.value = "data:image/gif;base64," + res.img
    loginForm.value.uuid = res.uuid
  }
})
  • captchaEnabled.value:根据后端返回决定是否启用验证码(默认启用)
  • codeUrl.value:将 base64 图片数据拼接为可直接显示的 URL
  • loginForm.value.uuid:保存后端返回的验证码唯一标识(UUID)

2、前端界面显示

在模板中通过 v-if="captchaEnabled" 控制验证码区域的显示:

html 复制代码
<el-form-item prop="code" v-if="captchaEnabled">
  <el-input v-model="loginForm.code" placeholder="验证码" />
  <div class="login-code">
    <img :src="codeUrl" @click="getCode" title="点击刷新验证码" />
  </div>
</el-form-item>
  • 点击图片时重新调用 getCode(),刷新验证码
  • 用户输入验证码后,与 uuid 一起提交给后端

3、后端生成验证码接口(GET /captchaImage)

通过getCodeImg引入

找到对应代码:

解释:

定义一个名为 getCodeImg 的函数
request:这是封装好的 Axios 请求函数,通常是项目中封装的 HTTP 请求方法。

整个 request({ ... }) 是一个 HTTP 请求配置对象。

请求的后端接口地址是 /captchaImage

使用 HTTP 的 GET 方法发送请求,用于获取数据

设置请求超时时间为 20 秒(20000 毫秒),如果 20 秒内没有返回结果,请求将自动中断,并进入 .catch() 分支。

在登录页F12

打开 request 文件

创建一个 Axios 实例 service,用于发起 HTTP 请求

配置该实例的基础 URL 为一个环境变量,实现环境自适应

设置请求超时时间为 10 秒,防止请求长时间挂起

值在配置文件中定义,配置文件:

4、用户提交登录信息

用户点击登录时,前端调用 handleLogin() 提交数据:

js 复制代码
userStore.login(loginForm.value)

提交内容包括:

  • username:用户名
  • password:密码
  • code:用户输入的验证码
  • uuid:当前验证码的唯一标识

5、后端验证验证码逻辑(POST /login)

登录请求:

js 复制代码
export function login(username, password, code, uuid) {
  return request({
    url: '/login',
    method: 'post',
    data: { username, password, code, uuid }
  })
}

后端伪代码逻辑:

js 复制代码
function login(username, password, code, uuid) {
  // 1. 检查是否启用验证码
  if (captchaEnabled) {

    // 2. 从 Redis 获取验证码
    String redisCode = redis.get(uuid);

    // 3. 验证码比对(忽略大小写)
    if (!redisCode || !redisCode.equalsIgnoreCase(code)) {
      return error("验证码错误");
    }

    // 4. 删除已使用的验证码
    redis.delete(uuid);
  }

  // 5. 继续账号密码验证...
}

6、登录失败处理(前端)

js 复制代码
userStore.login(loginForm.value).catch(() => {
  loading.value = false
  if (captchaEnabled.value) {
    getCode() // 登录失败时刷新验证码
  }
})
  • 登录失败时自动刷新验证码,防止暴力破解
  • 用户点击图片也可手动刷新验证码

📋完整流程图

复制代码
getCodeImg()
    ↓
调用 request()
    ↓
发送 GET 请求到 /captchaImage
    ↓
请求头中设置 isToken = false(表示不需要 token)
    ↓
等待响应(最多 20 秒)
    ↓
返回 base64 图片和 uuid

新增岗位功能详细解析

新增功能是岗位管理系统的核心操作之一,涉及前后端协同工作。下面从用户操作到数据存储的完整流程进行详细解析:

一、前端实现流程

1. 用户触发新增操作

javascript 复制代码
<el-button 
  type="primary" 
  plain 
  icon="Plus"
  @click="handleAdd"
  v-hasPermi="['system:post:add']"
>新增</el-button>
  • v-hasPermi指令校验用户是否有system:post:add权限
  • 点击按钮触发handleAdd方法

2. 初始化表单

javascript 复制代码
function handleAdd() {
  reset() // 重置表单数据
  open.value = true // 打开对话框
  title.value = "添加岗位" // 设置对话框标题
}

function reset() {
  form.value = {
    postId: undefined,
    postCode: undefined,
    postName: undefined,
    postSort: 0,
    status: "0",
    remark: undefined
  }
  proxy.resetForm("postRef") // 重置表单验证状态
}
  • 初始化表单对象,设置默认值(如状态默认为"0"正常)
  • 重置表单验证状态,清除之前的错误提示

3. 表单结构

javascript 复制代码
<el-dialog :title="title" v-model="open" width="500px">
  <el-form ref="postRef" :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>
</el-dialog>
  • 使用el-dialog组件实现模态对话框
  • el-form绑定表单数据和验证规则

4. 表单验证规则

javascript 复制代码
const rules = {
    postName: [{ required: true, message: "岗位名称不能为空", trigger: "blur" }],
    postCode: [{ required: true, message: "岗位编码不能为空", trigger: "blur" }],
    postSort: [{ required: true, message: "岗位顺序不能为空", trigger: "blur" }],
}
  • 定义字段级验证规则
  • required标记必填字段
  • trigger: 'blur'表示失去焦点时触发验证

5. 提交表单

javascript 复制代码
function submitForm() {
  proxy.$refs["postRef"].validate(valid => {
    if (valid) {
      addPost(form.value).then(response => {
        proxy.$modal.msgSuccess("新增成功")
        open.value = false // 关闭对话框
        getList() // 刷新列表
      })
    }
  })
}
  1. 触发表单验证
  2. 验证通过后调用addPost API
  3. 显示操作成功提示
  4. 关闭对话框并刷新岗位列表

二、后端实现流程

1. 控制器接收请求

java 复制代码
@PostMapping
@PreAuthorize("@ss.hasPermi('system:post:add')")
@Log(title = "岗位管理", businessType = BusinessType.INSERT)
public AjaxResult add(@Validated @RequestBody SysPost post) {
    // 唯一性校验
    if (!postService.checkPostNameUnique(post)) {
        return error("新增岗位'" + post.getPostName() + "'失败,岗位名称已存在");
    } else if (!postService.checkPostCodeUnique(post)) {
        return error("新增岗位'" + post.getPostName() + "'失败,岗位编码已存在");
    }
    // 设置创建人
    post.setCreateBy(getUsername());
    // 执行插入
    return toAjax(postService.insertPost(post));
}
  • @PostMapping处理POST请求
  • @PreAuthorize校验用户权限
  • @Log记录操作日志(类型为INSERT)
  • @Validated触发实体类字段验证
  • @RequestBody接收JSON格式的岗位数据

2. 唯一性校验

java 复制代码
// 校验岗位名称唯一性
    @Override
    public boolean checkPostNameUnique(SysPost post) {
        Long postId = StringUtils.isNull(post.getPostId()) ? -1L : post.getPostId();
        SysPost info = postMapper.checkPostNameUnique(post.getPostName());
        if (StringUtils.isNotNull(info) && info.getPostId().longValue() != postId.longValue()) {
            return UserConstants.NOT_UNIQUE;
        }
        return UserConstants.UNIQUE;
    }

// Mapper查询
@Select("select * from sys_post where post_name = #{postName} limit 1")
SysPost checkPostNameUnique(String postName);
  • 查询数据库是否存在相同岗位名称
  • 排除当前岗位自身(更新时使用)
  • 同样逻辑校验岗位编码唯一性

3. 数据插入实现

java 复制代码
// 服务层
public int insertPost(SysPost post) {
    return postMapper.insertPost(post);
}

// Mapper XML
<insert id="insertPost" parameterType="SysPost" useGeneratedKeys="true" keyProperty="postId">
    insert into sys_post(
    <if test="postId != null and postId != 0">post_id,</if>
    <if test="postCode != null and postCode != ''">post_code,</if>
    <if test="postName != null and postName != ''">post_name,</if>
    <if test="postSort != null">post_sort,</if>
    <if test="status != null and status != ''">status,</if>
    <if test="remark != null and remark != ''">remark,</if>
    <if test="createBy != null and createBy != ''">create_by,</if>
    create_time
    )values(
    <if test="postId != null and postId != 0">#{postId},</if>
    <if test="postCode != null and postCode != ''">#{postCode},</if>
    <if test="postName != null and postName != ''">#{postName},</if>
    <if test="postSort != null">#{postSort},</if>
    <if test="status != null and status != ''">#{status},</if>
    <if test="remark != null and remark != ''">#{remark},</if>
    <if test="createBy != null and createBy != ''">#{createBy},</if>
    sysdate()
    )
</insert>
  • 动态生成SQL语句
  • useGeneratedKeys="true"获取自增主键
  • 自动填充创建时间和创建人
  • 只插入非空字段,提高灵活性

4. 实体类验证

java 复制代码
public class SysPost extends BaseEntity {
    /**
     * 岗位编码
     */
    @Excel(name = "岗位编码")
    private String postCode;

    /**
     * 岗位名称
     */
    @Excel(name = "岗位名称")
    private String postName;

    /**
     * 岗位排序
     */
    @Excel(name = "岗位排序")
    private Integer postSort;

	// 其他字段
}
  • @NotBlank验证非空字符串
  • @Size限制字符串长度
  • @NotNull确保数值字段不为空
  • 验证失败自动返回错误信息给前端

三、关键特性与设计思想

  1. 多层验证机制

    • 前端:Element Plus表单验证
    • 网络层:JSON格式校验
    • 后端:JSR-303实体验证
    • 业务层:唯一性校验
    • 数据库:唯一约束(需在DDL中定义)
  2. 操作审计

    • 通过@Log注解自动记录:
java 复制代码
     @Log(title = "岗位管理", businessType = BusinessType.INSERT)
  1. 安全控制

    • 权限校验:@PreAuthorize("@ss.hasPermi('system:post:add')"
    • 数据隔离:自动注入当前用户getUsername()
  2. 响应式设计

    • 前端:提交后自动刷新列表
    • 后端:返回标准化的AjaxResult对象
  3. 异常处理

    • 唯一性冲突返回可读性错误信息
    • 数据库操作异常全局处理

四、完整数据流

  1. 用户点击"新增"按钮
  2. 前端打开对话框并重置表单
  3. 用户填写表单并提交
  4. 前端进行字段级验证
  5. 验证通过后调用/system/post POST API
  6. 后端控制器接收请求:
    • 权限校验
    • 实体字段验证
    • 业务规则校验(唯一性)
  7. 服务层设置创建人
  8. Mapper执行动态SQL插入
  9. 返回操作结果(成功/失败)
  10. 前端提示结果并刷新列表

这种设计实现了前后端分离架构下的高效协作,通过多层验证保证数据质量,利用注解简化开发,同时确保系统的安全性和可维护性。

修改岗位功能详细解析

我将详细讲解岗位管理模块中的修改功能,从前端到后端的完整实现流程。修改功能是CRUD操作中的重要环节,涉及数据加载、校验、更新等多个关键步骤。

一、前端实现流程

1. 修改按钮触发 (Post.vue)

javascript 复制代码
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
  <template #default="scope">
      <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:post:edit']">修改</el-button>
  </template>
</el-table-column>
  • 在表格操作列中放置修改按钮
  • 使用v-hasPermi指令进行权限控制
  • 点击时调用handleUpdate方法并传入当前行数据

2. 处理修改操作 (Post.vue)

javascript 复制代码
// 修改按钮操作
function handleUpdate(row) {
  reset(); // 重置表单状态
  const postId = row.postId; // 获取当前岗位ID
  getPost(postId).then(response => {
    form.value = response.data; // 填充表单数据
    open.value = true; // 打开对话框
    title.value = "修改岗位"; // 设置对话框标题
  });
}

执行流程:

  1. 重置表单状态,确保无残留数据
  2. 从行数据中获取岗位ID
  3. 调用API获取岗位详细信息
  4. 将返回数据填充到表单
  5. 打开对话框并设置标题

3. 获取岗位详情API (post.js)

javascript 复制代码
// 查询岗位详细
export function getPost(postId) {
  return request({
    url: '/system/post/' + postId,
    method: 'get'
  })
}
  • 向后台发送GET请求
  • URL格式:/system/post/{postId}
  • 获取指定ID的岗位详情

4. 表单提交处理 (Post.vue)

javascript 复制代码
// 提交按钮
function submitForm() {
  proxy.$refs["postRef"].validate(valid => {
    if (valid) {
      if (form.value.postId != undefined) {
        // 修改操作
        updatePost(form.value).then(response => {
          proxy.$modal.msgSuccess("修改成功");
          open.value = false; // 关闭对话框
          getList(); // 刷新列表
        })
      } else {
        // 新增操作...
      }
    }
  });
}
  • 先进行表单验证
  • 根据postId判断是修改还是新增
  • 调用updatePostAPI提交修改
  • 成功后关闭对话框并刷新列表

5. 修改岗位API (post.js)

javascript 复制代码
// 修改岗位
export function updatePost(data) {
  return request({
    url: '/system/post',
    method: 'put',
    data: data
  })
}
  • 使用HTTP PUT方法
  • 将整个表单数据作为请求体发送
  • URL为/system/post

二、后端实现流程

1. 控制器接收请求 (SysPostController.java)

java 复制代码
@PreAuthorize("@ss.hasPermi('system:post:edit')")
@Log(title = "岗位管理", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@Validated @RequestBody SysPost post) {
    // 唯一性校验
     if (!postService.checkPostNameUnique(post)) {
         return error("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在");
     } else if (!postService.checkPostCodeUnique(post)) {
         return error("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在");
     }
    // 设置更新人
    post.setUpdateBy(getUsername());
    // 执行更新
    return toAjax(postService.updatePost(post));
}

执行流程:

  1. @PreAuthorize进行权限校验
  2. @Log记录操作日志
  3. @Validated进行参数校验(基于JSR-303)
  4. 校验岗位名称和编码的唯一性
  5. 设置更新人(当前登录用户)
  6. 调用服务层执行更新

2. 服务层处理 (SysPostServiceImpl.java)

java 复制代码
@Override
public int updatePost(SysPost post) {
    return postMapper.updatePost(post);
}
  • 直接调用Mapper层执行更新
  • 返回影响行数

3. 唯一性校验逻辑 (SysPostServiceImpl.java)

java 复制代码
@Override
public boolean checkPostNameUnique(SysPost post) {
    Long postId = StringUtils.isNull(post.getPostId()) ? -1L : post.getPostId();
    SysPost info = postMapper.checkPostNameUnique(post.getPostName());
    if (StringUtils.isNotNull(info) && info.getPostId().longValue() != postId.longValue()) {
        return UserConstants.NOT_UNIQUE;
    }
    return UserConstants.UNIQUE;
}

校验逻辑:

  1. 获取当前岗位ID(修改时为真实ID,新增时为null)
  2. 查询数据库是否存在相同名称的岗位
  3. 如果存在同名岗位:
    • 且ID不同:表示是其他岗位的同名,返回不唯一
    • 且ID相同:表示是自身,允许更新

4. Mapper层SQL (SysPostMapper.xml)

xml 复制代码
<update id="updatePost" parameterType="SysPost">
    update sys_post
    <set>
        <if test="postCode != null and postCode != ''">post_code = #{postCode},</if>
        <if test="postName != null and postName != ''">post_name = #{postName},</if>
        <if test="postSort != null">post_sort = #{postSort},</if>
        <if test="status != null and status != ''">status = #{status},</if>
        <if test="remark != null">remark = #{remark},</if>
        <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
        update_time = sysdate()
    </set>
    where post_id = #{postId}
</update>

SQL特点:

  1. 使用动态SQL(<set><if>标签)
  2. 只更新非空字段
  3. 自动设置更新时间
  4. 使用数据库函数设置时间(sysdate())
  5. 根据postId定位记录

三、关键设计要点

  1. 权限控制双重保障

    • 前端:v-hasPermi指令控制按钮显示
    • 后端:@PreAuthorize注解进行方法级权限校验
  2. 数据一致性保证

    • 唯一性校验:防止名称/编码重复
    • 事务管理:确保更新操作的原子性
  3. 审计字段自动化

    • 自动记录更新人:post.setUpdateBy(getUsername())
    • 自动更新时间:update_time = sysdate()
  4. 前后端校验结合

    • 前端:Element Plus表单校验
    • 后端:JSR-303参数校验 + 业务逻辑校验
  5. 性能优化

    • 动态SQL:只更新变化的字段
    • 最小化数据传输:前端只发送必要字段
  6. 用户体验优化

    • 修改前加载完整数据
    • 操作成功自动刷新列表
    • 明确的错误提示信息

删除岗位功能详细解析

删除功能是岗位管理系统中最敏感的操作之一,需要特别关注数据完整性和安全控制。下面从前后端协同角度深入解析删除功能的实现:

一、前端实现流程

1. 删除触发方式

前端支持两种删除模式:

javascript 复制代码
<!-- 单个删除(行内操作) -->
<el-table-column label="操作">
  <template #default="scope">
    <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:post:remove']">删除</el-button>

  </template>
</el-table-column>

<!-- 批量删除(顶部操作栏) -->
<el-button
   type="danger"
   plain
   icon="Delete"
   :disabled="multiple"
   @click="handleDelete"
   v-hasPermi="['system:post:remove']"
>删除</el-button>

2. 删除处理逻辑

javascript 复制代码
function handleDelete(row) {
  // 获取待删除的岗位ID
  const postIds = row.postId || ids.value;
  
  // 确认对话框
  proxy.$modal.confirm('是否确认删除岗位编号为"' + postIds + '"的数据项?').then(function() {
      // 调用删除API
      return delPost(postIds);
    }).then(() => {
      // 删除成功后刷新列表
      getList();
      proxy.$modal.msgSuccess("删除成功");
    }).catch(() => {
      // 用户取消操作
    });
}

3. 关键处理步骤

  1. 获取删除目标
    • 单个删除:从行数据获取row.postId
    • 批量删除:从选中的ids数组中获取多个ID
javascript 复制代码
   const postIds = row.postId || ids.value;
  1. 二次确认

    • 使用$modal.confirm显示确认对话框
    • 明确显示将被删除的岗位编号
  2. API调用

    • 调用delPost方法发送删除请求
    • 支持单个ID或ID数组(自动处理)
  3. 结果反馈

    • 成功:显示"删除成功"提示
    • 失败:全局异常处理(已在底层封装)
  4. 状态更新

    • 刷新岗位列表数据
    • 重置选中状态

二、后端实现流程

1. 控制器入口

java 复制代码
@PreAuthorize("@ss.hasPermi('system:post:remove')")
@Log(title = "岗位管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{postIds}")
public AjaxResult remove(@PathVariable Long[] postIds) {
    return toAjax(postService.deletePostByIds(postIds));
}
  • @DeleteMapping:处理DELETE请求
  • @PathVariable:获取URL路径中的岗位ID数组
  • @PreAuthorize:权限校验(需system:post:remove权限)
  • @Log:记录操作日志(类型为DELETE)

2. 服务层实现

java 复制代码
@Override
public int deletePostByIds(Long[] postIds) {
    // 1. 检查岗位是否被用户使用
    for (Long postId : postIds) {
        SysPost post = selectPostById(postId);
        if (countUserPostById(postId) > 0) {
           throw new ServiceException(String.format("%1$s已分配,不能删除", post.getPostName()));
        }
    }
    
    // 2. 执行批量删除
    return postMapper.deletePostByIds(postIds);
}

3. 关键业务逻辑

  1. 使用状态检查
java 复制代码
   // 查询岗位用户关联数量
   public int countUserPostById(Long postId) {
       return userPostMapper.countUserPostById(postId);
   }
   
   // SysUserPostMapper.xml
   <select id="countUserPostById" resultType="int">
       SELECT COUNT(1) 
       FROM sys_user_post 
       WHERE post_id = #{postId}
   </select>
  1. 安全删除机制

    • 遍历每个待删除岗位
    • 检查sys_user_post关联表
    • 如果存在关联用户,抛出业务异常
    • 包含岗位名称的友好错误提示
  2. 批量删除执行

xml 复制代码
   <delete id="deletePostByIds" parameterType="Long">
       DELETE FROM sys_post WHERE post_id IN
       <foreach collection="array" item="postId" 
                open="(" separator="," close=")">
           #{postId}
       </foreach>
   </delete>
  • 使用MyBatis的<foreach>处理ID数组
  • 生成DELETE FROM sys_post WHERE post_id IN (1,2,3)语句

三、安全与完整性设计

1. 多级保护机制

层级 保护措施 目的
前端 v-hasPermi指令 控制按钮显示
网络 JWT令牌验证 身份认证
应用 @PreAuthorize注解 方法级权限控制
数据 关联检查 防止误删使用中的岗位
数据库 外键约束 最终数据保护

2. 外键约束建议(DDL示例)

sql 复制代码
CREATE TABLE sys_user_post (
  user_id BIGINT NOT NULL,
  post_id BIGINT NOT NULL,
  PRIMARY KEY (user_id, post_id),
  FOREIGN KEY (post_id) 
    REFERENCES sys_post(post_id)
    ON DELETE RESTRICT  -- 阻止删除被引用的岗位
);

四、异常处理流程

1. 业务异常处理

java 复制代码
// 服务层抛出异常
if (countUserPostById(postId) > 0) {
    throw new ServiceException(
        String.format("%1$s已分配,不能删除", post.getPostName())
    );
}

// 全局异常处理器
@ExceptionHandler(ServiceException.class)
public AjaxResult handleServiceException(ServiceException e) {
    return AjaxResult.error(e.getMessage());
}
  • 返回HTTP 200状态码(前端能处理的业务异常)
  • 错误信息格式:{ code: 500, msg: "岗位已分配,不能删除" }

2. 前端异常处理

javascript 复制代码
// post.js API封装
export function delPost(postId) {
  return request({
    url: '/system/post/' + postId,
    method: 'delete'
  })
}

// 全局响应拦截器
service.interceptors.response.use(
  response => {
    const res = response.data;
    if (res.code !== 200) {
      // 显示后端返回的错误信息
      Message.error(res.msg || 'Error');
      return Promise.reject(new Error(res.msg || 'Error'));
    }
    return res;
  },
  error => {
    // 处理HTTP错误(如401, 500等)
  }
);

五、设计亮点分析

  1. 批量操作优化

    • 单次数据库交互完成批量删除(IN语句)
    • 减少数据库连接开销
  2. 用户友好设计

    • 明确提示哪个岗位无法删除
    • 二次确认避免误操作
    • 批量选择状态自动管理
  3. 事务完整性

java 复制代码
   @Transactional(rollbackFor = Exception.class)
   public int deletePostByIds(Long[] postIds) {
       // 操作在同一个事务中
   }
  • 整个删除操作原子性执行
  • 检查与删除要么全成功,要么全回滚
  1. 前后端协作
    • RESTful风格API:DELETE /system/post/{ids}
    • 统一的ID传递格式(数组自动转换)
    • 标准化的响应格式(AjaxResult)

六、完整工作流程

  1. 前端操作

    • 用户选择单个/多个岗位
    • 点击删除按钮
    • 确认删除提示
  2. 请求发送

http 复制代码
   DELETE /system/post/12,34,56
   Authorization: Bearer xxxx
  1. 后端处理

    • 权限验证(@PreAuthorize)
    • 日志记录(@Log)
    • 遍历检查每个岗位使用状态
    • 执行批量删除SQL
    • 返回操作结果
  2. 结果反馈

    • 成功:{ code: 200, msg: "操作成功" }
    • 失败:{ code: 500, msg: "经理岗位已分配,不能删除" }
  3. 前端响应

    • 显示操作结果提示
    • 刷新岗位列表
    • 重置选中状态

这种设计确保了删除操作的安全性、完整性和用户体验,通过多层校验防止数据误删,同时提供清晰的反馈帮助用户理解操作结果。

查询岗位功能详细解析

我将详细讲解岗位管理模块中的各种查询功能,包括分页查询、条件过滤、详情查询和下拉框查询等。查询功能是系统的核心基础功能,设计良好的查询机制能极大提升用户体验。

一、查询功能分类

查询类型 前端调用方法 后端接口 主要用途
分页条件查询 listPost(query) GET /system/post/list 管理页面主列表展示
导出查询 同分页查询 POST /system/post/export Excel导出功能
详情查询 getPost(postId) GET /system/post/{postId} 查看/修改单个岗位详情
下拉框查询 optionselect() GET /system/post/optionselect 用户管理中的岗位选择下拉框

二、分页条件查询(核心功能)

1. 前端实现流程

页面组件 (Post.vue)

javascript 复制代码
<template>
	<!-- 搜索表单 -->
	<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
	    <el-form-item label="岗位编码" prop="postCode">
	       <el-input
	          v-model="queryParams.postCode"
	          placeholder="请输入岗位编码"
	          clearable
	          style="width: 200px"
	          @keyup.enter="handleQuery"
	       />
	    </el-form-item>
	    <el-form-item>
	       <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
	       <el-button icon="Refresh" @click="resetQuery">重置</el-button>
	    </el-form-item>
	 </el-form>
  
  <!-- 数据表格 -->
  <el-table :data="postList" v-loading="loading">
    <!-- 表格列定义 -->
    <el-table-column label="岗位编码" align="center" prop="postCode" />
    
  </el-table>
  
  <!-- 分页组件 -->
  <pagination 
    :total="total" 
    v-model:page="queryParams.pageNum"
    v-model:limit="queryParams.pageSize"
    @pagination="getList"
  />
</template>

<script setup>
import { listPost } from "@/api/system/post";

// 查询参数
const queryParams = reactive({
  pageNum: 1,      // 当前页码
  pageSize: 10,     // 每页条数
  postCode: "",     // 岗位编码条件
  postName: "",     // 岗位名称条件
  status: ""        // 状态条件
});

// 岗位列表数据
const postList = ref([]);
// 总记录数
const total = ref(0);
// 加载状态
const loading = ref(true);

// 获取岗位列表
function getList() {
  loading.value = true;
  listPost(queryParams).then(response => {
    postList.value = response.rows;  // 当前页数据
    total.value = response.total;    // 总记录数
    loading.value = false;
  });
}

// 处理搜索
function handleQuery() {
  queryParams.pageNum = 1;  // 重置到第一页
  getList();
}

// 初始化加载数据
getList();
</script>

2. 后端实现流程

Controller层 (SysPostController.java)

java 复制代码
@PreAuthorize("@ss.hasPermi('system:post:list')")
@GetMapping("/list")
public TableDataInfo list(SysPost post) {
    // 1. 启动分页
    startPage();  
    // 2. 查询数据
    List<SysPost> list = postService.selectPostList(post);
    // 3. 封装分页结果
    return getDataTable(list);
}

Service层 (SysPostServiceImpl.java)

java 复制代码
@Override
public List<SysPost> selectPostList(SysPost post) {
    return postMapper.selectPostList(post);
}

Mapper XML (SysPostMapper.xml)

xml 复制代码
<select id="selectPostList" parameterType="SysPost" resultMap="SysPostResult">
    SELECT post_id, post_code, post_name, post_sort, status, create_time
    FROM sys_post
    <where>
        <if test="postCode != null and postCode != ''">
            AND post_code LIKE CONCAT('%', #{postCode}, '%')
        </if>
        <if test="postName != null and postName != ''">
            AND post_name LIKE CONCAT('%', #{postName}, '%')
        </if>
        <if test="status != null and status != ''">
            AND status = #{status}
        </if>
    </where>
    ORDER BY post_sort ASC
</select>

3. 分页机制

分页关键点

  1. startPage()方法从请求参数中解析pageNumpageSize
  2. PageHelper自动改写SQL添加分页语句
  3. 分页信息存储在ThreadLocal中
  4. 查询结束后自动获取总记录数

三、导出查询

1. 前端实现

javascript 复制代码
function handleExport() {
  proxy.download("system/post/export", {
    ...queryParams.value
  }, `post_${new Date().getTime()}.xlsx`);
}

2. 后端实现

java 复制代码
@Log(title = "岗位管理", businessType = BusinessType.EXPORT)
@PreAuthorize("@ss.hasPermi('system:post:export')")
@PostMapping("/export")
public void export(HttpServletResponse response, SysPost post) {
    // 1. 查询所有符合条件的数据(不分页)
    List<SysPost> list = postService.selectPostList(post);
    // 2. 使用Excel工具类导出
    ExcelUtil<SysPost> util = new ExcelUtil<>(SysPost.class);
    util.exportExcel(response, list, "岗位数据");
}

特点

  • 复用相同的查询逻辑selectPostList
  • 不分页查询所有数据
  • 使用ExcelUtil工具类简化导出
  • 自动将实体字段映射到Excel列

四、详情查询(单个岗位查询)

1. 前端实现

javascript 复制代码
// 查询岗位详细
function handleUpdate(row) {
  const postId = row.postId;
  getPost(postId).then(response => {
    form.value = response.data;
    open.value = true;
  });
}

// API方法
export function getPost(postId) {
  return request({
    url: '/system/post/' + postId,
    method: 'get'
  })
}

2. 后端实现

java 复制代码
@PreAuthorize("@ss.hasPermi('system:post:query')")
@GetMapping(value = "/{postId}")
public AjaxResult getInfo(@PathVariable Long postId) {
    return success(postService.selectPostById(postId));
}

// Service实现
@Override
public SysPost selectPostById(Long postId) {
    return postMapper.selectPostById(postId);
}

// Mapper XML
<select id="selectPostById" parameterType="Long" resultMap="SysPostResult">
    SELECT * FROM sys_post WHERE post_id = #{postId}
</select>

五、查询功能设计亮点

  1. 统一查询逻辑复用

    • 分页查询和导出查询共用同一套查询逻辑
    • 避免代码重复,保证数据一致性
  2. 灵活的条件组合

sql 复制代码
   SELECT * FROM sys_post
   WHERE 1=1
     /* 动态添加条件 */
     AND post_code LIKE '%DEV%'
     AND status = '0'
   ORDER BY post_sort ASC
  • 使用MyBatis动态SQL
  • 支持多条件自由组合
  1. 安全的分页机制

    • 最大分页限制(配置文件设置)
    • 防止恶意请求大量数据
  2. 响应式前端设计

javascript 复制代码
   <el-table v-loading="loading" :data="postList">
     <el-table-column label="状态">
       <template #default="scope">
         <dict-tag :options="sys_normal_disable" :value="scope.row.status"/>
       </template>
     </el-table-column>
   </el-table>
  • 加载状态提示
  • 字典值自动转换
  • 分页组件与表格联动
  1. 完善的权限控制
java 复制代码
   // 列表查询权限
   @PreAuthorize("@ss.hasPermi('system:post:list')")
   
   // 导出权限
   @PreAuthorize("@ss.hasPermi('system:post:export')")
   
   // 详情查看权限
   @PreAuthorize("@ss.hasPermi('system:post:query')")

六、复杂查询场景处理

统计查询

java 复制代码
// 统计岗位使用人数
@Override
public int countUserPostById(Long postId) {
    return userPostMapper.countUserPostById(postId);
}

// Mapper
<select id="countUserPostById" resultType="int">
    SELECT COUNT(1) FROM sys_user_post WHERE post_id = #{postId}
</select>

岗位导出功能详细解析

导出功能是岗位管理系统的重要特性,允许用户将查询结果导出为Excel文件。下面从技术实现角度深入分析导出功能的完整流程:

一、前端实现流程

1. 导出按钮触发

javascript 复制代码
<el-button
  type="warning"
  plain
  icon="Download"
  @click="handleExport"
  v-hasPermi="['system:post:export']"
>导出</el-button>
  • v-hasPermi指令校验导出权限
  • 点击触发handleExport方法

2. 导出处理逻辑

javascript 复制代码
function handleExport() {
  // 调用封装的下载方法
  proxy.download("system/post/export", {
    ...queryParams.value  // 携带当前查询条件
  }, `post_${new Date().getTime()}.xlsx`); // 生成带时间戳的文件名
}

3. 下载方法封装

javascript 复制代码
// @/utils/request.js 在post.js内找:import request from '@/utils/request'
// 通用下载方法
export function download(url, params, filename, config) {
  downloadLoadingInstance = ElLoading.service({ text: "正在下载数据,请稍候", background: "rgba(0, 0, 0, 0.7)", })
  return service.post(url, params, {
    transformRequest: [(params) => { return tansParams(params) }],
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    responseType: 'blob',
    ...config
  }).then(async (data) => {
    const isBlob = blobValidate(data)
    if (isBlob) {
      const blob = new Blob([data])
      saveAs(blob, filename)
    } else {
      const resText = await data.text()
      const rspObj = JSON.parse(resText)
      const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
      ElMessage.error(errMsg)
    }
    downloadLoadingInstance.close()
  }).catch((r) => {
    console.error(r)
    ElMessage.error('下载文件出现错误,请联系管理员!')
    downloadLoadingInstance.close()
  })
}

二、后端实现流程

1. 控制器入口

java 复制代码
@Log(title = "岗位管理", businessType = BusinessType.EXPORT)
@PreAuthorize("@ss.hasPermi('system:post:export')")
@PostMapping("/export")
public void export(HttpServletResponse response, SysPost post) {
    // 查询所有符合条件的岗位
    List<SysPost> list = postService.selectPostList(post);

    // 使用Excel工具类导出
    ExcelUtil<SysPost> util = new ExcelUtil<>(SysPost.class);
    util.exportExcel(response, list, "岗位数据");
}

2. 实体类Excel注解

java 复制代码
public class SysPost extends BaseEntity {
    @Excel(name = "岗位序号", cellType = ColumnType.NUMERIC)
    private Long postId;

    @Excel(name = "岗位编码")
    private String postCode;

    @Excel(name = "岗位名称")
    private String postName;

    @Excel(name = "岗位排序", cellType = ColumnType.NUMERIC)
    private Integer postSort;

    @Excel(name = "状态", readConverterExp = "0=正常,1=停用")
    private String status;
}

导出功能通过前后端协同实现,后端负责数据处理和Excel生成,前端负责文件下载。合理的设计可以支持从几百条到数百万条数据的导出需求,同时保证系统的稳定性和安全性。

相关推荐
fake_ss198几秒前
计算机网络基础(一) --- (网络通信三要素)
java·网络·tcp/ip·udp·信息与通信
●VON15 分钟前
重生之我在暑假学习微服务第四天《Docker-下篇》
java·学习·docker·微服务·容器
优创学社215 分钟前
Springboot社区养老保险系统小程序
java·spring boot·后端
努力奋斗的小涛涛17 分钟前
java导出pdf(使用html)
java·pdf
小杰来搬砖30 分钟前
讲解java中基于Session实现登录的流程
后端
八苦30 分钟前
c# ACME client (补充)
后端
Rockson44 分钟前
Websocket实时行情接口 (2025最新使用教程)
java·javascript·websocket
用户1512905452201 小时前
A-Frame引擎开发:A-Frame渲染技术_(1).A-Frame引擎简介
后端
用户1512905452201 小时前
linux查看日志文件tail -f用法
后端
wr3541010661 小时前
Mac电脑使用IDEA启动服务后,报service异常
java·ide·spring boot·macos·tomcat·maven·java-consul