Implementing Audit Functionality with SpringBoot and Vue.js
This article provides a comprehensive guide on building an audit feature using SpringBoot for the backend and Vue.js for the frontend, covering multiple implementation strategies, database schema design, RESTful APIs, file upload handling, and complete code examples for both server and client sides.
The article begins by outlining four different approaches to implement an audit function: a simple direct table method, a modal dialog method, a buffered parameter method, and a temporary table method, each with its own advantages and disadvantages.
SpringBoot Implementation
1. Database Table Creation
CREATE TABLE `audit` (
`id` int NOT NULL AUTO_INCREMENT COMMENT 'ID',
`name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '报修名称',
`user` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '报修人',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '报修时间',
`img` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '详情图片',
`state` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT '待审核' COMMENT '待审核,审核通过,审核不通过',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;2. Java Backend (AuditController)
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.common.Result;
import com.example.demo.entity.Audit;
import com.example.demo.service.IAuditService;
import com.example.demo.utils.TokenUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
@CrossOrigin
@RestController
@RequestMapping("/audit")
public class AuditController {
@Resource
private IAuditService auditService;
@Resource
private FileMapper fileMapper;
@PostMapping
public Result save(@RequestBody Audit audit) {
if (audit.getId() == null) {
audit.setUser(TokenUtils.getCurrentUser().getUsername());
}
auditService.saveOrUpdate(audit);
return Result.success();
}
@PostMapping("/del/batch")
public Result deleteBatch(@RequestBody List<Integer> ids) {
return Result.success(auditService.removeByIds(ids));
}
@GetMapping
public Result findAll() {
return Result.success(auditService.list());
}
@GetMapping("/{id}")
public Result findOne(@PathVariable Integer id) {
return Result.success(auditService.getById(id));
}
@GetMapping("/username/{username}")
public Result findByUsername(@PathVariable String username) {
QueryWrapper<Audit> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", username);
return Result.success(auditService.getOne(queryWrapper));
}
@GetMapping("/page")
public Result findPage(@RequestParam Integer pageNum,
@RequestParam Integer pageSize,
@RequestParam(defaultValue = "") String name) {
QueryWrapper<Audit> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("id");
if (!"".equals(name)) {
queryWrapper.like("name", name);
}
User currentUser = TokenUtils.getCurrentUser();
return Result.success(auditService.page(new Page<>(pageNum, pageSize), queryWrapper));
}
}Frontend Integration (Vue + Element UI)
The Vue component provides a table to list audit records, search functionality, pagination, and dialogs for creating or editing entries. It uses Element UI components such as el-table, el-dialog, el-button, and el-upload for image handling.
<template>
<div>
<el-input v-model="name" placeholder="请输入报修描述"></el-input>
<el-button @click="load">搜索</el-button>
<el-button @click="reset">刷新</el-button>
<el-button type="primary" @click="handleAdd">新增</el-button>
<el-table :data="tableData" @selection-change="handleSelectionChange">
...
</el-table>
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="pageNum" :page-size="pageSize" :total="total"></el-pagination>
<el-dialog :visible.sync="dialogFormVisible" title="用户信息">
<el-form>
<el-form-item label="报修描述">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="物品图片">
<el-upload action="/file/upload" :on-success="handleImgUploadSuccess">
<el-button type="primary">点击上传</el-button>
</el-upload>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="save">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
name: "Audit",
data() { return { tableData: [], total: 0, pageNum: 1, pageSize: 5, name: "", form: {}, dialogFormVisible: false, multipleSelection: [] }; },
created() { this.load(); },
methods: {
load() { /* request /audit/page */ },
save() { /* request POST /audit */ },
handleAdd() { this.dialogFormVisible = true; this.form = {}; },
handleEdit(row) { this.form = row; this.dialogFormVisible = true; },
handleSelectionChange(val) { this.multipleSelection = val; },
delBatch() { /* request POST /audit/del/batch */ },
reset() { this.name = ""; this.load(); },
handleSizeChange(size) { this.pageSize = size; this.load(); },
handleCurrentChange(num) { this.pageNum = num; this.load(); },
handleImgUploadSuccess(res) { this.form.img = res; }
}
};
</script>File Upload Service
A separate FileController handles file uploads, deduplicates files using MD5, stores metadata in a MySQL table, and provides a download endpoint.
@RestController
@RequestMapping("/file")
public class FileController {
@Value("${files.upload.path}")
private String fileUploadPath;
@Value("${server.ip}")
private String serverIp;
@Resource
private FileMapper fileMapper;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@PostMapping("/upload")
public String upload(@RequestParam MultipartFile file) throws IOException {
String originalFilename = file.getOriginalFilename();
String type = FileUtil.extName(originalFilename);
long size = file.getSize();
String fileUUID = IdUtil.fastSimpleUUID() + StrUtil.DOT + type;
File uploadFile = new File(fileUploadPath + fileUUID);
File parentFile = uploadFile.getParentFile();
if (!parentFile.exists()) { parentFile.mkdirs(); }
String md5 = SecureUtil.md5(file.getInputStream());
Files dbFiles = getFileByMd5(md5);
String url;
if (dbFiles != null) {
url = dbFiles.getUrl();
} else {
file.transferTo(uploadFile);
url = "http://" + serverIp + ":9090/file/" + fileUUID;
}
Files saveFile = new Files();
saveFile.setName(originalFilename);
saveFile.setType(type);
saveFile.setSize(size/1024);
saveFile.setUrl(url);
saveFile.setMd5(md5);
fileMapper.insert(saveFile);
return url;
}
@GetMapping("/{fileUUID}")
public void download(@PathVariable String fileUUID, HttpServletResponse response) throws IOException {
File uploadFile = new File(fileUploadPath + fileUUID);
ServletOutputStream os = response.getOutputStream();
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileUUID, "UTF-8"));
response.setContentType("application/octet-stream");
os.write(FileUtil.readBytes(uploadFile));
os.flush();
os.close();
}
private Files getFileByMd5(String md5) {
QueryWrapper<Files> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("md5", md5);
List<Files> list = fileMapper.selectList(queryWrapper);
return list.isEmpty() ? null : list.get(0);
}
// other CRUD methods omitted for brevity
}Conclusion
The guide demonstrates how to design a flexible audit workflow, implement the necessary backend APIs with SpringBoot, manage file uploads efficiently, and create a responsive Vue.js frontend, providing a complete end‑to‑end solution for audit features in Java web applications.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
