
每年到了毕业设计季节很多同学都会在选题、技术选型和系统实现之间反复纠结。做传统单体架构的 HR 管理系统担心技术亮点不足直接上微服务又怕时间不够、踩坑太多。今年还有一个新变量AI 大模型火了如果能把它合理地融入系统既能体现对前沿技术的理解又能让项目在答辩中更有竞争力。本文要整理的就是一套基于 SpringCloud Layui AI 的智能企业微服务人力资源管理系统从架构设计、环境搭建、核心代码到上线部署完整走一遍实现流程。这篇文章适合这几类读者正在准备计算机毕业设计、需要一套完整可演示项目的同学想快速入门 SpringCloud 微服务但同时希望前端不复杂的后端开发者以及想在 HR 系统中尝试接入 AI 能力的工程实践者。读完之后你能理解微服务拆分的思路能跑通一个多模块工程能掌握 Layui 后台管理页面的快速搭建方法也能知道大模型接口如何以可控成本集成到业务模块中。整套系统涉及 SpringCloud、Layui、AI 三条技术线我会先把它们各自的定位讲清楚再按照“架构设计 - 工程搭建 - 前端实现 - AI 集成 - 运行验证 - 排错 - 工程建议”的顺序展开。1. 项目背景与核心概念1.1 为什么人力资源管理系统需要微服务架构先回答一个大家都会问的问题一个 HR 管理系统真的需要微服务吗从业务功能上看人力资源管理系统通常包含员工管理、组织架构、考勤请假、薪资核算、招聘管理、绩效管理、培训管理等模块功能边界其实很清晰。如果企业规模不大、并发很低单体应用完全够用。但毕业设计或课程项目选择微服务架构本质上是希望完成一次完整的技术训练理解服务如何拆分、注册中心如何工作、服务间如何通信、网关如何统一入口、配置如何集中管理。这些能力在真实企业中非常常见而在学校课程里往往只讲了概念没有落地。从另一个角度看微服务架构也确实能解决 HR 系统的两个实际痛点模块独立发布。薪资模块和招聘模块的迭代频率不同拆开后可以单独发布不影响其他服务。独立扩展。招聘季可能外部流量和简历量大招聘服务可以单独扩容而不需要把整个系统一起扩容。所以在项目中采用 SpringCloud 微服务架构不是为了让技术栈显得“高级”而是有一套合理的拆分依据。这个思路在论文的“架构设计”章节和答辩 PPT 中都能成为加分项。1.2 SpringCloud、Layui、AI 在项目中的各自定位这三个技术名词放到一个项目里分别承担不同角色SpringCloud后端的微服务基础设施。它提供服务注册与发现、配置中心、网关路由、负载均衡、远程调用等能力是整个系统的骨架。Layui前端的 UI 框架。Layui 和 Vue、React 这类重框架不同它更接近传统 jQuery 风格入门成本低页面美观特别适合快速搭建后台管理系统。在毕业设计时间有限的情况下用 Layui 做管理端是非常务实的选择。AI系统的智能化增值能力。这里可以做的场景很多比如智能简历解析、智能问答助手、员工情绪分析、薪资异常检测等。在项目中集成 AI并不是要把整个系统改造成 AI 系统而是在合适的业务节点引入大模型或机器学习能力。简单总结SpringCloud 负责系统的“骨架”Layui 负责系统的“面孔”AI 负责系统的“亮点”。1.3 系统核心功能模块梳理一个完整的人力资源管理系统至少要覆盖以下模块模块核心功能说明员工管理员工档案新增、编辑、查询、离职核心主数据所有模块围绕它运行组织管理部门、岗位、职级的维护树形结构是常见难点考勤管理打卡记录、请假审批、出差登记涉及流程状态流转薪资管理薪资项配置、月度薪资计算、工资条涉及敏感数据需做权限控制招聘管理职位发布、简历收集、面试安排可接 AI 简历解析绩效管理考核模板、考核评分、结果查询流程类业务系统管理用户、角色、菜单、日志基础权限体系AI 助手智能问答、简历筛选建议本项目特色模块在论文写作时系统功能结构图、角色权限表、数据库 ER 图这三样东西是必配的建议提前准备好。2. 微服务架构设计与技术选型2.1 服务拆分方案合理拆分是微服务架构最重要、也最容易出错的一步。拆分过细会导致服务数量爆炸维护成本高拆分过粗又体现不出微服务的优势。本项目采用按业务域拆分的方案将系统拆分为以下服务服务名称职责端口示例gateway-server统一网关路由转发、鉴权过滤9000system-server用户、角色、菜单、日志等系统基础功能9100employee-server员工档案、组织架构、岗位管理9200attendance-server考勤、请假、加班、出差9300salary-server薪资项、薪资计算、工资条9400recruit-server招聘职位、简历管理、面试安排9500ai-serverAI 能力封装简历解析、智能问答9600每个服务独立数据库通过 Nacos 注册与发现。服务之间通过 OpenFeign 进行远程调用。这样既保证了模块隔离又不至于把工程拆得太碎适合毕设规模。2.2 注册中心与配置中心选型SpringCloud 的注册中心有 Eureka、Consul、Nacos 等多个选择。这里推荐 Nacos原因有三个同时具备注册中心和配置中心功能一套中间件解决两个问题减少部署成本。控制台界面友好答辩演示时可以看到服务列表和配置信息演示效果好。SpringCloud Alibaba 生态在国内企业中使用广泛相关中文资料多踩坑容易找到解决方案。Nacos 需要单独安装并启动下面第 3 部分会给出具体说明。2.3 数据库设计思路与数据隔离微服务架构下数据库设计的核心原则是“服务自治数据隔离”。每个服务只访问自己的数据库不允许直接访问其他服务的表。比如员工服务和薪资服务都涉及“用户”概念但薪资服务需要员工信息时应该通过 openfeign 调用员工服务接口而不是去员工库里查表。实际建表时各服务可以准备独立的数据库例如hr_system系统管理库存放用户、角色、菜单表。hr_employee员工库存放员工档案、部门、岗位表。hr_attendance考勤库存放打卡、请假、加班表。hr_salary薪资库存放薪资项、工资条表。hr_recruit招聘库存放职位、简历、面试表。hr_aiAI 应用库存放简历解析记录、问答记录表。如果本地机器资源有限也可以共用一个 MySQL 实例、用不同 database 名区分。这在单机演示环境中完全可行并且能在论文中说明“逻辑隔离、物理共享”的折中策略。3. 环境准备与版本说明3.1 开发环境清单编写和运行本项目所需要的环境如下环境版本建议说明JDK1.8 或 11SpringCloud 2021 版本对应 Boot 2.6/2.7JDK8 即可Maven3.6多模块构建必需MySQL5.7 或 8.0数据库Nacos2.x注册中心和配置中心Redis5.x/6.x/7.x缓存、验证码存储按需IDEIntelliJ IDEA后端开发Node.js非必需本前端为 Layui 静态资源不强制使用注意版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路不要盲目照抄版本号。3.2 创建数据库并初始化基础表在 MySQL 中创建数据库。为了方便本地演示先建一个系统库CREATE DATABASE IF NOT EXISTS hr_system DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci; USE hr_system; CREATE TABLE sys_user ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 用户ID, username VARCHAR(50) NOT NULL COMMENT 登录名, password VARCHAR(100) NOT NULL COMMENT 密码(BCrypt加密), real_name VARCHAR(50) DEFAULT NULL COMMENT 真实姓名, email VARCHAR(100) DEFAULT NULL COMMENT 邮箱, phone VARCHAR(20) DEFAULT NULL COMMENT 手机号, status TINYINT DEFAULT 1 COMMENT 状态:1启用,0停用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB COMMENT系统用户表; CREATE TABLE sys_role ( id BIGINT NOT NULL AUTO_INCREMENT, role_name VARCHAR(50) NOT NULL, role_code VARCHAR(50) NOT NULL, remark VARCHAR(255) DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB COMMENT角色表; CREATE TABLE sys_user_role ( user_id BIGINT NOT NULL, role_id BIGINT NOT NULL, PRIMARY KEY (user_id, role_id) ) ENGINEInnoDB COMMENT用户角色关联表;初始化一条管理员账号密码使用 BCrypt 加密。这里不写死密文项目启动时可以通过 CommandLineRunner 初始化避免手写 BCrypt 字符串容易出错。INSERT INTO sys_user (username, password, real_name, status) VALUES (admin, {noop}123456, 系统管理员, 1);{noop} 是 Spring Security 中表示明文密码的前缀仅用于快速测试生产环境务必使用 BCrypt。4. 核心工程搭建与配置4.1 Maven 父工程与多模块结构在 IDEA 中创建一个 Maven 工程作为父工程打包方式为 pom。核心依赖和版本管理在父工程中统一声明。为了不污染本地仓库也可以使用 dependencyManagement 只做版本管束不实际引入依赖。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent properties java.version1.8/java.version spring-cloud.version2021.0.8/spring-cloud.version spring-cloud-alibaba.version2021.0.5.0/spring-cloud-alibaba.version /properties dependencyManagement dependencies dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-dependencies/artifactId version${spring-cloud.version}/version typepom/type scopeimport/scope /dependency dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-alibaba-dependencies/artifactId version${spring-cloud-alibaba.version}/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement父工程下包含以下 modulecommon公共模块存放统一返回结果、异常处理、工具类、实体基类。gateway-server网关服务。system-server系统管理服务。employee-server员工服务。recruit-server招聘服务。ai-serverAI 服务。在 IDEA 中创建多模块工程时建议先建父工程再通过 New Module 逐步添加每个子模块继承父工程即可。4.2 公共模块的实现公共模块是所有服务的公共依赖最核心的是统一返回结果类。这样在前端 Layui 中接收数据时结构一致处理逻辑统一。package com.example.common.result; import lombok.Data; Data public class ResultT { private Integer code; private String msg; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMsg(操作成功); result.setData(data); return result; } public static T ResultT error(String msg) { ResultT result new Result(); result.setCode(500); result.setMsg(msg); return result; } public static T ResultT error(Integer code, String msg) { ResultT result new Result(); result.setCode(code); result.setMsg(msg); return result; } }Layui 的 table 组件默认接收的格式是{code: 0, msg: , count: 100, data: [...]}其中 code 希望是 0 或 200。为了让前端统一可以在返回时做一层适配或者在 Layui table 的 parseData 回调中做转换。这里稍后会在前端部分细说。4.3 Nacos 注册与配置中心接入以 system-server 为例在 pom.xml 中添加依赖dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId /dependency dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-config/artifactId /dependency配置文件 bootstrap.ymlserver: port: 9100 spring: application: name: system-server cloud: nacos: discovery: server-addr: 127.0.0.1:8848 config: server-addr: 127.0.0.1:8848 file-extension: yaml group: DEFAULT_GROUP datasource: url: jdbc:mysql://127.0.0.1:3306/hr_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl配置中心不是必须使用但如果使用了 Nacos config需要把共享配置放到 Nacos 中。本地调试时为了快速启动也可以先注释掉 config 依赖只保留 discovery。两种方式都行关键是理解它们各自的启动顺序先连配置中心读取配置再启动服务。启动类加 EnableDiscoveryClientpackage com.example.system; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; SpringBootApplication EnableDiscoveryClient public class SystemServerApplication { public static void main(String[] args) { SpringApplication.run(SystemServerApplication.class, args); } }启动后访问 Nacos 控制台http://127.0.0.1:8848/nacos默认用户名密码都是 nacos就能看到 system-server 已经注册到服务列表。4.4 Gateway 网关与统一路由网关是所有请求的入口负责将前端请求转发到对应微服务。创建 gateway-server 模块dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-gateway/artifactId /dependency dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId /dependencyapplication.ymlserver: port: 9000 spring: application: name: gateway-server cloud: nacos: discovery: server-addr: 127.0.0.1:8848 gateway: routes: - id: system-route uri: lb://system-server predicates: - Path/api/system/** filters: - StripPrefix2 - id: employee-route uri: lb://employee-server predicates: - Path/api/employee/** filters: - StripPrefix2 - id: recruit-route uri: lb://recruit-server predicates: - Path/api/recruit/** filters: - StripPrefix2 - id: ai-route uri: lb://ai-server predicates: - Path/api/ai/** filters: - StripPrefix2 globalcors: cors-configurations: [/**]: allowedOriginPatterns: * allowedMethods: * allowedHeaders: * allowCredentials: true这里的路由规则解释一下前端请求http://localhost:9000/api/system/user/list时网关先命中Path/api/system/**这条路由StripPrefix2会去掉/api/system两层前缀剩余/user/list转发到 system-server。由于基于 Nacos 的负载均衡uri 使用lb://system-server会自动发现服务实例并做负载均衡。5. 基于 Layui 的后台管理前端实现5.1 Layui 页面基本布局Layui 的使用非常简单不需要打包构建直接引入 CSS 和 JS 文件即可。如果是本地离线环境可以在 Layui 官网下载 layui 2.x 版本解压后放到项目的 static 目录。后台首页通常采用左侧菜单 右侧内容区域的布局!DOCTYPE html html langzh-CN head meta charsetUTF-8 title智能HR管理系统/title link relstylesheet href/layui/css/layui.css /head body div classlayui-layout layui-layout-admin div classlayui-header div classlayui-logo layui-hide-xs layui-bg-black智能HR管理系统/div ul classlayui-nav layui-layout-right li classlayui-nav-item a hrefjavascript:;管理员/a dl classlayui-nav-child dda href/logout退出登录/a/dd /dl /li /ul /div div classlayui-side layui-bg-black div classlayui-side-scroll ul classlayui-nav layui-nav-tree lay-filtermenu li classlayui-nav-item a hrefjavascript:;系统管理/a dl classlayui-nav-child dda>div stylepadding: 15px; div stylemargin-bottom: 10px; button classlayui-btn idbtnAdd新增用户/button /div table iduserTable lay-filteruserTable/table /div script src/layui/layui.js/script script layui.use([table, layer, form], function(){ var table layui.table; var layer layui.layer; var form layui.form; table.render({ elem: #userTable, url: /api/system/user/list, page: true, cols: [[ {field: id, title: ID, width: 80}, {field: username, title: 登录名, width: 120}, {field: realName, title: 真实姓名, width: 120}, {field: email, title: 邮箱, width: 200}, {field: phone, title: 手机号, width: 150}, {field: status, title: 状态, width: 100, templet: function(d){ return d.status 1 ? 启用 : 停用; }}, {field: createTime, title: 创建时间, width: 180}, {title: 操作, toolbar: #userBar, width: 150} ]] }); // 行工具条事件 table.on(tool(userTable), function(obj){ var data obj.data; if(obj.event edit){ // 打开编辑弹窗 } else if(obj.event delete){ layer.confirm(确定删除该用户吗, function(index){ $.ajax({ url: /api/system/user/delete/ data.id, method: delete, success: function(res){ if(res.code 200){ layer.msg(删除成功); table.reload(userTable); } else { layer.msg(res.msg, {icon: 2}); } } }); layer.close(index); }); } }); }); /script script typetext/html iduserBar a classlayui-btn layui-btn-xs lay-eventedit编辑/a a classlayui-btn layui-btn-danger layui-btn-xs lay-eventdelete删除/a /script很多同学在使用 Layui table 时遇到表头错位问题尤其是页面有滚动条时。产生这个问题的原因通常是 table 容器初始化时宽度计算不完全。解决办法有两个一是给 table 父容器设置固定高度并将 table 的 height 参数设置为可选值二是调用table.resize(userTable)重新计算。如果表头在滚动时一直显示异常还要检查页面有没有在 CSS 中给layui-table-header加 overflow 属性默认样式通常不需要修改。5.3 Layui Ajax 请求 GET 写法项目中大量使用 jQuery 的 ajax 方法。Layui 内置了 jQuery不需要额外引入。一个常见的 GET 请求示例如下// 带参数查询 function loadData(page, limit, keyword){ $.ajax({ url: /api/employee/list, type: get, data: { page: page, limit: limit, keyword: keyword }, dataType: json, success: function(res){ // 渲染表格数据 console.log(res); }, error: function(xhr, status, error){ layer.msg(请求失败 error); } }); }注意Layui 中如果使用 table.render 自带的分页和排序功能不需要手动写 $.ajax 获取列表它会自动请求 url 并装载数据。手动 ajax 一般用于下拉框级联、详情查询等场景。5.4 表单弹窗与提交新增用户的弹窗可以用 layer.open 加载独立页面也可以使用隐藏表单$(#btnAdd).click(function(){ layer.open({ type: 1, title: 新增用户, area: [500px, 400px], content: $(#userFormWrap), // 隐藏的表单容器 btn: [保存, 取消], yes: function(index, layero){ var formData { username: $(#username).val(), realName: $(#realName).val(), email: $(#email).val(), phone: $(#phone).val() }; $.ajax({ url: /api/system/user/add, type: post, contentType: application/json, data: JSON.stringify(formData), success: function(res){ if(res.code 200){ layer.msg(新增成功); layer.close(index); table.reload(userTable); } else { layer.msg(res.msg, {icon: 2}); } } }); } }); });建议在调用后端接口前先用 layui 的 form 模块做一次非空校验减少无效请求。校验规则可以写在表单元素的lay-verify属性上。6. AI 能力在 HR 系统中的集成实践6.1 AI 能做什么场景选择AI 大模型接入 HR 系统最容易落地、演示效果也最直观的三个场景智能简历解析用户上传简历文件AI 自动提取姓名、联系方式、工作经历、技能标签并输出匹配度评分。智能问答助手员工在系统内提问社保、考勤制度、薪资计算方式等AI 根据知识库回答。招聘 JD 生成HR 输入岗位关键词AI 自动生成职位描述和任职要求。考虑到毕设演示的稳定性推荐选择“智能简历解析 智能问答”组合因为它们不依赖大量训练数据可以直接调用大模型接口完成。6.2 AI 服务模块设计单独创建 ai-server 模块不要将 AI 调用逻辑散落在其他业务服务中。好处是AI 接口的密钥管理、调用频率控制、异常降级都集中在一个地方后续替换模型厂商时只改一个服务。ai-server 内部设计如下public interface AiService { /** * 文本生成 */ String generate(String prompt, String systemPrompt); /** * 简历结构化解析 */ ResumeParseResult parseResume(String filePath); }大模型的调用细节不建议直接暴露给 Controller而应该封装在 service 层。这样 Controller 只负责接收参数和返回结果模型切换不影响接口层。6.3 调用大模型接口的通用实现不同大模型厂商的接口细节不同但思路相通通过 HTTP POST 发送 prompt拿到返回文本。为了项目可扩展可以基于 HttpClient 封装一个简单的客户端。package com.example.ai.client; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.*; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; Component public class LlmClient { Value(${ai.llm.api-url:}) private String apiUrl; Value(${ai.llm.api-key:}) private String apiKey; Value(${ai.llm.model:qwen-plus}) private String model; private final RestTemplate restTemplate; public LlmClient(RestTemplate restTemplate) { this.restTemplate restTemplate; } public String chat(String systemPrompt, String userPrompt) { HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.setBearerAuth(apiKey); JSONObject body new JSONObject(); body.put(model, model); JSONArray messages new JSONArray(); JSONObject systemMsg new JSONObject(); systemMsg.put(role, system); systemMsg.put(content, systemPrompt); messages.add(systemMsg); JSONObject userMsg new JSONObject(); userMsg.put(role, user); userMsg.put(content, userPrompt); messages.add(userMsg); body.put(messages, messages); body.put(temperature, 0.7); HttpEntityString entity new HttpEntity(body.toJSONString(), headers); ResponseEntityString response restTemplate.exchange(apiUrl, HttpMethod.POST, entity, String.class); String responseBody response.getBody(); JSONObject jsonObject JSON.parseObject(responseBody); return jsonObject.getJSONArray(choices).getJSONObject(0) .getJSONObject(message).getString(content); } }注意上述代码中的 api-url、model、返回体解析字段完全取决于你使用的模型服务商属于“示例思路”接入时需要根据实际文档调整。在实际项目中更推荐使用官方 SDK 而不是手写 HTTP 客户端因为 SDK 会处理协议细节和重试策略。6.4 智能简历解析模块简历解析的完整流程是上传文件 - 提取文本 - 调用大模型 - 结构化保存。这里用一个简单实现来演示RestController RequestMapping(/api/ai) public class AiResumeController { Resource private AiResumeService resumeService; PostMapping(/resume/parse) public ResultResumeParseResult parse(RequestParam(file) MultipartFile file) { if (file.isEmpty()) { return Result.error(请选择简历文件); } try { ResumeParseResult result resumeService.parse(file); return Result.success(result); } catch (Exception e) { log.error(简历解析失败, e); return Result.error(简历解析失败请稍后重试); } } }service 实现大致逻辑public ResumeParseResult parse(MultipartFile file) { // 1. 提取文本docx/pdf/txt可以使用 Apache POI 或 PDFBox String text extractText(file); // 2. 构造 prompt 提示大模型 String prompt 请提取以下简历信息姓名、电话、邮箱、工作年限、技能标签、教育经历返回 JSON 格式。\n简历内容\n text; // 3. 调用大模型 String jsonText llmClient.chat(你是一个专业的HR助理擅长结构化解析简历。, prompt); // 4. 解析结果并保存记录 ResumeParseResult result JSON.parseObject(jsonText, ResumeParseResult.class); // 5. 记录解析日志便于问题追溯 resumeLogService.saveLog(file.getOriginalFilename(), text, jsonText); return result; }这里的大模型输出可能不是严格的 JSON实际生产中需要做容错处理。常见做法是使用 JSON 解析失败时提示用户重新提交或者对输出做二次修正。毕设中能做到“文档格式正确时稳定解析”就足够。6.5 知识库问答助手智能问答助手通常使用 RAG检索增强生成思路先把常见的人力资源制度文档切分存入向量数据库用户提问时先检索相关片段再把片段拼进 prompt让大模型基于知识库内容回答。如果不想引入向量数据库的复杂度可以用最简单的“FAQ 词表匹配 大模型润色”方案用一个表存储常见问题关键词和标准答案命中关键词直接返回未命中则走大模型生成。这个做法尤其适合毕设环境不依赖额外的向量数据库服务。7. 系统运行与验证7.1 服务启动顺序多模块项目启动顺序非常重要否则会出现服务注册失败或无法调用的问题。推荐顺序启动 MySQL确认数据库和表结构已初始化。启动 Nacos访问控制台确认可用。启动网关服务 gateway-server。依次启动其他业务服务 system-server、employee-server、recruit-server、ai-server。在 IDEA 中可以通过 Edit Configurations 配置一个 Compound 运行方式把多个服务统一启动。也可以直接在 application.yml 中为每个子模块设置不同的端口开发时逐个启动并观察 Nacos 控制台的服务列表。7.2 验证注册中心服务列表启动完成后打开 Nacos 控制台应能看到至少 5 个服务gateway-serversystem-serveremployee-serverrecruit-serverai-server7.3 测试接口和页面用浏览器访问http://localhost:9000/api/system/user/list如果配置正确应返回 JSON 数据。这里如果不走网关、直接访问http://localhost:9100/user/list也能通但正式使用时统一走网关。前端测试时按以下流程走一遍打开系统首页登录管理员账号。进入用户管理新增一个用户验证表格刷新。进入员工管理新增员工档案检查部门下拉框是否正常。进入招聘管理上传一份测试简历点击“AI 解析”观察解析结果是否结构化显示。打开 AI 助手对话窗口输入“请计算我本月请了 2 天事假后的薪资”观察回答是否能引用知识库内容。7.4 常见联调问题在联调阶段最容易遇到的问题就是跨域。如果前端静态资源通过 IDEA 直接启动比如端口 8080而后端网关在 9000就会出现跨域问题。解决方案分为两种方案一前端不走网关直接请求各个服务的端口。这种方式开发时最省事但生产环境不合理。方案二前端仍然走网关在网关配置 CORS。我们在第 4.4 节中已经加入了 globalcors 配置这种方式更接近真实部署。8. 常见问题与排查思路问题现象常见原因解决思路Nacos 启动后控制台无法访问版本不兼容或 8848 端口被占用检查端口占用lsof -i:8848换端口或结束占用进程服务启动时报数据源无法连接MySQL 连接信息配置错误或数据库未建表确认 url、username、password先用 Navicat 连接测试服务注册不上 Nacos依赖缺失、地址错误、未加 EnableDiscoveryClient检查 pom 是否有 discovery 依赖检查 server-addr 地址网关转发失败 503目标服务未启动或服务名与 uri 不匹配检查 Nacos 服务列表核对 application.yml 中的服务名前端 Layui 表格不渲染后端接口返回格式不满足 table 组件要求检查返回 JSON调整 code 为 0 或 200data 为数组Layui 表头错位或宽度异常table 容器初始化时页面元素未加载完成使用 table.resize 重绘或给容器设定高宽度AI 接口超时大模型响应时间长、网络问题在 ai-server 设置超时时间前端做 loading 提示避免重复点击AI 接口返回 JSON 解析失败大模型输出包含非 JSON 内容解析前做字符串裁剪或加入修正提示记录原始返回便于排查多服务同时启动导致端口冲突各服务端口重复约定端口号并写入文档IDEA 中逐个确认8.1 排查思路遇到问题时先不要急着改代码按下面的顺序排查看日志。每个服务启动时都会打印注册信息如果注册失败日志中会有明确提示。看 Nacos 控制台。服务列表里有没有目标服务实例是否健康。直接调用服务接口。绕过网关直接访问http://localhost:服务端口/接口先确认服务本身是否正常。再看网关路由。用 Postman 测试网关接口观察路径转换是否正确。最后看前端控制台。F12 打开 Network看请求 URL、请求方式、返回状态码。9. 最佳实践与工程建议9.1 权限与数据安全人力资源系统的数据非常敏感员工薪资、身份证号、联系方式都属于需要严格保护的信息。项目中至少要做到使用 Spring Security 或 Sa-Token 做统一认证授权不能只靠前端隐藏菜单。密码不能明文存储使用 BCrypt 加密。薪资接口、员工导出接口必须做角色鉴权例如只有 HR 管理员可以访问。接口层做参数校验防止 SQL 注入和越权访问。AI 调用日志中不能出现明文敏感个人信息可以脱敏后再记录。如果使用 Spring Security建议重点关注密码加密、登录认证、接口授权这三个环节。Sa-Token 相对 Spring Security 更轻量中文文档友好毕设项目中也很受欢迎。9.2 日志与异常处理微服务架构下服务之间调用链比较长排查问题比单体系统困难。建议从项目初期就建立规范所有 Controller 层异常统一使用RestControllerAdvice处理不让异常堆栈直接暴露给前端。每个服务都配置全局 traceId便于串联日志。使用 logback 按天生成日志文件区分 info 和 error 级别。AI 服务的大模型调用记录入数据库便于追溯生成结果。一个简单的全局异常处理示例RestControllerAdvice public class GlobalExceptionHandler { ExceptionHandler(BusinessException.class) public ResultString handleBusinessException(BusinessException e) { log.warn(业务异常{}, e.getMessage()); return Result.error(e.getCode(), e.getMessage()); } ExceptionHandler(Exception.class) public ResultString handleException(Exception e) { log.error(系统异常, e); return Result.error(系统繁忙请稍后重试); } }9.3 AI 集成的工程化建议AI 功能虽然能增加项目亮点但如果处理不当也会成为整个项目的不稳定因素。建议注意以下几点调用超时设置。大模型接口响应可能在 10 秒以上必须为 RestTemplate 或 HttpClient 设置明确的读取超时时间不能让请求无限等待。降级方案。AI 服务不可用时不阻塞 HR 核心业务。比如简历解析失败时提示用户“可手动填写”问答助手失败时提示“该问题请咨询 HR 部门”。成本控制。大模型 API 按 token 计费毕设场景流量小但也要注意防止循环调用。例如在解析简历前判断文件大小超大文件先压缩或截断。Prompt 设计。同一套功能Prompt 写得好不好直接影响输出质量。建议将系统提示词放在配置文件或数据库中而不是硬编码在代码里方便后续调优。ai: llm: api-url: https://your-llm-endpoint/v1/chat/completions api-key: ${AI_API_KEY} model: qwen-plus timeout-seconds: 30 prompts: resume-system: 你是一个专业的HR助理擅长结构化解析简历请只输出JSON格式。 qa-system: 你是企业内部HR智能助手回答必须基于提供的知识库内容不要编造信息。配置文件使用${AI_API_KEY}环境变量注入密钥避免密钥硬编码提交到 Git 仓库。这个习惯在简历和面试中都能体现工程素养。9.4 论文写作与答辩 PPT 建议很多同学最后卡在论文和 PPT 上这里给出几个实用的建议论文架构图。画一张“系统总体架构图”从展示层Layui到网关层Gateway再到业务服务层最后到基础设施层Nacos、MySQL、Redis一张图能说清楚整个系统。数据库设计说明。每个服务对应的表要给全字段说明重点表要画 ER 图。截图素材。提前把系统页面截图、Nacos 服务列表截图、接口返回 JSON 截图保存好论文和 PPT 中大量需要这些素材。演示脚本。准备一份演示流程从登录开始每个功能点对应的操作步骤和预期结果都要写清楚避免答辩时手忙脚乱。10. 总结这套基于 SpringCloud Layui AI 的人力资源管理系统核心价值在于把主流微服务技术、快速前端开发方式和时下热门的大模型能力有机地结合到一起。从工程搭建角度看它不是一个“玩具项目”而是完整覆盖了服务注册、配置管理、网关路由、前后端分离、AI 接口集成、异常处理、权限控制等真实项目中的关键环节。如果接下来打算继续深入可以从以下方向挑选一个扩展引入分布式事务框架 Seata处理薪资发放这类跨服务一致性要求高的场景。使用 XXL-Job 定时任务实现每月自动计算薪资并推送工资条。将前端从 Layui 升级为 Vue3 Element Plus配合后端接口不变体验前后端分离开发流程。在 AI 模块继续扩展 RAG 知识库问答把 HR 制度文档接入向量数据库让问答助手更接近真实产品。技术选型没有标准答案只要能在答辩中把“为什么这样选”、“遇到了什么问题”、“如何解决”讲清楚这套项目就能成为一份高质量的毕业设计作品。写代码和写论文的过程中遇到具体报错欢迎按本文第 8 部分的排查思路定位问题大部分坑都能通过日志和 Nacos 控制台找到线索。