2025/12/9 每日总结 系统整合与业务落地:从独立模块到完整Web应用
系统整合与业务落地:从独立模块到完整Web应用
在前两篇博客中,我们已经实现了故事生成、插图匹配、语音合成三大核心模块的独立功能。但单一模块无法构成可用产品,本次我们聚焦“系统整合”与“业务落地”——将零散模块串联成自动化工作流,优化数据库支撑多业务场景,实现用户管理与故事管理核心功能,让平台从“技术工具”升级为“能直接面向用户的完整Web应用”。
一、核心目标:打破模块壁垒,实现端到端自动化
三大模块独立运行时,需用户手动触发各步骤,体验割裂。本次整合的核心目标是:用户输入关键词后,平台自动完成“故事生成→插图匹配→音频合成→数据存储→结果展示”全流程,无需人工干预,同时支持用户专属化管理与故事资源维护。
为实现这一目标,需解决三大核心问题:
-
模块联动:确保各模块参数自动传递(如故事正文同步给插图、音频模块);
-
数据支撑:优化数据库结构,适配用户、故事、标签的多维度关联;
-
业务闭环:实现“生成-存储-管理-展示”全链路,满足用户实际使用需求。
二、数据库优化:构建“用户-故事-资源”多维度关联体系
独立模块仅需storys表存储基础数据,完整应用需支撑用户专属、标签复用等功能,因此扩展数据库表结构,形成多表关联体系。
1. 核心表结构设计(新增+优化)
(1)用户表(users):支撑用户管理
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | INT | 主键,自增,用户唯一标识 |
| VARCHAR(100) | 唯一标识,用户登录账号(不可重复) | |
| nickname | VARCHAR(50) | 展示名称,登录后页面显示 |
| password | VARCHAR(255) | 加密存储的登录密码(采用BCrypt加密,避免明文泄露) |
| created_at | DATETIME | 账号创建时间 |
| last_login | DATETIME | 最近登录时间 |
(2)标签表(tags):支撑关键词复用
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | INT | 主键,自增 |
| name | VARCHAR(50) | 标签名称(如“宇航员、小狗、月球”) |
| created_at | DATETIME | 标签创建时间 |
(3)故事-标签关联表(story_tag_association):多对多关联
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | INT | 主键,自增 |
| story_id | INT | 关联storys表的主键 |
| tag_id | INT | 关联tags表的主键 |
(4)优化后的故事表(storys):关联用户与资源
在原有字段基础上新增关联字段,确保故事专属化与资源联动:
| 新增字段名 | 类型 | 说明 |
|---|---|---|
| user_id | INT | 关联users表主键,标识故事所属用户(未登录用户为NULL) |
| img | VARCHAR(255) | 插图本地文件路径 |
| img_url | VARCHAR(255) | 插图访问URL |
| img_prompt | VARCHAR(500) | 插图生成提示词 |
| audio_url | VARCHAR(255) | 音频访问URL |
| is_collected | TINYINT(1) | 是否收藏(0=未收藏,1=已收藏) |
2. 数据库关联逻辑
-
一对一:
users.id→storys.user_id(一个用户可拥有多个故事,一个故事归属一个用户); -
多对多:
storys.id↔tags.id(通过story_tag_association关联,一个故事可对应多个标签,一个标签可关联多个故事); -
优势:避免数据冗余(如标签重复存储),支持“最近使用标签”“故事按标签筛选”等功能。
3. 数据库初始化代码(
database.py核心逻辑)import pymysql from config import DATABASE_URL from datetime import datetime import bcrypt class Database: def __init__(self): # 解析数据库URL,建立连接 self.conn = pymysql.connect( host=DATABASE_URL.split('@')[1].split(':')[0], port=int(DATABASE_URL.split(':')[2].split('/')[0]), user=DATABASE_URL.split('//')[1].split(':')[0], password=DATABASE_URL.split(':')[1].split('@')[0], database=DATABASE_URL.split('/')[-1], charset='utf8mb4' ) self.cursor = self.conn.cursor(pymysql.cursors.DictCursor) self._create_tables() # 初始化时创建表 def _create_tables(self): # 创建用户表 self.cursor.execute(""" CREATE TABLE IF NOT EXISTS users ( id INT AUTO_INCREMENT PRIMARY KEY, email VARCHAR(100) UNIQUE NOT NULL, nickname VARCHAR(50) NOT NULL, password VARCHAR(255) NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, last_login DATETIME NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; """) # 创建标签表 self.cursor.execute(""" CREATE TABLE IF NOT EXISTS tags ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; """) # 创建故事表(优化后) self.cursor.execute(""" CREATE TABLE IF NOT EXISTS storys ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255) NOT NULL, summary VARCHAR(500) NOT NULL, content TEXT NOT NULL, keywords VARCHAR(100) NOT NULL, created_time DATETIME DEFAULT CURRENT_TIMESTAMP, user_id INT NULL, img VARCHAR(255) NULL, img_url VARCHAR(255) NULL, img_prompt VARCHAR(500) NULL, audio_url VARCHAR(255) NULL, is_collected TINYINT(1) DEFAULT 0, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; """) # 创建故事-标签关联表 self.cursor.execute(""" CREATE TABLE IF NOT EXISTS story_tag_association ( id INT AUTO_INCREMENT PRIMARY KEY, story_id INT NOT NULL, tag_id INT NOT NULL, FOREIGN KEY (story_id) REFERENCES storys(id) ON DELETE CASCADE, FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; """) self.conn.commit() # 新增用户(密码加密) def add_user(self, email, nickname, password): hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()) self.cursor.execute(""" INSERT INTO users (email, nickname, password) VALUES (%s, %s, %s) """, (email, nickname, hashed_password.decode('utf-8'))) self.conn.commit() return self.cursor.lastrowid # 存储故事(关联用户与标签) def save_story(self, story_data, user_id=None): # 插入故事主数据 self.cursor.execute(""" INSERT INTO storys (title, summary, content, keywords, user_id, img, img_url, img_prompt, audio_url) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) """, ( story_data['title'], story_data['summary'], story_data['content'], story_data['keywords'], user_id, story_data.get('img'), story_data.get('img_url'), story_data.get('img_prompt'), story_data.get('audio_url') )) story_id = self.cursor.lastrowid # 拆分关键词为标签,关联故事与标签 tags = [tag.strip() for tag in story_data['keywords'].split(',')] for tag in tags: if tag: # 查找标签是否已存在,不存在则创建 self.cursor.execute("SELECT id FROM tags WHERE name = %s", (tag,)) tag_result = self.cursor.fetchone() if tag_result: tag_id = tag_result['id'] else: self.cursor.execute("INSERT INTO tags (name) VALUES (%s)", (tag,)) tag_id = self.cursor.lastrowid # 建立故事与标签关联 self.cursor.execute(""" INSERT INTO story_tag_association (story_id, tag_id) VALUES (%s, %s) """, (story_id, tag_id)) self.conn.commit() return story_id三、模块联动:构建自动化工作流(
app.py核心实现)模块联动的核心是“参数自动传递”与“流程顺序控制”,通过Flask框架搭建后端接口,串联三大模块,实现从关键词到多媒体故事的自动化生成。
1. 核心依赖与初始化
from flask import Flask, request, jsonify, session, redirect, url_for from kimi_api import KimiAPI from image_api import ImageGenerationAPI from tts_api import TextToSpeechAPI from database import Database import time app = Flask(__name__) app.secret_key = "story_platform_secret_key" # 用于session管理(用户登录状态) # 初始化各模块与数据库 kimi_api = KimiAPI() image_api = ImageGenerationAPI() tts_api = TextToSpeechAPI() db = Database()2. 自动化生成接口(核心工作流)
@app.route('/generate-story', methods=['POST']) def generate_story(): try: data = request.json keywords = data.get('keywords', '').strip() user_id = session.get('user_id') # 从session获取当前登录用户ID(未登录为None) if not keywords: return jsonify({"status": "failed", "error": "关键词不能为空"}), 400 # 步骤1:生成故事文本(调用Kimi API) story_text = kimi_api.generate_story(keywords) story_text['keywords'] = keywords # 补充关键词字段 # 步骤2:生成配套插图(调用阿里云百炼API) image_prompt = image_api.generate_image_prompt( story_title=story_text['title'], story_content=story_text['content'], keywords=keywords ) image_data = image_api.generate_image_from_text(image_prompt, story_text['title']) if image_data['status'] == 'success': story_text['img'] = image_data['image_path'] story_text['img_url'] = image_data['image_url'] story_text['img_prompt'] = image_prompt else: return jsonify({"status": "failed", "error": f"插图生成失败:{image_data['error']}"}), 500 # 步骤3:生成朗读音频(调用百度TTS API) audio_data = tts_api.generate_speech_from_text(story_text['content'], story_text['title']) if audio_data['status'] == 'success': story_text['audio_url'] = audio_data['audio_url'] else: return jsonify({"status": "failed", "error": f"音频生成失败:{audio_data['error']}"}), 500 # 步骤4:存储到数据库(关联用户与标签) story_id = db.save_story(story_text, user_id) # 步骤5:返回完整结果(供前端展示) return jsonify({ "status": "success", "data": { "story_id": story_id, "title": story_text['title'], "summary": story_text['summary'], "content": story_text['content'], "keywords": keywords, "img_url": story_text['img_url'], "audio_url": story_text['audio_url'], "created_time": time.strftime("%Y-%m-%d %H:%M:%S") } }), 200 except Exception as e: return jsonify({"status": "failed", "error": str(e)}), 5003. 关键优化:确保流程稳定
-
顺序执行:严格按照“文本→插图→音频”顺序调用,避免依赖缺失;
-
状态校验:每个模块生成后校验状态,失败则直接返回错误,避免无效流程;
-
数据关联:将用户ID、标签信息与故事绑定,确保后续管理功能可用;
-
会话管理:通过
session存储用户登录状态,未登录用户生成的故事不关联user_id,登录后自动关联个人账号。四、业务功能实现:用户管理与故事管理
完整应用需满足“用户能注册登录、能管理自己的故事”,因此实现两大核心业务模块。
1. 用户管理模块(注册+登录+退出)
(1)用户注册接口
@app.route('/register', methods=['POST']) def register(): data = request.json email = data.get('email', '').strip() nickname = data.get('nickname', '').strip() password = data.get('password', '').strip() # 校验参数 if not (email and nickname and password): return jsonify({"status": "failed", "error": "邮箱、昵称、密码不能为空"}), 400 if '@' not in email: return jsonify({"status": "failed", "error": "邮箱格式错误"}), 400 # 校验邮箱是否已注册 db.cursor.execute("SELECT id FROM users WHERE email = %s", (email,)) if db.cursor.fetchone(): return jsonify({"status": "failed", "error": "该邮箱已注册"}), 400 # 新增用户 user_id = db.add_user(email, nickname, password) return jsonify({"status": "success", "message": "注册成功", "user_id": user_id}), 201(2)用户登录接口
@app.route('/login', methods=['POST']) def login(): data = request.json email = data.get('email', '').strip() password = data.get('password', '').strip() # 查找用户 db.cursor.execute("SELECT id, nickname, password FROM users WHERE email = %s", (email,)) user = db.cursor.fetchone() if not user: return jsonify({"status": "failed", "error": "邮箱或密码错误"}), 401 # 校验密码(BCrypt解密比对) if not bcrypt.checkpw(password.encode('utf-8'), user['password'].encode('utf-8')): return jsonify({"status": "failed", "error": "邮箱或密码错误"}), 401 # 记录登录状态到session session['user_id'] = user['id'] session['nickname'] = user['nickname'] session['email'] = email # 更新最近登录时间 db.cursor.execute("UPDATE users SET last_login = NOW() WHERE id = %s", (user['id'],)) db.conn.commit() return jsonify({ "status": "success", "message": "登录成功", "data": { "user_id": user['id'], "nickname": user['nickname'], "email": email } }), 200(3)用户退出接口
@app.route('/logout', methods=['POST']) def logout(): # 清除session中的登录状态 session.clear() return jsonify({"status": "success", "message": "退出成功"}), 2002. 故事管理模块(查询+收藏+删除)
(1)查询个人故事(登录后可见)
@app.route('/my-stories', methods=['GET']) def my_stories(): user_id = session.get('user_id') if not user_id: return jsonify({"status": "failed", "error": "请先登录"}), 401 # 查询当前用户的所有故事 db.cursor.execute(""" SELECT id, title, summary, keywords, created_time, img_url, audio_url, is_collected FROM storys WHERE user_id = %s ORDER BY created_time DESC """, (user_id,)) stories = db.cursor.fetchall() return jsonify({ "status": "success", "data": stories }), 200(2)故事收藏/取消收藏
@app.route('/story/<<int:story_id>/collect', methods=['POST']) def collect_story(story_id): user_id = session.get('user_id') if not user_id: return jsonify({"status": "failed", "error": "请先登录"}), 401 # 校验故事是否属于当前用户 db.cursor.execute("SELECT id FROM storys WHERE id = %s AND user_id = %s", (story_id, user_id)) if not db.cursor.fetchone(): return jsonify({"status": "failed", "error": "无权限操作该故事"}), 403 # 切换收藏状态(0→1,1→0) db.cursor.execute(""" UPDATE storys SET is_collected = 1 - is_collected WHERE id = %s """, (story_id,)) db.conn.commit() # 查询更新后的状态 db.cursor.execute("SELECT is_collected FROM storys WHERE id = %s", (story_id,)) is_collected = db.cursor.fetchone()['is_collected'] return jsonify({ "status": "success", "message": "收藏成功" if is_collected else "取消收藏成功", "data": {"is_collected": is_collected} }), 200(3)删除故事(含关联资源)
@app.route('/story/<<int:story_id>/delete', methods=['POST']) def delete_story(story_id): user_id = session.get('user_id') if not user_id: return jsonify({"status": "failed", "error": "请先登录"}), 401 # 查询故事信息(含资源路径) db.cursor.execute(""" SELECT img, audio_url FROM storys WHERE id = %s AND user_id = %s """, (story_id, user_id)) story = db.cursor.fetchone() if not story: return jsonify({"status": "failed", "error": "无权限操作该故事"}), 403 # 步骤1:删除本地资源文件(插图+音频) if story['img'] and os.path.exists(story['img']): os.remove(story['img']) audio_path = story['audio_url'].replace('/audio/', 'audio/') if story['audio_url'] else '' if audio_path and os.path.exists(audio_path): os.remove(audio_path) # 步骤2:删除数据库记录(关联表会自动级联删除) db.cursor.execute("DELETE FROM storys WHERE id = %s", (story_id,)) db.conn.commit() return jsonify({"status": "success", "message": "故事删除成功"}), 200五、整合过程中的坑与解决方案
模块整合与业务落地是最容易出现问题的阶段,以下是核心问题及解决思路:
1. 模块参数不兼容
-
问题:Kimi API返回的故事结构与插图、音频模块的输入参数格式不一致;
-
解决方案:统一数据格式,在
generate_story接口中对故事文本进行标准化处理,确保title、content等字段统一传递。2. 数据库关联查询效率低
-
问题:多表关联查询个人故事时,数据量增大后响应变慢;
-
解决方案:为
storys.user_id、story_tag_association.story_id等关联字段创建索引,减少查询扫描范围。3. 资源删除不彻底
-
问题:删除故事时只删除数据库记录,本地插图和音频文件残留;
-
解决方案:删除故事前先查询资源路径,通过
os.remove()删除本地文件,再删除数据库记录。4. 未登录用户故事归属问题
-
问题:未登录用户生成的故事无法关联用户,登录后无法识别;
-
解决方案:未登录用户生成的故事
user_id设为NULL,登录后可通过“绑定历史故事”功能(扩展接口)关联个人账号。5. 前端与后端路径不一致
-
问题:后端返回的
img_url(如/images/xxx.png)前端无法访问; -
解决方案:在Flask中配置静态文件目录,暴露
images和audio文件夹:# 配置静态文件服务 app.static_folder = 'static' app.add_url_rule('/images/<path:filename>', 'images', build_only=True) app.add_url_rule('/audio/<path:filename>', 'audio', build_only=True)六、总结:从“模块”到“产品”的关键跨越
本次系统整合与业务落地,核心是完成了三大跨越:
- 流程跨越:从“手动触发”到“自动联动”,用户输入关键词即可获得完整多媒体故事;
- 数据跨越:从“单表存储”到“多表关联”,支撑用户专属、标签复用等业务场景;
- 功能跨越:从“生成工具”到“管理平台”,实现注册登录、故事收藏、资源删除等完整业务闭环。
至此,平台已具备实际使用价值——用户可注册账号,生成专属故事,管理个人资源,体验“可读、可看、可听”的沉浸式服务。

浙公网安备 33010602011771号