2025/12/9 每日总结 系统整合与业务落地:从独立模块到完整Web应用

系统整合与业务落地:从独立模块到完整Web应用

在前两篇博客中,我们已经实现了故事生成、插图匹配、语音合成三大核心模块的独立功能。但单一模块无法构成可用产品,本次我们聚焦“系统整合”与“业务落地”——将零散模块串联成自动化工作流,优化数据库支撑多业务场景,实现用户管理与故事管理核心功能,让平台从“技术工具”升级为“能直接面向用户的完整Web应用”。

一、核心目标:打破模块壁垒,实现端到端自动化

三大模块独立运行时,需用户手动触发各步骤,体验割裂。本次整合的核心目标是:用户输入关键词后,平台自动完成“故事生成→插图匹配→音频合成→数据存储→结果展示”全流程,无需人工干预,同时支持用户专属化管理与故事资源维护。
为实现这一目标,需解决三大核心问题:

  1. 模块联动:确保各模块参数自动传递(如故事正文同步给插图、音频模块);

  2. 数据支撑:优化数据库结构,适配用户、故事、标签的多维度关联;

  3. 业务闭环:实现“生成-存储-管理-展示”全链路,满足用户实际使用需求。

二、数据库优化:构建“用户-故事-资源”多维度关联体系

独立模块仅需storys表存储基础数据,完整应用需支撑用户专属、标签复用等功能,因此扩展数据库表结构,形成多表关联体系。

1. 核心表结构设计(新增+优化)

(1)用户表(users):支撑用户管理

字段名 类型 说明
id INT 主键,自增,用户唯一标识
email 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.idstorys.user_id(一个用户可拥有多个故事,一个故事归属一个用户);

  • 多对多:storys.idtags.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)}), 500
    

    3. 关键优化:确保流程稳定

  • 顺序执行:严格按照“文本→插图→音频”顺序调用,避免依赖缺失;

  • 状态校验:每个模块生成后校验状态,失败则直接返回错误,避免无效流程;

  • 数据关联:将用户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": "退出成功"}), 200
    

    2. 故事管理模块(查询+收藏+删除)

    (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接口中对故事文本进行标准化处理,确保titlecontent等字段统一传递。

    2. 数据库关联查询效率低

  • 问题:多表关联查询个人故事时,数据量增大后响应变慢;

  • 解决方案:为storys.user_idstory_tag_association.story_id等关联字段创建索引,减少查询扫描范围。

    3. 资源删除不彻底

  • 问题:删除故事时只删除数据库记录,本地插图和音频文件残留;

  • 解决方案:删除故事前先查询资源路径,通过os.remove()删除本地文件,再删除数据库记录。

    4. 未登录用户故事归属问题

  • 问题:未登录用户生成的故事无法关联用户,登录后无法识别;

  • 解决方案:未登录用户生成的故事user_id设为NULL,登录后可通过“绑定历史故事”功能(扩展接口)关联个人账号。

    5. 前端与后端路径不一致

  • 问题:后端返回的img_url(如/images/xxx.png)前端无法访问;

  • 解决方案:在Flask中配置静态文件目录,暴露imagesaudio文件夹:

    # 配置静态文件服务
    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)
    

    六、总结:从“模块”到“产品”的关键跨越

    本次系统整合与业务落地,核心是完成了三大跨越:

  1. 流程跨越:从“手动触发”到“自动联动”,用户输入关键词即可获得完整多媒体故事;
  2. 数据跨越:从“单表存储”到“多表关联”,支撑用户专属、标签复用等业务场景;
  3. 功能跨越:从“生成工具”到“管理平台”,实现注册登录、故事收藏、资源删除等完整业务闭环。
    至此,平台已具备实际使用价值——用户可注册账号,生成专属故事,管理个人资源,体验“可读、可看、可听”的沉浸式服务。
posted @ 2026-01-06 04:15  Moonbeamsc  阅读(5)  评论(0)    收藏  举报
返回顶端