news 2026/9/7 17:47:20

基于SpringCloud+Layui+AI的智能政务微服务审批系统设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于SpringCloud+Layui+AI的智能政务微服务审批系统设计

又到毕业设计选题季,我陆续收到不少同学的私信,问题高度相似:“想做一个XX管理系统,用SSM框架行不行?”“单体架构的后台管理系统,答辩时会不会被老师觉得太简单?”说实话,SSM单体写一套增删改查确实不难,但真正到答辩或面试时,老师很可能追问一句:“如果多个部门同时审批,系统卡顿怎么办?”“权限是怎么做细粒度控制的?”“流程状态流转的代码在哪一层?”这些问题,如果只停留在单体架构,往往答得没有底气。

今年有一个毕业设计题目值得拆解——基于SpringCloud+Layui+AI的智能政务微服务审批管理系统。这个题目最大的价值在于它没有停留在“管理系统”的舒适区,而是把三条技术线组合在一起:SpringCloud做微服务治理,Layui做后台管理界面,AI作为创新点接入审批业务。从实际工程角度来看,这个组合兼顾了毕业设计的查重性、演示效果和答辩深度。

本文不打算吹嘘这套项目有多“完美”,而是想从技术拆解的角度说清楚:微服务为什么这样拆、SpringCloud核心组件分别解决什么问题、AI能力到底能落在哪些审批环节、Layui在这种前后端分离程度不高的项目里怎么用最合适。如果你正在准备类似的毕业设计,或者想从单体项目往微服务方向过渡,这篇文章可以当成一份“技术预演”来读。全文不依赖某个特定版本,重点讲清楚思路和关键配置,你拿到任何一套SpringCloud环境都能对应上。

1. 为什么这个题目踩中了主流技术栈

先说一个比较扎心的现象:每年毕业设计里,“XX管理系统”占了相当大的比例,但其中很多系统还是传统的单体架构——一个SpringBoot应用,配上MyBatis和Thymeleaf或者JSP,前端页面直接丢在静态资源目录里。这样的系统用来演示“能跑”是没问题的,但放到“技术含金量”的维度看,确实缺少可聊的空间。

这个题目真正的聪明之处,在于它用一套业务系统,同时覆盖了三类技术关键词:

SpringCloud微服务。这是目前企业在数字化转型中最常用到的技术栈之一。业务拆分、服务注册、网关路由、远程调用、配置中心,这些概念在面试题里反复出现,但在毕业设计里能完整落地的不多。政务审批场景天然适合拆微服务,因为参与方多、流程长、模块边界清晰:用户登录认证是一块,审批业务是一块,流程流转是一块,AI辅助能力又是一块。

Layui后台管理。不少人觉得Layui“老”,但“老”不等于“不好用”。对于毕业设计和中小型后台系统,Layui的组件化思路、自带表格分页、表单验证、弹出层,开发效率极高。它不需要Node构建链,也不需要Webpack配置,引入layui.jslayui.css就能开始写页面。在时间紧张的情况下,用Layui比用Vue+Element UI从零搭一套工程更稳妥。

AI能力接入。现在的毕业设计如果没有一点AI元素,答辩时容易被问“你的系统有什么亮点”。这个题目把AI放在审批场景里,已经有了很自然的切入点:材料预审、智能问答、文本查重、审批意见生成。AI不是飘在业务之外的花架子,而是真正嵌在审批流程里。

所以这个题目的选题逻辑是:用微服务架构承载一套真实业务,用Layui保证后台开发效率,用AI能力制造创新点。它不追求技术上的“新”,而是追求技术组合上的“合理”。

2. 政务审批场景与微服务架构的匹配逻辑

在展开技术细节之前,有必要先把业务场景讲清楚。政务审批管理系统,典型的使用对象是办事群众和审批人员。办事群众在线提交材料,系统对材料做初步检查,然后流转到审批人员那里,经过多个节点——部门初审、分管领导审批、终审——最后给出审批结果。

这个场景放在单体架构里能不能做?能。但如果考虑更多真实因素,单体的短板就暴露出来了:

  • 审批高峰期,材料提交和审批查询集中在同一时段,数据库连接和接口响应容易被拖垮。
  • 审批材料、用户信息、流程实例、日志记录,这几类数据的访问模式和量级差别很大,放在同一个数据库里,索引和表结构互相牵制。
  • 不同审批节点可能需要不同的权限策略,单体架构中权限拦截通常写在Filter或者Interceptor里,时间长了会越来越难维护。
  • AI能力如果要接入,比如材料预审,调用的是外部大模型API,响应时间比普通数据库查询慢一个数量级,如果放在原有请求链路里,很容易把整个系统拖慢。

微服务架构不是万能的,但在这个场景里,它的多数特性都能派上用场:

微服务特性在审批系统中的实际位置
服务注册与发现各业务服务启动后自动注册到Nacos,网关按服务名转发
网关路由与鉴权所有外部请求先经过Gateway,再分发到对应服务
远程调用审批服务调用用户服务获取审批人信息,调用AI服务完成材料预审
配置中心各服务的数据库配置、API Key、业务开关统一管理
独立部署AI服务可以独立扩容,即使AI服务暂时不可用,审批主流程也能继续走

对于毕业设计来说,这个场景的最大好处是:每个微服务都能找到明确的业务归属,不会出现“为了微服务而微服务”的尴尬。答辩时被问到“为什么把这个模块拆出来”,可以拿出业务层面的理由,而不是只说“老师让我这么做的”。

3. 系统总体架构与技术选型

这个项目的架构分层比较清晰,可以概括为“入口-治理-业务-数据-智能”五个层面。

入口层:Layui前端页面 + Nginx静态资源服务。页面运行在浏览器里,通过HTTP请求访问后端接口。Layui本身只是一个前端UI框架,不负责页面渲染,也不承担路由跳转,它只需要通过ajax与后端交互。

治理层:SpringCloud Gateway网关 + Nacos注册中心/配置中心。网关负责统一入口、跨域处理、简单鉴权、路由转发。Nacos负责服务注册发现和配置管理。

业务服务层:服务按业务边界拆为多个独立模块。用户认证服务负责登录、权限、用户信息;业务审批服务负责审批任务的创建、流转、查询;流程引擎服务负责状态机的推进;AI服务负责与大模型API交互,输出预审结果或智能问答内容。

数据层:MySQL存储业务数据,Redis缓存用户会话和热点审批数据。各微服务可以共用同一个MySQL实例,但逻辑上通过不同数据库或不同表前缀做隔离。毕业设计阶段不建议每个服务独立拆一个数据库实例,那样会显著增加部署复杂度。

智能层:通过HTTP调用大模型API,对审批材料做预审分析,对群众常见问题做智能答复。

下面这张表汇总了项目里最核心的技术组件:

技术组件在本项目中的作用
Spring Boot各微服务的基础开发框架
Spring Cloud Alibaba提供微服务治理能力
Nacos服务注册发现 + 配置中心
Spring Cloud Gateway统一API网关,路由和跨域
OpenFeign服务之间声明式远程调用
MySQL用户信息、审批业务数据存储
Redis登录Token、审批缓存、限流计数
MyBatis-Plus数据持久层,简化单表CRUD
Layui后台管理界面UI框架
大模型APIAI材料预审、智能问答(通常以OpenAI兼容接口对接)

从技术栈来看,它并没有引入太冷门的东西,每个技术国产化率也很高,Nacos、MyBatis-Plus在中文技术社区的资料非常丰富。这一点对于毕业设计尤其重要,遇到问题能搜到解决方案。

这里要多说一句版本兼容的问题:Spring Boot 2.x和Spring Cloud Hoxton/2021.x搭配比较稳定,Spring Boot 3.x则要求Spring Cloud 2022.x以上,同时JDK需要17以上。到写代码之前,先查一下官方版本兼容矩阵,不要凭感觉乱搭版本。本文的代码以Spring Boot 2.x风格演示,如果你用Spring Boot 3.x,把javax.servlet相关改成jakarta.servlet即可。

4. 环境准备与前置条件

环境准备这一步是毕业设计里最容易出问题的地方,很多同学在配置环境上花的时间比写代码还多。下面按推荐顺序列出需要准备的环境。

4.1 JDK环境

推荐JDK 8或者JDK 11。如果你打算用较新的Spring Boot 3.x,那么JDK 17是基础。安装后配置好JAVA_HOME环境变量,命令行执行java -version确认版本。

4.2 Maven环境

Maven负责依赖管理。建议配置阿里云镜像,否则下载SpringCloud相关依赖会非常慢。在settings.xml里加入镜像:

<mirror> <id>aliyunmaven</id> <mirrorOf>central</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>

4.3 MySQL

安装MySQL 5.7或8.0。本项目的核心业务数据都存MySQL,字符集建议用utf8mb4,排序规则utf8mb4_general_ci,这样能兼容中文和一些特殊字符。

4.4 Redis

Redis在本项目中的职责是缓存登录用户信息、维护Token有效期,也可以用来做接口限流。安装后默认端口6379,密码可以设置,但毕业设计阶段不设置密码也能跑通。

4.5 Nacos

Nacos在整个微服务架构中扮演两个角色:注册中心和配置中心。下载Nacos Server后,Windows环境直接进入bin目录,双击startup.cmd启动,Linux/Mac环境执行sh startup.sh -m standalone。启动成功后浏览器访问http://localhost:8848/nacos,默认账号密码都是nacos

4.6 开发工具

推荐IntelliJ IDEA。创建父工程后,在IDEA里同时打开多个子模块,启动时按依赖顺序逐个启动服务。

5. 父工程搭建与微服务模块划分

这一节直接进入代码实操。先创建一个Maven父工程,用来统一管理所有子模块的依赖版本。

文件路径:pom.xml

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>gov-approval-parent</artifactId> <version>1.0.0</version> <packaging>pom</packaging> <properties> <spring.boot.version>2.7.18</spring.boot.version> <spring.cloud.version>2021.0.8</spring.cloud.version> <spring.cloud.alibaba.version>2021.0.5.0</spring.cloud.alibaba.version> <mybatis.plus.version>3.5.3.1</mybatis.plus.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring.boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring.cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring.cloud.alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <modules> <module>gov-common</module> <module>gov-gateway</module> <module>gov-auth</module> <module>gov-approval</module> <module>gov-ai</module> </modules> </project>

这里的模块划分对应实际业务:

模块职责
gov-common公共工具类、统一返回结果、异常处理
gov-gateway网关服务,负责路由转发和跨域
gov-auth用户认证与权限服务
gov-approval审批业务服务,核心流程
gov-aiAI能力服务,对接大模型API

父工程只是依赖管理容器,不写业务代码。这个阶段最大的坑是版本组合,如果Spring Boot、Spring Cloud、Spring Cloud Alibaba三者版本不兼容,会出现启动报错或者组件不可用。上面的版本组合经过大量项目验证,稳妥性较好。

6. 服务注册与发现:Nacos接入

微服务架构里,服务启动后需要注册到注册中心,其他服务才能通过服务名找到它。这个项目选择Nacos,除了配置中心的集成能力强之外,还有一点非常实际:Nacos控制台自带中文界面,启动后能看到每个服务是否在线、实例数量是多少,演示效果比Eureka更直观。

gov-approval服务为例,在pom.xml中加入依赖:

<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>

配置文件采用bootstrap.yml,原因在于Nacos配置中心的客户端需要在Spring容器初始化之前连接配置服务器。

文件路径:gov-approval/src/main/resources/bootstrap.yml

spring: application: name: gov-approval cloud: nacos: server-addr: 127.0.0.1:8848 username: nacos password: nacos discovery: namespace: public group: DEFAULT_GROUP config: namespace: public group: DEFAULT_GROUP file-extension: yaml

启动类的写法与普通SpringBoot一致,重点是加上服务发现注解:

package com.example.govapproval; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient public class GovApprovalApplication { public static void main(String[] args) { SpringApplication.run(GovApprovalApplication.class, args); } }

启动后,在Nacos控制台的“服务管理”页面应该能看到gov-approval服务已经注册,状态为健康。如果没有出现,优先检查三件事:Nacos是否真的启动成功、server-addr地址是否写对、application.name是否与Maven中模块名一致。

7. 网关路由与跨域配置

服务一旦多了,前端不能直接请求每一个服务的端口,否则跨域问题会让人崩溃。网关的作用就是把所有请求收敛到一个端口,然后按路径前缀转发到不同的服务。

文件路径:gov-gateway/src/main/resources/application.yml

server: port: 8080 spring: application: name: gov-gateway cloud: nacos: server-addr: 127.0.0.1:8848 gateway: discovery: locator: enabled: true lower-case-service-id: true routes: - id: auth-route uri: lb://gov-auth predicates: - Path=/api/auth/** filters: - StripPrefix=1 - id: approval-route uri: lb://gov-approval predicates: - Path=/api/approval/** filters: - StripPrefix=1 - id: ai-route uri: lb://gov-ai predicates: - Path=/api/ai/** filters: - StripPrefix=1

配置里要注意几个细节:

lb://前缀表示使用负载均衡方式调用服务,SpringCloud Gateway会从Nacos拿到服务实例列表,然后按照负载均衡策略分发请求。不写lb://,网关就不知道去哪里找服务。

**StripPrefix=1**指去掉路径中的第一个前缀。比如前端请求/api/auth/user/info,经网关转发到gov-auth服务时,实际路径变成了/user/info。如果服务端的Controller里写的是/api/auth/user/info,那么可以不启用这个过滤器,前后保持一致即可。

跨域配置是前后端分离项目的重点。Layui页面如果部署在8081端口,请求网关8080,必然触发跨域。网关层统一处理跨域比较省事:

package com.example.govgateway.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.reactive.CorsWebFilter; import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; @Configuration public class CorsConfig { @Bean public CorsWebFilter corsWebFilter() { CorsConfiguration config = new CorsConfiguration(); config.addAllowedOriginPattern("*"); config.addAllowedMethod("*"); config.addAllowedHeader("*"); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return new CorsWebFilter(source); } }

这里真正容易踩坑的地方是:addAllowedOrigin("*")setAllowCredentials(true)同时使用时会报错,SpringCloud Gateway不支持这种组合。所以推荐用addAllowedOriginPattern("*")

8. 审批服务间的远程调用:OpenFeign

在微服务架构里,服务间调用最常见的方式是OpenFeign。比如审批业务里有一个常见需求:创建审批任务时,需要根据审批人ID去用户服务查询审批人的姓名、部门、岗位。如果审批服务直接连用户数据库,就破坏了微服务的边界,所以要让审批服务通过Feign调用用户服务接口。

首先在gov-approvalpom.xml中加入依赖:

<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>

在启动类上添加@EnableFeignClients注解:

package com.example.govapproval; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class GovApprovalApplication { public static void main(String[] args) { SpringApplication.run(GovApprovalApplication.class, args); } }

然后写一个Feign客户端接口:

package com.example.govapproval.feign; import com.example.common.result.R; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @FeignClient(name = "gov-auth", fallback = UserClientFallback.class) public interface UserClient { @GetMapping("/api/auth/user/{id}") R<UserInfoVO> getUserById(@PathVariable("id") Long id); }

Fallback类用于服务降级:

package com.example.govapproval.feign; import com.example.common.result.R; import org.springframework.stereotype.Component; @Component public class UserClientFallback implements UserClient { @Override public R<UserInfoVO> getUserById(Long id) { return R.fail("用户服务暂时不可用"); } }

要启用Feign的降级能力,还需要在配置文件中打开熔断开关:

feign: circuitbreaker: enabled: true

一个小建议:Feign接口的路径和返回值一定要和服务端实际接口保持一致。很多同学在联调时出现“Feign调用404”的问题,排查时发现Controller路径写的是/user/{id},Feign客户端却写成了/user/get/{id}。这种问题多看服务端日志就能定位。

9. 审批流程核心实现

审批流程是这个系统的业务核心,也是答辩时最能体现“系统设计能力”的部分。项目里可以设计一个清晰的状态机,保证审批任务从提交到结束的每一步都有状态记录。

审批状态可以这样定义:

状态说明
PENDING_SUBMIT待提交
UNDER_REVIEW部门初审中
LEADER_REVIEW领导审批中
APPROVED已通过
REJECTED已驳回
WITHDRAWN已撤回

文件路径:gov-approval/src/main/java/com/example/govapproval/enums/ApprovalStatus.java

package com.example.govapproval.enums; public enum ApprovalStatus { PENDING_SUBMIT(0, "待提交"), UNDER_REVIEW(1, "部门初审中"), LEADER_REVIEW(2, "领导审批中"), APPROVED(3, "已通过"), REJECTED(4, "已驳回"), WITHDRAWN(5, "已撤回"); private final Integer code; private final String desc; ApprovalStatus(Integer code, String desc) { this.code = code; this.desc = desc; } public Integer getCode() { return code; } public String getDesc() { return desc; } public static ApprovalStatus of(Integer code) { for (ApprovalStatus status : values()) { if (status.code.equals(code)) { return status; } } return null; } }

审批任务的Service层不必把逻辑堆在Controller里。一个比较合理的Controller可以只负责接收参数和返回结果,状态流转放在Service层:

package com.example.govapproval.service; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.govapproval.entity.ApprovalRecord; import com.example.govapproval.enums.ApprovalStatus; import com.example.govapproval.mapper.ApprovalRecordMapper; import org.springframework.stereotype.Service; import java.time.LocalDateTime; @Service public class ApprovalRecordService extends ServiceImpl<ApprovalRecordMapper, ApprovalRecord> { /** * 提交审批材料 */ public void submit(Long recordId) { ApprovalRecord record = getById(recordId); if (record == null) { throw new RuntimeException("审批记录不存在"); } if (!ApprovalStatus.PENDING_SUBMIT.getCode().equals(record.getStatus())) { throw new RuntimeException("当前状态不允许提交"); } record.setStatus(ApprovalStatus.UNDER_REVIEW.getCode()); record.setSubmitTime(LocalDateTime.now()); updateById(record); } /** * 审批通过 */ public void approve(Long recordId, Long approverId, String comment) { ApprovalRecord record = getById(recordId); if (record == null) { throw new RuntimeException("审批记录不存在"); } if (!ApprovalStatus.UNDER_REVIEW.getCode().equals(record.getStatus()) && !ApprovalStatus.LEADER_REVIEW.getCode().equals(record.getStatus())) { throw new RuntimeException("当前状态不允许审批"); } record.setStatus(ApprovalStatus.APPROVED.getCode()); record.setApproverId(approverId); record.setApproveComment(comment); record.setApproveTime(LocalDateTime.now()); updateById(record); } /** * 驳回 */ public void reject(Long recordId, Long approverId, String comment) { ApprovalRecord record = getById(recordId); if (record == null) { throw new RuntimeException("审批记录不存在"); } record.setStatus(ApprovalStatus.REJECTED.getCode()); record.setApproverId(approverId); record.setApproveComment(comment); record.setApproveTime(LocalDateTime.now()); updateById(record); } }

状态机用枚举加显式校验,比在代码里散落魔法数字要清晰很多。答辩时可以直接说:审批状态的合法性由状态机保证,每个状态都定义在枚举类中,流转操作有前置校验,这样非法状态变更在业务入口就被拦住了。

10. AI能力如何接入审批系统

这个项目的名字里带“AI”,所以这一节需要重点讲清楚AI到底落在哪里。合理的落点有三个:

第一,材料预审。群众提交审批材料后,系统调用大模型API,检查材料是否完整、格式是否合规、内容是否存在明显矛盾。这个环节的价值在于减少审批人员的重复劳动。

第二,智能问答。办事群众在提交材料前经常会问“这个材料需要几份”“审批需要多少天”,与其写死FAQ,不如接入大模型做语义理解。

第三,审批意见辅助生成。审批人员填写意见时,系统根据材料摘要生成一段建议性描述,审批人员确认后再提交。

为了方便维护,AI调用统一放在gov-ai服务中,其他服务通过Feign或HTTP调用。

大模型API的对接方式通常是HTTP调用。这里用一个简化示例演示,假设模型服务提供了OpenAI兼容接口:

文件路径:gov-ai/src/main/java/com/example/govai/service/AiReviewService.java

package com.example.govai.service; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.*; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import java.util.List; import java.util.Map; @Service public class AiReviewService { @Value("${ai.api.url}") private String apiUrl; @Value("${ai.api.key}") private String apiKey; @Value("${ai.api.model}") private String model; private final RestTemplate restTemplate; public AiReviewService(RestTemplate restTemplate) { this.restTemplate = restTemplate; } /** * 材料预审 */ public String preReview(String materialContent) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.setBearerAuth(apiKey); JSONObject payload = new JSONObject(); payload.put("model", model); JSONObject userMessage = new JSONObject(); userMessage.put("role", "user"); userMessage.put("content", "你是政务审批材料预审助手。请检查以下材料是否存在以下问题:" + "1. 材料是否完整;2. 是否有明显格式错误;3. 是否有信息矛盾。\n\n材料内容:\n" + materialContent); payload.put("messages", List.of(userMessage)); HttpEntity<String> requestEntity = new HttpEntity<>(payload.toJSONString(), headers); ResponseEntity<String> response = restTemplate.exchange( apiUrl, HttpMethod.POST, requestEntity, String.class ); if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) { JSONObject body = JSON.parseObject(response.getBody()); return body.getJSONArray("choices").getJSONObject(0) .getJSONObject("message") .getString("content"); } return "AI预审服务暂时不可用,请稍后重试"; } }

这里需要提醒一件事:真实项目中,大模型API的Key绝对不能硬编码在代码里。正确做法是放在Nacos配置中心,或者本地的application.yml里,并在.gitignore中排除;同时调用AI必须设置超时时间。因为大模型API的响应速度通常在几秒到几十秒,如果不设超时,一个慢请求可能挂住整个线程。建议统一配置连接超时和读超时:

ai: api: url: https://your-llm-api.example.com/v1/chat/completions key: ${AI_API_KEY} model: gpt-4o-mini

RestTemplate的超时配置:

package com.example.govai.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.web.client.RestTemplate; import java.net.SocketTimeoutException; @Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate() { SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setConnectTimeout(5000); factory.setReadTimeout(30000); return new RestTemplate(factory); } }

AI接入还有一个容易被忽视的问题:降级策略。如果大模型API超时或者说服务不可用,不能因此阻塞审批主流程。正确的做法是捕获异常后给前端返回一个“AI预审暂不可用,请人工预审”的提示,让流程继续走人工通道。这也是答辩时值得展开讲的技术点。

11. Layui后台管理页面的关键写法

Layui在这个项目里负责后台管理界面。虽然现在Vue很流行,但Layui在快速开发场景下确实高效——不需要Node环境,不需要安装依赖,一个JS文件全部搞定。

Layui页面加载时,顶栏和侧边栏用layui.layout组件,内容区用iframe嵌入子页面,这是一种非常经典的后台布局方式。

子页面中典型的表格渲染方式是table.render。这里重点讲两个高频问题。

第一个问题:Layui table 单个列怎么加点击事件?

很多人往table里塞一个“操作”列,然后在done回调里绑定点击事件,其实Layui本身提供了table.on('tool(...))事件。解决办法是:在列配置中通过templet指定一个自定义按钮,然后在table.on('tool(filter)')里监听点击。

table.render({ elem: '#approvalTable', url: '/api/approval/list', page: true, cols: [[ { field: 'id', title: '审批编号', width: 100 } , { field: 'title', title: '事项名称' } , { field: 'statusName', title: '当前状态', width: 120 } , { field: 'createTime', title: '提交时间', width: 180 } , { title: '操作', width: 200, templet: function (d) { return '<button class="layui-btn layui-btn-sm" lay-event="detail">详情</button>' + '<button class="layui-btn layui-btn-sm layui-btn-normal" lay-event="approve">审批</button>'; }} ]] });

注意lay-event属性定义了按钮的事件名。然后通过table.on('tool(filter)', callback)监听:

table.on('tool(approvalTable)', function (obj) { var data = obj.data; if (obj.event === 'detail') { layer.open({ type: 2, title: '审批详情', area: ['600px', '500px'], content: '/page/approval/detail.html?id=' + data.id }); } else if (obj.event === 'approve') { layer.open({ type: 2, title: '审批处理', area: ['700px', '550px'], content: '/page/approval/handle.html?id=' + data.id }); } });

这个方式的优势在于:不用操作DOM、不用绑定多余的onclick,事件完全由Layui统一管理,页面刷新后事件依然有效。

第二个问题:Layui table 的数据格式要求。

Layui table对后端返回的数据格式有严格要求,必须是一个符合以下结构的JSON:

{ "code": 0, "msg": "", "count": 100, "data": [ { "id": 1, "title": "企业设立登记", "statusName": "部门初审中" }, { "id": 2, "title": "建设项目审批", "statusName": "领导审批中" } ] }

其中code必须为0,否则Layui会认为请求失败;count是总记录数,用于分页;data是当前页的列表数据。

所以后端封装统一返回结果时,字段名要严格对齐。比如定义一个R类:

package com.example.common.result; import lombok.Data; @Data public class R<T> { private Integer code; private String msg; private Long count; private T data; public static <T> R<T> ok(T data, Long count) { R<T> result = new R<>(); result.setCode(0); result.setMsg("成功"); result.setCount(count); result.setData(data); return result; } public static <T> R<T> fail(String msg) { R<T> result = new R<>(); result.setCode(500); result.setMsg(msg); return result; } }

注意这里count用了Long类型,如果数据库分页用的Page对象,把page.getTotal()直接放进去就行。

12. 系统启动顺序与验证方式

微服务项目启动有先后顺序,顺序不对会有一堆注册失败的错觉。推荐按以下顺序启动:

第一步启动Nacos。Nacos不启动,后面所有服务都会报连接超时。

第二步启动gov-auth。用户服务是基础服务,审批服务和AI服务启动时可能需要调用它。

第三步启动gov-approvalgov-ai。这两个服务依赖Nacos注册中心,启动后会自动注册。

第四步启动gov-gateway。网关最后启动,这样网关在初始化路由时,下游服务的实例信息已经全部就绪。

验证方式可以分三层:

第一层看注册中心。打开Nacos控制台的服务管理页面,确认gov-authgov-approvalgov-aigov-gateway都显示在线。

第二层看网关转发。直接请求网关地址,例如:

curl "http://localhost:8080/api/auth/user/1"

如果响应正常,说明网关把请求正确转发到了gov-auth服务。

第三层看前端页面。浏览器打开Layui后台页面,进入审批列表,看表格数据是否正常加载。可以顺手测试一下“详情”按钮是否能弹出层。

13. 常见问题与排查思路

微服务项目出了问题,不要急着重启服务,先看日志。下面把这类项目最容易遇到的问题按现象整理成一张表:

问题现象可能原因排查方式解决方案
服务启动报“找不到配置”Nacos配置中心没有建对应的Data ID检查Nacos控制台配置列表是否存在手动创建Data ID,格式为服务名.yaml
注册中心看不到服务实例服务启动失败或Nacos地址错误查看服务日志,检查bootstrap.yml修正server-addr,重新启动
网关转发返回503下游服务不在线或负载均衡拉取不到实例检查下游服务是否注册成功等下游服务启动后重试,或检查服务名
在iframe子页面里Layui表格不渲染子页面没有引入layui.js或table模块浏览器F12控制台看JS报错在子页面重新layui.use(['table'])
Layui表格显示“返回数据格式不正确”后端没有返回统一格式看Network响应体code置为0,补充countdata
Feign调用404路径不一致,或服务名写错看服务端日志确认请求是否到达统一接口路径;检查@FeignClient(name)
AI接口调用超时大模型API响应慢gov-ai日志,检查调用耗时设置读超时时间,加入降级逻辑
跨域报错网关CORS配置不正确或浏览器拦截看浏览器Network响应头是否有CORS头使用addAllowedOriginPattern("*")配置

这里重点说一个容易迷惑的现象:网关或服务首次启动时,控制台可能会出现红色报错,但Nacos页面里服务却显示在线。这通常是因为SpringCloud的某些组件启动时会尝试连接一些可选依赖,连接失败后自动降级。不要一看到红色日志就紧张,先确认关键组件是否就绪,再根据实际功能是否受影响决定是否处理。

14. 项目设计上的几个加分项

这部分内容是给答辩准备的。同样的项目,为什么有人能拿优秀,有人只拿及格?差别往往在于有没有把设计思考讲出来。

第一个加分项:服务拆分的业务依据。答辩时不要只说“我把系统拆成了几个微服务”,要讲清楚边界在哪里。比如用户认证服务独立出来,是因为它是一个被所有业务服务调用的基础能力;AI服务独立出来,是因为它的响应时效模型和普通数据库请求差异很大,独立部署可以避免互相拖累。这种回答体现的是架构能力。

第二个加分项:数据一致性的处理。审批系统里,如果审批服务调用AI服务做预审,AI服务返回了结果,但审批服务在保存时出了异常,两边数据就不一致了。这里可以引入一个简单的方案:把AI预审结果先保存为草稿,审批人员确认后再更新审批状态。这样就在不引入分布式事务组件的情况下,把一致性问题通过业务编排化解了一部分。分布式事务组件Seata在这个项目里可以提,但没必要真引入,毕业设计阶段复杂度会明显上升。

第三个加分项:权限设计。政务审批系统天然需要角色权限。这里可以讲RBAC模型:用户-角色-菜单/权限的三层关联。JWT Token放在请求头里,网关层做统一鉴权,业务服务里做细粒度校验。能讲清楚权限模型的层次关系,是管理系统的加分项。

第四个加分项:安全兜底。生产环境中数据库操作、审批通过、驳回操作都要有操作日志和审计记录。在日志里记录操作人、操作时间、操作内容和结果,出了问题能追溯。这是政务系统非常看重的点。

15. 写在最后

如果把这套毕业设计当成一个纯“管理系统”来做,那就浪费了题目的架构价值。SpringCloud让你理解服务拆分的边界,Nacos让你感受注册中心和配置中心的存在意义,Gateway让你知道网关不是一层简单的转发,Layui帮你理解传统后台管理页面的开发效率,AI接入则逼着你去思考外部能力如何与业务系统集成。

如果你也想做一个类似的系统,我的建议是:先不要急着写代码。找一张纸,把审批业务的角色、状态、流程节点列出来,再对照本篇文章的结构画出服务边界,然后逐步填充代码。把“单体跑通”和“微服务拆分”分成两个阶段来推进,能显著降低调试复杂度。一个技术上成熟、答辩思路清晰的毕业设计,靠的不是代码堆数量,而是每一步选择都有明确的理由。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 17:47:04

gcc hello.c背后:从预处理到链接的编译全流程解析

“hello.c”几乎是我见过的第一段C代码&#xff0c;而gcc hello.c也是我敲下的第一条编译命令。当时只觉得这条命令神奇&#xff1a;一个文本文件&#xff0c;敲一下回车&#xff0c;就变成了能运行的程序。后来折腾的东西多了才发现&#xff0c;这短短一条命令背后藏着一整套工…

作者头像 李华
网站建设 2026/9/7 17:44:59

动画制作中的版本管理与文件命名规范解析

1. 项目概述&#xff1a;解密"dragonballz_e159-2"的创作逻辑这个看似随机的字符串组合&#xff0c;实际上蕴含着动画创作领域的典型版本管理智慧。作为从业15年的动画制片人&#xff0c;我见过太多类似"dragonballz_e159-2"这样的版本代号——它们就像动画…

作者头像 李华
网站建设 2026/9/7 17:44:31

前端BUG、后端BUG、环境BUG如何快速区分?一套定位方法论

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 17:43:32

校园网断网环境下远程访问实验室电脑的解决方案

1. 校园网断网远程访问的核心痛点校园网环境下远程访问实验室或宿舍电脑的需求非常普遍&#xff0c;但经常遇到三个典型问题&#xff1a;首先是校园网IP地址动态分配且不固定&#xff0c;一旦路由器重启就会导致远程连接失效&#xff1b;其次是校园网防火墙通常会屏蔽3389等常见…

作者头像 李华
网站建设 2026/9/7 17:42:11

Harness架构深度拆解:从零搭建企业级AI Agent运行时骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华