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.

Top Architect
Top Architect
Top Architect
Implementing Audit Functionality with SpringBoot and Vue.js

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Top Architect
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.