Backend Development 20 min read

Implementing an Audit Functionality with SpringBoot, MySQL, and Vue.js

This article provides a step‑by‑step guide on designing and implementing an audit workflow in a SpringBoot application, covering database schema creation, multiple backend implementation strategies, RESTful controller code, Vue.js front‑end integration, and file upload handling with deduplication.

Architecture Digest
Architecture Digest
Architecture Digest
Implementing an Audit Functionality with SpringBoot, MySQL, and Vue.js

The article introduces a practical audit feature for a SpringBoot‑based system, explaining how users submit requests that administrators can later approve or reject, with the status reflected in both front‑end and back‑end interfaces.

1. Audit Function Implementation Options

Four design patterns are discussed:

Simple Table Transfer : Insert data into a temporary table (A) and move it to the target table (B) after approval. Advantages: simple logic; Disadvantages: tight coupling with back‑end.

Popup Confirmation : Front‑end triggers a modal for approval; after approval, the back‑end processes the request. Advantages: no back‑end embedding, supports full CRUD; Disadvantages: requires both operator and reviewer present.

Parameter Buffering : Store request parameters in a dedicated table, approve asynchronously, then invoke business logic. Advantages: no embedding, supports export and async operations; Disadvantages: needs framework support.

Temporary Table : Add an audit‑status column to each related table and operate on the temporary table first. Advantages: no framework dependency, supports async and unified data handling; Disadvantages: high back‑end coupling.

2. SpringBoot Backend Implementation

SQL for the audit table:

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 NULL COMMENT '待审核,审核通过,审核不通过',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Key controller AuditController (RESTful endpoints for CRUD, pagination, and state changes):

@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
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
qw = new QueryWrapper<>();
        qw.eq("username", username);
        return Result.success(auditService.getOne(qw));
    }

    @GetMapping("/page")
    public Result findPage(@RequestParam Integer pageNum,
                           @RequestParam Integer pageSize,
                           @RequestParam(defaultValue = "") String name) {
        QueryWrapper
qw = new QueryWrapper<>();
        qw.orderByDesc("id");
        if (!"".equals(name)) {
            qw.like("name", name);
        }
        User currentUser = TokenUtils.getCurrentUser();
        // role‑based filtering can be added here
        return Result.success(auditService.page(new Page<>(pageNum, pageSize), qw));
    }
}

3. Front‑End Integration (Vue + Element‑UI)

The Vue component handles listing, searching, pagination, adding, editing, and batch deletion of audit records. Key template snippets:

<el-table :data="tableData" border stripe>
  <el-table-column prop="name" label="报修描述"/>
  <el-table-column prop="user" label="用户"/>
  <el-table-column prop="createTime" label="创建时间"/>
  <el-table-column label="图片">
    <template slot-scope="scope">
      <el-image :src="scope.row.img" :preview-src-list="[scope.row.img]" style="width:100px;height:100px"/>
    </template>
  </el-table-column>
  <el-table-column prop="state" label="进度"/>
  <el-table-column label="操作">
    <template slot-scope="scope">
      <el-button type="success" @click="handleEdit(scope.row)" :disabled="scope.row.state !== '待审核'">编辑</el-button>
    </template>
  </el-table-column>
</el-table>

Corresponding script includes methods for loading data, saving, editing, batch deletion, pagination handling, and image upload success handling.

4. File Upload and Management

SQL for the file table (stores uploaded file metadata and deduplication info):

CREATE TABLE `file` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '文件名称',
  `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '文件类型',
  `size` bigint DEFAULT NULL COMMENT '文件大小(kb)',
  `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '下载链接',
  `md5` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '文件md5',
  `creat_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '时间',
  `is_delete` tinyint(1) DEFAULT '0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=115 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

FileController provides endpoints for uploading files with MD5 deduplication, downloading by UUID, updating metadata, and soft‑deleting records:

@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
qw = new QueryWrapper<>();
        qw.eq("md5", md5);
        List
list = fileMapper.selectList(qw);
        return list.isEmpty() ? null : list.get(0);
    }

    // Additional endpoints for update, detail, soft‑delete, batch delete, and pagination omitted for brevity
}

5. Summary

The guide demonstrates a complete end‑to‑end solution for an audit workflow, combining MySQL schema design, SpringBoot REST services, Vue.js UI components, and a robust file upload mechanism with MD5‑based deduplication, providing a reusable pattern for similar enterprise applications.

Backend DevelopmentMySQLFile UploadVue.jsSpringBootREST APIaudit
Architecture Digest
Written by

Architecture Digest

Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.

0 followers
Reader feedback

How this landed with the community

login 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.