一、技术选型与环境准备
本系统采用前后端分离架构,后端使用Spring Boot 3.1.5 + MyBatis Plus,前端使用Vue 3 + Element Plus。开发前请确保本地环境已安装以下软件:
- JDK 17:Java开发环境,配置好JAVA_HOME环境变量。
- Node.js 18+:前端运行环境。
- Maven 3.8+:后端依赖管理工具。
- MySQL 8.0+:数据存储服务。
我们创建后端项目。打开终端,执行以下命令快速生成Spring Boot骨架,或者直接在IDEA中创建Spring Boot项目。
1. 后端项目初始化
在pom.xml中引入核心依赖,请确保版本号一致以避免兼容性问题:
```xml
org.springframework.boot
spring-boot-starter-web
com.baomidou
mybatis-plus-boot-starter
3.5.3.2
mysql
mysql-connector-java
8.0.33
org.projectlombok
lombok
true
```
2. 前端项目初始化
在终端执行以下命令创建Vue 3项目,并安装必要的UI组件库和HTTP请求库:
```bash
npm create vue@latest license-system
cd license-system
npm install element-plus axios
```
二、数据库设计与配置
在MySQL中执行以下SQL脚本,建立数据库及核心业务表。该表包含许可证的基本信息、有效期状态以及电子档案的存储路径。
```sql
CREATE DATABASE IF NOT EXISTS license_db DEFAULT CHARSET utf8mb4;
USE license_db;
CREATE TABLE t_license (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
license_no VARCHAR(64) NOT NULL COMMENT '许可证编号',
company_name VARCHAR(128) NOT NULL COMMENT '企业名称',
product_scope TEXT COMMENT '生产范围',
issue_date DATE COMMENT '发证日期',
expiry_date DATE COMMENT '有效期至',
file_url VARCHAR(255) COMMENT '电子档案路径',
status TINYINT DEFAULT 1 COMMENT '状态 1:有效 0:过期',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='生产许可证档案表';
```
后端数据源配置
在src/main/resources/application.yml中配置数据库连接信息及文件上传路径:
```yaml
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/license_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: 123456 请修改为实际数据库密码
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
file:
upload-dir: D:/license_files/ Windows环境,Linux请改为 /data/license_files/
```
三、后端核心功能开发
1. 实体类与Mapper
创建entity/License.java,使用Lombok简化代码:
```java
package com.example.license.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.time.LocalDate;
@Data
public class License {
@TableId(type = IdType.AUTO)
private Long id;
private String licenseNo;
private String companyName;
private String productScope;
private LocalDate issueDate;
private LocalDate expiryDate;
private String fileUrl;
private Integer status;
}
```
创建mapper/LicenseMapper.java:
```java
package com.example.license.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.license.entity.License;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface LicenseMapper extends BaseMapper
{
}
```
2. 文件上传与业务逻辑
创建service/LicenseService.java处理文件存储和业务逻辑。这里重点实现文件上传到本地磁盘并返回访问路径的功能。
```java
package com.example.license.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.license.entity.License;
import com.example.license.mapper.LicenseMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
@Service
public class LicenseService {
private final LicenseMapper licenseMapper;
@Value("${file.upload-dir}")
private String uploadDir;
public LicenseService(LicenseMapper licenseMapper) {
this.licenseMapper = licenseMapper;
}
public Page list(int current, int size, String keyword) {
Page page = new Page<>(current, size);
QueryWrapper wrapper = new QueryWrapper<>();
if (keyword != null && !keyword.isEmpty()) {
wrapper.like("company_name", keyword).or().like("license_no", keyword);
}
return licenseMapper.selectPage(page, wrapper);
}
public boolean save(License license, MultipartFile file) throws IOException {
if (file != null && !file.isEmpty()) {
String originalFilename = file.getOriginalFilename();
String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
String newFileName = UUID.randomUUID() + extension;
String dateStr = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
String relativePath = dateStr + "/" + newFileName;
File destFile = Paths.get(uploadDir, relativePath).toFile();
if (!destFile.getParentFile().exists()) {
destFile.getParentFile().mkdirs();
}
file.transferTo(destFile);
license.setFileUrl("/files/" + relativePath); // 设置访问路径
}
// 自动判断状态
if (license.getExpiryDate().isBefore(LocalDate.now())) {
license.setStatus(0);
} else {
license.setStatus(1);
}
return licenseMapper.insert(license) > 0;
}
public boolean delete(Long id) {
return licenseMapper.deleteById(id) > 0;
}
}
```

3. 控制器层
创建controller/LicenseController.java暴露REST接口:
```java
package com.example.license.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.license.entity.License;
import com.example.license.service.LicenseService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
@RestController
@RequestMapping("/api/license")
public class LicenseController {
private final LicenseService licenseService;
@Value("${file.upload-dir}")
private String uploadDir;
public LicenseController(LicenseService licenseService) {
this.licenseService = licenseService;
}
@GetMapping("/list")
public Page list(@RequestParam(defaultValue = "1") int current,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String keyword) {
return licenseService.list(current, size, keyword);
}
@PostMapping("/save")
public boolean save(@ModelAttribute License license, @RequestParam(required = false) MultipartFile file) throws IOException {
return licenseService.save(license, file);
}
@DeleteMapping("/{id}")
public boolean delete(@PathVariable Long id) {
return licenseService.delete(id);
}
// 文件访问映射
@GetMapping("/files/")
public ResponseEntity serveFile() {
// 注意:实际生产中需要更严谨的路径解析防止目录遍历攻击
return ResponseEntity.ok().build();
}
}
```
为了解决文件访问问题,添加一个配置类config/WebConfig.java来映射静态资源:
```java
package com.example.license.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Value("${file.upload-dir}")
private String uploadDir;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/files/")
.addResourceLocations("file:" + uploadDir);
}
}
```
四、前端界面与交互开发
1. 配置Axios与Element Plus
在src/main.js中全局引入组件库:
```javascript
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import axios from 'axios'
const app = createApp(App)
app.use(ElementPlus)
app.config.globalProperties.$http = axios
axios.defaults.baseURL = 'http://localhost:8080/api'
app.mount('app')
```
2. 核心管理页面组件
修改src/App.vue,编写包含表格查询、新增/编辑弹窗、文件上传的完整逻辑:
```vue
生产许可证档案管理
查询
新增档案
{{ scope.row.status === 1 ? '有效' : '过期' }}
查看
无
删除
点击上传
取消
保存
```
五、系统启动与功能验证
1. 启动后端服务
在IDEA中运行LicenseApplication.java(包含main方法的启动类),确保控制台无报错,端口8080启动成功。
2. 启动前端服务
在项目根目录下执行:
```bash
npm run dev
```
终端会输出本地访问地址,通常是 http://localhost:5173。
3. 功能验证步骤
- 录入测试:打开前端页面,点击“新增档案”,输入“测试企业01”,许可证号“LICENSE-2023001”,选择一张图片作为电子档案,点击保存。观察列表是否刷新并显示新数据。
- 状态逻辑验证:录入一条有效期日期设置为昨天的数据,保存后观察列表中该条数据的“状态”标签是否自动变为红色的“过期”。
- 文件预览:点击列表中的“查看”按钮,确认浏览器新标签页能正确打开上传的图片文件。
- 搜索功能:在搜索框输入“测试”,点击查询,确认列表仅过滤出包含该关键字的企业。
至此,一套具备基础CRUD、文件上传、状态自动判断的生产许可证档案管理系统已搭建完成。该方案代码结构清晰,无多余依赖,可直接作为企业内部管理工具的基础版本进行二次开发。