新闻详情

新闻详情

首页 / 资讯中心 / 详情

苍穹外卖day1:员工登录与本地图片上传全流程实操

发布时间:2026/10/2 14:21:26来源:尧图网络
苍穹外卖day1:员工登录与本地图片上传全流程实操
今天聊点实在的苍穹外卖 day1到底该做哪些事。黑马这套“苍穹外卖”是很多 Java 学习者绕不开的实战项目也是一套标准的 Spring Boot Vue3 前后端分离应用。如果你刚拿到这个项目大概率会先在环境配置和登录功能上花掉一整天而本地上传图片这个功能虽然正式开放一般不在第一天的清单里但它其实是检验你项目基础是否扎实的最佳试金石——好多同学就是栽在这里上传成功了但浏览器访问 404。这篇笔记我按自己实际做项目的顺序来写不照搬教程目录重点说清楚“为什么这样做”和“踩了哪些坑”。适合刚把 Java 基础过完、准备做第一个完整项目的读者也适合已经在写苍穹外卖、卡在某个环节不知道怎么破的同学。学完这个 day1你能跑通员工登录并顺手把本地图片上传与访问机制打通后面做菜品、套餐、店铺状态这些模块会省很多事。1. 项目整体认知与技术选型拆解1.1 苍穹外卖到底在练什么先说结论这不是一个普通的 CRUD 练习项目而是一个包含管理端和用户端的完整外卖业务闭环。管理端相当于商家后台要处理员工登录、分类管理、菜品管理、套餐管理、店铺营业状态、订单管理用户端相当于 C 端小程序或 H5 页面涉及微信登录、浏览菜品、购物车、下单支付、催单等流程。业务量虽然不大但覆盖了一个真实项目绝大多数基础组件权限校验、文件上传、数据缓存、分页查询、多表关联、前后端分离、接口联调。第一天不需要把全部业务做完但应该把项目的“骨架”立起来。也就是说你要能清楚地说出前端哪个页面调后端的哪个接口后端请求经过哪些层数据库的表和实体对应关系是什么。很多同学一做项目就闷头敲代码结果做到第三天发现“员工登录时模糊查询那一段跟前面的逻辑完全割裂”就是因为头一天没把整体数据流理顺。另外这个项目还有一个非常实用的学习点——本地上传图片。虽然它本身只是文件上传功能没有太多业务含量但它是整个项目第一个触动“静态资源映射”的知识点也是后续所有上传图片模块的地基。把这个功能吃透后面接 OSS、MinIO 都只是换一种存储实现而已。1.2 技术栈选型背后的为什么苍穹外卖的技术栈看起来中规中矩其实是精心挑选过的每一项都对应实际开发中的经典问题组件选型为什么是它后端框架Spring Boot 2.7.x快速搭建、自动配置社区资料最多踩坑容易搜ORMMyBatisSQL 可控复杂关联查询写起来直白适合练习 SQL 能力数据库MySQL主流关系型数据库部署简单配套管理工具多缓存Redis用来做缓存和门店状态存储为后续“性能优化”铺路鉴权JWT 拦截器无状态、不占服务端内存贴合前后端分离场景前端Vue3 Element Plus组件化开发UI 成熟大量后台管理系统用它构建Maven依赖管理清晰一个 pom.xml 就能搞定整个项目依赖这里我想额外说一个问题为什么不直接上 Spring Security答案很简单学习梯度。苍穹外卖第一天的核心是让你搞懂“前端带 token、后端验 token”这个机制本身。Spring Security 虽然强大但过滤器链、自定义认证提供者这些概念对刚接触项目的人来说门槛太高容易把注意力从业务逻辑拉走。用拦截器实现同一个效果代码量少、思路直观看到每一行代码在做什么学习效率反而更高。技术选型这件事我一直跟身边人强调不是越新越好而是越稳越好。教程用 Spring Boot 2.7 你就不要硬上 3.x教程用 MySQL 5.7 你也不要只因为新项目就装 8.0。并不是说新版本不能用而是你在学习阶段遇到一个报错检索到的答案可能都是老版本环境的版本差异会让你排查半天。先把一套组合跑通再谈升级。2. day1 核心任务与前置环境准备2.1 环境版本清单与安装建议我见过太多人第一天就卡在环境上不是 JDK 版本不兼容就是 MySQL 字符集没设置对。这里给出一份我实测的版本清单照着装基本不会出大问题软件推荐版本说明JDK8 或 11Spring Boot 2.7 都能跑别装 JDK 21Maven3.6.x 或 3.8.x3.9 也行但 settings.xml 别乱改MySQL5.7 或 8.0安装时选 utf8mb4 字符集Redis6.xWindows 下推荐用安装版别用太旧的 3.xNode.js16 或 18跑 Vue3 前端项目用的IDEIntelliJ IDEA社区版够用装 Lombok 插件关键提醒JDK 版本一定要在开项目之前确认。有些教程默认你用 JDK 8如果你电脑上同时装了 JDK 17Spring Boot 2.7 虽然能跑但某些老版本 Lombok 会直接报IncompatibleClassChangeError排查起来非常挠头。我建议学习环境尽量隔离用一个专门给苍穹外卖准备的 JDK 8/11 版本。MySQL 这里多说一句导入sky_take_out.sql脚本时记得先创建数据库再导入并且设置好默认字符集CREATE DATABASE IF NOT EXISTS sky_take_out DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;不使用 utf8mb4 的话后面菜品名称里出现“靓汤”“brunch”这类字符时数据库会报Incorrect string value错误。Redis 在第一天就启动着不需要专门写代码。因为后面 JWT 令牌校验、缓存功能都依赖它如果你暂时用不到缓存至少也要保证 Redis 服务是通的否则项目启动检查 Redis 连接时可能报错。Windows 用户我建议直接用压缩包版本跑一个redis-server.exe就能用别只凭记忆去下载“绿色版”容易找到带广告的捆绑包。2.2 后端工程初始化与配置文件新建 Spring Boot 工程时不用迷信 start.spring.io 的 Latest 版本直接选 Spring Boot 2.7.x。Dependencies 勾这几个核心依赖Spring WebMyBatis FrameworkMySQL DriverSpring Data RedisLombokValidationJWT 相关包在 start.spring.io 里没有需要自己在pom.xml里引入jjwt或java-jwt。项目里常用的是io.jsonwebtoken:jjwt:0.9.1这个版本与新 JDK 有一些兼容问题所以前面强调 JDK 1.8/11 是稳妥的。包结构我建议这样建跟后续教程保持一致com.sky ├── common通用类结果封装、常量、全局异常 ├── config配置类WebMvc、拦截器注册 ├── controller接口层 ├── entity数据库实体 ├── mapperMyBatis接口 ├── pojoDTO、VO ├── service业务层 ├── interceptor自定义拦截器 └── utilsJwtUtil、AliOssUtil等工具application.yml是整个 day1 的关键很多启动失败都是从这里开始的。我贴一份实际跑通的配置server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/sky_take_out?useUnicodetruecharacterEncodingutf-8serverTimezoneAsia/Shanghai username: root password: 123456 redis: host: localhost port: 6379 servlet: multipart: max-file-size: 5MB max-request-size: 20MB mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.sky.entity sky: jwt: secret-key: sky-take-out-secret ttl: 7200000 token-name: token file: upload-path: D:/skyUpload/几个细节你拿去就能用不用再翻别人的配置第一数据库连接串里的serverTimezoneAsia/Shanghai必须带不然 MySQL 8.x 会给你抛时区错误。第二mybatis.mapper-locations写成classpath:mapper/*.xml确保 Mapper 接口和 XML 文件能对上否则启动不报错但一执行查询就会报Invalid bound statement。第三file.upload-path是给图片上传用的Windows 上建议写D:/skyUpload/Linux 上写/usr/local/skyUpload/路径末尾的斜杠一定要带不带会导致拼接文件路径时目录名和文件名粘在一起。3. 登录功能与“本地上传图片”的实操要点3.1 员工登录全流程实现day1 最核心的业务功能是员工登录逻辑并不复杂前端把账号密码交到后端后端查库、比对密码校验员工状态然后签发一个 JWT 令牌返回给前端前端把令牌存在本地后续请求全部带上。先写接收参数的 DTOData public class LoginDTO { NotBlank(message 用户名不能为空) private String username; NotBlank(message 密码不能为空) private String password; }Controller 不需要在方法里写一堆业务逻辑只负责收参和返回RestController RequestMapping(/employee) public class EmployeeController { Autowired private EmployeeService employeeService; PostMapping(/login) public ResultEmployeeLoginVO login(RequestBody LoginDTO loginDTO) { Employee employee employeeService.login(loginDTO); // 生成JWT令牌 MapString, Object claims new HashMap(); claims.put(empId, employee.getId()); String jwt JwtUtil.createJWT( jwtProperties.getSecretKey(), jwtProperties.getTtl(), claims); // 封装VO返回 EmployeeLoginVO employeeLoginVO EmployeeLoginVO.builder() .id(employee.getId()) .userName(employee.getUsername()) .name(employee.getName()) .token(jwt) .build(); return Result.success(employeeLoginVO); } }Service 里的核心判断是这样的public Employee login(LoginDTO loginDTO) { // 1. 根据用户名查数据库 Employee employee employeeMapper.getByUsername(loginDTO.getUsername()); // 2. 用户不存在 if (employee null) { throw new AccountNotFoundException(用户名不存在); } // 3. 密码核对需要做MD5后再比对 String md5Password DigestUtils.md5DigestAsHex(loginDTO.getPassword().getBytes()); if (!md5Password.equals(employee.getPassword())) { throw new PasswordErrorException(密码错误); } // 4. 账号状态校验 if (employee.getStatus() 0) { throw new AccountLockedException(账号已禁用); } return employee; }这里有两个特别值得注意的细节。第一个是密码绝不能明文存储在数据库里就算做练习也一样。项目里用 MD5 加密码再落库虽然 MD5 不算强加密但对于这个学习场景足够说明问题数据表里永远不存真实密码。第二个是异常的处理方式。项目里通常有全局异常处理器RestControllerAdviceExceptionHandler你抛出业务异常后由全局处理器统一转成 JSON 返回给前端Controller 里不要自己去 try-catch否则每个接口都要写一套异常判断又乱又容易漏。登录还有一种“看似成功但实际没通过校验”的经典情况前端请求时根本没带 token或者 token 过期了。这个靠拦截器解决。我写一个普通的拦截器核心逻辑是public class JwtTokenInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 跨域预检请求直接放行 if (HttpMethod.OPTIONS.matches(request.getMethod())) { return true; } // 从请求头取token String token request.getHeader(token); if (token null || token.isEmpty()) { response.setStatus(401); return false; } try { Claims claims JwtUtil.parseJWT(jwtProperties.getSecretKey(), token); Long empId Long.valueOf(claims.get(empId).toString()); // 存入当前线程上下文后续业务里可以直接取当前操作人 BaseContext.setCurrentId(empId); return true; } catch (Exception e) { response.setStatus(401); return false; } } }拦截器注册在 WebMvc 配置类里注意要把登录接口排除掉Configuration public class WebMvcConfiguration implements WebMvcConfigurer { Autowired private JwtTokenInterceptor jwtTokenInterceptor; Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtTokenInterceptor) .addPathPatterns(/**) .excludePathPatterns(/employee/login); } }如果你在 day1 就做到了这一步那整个登录链路你已经掌握了前端请求携带 token、后端拦截器校验 token、业务线程通过 BaseContext 拿到当前登录用户 ID。这个模式后面几乎所有接口都会复用。3.2 本地上传图片功能实现与静态资源映射“苍穹外卖本地上传图片”这个热搜词说白了就是项目中把图片存到服务器本地磁盘而不是直接丢给云服务商。很多同学在这块报 404关键是没有理解 Spring Boot 的静态资源映射是怎么工作的。图片上传的本质是三步接收文件、保存到磁盘、返回一个可访问的 URL。先看上传接口RestController RequestMapping(/common) public class CommonController { Value(${file.upload-path}) private String uploadPath; PostMapping(/upload) public ResultString upload(MultipartFile file) throws IOException { // 1. 判断是否为空 if (file null || file.isEmpty()) { return Result.error(文件为空); } // 2. 获取原始文件名截取扩展名 String originalFilename file.getOriginalFilename(); String extension originalFilename.substring(originalFilename.lastIndexOf(.)); // 3. 生成新文件名 String newFileName UUID.randomUUID() extension; // 4. 检查目录是否存在不存在则创建 File dir new File(uploadPath); if (!dir.exists()) { dir.mkdirs(); } // 5. 保存文件 File targetFile new File(uploadPath newFileName); file.transferTo(targetFile); // 6. 返回浏览器可访问的相对地址 return Result.success(/upload/ newFileName); } }这里有几个细节我想重点解释一下。为什么要用UUID.randomUUID()重命名因为直接返回用户上传的原始文件名会有两个问题一是文件名包含中文时容易出现编码问题二是不同用户如果上传同名文件会互相覆盖。UUID重命名后虽然文件名不可读但换来的是全局唯一安全性和稳定性都更高。为什么接口返回/upload/xxx.jpg而不是完整路径D:/skyUpload/xxx.jpg因为前端需要的只是一个相对 URL这个 URL 通过 Spring MVC 的静态资源映射就能映射到磁盘路径上。把真实磁盘路径直接暴露给前端是坏味道一旦拆分成多台服务器这种写法立刻穿帮。真正让访问不 404 的关键是资源映射配置Configuration public class WebMvcConfiguration implements WebMvcConfigurer { Value(${file.upload-path}) private String uploadPath; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadPath); } }addResourceHandler(/upload/**)的意思是当浏览器请求路径以/upload/开头时Spring Boot 会去file:后面的磁盘路径下找文件。比如你访问http://localhost:8080/upload/abc.jpg实际读取的是D:/skyUpload/abc.jpg。注意addResourceLocations必须写file:前缀这是本地文件协议的标志。如果你不加file:Spring 会以为你要从 classpath 或 Servlet 容器上下文里找资源结果永远是 404。另外还有两处容易坑人的地方。第一上传目录的权限。Linux 环境下如果目录创建失败程序不会报错但保存文件时会有FileNotFoundException这时候要检查运行 Java 进程的用户对目标目录有没有写权限。第二配置文件里的upload-path末尾必须带斜杠。如果写成D:/skyUpload拼接new File(uploadPath newFileName)时会变成D:/skyUploadabc.jpg直接报“系统找不到指定的路径”。4. 常见问题排查与避坑记录4.1 day1 启动与登录过程中的高频异常我按照自己实际带项目时遇到的高频问题整理了一张表每个问题都是真实场景不是教科书上的理论情况。端口被占用8080被其他程序占用的概率很高。排查时先看日志里的错误信息如果是Port 8080 was already in use一种方式是改server.port另一种方式是杀掉占用进程。Windows 下可以执行netstat -ano | findstr 8080找到 PID再taskkill /PID pid /F强制结束。如果是 Redis 的 6379 端口被占用同理处理。MySQL 连不上Unknown database 或 Access denied这类报错多半是数据库和用户权限问题。先确认sky_take_out数据库真的创建了再确认application.yml里的用户名密码和本地 MySQL 一致。如果你是 MySQL 8.0连接驱动要用com.mysql.cj.jdbc.Driver如果你的 Spring Boot 版本较老默认还是旧的com.mysql.jdbc.Driver也要手动改一下。Redis 连接失败Redis 在 Windows 下启动之后不会显示日志窗口很多人以为没启动成功。其实只要能看到redis-server.exe进程在运行并且配置文件里的host、port跟实际一致就行。如果设置了 Redis 密码application.yml里还要配置spring.data.redis.password。Invalid bound statement (not found)这句话几乎每个用 MyBatis 的人都会见到。绝大多数原因是 Mapper 接口和 XML 文件没绑定上。检查两点XML 的namespace必须写接口全限定名application.yml中mapper-locations必须扫描到 XML 所在目录。另外XML 文件要放在src/main/resources/mapper/下而不是src/main/java。前端接口请求失败代理没有配好如果前端运行后登录按钮一提交就报 404 或 405很可能是前端代理问题。Vue 项目里检查vite.config.js中的代理配置server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, rewrite: path path.replace(/^\/api/, ) } } }很多同学后端的 Controller 路径是/employee/login前端请求却是/api/employee/login中间多了一层api前缀。这就需要代理配置 rewrite 把/api去掉否则后端匹配不到路由。4.2 本地上传图片相关的排查接下来单独把“本地上传图片”的坑拉出来说因为它属于“看起来很简单、实际翻车率极高”的功能。我见过不少同学卡在这里一两个小时翻来覆去不知道哪出了问题。上传接口返回 401如果你上传图片时发现请求被拦截了先去检查拦截器配置。很多人只排除了/employee/login却忘了图片上传走的是/common/upload它被拦下来了。解决办法是在excludePathPatterns里加上/common/upload。不过这里要留个心眼如果下一步你要做用户端用户端的某些接口也走同一个拦截器的话排除路径要考虑清楚别把不该放行的也放行了。上传成功但访问不到图片这个是最隐蔽的问题。文件明明保存到了本地目录但浏览器访问http://localhost:8080/upload/xxx.jpg时返回 404。遇到这种情况我建议按照以下顺序排查访问路径是否真的带上了/upload前缀你在接口里返回的是/upload/文件名前端有没有把域名拼接正确。资源映射配置里file:后面的路径是否和application.yml一致有人把D:/skyUpload/写成了D://skyUpload/或者漏了末尾斜杠。文件是否真的保存到了配置目录如果 IDEA 工作目录和磁盘上路径不一致可能出现“程序写到了 A 目录你手动跑去看 B 目录”的情况。这里我再给一个排查小技巧直接在浏览器访问一个你确定存在的文件比如手工在uploadPath目录下新建一个test.jpg再去访问/upload/test.jpg。如果手工文件能显示而程序上传的显示不了问题在保存路径如果手工文件也显示不了问题在资源映射。文件名中文乱码图片名称如果是中文访问时 URL 编码不一致会导致 404。这个问题本质上就是上传时没有重命名造成的连锁反应。我强烈建议按项目里的规范来统一用UUID重命名原始文件名只用来截取扩展名不做任何业务依赖。这个习惯放到以后接云存储时同样适用。上传大文件报 MaxUploadSizeExceededExceptionSpring Boot 默认上传文件大小上限一般是 1MB超过就会抛异常。如果你是图片上传问题不大但如果后续菜单里要传产品详情图片就可能超限。在application.yml中调整spring: servlet: multipart: max-file-size: 20MB max-request-size: 50MB注意max-file-size是单个文件大小max-request-size是一次请求最多允许的总大小。前者比后者大的时候多文件上传依然会失败。Linux 上出现 FileNotFoundException本地 Windows 跑得好好的部署到 Linux 就上传失败十有八九是目录权限问题。你可以用ls -ld /usr/local/skyUpload查看目录权限如果没有读写权限要么用chmod 755调权限要么换目录路径并赋予当前运行用户权限。另外上传目录不要太深避免路径中包含不存在的一级目录导致mkdirs()没生效。5. 个人实操体会与后续扩展我自己给项目做 day1 时有个习惯不止做登录而是把图片上传也一起跑通哪怕后面还没有真正用到图片的功能。因为本地上传图片涉及的知识点非常独立而且它是后面很多模块的公共基础。你在第一天硬着头皮把这块打通后面做分类页面、菜品页面时几乎不需要再重新思考上传逻辑只是把 URL 存到数据库而已。反过来如果拖到后面再做往往会被一个“上传失败”卡住整个开发节奏。再分享一个我踩过多次的坑环境版本一定要锁定不要顺手升级。第一天搭建项目时大家很爱顺手把 Spring Boot 升级到最新、JDK 换到 17、Redis 换到 7结果一个小毛病排查半天最后发现是版本不兼容。学习阶段讲究的是稳定复现你只要锁定一套能跑通的组合其它版本的差异等以后工作里再熟悉也不迟。最后说说这块还能怎么扩。本地上传图片解决的是“开发环境够用”的问题部署到线上时通常会把这套逻辑替换成 OSS 或 MinIO。你在 day1 写的CommonController.upload方法只要把存储部分换个实现就能平滑迁移到云存储。所以前面把接口返回相对路径、用UUID重命名这些习惯养好后面接云存储会非常顺畅。苍穹外卖做完之后我建议你花点时间把上传逻辑抽象成接口再做一版对象存储的实现这对理解项目架构的演进会有很大帮助。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

百度云分享链接批量检测:用代码快速筛出失效链接 2026/10/2 15:02:53

百度云分享链接批量检测:用代码快速筛出失效链接

管理一个攒了好几年的网盘链接库,迟早会碰到同一个扎心场景:点开收藏夹里几十条分享链接,一条条点过去,一半以上提示"你访问的页面不存在"或者"链接已失效"。手动排查一遍,半小时没了,…

阅读更多 →
Excel Range.Value数组:VBA与VB.NET的差异与避坑指南 2026/10/2 15:02:53

Excel Range.Value数组:VBA与VB.NET的差异与避坑指南

做Excel二次开发的人,十有八九都写过或者抄过这行代码:arr Range("A1:C10").Value。在VBA里这行代码贼好用,一次把10行3列的数据打包进内存,循环、查找、统计都快得飞起。可是当你把同样的思路搬到VB.NET,打…

阅读更多 →
Mamba并行扫描与硬件感知优化:从串行递推到GPU高效实现 2026/10/2 15:02:47

Mamba并行扫描与硬件感知优化:从串行递推到GPU高效实现

熟悉状态空间模型序列预测的朋友,大概都听过Mamba的大名。作为SSM赛道里第一个把选择性机制跑出Transformer级效果的结构,Mamba真正的护城河并不只在理论设计上,更在下篇要聊的并行扫描和硬件感知优化里。很多人一开始读Mamba论文&#xff0c…

阅读更多 →
揭秘!那些备受瞩目的知名SEO优化品牌究竟是谁? 2026/10/2 15:02:47

揭秘!那些备受瞩目的知名SEO优化品牌究竟是谁?

痛点深度剖析我们团队在实践中发现,SEO优化领域存在诸多痛点。在流量获取方面,SEO见效极慢,不少企业做了半年优化,关键词排名却毫无变动,开始怀疑SEO的有效性;SEM则烧钱快,谷歌广告点击成本不断…

阅读更多 →
大语言模型如何当你的AI学习教练:提示词、RAG与实操方法 2026/10/2 15:02:47

大语言模型如何当你的AI学习教练:提示词、RAG与实操方法

1. 先用一句话给LLM定位:它不是答案机,是你的“学习教练”最近总有人问我:想入行人工智能,到底该从哪起步?我的回答往往让他们愣一下——先别急着买一堆几千块的课,也别一上来就啃那本六百页的教材&#xf…

阅读更多 →
LeetCode刷题策略:用热门100题与周赛复盘构建算法体系 2026/10/2 15:02:47

LeetCode刷题策略:用热门100题与周赛复盘构建算法体系

很多人问我LeetCode到底怎么刷才有效,我通常会给一个很反直觉的回答:别急着刷题,先定策略。我自己在刷题这件事上踩过最大的坑,就是跟风刷题,今天一道数组明天一道图论,三个月后回头一看,写过的…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉