
✨道路是曲折的,前途是光明的!
📝 专注C/C++、Linux编程与人工智能领域,分享学习笔记!
🌟 感谢各位小伙伴的长期陪伴与支持,欢迎文末添加好友一起交流!

📚 目录
前言
前后端分离架构已成为企业级应用开发的主流选择。本文将通过一个完整的企业管理系统实战项目,详细介绍如何使用 SpringBoot + Vue 技术栈,实现权限管理、工作流引擎和报表系统三大核心功能。

项目特色
- 前后端分离:RESTful API 设计,便于扩展和维护
- RBAC权限模型:细粒度的权限控制体系
- Flowable工作流:可视化流程设计与执行
- 动态报表:灵活配置的数据可视化方案
一、项目背景与技术选型
1.1 技术栈总览
| 层次 | 技术选型 | 说明 |
|---|---|---|
| 前端框架 | Vue 3 + TypeScript | 渐进式框架 |
| UI组件库 | Element Plus | 企业级组件库 |
| 状态管理 | Pinia | Vue 3 官方推荐 |
| 后端框架 | Spring Boot 2.7 | Java 微服务框架 |
| ORM框架 | MyBatis-Plus | 持久层增强 |
| 数据库 | MySQL 8.0 | 关系型数据库 |
| 缓存 | Redis 6.0 | 高性能缓存 |
| 工作流引擎 | Flowable 7.0 | 开源BPMN平台 |
| 报表工具 | ECharts + UReport2 | 数据可视化 |
1.2 项目结构
enterprise-system/
├── enterprise-admin/ # 前端项目
│ ├── src/
│ │ ├── api/ # API接口
│ │ ├── assets/ # 静态资源
│ │ ├── components/ # 公共组件
│ │ ├── views/ # 页面视图
│ │ ├── router/ # 路由配置
│ │ ├── store/ # 状态管理
│ │ └── utils/ # 工具函数
│ └── package.json
├── enterprise-server/ # 后端项目
│ ├── src/main/java/com/enterprise/
│ │ ├── controller/ # 控制层
│ │ ├── service/ # 业务层
│ │ ├── mapper/ # 数据访问层
│ │ ├── entity/ # 实体类
│ │ ├── dto/ # 数据传输对象
│ │ ├── config/ # 配置类
│ │ └── security/ # 安全相关
│ └── pom.xml
└── docs/ # 项目文档
二、系统架构设计
2.1 整体架构图
数据层
应用层
网关层
前端层
Vue 3 应用
Element Plus UI
Vue Router
Pinia Store
Nginx 反向代理
API Gateway
认证服务
用户服务
权限服务
工作流服务
报表服务
MySQL 数据库
Redis 缓存
Flowable DB
2.2 数据库设计
ER图
has
belongs
has
belongs
creates
defines
USER
bigint
id
PK
string
username
string
password
string
email
string
phone
datetime
create_time
USER_ROLE
ROLE
bigint
id
PK
string
role_name
string
role_code
string
description
ROLE_PERMISSION
PERMISSION
bigint
id
PK
string
permission_name
string
resource_type
string
resource_url
string
permission_code
WORKFLOW_INSTANCE
bigint
id
PK
string
instance_id
bigint
definition_id
bigint
starter_id
string
status
WORKFLOW_DEFINITION
bigint
id
PK
string
process_key
string
process_name
text
bpmn_xml
int
version
三、权限管理模块
3.1 RBAC权限模型
权限模型架构图
分配
拥有
关联
用户
角色
权限
资源
菜单
按钮
接口
数据
3.2 Spring Security + JWT 实现
安全配置类
java
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Autowired
private UserDetailsService jwtUserDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// 禁用CSRF
.csrf().disable()
// 异常处理
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
// 会话管理
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// 授权配置
.authorizeRequests()
// 公开接口
.antMatchers(
"/api/auth/**",
"/api/public/**",
"/swagger-ui/**",
"/v3/api-docs/**"
).permitAll()
// 管理员接口
.antMatchers("/api/admin/**").hasRole("ADMIN")
// 其他接口需要认证
.anyRequest().authenticated();
// 添加JWT过滤器
httpSecurity.addFilterBefore(jwtRequestFilter,
UsernamePasswordAuthenticationFilter.class);
}
}
JWT工具类
java
@Component
public class JwtTokenUtil {
private static final String SECRET = "enterprise-secret-key-2024";
private static final long EXPIRATION = 86400000; // 24小时
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put("username", userDetails.getUsername());
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername())
&& !isTokenExpired(token));
}
public String extractUsername(String token) {
return extractClaims(token).getSubject();
}
private Claims extractClaims(String token) {
return Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
}
private Boolean isTokenExpired(String token) {
return extractClaims(token)
.getExpiration()
.before(new Date());
}
}
权限注解使用
java
@RestController
@RequestMapping("/api/user")
public class UserController {
// 需要USER角色
@PreAuthorize("hasRole('USER')")
@GetMapping("/profile")
public Result<UserProfile> getProfile() {
return Result.success(userService.getProfile());
}
// 需要USER_READ权限
@PreAuthorize("hasAuthority('USER_READ')")
@GetMapping("/{id}")
public Result<User> getUserById(@PathVariable Long id) {
return Result.success(userService.getById(id));
}
// 需要ADMIN角色或当前用户ID匹配
@PreAuthorize("hasRole('ADMIN') or #id == authentication.principal.id")
@PutMapping("/{id}")
public Result<Void> updateUser(@PathVariable Long id, @RequestBody UserDTO userDTO) {
userService.updateUser(id, userDTO);
return Result.success();
}
}
3.3 前端权限控制
路由守卫
typescript
// router/guard.ts
import router from './index'
import { useUserStore } from '@/store/user'
import { getToken } from '@/utils/auth'
import { ElMessage } from 'element-plus'
const whiteList = ['/login', '/404', '/403']
router.beforeEach(async (to, from, next) => {
const userStore = useUserStore()
const hasToken = getToken()
if (hasToken) {
if (to.path === '/login') {
next({ path: '/' })
} else {
// 检查是否已获取用户信息
if (userStore.userId) {
next()
} else {
try {
// 获取用户信息和权限
await userStore.getUserInfo()
// 动态添加路由
await userStore.generateRoutes()
next({ ...to, replace: true })
} catch (error) {
// Token失效,重新登录
await userStore.logout()
ElMessage.error('登录状态已过期,请重新登录')
next(`/login?redirect=${to.path}`)
}
}
}
} else {
// 未登录
if (whiteList.includes(to.path)) {
next()
} else {
next(`/login?redirect=${to.path}`)
}
}
})
自定义权限指令
typescript
// directives/permission.ts
import type { Directive, DirectiveBinding } from 'vue'
import { useUserStore } from '@/store/user'
const permission: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
const { value } = binding
const userStore = useUserStore()
const permissions = userStore.permissions
if (value && value instanceof Array && value.length > 0) {
const hasPermission = permissions.some((permission: string) => {
return value.includes(permission)
})
if (!hasPermission) {
el.parentNode?.removeChild(el)
}
} else {
throw new Error('需要权限!如 v-permission="[\'user:add\']"')
}
}
}
export default permission
// main.ts 注册
import permission from './directives/permission'
app.directive('permission', permission)
组件中使用
vue
<template>
<div>
<!-- 有删除权限时显示删除按钮 -->
<el-button
v-permission="['user:delete']"
type="danger"
@click="handleDelete"
>
删除
</el-button>
<!-- 多个权限满足其一即可 -->
<el-button
v-permission="['user:edit', 'user:audit']"
type="primary"
@click="handleEdit"
>
编辑
</el-button>
</div>
</template>
四、工作流引擎集成
4.1 Flowable 集成配置
Maven依赖
xml
<dependencies>
<!-- Flowable 核心依赖 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>7.0.0</version>
</dependency>
<!-- Flowable REST API -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-rest</artifactId>
<version>7.0.0</version>
</dependency>
</dependencies>
配置文件
yaml
flowable:
# 数据库配置
database-schema-update: true
database-type: mysql
# 异步执行器
async-executor-activate: true
async-history-enabled: true
# 流程定义存储
process:
definition-cache-limit: 100
# 邮件服务器配置
mail:
server:
host: smtp.example.com
port: 587
username: workflow@example.com
password: password
# REST API配置
rest:
app:
enable: true
4.2 流程定义服务
java
@Service
public class WorkflowService {
@Autowired
private ProcessEngine processEngine;
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Autowired
private HistoryService historyService;
/**
* 部署流程定义
*/
public String deployProcess(String processName, MultipartFile bpmnFile) {
Deployment deployment = processEngine.getRepositoryService()
.createDeployment()
.name(processName)
.addInputStream(
bpmnFile.getOriginalFilename(),
bpmnFile.getInputStream()
)
.deploy();
return deployment.getId();
}
/**
* 启动流程实例
*/
public String startProcess(String processKey, Map<String, Object> variables) {
ProcessInstance instance = runtimeService.startProcessInstanceByKey(
processKey,
variables
);
return instance.getId();
}
/**
* 完成任务
*/
public void completeTask(String taskId, Map<String, Object> variables) {
taskService.complete(taskId, variables);
}
/**
* 获取用户待办任务
*/
public List<TaskDTO> getUserTasks(String userId) {
List<Task> tasks = taskService.createTaskQuery()
.taskAssignee(userId)
.orderByTaskCreateTime()
.desc()
.list();
return tasks.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
/**
* 获取流程历史
*/
public List<HistoricActivityDTO> getProcessHistory(String processInstanceId) {
List<HistoricActivityInstance> activities = historyService
.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId)
.orderByHistoricActivityInstanceStartTime()
.asc()
.list();
return activities.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
/**
* 获取流程图进度
*/
public List<String> getActiveActivityIds(String processInstanceId) {
return runtimeService.getActiveActivityIds(processInstanceId);
}
}
4.3 请假审批流程示例
BPMN流程定义 (leave-request.bpmn20.xml)
xml
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:flowable="http://flowable.org/bpmn"
targetNamespace="Examples">
<process id="leaveRequest" name="请假审批流程" isExecutable="true">
<!-- 开始节点 -->
<startEvent id="startEvent" name="提交申请"/>
<!-- 员工填写请假单 -->
<userTask id="fillLeaveForm" name="填写请假单"
flowable:assignee="${applicant}">
<extensionElements>
<flowable:taskListener event="create"
class="com.enterprise.workflow.listener.LeaveTaskListener"/>
</extensionElements>
</userTask>
<!-- 排他网关:判断请假天数 -->
<exclusiveGateway id="checkDaysGateway" name="判断天数"/>
<!-- 部门主管审批(3天以内) -->
<userTask id="managerApproval" name="部门主管审批"
flowable:candidateGroups="MANAGER"/>
<!-- 总监审批(3-7天) -->
<userTask id="directorApproval" name="总监审批"
flowable:candidateGroups="DIRECTOR"/>
<!-- 总经理审批(7天以上) -->
<userTask id="ceoApproval" name="总经理审批"
flowable:candidateGroups="CEO"/>
<!-- 审批结果网关 -->
<exclusiveGateway id="approvalResultGateway" name="审批结果"/>
<!-- 审批通过 -->
<serviceTask id="notifyApproved" name="发送通过通知"
flowable:class="com.enterprise.workflow.delegate.ApprovedNotifyDelegate"/>
<!-- 审批拒绝 -->
<serviceTask id="notifyRejected" name="发送拒绝通知"
flowable:class="com.enterprise.workflow.delegate.RejectedNotifyDelegate"/>
<!-- 结束节点 -->
<endEvent id="endEvent" name="流程结束"/>
<!-- 连接线 -->
<sequenceFlow sourceRef="startEvent" targetRef="fillLeaveForm"/>
<sequenceFlow sourceRef="fillLeaveForm" targetRef="checkDaysGateway"/>
<!-- 3天以内 -->
<sequenceFlow sourceRef="checkDaysGateway" targetRef="managerApproval">
<conditionExpression xsi:type="tFormalExpression">
${leaveDays <= 3}
</conditionExpression>
</sequenceFlow>
<!-- 3-7天 -->
<sequenceFlow sourceRef="checkDaysGateway" targetRef="directorApproval">
<conditionExpression xsi:type="tFormalExpression">
${leaveDays > 3 && leaveDays <= 7}
</conditionExpression>
</sequenceFlow>
<!-- 7天以上 -->
<sequenceFlow sourceRef="checkDaysGateway" targetRef="ceoApproval">
<conditionExpression xsi:type="tFormalExpression">
${leaveDays > 7}
</conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="managerApproval" targetRef="approvalResultGateway"/>
<sequenceFlow sourceRef="directorApproval" targetRef="approvalResultGateway"/>
<sequenceFlow sourceRef="ceoApproval" targetRef="approvalResultGateway"/>
<!-- 通过 -->
<sequenceFlow sourceRef="approvalResultGateway" targetRef="notifyApproved">
<conditionExpression xsi:type="tFormalExpression">
${approved == true}
</conditionExpression>
</sequenceFlow>
<!-- 拒绝 -->
<sequenceFlow sourceRef="approvalResultGateway" targetRef="notifyRejected">
<conditionExpression xsi:type="tFormalExpression">
${approved == false}
</conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="notifyApproved" targetRef="endEvent"/>
<sequenceFlow sourceRef="notifyRejected" targetRef="endEvent"/>
</process>
</definitions>
4.4 前端流程图组件
vue
<template>
<div class="workflow-viewer">
<div ref="bpmnCanvas" class="bpmn-canvas"></div>
<!-- 当前任务高亮 -->
<div class="task-info">
<el-tag>当前环节: {{ currentTaskName }}</el-tag>
<el-tag type="success">处理人: {{ currentAssignee }}</el-tag>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import BpmnJS from 'bpmn-js/lib/NavigatedViewer'
import 'bpmn-js/dist/assets/diagram-js.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css'
interface Props {
processInstanceId: string
xml: string
activeActivityIds: string[]
}
const props = defineProps<Props>()
const bpmnCanvas = ref<HTMLElement>()
const currentTaskName = ref('')
const currentAssignee = ref('')
let bpmnViewer: BpmnJS | null = null
onMounted(async () => {
if (bpmnCanvas.value) {
bpmnViewer = new BpmnJS({
container: bpmnCanvas.value
})
try {
await bpmnViewer.importXML(props.xml)
// 高亮当前活动节点
const canvas = bpmnViewer.get('canvas')
const elementRegistry = bpmnViewer.get('elementRegistry')
props.activeActivityIds.forEach(id => {
const element = elementRegistry.get(id)
if (element) {
canvas.addMarker(id, 'highlight')
}
})
} catch (error) {
console.error('流程图渲染失败:', error)
}
}
})
</script>
<style>
.bpmn-canvas {
height: 600px;
border: 1px solid #ddd;
}
.highlight {
fill: #67C23A !important;
stroke: #67C23A !important;
}
</style>
五、报表系统实现
5.1 报表系统架构
报表配置
数据源配置
报表设计器
MySQL
API接口
Excel导入
拖拽设计
SQL编辑器
模板选择
报表引擎
数据查询
数据处理
格式转换
前端展示
ECharts图表
数据表格
导出功能
5.2 动态报表服务
java
@Service
public class ReportService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private ReportConfigMapper reportConfigMapper;
/**
* 执行动态SQL查询
*/
public List<Map<String, Object>> executeQuery(Long reportId,
Map<String, Object> params) {
ReportConfig config = reportConfigMapper.selectById(reportId);
// 参数替换
String sql = replaceParams(config.getSqlTemplate(), params);
// 执行查询
return jdbcTemplate.queryForList(sql);
}
/**
* 生成图表数据
*/
public ChartDataVO generateChartData(Long reportId,
Map<String, Object> params) {
ReportConfig config = reportConfigMapper.selectById(reportId);
// 查询数据
List<Map<String, Object>> data = executeQuery(reportId, params);
// 构建图表数据
ChartDataVO chartData = new ChartDataVO();
chartData.setType(config.getChartType());
// 根据配置构建X轴和Y轴数据
if ("bar".equals(config.getChartType())
|| "line".equals(config.getChartType())) {
List<String> xAxis = new ArrayList<>();
List<BigDecimal> yAxis = new ArrayList<>();
for (Map<String, Object> row : data) {
xAxis.add(row.get(config.getXAxisField()).toString());
yAxis.add((BigDecimal) row.get(config.getYAxisField()));
}
chartData.setXAxis(xAxis);
chartData.setSeries(Collections.singletonList(
new SeriesVO("数值", yAxis)
));
} else if ("pie".equals(config.getChartType())) {
List<PieDataVO> pieData = new ArrayList<>();
for (Map<String, Object> row : data) {
pieData.add(new PieDataVO(
row.get(config.getNameField()).toString(),
((BigDecimal) row.get(config.getValueField())).doubleValue()
));
}
chartData.setData(pieData);
}
return chartData;
}
/**
* 导出报表
*/
public void exportReport(Long reportId,
Map<String, Object> params,
HttpServletResponse response) throws IOException {
List<Map<String, Object>> data = executeQuery(reportId, params);
// 使用EasyExcel导出
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-disposition",
"attachment;filename=report.xlsx");
EasyExcel.write(response.getOutputStream())
.sheet("报表数据")
.doWrite(data);
}
private String replaceParams(String template, Map<String, Object> params) {
String result = template;
for (Map.Entry<String, Object> entry : params.entrySet()) {
result = result.replace("${" + entry.getKey() + "}",
String.valueOf(entry.getValue()));
}
return result;
}
}
5.3 报表配置实体
java
@Data
@TableName("sys_report_config")
public class ReportConfig {
@TableId(type = IdType.AUTO)
private Long id;
/**
* 报表名称
*/
private String reportName;
/**
* 报表类型: table-表格, bar-柱状图, line-折线图, pie-饼图
*/
private String reportType;
/**
* 数据源类型: database, api
*/
private String dataSourceType;
/**
* SQL模板
*/
private String sqlTemplate;
/**
* 图表类型
*/
private String chartType;
/**
* X轴字段
*/
private String xAxisField;
/**
* Y轴字段
*/
private String yAxisField;
/**
* 名称字段(饼图用)
*/
private String nameField;
/**
* 数值字段(饼图用)
*/
private String valueField;
/**
* 参数配置(JSON格式)
*/
private String paramConfig;
/**
* 创建时间
*/
private LocalDateTime createTime;
}
5.4 前端报表组件
vue
<template>
<div class="dynamic-report">
<!-- 查询条件 -->
<el-form :model="queryParams" inline>
<el-form-item
v-for="param in paramList"
:key="param.name"
:label="param.label"
>
<el-input
v-if="param.type === 'input'"
v-model="queryParams[param.name]"
/>
<el-date-picker
v-else-if="param.type === 'date'"
v-model="queryParams[param.name]"
type="daterange"
/>
<el-select
v-else-if="param.type === 'select'"
v-model="queryParams[param.name]"
>
<el-option
v-for="opt in param.options"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadReport">查询</el-button>
<el-button @click="exportReport">导出</el-button>
</el-form-item>
</el-form>
<!-- 图表展示 -->
<div v-if="reportConfig.reportType === 'chart'" ref="chartRef" class="chart"></div>
<!-- 表格展示 -->
<el-table v-else :data="tableData" border>
<el-table-column
v-for="col in columns"
:key="col.prop"
:prop="col.prop"
:label="col.label"
/>
</el-table>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import * as echarts from 'echarts'
import { getReportData, exportReportData } from '@/api/report'
interface Props {
reportId: number
}
const props = defineProps<Props>()
const chartRef = ref<HTMLElement>()
const chartInstance = ref<echarts.ECharts>()
const queryParams = ref<Record<string, any>>({})
const paramList = ref<any[]>([])
const reportConfig = ref<any>({})
const tableData = ref<any[]>([])
const columns = ref<any[]>([])
onMounted(async () => {
// 加载报表配置
const config = await getReportConfig(props.reportId)
reportConfig.value = config
paramList.value = JSON.parse(config.paramConfig || '[]')
// 初始化图表
if (config.reportType === 'chart' && chartRef.value) {
chartInstance.value = echarts.init(chartRef.value)
}
// 加载初始数据
await loadReport()
})
onUnmounted(() => {
chartInstance.value?.dispose()
})
async function loadReport() {
const data = await getReportData(props.reportId, queryParams.value)
if (reportConfig.value.reportType === 'chart') {
renderChart(data)
} else {
tableData.value = data.list
columns.value = data.columns
}
}
function renderChart(data: any) {
const option = generateChartOption(reportConfig.value.chartType, data)
chartInstance.value?.setOption(option)
}
function generateChartOption(type: string, data: any) {
const baseOption = {
tooltip: { trigger: 'axis' },
legend: { data: data.series?.map((s: any) => s.name) },
}
if (type === 'bar' || type === 'line') {
return {
...baseOption,
xAxis: { type: 'category', data: data.xAxis },
yAxis: { type: 'value' },
series: data.series?.map((s: any) => ({
name: s.name,
type: type,
data: s.data
}))
}
} else if (type === 'pie') {
return {
tooltip: { trigger: 'item' },
series: [{
type: 'pie',
data: data.data
}]
}
}
return baseOption
}
async function exportReport() {
await exportReportData(props.reportId, queryParams.value)
}
</script>
<style scoped>
.chart {
height: 500px;
margin-top: 20px;
}
</style>
5.5 销售统计报表示例
30% 25% 25% 20% 2025年季度销售占比 第一季度 第二季度 第三季度 第四季度
六、核心代码实现
6.1 统一响应格式
java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
private Integer code;
private String message;
private T data;
private Long timestamp;
public static <T> Result<T> success(T data) {
return new Result<>(200, "操作成功", data, System.currentTimeMillis());
}
public static <T> Result<T> success() {
return success(null);
}
public static <T> Result<T> error(String message) {
return new Result<>(500, message, null, System.currentTimeMillis());
}
public static <T> Result<T> error(Integer code, String message) {
return new Result<>(code, message, null, System.currentTimeMillis());
}
}
6.2 全局异常处理
java
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 业务异常
*/
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusinessException(BusinessException e) {
log.error("业务异常: {}", e.getMessage());
return Result.error(e.getCode(), e.getMessage());
}
/**
* 参数校验异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<Void> handleValidationException(MethodArgumentNotValidException e) {
String message = e.getBindingResult()
.getFieldErrors()
.stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
log.error("参数校验失败: {}", message);
return Result.error(400, message);
}
/**
* 权限异常
*/
@ExceptionHandler(AccessDeniedException.class)
public Result<Void> handleAccessDeniedException(AccessDeniedException e) {
log.error("权限不足: {}", e.getMessage());
return Result.error(403, "权限不足");
}
/**
* 系统异常
*/
@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e) {
log.error("系统异常", e);
return Result.error("系统错误,请联系管理员");
}
}
6.3 MyBatis-Plus 配置
java
@Configuration
@MapperScan("com.enterprise.mapper")
public class MyBatisPlusConfig {
/**
* 分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(
new PaginationInnerInterceptor(DbType.MYSQL)
);
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
/**
* 数据填充处理器
*/
@Bean
public MetaObjectHandler metaObjectHandler() {
return new MetaObjectHandler() {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime",
LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime",
LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime",
LocalDateTime.class, LocalDateTime.now());
}
};
}
}
七、部署与运维
7.1 Docker 容器化部署
后端 Dockerfile
dockerfile
FROM openjdk:11-jre-slim
LABEL maintainer="enterprise-system"
WORKDIR /app
# 添加JAR文件
COPY target/enterprise-server.jar app.jar
# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 暴露端口
EXPOSE 8080
# 启动应用
ENTRYPOINT ["java", "-jar", "-Xms512m", "-Xmx1024m", "app.jar"]
Docker Compose 编排
yaml
version: '3.8'
services:
# MySQL数据库
mysql:
image: mysql:8.0
container_name: enterprise-mysql
environment:
MYSQL_ROOT_PASSWORD: root123456
MYSQL_DATABASE: enterprise_db
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
- ./sql:/docker-entrypoint-initdb.d
networks:
- enterprise-network
# Redis缓存
redis:
image: redis:6.2-alpine
container_name: enterprise-redis
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- enterprise-network
# 后端应用
backend:
build: ./enterprise-server
container_name: enterprise-backend
ports:
- "8080:8080"
environment:
SPRING_PROFILES_ACTIVE: prod
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/enterprise_db?useSSL=false
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: root123456
SPRING_REDIS_HOST: redis
SPRING_REDIS_PORT: 6379
depends_on:
- mysql
- redis
networks:
- enterprise-network
# 前端应用
frontend:
image: nginx:alpine
container_name: enterprise-frontend
ports:
- "80:80"
volumes:
- ./enterprise-admin/dist:/usr/share/nginx/html
- ./nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- backend
networks:
- enterprise-network
volumes:
mysql-data:
redis-data:
networks:
enterprise-network:
driver: bridge
7.2 CI/CD 流程
否
是
否
是
开发提交代码
Gitlab触发
Maven构建
单元测试
测试通过?
通知修复
Docker构建
推送镜像
部署到测试环境
集成测试
测试通过?
部署到生产环境
健康检查
7.3 Nginx 配置
nginx
upstream backend {
server backend:8080;
}
server {
listen 80;
server_name your-domain.com;
# 前端静态资源
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
# API代理
location /api/ {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 文件上传大小限制
client_max_body_size 100M;
# Gzip压缩
gzip on;
gzip_types text/plain text/css application/json application/javascript;
}
八、总结
项目技术要点
| 模块 | 核心技术 | 难点 |
|---|---|---|
| 权限管理 | Spring Security + JWT | 细粒度权限控制、动态路由 |
| 工作流 | Flowable BPMN | 流程设计、复杂网关、任务监听 |
| 报表系统 | ECharts + 动态SQL | 灵活配置、大数据量性能 |
学习路径
Java基础
Spring Boot
MyBatis-Plus
Spring Security
前后端分离
工作流引擎
报表系统