Xuan's blog - springboot
type: channel
link: https://blog.ybyq.wang/tag/springboot/
items: 宝塔面板部署 Springboot+Vue+Sqlite 前后端项目教程
items: 基于Spring Boot + Vue.js的GPT AI聊天应用：从开发到部署的完整实践
items: 软件工程实践八：Web 前端项目实战（SSE、Axios 与代理）
items: 软件工程实践七：AI 大模型 API 合并与适配层设计
items: 软件工程实践六：阿里大模型（通义千问）Java 对接最简方案
items: 软件工程实践五：Spring Boot 接口拦截与 API 监控、流量控制
items: 软件工程实践四：MyBatis-Plus 教程（连接、分页、查询）
items: 软件工程实践三：RESTful API 设计原则
items: 软件工程实践二：Spring Boot 知识回顾
items: Springboot实现后台图片上传

宝塔面板部署 Springboot+Vue+Sqlite 前后端项目教程
type: item
date: 2026-01-12T19:02:00+08:00
description: 前言本文记录一个宠物商店后台管理系统项目在宝塔面板上的部署流程，前端使用 Vue，后端为 Spring Boot。下文按“先部署后端，再部署前端”的顺序来说明。这篇教程是半年前部署一个零工平台前后端项目时写的，写了一半停了，最近又部署了一个顺便把它完善一下。项目概述前端：vue 项目后端：Spring Boot 3.3.5 + Java 17 + SQLite 数据库域名：https://petad.xuan.cyou后端端口：8083前端项目部署前的设置配置说明：API 接口路径与环境设置一般项目支持两种常见的接口路径配置方式，请根据后端接口的实际路径选择对应的方案。场景一：接口已统一包含 /api 前缀（本项目选择）若后端所有接口已统一添加 /api 前缀（例如：/api/product/list），则前端环境变量配置如下：# .env.development VITE_APP_BASE_API=http://localhost:8083 # .env.production VITE_APP_BASE_API=https://petad.xuan.cyou在 axios 实例中直接使用该变量作为 baseURL：// src/api/request.js 片段 const service = axios.create({ baseURL: import.meta.env.VITE_APP_BASE_API || 'http://localhost:8083', timeout: 10000 })各接口定义中需添加 /api 前缀：// 示例接口：获取商品列表 export function getProductList(params) { return request({ url: '/api/product/list', // 添加/api前缀 method: 'get', params }) }场景二：接口未统一添加 /api 前缀若后端接口未统一添加 /api 前缀，则可将前缀直接配置在环境变量中：# .env.development VITE_APP_BASE_API=http://localhost:8083/api # .env.production VITE_APP_BASE_API=https://petad.xuan.cyou/api在 axios 配置中直接使用完整基础路径：// src/api/request.js 片段 const service = axios.create({ baseURL: import.meta.env.VITE_APP_BASE_API || 'http://localhost:8083/api', timeout: 10000 })此时，接口定义中不再需要 /api 前缀：// 示例接口：获取商品列表 export function getProductList(params) { return request({ url: '/product/list', // 无需/api前缀 method: 'get', params }) }通用配置：开发环境代理无论选择上述哪种场景，在开发环境下均需在 vite.config.js 中配置代理，以便正确转发请求至后端服务：// vite.config.js 片段 server: { proxy: { '/api': { target: 'http://localhost:8083', // 后端服务地址 changeOrigin: true }, '/images': { target: 'http://localhost:8083', changeOrigin: true } } }补充说明：路由模式与刷新 404 问题如果项目使用 Vue Router 的 history 模式，部署后刷新非首页路由可能出现 404 错误。此时需在 Web 服务器（如 Nginx）中配置伪静态规则，将请求重定向至 index.html。以 Nginx 为例，可添加如下配置：location / { try_files $uri $uri/ /index.html; }云服务器准备1.选购我选用的是阿里云的云服务器，如果只是临时部署这个项目作为演示，选择最低配的就可以。镜像直接选择宝塔的应用镜像就行如果有别的需求，尽量选择Debian，Centos Stream，Ubuntu等如果选择的是上面三个，你需要手动配置宝塔面板并开放访问端口2.查看面板登录信息宝塔配置1. 登录并进入面板2. 创建一个静态网站我用的是 SQLite，这里无需新创建数据库。申请免费SSL证书，启用https3. 上传项目前端代码将前端打包后的 dist 目录上传到这里…

基于Spring Boot + Vue.js的GPT AI聊天应用：从开发到部署的完整实践
type: item
date: 2025-09-23T21:57:00+08:00
description: 前言一个人一个下午做成的，为了应对周四的作业答辩。🚀 项目概述本文将详细介绍一个基于Spring Boot后端和Vue.js前端的GPT AI聊天应用的完整开发流程。该项目集成了OpenAI、阿里云通义千问、DeepSeek、豆包、Kimi等多个主流AI平台，支持文本对话和图像生成功能，并实现了用户认证、会话管理、管理员面板、用户统计等完整的业务功能。体验地址：https://gpt.xuan.cyou/技术栈后端: Spring Boot 3.5.5, Java 17, SQLite, JPA/Hibernate前端: Vue.js 3, Vite, Pinia, JavaScript ES6+AI集成: OpenAI GPT-4, 阿里云通义千问, DeepSeek, 豆包, Kimi部署: 宝塔面板, Nginx, SSL证书📁 项目架构GPT-AI-Project/ ├── gpt-ai-java/ # Spring Boot 后端 │ ├── src/main/java/ │ │ ├── controller/ # REST API控制器 (用户, AI, 管理员等) │ │ ├── service/ # 业务逻辑层 (多AI平台服务, 用户统计等) │ │ ├── entity/ # 数据实体 (用户, 对话历史, 统计数据等) │ │ ├── config/ # 配置类 │ │ └── adopter/ # AI平台适配器 (OpenAI, Ali, DeepSeek, Kimi等) │ ├── src/main/resources/ │ │ └── application.yaml # 应用配置 │ └── pom.xml # Maven依赖配置 └── frontend/ # Vue.js 前端 ├── src/ │ ├── components/ # Vue组件 │ ├── views/ # 页面视图 (聊天, 图像, 管理面板, 用户后台等) │ ├── api/ # API调用封装 │ └── store/ # 状态管理 (Pinia) ├── public/ # 静态资源 └── package.json # NPM依赖配置🔧 核心功能实现1. 后端核心架构1.1 多AI平台集成项目支持OpenAI和阿里云通义千问两个AI平台，通过适配器模式实现：// AI控制器统一入口 @RestController @RequestMapping("/api/ai") public class AiController { @GetMapping("/chat/text/stream") public SseEmitter streamChat( @RequestParam String platform, @RequestParam String model, @RequestParam String message ) { // 根据平台选择对应的AI服务 (示例) if ("openai".equals(platform)) { return openAiController.sendTextStream(model, message); } else if ("ali".equals(platform)) { return aliAiController.sendTextStream(model, message); } // ... 此处添加对 deepseek, doubao, kimi 等平台的路由逻辑 return null; // or throw an exception } }1.2 流式响应实现使用Server-Sent Events (SSE) 实现实时流式对话：@PostMapping("/chat/text/stream") public SseEmitter sendTextStream(String model, String message) { SseEmitter emitter = new SseEmitter(0L); CompletableFuture.runAsync(() -> { try { // 流式读取AI响应并实时推送 openAIService.sendText(model, message, true, delta -> { emitter.send(SseEmitter.event().name("delta").data(delta)); }); emitter.send(SseEmitter.event().name("done").data("[DONE]")); emitter.complete(); } catch (Exception e…

软件工程实践八：Web 前端项目实战（SSE、Axios 与代理）
type: item
date: 2025-09-19T14:48:42+08:00
description: 适合人群：具备 Vue 基础，希望接入 AI 文本/图片与流式输出的开发者或技术爱好者。建议先看：实践三（RESTful API 设计原则）、实践五（接口监控与限流）。你将收获：SSE 两种实现、Axios 标准化封装、vite 代理配置与常见坑。创建项目npm create vue@latest实现文本聊天页面目标：实现基础的文本输入、发送与消息列表展示。要点：输入框与发送按钮、消息时间顺序、滚动定位到最新消息。组件建议：ChatInput（输入区）、ChatList（消息区）。实现图片目标：支持根据文本生成图片并展示缩略图。要点：提交参数（prompt、size、数量）、生成态 loading、失败重试、下载按钮。组件建议：ImageGenerateForm、ImageGrid。实现文本聊天SSE的方式sse 前端实现方式第一种 EventSourceconst url = "/api/ai/chat/stream?platform=openai&model=gpt-4o&message=" + encodeURIComponent(message); const sse = new EventSource(url); const handleDelta = (event: MessageEvent) => { const data = (event as MessageEvent).data; }; const handleDone = () => { sse.close(); }; // Some servers don't set a named event; treat default messages as delta sse.onmessage = handleDelta; sse.addEventListener("delta", handleDelta as EventListener); sse.addEventListener("done", handleDone as EventListener); sse.onerror = (err) => { console.error("SSE error", err); sse.close(); };第二种 使用自带的fetch const res = await fetch("/api/ai/chat/stream", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ platform: "openai", model: "gpt-4o", message }), }); if (!res.ok || !res.body) throw new Error(`请求失败: ${res.status}`); const reader = res.body.getReader(); const decoder = new TextDecoder("utf-8"); let buffer = ""; while (true) { const { value, done } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); // Try to parse as SSE events: data: {json}\n\n let sepIndex: number; while ((sepIndex = buffer.indexOf("\n\n")) !== -1) { const rawEvent = buffer.slice(0, sepIndex); buffer = buffer.slice(sepIndex + 2); const dataLines = rawEvent .split("\n") .filter((l) => l.startsWith("data:")) .map((l) => l.replace(/^data:\s?/, "")) .join(""); console.log("dataLines", dataLines); if (!dataLines) continue; consumeChunk(dataLines, assistantId); } // Fallback: handle NDJSON lines if server doesn't use blank-line separators // let lineIndex: number; // while ((lin…

软件工程实践七：AI 大模型 API 合并与适配层设计
type: item
date: 2025-09-19T14:36:25+08:00
description: 适合人群：希望统一多厂商（OpenAI、阿里、科大讯飞等）调用体验的后端开发者或技术爱好者。建议先看：实践三（RESTful API 设计原则）、实践六（阿里大模型对接）。你将收获：一套可扩展的适配层模型与返回体规范，最小变更对接新厂商。设计目标与边界统一接口：隐藏厂商差异，暴露稳定域模型（模型、聊天、流式聊天、图片）。易于扩展：新增厂商仅需实现适配器，不影响上层调用与返回结构。一致的错误语义：统一 success/code/message，流式以 [process]/[done] 信号闭环。安全与合规：不在代码中硬编码密钥，敏感信息通过环境变量或配置中心注入。对输入参数进行统一几乎不改返回值统一模型@Data public class BigModel { private String id; public BigModel(String id) { this.id = id; } public BigModel() {} } 文字聊天@Data public class ChatMessage { private String id; private String content; private String status; // success fail private String error; }文字聊天流@Data public class ChatMessageStream { private String id; private String content; private String status; // done process }图片@Data public class ChatImage { private String id; private String imageUrl; private String status; // success fail private String error; }流程合并使用统一接口OpenAI@Service public class OpenAiAdopter { @Autowired private OpenAiService openAiService; public List<BigModel> getModels() { Map model = openAiService.getModel(); List<Map> models = (List<Map>) model.get("data"); return models.stream().map(m -> new BigModel(m.get("id").toString())).collect(Collectors.toList()); } public ChatMessage chat(String model, String message) { Map chat = openAiService.chat(model, message); String id = chat.get("id").toString(); List<Map> output = (List<Map>) chat.get("output"); ChatMessage chatMessage = new ChatMessage(); chatMessage.setId(id); String contentStr = ""; chatMessage.setStatus("completed"); for (Map map : output) { if (!map.get("type").toString().equals("message")) { continue; } if (!map.get("status").toString().equals("completed")) { continue; } List<Map> contents = (List<Map>) map.get("content"); for (Map content : contents) { contentStr += content.get("text").toString(); } } chatMessage.setContent(contentStr); return chatMessage; } public void chatStream(String model, String message, Consumer<ChatMessageStream> onDelta){ openAiService.chatStream(model, message, (delta)->{ ChatMessageStream chatMessag…

软件工程实践六：阿里大模型（通义千问）Java 对接最简方案
type: item
date: 2025-09-18T15:16:59+08:00
description: 适用场景：快速在 Java 本地/服务端调用通义千问系列模型进行对话生成。适合人群：熟悉 Spring Boot/RestTemplate 的 Java 开发者或技术爱好者。建议先看：实践三（RESTful API 设计原则）。你将收获：最小可运行的文本对话/流式对话/图片生成调用，含代理与安全要点。1. 准备工作JDK: 8+（推荐 17+）构建工具: MavenAPI Key: 在阿里云百炼/灵积控制台创建 API-KEY百炼控制台：通义千问-文生图模型（Qwen-Image）创建RestTemplate（安全与代理）@Bean("aliAiHttpClient") public RestTemplate aliRestTemplate(RestTemplateBuilder builder) { ClientHttpRequestInterceptor authInterceptor = (request, body, execution) -> { request.getHeaders().setBearerAuth(System.getenv("ALI_API_KEY")); return execution.execute(request, body); }; RestTemplate restTemplate = builder .connectTimeout(Duration.ofSeconds(10)) .readTimeout(Duration.ofSeconds(60)) .additionalInterceptors(authInterceptor) .build(); SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); // 设置代理（以 http://127.0.0.1:7890 为例） Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 7890)); factory.setProxy(proxy); restTemplate.setRequestFactory(factory); return restTemplate; }获取模型public List getModel() { List<Map<String, String>> modelList = new ArrayList<>(); Map<String, String> model = new HashMap<>(); model.put("id", "qwen-plus"); modelList.add(model); model = new HashMap<>(); model.put("id", "qwen-image"); modelList.add(model); return modelList; }文字聊天public Map chat(String model, String message) throws JsonProcessingException { HttpHeaders headers = new HttpHeaders(); Map param = new HashMap(); param.put("model", model); Map messageMap = new HashMap(); messageMap.put("role", "user"); messageMap.put("content", message); param.put("messages", List.of(messageMap)); HttpEntity<Map> entity = new HttpEntity<>(param, headers); ResponseEntity<String> exchange = restTemplate.exchange("https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions", HttpMethod.POST, entity, String.class); return objectMapper.readValue(exchange.getBody(), Map.class); }文字聊天 SSE public void chatStream(String model, String message, Consumer<String> onDelta) throws JsonProcessingExcep…

软件工程实践五：Spring Boot 接口拦截与 API 监控、流量控制
type: item
date: 2025-09-18T14:55:00+08:00
description: 适合人群：具备 Spring Web 基础、希望在接口层做统一治理的开发者或技术爱好者。建议先看：软件工程实践二（Spring Boot 知识回顾）、实践三（RESTful API 设计原则）。1. 方案选型速览Filter（Servlet 层）：最靠前，处理包括静态资源；适合全局跨域、XSS、统一编码等。HandlerInterceptor（Spring MVC 层）：在进入 Controller 前后执行；适合接口打点、鉴权、限流、审计。AOP（方法层）：对注解/切点进行横切；适合注解式限流、审计、幂等等。API Gateway（边界层）：最靠外；适合全局流控、黑白名单、熔断、灰度，一处管多服务。一般应用内优先用 Interceptor + AOP；如果有网关，也可在网关做粗粒度限流，在应用内做精细化限流与监控。国内比较熟悉的：Sentinel2. 基于 HandlerInterceptor 的接口监控目标：记录每次请求的关键信息与耗时，并输出到日志或指标系统。示例：// RequestLoggingInterceptor.java // 功能：为每个进入的 HTTP 请求生成请求ID、记录入站/出站日志与耗时 package com.example.demo.web; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import java.util.UUID; public class RequestLoggingInterceptor implements HandlerInterceptor { private static final Logger log = LoggerFactory.getLogger(RequestLoggingInterceptor.class); private static final String REQ_ID = "reqId"; // MDC 中的请求 ID 键 private static final String START = "startAt"; // 请求开始时间属性键 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String requestId = UUID.randomUUID().toString(); // 生成请求唯一 ID，便于链路追踪 MDC.put(REQ_ID, requestId); // 放入日志 MDC request.setAttribute(START, System.currentTimeMillis()); // 记录起始时间 String method = request.getMethod(); String uri = request.getRequestURI(); String ip = getClientIp(request); // 识别真实客户端 IP（支持代理场景） log.info("[IN] {} {} ip={} ua={}", method, uri, ip, request.getHeader("User-Agent")); return true; // 返回 true 放行请求 } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { // 可在此处添加对响应体或模型的额外处理 } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { Long start = (Long) request.getAtt…

软件工程实践四：MyBatis-Plus 教程（连接、分页、查询）
type: item
date: 2025-09-11T12:41:00+08:00
description: 概述目标：用最少代码完成单表 CRUD、分页查询，并给出 JOIN 查询的常见做法。环境：Spring Boot 3.x + MyBatis-Plus 3.5+ + MySQL 8.x。1. 依赖与连接配置1.1 Maven 依赖（片段）<dependencies> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> <version>3.5.12</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>8.0.33</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>1.2 application.yml 连接spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC username: root password: root mybatis-plus: configuration: map-underscore-to-camel-case: true mapper-locations: classpath*:mapper/**/*.xml1.3 启动类与包扫描@SpringBootApplication @MapperScan("com.example.demo.mapper") // 若在接口上使用 @Mapper，可不加 @MapperScan public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }1.4 分页插件配置@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }2. 示例模型与 Mapper2.1 表结构（示例）CREATE TABLE `t_user` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `name` VARCHAR(64) NOT NULL, `age` INT NOT NULL, `email` VARCHAR(128), `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP );2.2 实体 Entity@Data @TableName("t_user") public class User { @TableId(type = IdType.AUTO) private Long id; private String name; private Integer age; private String email; @TableField("created_at") private LocalDateTime createdAt; }2.3 Mapper 接口public interface UserMapper extends BaseMapper<User> {}3. 常见查询（QueryWrapper / Lambda）3.1 基础查询@Autowired private UserMapper userMapper; // 主键 User u1 = userMapper.s…

软件工程实践三：RESTful API 设计原则
type: item
date: 2025-09-11T10:30:00+08:00
description: 目标与范围明确一致的接口风格，降低客户端心智负担，提升跨团队协作效率。规范涵盖：资源建模、URI 设计、HTTP 方法与状态码、请求/响应、错误、分页过滤排序、版本化、安全、缓存、可观测性与文档。1. 核心理念资源导向：一切皆资源（名词复数命名），动作用 HTTP 方法表达。统一接口：方法、状态码、媒体类型、错误结构一致。无状态：每个请求自包含认证与上下文，不依赖服务器会话。可缓存：合理使用条件请求与缓存头，降低延迟与负载。可演进：通过版本化与向后兼容策略平滑升级。2. 资源建模与 URI 规范资源命名：使用复数、短小、层级表达从属关系。/api/v1/users/api/v1/users/{userId}/api/v1/users/{userId}/orders关联过滤：也可使用查询参数表达关联（优先简单方案）。/api/v1/orders?userId=123避免在路径中使用动词；确需动作，用子资源表达。POST /api/v1/invoices/{id}/pay标识符：建议使用不可泄漏信息的 ID（UUID/雪花），避免自增 ID 暴露业务规模。路径示例与反例：推荐：GET /api/v1/users/{id}，PATCH /api/v1/users/{id}，DELETE /api/v1/users/{id}不推荐：POST /api/update/users/{id}、POST /api/create/users/{id}（应分别使用 PUT/PATCH 与 POST /api/v1/users）3. HTTP 方法语义与幂等性GET（安全、幂等）：获取资源或集合。POST（非幂等）：创建资源、触发计算/异步任务。PUT（幂等）：整体替换资源（客户端提供完整表述）。PATCH（建议近幂等）：局部更新，使用 JSON Merge Patch 或 JSON Patch。DELETE（幂等）：删除资源（软删/硬删在语义上对客户端保持透明）。4. 标准状态码2xx：200 OK：成功，返回资源或结果；201 Created：创建成功，Location 指向新资源；202 Accepted：已受理异步任务；204 No Content：成功但无响应体（删除、幂等更新）。4xx：400 Bad Request：参数错误/校验失败；401 Unauthorized：未认证或凭证无效；403 Forbidden：已认证但无权限；404 Not Found：资源不存在；409 Conflict：资源状态冲突（如唯一键冲突）；412 Precondition Failed：条件请求失败（ETag 并发控制）；415 Unsupported Media Type：媒体类型不支持；422 Unprocessable Entity：语义错误（校验未通过）；429 Too Many Requests：限流触发。5xx：服务器错误，尽量避免；记录告警并快速恢复。5. 请求与响应规范媒体类型：请求/响应 Content-Type/Accept 统一使用 application/json; charset=utf-8。字段命名与格式：推荐 camelCase；时间使用 ISO 8601（UTC），如 2025-08-20T10:30:00Z。大整数（如 ID）防止前端精度丢失可按 string 传输。统一返回结构（可选但推荐，便于一致性与观测）：{ "code": "OK", // 可选 "message": "success", // 可选 "data": { /* 资源对象或结果 */ }, "requestId": "f7a2b..."， }错误返回结构：{ "code": "VALIDATION_ERROR", // 可以是数字，字符串 "message": "email is invalid",// 提示 "requestId": "9e6d7...", // 可选 "details": [ // 可选 { "field": "email", "issue": "must be a valid email" } ] }6. 示例：Todo 列表接口（内存 List）用途：演示使用内存 List 存储并返回 todo 集合（示例代码，非生产）。路径：GET /api/v1/todos返回：200 OK，application/json; charset=utf-86.1 控制器示例（Java / Spring）package com.example.demo.todo; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.w…

软件工程实践二：Spring Boot 知识回顾
type: item
date: 2025-09-10T09:49:21+08:00
description: 使用 IntelliJ IDEA 创建 Spring Boot Web 项目（仅 Web 依赖）目标：用 IntelliJ IDEA 快速创建只包含 Spring Web 的最小可运行项目，启动后通过浏览器访问 http://localhost:8080/api/hello 返回 JSON。一、创建项目（Spring Boot 向导）步骤说明：打开 IDEA → New Project → 选择 Spring Boot；服务地址保持默认。Project Metadata：Group：如 com.exampleArtifact/Name：如 demoPackaging：jar（默认）Java：17（或本机已安装的 LTS 版本）Build system：Maven（不要选 Gradle）Dependencies：搜索并仅勾选 Spring Web。Finish → 等待 Maven 下载依赖与索引完成。提示：若看不到 Spring Boot(Initializr)，确保安装了 IntelliJ 的 Spring 插件，或使用 File > New > Project... 再选择。二、项目最小代码示例1) 主启动类（IDE 已生成，检查包名与类名）package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }2) Hello 控制器（新增）package com.example.demo.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Date; import java.util.Map; @RestController @RequestMapping("/api") public class HelloController { @GetMapping("/hello") public Map<String, Object> hello() { return Map.of( "msg", "hello", "time", new Date() ); } }3) 应用配置（可选）# src/main/resources/application.yml server: # 如需修改端口，取消注释并调整值 # port: 8080三、运行与验证在 DemoApplication 类左侧点击绿色运行箭头，或使用菜单 Run。控制台看到 Started DemoApplication ... 表示启动成功。浏览器/工具访问：http://localhost:8080/api/hello期望响应示例：{"msg":"hello","time":"2025-01-01T00:00:00Z"}常见问题：端口占用：在 application.yml 配置 server.port: 8081。依赖未下载：检查网络代理或尝试 Reload All Maven Projects。JDK 不匹配：Project Structure > Project/Modules 指定与向导一致的 Java 版本。四、标准目录结构与说明 demo/ ← 工程根目录 ├─ pom.xml ← 构建脚本（Maven） ├─ src │ ├─ main │ │ ├─ java │ │ │ └─ com │ │ │ └─ example │ │ │ └─ demo │ │ │ ├─ DemoApplication.java ← 启动类 │ │ │ └─ controller │ │ │ └─ HelloController.java ← Web 控制器 │ │ └─ resources │ │ ├─ application.yml ← 应用配置 │ │ └─ static/ temp…

Springboot实现后台图片上传
type: item
date: 2025-07-02T20:05:00+08:00
description: 本文将深入探讨一个标准的Web应用后台管理系统中，图片上传功能的完整实现流程。我们将从前端用户交互开始，一直到后端的文件处理、存储和最终的URL返回。整体流程概览图片上传功能的核心流程可以分为以下几个步骤：前端交互：管理员在前端页面选择图片文件。文件上传：前端将图片文件通过HTTP POST请求发送到后端指定的API端点。后端接收：后端控制器（Controller）接收到上传的文件。文件处理：后端服务对文件进行处理，如生成唯一文件名、校验文件类型和大小等。文件存储：将处理后的文件保存到服务器的指定目录中。返回结果：后端向前端返回一个包含图片可访问URL的成功响应。前端展示：前端接收到URL后，将其用于图片预览或表单提交。下面是这个流程的可视化图表：后端实现细节后端采用Spring Boot框架，下面是关键代码的解析。1. 文件上传控制器 (UploadController.java)这个控制器是处理文件上传请求的入口。@RestController @RequestMapping("/api/admin/upload") @CrossOrigin(origins = "*") public class UploadController { private static final Logger logger = LoggerFactory.getLogger(UploadController.class); /** * 上传图片 */ @PostMapping("/image") public Result<?> uploadImage(@RequestParam("file") MultipartFile file) { if (file.isEmpty()) { return Result.error("上传失败，请选择文件"); } try { // 定义存储目录 String resourcePath = "src/main/resources/static/images"; File directory = new File(resourcePath); if (!directory.exists()) { directory.mkdirs(); } // 生成唯一文件名，防止重名覆盖 String originalFilename = file.getOriginalFilename(); String extension = originalFilename.substring(originalFilename.lastIndexOf(".")); String filename = UUID.randomUUID().toString() + extension; // 保存文件 Path filePath = Paths.get(directory.getAbsolutePath(), filename); Files.write(filePath, file.getBytes()); // 构建返回给前端的数据 Map<String, String> data = new HashMap<>(); data.put("url", "/images/" + filename); // 返回相对路径 return Result.success(data); } catch (IOException e) { logger.error("上传图片失败", e); return Result.error("上传图片失败: " + e.getMessage()); } } }关键点：@PostMapping("/image")：定义了处理POST请求的API端点。@RequestParam("file") MultipartFile file：通过Spring MVC接收上传的文件。唯一文件名：使用 UUID.randomUUID() 来生成一个全球唯一的文件名，避免了用户上传同名文件时互相覆盖的问题。文件存储：文件被保存在 src/main/resources/static/images/ 目录下。这是一个标准的Maven项目静态资源目录。返回URL：向前端返回的是一个相对URL，如 /images/xxxx.png，而不是完整的文件系统路径。2. 静态资源服务控制器 (StaticResourceController.java)文件上传到服务器后，我们还需要一个机制让浏览器能够通过URL访问到这些图片。StaticResourceController 就是为此而生。@Controller @RequestMapping("/images") public class StaticResourceControlle…