数据库课程设计实践:构建DeOldify图像处理任务管理系统
数据库课程设计实践:构建DeOldify图像处理任务管理系统
每次数据库课程设计,是不是都感觉在做“学生信息管理系统”或者“图书管理系统”?这些经典题目虽然能练手,但总觉得离现在火热的技术有点远。这次,我们来点不一样的——用数据库技术,亲手搭建一个能调用AI模型、处理真实任务的图像上色管理系统。
想象一下,用户上传一张黑白老照片,系统自动调用DeOldify模型为它上色,然后返回一张色彩生动的图片。这背后,需要一个可靠的系统来管理用户、排队任务、记录状态。这不正是数据库大显身手的地方吗?这个项目将带你从零开始,设计数据库表、编写后端API,并最终与星图GPU平台上的DeOldify服务对接,完成一个完整的、有实际应用价值的系统。它不仅能帮你巩固数据库知识,还能让你亲手体验如何将AI能力集成到自己的应用中。
1. 项目概述与核心价值
这个项目本质上是一个“任务调度与状态管理”系统。它的核心业务流程非常清晰:用户提交任务,系统记录并排队,调用AI服务处理,更新状态并返回结果。整个过程,数据库扮演着“大脑”和“记事本”的角色,确保每个环节的信息都不丢失、不错乱。
对于学习者而言,这个项目的价值是多方面的。首先,它跳出了传统管理系统的框架,引入了“异步任务”和“外部服务集成”这两个在现代Web开发中极其常见的概念。其次,你将面对真实的技术选型问题,比如图片怎么存、任务状态怎么设计、如何保证API的健壮性。最后,当你看到自己写的代码成功调起AI模型,将一张黑白照片变得色彩斑斓时,那种成就感是无可比拟的。这不仅仅是一次课程作业,更是一个可以写进简历的、能体现你综合工程能力的实战项目。
2. 系统架构与核心流程
在动手写代码之前,我们先在脑子里把整个系统跑一遍。一个简化的系统架构可以分为三层:表现层、应用层和数据层。
表现层就是用户直接交互的界面,可能是一个简单的网页表单,让用户上传图片并点击“开始上色”。应用层是我们后端服务的核心,它接收前端的请求,与数据库和AI服务打交道。数据层就是我们的MySQL数据库,负责持久化存储所有信息。而DeOldify模型则作为一个独立的AI服务,部署在星图GPU平台上,等待我们的调用。
整个核心流程就像一条流水线:
- 用户通过前端页面上传一张黑白图片。
- 后端API接收到图片,立即在数据库中创建一条新的“任务”记录,状态标记为“等待中”,并把图片保存到服务器的某个目录(或对象存储),将路径记入数据库。
- 后端有一个任务调度器(可以是一个独立的进程或线程),定时扫描数据库中状态为“等待中”的任务。
- 调度器取出一个任务,调用星图平台上的DeOldify服务API,将图片路径或二进制数据发送过去。
- 调用成功后,将数据库中该任务的状态更新为“处理中”。
- DeOldify服务处理完成后(可能是异步回调,也可能是我们轮询结果),后端获取到处理后的彩色图片。
- 后端将彩色图片保存,更新数据库中的任务状态为“已完成”,并记录彩色图片的存储路径和完成时间。
- 用户在前端可以通过任务ID查询状态,当状态变为“已完成”时,前端就能展示或提供下载链接了。
这个流程里,数据库在每一步都至关重要,它记录了任务的“生命轨迹”。
3. 数据库设计与实现
数据库设计是整个系统的基石,设计得好,后续开发会非常顺畅。我们主要需要三张核心表。
3.1 核心表结构设计
用户表 (users)这张表管理系统的使用者。虽然我们这个demo可能不需要复杂的用户体系,但设计上保留它以体现完整性。
CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '用户名,用于登录', `email` varchar(100) DEFAULT NULL COMMENT '邮箱', `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';任务表 (tasks)这是系统的核心表,记录了每一个上色任务的完整信息。字段设计需要仔细考虑。
CREATE TABLE `tasks` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) DEFAULT NULL COMMENT '提交任务的用户ID,关联users.id', `task_name` varchar(255) DEFAULT '未命名任务' COMMENT '任务名称,用户可自定义', `original_image_path` varchar(500) NOT NULL COMMENT '原始黑白图片的服务器存储路径', `processed_image_path` varchar(500) DEFAULT NULL COMMENT '处理后的彩色图片存储路径', `status` enum('pending', 'processing', 'completed', 'failed') NOT NULL DEFAULT 'pending' COMMENT '任务状态:等待中、处理中、已完成、失败', `submit_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '任务提交时间', `start_time` timestamp NULL DEFAULT NULL COMMENT '任务开始处理时间', `completion_time` timestamp NULL DEFAULT NULL COMMENT '任务完成时间', `error_message` text COMMENT '如果任务失败,记录错误信息', PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`), KEY `idx_status` (`status`), KEY `idx_submit_time` (`submit_time`), CONSTRAINT `fk_tasks_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='图像上色任务表';字段设计思路:
status使用枚举类型,明确限定几种状态,比用数字代码更直观。original_image_path和processed_image_path存储的是图片在服务器上的相对或绝对路径。在实际生产环境中,更推荐将图片存入对象存储(如阿里云OSS),这里存储的就是文件的URL。- 索引的创建:
user_id用于查询某个用户的所有任务;status和submit_time是任务调度器最常用来查询的字段,加上索引能极大提升扫描效率。 - 时间戳字段清晰地记录了任务的生命周期,便于后期统计和分析。
(可选)任务参数表 (task_parameters)如果未来DeOldify服务支持调整参数(如上色强度、艺术风格),我们可以用这张表来扩展,实现更灵活的任务管理。这里采用与任务表一对一的关联。
CREATE TABLE `task_parameters` ( `task_id` int(11) NOT NULL COMMENT '关联的任务ID', `render_factor` int(11) DEFAULT 35 COMMENT '渲染因子,影响上色效果强度', `artistic` tinyint(1) DEFAULT 0 COMMENT '是否使用艺术模式', `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`task_id`), CONSTRAINT `fk_params_task` FOREIGN KEY (`task_id`) REFERENCES `tasks` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务参数表';3.2 数据关系与操作
用户和任务之间是一对多的关系(一个用户可以提交多个任务)。任务和任务参数之间是一对一的关系。
主要的数据库操作集中在任务表上:
- 插入 (Create):用户提交任务时,插入一条状态为
pending的新记录。 - 查询 (Read):前端查询任务列表或单个任务详情;任务调度器查询状态为
pending的任务。 - 更新 (Update):任务状态流转(
pending->processing->completed/failed)时,更新对应字段,特别是status,start_time,completion_time。 - 删除 (Delete):通常我们不物理删除任务,而是通过状态过滤。必要时可提供管理功能。
4. 后端服务开发实战
数据库设计好后,我们开始用Python(Flask框架为例)搭建后端服务。项目结构可以这样组织:
deoldify-management-system/ ├── app.py # 应用主入口 ├── config.py # 配置文件 ├── models.py # 数据库模型(使用SQLAlchemy) ├── routes.py # API路由 ├── services/ # 业务逻辑层 │ ├── task_service.py # 任务管理服务 │ └── deoldify_client.py # DeOldify服务调用客户端 ├── utils/ # 工具函数 │ └── file_handler.py # 文件上传保存工具 └── requirements.txt # 项目依赖4.1 核心API接口实现
我们首先实现两个最关键的API:提交任务和查询任务状态。
1. 提交任务接口 (POST /api/tasks)这个接口接收用户上传的图片,保存文件,并在数据库中创建任务记录。
# routes.py from flask import request, jsonify from services.task_service import TaskService from utils.file_handler import save_uploaded_image import uuid @app.route('/api/tasks', methods=['POST']) def create_task(): # 1. 接收表单数据和文件 if 'image' not in request.files: return jsonify({'error': 'No image file provided'}), 400 file = request.files['image'] task_name = request.form.get('task_name', '未命名任务') if file.filename == '': return jsonify({'error': 'No selected file'}), 400 # 2. 保存上传的图片到本地目录 original_filename = file.filename # 生成唯一文件名,防止冲突 saved_filename = f"{uuid.uuid4().hex}_{original_filename}" original_image_path = save_uploaded_image(file, saved_filename) # 3. 调用服务层,创建数据库记录 try: # 假设当前用户ID为1(演示用,真实场景从会话获取) new_task = TaskService.create_task( user_id=1, task_name=task_name, original_image_path=original_image_path ) return jsonify({ 'message': 'Task created successfully', 'task_id': new_task.id, 'status': new_task.status }), 201 except Exception as e: # 如果数据库操作失败,可以考虑删除已保存的图片 # os.remove(original_image_path) return jsonify({'error': str(e)}), 5002. 查询任务状态接口 (GET /api/tasks/int:task_id)这个接口让前端能轮询或直接查看任务的处理结果。
# routes.py @app.route('/api/tasks/<int:task_id>', methods=['GET']) def get_task_status(task_id): task = TaskService.get_task_by_id(task_id) if not task: return jsonify({'error': 'Task not found'}), 404 response_data = { 'task_id': task.id, 'task_name': task.task_name, 'status': task.status, 'submit_time': task.submit_time.isoformat() if task.submit_time else None, 'completion_time': task.completion_time.isoformat() if task.completion_time else None, } # 如果任务已完成,返回处理后的图片访问地址 if task.status == 'completed' and task.processed_image_path: # 这里拼接一个完整的HTTP访问URL,而不是服务器路径 response_data['processed_image_url'] = f'/static/processed/{os.path.basename(task.processed_image_path)}' return jsonify(response_data), 2004.2 任务调度与DeOldify服务集成
任务创建后处于“等待中”,我们需要一个“工人”来消费这些任务。这里实现一个简单的调度器,可以是一个独立的脚本,通过定时任务(如cron)或后台线程运行。
任务调度器核心逻辑 (services/task_service.py)
# services/task_service.py (部分代码) import requests from models import db, Task from config import Config from .deoldify_client import DeOldifyClient import time class TaskService: # ... 其他方法 ... @staticmethod def process_pending_tasks(): """获取并处理一个等待中的任务""" # 使用数据库事务和行锁,防止多个调度器处理同一个任务 # 这里简化处理,实际应考虑更严谨的并发控制 task = Task.query.filter_by(status='pending').order_by(Task.submit_time.asc()).first() if not task: print("No pending tasks found.") return print(f"Processing task {task.id}...") task.status = 'processing' task.start_time = db.func.now() db.session.commit() try: # 调用DeOldify客户端处理图片 deoldify_client = DeOldifyClient(api_url=Config.DEOLDIFY_API_URL) processed_image_path = deoldify_client.colorize_image(task.original_image_path) # 更新任务为完成状态 task.status = 'completed' task.processed_image_path = processed_image_path task.completion_time = db.func.now() db.session.commit() print(f"Task {task.id} completed successfully.") except Exception as e: # 处理失败 task.status = 'failed' task.error_message = str(e) db.session.commit() print(f"Task {task.id} failed: {e}")DeOldify服务客户端 (services/deoldify_client.py)这部分代码负责与星图GPU平台上部署的DeOldify服务进行通信。你需要根据平台提供的具体API文档进行调整。
# services/deoldify_client.py import requests import os import time class DeOldifyClient: def __init__(self, api_url, api_key=None): self.api_url = api_url # 例如:http://your-deoldify-service-address/predict self.api_key = api_key self.headers = {'Content-Type': 'application/json'} if api_key: self.headers['Authorization'] = f'Bearer {api_key}' def colorize_image(self, image_path): """调用DeOldify API为图片上色""" # 方式1:如果API支持文件上传 with open(image_path, 'rb') as img_file: files = {'image': img_file} response = requests.post(self.api_url, files=files, headers=self.headers) # 方式2:如果API需要base64编码或图片URL # 需要根据实际API要求调整 if response.status_code != 200: raise Exception(f"DeOldify API error: {response.status_code}, {response.text}") result = response.json() # 假设API返回处理后的图片base64数据或URL # 这里需要将返回的图片数据保存到本地,并返回保存路径 processed_data = result.get('processed_image') # ... 保存图片的逻辑 ... saved_path = self._save_processed_image(processed_data, image_path) return saved_path def _save_processed_image(self, image_data, original_path): """保存处理后的图片,生成新文件名""" import base64 from PIL import Image import io # 示例:处理base64数据 image_bytes = base64.b64decode(image_data) image = Image.open(io.BytesIO(image_bytes)) # 生成新文件名,例如在原文件名上加‘_colored’ original_name = os.path.basename(original_path) name_without_ext, ext = os.path.splitext(original_name) new_filename = f"{name_without_ext}_colored{ext}" save_path = os.path.join(Config.PROCESSED_IMAGE_FOLDER, new_filename) image.save(save_path) return save_path5. 前端界面与系统演示
后端逻辑完成后,一个简单的前端界面能让整个系统“活”起来。我们可以用最基础的HTML+JavaScript来实现。
核心功能页面 (index.html)
<!DOCTYPE html> <html> <head> <title>老照片上色系统</title> <style> .container { max-width: 800px; margin: auto; padding: 20px; } .upload-area { border: 2px dashed #ccc; padding: 40px; text-align: center; margin-bottom: 20px; } .task-list { margin-top: 30px; } .task-item { border: 1px solid #eee; padding: 10px; margin-bottom: 10px; } .status-pending { color: orange; } .status-processing { color: blue; } .status-completed { color: green; } .status-failed { color: red; } </style> </head> <body> <div class="container"> <h1>老照片上色系统</h1> <div class="upload-area"> <input type="file" id="imageInput" accept="image/*"> <input type="text" id="taskName" placeholder="给任务起个名字(可选)"> <button onclick="submitTask()">开始上色</button> </div> <div id="currentTaskInfo" style="display:none;"> <h3>当前任务状态</h3> <p>任务ID: <span id="currentTaskId"></span></p> <p>状态: <span id="currentTaskStatus" class="status-pending"></span></p> <img id="processedImage" style="max-width:100%; display:none; margin-top:15px;"> </div> <div class="task-list"> <h3>历史任务</h3> <div id="taskListContainer"></div> </div> </div> <script> let currentTaskId = null; let pollInterval = null; async function submitTask() { const fileInput = document.getElementById('imageInput'); const taskNameInput = document.getElementById('taskName'); if (!fileInput.files[0]) { alert('请选择一张图片'); return; } const formData = new FormData(); formData.append('image', fileInput.files[0]); formData.append('task_name', taskNameInput.value || '未命名任务'); try { const response = await fetch('/api/tasks', { method: 'POST', body: formData }); const result = await response.json(); if (response.ok) { currentTaskId = result.task_id; document.getElementById('currentTaskId').textContent = currentTaskId; document.getElementById('currentTaskStatus').textContent = result.status; document.getElementById('currentTaskInfo').style.display = 'block'; startPollingTaskStatus(); } else { alert('提交失败: ' + result.error); } } catch (error) { alert('网络错误: ' + error); } } function startPollingTaskStatus() { if (pollInterval) clearInterval(pollInterval); pollInterval = setInterval(async () => { const response = await fetch(`/api/tasks/${currentTaskId}`); const task = await response.json(); const statusElem = document.getElementById('currentTaskStatus'); statusElem.textContent = task.status; statusElem.className = `status-${task.status}`; if (task.status === 'completed') { clearInterval(pollInterval); const imgElem = document.getElementById('processedImage'); imgElem.src = task.processed_image_url; imgElem.style.display = 'block'; loadTaskList(); // 刷新历史任务列表 } else if (task.status === 'failed') { clearInterval(pollInterval); alert('任务处理失败'); } }, 2000); // 每2秒轮询一次 } async function loadTaskList() { const response = await fetch('/api/tasks?limit=10'); const tasks = await response.json(); const container = document.getElementById('taskListContainer'); container.innerHTML = tasks.map(task => ` <div class="task-item"> <strong>${task.task_name}</strong> (ID: ${task.task_id})<br> 状态: <span class="status-${task.status}">${task.status}</span><br> 提交时间: ${new Date(task.submit_time).toLocaleString()} </div> `).join(''); } // 页面加载时获取历史任务 window.onload = loadTaskList; </script> </body> </html>这个前端页面实现了文件上传、任务状态实时轮询、历史任务展示等核心功能。用户上传图片后,页面会显示当前任务ID和状态,并自动刷新,直到任务完成并展示彩色图片。
6. 项目总结与扩展思考
走完整个开发流程,你会发现这个项目把数据库课程里的许多知识点都串了起来:ER设计、SQL语句、索引优化、事务概念,并且在真实的业务逻辑中得到了应用。更重要的是,你接触到了API设计、异步任务处理、外部服务集成这些在现代应用开发中必不可少的技能。
实际部署运行时,你可能会遇到一些挑战,比如图片上传的并发处理、任务调度器的稳定性、DeOldify服务调用超时或失败的重试机制等。这些都是很好的扩展方向。例如,你可以引入消息队列(如Redis或RabbitMQ)来解耦任务提交和处理,让系统更健壮;或者为任务添加优先级;甚至开发一个管理员后台,来监控所有任务的执行情况。
这个项目就像一个技术骨架,你可以根据自己的兴趣往里填充更多“肌肉”。比如,尝试用Docker容器化部署整个应用,或者用Vue/React重构一个更美观的前端。通过这样一个从设计到实现的完整项目,你收获的将不仅仅是一个数据库课程设计的分数,更是一套解决实际问题的工程思维和动手能力。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
