在中小企业的日常运营中,信息孤岛和低效协作是普遍痛点。我去年为一家50人规模的贸易公司实施这套系统前,他们用着3个不同的Excel表格管理员工、4个微信群同步通知、邮件往来处理文件审批——每次季度报表都要耗费财务团队整整两天时间核对数据。这正是我们开发这套轻量级管理系统的现实意义所在。
这套系统最核心的价值在于用技术手段解决三个实际问题:
在技术选型阶段,我们对比过三种主流方案:
最终选择SpringBoot+Vue.js的分离架构,主要基于以下考量:
后端选择SpringBoot的关键因素:
前端选择Vue.js的实践优势:
原始设计中的员工表存在两个潜在问题:
我们的优化方案:
sql复制ALTER TABLE employee
MODIFY COLUMN contact_phone VARCHAR(20),
ADD COLUMN position_id INT AFTER department_id;
同时新增职位维度表:
sql复制CREATE TABLE position (
position_id INT PRIMARY KEY AUTO_INCREMENT,
position_name VARCHAR(30) NOT NULL UNIQUE,
level TINYINT COMMENT '职级'
);
为适应企业组织架构调整,我们在部门表增加parent_id字段实现多级部门:
sql复制ALTER TABLE department
ADD COLUMN parent_id INT DEFAULT NULL AFTER department_id,
ADD CONSTRAINT fk_parent FOREIGN KEY (parent_id) REFERENCES department(department_id);
这种设计带来三个业务价值:
我们采用改良的RBAC(基于角色的访问控制)模型,在标准角色-权限关联基础上增加了两层控制:
权限表结构设计:
sql复制CREATE TABLE permission (
perm_id INT PRIMARY KEY,
perm_name VARCHAR(50) NOT NULL,
perm_key VARCHAR(30) NOT NULL UNIQUE,
menu_id INT NOT NULL
);
CREATE TABLE role_permission (
role_id INT NOT NULL,
perm_id INT NOT NULL,
data_scope TINYINT COMMENT '1全部 2本部门 3自定义',
PRIMARY KEY (role_id, perm_id)
);
在Vue中实现动态路由的核心代码:
javascript复制// 过滤异步路由表
function filterAsyncRoutes(routes, roles) {
return routes.filter(route => {
if (hasPermission(roles, route.meta?.roles)) {
if (route.children) {
route.children = filterAsyncRoutes(route.children, roles)
}
return true
}
return false
})
}
关键点:权限变更后需要调用
resetToken()强制刷新前端路由,这是很多初学者容易遗漏的步骤
针对中小企业特点,我们采用混合存储方案:
文件上传的断点续传实现:
java复制@PostMapping("/upload")
public ResponseEntity<String> chunkUpload(
@RequestParam MultipartFile file,
@RequestParam String chunkId,
@RequestParam int chunkIndex,
@RequestParam int totalChunks) {
// 临时存储分片
String tempDir = "/tmp/upload/" + chunkId;
FileUtils.forceMkdir(new File(tempDir));
file.transferTo(new File(tempDir + "/" + chunkIndex));
// 检查是否全部上传完成
if (FileUtils.listFiles(new File(tempDir), null, false).size() == totalChunks) {
mergeFiles(chunkId, tempDir);
}
return ResponseEntity.ok("success");
}
java复制@PreAuthorize("@filePermission.hasPermission(#fileId, 'download')")
@GetMapping("/download/{fileId}")
public void downloadFile(@PathVariable String fileId, HttpServletResponse response) {
// ...
}
java复制public boolean scanVirus(File file) throws IOException {
Process proc = Runtime.getRuntime().exec("clamscan --stdout " + file.getPath());
return proc.waitFor() == 0;
}
问题现象:用户频繁被登出,控制台出现"JWT expired"错误
排查过程:
ntpstat根本原因:前端未正确处理401响应,导致token刷新失败
解决方案:
javascript复制// 响应拦截器增加token自动刷新
service.interceptors.response.use(
response => response,
async error => {
if (error.response.status === 401) {
if (!isRefreshing) {
isRefreshing = true
try {
const { data } = await refreshToken()
setToken(data.token)
error.config.headers['Authorization'] = 'Bearer ' + data.token
return service(error.config)
} finally {
isRefreshing = false
}
}
}
return Promise.reject(error)
}
)
性能对比:
| 数据量 | 原始方案 | 优化方案 |
|---|---|---|
| 50节点 | 320ms | 45ms |
| 200节点 | 2100ms | 68ms |
优化措施:
优化后的查询SQL:
sql复制SELECT * FROM department
WHERE path LIKE CONCAT((SELECT path FROM department WHERE department_id=?), ',%')
ORDER BY path
对于20人以下团队,推荐使用Docker Compose单机部署:
yaml复制version: '3'
services:
mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
frontend:
build: ./frontend
ports:
- "80:80"
volumes:
mysql_data:
启动命令:
bash复制DB_PASSWORD=yourpassword docker-compose up -d
properties复制management.endpoints.web.exposure.include=health,info,metrics
management.metrics.export.prometheus.enabled=true
yaml复制scrape_configs:
- job_name: 'springboot'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['backend:8080']
yaml复制groups:
- name: spring.rules
rules:
- alert: HighErrorRate
expr: rate(http_server_requests_errors_total[1m]) > 0.1
for: 5m
对于需要扩展系统的开发者,推荐从三个方向入手:
xml复制<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.1.0.M6</version>
</dependency>
javascript复制// main.js
import Vant from 'vant';
import 'vant/lib/index.css';
createApp(App)
.use(Vant)
.mount('#app')
java复制@ExcelIgnoreUnannotated
public class EmployeeExportVO {
@ExcelProperty("员工姓名")
private String name;
@ExcelProperty(value = "入职日期", format = "yyyy-MM-dd")
private Date hireDate;
}
这套系统在实际交付中经历过三次重大架构调整,最深刻的教训是:在中小型系统中,过度设计带来的复杂度往往比功能不足危害更大。建议开发者根据企业实际人员规模和技术能力,合理裁剪功能模块。比如对50人以下企业,可以暂时去掉消息队列等中间件,采用更直接的同步处理方式。