ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

SpringBoot3+Vue3实战:构建高校园区一体化智慧管理平台与AI助手

2026/8/31 8:28:09 拓冰建站 浏览量
SpringBoot3+Vue3实战:构建高校园区一体化智慧管理平台与AI助手 高校或者园区的一体化智慧管理平台并不是简单地把几个页面放到一起。它要把人员管理、设备管理、通知公告、数据统计等业务放进同一套系统中同时让AI成为可用能力而不是演示Demo。基于SpringBoot3和Vue3构建这样的平台是当前毕业设计和中小型项目里很常见的做法。下面要实现的是一个既可以作为毕业设计原型也能继续向生产环境演进的完整案例。这个案例会覆盖前后端分离架构、用户认证与权限控制、基础业务模块、AI助手接入、数据库设计、联调验证、常见问题排查以及毕业论文和搭建指导视频的组织方式。读者按顺序操作后可以跑通一个带登录、通知公告、园区设备管理和AI问答的一体化平台并理解每个模块为什么这样设计。1. 先理解高校/园区一体化智慧管理平台的技术画像与AI定位1.1 为什么需要一体化平台高校和园区在信息化建设过程中容易出现一个共同问题业务系统彼此独立。有人事系统、设备报修系统、门禁系统、公告发布系统还有日常办公审批系统。每个系统都有自己的登录方式、数据库和操作习惯导致师生需要记多套账号管理员需要在多个后台之间切换数据也无法统一分析。一体化智慧管理平台要解决的是“统一入口、统一数据、统一流程”的问题。它不是把旧系统全部推翻而是先把高频核心业务纳入同一套平台包括用户管理、通知公告、园区设备、访客记录、报修工单、AI服务等。对毕业设计而言不需要做成大型ERP但要把“一体化”的架构思路体现出来。对实际项目而言可以按照模块拆分微服务但初期用一个单体SpringBoot项目承载所有模块更利于快速迭代。1.2 技术选型为什么是SpringBoot3 Vue3 AI后端选择SpringBoot3是因为它基于Spring Framework 6和Jakarta EE支持Java 17及以上版本启动速度更快配置更简洁也适合新项目长期维护。Vue3则使用Composition API和TypeScript代码组织更清晰生态也已经非常成熟。AI能力在智慧管理平台中不是独立玩具。常见场景包括师生通过对话框咨询学校制度、园区规定、报修流程。管理员用AI辅助生成通知公告和会议纪要。园区物业人员用AI分析设备故障描述定位可能原因。把AI能力封装成平台后端接口供前端多个模块调用。AI接入方式建议放到本地模型或国内合规模型API上避免把校园数据发送到不可控的境外服务。本例以Ollama本地大模型为例通过OpenAI兼容接口调用模型跑在本地或学校内网服务器数据不出校适合高校场景。1.3 系统功能边界与核心模块本文实现的核心功能如下模块功能说明涉及技术点用户认证登录、JWT签发、请求鉴权Spring Security、Hutool JWT用户管理用户增删改查、角色区分MyBatis-Plus通知公告发布、查询、置顶REST API、Vue列表页园区设备管理设备列表、状态维护CRUD、分页AI助手接收用户问题返回大模型结果Ollama、RestClient一个完整平台还需要工单、门禁、统计报表但本文先把主链路跑通。后续在模块扩展时只需要按相同模式复制Controller、Service、Mapper不影响整体架构。2. 环境准备与前后端项目初始化2.1 后端环境要求后端使用Spring Boot 3.2.x依赖Java 17。建议使用Maven 3.8以上版本。数据库使用MySQL 8.x缓存可以用Redis但首次跑通时Redis不是必须。为了减少环境依赖密钥和配置可以通过application.yml统一管理。推荐环境清单工具版本要求用途JDK17及以上编译运行SpringBoot3Maven3.8以上依赖管理MySQL8.x数据存储Node.js18及以上运行Vite和Vue3npm/pnpmnpm 9或pnpm 8前端依赖安装Ollama最新稳定版本地大模型推理安装Ollama后需要提前拉取模型。比如执行ollama pull qwen2.5:7b拉取成功后执行ollama run qwen2.5:7b测试一次确认服务端口11434可以访问。2.2 前端环境要求前端使用Vite作为构建工具Vue3配合TypeScript。创建项目时直接使用Vite官方脚手架npm create vitelatest campus-web -- --template vue-ts cd campus-web npm install然后安装项目依赖npm install element-plus element-plus/icons-vue pinia vue-router axiosVite脚手架默认生成的目录结构已经足够后续需要补建src/api、src/router、src/stores、src/utils等目录分别放接口请求、路由配置、全局状态、工具函数。2.3 初始化SpringBoot3后端工程可以使用Spring Initializr生成基础工程也可以直接创建Maven项目。后端工程建议使用campus-server作为根目录包名使用com.campus.admin这样可以避免与前端目录混淆。pom.xml核心依赖如下parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.5/version relativePath/ /parent properties java.version17/java.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-security/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version3.5.7/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdcn.hutool/groupId artifactIdhutool-jwt/artifactId version5.8.25/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies注意MyBatis-Plus从3.5.3版本开始才提供mybatis-plus-spring-boot3-starter如果使用旧版本会出现自动配置类加载失败。版本号需要根据实际仓库和SpringBoot版本确认。2.4 配置application.yml在src/main/resources/application.yml中配置数据源、MyBatis-Plus、JWT密钥。server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/campus_platform?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: root ai: ollama: base-url: http://localhost:11434 chat: model: qwen2.5:7b mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 custom: jwt: secret: campus-platform-secret-key-please-change-32bytecustom.jwt.secret是JWT签名密钥生产环境不要写死在代码中建议通过环境变量注入。示例中的密钥长度已经满足HS256算法要求此处仅用于演示。3. 数据库设计与初始化脚本3.1 数据库设计原则高校/园区一体化平台涉及多类实体先从最小闭环开始用户、角色、通知公告、园区设备、AI对话记录。用户表保存登录账号和基本信息通知公告表记录发布人和内容设备表记录园区设备状态AI对话记录表用于存储用户提问和AI返回结果方便后续分析和审计。主键统一使用自增ID创建时间使用datetime类型状态字段用tinyint。数据库名选择campus_platform字符集选择utf8mb4避免中文和Emoji存储问题。3.2 核心表结构用户表CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录账号, password VARCHAR(100) NOT NULL COMMENT BCrypt加密密码, real_name VARCHAR(50) COMMENT 姓名, role VARCHAR(20) DEFAULT USER COMMENT 角色ADMIN/OPERATOR/USER, status TINYINT DEFAULT 1 COMMENT 状态1启用 0停用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) COMMENT 用户表;通知公告表CREATE TABLE sys_notice ( id BIGINT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL, content TEXT, publisher VARCHAR(50), top_flag TINYINT DEFAULT 0 COMMENT 是否置顶, status TINYINT DEFAULT 1 COMMENT 1发布 0下线, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT 通知公告表;园区设备表CREATE TABLE campus_device ( id BIGINT PRIMARY KEY AUTO_INCREMENT, device_name VARCHAR(100) NOT NULL, location VARCHAR(100) COMMENT 安装位置, device_type VARCHAR(50) COMMENT 设备类型摄像头/门禁/水电表等, status TINYINT DEFAULT 1 COMMENT 1正常 2告警 3离线, remark VARCHAR(255), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT 园区设备表;AI对话记录表CREATE TABLE ai_chat_history ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, user_message TEXT NOT NULL, ai_message TEXT, model_name VARCHAR(50), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT AI对话记录表;3.3 初始化数据插入默认管理员账号。密码必须是BCrypt加密后的值不能存明文。示例中密码123456的BCrypt值可以直接在登录接口中生成但表中需要提前插入一条记录INSERT INTO sys_user (username, password, real_name, role) VALUES (admin, $2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2, 系统管理员, ADMIN); INSERT INTO sys_notice (title, content, publisher) VALUES (平台正式试运行, 智慧管理平台开始试运行欢迎提交反馈。, 管理员); INSERT INTO campus_device (device_name, location, device_type, status) VALUES (东门摄像头, 东门入口, 摄像头, 1);如果不想手工计算BCrypt值可以在启动类中写入一个临时CommandLineRunner生成也可以使用在线工具计算后再插入。这里给出的值只用于演示实际项目必须重新生成。3.4 实体类与Mapper示例实体类使用Lombok简化getter/setter。以用户实体为例Data TableName(sys_user) public class User { TableId(type IdType.AUTO) private Long id; private String username; private String password; private String realName; private String role; private Integer status; private LocalDateTime createTime; private LocalDateTime updateTime; }Mapper接口继承MyBatis-Plus的BaseMapperpublic interface UserMapper extends BaseMapperUser { }Service中用LambdaQueryWrapper查询用户public User findByUsername(String username) { return userMapper.selectOne( new LambdaQueryWrapperUser() .eq(User::getUsername, username) .eq(User::getStatus, 1) ); }这种写法的好处是类型安全表字段变更时编译阶段更容易发现错误比直接拼字符串SQL更稳。4. 后端核心实现认证、业务接口与AI模型接入4.1 统一响应结构所有接口返回统一结构前端拦截器可以统一处理错误码。这里定义ResultTData public class ResultT { private Integer code; private String msg; private T data; public static T ResultT ok(T data) { ResultT r new Result(); r.code 200; r.msg success; r.data data; return r; } public static T ResultT error(Integer code, String msg) { ResultT r new Result(); r.code code; r.msg msg; return r; } }同时添加全局异常处理器把业务异常和未知异常转换成统一结构输出到前端避免出现堆栈信息直接暴露在页面上。4.2 Spring Security与JWT认证认证设计采用无状态JWT。用户登录成功后后端签发Token前端每次请求在Authorization头带上Bearer token后端过滤器校验Token并写入安全上下文。登录接口RestController RequestMapping(/api/auth) public class AuthController { private final AuthenticationManager authenticationManager; private final UserService userService; public AuthController(AuthenticationManager authenticationManager, UserService userService) { this.authenticationManager authenticationManager; this.userService userService; } PostMapping(/login) public ResultMapString, Object login(RequestBody LoginRequest request) { Authentication authentication authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()) ); User user (User) authentication.getPrincipal(); String token JWTUtil.createToken( Map.of(userId, user.getId(), username, user.getUsername()), jwtSecret.getBytes() ); MapString, Object data new HashMap(); data.put(token, token); data.put(username, user.getUsername()); data.put(realName, user.getRealName()); data.put(role, user.getRole()); return Result.ok(data); } }自定义过滤器解析JWTComponent public class JwtAuthenticationFilter extends OncePerRequestFilter { Value(${custom.jwt.secret}) private String secret; Autowired private UserService userService; Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String header request.getHeader(Authorization); if (header ! null header.startsWith(Bearer )) { String token header.substring(7); try { JWT jwt JWTUtil.parseToken(token); String username (String) jwt.getPayload(username); User user userService.findByUsername(username); if (user ! null) { UsernamePasswordAuthenticationToken authentication new UsernamePasswordAuthenticationToken(user, null, List.of(new SimpleGrantedAuthority(ROLE_ user.getRole()))); SecurityContextHolder.getContext().setAuthentication(authentication); } } catch (Exception ignored) { // Token无效或过期保持匿名状态 } } filterChain.doFilter(request, response); } }Security配置中放行登录接口其余接口需要认证Configuration EnableWebSecurity public class SecurityConfig { Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } Bean public SecurityFilterChain filterChain(HttpSecurity http, JwtAuthenticationFilter jwtFilter) throws Exception { http.csrf(AbstractHttpConfigurer::disable) .sessionManagement(session - session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth - auth .requestMatchers(/api/auth/login).permitAll() .requestMatchers(/error).permitAll() .anyRequest().authenticated() ) .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } }这里有几个关键点要注意第一Spring Security 6默认会把无权限请求重定向到登录页前后端分离项目中需要自定义401响应否则前端拿到的不是JSON。可以添加AuthenticationEntryPoint实现。第二JWT密钥必须足够长HS256要求至少32字节。第三过滤器不能把异常吞掉后继续放行否则会以匿名身份访问受保护接口最终返回401但日志不够明确。4.3 Spring AI与Ollama本地模型接入引入Spring AI需要处理版本兼容问题而且Spring AI的模块迭代较快。这里直接通过RestClient调用Ollama的OpenAI兼容接口依赖更少原理更透明。Service public class AiAssistantService { private static final String OLLAMA_BASE_URL http://localhost:11434; private static final String MODEL_NAME qwen2.5:7b; private final RestClient restClient; public AiAssistantService(RestClient.Builder builder) { this.restClient builder.baseUrl(OLLAMA_BASE_URL).build(); } public String chat(String userMessage) { MapString, Object body new HashMap(); body.put(model, MODEL_NAME); body.put(messages, List.of( Map.of(role, system, content, 你是校园智能助手请简洁、准确地回答问题。), Map.of(role, user, content, userMessage) )); body.put(stream, false); Map?, ? response restClient.post() .uri(/v1/chat/completions) .contentType(MediaType.APPLICATION_JSON) .body(body) .retrieve() .body(Map.class); if (response null || !response.containsKey(choices)) { throw new RuntimeException(AI模型返回异常); } List? choices (List?) response.get(choices); Map?, ? choice (Map?, ?) choices.get(0); Map?, ? message (Map?, ?) choice.get(message); return (String) message.get(content); } }Controller层把AI服务封装成平台接口RestController RequestMapping(/api/ai) public class AiController { private final AiAssistantService aiAssistantService; public AiController(AiAssistantService aiAssistantService) { this.aiAssistantService aiAssistantService; } PostMapping(/chat) public ResultString chat(RequestBody ChatRequest request) { String answer aiAssistantService.chat(request.getMessage()); return Result.ok(answer); } }实际项目中不建议把每次提问都直接透传给大模型应该在Service层增加提示词模板、敏感词过滤、调用频率限制和对话历史管理。生产环境还要考虑超时时间、重试策略和模型负载。4.4 园区设备管理接口设备管理是园区模块的基础接口提供分页查询和状态维护。RestController RequestMapping(/api/device) public class DeviceController { GetMapping(/list) public ResultIPageCampusDevice list(RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size) { PageCampusDevice pageParam new Page(page, size); IPageCampusDevice result deviceService.page(pageParam); return Result.ok(result); } PutMapping(/status/{id}) public ResultVoid updateStatus(PathVariable Long id, RequestParam Integer status) { CampusDevice device new CampusDevice(); device.setId(id); device.setStatus(status); deviceService.updateById(device); return Result.ok(null); } }分页查询是后台管理系统的通用需求。前端传当前页和每页条数后端返回包含records、total、current、size的IPage对象前端直接用表格组件渲染。5. Vue3前端核心实现登录、布局、AI助手5.1 项目结构与路由前端工程建议按功能模块组织campus-web/ ├── src/ │ ├── api/ │ │ ├── http.ts │ │ ├── auth.ts │ │ ├── notice.ts │ │ └── ai.ts │ ├── router/ │ │ └── index.ts │ ├── stores/ │ │ └── user.ts │ ├── views/ │ │ ├── LoginView.vue │ │ ├── DashboardView.vue │ │ ├── DeviceView.vue │ │ └── AiChatView.vue │ ├── layout/ │ │ └── MainLayout.vue │ ├── App.vue │ └── main.ts路由使用createRouter和createWebHistory并配置登录页和主布局。import { createRouter, createWebHistory } from vue-router const router createRouter({ history: createWebHistory(), routes: [ { path: /login, component: () import(../views/LoginView.vue) }, { path: /, component: () import(../layout/MainLayout.vue), children: [ { path: , redirect: /dashboard }, { path: dashboard, component: () import(../views/DashboardView.vue) }, { path: device, component: () import(../views/DeviceView.vue) }, { path: ai, component: () import(../views/AiChatView.vue) } ] } ] })Vue3官方推荐使用createWebHistory但生产环境部署到Nginx时需要配置try_files否则刷新子路由会404。如果暂时不想处理可以改用createWebHashHistory跑通本地演示。5.2 axios请求封装前端所有请求走统一封装的http实例。这样每个请求不用重复写token和错误处理。import axios from axios import { ElMessage } from element-plus const http axios.create({ baseURL: /api, timeout: 15000 }) http.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) http.interceptors.response.use( response { const res response.data if (res.code 200) { return res } ElMessage.error(res.msg || 请求失败) return Promise.reject(new Error(res.msg || 请求失败)) }, error { if (error.response?.status 401) { localStorage.removeItem(token) localStorage.removeItem(userInfo) window.location.href /login } ElMessage.error(error.response?.data?.msg || 网络异常) return Promise.reject(error) } ) export default http这里返回的是整个Result对象而不是response.data.data。所以在页面中调用const res await http.get(/notice/list)之后需要取res.data才是业务数据。这样设计可以统一处理业务码和HTTP状态码的差异。5.3 登录页面与全局状态管理登录页面使用Element Plus表单组件template el-form :modelform label-width80px el-form-item label账号 el-input v-modelform.username / /el-form-item el-form-item label密码 el-input v-modelform.password typepassword show-password / /el-form-item el-button typeprimary clickhandleLogin登录/el-button /el-form /template script setup langts import { reactive } from vue import { useRouter } from vue-router import http from ../api/http import { ElMessage } from element-plus const router useRouter() const form reactive({ username: admin, password: 123456 }) async function handleLogin() { const res await http.post(/auth/login, form) localStorage.setItem(token, res.data.token) localStorage.setItem(userInfo, JSON.stringify(res.data)) ElMessage.success(登录成功) router.push(/dashboard) } /script登录成功后把token存入localStorage刷新页面时通过路由守卫判断是否存在token。更安全的方式是把token放入内存或使用Pinia持久化插件但本地存储对演示项目足够。路由守卫router.beforeEach((to) { const token localStorage.getItem(token) if (!token to.path ! /login) { return /login } return true })5.4 AI助手组件AI组件是“带AI”的关键体现。页面用一个消息数组保存对话用户输入问题后调用后端接口再把返回内容追加到消息列表中。script setup langts import { ref } from vue import http from ../api/http const messages refArray{ role: string; content: string }([]) const input ref() const loading ref(false) async function send() { const text input.value.trim() if (!text || loading.value) return messages.value.push({ role: user, content: text }) input.value loading.value true try { const res await http.post(/ai/chat, { message: text }) messages.value.push({ role: assistant, content: res.data }) } finally { loading.value false } } /script这里要注意AI请求是同步等待模式模型推理在本地需要几秒到几十秒前端要显示加载状态。如果请求时间很长可以后续改成SSE流式输出让AI回复逐字显示。5.5 仪表盘与设备管理页面仪表盘用于展示平台核心指标。可以调用通知接口显示最新公告再通过卡片展示设备总数、正常设备和告警设备数量。设备管理页面使用el-table展示分页数据提供状态切换按钮。el-table :datatableData el-table-column propdeviceName label设备名称 / el-table-column proplocation label位置 / el-table-column propdeviceType label类型 / el-table-column label状态 template #default{ row } el-tag :typestatusTag(row.status){{ statusText(row.status) }}/el-tag /template /el-table-column /el-table页面不需要写得非常复杂关键是能体现前后端数据联调已经跑通。后续扩展用户管理、工单、报表时复用这套列表和表单模式即可。6. 联调运行与验证6.1 启动顺序建议按以下顺序启动启动MySQL创建数据库并执行初始化脚本。启动Ollama执行ollama run qwen2.5:7b。启动后端在campus-server目录执行mvn spring-boot:run。启动前端在campus-web目录执行npm run dev。如果前后端不在同一台机器需要修改前端代理目标地址和后端Ollama地址。Ollama默认监听127.0.0.1:11434如果后端部署在远程服务器需要让Ollama监听0.0.0.0并配置防火墙白名单仅允许可信内网访问。6.2 接口测试使用Apifox或Postman调用登录接口POST http://localhost:8080/api/auth/login Content-Type: application/json { username: admin, password: 123456 }预期响应{ code: 200, msg: success, data: { token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..., realName: 系统管理员, role: ADMIN } }拿到token后调用设备列表接口GET http://localhost:8080/api/device/list?page1size10 Authorization: Bearer token返回值中应该包含初始化时插入的“东门摄像头”。如果没有数据优先检查数据库初始化脚本是否执行成功。6.3 前端页面验证浏览器访问http://localhost:5173未登录时访问/dashboard应自动跳转到/login。登录成功后进入仪表盘左侧菜单切换到设备管理能正常显示设备列表。再切换到AI助手页面提问“介绍一下校历安排查询流程”后端调用本地模型后返回结果。如果AI接口长时间无响应可以先在浏览器开发者工具Network中看请求状态码再到后端控制台查看异常日志。Ollama模型第一次加载需要较长冷启动时间第二次提问会明显变快。7. 常见问题与排查路径7.1 后端启动失败或连接数据库报错现象启动时提示Access denied for user rootlocalhost或Communications link failure。检查顺序检查项命令或位置处理方式MySQL服务systemctl status mysql启动MySQL用户名密码application.yml中的datasource确认密码正确数据库名称show databases;创建campus_platform时区参数url中的serverTimezone使用Asia/Shanghai预防建议数据库连接串统一使用utf8mb4避免中文乱码本地和测试环境使用不同配置不要改代码。7.2 前端启动报错init_runtime_dom_esm_bundler is not define这个错误常见于Vue生产包和开发包引用混乱或者vitejs/plugin-vue版本与Vue版本不匹配。出现该错误时先看是哪个文件抛出的。如果是main.ts或App.vue抛出大概率是依赖版本冲突。处理步骤rm -rf node_modules package-lock.json npm install npm run dev如果仍然报错检查package.json中Vue是否为3.xvitejs/plugin-vue是否为当前Vite版本兼容的最新版本。可以把Vite升级到5.x同时更新插件。7.3 接口跨域错误前端直接请求http://localhost:8080时会出现CORS错误。本地开发推荐配置Vite代理在vite.config.ts中设置import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })这样前端请求/api/auth/login会被代理到http://localhost:8080/api/auth/login没有跨域问题。如果前后端部署在不同域名则需要在后端增加CORS配置但注意不要使用allowCredentials(true)同时允许所有来源。7.4 登录成功但请求接口返回401可能原因有前端没有在请求头带token。后端Security没有放行登录接口但登录接口实际能访问说明过滤器没问题。JWT解析失败密钥不一致。用户状态被置为停用导致查询不到用户。排查时先看后端日志中是否打印JwtAuthenticationFilter抛出的异常。当前示例代码会静默忽略异常排查不便。建议在开发环境把异常信息打印到日志在正式环境再根据安全策略决定是否隐藏。7.5 AI模型连接失败如果调用AI接口后返回500先确认Ollama服务状态curl http://127.0.0.1:11434/v1/chat/completions \ -H Content-Type: application/json \ -d {model:qwen2.5:7b,messages:[{role:user,content:你好}],stream:false}如果curl能正常返回问题在后端RestClient配置如果curl不能返回问题在Ollama服务和模型名称。要注意模型名称必须与ollama list显示的名称完全一致包括标签。8. 生产部署、毕业论文与搭建视频交付建议8.1 学习环境与生产环境差异本地跑通后离上线还有一段距离。生产环境至少需要增加以下能力能力本地演示生产环境配置管理application.yml写死环境变量或配置中心密码安全默认密钥独立密钥并定期轮换日志控制台输出结构化日志集中采集监控无Prometheus Grafana备份无定期数据库备份部署IDE或命令行Docker Compose/K8s模型调用本地Ollama容器化部署GPU集群毕业论文中要体现这些差异不能只写“系统可以正常访问”还要做性能测试和安全性分析。8.2 毕业论文如何组织毕业设计论文建议围绕“问题-设计-实现-验证”主线展开常见章节如下绪论背景、国内外现状、研究内容。相关技术介绍SpringBoot3、Vue3、AI大模型、MySQL。需求分析角色分析、功能需求、非功能需求。系统设计总体架构、功能模块、数据库设计、接口设计。系统实现按认证模块、通知模块、设备模块、AI模块分别说明。系统测试测试环境、功能测试用例、性能测试结果。总结与展望。写论文时不要把整段代码贴进去而要用代码片段说明关键算法和实现思路。每张表都要有字段说明每个接口都要有请求和响应示例。8.3 指导搭建视频如何组织搭建视频建议按“演示效果→环境准备→数据库→后端→前端→联调→常见问题”的顺序录制每集5到10分钟方便学习者按需回看。第一集展示最终效果让读者知道要做什么。第二集安装JDK、Maven、MySQL、Node、Ollama。第三集执行数据库脚本。第四集启动后端并验证登录接口。第五集启动前端并完成页面联调。这样视频和博客内容配合可以形成完整交付物。8.4 推荐的最佳实践清单发布和提交前可以按这个清单自查[ ] 数据库脚本能否在空库上完整执行。[ ] 后端启动后没有红色堆栈日志。[ ] 前端npm run build能成功构建。[ ] 登录、通知列表、设备列表、AI问答四个核心链路都已验证。[ ] JWT密钥没有硬编码在Git仓库中。[ ] Ollama模型列表和代码中的模型名一致。[ ] 接口错误统一返回Result结构没有裸堆栈。[ ] 毕业论文中的表结构和代码与项目实际版本一致。[ ] 搭建视频中的界面和最终代码一致。高校/园区一体化平台的重点不在技术堆砌而是把认证、业务、AI三条链路打通。基于SpringBoot3和Vue3先完成一个最小可运行系统再按模块逐步扩展是投入产出比最高的路线。对于毕业设计优先把登录认证、数据列表、AI助手三大主链路做扎实再去丰富业务模块和优化交互体验这样既容易答辩也方便后期继续演化成真正的生产系统。