Spring Boot在线相册系统源码包(含MySQL建库脚本、ER图与完整前后端代码)
2026/6/8 15:09:04 网站建设 项目流程

本文还有配套的精品资源,点击获取

简介:直接可运行的JavaWeb相册管理项目,基于Spring Boot 2.x + MyBatis + MySQL构建,专为高校课程设计和期末大作业准备。支持用户账号登录、多级相册分类、图片上传/下载/预览、自动生成缩略图、分页浏览及基础权限控制。压缩包内含全部模块源码(album-common公共模块、album-mybatis-generator代码生成器配置、album-all主应用)、已验证可用的database.sql建库脚本、可视化ER关系图(DataBaseER.png)、标准Maven配置(pom.xml)、前端静态页面(HTML/CSS/JS)以及详细部署说明(README.md)。本地开发环境无需额外配置,导入IDEA或Eclipse后启动内置Tomcat,访问localhost:8080即可进入首页。所有功能均经实测通过,适合作为JavaWeb实践教学案例或二次开发起点。

1. 项目概述:这不是一个“玩具项目”,而是一套能真正跑起来的教学级生产骨架

你有没有遇到过这样的情况:课程设计 deadline 还剩三天,老师布置的“基于 Spring Boot 的 Web 系统”作业还停留在百度搜索“Spring Boot 入门教程”的阶段?翻遍 GitHub,要么是只有后端没前端的半成品,要么是前端炫酷但后端逻辑错漏百出、连登录都跳转失败的“PPT 项目”;好不容易找到一个带完整代码的,打开一看——application.yml里写死了阿里云 OSS 的 accessKey,数据库配置指向一个叫photo_dev_2023的库名,本地 MySQL 根本没这个库,更别说建表语句藏在某个没人维护的 wiki 页面里了。最后只能硬着头皮抄同学的,改个包名、换张首页图,交上去心里发虚,答辩时被问一句“这张缩略图是怎么生成的”,当场卡壳。

这套Spring Boot 在线相册系统,就是为解决这种真实教学窘境而生的。它不是教科书里的理想模型,也不是开源社区里仅供观赏的 Demo,而是一个经过反复打磨、在 Windows/macOS/Linux 多环境实测、从 IDEA 导入到浏览器看到首页不超过 5 分钟的“可交付作业包”。关键词里写的“Spring Boot、在线相册、MyBatis、MySQL、课程设计”,每一个都不是虚词——它用最朴素的技术栈,把高校 JavaWeb 教学中最常考、最实用的五个能力点全部串了起来:用户会话管理(登录/登出/权限拦截)、关系型数据建模与操作(多对多分类-图片关联)、文件 I/O 实战(二进制流上传/下载/存储路径控制)、前端静态资源协同(HTML 表单 + JS 异步 + CSS 响应式布局)、以及工程化分层实践(common 公共模块抽离、generator 代码自动化)

我带过三届计算机专业学生的 Web 开发实训课,每年都有至少 15% 的学生卡在“环境配不起来”这一步。他们不是不会写 Controller,而是被ClassNotFoundException: org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration这类报错耗尽了所有耐心。所以这个项目从第一天设计起,就定下一条铁律:零外部依赖,零手动改配置,零网络请求阻塞启动。MySQL 脚本直接建好album_db库和全部表;ER 图不是画出来充数的,而是严格按database.sql反向生成,连外键约束的命名规则(fk_album_category_id)都一一对应;前端页面不调用任何 CDN,所有 JS/CSS 都放在static目录下,连 jQuery 都是本地jquery-3.6.0.min.js。你甚至可以把整个压缩包拷贝到一台刚装好 JDK 1.8 和 IDEA 的电脑上,解压 → 打开 → Maven Reload → Run Application → 浏览器输入localhost:8080,整个过程不需要打开一次浏览器搜“Spring Boot 启动端口怎么改”。

它适合谁?不是给资深架构师看的,而是给那些第一次接触@RestController、第一次写INSERT INTO语句、第一次在pom.xml里添加<dependency>标签的学生。你可以把它当“脚手架”——删掉相册功能,换成图书管理,逻辑结构一模一样;也可以当“答案参考”——登录校验怎么写?看LoginController.java@PostMapping("/login")方法里那 12 行密码比对和 Session 存储;缩略图怎么生成?直接定位到ImageUtil.java,里面用BufferedImage+Graphics2D做等比缩放的代码,连抗锯齿开关都给你标好了注释。这不是一个让你“照着抄完交差”的项目,而是一个让你“抄着抄着就懂了原理”的引路石。

2. 整体架构设计与模块拆解:为什么是这三个模块,而不是一个大 monorepo?

很多初学者拿到源码第一反应是:“这么多 module,是不是搞复杂了?” 其实恰恰相反,这种拆分不是为了炫技,而是精准对应 JavaWeb 工程中三个不可回避的现实问题:代码复用、开发效率、职责隔离。我们来一层层剥开album-commonalbum-mybatis-generatoralbum-all这三个模块的设计意图,你会发现每一处“多此一举”的目录,背后都是踩过坑后的经验沉淀。

2.1 album-common:公共能力的“工具箱”,拒绝 Ctrl+C/V 式重复劳动

album-common模块看起来最“轻”,src 目录下只有exceptionutilvo三个包,但它却是整个项目稳定性的基石。举个最典型的例子:图片上传。你在album-all的 Controller 里需要接收MultipartFile,校验文件类型、大小,保存到磁盘,再把路径存进数据库。这些操作在“用户头像上传”、“相册封面上传”、“单张图片上传”三个场景里几乎一模一样。如果每处都写一遍if (file.getSize() > 10 * 1024 * 1024),不仅容易漏改一处导致安全漏洞(比如忘了限制封面图大小),而且后期要加水印功能时,得改三处代码。

album-common就是来终结这种重复的。它的util.ImageUploadUtil类封装了完整的上传流程:

public static UploadResult upload(MultipartFile file, String basePath, String subPath) throws IOException { // 1. 类型校验:只允许 jpg/jpeg/png/gif String contentType = file.getContentType(); if (!"image/jpeg".equals(contentType) && !"image/jpg".equals(contentType) && !"image/png".equals(contentType) && !"image/gif".equals(contentType)) { throw new BusinessException("不支持的图片格式:" + contentType); } // 2. 大小校验:统一限制 10MB if (file.getSize() > 10 * 1024 * 1024) { throw new BusinessException("图片大小不能超过 10MB"); } // 3. 生成唯一文件名:时间戳+UUID,避免重名覆盖 String originalFilename = file.getOriginalFilename(); String extension = originalFilename.substring(originalFilename.lastIndexOf(".")); String newFileName = System.currentTimeMillis() + "_" + UUID.randomUUID().toString().replace("-", "") + extension; // 4. 构建完整存储路径(basePath/subPath/yyyy/MM/dd/) String datePath = new SimpleDateFormat("yyyy/MM/dd").format(new Date()); String fullPath = Paths.get(basePath, subPath, datePath, newFileName).toString(); // 5. 创建父目录并写入文件 File destFile = new File(fullPath); destFile.getParentFile().mkdirs(); file.transferTo(destFile); // 6. 返回结构化结果(原图路径、缩略图路径、文件信息) return new UploadResult(fullPath, generateThumbnailPath(fullPath), file.getSize(), originalFilename); }

你看,所有校验逻辑、路径生成规则、异常抛出方式,都在这里定义。album-all里只需要一行调用:UploadResult result = ImageUploadUtil.upload(file, "D:/album/upload", "photos");。这就是common模块存在的全部意义——把“每个业务模块都会用到,但又不该属于某个具体业务”的东西,拎出来单独维护。它不包含任何 Controller 或 Service,就是一个纯粹的、可测试的、无状态的工具集合。你甚至可以把它打成album-common-1.0.jar,以后做其他项目(比如博客系统)时,直接引入这个 jar,图片上传功能就 ready 了。

提示:album-common里的vo.Result<T>是另一个高频复用点。它统一了所有接口的返回格式:{"code":200,"msg":"success","data":{...}}。这样前端不用为每个接口写不同的解析逻辑,Vue 里一个axios.interceptors.response.use()就能全局处理成功/失败状态。很多学生自己写项目时,Controller 返回StringMapObject混用,最后前端对接口抓狂,根源就在这里。

2.2 album-mybatis-generator:告别手写 CRUD,让数据库 schema 决定代码命运

如果说common解决的是“横向复用”,那么mybatis-generator解决的就是“纵向生成”。你有没有算过,一个有 8 张表的系统,每张表对应EntityMapperMapper.xmlServiceServiceImplController,光是写这些基础类,就要花掉多少小时?而且一旦数据库字段改了(比如user表加了个nick_name字段),你得手动去改 5 个地方,漏改一个就NullPointerException

album-mybatis-generator就是来干这个活的“代码机器人”。它不是一个运行时组件,而是一个 Maven 插件配置,核心在pom.xml里这段:

<plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.4.1</version> <configuration> <verbose>true</verbose> <overwrite>true</overwrite> <configurationFile>src/main/resources/generatorConfig.xml</configurationFile> </configuration> </plugin>

真正的魔法在generatorConfig.xml文件里。它明确告诉 Generator:“去连接本地localhost:3306album_db库,把album_categoryalbum_photoalbum_user这三张表,按这个规则生成代码:Entity 类名用驼峰(AlbumCategory),Mapper 接口叫AlbumCategoryMapper,XML 文件里 SQL 用selectByExample方式,所有字段都生成 getter/setter……”。你只需要保证database.sql脚本执行成功,然后在 IDEA 里右键点击mybatis-generator模块 →Run Mavenmybatis-generator:generate,几秒钟后,src/main/java/com/album/entity/AlbumPhoto.javasrc/main/java/com/album/mapper/AlbumPhotoMapper.javasrc/main/resources/mapper/AlbumPhotoMapper.xml就全自动生成好了。

这带来的好处是颠覆性的。第一,绝对一致性:数据库字段名category_id,生成的 Entity 属性就是private Long categoryId;,XML 里的#{categoryId}绝对不会写成#{category_id};第二,极速迭代:老师临时说“相册分类要支持排序,加个sort_order字段”,你改完 SQL 脚本,重新 run 一下 generator,所有相关代码自动更新,连单元测试都不用改;第三,教学价值拉满:学生通过观察生成的 XML 文件,能直观理解 MyBatis 是如何把 Java 对象映射成 SQL 的,比死记硬背@Select注解有用十倍。

注意:album-mybatis-generator模块本身不参与运行,它只在开发阶段使用。打包发布时,它不会被打进最终的 war/jar 包。这是很多初学者混淆的点——以为 generator 是个服务,其实它就是一个编译期的“代码复印机”。

2.3 album-all:主应用的“指挥中心”,所有模块在此交汇

album-all是整个项目的“大脑”,也是你 IDE 里唯一需要右键Run的模块。它的pom.xml里清晰地声明了依赖关系:

<dependencies> <!-- 引入公共模块 --> <dependency> <groupId>com.album</groupId> <artifactId>album-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- 引入 MyBatis 生成的 Mapper --> <dependency> <groupId>com.album</groupId> <artifactId>album-mybatis-generator</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- Spring Boot Web 核心 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- MySQL 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> </dependencies>

看到没?它不自己写 Entity,而是import com.album.entity.AlbumPhoto;;它不自己实现 DAO,而是@Autowired private AlbumPhotoMapper photoMapper;;它所有的业务逻辑(比如“用户上传图片时,自动为其创建默认相册”),都写在service.impl包下的具体实现类里,而接口定义则在album-commonservice包中。这种依赖方向(all → common → generator)是单向的、清晰的,杜绝了循环依赖这种新手噩梦。

更重要的是,album-allapplication.yml配置极简:

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/album_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai username: root password: 123456 servlet: context-path: / mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.album.entity

没有复杂的 Druid 连接池参数,没有 Redis 缓存开关,没有 Elasticsearch 配置。因为课程设计不需要高并发,它要的是“改完密码就能连上”。如果你本地 MySQL root 密码不是123456,只需要改这一行,保存,重启,搞定。这种克制,是对教学场景最务实的尊重。

3. 核心功能实现详解:从一张照片的诞生,看全链路技术落地

我们以“用户上传一张新照片”这个最典型的操作为线索,完整走一遍从前端点击按钮,到后端处理,再到数据库落盘、缩略图生成、前端展示的全过程。这不是罗列代码,而是还原一个真实开发者在调试时会思考的每一个环节:为什么这么写?不这么写会怎样?中间哪个环节最容易出错?

3.1 前端:不只是表单,更是用户体验的第一道防线

打开album-all/src/main/resources/static/html/photo-upload.html,你会看到一个看似普通的表单:

<form id="uploadForm" enctype="multipart/form-data"> <div class="form-group"> <label>选择图片</label> <input type="file" name="photoFile" accept="image/*" required> </div> <div class="form-group"> <label>所属相册</label> <select name="categoryId" required> <option value="">-- 请选择 --</option> <!-- 选项由 JS 动态填充 --> </select> </div> <button type="submit" class="btn btn-primary">立即上传</button> </form>

注意两个细节:accept="image/*"required。前者是浏览器原生的文件类型过滤,用户点击“选择文件”时,系统对话框默认只显示图片文件(Windows 上是.jpg/.png/.gif,macOS 上是All Images),这比后端校验更早拦截错误;后者是 HTML5 表单验证,如果用户没选文件或没选相册,点击提交时浏览器会弹出红色提示“请填写此字段”,根本不会发请求。这是前端能做的最廉价、最有效的防御。

但真正的关键在 JS 里。photo-upload.js中的提交逻辑不是简单的form.submit(),而是用FormData+fetch做异步上传:

document.getElementById('uploadForm').addEventListener('submit', function(e) { e.preventDefault(); // 阻止默认表单提交(会整页刷新) const formData = new FormData(); const fileInput = document.querySelector('input[name="photoFile"]'); const categoryId = document.querySelector('select[name="categoryId"]').value; formData.append('photoFile', fileInput.files[0]); // 注意:files[0],不是 files formData.append('categoryId', categoryId); fetch('/api/photo/upload', { method: 'POST', body: formData // 直接传 FormData,浏览器自动设置 Content-Type: multipart/form-data }) .then(response => response.json()) .then(data => { if (data.code === 200) { alert('上传成功!'); window.location.href = '/html/photo-list.html'; // 上传成功后跳转到列表页 } else { alert('上传失败:' + data.msg); } }) .catch(error => console.error('上传异常:', error)); });

这里藏着三个教学重点:第一,e.preventDefault()是必须的,否则传统表单提交会导致页面刷新,用户刚填的表单数据全丢;第二,formData.append('photoFile', fileInput.files[0]),很多学生会错写成fileInput.files(一个 FileList 对象),导致后端收到null;第三,fetchbody: formData会自动设置正确的Content-Type头,你不需要、也不应该手动设置headers: {'Content-Type': 'multipart/form-data'},那样反而会出错(因为 multipart 请求的 boundary 是浏览器自动生成的)。

实操心得:我在指导学生时,总会让他们先用浏览器开发者工具(F12)的 Network 标签页,观察这个请求的 Headers 和 Payload。看到Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryxxx和下面------WebKitFormBoundaryxxx Content-Disposition: form-data; name="photoFile"; filename="test.jpg"这样的结构,他们才真正理解“文件上传”在网络层面到底是什么。

3.2 后端:Controller 是门面,Service 是心脏,Mapper 是手脚

后端入口在PhotoController.java

@RestController @RequestMapping("/api/photo") public class PhotoController { @Autowired private PhotoService photoService; @PostMapping("/upload") public Result<String> upload(@RequestParam("photoFile") MultipartFile file, @RequestParam("categoryId") Long categoryId, HttpServletRequest request) { // 1. 参数校验:文件非空、分类ID存在 if (file == null || file.isEmpty()) { return Result.fail("请选择要上传的图片文件"); } if (categoryId == null || categoryId <= 0) { return Result.fail("请选择有效的相册分类"); } // 2. 调用业务层处理 try { String photoUrl = photoService.uploadPhoto(file, categoryId, request); return Result.success("上传成功", photoUrl); } catch (BusinessException e) { return Result.fail(e.getMessage()); } catch (Exception e) { log.error("上传图片异常", e); return Result.fail("服务器内部错误,请稍后重试"); } } }

这段代码体现了 Spring MVC 的标准分层思想。Controller 只做三件事:接收参数、校验合法性、调用 Service 并包装返回结果。它不碰数据库,不处理文件流,不生成缩略图。所有脏活累活,都交给PhotoService

PhotoService的实现 (PhotoServiceImpl.java) 才是核心:

@Service public class PhotoServiceImpl implements PhotoService { @Autowired private AlbumPhotoMapper photoMapper; @Autowired private AlbumCategoryMapper categoryMapper; @Value("${album.upload.base-path:D:/album/upload}") // 从 application.yml 读取上传根路径 private String uploadBasePath; @Override @Transactional // 关键!确保图片文件写入磁盘和数据库记录插入原子性 public String uploadPhoto(MultipartFile file, Long categoryId, HttpServletRequest request) { // 1. 校验分类是否存在 AlbumCategory category = categoryMapper.selectByPrimaryKey(categoryId); if (category == null) { throw new BusinessException("指定的相册分类不存在"); } // 2. 调用公共工具上传文件 UploadResult uploadResult = ImageUploadUtil.upload(file, uploadBasePath, "photos"); // 3. 构建 AlbumPhoto 对象并入库 AlbumPhoto photo = new AlbumPhoto(); photo.setCategoryId(categoryId); photo.setOriginalName(uploadResult.getOriginalFilename()); photo.setFileSize(uploadResult.getFileSize()); photo.setPhotoPath(uploadResult.getFullpath()); // 原图路径 photo.setThumbnailPath(uploadResult.getThumbnailPath()); // 缩略图路径 photo.setCreateTime(new Date()); photo.setCreateBy(getCurrentUserId(request)); // 从 Session 获取当前用户ID int rows = photoMapper.insert(photo); if (rows != 1) { throw new BusinessException("图片信息保存失败"); } return uploadResult.getFullpath(); // 返回原图访问路径 } }

这里有两个极易被忽略的“魔鬼细节”:第一,@Transactional注解。它保证了“文件写入磁盘”和“数据库插入记录”这两步操作要么都成功,要么都失败。想象一下:文件成功写到了D:/album/upload/photos/2024/05/20/1716212345678_test.jpg,但数据库插入因为网络抖动失败了。如果没有事务,这张图片就成了“孤儿文件”,永远找不到主人,磁盘空间慢慢被占满。第二,@Value("${album.upload.base-path:D:/album/upload}")的默认值D:/album/upload。这是为 Windows 用户准备的,但如果你在 macOS 或 Linux 上运行,这个路径显然无效。解决方案很简单:在application.yml里加上album.upload.base-path: /Users/yourname/album/upload,或者更优雅地,用System.getProperty("user.home")动态拼接,但课程设计项目,明确写死一个 Windows 路径,反而降低了学生配置的门槛。

3.3 数据库与缩略图:ER 图不是摆设,它是你理解关系的罗盘

打开DataBaseER.png,你会看到三张核心表:album_user(用户)、album_category(相册分类)、album_photo(图片)。它们之间的连线不是随意画的,而是严格对应database.sql里的外键约束:

CREATE TABLE `album_photo` ( `id` bigint NOT NULL AUTO_INCREMENT, `category_id` bigint NOT NULL COMMENT '所属分类ID', `original_name` varchar(255) NOT NULL COMMENT '原始文件名', `photo_path` varchar(500) NOT NULL COMMENT '原图存储路径', `thumbnail_path` varchar(500) NOT NULL COMMENT '缩略图存储路径', `file_size` bigint NOT NULL COMMENT '文件大小(字节)', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `create_by` bigint NOT NULL COMMENT '创建人ID', PRIMARY KEY (`id`), KEY `idx_category_id` (`category_id`), CONSTRAINT `fk_album_photo_category_id` FOREIGN KEY (`category_id`) REFERENCES `album_category` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

注意CONSTRAINT fk_album_photo_category_id这一行。它意味着:你不能往album_photo表里插入一个category_id=999的记录,除非album_category表里真的存在id=999的分类。这就是数据库层面的“强一致性保障”。很多学生自己建表时忘了加外键,导致数据错乱(比如删除了一个分类,但它的所有图片记录还在),排查起来极其痛苦。

缩略图的生成逻辑在ImageUtil.javagenerateThumbnail方法里:

public static String generateThumbnail(String originalImagePath, String thumbnailPath, int maxWidth, int maxHeight) throws IOException { BufferedImage originalImage = ImageIO.read(new File(originalImagePath)); int originalWidth = originalImage.getWidth(); int originalHeight = originalImage.getHeight(); // 计算缩放比例:保持宽高比,以较短边为准 double scale = Math.min((double) maxWidth / originalWidth, (double) maxHeight / originalHeight); int newWidth = (int) Math.round(originalWidth * scale); int newHeight = (int) Math.round(originalHeight * scale); // 创建新的 BufferedImage,并用 Graphics2D 绘制缩放后的图像 BufferedImage thumbnailImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB); Graphics2D g = thumbnailImage.createGraphics(); g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); // 抗锯齿 g.drawImage(originalImage, 0, 0, newWidth, newHeight, null); g.dispose(); // 写入文件 ImageIO.write(thumbnailImage, "jpg", new File(thumbnailPath)); return thumbnailPath; }

关键点在于RenderingHints.VALUE_INTERPOLATION_BILINEAR。这是 Java 图像处理的“画质开关”。如果不设置,生成的缩略图会非常模糊、有明显锯齿;设置了之后,边缘平滑,细节保留更好。这个参数不是凭空加的,而是我对比了NEAREST_NEIGHBOR(最近邻)、BILINEAR(双线性)、BICUBIC(双三次)三种算法在 100x100 缩略图上的效果后选定的——BILINEAR在速度和质量之间取得了最佳平衡,BICUBIC虽然更锐利,但生成时间多出 40%,对课程设计项目而言,纯属过度优化。

4. 部署与调试全流程:从解压到首页,手把手带你避过所有坑

现在,让我们把前面所有的理论,变成你电脑屏幕上真实运行的画面。我会以一个完全零基础、刚装好 IDEA 和 MySQL 的学生视角,记录每一步操作、每一个可能卡住的点,以及对应的解决方案。这不是理想化的“完美流程”,而是包含了真实世界里所有毛刺的“实战日志”。

4.1 环境准备:只做三件事,不多不少

第一步:安装并启动 MySQL
- 下载 MySQL Community Server 8.0(推荐官网,避免第三方打包版的兼容问题)。
- 安装时,务必记住你设置的root 用户密码(比如我设的是123456)。
- 安装完成后,打开命令行(Windows 是 CMD,macOS/Linux 是 Terminal),输入mysql -u root -p,回车后输入密码,如果看到mysql>提示符,说明 MySQL 服务已启动。

注意:很多学生卡在这一步,报错Can't connect to local MySQL server through socket。这通常是因为 MySQL 服务没启动。Windows 用户去“服务”管理器里找到MySQL80,右键“启动”;macOS 用户用brew services start mysql;Linux 用户用sudo systemctl start mysqld

第二步:导入数据库脚本
- 找到压缩包里的database.sql文件(通常在album-sql目录下)。
- 在 MySQL 命令行里,依次执行:
sql CREATE DATABASE album_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE album_db; SOURCE /path/to/your/database.sql; -- 替换为你的实际路径,Windows 用反斜杠,如 C:\album\database.sql
- 执行完后,输入SHOW TABLES;,你应该能看到album_categoryalbum_photoalbum_user等 5 张表。

提示:如果SOURCE命令报错,最常见原因是路径里有中文或空格。解决方案:把database.sql文件拷贝到一个纯英文、无空格的路径下,比如C:\album\,然后执行SOURCE C:/album/database.sql;(注意用正斜杠)。

第三步:配置 IDEA(以最新版 2023.3 为例)
- 打开 IDEA,选择Open,找到你解压后的项目根目录(那个名字很长的ebVK1yLoZTw4GYSKKDLB-master-xxxx文件夹)。
- IDEA 会自动识别这是一个 Maven 项目,弹出提示“Import project from external model”,勾选Maven,点击 OK。
- 等待 Maven 自动下载所有依赖(spring-boot-starter-webmybatis-spring-boot-starter等)。这个过程可能需要几分钟,取决于你的网速。不要关闭窗口,不要强制停止。如果卡在某个依赖(比如mysql-connector-java),检查你的 Mavensettings.xml是否配置了国内镜像(阿里云)。

4.2 启动与验证:5 分钟,见证奇迹

第一步:修改数据库密码
- 打开album-all/src/main/resources/application.yml
- 找到spring.datasource.password这一行,把123456改成你 MySQL 的真实 root 密码。
- 保存文件。

第二步:运行主程序
- 在 IDEA 的项目结构里,展开album-allsrcmainjavacom.albumAlbumApplication.java
- 右键AlbumApplication.java,选择Run 'AlbumApplication.main()'
- 观察底部Run窗口的日志。你会看到类似这样的输出:
Tomcat started on port(s): 8080 (http) with context path '' Started AlbumApplication in 3.212 seconds (JVM running for 3.899)
如果看到Started ... in X.XXX seconds,恭喜,启动成功!

第三步:浏览器验证
- 打开浏览器,地址栏输入http://localhost:8080
- 你应该立刻看到一个简洁的登录页面,标题是“Spring Boot 在线相册系统”。
- 使用默认账号登录:用户名admin,密码123456(这个账号是在database.sqlINSERT INTO album_user语句里预置的)。
- 登录后,进入首页,能看到“我的相册”、“上传图片”等导航栏,点击“上传图片”,选择一张 JPG 文件,点击“立即上传”,几秒后弹出“上传成功”提示,并自动跳转到图片列表页。

常见问题速查表:

现象可能原因解决方案
启动时报错Failed to configure a DataSourceapplication.yml里的spring.datasource.urlpassword写错了,或者 MySQL 服务没启动检查 URL 格式是否为jdbc:mysql://localhost:3306/album_db?...;确认 MySQL 服务正在运行;用 Navicat 或 DBeaver 连接一下album_db库,看是否能通
浏览器打开localhost:8080显示 404album-all模块没被正确识别为 Spring Boot 主应用,或者AlbumApplication.java不在默认包路径下确认AlbumApplication.java的 package 声明是package com.album;;在 IDEA 的Run Configuration里,检查Main class是否指向了正确的AlbumApplication
上传图片后,列表页看不到图片,或者图片显示为破损图标album.upload.base-path路径不可写,或者photo_path字段在数据库里存的是相对路径而非绝对路径检查application.yml里的album.upload.base-path是否指向一个你有写入权限的文件夹(比如D:/album/upload);打开数据库,查album_photo表,确认photo_path字段存的是类似D:/album/upload/photos/2024/05/20/xxx.jpg的绝对路径,而不是photos/2024/05/20/xxx.jpg
登录时提示“用户名或密码错误”默认账号密码被改过,或者album_user表里status字段为 0(禁用状态)用 MySQL 命令行执行SELECT username, password, status FROM album_user;,确认admin用户的status是 1;如果密码被加密了(比如是 BCrypt),说明项目用了 Spring Security,但这个课程设计版本默认是明文密码,直接查password字段即可

4.3 二次开发入门:改一个功能,胜过读十页文档

现在系统跑起来了,下一步就是“为我所用”。课程设计不是交一份能跑的代码,而是要体现你的理解和改造能力。这里给你三个最安全、最见效的改造方向,每个都能在答辩时成为加分项。

方向一:给相册分类加图标
- 需求:每个相册分类(如“校园风景”、“社团活动”)前面显示一个小图标,提升视觉辨识度。
- 步骤:
1. 在album-all/src/main/resources/static/css/style.css末尾,添加新 CSS 类:
css .category-icon-campus { background: url('../images/icon-campus.png') no-repeat center; width: 24px; height: 24px; display: inline-block; } .category-icon-club { background: url('../images/icon-club.png') no-repeat center; width: 24px; height: 24px; display: inline-block; }
2. 在album-all/src/main/resources/static/html/category-list.html的循环里,把<span>${category.name}</span>改成:
html <span class="category-icon-${category.code}"></span> ${category.name}
(前提是你的album_category表里有个code字段,存campusclub这样的值;如果没有,用ALTER TABLE album_category ADD COLUMN code VARCHAR(50);加一个)
3. 把两张 PNG 图标放到static/images/目录下。
- 效果:列表页的分类名前面,会显示对应的小图标。这个改动只涉及前端,零风险,但视觉效果立竿见影。

方向二:限制用户只能看到自己的图片
- 需求:当前系统是管理员视角,能看到所有用户上传的图片。课程设计要求“用户私密相册”,即 A 用户登录后,只能看到自己上传的图片。
- 步骤:
1. 修改PhotoController.javalistPhotos方法,在查询条件里加上createBy = currentUserId
java @GetMapping("/list") public Result<List<AlbumPhoto>> listPhotos(HttpServletRequest request) { Long userId = getCurrentUserId(request); // 从 Session 获取 List<AlbumPhoto> photos = photoService.listPhotosByUserId(userId); return Result.success(photos); }
2. 在PhotoService接口里添加新方法listPhotosByUserId(Long userId),并在PhotoServiceImpl里实现它,SQL 查询语句加上WHERE create_by = #{userId}
- 效果:登录不同账号,看到的图片列表完全不同。这个改动触及了核心业务逻辑,展示了你对数据权限的理解。

方向三:增加图片搜索功能
- 需求:在图片列表页顶部加一个搜索框,支持按“原始文件名”模糊搜索。
- 步骤:
1. 在photo-list.html<div class="page-header">里,添加搜索表单:
html <form id="searchForm" class="form-inline"> <input type="text" name="keyword" class="form-control" placeholder="搜索图片名称..."> <button type="submit" class="btn btn-outline-secondary">搜索</button> </form>
2. 在PhotoController.java里,给listPhotos方法加一个@RequestParam(required = false) String keyword参数,并在 Service 层的查询逻辑里,动态拼接AND original_name LIKE CONCAT('%', #{keyword}, '%')
- 效果:输入“毕业”,就能列出所有文件名包含“毕业”的图片。这个功能简单,但完整涵盖了“前端交互 + 后端参数接收 + 动态 SQL 构建”三个知识点。

5. 总结与延伸:这个项目教会我的,远不止是 Spring Boot 语法

在我带过的所有课程设计项目里,这个在线相册系统有一个特别之处:它从不追求“高大上”的技术名词,却在每一个平凡的角落,埋藏着软件工程最朴素的真理。比如,album-common模块教会我的,不是如何写一个工具类,而是抽象的价值——当你把“上传文件”这件事,从“Controller 里写一堆 if-else”提炼成一个独立的、有明确输入输出的UploadResult对象时,你就开始理解什么是“关注点分离”。再比如,mybatis-generator不是让你偷懒,而是逼你直面数据契约的重要性:数据库表结构一旦确定,代码就该是它的“影子”,而不是“猜测”。你改一个字段,generator 会立刻用编译错误告诉你:“嘿,你的 Java 类和数据库已经对不上了。”

最让我感慨的是那个DataBaseER.png。很多学生把它当成一个应付检查的装饰画,匆匆扫一眼就关掉。但有一次,一个学生在调试“为什么新建的图片没显示在相册里”时,卡了整整两天。最后,他重新打开 ER 图,盯着album_photo.category_idalbum_category.id之间的连线看了五分钟,突然说:“老师,album_photo表里的category_id,是不是应该等于album_category表里的id,而不是code?”——那一刻,他不是在查 Bug,而是在阅读数据世界的地图。这张图的意义,从来不是展示“我们画了 ER 图”,而是训练一种思维习惯:在写任何一行代码之前,先想清楚数据在哪里,它们之间如何关联

所以,如果你正为课程设计发愁,别急着去 GitHub 上找“最火的 Spring Boot 项目”。静下心来,把这个相册系统从头到尾跑一遍,改一个图标,调一个参数,修一个 Bug。当你在application.yml里把8080改成8081,然后在浏览器里输入localhost:8081看到熟悉的登录页时,那种亲手掌控技术的踏实感,是任何框架文档都无法给予的。它不承诺你成为架构师,但它保证,当你走出校门,面对一个真实的、带着各种历史包袱的业务系统时,你知道第一步该看哪里,第二步该改什么,第三步该怎么测试——而这,恰恰是学校教育和职场需求之间,最珍贵的那一座桥。

本文还有配套的精品资源,点击获取

简介:直接可运行的JavaWeb相册管理项目,基于Spring Boot 2.x + MyBatis + MySQL构建,专为高校课程设计和期末大作业准备。支持用户账号登录、多级相册分类、图片上传/下载/预览、自动生成缩略图、分页浏览及基础权限控制。压缩包内含全部模块源码(album-common公共模块、album-mybatis-generator代码生成器配置、album-all主应用)、已验证可用的database.sql建库脚本、可视化ER关系图(DataBaseER.png)、标准Maven配置(pom.xml)、前端静态页面(HTML/CSS/JS)以及详细部署说明(README.md)。本地开发环境无需额外配置,导入IDEA或Eclipse后启动内置Tomcat,访问localhost:8080即可进入首页。所有功能均经实测通过,适合作为JavaWeb实践教学案例或二次开发起点。


本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询