SpringBoot + Vue 前后端分离项目实战:权限 + 工作流 + 报表

SpringBoot + Vue 前后端分离项目实战:权限 + 工作流 + 报表
在这里插入图片描述

✨道路是曲折的,前途是光明的!

📝 专注C/C++、Linux编程与人工智能领域,分享学习笔记!

🌟 感谢各位小伙伴的长期陪伴与支持,欢迎文末添加好友一起交流!

在这里插入图片描述

📚 目录


前言

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

在这里插入图片描述

项目特色

  • 前后端分离:RESTful API 设计,便于扩展和维护
  • RBAC权限模型:细粒度的权限控制体系
  • Flowable工作流:可视化流程设计与执行
  • 动态报表:灵活配置的数据可视化方案

一、项目背景与技术选型

1.1 技术栈总览

层次技术选型说明
前端框架Vue 3 + TypeScript渐进式框架
UI组件库Element Plus企业级组件库
状态管理PiniaVue 3 官方推荐
后端框架Spring Boot 2.7Java 微服务框架
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 实现

安全配置类
@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled =true)publicclassSecurityConfigextendsWebSecurityConfigurerAdapter{@AutowiredprivateJwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;@AutowiredprivateJwtRequestFilter jwtRequestFilter;@AutowiredprivateUserDetailsService jwtUserDetailsService;@BeanpublicPasswordEncoderpasswordEncoder(){returnnewBCryptPasswordEncoder();}@Bean@OverridepublicAuthenticationManagerauthenticationManagerBean()throwsException{returnsuper.authenticationManagerBean();}@Overrideprotectedvoidconfigure(HttpSecurity httpSecurity)throwsException{ 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工具类
@ComponentpublicclassJwtTokenUtil{privatestaticfinalString SECRET ="enterprise-secret-key-2024";privatestaticfinallong EXPIRATION =86400000;// 24小时publicStringgenerateToken(UserDetails userDetails){Map<String,Object> claims =newHashMap<>(); claims.put("username", userDetails.getUsername());returncreateToken(claims, userDetails.getUsername());}privateStringcreateToken(Map<String,Object> claims,String subject){returnJwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(newDate()).setExpiration(newDate(System.currentTimeMillis()+ EXPIRATION)).signWith(SignatureAlgorithm.HS512, SECRET).compact();}publicBooleanvalidateToken(String token,UserDetails userDetails){finalString username =extractUsername(token);return(username.equals(userDetails.getUsername())&&!isTokenExpired(token));}publicStringextractUsername(String token){returnextractClaims(token).getSubject();}privateClaimsextractClaims(String token){returnJwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();}privateBooleanisTokenExpired(String token){returnextractClaims(token).getExpiration().before(newDate());}}
权限注解使用
@RestController@RequestMapping("/api/user")publicclassUserController{// 需要USER角色@PreAuthorize("hasRole('USER')")@GetMapping("/profile")publicResult<UserProfile>getProfile(){returnResult.success(userService.getProfile());}// 需要USER_READ权限@PreAuthorize("hasAuthority('USER_READ')")@GetMapping("/{id}")publicResult<User>getUserById(@PathVariableLong id){returnResult.success(userService.getById(id));}// 需要ADMIN角色或当前用户ID匹配@PreAuthorize("hasRole('ADMIN') or #id == authentication.principal.id")@PutMapping("/{id}")publicResult<Void>updateUser(@PathVariableLong id,@RequestBodyUserDTO userDTO){ userService.updateUser(id, userDTO);returnResult.success();}}

3.3 前端权限控制

路由守卫
// router/guard.tsimport 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}`)}}})
自定义权限指令
// directives/permission.tsimporttype{ 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 instanceofArray&& value.length >0){const hasPermission = permissions.some((permission:string)=>{return value.includes(permission)})if(!hasPermission){ el.parentNode?.removeChild(el)}}else{thrownewError('需要权限!如 v-permission="[\'user:add\']"')}}}exportdefault permission // main.ts 注册import permission from'./directives/permission' app.directive('permission', permission)
组件中使用
<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依赖
<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>
配置文件
flowable:# 数据库配置database-schema-update:truedatabase-type: mysql # 异步执行器async-executor-activate:trueasync-history-enabled:true# 流程定义存储process:definition-cache-limit:100# 邮件服务器配置mail:server:host: smtp.example.com port:587username: [email protected] password: password # REST API配置rest:app:enable:true

4.2 流程定义服务

@ServicepublicclassWorkflowService{@AutowiredprivateProcessEngine processEngine;@AutowiredprivateRuntimeService runtimeService;@AutowiredprivateTaskService taskService;@AutowiredprivateHistoryService historyService;/** * 部署流程定义 */publicStringdeployProcess(String processName,MultipartFile bpmnFile){Deployment deployment = processEngine.getRepositoryService().createDeployment().name(processName).addInputStream( bpmnFile.getOriginalFilename(), bpmnFile.getInputStream()).deploy();return deployment.getId();}/** * 启动流程实例 */publicStringstartProcess(String processKey,Map<String,Object> variables){ProcessInstance instance = runtimeService.startProcessInstanceByKey( processKey, variables );return instance.getId();}/** * 完成任务 */publicvoidcompleteTask(String taskId,Map<String,Object> variables){ taskService.complete(taskId, variables);}/** * 获取用户待办任务 */publicList<TaskDTO>getUserTasks(String userId){List<Task> tasks = taskService.createTaskQuery().taskAssignee(userId).orderByTaskCreateTime().desc().list();return tasks.stream().map(this::convertToDTO).collect(Collectors.toList());}/** * 获取流程历史 */publicList<HistoricActivityDTO>getProcessHistory(String processInstanceId){List<HistoricActivityInstance> activities = historyService .createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list();return activities.stream().map(this::convertToDTO).collect(Collectors.toList());}/** * 获取流程图进度 */publicList<String>getActiveActivityIds(String processInstanceId){return runtimeService.getActiveActivityIds(processInstanceId);}}

4.3 请假审批流程示例

BPMN流程定义 (leave-request.bpmn20.xml)
<?xml version="1.0" encoding="UTF-8"?><definitionsxmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"xmlns:flowable="http://flowable.org/bpmn"targetNamespace="Examples"><processid="leaveRequest"name="请假审批流程"isExecutable="true"><!-- 开始节点 --><startEventid="startEvent"name="提交申请"/><!-- 员工填写请假单 --><userTaskid="fillLeaveForm"name="填写请假单"flowable:assignee="${applicant}"><extensionElements><flowable:taskListenerevent="create"class="com.enterprise.workflow.listener.LeaveTaskListener"/></extensionElements></userTask><!-- 排他网关:判断请假天数 --><exclusiveGatewayid="checkDaysGateway"name="判断天数"/><!-- 部门主管审批(3天以内) --><userTaskid="managerApproval"name="部门主管审批"flowable:candidateGroups="MANAGER"/><!-- 总监审批(3-7天) --><userTaskid="directorApproval"name="总监审批"flowable:candidateGroups="DIRECTOR"/><!-- 总经理审批(7天以上) --><userTaskid="ceoApproval"name="总经理审批"flowable:candidateGroups="CEO"/><!-- 审批结果网关 --><exclusiveGatewayid="approvalResultGateway"name="审批结果"/><!-- 审批通过 --><serviceTaskid="notifyApproved"name="发送通过通知"flowable:class="com.enterprise.workflow.delegate.ApprovedNotifyDelegate"/><!-- 审批拒绝 --><serviceTaskid="notifyRejected"name="发送拒绝通知"flowable:class="com.enterprise.workflow.delegate.RejectedNotifyDelegate"/><!-- 结束节点 --><endEventid="endEvent"name="流程结束"/><!-- 连接线 --><sequenceFlowsourceRef="startEvent"targetRef="fillLeaveForm"/><sequenceFlowsourceRef="fillLeaveForm"targetRef="checkDaysGateway"/><!-- 3天以内 --><sequenceFlowsourceRef="checkDaysGateway"targetRef="managerApproval"><conditionExpressionxsi:type="tFormalExpression"> ${leaveDays <= 3} </conditionExpression></sequenceFlow><!-- 3-7天 --><sequenceFlowsourceRef="checkDaysGateway"targetRef="directorApproval"><conditionExpressionxsi:type="tFormalExpression"> ${leaveDays > 3 && leaveDays <= 7} </conditionExpression></sequenceFlow><!-- 7天以上 --><sequenceFlowsourceRef="checkDaysGateway"targetRef="ceoApproval"><conditionExpressionxsi:type="tFormalExpression"> ${leaveDays > 7} </conditionExpression></sequenceFlow><sequenceFlowsourceRef="managerApproval"targetRef="approvalResultGateway"/><sequenceFlowsourceRef="directorApproval"targetRef="approvalResultGateway"/><sequenceFlowsourceRef="ceoApproval"targetRef="approvalResultGateway"/><!-- 通过 --><sequenceFlowsourceRef="approvalResultGateway"targetRef="notifyApproved"><conditionExpressionxsi:type="tFormalExpression"> ${approved == true} </conditionExpression></sequenceFlow><!-- 拒绝 --><sequenceFlowsourceRef="approvalResultGateway"targetRef="notifyRejected"><conditionExpressionxsi:type="tFormalExpression"> ${approved == false} </conditionExpression></sequenceFlow><sequenceFlowsourceRef="notifyApproved"targetRef="endEvent"/><sequenceFlowsourceRef="notifyRejected"targetRef="endEvent"/></process></definitions>

4.4 前端流程图组件

<template> <div> <div ref="bpmnCanvas"></div> <!-- 当前任务高亮 --> <div> <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 动态报表服务

@ServicepublicclassReportService{@AutowiredprivateJdbcTemplate jdbcTemplate;@AutowiredprivateReportConfigMapper reportConfigMapper;/** * 执行动态SQL查询 */publicList<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);}/** * 生成图表数据 */publicChartDataVOgenerateChartData(Long reportId,Map<String,Object> params){ReportConfig config = reportConfigMapper.selectById(reportId);// 查询数据List<Map<String,Object>> data =executeQuery(reportId, params);// 构建图表数据ChartDataVO chartData =newChartDataVO(); chartData.setType(config.getChartType());// 根据配置构建X轴和Y轴数据if("bar".equals(config.getChartType())||"line".equals(config.getChartType())){List<String> xAxis =newArrayList<>();List<BigDecimal> yAxis =newArrayList<>();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(newSeriesVO("数值", yAxis)));}elseif("pie".equals(config.getChartType())){List<PieDataVO> pieData =newArrayList<>();for(Map<String,Object> row : data){ pieData.add(newPieDataVO( row.get(config.getNameField()).toString(),((BigDecimal) row.get(config.getValueField())).doubleValue()));} chartData.setData(pieData);}return chartData;}/** * 导出报表 */publicvoidexportReport(Long reportId,Map<String,Object> params,HttpServletResponse response)throwsIOException{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);}privateStringreplaceParams(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 报表配置实体

@Data@TableName("sys_report_config")publicclassReportConfig{@TableId(type =IdType.AUTO)privateLong id;/** * 报表名称 */privateString reportName;/** * 报表类型: table-表格, bar-柱状图, line-折线图, pie-饼图 */privateString reportType;/** * 数据源类型: database, api */privateString dataSourceType;/** * SQL模板 */privateString sqlTemplate;/** * 图表类型 */privateString chartType;/** * X轴字段 */privateString xAxisField;/** * Y轴字段 */privateString yAxisField;/** * 名称字段(饼图用) */privateString nameField;/** * 数值字段(饼图用) */privateString valueField;/** * 参数配置(JSON格式) */privateString paramConfig;/** * 创建时间 */privateLocalDateTime createTime;}

5.4 前端报表组件

<template> <div> <!-- 查询条件 --> <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"></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 统一响应格式

@Data@NoArgsConstructor@AllArgsConstructorpublicclassResult<T>{privateInteger code;privateString message;privateT data;privateLong timestamp;publicstatic<T>Result<T>success(T data){returnnewResult<>(200,"操作成功", data,System.currentTimeMillis());}publicstatic<T>Result<T>success(){returnsuccess(null);}publicstatic<T>Result<T>error(String message){returnnewResult<>(500, message,null,System.currentTimeMillis());}publicstatic<T>Result<T>error(Integer code,String message){returnnewResult<>(code, message,null,System.currentTimeMillis());}}

6.2 全局异常处理

@RestControllerAdvice@Slf4jpublicclassGlobalExceptionHandler{/** * 业务异常 */@ExceptionHandler(BusinessException.class)publicResult<Void>handleBusinessException(BusinessException e){ log.error("业务异常: {}", e.getMessage());returnResult.error(e.getCode(), e.getMessage());}/** * 参数校验异常 */@ExceptionHandler(MethodArgumentNotValidException.class)publicResult<Void>handleValidationException(MethodArgumentNotValidException e){String message = e.getBindingResult().getFieldErrors().stream().map(FieldError::getDefaultMessage).collect(Collectors.joining(", ")); log.error("参数校验失败: {}", message);returnResult.error(400, message);}/** * 权限异常 */@ExceptionHandler(AccessDeniedException.class)publicResult<Void>handleAccessDeniedException(AccessDeniedException e){ log.error("权限不足: {}", e.getMessage());returnResult.error(403,"权限不足");}/** * 系统异常 */@ExceptionHandler(Exception.class)publicResult<Void>handleException(Exception e){ log.error("系统异常", e);returnResult.error("系统错误,请联系管理员");}}

6.3 MyBatis-Plus 配置

@Configuration@MapperScan("com.enterprise.mapper")publicclassMyBatisPlusConfig{/** * 分页插件 */@BeanpublicMybatisPlusInterceptormybatisPlusInterceptor(){MybatisPlusInterceptor interceptor =newMybatisPlusInterceptor();// 分页插件 interceptor.addInnerInterceptor(newPaginationInnerInterceptor(DbType.MYSQL));// 乐观锁插件 interceptor.addInnerInterceptor(newOptimisticLockerInnerInterceptor());return interceptor;}/** * 数据填充处理器 */@BeanpublicMetaObjectHandlermetaObjectHandler(){returnnewMetaObjectHandler(){@OverridepublicvoidinsertFill(MetaObject metaObject){this.strictInsertFill(metaObject,"createTime",LocalDateTime.class,LocalDateTime.now());this.strictInsertFill(metaObject,"updateTime",LocalDateTime.class,LocalDateTime.now());}@OverridepublicvoidupdateFill(MetaObject metaObject){this.strictUpdateFill(metaObject,"updateTime",LocalDateTime.class,LocalDateTime.now());}};}}

七、部署与运维

7.1 Docker 容器化部署

后端 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 编排
version:'3.8'services:# MySQL数据库mysql:image: mysql:8.0container_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:6379depends_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 配置

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

前后端分离

工作流引擎

报表系统

参考文档

Read more

什么是人工智能?AI、机器学习、深度学习的关系

什么是人工智能?AI、机器学习、深度学习的关系

文章目录 * 什么是人工智能 * 人工智能的定义 * 人工智能的分类 * 什么是机器学习 * 机器学习的基本概念 * 机器学习的工作流程 * 机器学习的主要类型 * 什么是深度学习 * 深度学习的基本概念 * 深度学习的优势 * 深度学习的应用领域 * AI、机器学习、深度学习的关系 * 三者的层次关系 * 三者的发展历程 * 如何选择合适的方法 * 实际应用案例分析 * 案例一:垃圾邮件过滤 * 案例二:图像识别 * 案例三:推荐系统 * 学习路径建议 * 第一阶段:打好基础 * 第二阶段:深入学习 * 第三阶段:实战提升 * 总结 本篇文章将带你深入理解人工智能的核心概念,厘清AI、机器学习、深度学习之间的关系,为后续的学习打下坚实的基础。 什么是人工智能 人工智能的定义 人工智能,英文名称为Artificial Intelligence,简称AI,这个概念最早由约翰·麦卡锡在1956年的达特茅斯会议上提出。那么什么是人工智能呢?简单来说,人工智能就

By Ne0inhk
会提问的人,正在用AI收割下一个十年

会提问的人,正在用AI收割下一个十年

文章目录 * 引言:一场关于AI的颠覆性对话 * 从对话到收入:AI时代的新型生产关系 * 会说话就能赚钱?这不是天方夜谭 * 从想法到产品:三天的魔法 * 技术民主化:AI不再是工程师的专属 * 打破技术壁垒的革命 * 文科生的优势在哪里? * AI时代的商业逻辑:用户付费意愿超预期 * 价值认知的转变 * 为什么用户愿意付费? * 新的商业模式 * AI的边界:思考仍然是人类的专属 * 技术的局限性 * 人机协作的最佳模式 * 实践指南:如何开始你的AI创作之旅 * 第一步:转变思维方式 * 第二步:从小项目开始 * 第三步:快速迭代 * 第四步:关注用户价值 * 第五步:建立商业模式 * 《脉向AI》:探索AI时代的无限可能 * 为什么要关注这期访谈? * 这不仅仅是一次访谈 * 结语:属于每个人的AI时代 引言:一场关于AI的颠覆性对话 在这个技术迅猛发展的时代,我们总是习惯性地认为,掌握AI技术是程序员和工程师的专属特权。但如果我告诉你,文科生可能才是A

By Ne0inhk
2026年AI大模型应用开发保姆级教程:从入门到精通,这一篇就够了!AI大模型应用开发学习路线

2026年AI大模型应用开发保姆级教程:从入门到精通,这一篇就够了!AI大模型应用开发学习路线

🚀 AI大模型应用开发:从入门到精通的体系化学习路线 (2026版) 摘要: 随着ChatGPT、文心一言、通义千问等大模型的爆发,掌握AI大模型应用开发已成为开发者进阶、获取高薪的黄金技能!本文由深耕AI领域的ZEEKLOG专家撰写,为你梳理一条清晰、高效、可落地的学习路线,涵盖必备基础、核心理论、关键技术、工具链、项目实战全流程,助你从“小白”快速成长为能独立开发AI应用的高手!文末附赠精选学习资源清单! 📌 一、 为什么学习AI大模型应用开发? * 时代风口: AI大模型是当前科技革命的核心驱动力,重塑各行各业(办公、教育、医疗、金融、娱乐等),人才缺口巨大,薪资水平水涨船高。 * 降本增效: 利用大模型强大的生成、理解、推理能力,可以自动化大量重复性工作,大幅提升开发效率和产品智能化水平。 * 创新机遇: 大模型为开发者提供了前所未有的能力基石,催生无数创新应用场景(智能助手、个性化推荐、代码生成、内容创作、智能客服等)。 * 开发者必备技能: 未来,

By Ne0inhk
人工智能:扩散模型(Diffusion Model)原理与图像生成实战

人工智能:扩散模型(Diffusion Model)原理与图像生成实战

人工智能:扩散模型(Diffusion Model)原理与图像生成实战 1.1 本章学习目标与重点 💡 学习目标:掌握扩散模型的核心原理、前向扩散与反向扩散过程,以及基于扩散模型的图像生成任务实战流程。 💡 学习重点:理解扩散模型的噪声添加与噪声消除机制,学会使用 PyTorch 搭建 DDPM 模型,完成手写数字图像生成任务。 1.2 扩散模型的核心思想 1.2.1 为什么需要扩散模型 💡 传统的生成模型(如 GAN)存在训练不稳定、模式崩溃等问题。扩散模型作为一种基于概率的生成模型,通过逐步添加噪声和逐步去除噪声的双向过程,实现了更稳定的训练和更高质量的生成效果。 扩散模型的灵感来源于非平衡热力学,它的核心是将复杂的生成问题拆解为多个简单的马尔可夫链步骤。在图像生成、文本生成、语音合成等领域,扩散模型的表现已经超越了传统生成模型。 1.2.2 扩散模型的基本框架 💡 扩散模型包含两个核心过程:前向扩散过程和反向扩散过程。 1. 前向扩散过程:从真实数据出发,

By Ne0inhk