文章目录
- 什么是若依
- 使用若依
- 验证码的前端实现
-
- [📌 前后端验证码流程说明文档](#📌 前后端验证码流程说明文档)
-
- 1、前端初始化验证码
- 2、前端界面显示
- [3、后端生成验证码接口(GET /captchaImage)](#3、后端生成验证码接口(GET /captchaImage))
- 4、用户提交登录信息
- [5、后端验证验证码逻辑(POST /login)](#5、后端验证验证码逻辑(POST /login))
- 6、登录失败处理(前端)
- 新增岗位功能详细解析
-
- 一、前端实现流程
-
- [1. 用户触发新增操作](#1. 用户触发新增操作)
- [2. 初始化表单](#2. 初始化表单)
- [3. 表单结构](#3. 表单结构)
- [4. 表单验证规则](#4. 表单验证规则)
- [5. 提交表单](#5. 提交表单)
- 二、后端实现流程
-
- [1. 控制器接收请求](#1. 控制器接收请求)
- [2. 唯一性校验](#2. 唯一性校验)
- [3. 数据插入实现](#3. 数据插入实现)
- [4. 实体类验证](#4. 实体类验证)
- 三、关键特性与设计思想
- 四、完整数据流
- 修改岗位功能详细解析
-
- 一、前端实现流程
-
- [1. 修改按钮触发 (Post.vue)](#1. 修改按钮触发 (Post.vue))
- [2. 处理修改操作 (Post.vue)](#2. 处理修改操作 (Post.vue))
- [3. 获取岗位详情API (post.js)](#3. 获取岗位详情API (post.js))
- [4. 表单提交处理 (Post.vue)](#4. 表单提交处理 (Post.vue))
- [5. 修改岗位API (post.js)](#5. 修改岗位API (post.js))
- 二、后端实现流程
-
- [1. 控制器接收请求 (SysPostController.java)](#1. 控制器接收请求 (SysPostController.java))
- [2. 服务层处理 (SysPostServiceImpl.java)](#2. 服务层处理 (SysPostServiceImpl.java))
- [3. 唯一性校验逻辑 (SysPostServiceImpl.java)](#3. 唯一性校验逻辑 (SysPostServiceImpl.java))
- [4. Mapper层SQL (SysPostMapper.xml)](#4. Mapper层SQL (SysPostMapper.xml))
- 三、关键设计要点
- 删除岗位功能详细解析
-
- 一、前端实现流程
-
- [1. 删除触发方式](#1. 删除触发方式)
- [2. 删除处理逻辑](#2. 删除处理逻辑)
- [3. 关键处理步骤](#3. 关键处理步骤)
- 二、后端实现流程
-
- [1. 控制器入口](#1. 控制器入口)
- [2. 服务层实现](#2. 服务层实现)
- [3. 关键业务逻辑](#3. 关键业务逻辑)
- 三、安全与完整性设计
-
- [1. 多级保护机制](#1. 多级保护机制)
- [2. 外键约束建议(DDL示例)](#2. 外键约束建议(DDL示例))
- 四、异常处理流程
-
- [1. 业务异常处理](#1. 业务异常处理)
- [2. 前端异常处理](#2. 前端异常处理)
- 五、设计亮点分析
- 六、完整工作流程
- 查询岗位功能详细解析
-
- 一、查询功能分类
- 二、分页条件查询(核心功能)
-
- [1. 前端实现流程](#1. 前端实现流程)
- [2. 后端实现流程](#2. 后端实现流程)
- [3. 分页机制](#3. 分页机制)
- 三、导出查询
-
- [1. 前端实现](#1. 前端实现)
- [2. 后端实现](#2. 后端实现)
- 四、详情查询(单个岗位查询)
-
- [1. 前端实现](#1. 前端实现)
- [2. 后端实现](#2. 后端实现)
- 五、查询功能设计亮点
- 六、复杂查询场景处理
- 岗位导出功能详细解析
什么是若依
提示:这里可以添加本文要记录的大概内容:
若依是开源项目,便于二次开发,百度直接搜索:若依官网
1、减少了自己的代码量
2、学习优秀开源项目底层的编程思想,设计思路,提高自己的编程能力
使用若依
使用开源项目的步骤:
1、下载并运行
启动后端:
启动前端:VS CODE 插件GitHub Copilot Chat
,Agent
模式下
项目成功启动页面:
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 图片数据拼接为可直接显示的 URLloginForm.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() // 刷新列表
})
}
})
}
- 触发表单验证
- 验证通过后调用
addPost
API - 显示操作成功提示
- 关闭对话框并刷新岗位列表
二、后端实现流程
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
确保数值字段不为空- 验证失败自动返回错误信息给前端
三、关键特性与设计思想
-
多层验证机制:
- 前端:Element Plus表单验证
- 网络层:JSON格式校验
- 后端:JSR-303实体验证
- 业务层:唯一性校验
- 数据库:唯一约束(需在DDL中定义)
-
操作审计:
- 通过
@Log
注解自动记录:
- 通过
java
@Log(title = "岗位管理", businessType = BusinessType.INSERT)
-
安全控制:
- 权限校验:
@PreAuthorize("@ss.hasPermi('system:post:add')"
- 数据隔离:自动注入当前用户
getUsername()
- 权限校验:
-
响应式设计:
- 前端:提交后自动刷新列表
- 后端:返回标准化的AjaxResult对象
-
异常处理:
- 唯一性冲突返回可读性错误信息
- 数据库操作异常全局处理
四、完整数据流
- 用户点击"新增"按钮
- 前端打开对话框并重置表单
- 用户填写表单并提交
- 前端进行字段级验证
- 验证通过后调用
/system/post
POST API - 后端控制器接收请求:
- 权限校验
- 实体字段验证
- 业务规则校验(唯一性)
- 服务层设置创建人
- Mapper执行动态SQL插入
- 返回操作结果(成功/失败)
- 前端提示结果并刷新列表
这种设计实现了前后端分离架构下的高效协作,通过多层验证保证数据质量,利用注解简化开发,同时确保系统的安全性和可维护性。
修改岗位功能详细解析
我将详细讲解岗位管理模块中的修改功能,从前端到后端的完整实现流程。修改功能是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 = "修改岗位"; // 设置对话框标题
});
}
执行流程:
- 重置表单状态,确保无残留数据
- 从行数据中获取岗位ID
- 调用API获取岗位详细信息
- 将返回数据填充到表单
- 打开对话框并设置标题
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
判断是修改还是新增 - 调用
updatePost
API提交修改 - 成功后关闭对话框并刷新列表
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));
}
执行流程:
@PreAuthorize
进行权限校验@Log
记录操作日志@Validated
进行参数校验(基于JSR-303)- 校验岗位名称和编码的唯一性
- 设置更新人(当前登录用户)
- 调用服务层执行更新
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;
}
校验逻辑:
- 获取当前岗位ID(修改时为真实ID,新增时为null)
- 查询数据库是否存在相同名称的岗位
- 如果存在同名岗位:
- 且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特点:
- 使用动态SQL(
<set>
和<if>
标签) - 只更新非空字段
- 自动设置更新时间
- 使用数据库函数设置时间(sysdate())
- 根据postId定位记录
三、关键设计要点
-
权限控制双重保障
- 前端:
v-hasPermi
指令控制按钮显示 - 后端:
@PreAuthorize
注解进行方法级权限校验
- 前端:
-
数据一致性保证
- 唯一性校验:防止名称/编码重复
- 事务管理:确保更新操作的原子性
-
审计字段自动化
- 自动记录更新人:
post.setUpdateBy(getUsername())
- 自动更新时间:
update_time = sysdate()
- 自动记录更新人:
-
前后端校验结合
- 前端:Element Plus表单校验
- 后端:JSR-303参数校验 + 业务逻辑校验
-
性能优化
- 动态SQL:只更新变化的字段
- 最小化数据传输:前端只发送必要字段
-
用户体验优化
- 修改前加载完整数据
- 操作成功自动刷新列表
- 明确的错误提示信息
删除岗位功能详细解析
删除功能是岗位管理系统中最敏感的操作之一,需要特别关注数据完整性和安全控制。下面从前后端协同角度深入解析删除功能的实现:
一、前端实现流程
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. 关键处理步骤
- 获取删除目标 :
- 单个删除:从行数据获取
row.postId
- 批量删除:从选中的
ids
数组中获取多个ID
- 单个删除:从行数据获取
javascript
const postIds = row.postId || ids.value;
-
二次确认:
- 使用
$modal.confirm
显示确认对话框 - 明确显示将被删除的岗位编号
- 使用
-
API调用:
- 调用
delPost
方法发送删除请求 - 支持单个ID或ID数组(自动处理)
- 调用
-
结果反馈:
- 成功:显示"删除成功"提示
- 失败:全局异常处理(已在底层封装)
-
状态更新:
- 刷新岗位列表数据
- 重置选中状态
二、后端实现流程
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. 关键业务逻辑
- 使用状态检查:
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>
-
安全删除机制:
- 遍历每个待删除岗位
- 检查
sys_user_post
关联表 - 如果存在关联用户,抛出业务异常
- 包含岗位名称的友好错误提示
-
批量删除执行:
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等)
}
);
五、设计亮点分析
-
批量操作优化:
- 单次数据库交互完成批量删除(IN语句)
- 减少数据库连接开销
-
用户友好设计:
- 明确提示哪个岗位无法删除
- 二次确认避免误操作
- 批量选择状态自动管理
-
事务完整性:
java
@Transactional(rollbackFor = Exception.class)
public int deletePostByIds(Long[] postIds) {
// 操作在同一个事务中
}
- 整个删除操作原子性执行
- 检查与删除要么全成功,要么全回滚
- 前后端协作 :
- RESTful风格API:
DELETE /system/post/{ids}
- 统一的ID传递格式(数组自动转换)
- 标准化的响应格式(AjaxResult)
- RESTful风格API:
六、完整工作流程
-
前端操作:
- 用户选择单个/多个岗位
- 点击删除按钮
- 确认删除提示
-
请求发送:
http
DELETE /system/post/12,34,56
Authorization: Bearer xxxx
-
后端处理:
- 权限验证(@PreAuthorize)
- 日志记录(@Log)
- 遍历检查每个岗位使用状态
- 执行批量删除SQL
- 返回操作结果
-
结果反馈:
- 成功:
{ code: 200, msg: "操作成功" }
- 失败:
{ code: 500, msg: "经理岗位已分配,不能删除" }
- 成功:
-
前端响应:
- 显示操作结果提示
- 刷新岗位列表
- 重置选中状态
这种设计确保了删除操作的安全性、完整性和用户体验,通过多层校验防止数据误删,同时提供清晰的反馈帮助用户理解操作结果。
查询岗位功能详细解析
我将详细讲解岗位管理模块中的各种查询功能,包括分页查询、条件过滤、详情查询和下拉框查询等。查询功能是系统的核心基础功能,设计良好的查询机制能极大提升用户体验。
一、查询功能分类
查询类型 | 前端调用方法 | 后端接口 | 主要用途 |
---|---|---|---|
分页条件查询 | 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. 分页机制
分页关键点:
startPage()
方法从请求参数中解析pageNum
和pageSize
- PageHelper自动改写SQL添加分页语句
- 分页信息存储在ThreadLocal中
- 查询结束后自动获取总记录数
三、导出查询
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>
五、查询功能设计亮点
-
统一查询逻辑复用
- 分页查询和导出查询共用同一套查询逻辑
- 避免代码重复,保证数据一致性
-
灵活的条件组合
sql
SELECT * FROM sys_post
WHERE 1=1
/* 动态添加条件 */
AND post_code LIKE '%DEV%'
AND status = '0'
ORDER BY post_sort ASC
- 使用MyBatis动态SQL
- 支持多条件自由组合
-
安全的分页机制
- 最大分页限制(配置文件设置)
- 防止恶意请求大量数据
-
响应式前端设计
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>
- 加载状态提示
- 字典值自动转换
- 分页组件与表格联动
- 完善的权限控制
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生成,前端负责文件下载。合理的设计可以支持从几百条到数百万条数据的导出需求,同时保证系统的稳定性和安全性。