基于蓝耘元生代MaaS平台的视觉模型应用开发实战

一、引言:视觉生成AI的技术革命

随着多模态人工智能技术的快速发展,视觉模型生成能力正成为企业创新应用的核心驱动力。蓝耘元生代MaaS平台提供的视觉模型API,使开发者能够轻松集成先进的视频生成功能到各类应用中。本文将全面解析如何基于该平台开发一个完整的LLM视觉模型应用,涵盖前端界面设计、后端服务实现以及系统集成的最佳实践。

1.1 视觉生成AI的市场需求

视觉内容在现代数字营销、教育培训和娱乐产业中占据核心地位。传统视频制作需要专业团队、昂贵设备和大量时间,而AI视频生成技术将这些门槛大幅降低:

  • 内容创作:营销视频、产品演示、社交媒体内容
  • 教育培训:教学视频、模拟演示、交互式学习材料
  • 娱乐产业:短视频生成、虚拟偶像、互动叙事

1.2 技术架构概述

本文将实现的系统采用前后端分离架构:

用户界面
HTTP请求
Nginx反向代理
前端静态资源
Spring Boot后端
蓝耘元生代MaaS API
MySQL数据库
Redis缓存
异步任务处理
视频生成服务

二、环境准备与平台接入

2.1 注册平台账号并获取API密钥

首先,我们需要访问蓝耘元生代代码算云平台并完成注册:

  1. 访问 https://console.lanyun.net/#/register?source=2
  2. 完成企业邮箱注册和身份验证
  3. 进入API管理界面创建API Key

2.2 项目初始化与依赖配置

前端项目初始化
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AI视觉模型应用平台</title>
    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <!-- Font Awesome 图标 -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
    <!-- 自定义样式 -->
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <!-- 导航栏 -->
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand" href="#">
                <i class="fas fa-robot"></i> AI视觉生成平台
            </a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav ms-auto">
                    <li class="nav-item">
                        <a class="nav-link active" href="#">首页</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="#features">功能</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="#api">API文档</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="#contact">联系我们</a>
                    </li>
                </ul>
            </div>
        </div>
    </nav>

    <!-- 主要内容区域 -->
    <main id="app">
        <!-- 将在后续章节填充 -->
    </main>

    <!-- JavaScript 库 -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <!-- 自定义JS -->
    <script src="js/app.js"></script>
</body>
</html>
后端Spring Boot项目配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>com.example</groupId>
    <artifactId>ai-vision-platform</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>
        <relativePath/>
    </parent>
    
    <properties>
        <java.version>11</java.version>
        <lombok.version>1.18.24</lombok.version>
    </properties>
    
    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- Spring Boot Data JPA -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        
        <!-- MySQL 连接器 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        
        <!-- Redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
        
        <!-- 测试依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2.3 配置管理类实现

package com.example.aivisionplatform.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.client.RestTemplate;

@Configuration
public class AppConfig {
    
    @Value("${maas.api.key}")
    private String maasApiKey;
    
    @Value("${maas.base.url}")
    private String maasBaseUrl;
    
    @Value("${redis.host}")
    private String redisHost;
    
    @Value("${redis.port}")
    private int redisPort;
    
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
    
    @Bean
    public JedisConnectionFactory jedisConnectionFactory() {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
        config.setHostName(redisHost);
        config.setPort(redisPort);
        return new JedisConnectionFactory(config);
    }
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(jedisConnectionFactory());
        return template;
    }
    
    // Getter方法用于获取配置值
    public String getMaasApiKey() {
        return maasApiKey;
    }
    
    public String getMaasBaseUrl() {
        return maasBaseUrl;
    }
}

三、前端界面设计与实现

3.1 用户界面布局设计

基于Bootstrap 5设计响应式用户界面,包含以下主要功能区域:

  1. 视频生成参数面板:用于设置生成参数
  2. 预览区域:显示生成的视频和进度
  3. 任务历史面板:展示历史生成任务
  4. 账户信息面板:显示API使用情况和余额

在这里插入图片描述

图1:前端界面布局设计

3.2 视频生成表单实现

<div class="container mt-5">
    <div class="row">
        <div class="col-md-6">
            <div class="card">
                <div class="card-header">
                    <h5><i class="fas fa-video"></i> 视频生成参数</h5>
                </div>
                <div class="card-body">
                    <form id="videoGenerationForm">
                        <div class="mb-3">
                            <label for="modelSelect" class="form-label">选择模型</label>
                            <select class="form-select" id="modelSelect" required>
                                <option value="">-- 请选择模型 --</option>
                                <option value="I2V-01">I2V-01 (图像到视频)</option>
                                <option value="I2V-01-Director">I2V-01-Director (导演模式)</option>
                                <option value="T2V-01">T2V-01 (文本到视频)</option>
                                <option value="T2V-01-Director">T2V-01-Director (文本导演模式)</option>
                            </select>
                        </div>
                        
                        <div class="mb-3">
                            <label for="promptInput" class="form-label">提示词</label>
                            <textarea class="form-control" id="promptInput" rows="3" 
                                      placeholder="请输入视频描述..." required></textarea>
                            <div class="form-text">提示词优化: 
                                <div class="form-check form-check-inline">
                                    <input class="form-check-input" type="radio" name="promptOptimizer" id="optimizerYes" value="true" checked>
                                    <label class="form-check-label" for="optimizerYes">开启</label>
                                </div>
                                <div class="form-check form-check-inline">
                                    <input class="form-check-input" type="radio" name="promptOptimizer" id="optimizerNo" value="false">
                                    <label class="form-check-label" for="optimizerNo">关闭</label>
                                </div>
                            </div>
                        </div>
                        
                        <div class="mb-3">
                            <label for="firstFrameImage" class="form-label">首帧图片</label>
                            <input type="file" class="form-control" id="firstFrameImage" accept="image/*">
                            <div class="form-text">仅图像到视频模型需要</div>
                        </div>
                        
                        <div class="mb-3">
                            <label for="subjectReferences" class="form-label">主体参考图片</label>
                            <input type="file" class="form-control" id="subjectReferences" accept="image/*" multiple>
                            <div class="form-text">可上传多张图片作为参考</div>
                        </div>
                        
                        <div class="d-grid">
                            <button type="submit" class="btn btn-primary" id="generateBtn">
                                <i class="fas fa-magic"></i> 生成视频
                            </button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
        
        <div class="col-md-6">
            <div class="card">
                <div class="card-header">
                    <h5><i class="fas fa-play-circle"></i> 视频预览</h5>
                </div>
                <div class="card-body text-center">
                    <div id="previewArea" class="d-none">
                        <video id="generatedVideo" controls class="w-100"></video>
                        <div class="mt-3">
                            <a id="downloadLink" class="btn btn-success btn-sm">
                                <i class="fas fa-download"></i> 下载视频
                            </a>
                        </div>
                    </div>
                    <div id="emptyPreview" class="text-muted py-5">
                        <i class="fas fa-film fa-3x mb-3"></i>
                        <p>视频生成后将显示在这里</p>
                    </div>
                    <div id="progressArea" class="d-none mt-3">
                        <div class="progress mb-2">
                            <div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated" 
                                 role="progressbar" style="width: 0%"></div>
                        </div>
                        <small id="progressText">任务已提交,等待处理...</small>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

3.3 JavaScript核心逻辑实现

class VideoGenerationApp {
    constructor() {
        this.apiBaseUrl = 'https://your-backend-domain.com/api';
        this.currentTaskId = null;
        this.pollingInterval = null;
        this.initializeEventListeners();
    }

    initializeEventListeners() {
        // 表单提交事件
        document.getElementById('videoGenerationForm').addEventListener('submit', 
            (e) => this.handleFormSubmit(e));
        
        // 模型选择变化事件
        document.getElementById('modelSelect').addEventListener('change', 
            () => this.toggleImageInputs());
    }

    toggleImageInputs() {
        const model = document.getElementById('modelSelect').value;
        const firstFrameInput = document.getElementById('firstFrameImage');
        const subjectRefsInput = document.getElementById('subjectReferences');
        
        // 根据模型类型显示/隐藏图片输入
        if (model.startsWith('I2V')) {
            firstFrameInput.parentElement.classList.remove('d-none');
            subjectRefsInput.parentElement.classList.remove('d-none');
        } else {
            firstFrameInput.parentElement.classList.add('d-none');
            subjectRefsInput.parentElement.classList.add('d-none');
        }
    }

    async handleFormSubmit(e) {
        e.preventDefault();
        
        const generateBtn = document.getElementById('generateBtn');
        generateBtn.disabled = true;
        generateBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 生成中...';
        
        try {
            const formData = new FormData();
            formData.append('model', document.getElementById('modelSelect').value);
            formData.append('prompt', document.getElementById('promptInput').value);
            formData.append('promptOptimizer', 
                document.querySelector('input[name="promptOptimizer"]:checked').value);
            
            // 添加图片文件
            const firstFrameFile = document.getElementById('firstFrameImage').files[0];
            if (firstFrameFile) {
                formData.append('firstFrameImage', firstFrameFile);
            }
            
            const subjectRefFiles = document.getElementById('subjectReferences').files;
            for (let i = 0; i < subjectRefFiles.length; i++) {
                formData.append('subjectReferences', subjectRefFiles[i]);
            }
            
            // 调用后端API
            const response = await axios.post(`${this.apiBaseUrl}/video/generate`, formData, {
                headers: {
                    'Content-Type': 'multipart/form-data'
                }
            });
            
            this.currentTaskId = response.data.task_id;
            this.showProgressArea();
            this.startProgressPolling();
            
        } catch (error) {
            console.error('生成视频失败:', error);
            this.showError('视频生成失败,请稍后重试');
        } finally {
            generateBtn.disabled = false;
            generateBtn.innerHTML = '<i class="fas fa-magic"></i> 生成视频';
        }
    }

    showProgressArea() {
        document.getElementById('emptyPreview').classList.add('d-none');
        document.getElementById('previewArea').classList.add('d-none');
        document.getElementById('progressArea').classList.remove('d-none');
    }

    startProgressPolling() {
        // 清除现有轮询
        if (this.pollingInterval) {
            clearInterval(this.pollingInterval);
        }
        
        // 每5秒查询一次任务状态
        this.pollingInterval = setInterval(() => {
            this.checkTaskStatus();
        }, 5000);
        
        // 立即执行一次查询
        this.checkTaskStatus();
    }

    async checkTaskStatus() {
        if (!this.currentTaskId) return;
        
        try {
            const response = await axios.get(
                `${this.apiBaseUrl}/video/status/${this.currentTaskId}`
            );
            
            const status = response.data.status;
            document.getElementById('progressText').textContent = 
                this.getStatusText(status);
            
            // 更新进度条
            const progressBar = document.getElementById('progressBar');
            if (status === 'Pending') {
                progressBar.style.width = '25%';
            } else if (status === 'Processing') {
                progressBar.style.width = '60%';
            } else if (status === 'Success') {
                progressBar.style.width = '100%';
                this.handleTaskSuccess(response.data);
            } else if (status === 'Failed') {
                this.handleTaskFailure(response.data);
            }
            
        } catch (error) {
            console.error('查询任务状态失败:', error);
        }
    }

    getStatusText(status) {
        const statusTexts = {
            'Pending': '任务已提交,等待处理...',
            'Processing': '视频生成中,请稍候...',
            'Success': '视频生成成功!',
            'Failed': '视频生成失败'
        };
        return statusTexts[status] || '未知状态';
    }

    handleTaskSuccess(taskData) {
        // 停止轮询
        clearInterval(this.pollingInterval);
        this.pollingInterval = null;
        
        // 显示生成的视频
        document.getElementById('progressArea').classList.add('d-none');
        document.getElementById('previewArea').classList.remove('d-none');
        
        const videoElement = document.getElementById('generatedVideo');
        const downloadLink = document.getElementById('downloadLink');
        
        videoElement.src = taskData.videoDownloadUrl;
        downloadLink.href = taskData.videoDownloadUrl;
        downloadLink.download = `generated-video-${this.currentTaskId}.mp4`;
        
        // 通知用户
        this.showSuccess('视频生成成功!');
    }

    handleTaskFailure(taskData) {
        // 停止轮询
        clearInterval(this.pollingInterval);
        this.pollingInterval = null;
        
        // 显示错误信息
        document.getElementById('progressArea').classList.add('d-none');
        document.getElementById('emptyPreview').classList.remove('d-none');
        
        this.showError(`视频生成失败: ${taskData.error_message || '未知错误'}`);
    }

    showError(message) {
        this.showNotification(message, 'danger');
    }

    showSuccess(message) {
        this.showNotification(message, 'success');
    }

    showNotification(message, type) {
        // 创建通知元素
        const notification = document.createElement('div');
        notification.className = `alert alert-${type} alert-dismissible fade show`;
        notification.innerHTML = `
            ${message}
            <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
        `;
        
        // 添加到页面
        const container = document.querySelector('.container');
        container.insertBefore(notification, container.firstChild);
        
        // 5秒后自动消失
        setTimeout(() => {
            if (notification.parentNode) {
                notification.classList.remove('show');
                setTimeout(() => notification.remove(), 150);
            }
        }, 5000);
    }
}

// 初始化应用
document.addEventListener('DOMContentLoaded', () => {
    window.videoApp = new VideoGenerationApp();
});

3.4 响应式CSS样式设计

/* 自定义样式表 */
:root {
    --primary-color: #4e73df;
    --secondary-color: #6f42c1;
    --success-color: #1cc88a;
    --info-color: #36b9cc;
    --warning-color: #f6c23e;
    --danger-color: #e74a3b;
    --light-bg: #f8f9fc;
}

body {
    background-color: var(--light-bg);
    font-family: 'Nunito', -apple-system, BlinkMacSystemFont, sans-serif;
}

.card {
    border: none;
    border-radius: 0.35rem;
    box-shadow: 0 0.15rem 1.75rem 0 rgba(58, 59, 69, 0.15);
    margin-bottom: 1.5rem;
}

.card-header {
    background-color: #f8f9fc;
    border-bottom: 1px solid #e3e6f0;
    font-weight: 700;
    padding: 1rem 1.35rem;
}

.btn-primary {
    background-color: var(--primary-color);
    border-color: var(--primary-color);
}

.btn-primary:hover {
    background-color: #3a5ec7;
    border-color: #3a5ec7;
}

.progress {
    height: 0.5rem;
    border-radius: 0.35rem;
}

.progress-bar {
    background-color: var(--primary-color);
}

#previewArea video {
    border-radius: 0.35rem;
    background-color: #000;
}

.alert {
    border: none;
    border-radius: 0.35rem;
    margin-bottom: 1rem;
}

/* 动画效果 */
.fade-in {
    animation: fadeIn 0.5s;
}

@keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
}

/* 移动端适配 */
@media (max-width: 768px) {
    .container {
        padding: 0 15px;
    }
    
    .card-body {
        padding: 1rem;
    }
}

四、后端Java服务实现

4.1 数据模型设计

package com.example.aivisionplatform.entity;

import lombok.Data;
import javax.persistence.*;
import java.time.LocalDateTime;

@Entity
@Table(name = "video_generation_tasks")
@Data
public class VideoGenerationTask {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "task_id", unique = true, nullable = false)
    private String taskId;
    
    @Column(name = "user_id", nullable = false)
    private String userId;
    
    @Column(name = "model_name", nullable = false)
    private String modelName;
    
    @Column(name = "prompt", length = 1000)
    private String prompt;
    
    @Column(name = "prompt_optimizer")
    private Boolean promptOptimizer;
    
    @Column(name = "first_frame_image_url")
    private String firstFrameImageUrl;
    
    @Column(name = "subject_references_urls")
    private String subjectReferencesUrls;
    
    @Column(name = "status")
    @Enumerated(EnumType.STRING)
    private TaskStatus status;
    
    @Column(name = "video_download_url")
    private String videoDownloadUrl;
    
    @Column(name = "video_width")
    private Integer videoWidth;
    
    @Column(name = "video_height")
    private Integer videoHeight;
    
    @Column(name = "error_message")
    private String errorMessage;
    
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
    
    @PrePersist
    protected void onCreate() {
        createdAt = LocalDateTime.now();
        updatedAt = LocalDateTime.now();
    }
    
    @PreUpdate
    protected void onUpdate() {
        updatedAt = LocalDateTime.now();
    }
    
    public enum TaskStatus {
        PENDING, PROCESSING, SUCCESS, FAILED
    }
}

4.2 MaaS API服务客户端

package com.example.aivisionplatform.service;

import com.example.aivisionplatform.config.AppConfig;
import com.example.aivisionplatform.entity.VideoGenerationTask;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

import java.util.HashMap;
import java.util.Map;

@Service
@Slf4j
public class MaasApiService {
    
    private final RestTemplate restTemplate;
    private final AppConfig appConfig;
    private final FileStorageService fileStorageService;
    
    @Autowired
    public MaasApiService(RestTemplate restTemplate, AppConfig appConfig, 
                         FileStorageService fileStorageService) {
        this.restTemplate = restTemplate;
        this.appConfig = appConfig;
        this.fileStorageService = fileStorageService;
    }
    
    /**
     * 创建视频生成任务
     */
    public String createVideoGenerationTask(String model, String prompt, Boolean promptOptimizer,
                                          MultipartFile firstFrameImage, 
                                          MultipartFile[] subjectReferences) {
        // 准备请求头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.set("Authorization", "Bearer " + appConfig.getMaasApiKey());
        
        // 构建请求体
        Map<String, Object> requestBody = new HashMap<>();
        requestBody.put("model", model);
        requestBody.put("prompt", prompt);
        requestBody.put("promptOptimizer", promptOptimizer);
        
        // 处理首帧图片
        if (firstFrameImage != null && !firstFrameImage.isEmpty()) {
            try {
                String imageData = fileStorageService.convertToDataUri(firstFrameImage);
                requestBody.put("firstFrameImage", imageData);
            } catch (Exception e) {
                log.error("处理首帧图片失败", e);
                throw new RuntimeException("首帧图片处理失败", e);
            }
        }
        
        // 处理主体参考图片
        if (subjectReferences != null && subjectReferences.length > 0) {
            String[] subjectRefsArray = new String[subjectReferences.length];
            for (int i = 0; i < subjectReferences.length; i++) {
                try {
                    subjectRefsArray[i] = fileStorageService.convertToDataUri(subjectReferences[i]);
                } catch (Exception e) {
                    log.error("处理主体参考图片失败", e);
                    throw new RuntimeException("主体参考图片处理失败", e);
                }
            }
            requestBody.put("subjectReference", subjectRefsArray);
        }
        
        // 发送请求
        HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(requestBody, headers);
        String apiUrl = appConfig.getMaasBaseUrl() + "/v1/video_generation";
        
        try {
            ResponseEntity<Map> response = restTemplate.exchange(
                apiUrl, HttpMethod.POST, requestEntity, Map.class);
            
            if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
                Map<String, Object> responseBody = response.getBody();
                return (String) responseBody.get("task_id");
            } else {
                log.error("创建视频任务失败: {}", response.getStatusCode());
                throw new RuntimeException("API调用失败,状态码: " + response.getStatusCode());
            }
        } catch (Exception e) {
            log.error("调用MaaS API失败", e);
            throw new RuntimeException("视频生成服务暂时不可用", e);
        }
    }
    
    /**
     * 查询视频生成任务状态
     */
    public Map<String, Object> getVideoGenerationStatus(String taskId) {
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", "Bearer " + appConfig.getMaasApiKey());
        
        HttpEntity<String> requestEntity = new HttpEntity<>(headers);
        String apiUrl = appConfig.getMaasBaseUrl() + "/v1/query/video_generation?taskId=" + taskId;
        
        try {
            ResponseEntity<Map> response = restTemplate.exchange(
                apiUrl, HttpMethod.GET, requestEntity, Map.class);
            
            if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
                return response.getBody();
            } else {
                log.error("查询任务状态失败: {}", response.getStatusCode());
                throw new RuntimeException("状态查询失败,状态码: " + response.getStatusCode());
            }
        } catch (Exception e) {
            log.error("调用MaaS状态查询API失败", e);
            throw new RuntimeException("状态查询服务暂时不可用", e);
        }
    }
}

4.3 文件存储服务

package com.example.aivisionplatform.service;

import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.util.Base64;

@Service
public class FileStorageService {
    
    /**
     * 将MultipartFile转换为Data URI格式
     */
    public String convertToDataUri(MultipartFile file) throws Exception {
        if (file == null || file.isEmpty()) {
            throw new IllegalArgumentException("文件不能为空");
        }
        
        String contentType = file.getContentType();
        byte[] fileBytes = file.getBytes();
        String base64Content = Base64.getEncoder().encodeToString(fileBytes);
        
        return "data:" + contentType + ";base64," + base64Content;
    }
    
    /**
     * 从Data URI提取文件内容和类型
     */
    public FileContent extractFromDataUri(String dataUri) throws Exception {
        if (dataUri == null || !dataUri.startsWith("data:")) {
            throw new IllegalArgumentException("无效的Data URI格式");
        }
        
        // 解析Data URI
        int commaIndex = dataUri.indexOf(',');
        if (commaIndex == -1) {
            throw new IllegalArgumentException("无效的Data URI格式");
        }
        
        String metaData = dataUri.substring(0, commaIndex);
        String data = dataUri.substring(commaIndex + 1);
        
        // 解析元数据
        String[] metaParts = metaData.split(";");
        if (metaParts.length < 1) {
            throw new IllegalArgumentException("无效的Data URI元数据");
        }
        
        String contentType = metaParts[0].substring(5); // 去掉"data:"
        boolean isBase64 = false;
        
        for (String part : metaParts) {
            if (part.trim().equals("base64")) {
                isBase64 = true;
                break;
            }
        }
        
        if (!isBase64) {
            throw new IllegalArgumentException("只支持base64编码的Data URI");
        }
        
        // 解码base64数据
        byte[] fileBytes = Base64.getDecoder().decode(data);
        
        return new FileContent(contentType, fileBytes);
    }
    
    // 文件内容包装类
    public static class FileContent {
        private final String contentType;
        private final byte[] content;
        
        public FileContent(String contentType, byte[] content) {
            this.contentType = contentType;
            this.content = content;
        }
        
        public String getContentType() {
            return contentType;
        }
        
        public byte[] getContent() {
            return content;
        }
    }
}

4.4 任务管理服务

package com.example.aivisionplatform.service;

import com.example.aivisionplatform.entity.VideoGenerationTask;
import com.example.aivisionplatform.repository.VideoTaskRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

@Service
@Slf4j
public class TaskManagementService {
    
    private final VideoTaskRepository taskRepository;
    private final MaasApiService maasApiService;
    
    @Autowired
    public TaskManagementService(VideoTaskRepository taskRepository, 
                                MaasApiService maasApiService) {
        this.taskRepository = taskRepository;
        this.maasApiService = maasApiService;
    }
    
    /**
     * 创建新的视频生成任务
     */
    @Transactional
    public VideoGenerationTask createTask(String userId, String model, String prompt, 
                                        Boolean promptOptimizer, String firstFrameImage, 
                                        String subjectReferences) {
        VideoGenerationTask task = new VideoGenerationTask();
        task.setUserId(userId);
        task.setModelName(model);
        task.setPrompt(prompt);
        task.setPromptOptimizer(promptOptimizer);
        task.setFirstFrameImageUrl(firstFrameImage);
        task.setSubjectReferencesUrls(subjectReferences);
        task.setStatus(VideoGenerationTask.TaskStatus.PENDING);
        
        // 先保存到数据库获取ID
        task = taskRepository.save(task);
        
        try {
            // 调用MaaS API创建任务
            String taskId = maasApiService.createVideoGenerationTask(
                model, prompt, promptOptimizer, null, null); // 简化处理,实际需要处理文件
            
            task.setTaskId(taskId);
            return taskRepository.save(task);
            
        } catch (Exception e) {
            log.error("创建MaaS任务失败", e);
            task.setStatus(VideoGenerationTask.TaskStatus.FAILED);
            task.setErrorMessage(e.getMessage());
            return taskRepository.save(task);
        }
    }
    
    /**
     * 异步处理任务状态更新
     */
    @Async
    public void processTaskAsync(VideoGenerationTask task) {
        try {
            // 更新状态为处理中
            task.setStatus(VideoGenerationTask.TaskStatus.PROCESSING);
            taskRepository.save(task);
            
            // 定期检查任务状态
            boolean completed = false;
            int checkCount = 0;
            final int maxChecks = 60; // 最多检查60次(5分钟)
            
            while (!completed && checkCount < maxChecks) {
                try {
                    Thread.sleep(5000); // 每5秒检查一次
                    
                    Map<String, Object> status = maasApiService.getVideoGenerationStatus(task.getTaskId());
                    String taskStatus = (String) status.get("status");
                    
                    if ("Success".equalsIgnoreCase(taskStatus)) {
                        // 任务成功完成
                        task.setStatus(VideoGenerationTask.TaskStatus.SUCCESS);
                        task.setVideoDownloadUrl((String) status.get("videoDownloadUrl"));
                        task.setVideoWidth((Integer) status.get("videoWidth"));
                        task.setVideoHeight((Integer) status.get("videoHeight"));
                        completed = true;
                        
                    } else if ("Failed".equalsIgnoreCase(taskStatus)) {
                        // 任务失败
                        task.setStatus(VideoGenerationTask.TaskStatus.FAILED);
                        task.setErrorMessage((String) status.get("errorMessage"));
                        completed = true;
                        
                    } else {
                        // 任务仍在处理中,继续等待
                        checkCount++;
                    }
                    
                    taskRepository.save(task);
                    
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    log.error("任务处理被中断", e);
                    break;
                } catch (Exception e) {
                    log.error("检查任务状态失败", e);
                    // 继续尝试,不立即失败
                    checkCount++;
                }
            }
            
            if (!completed) {
                // 超时处理
                task.setStatus(VideoGenerationTask.TaskStatus.FAILED);
                task.setErrorMessage("任务处理超时");
                taskRepository.save(task);
            }
            
        } catch (Exception e) {
            log.error("异步处理任务失败", e);
            task.setStatus(VideoGenerationTask.TaskStatus.FAILED);
            task.setErrorMessage(e.getMessage());
            taskRepository.save(task);
        }
    }
    
    /**
     * 定时任务:处理卡住的任务
     */
    @Scheduled(fixedRate = 300000) // 每5分钟执行一次
    public void handleStuckTasks() {
        LocalDateTime threshold = LocalDateTime.now().minusMinutes(10);
        
        List<VideoGenerationTask> stuckTasks = taskRepository
            .findByStatusAndCreatedAtBefore(
                VideoGenerationTask.TaskStatus.PROCESSING, 
                threshold
            );
        
        for (VideoGenerationTask task : stuckTasks) {
            log.warn("发现卡住的任务: {}", task.getTaskId());
            task.setStatus(VideoGenerationTask.TaskStatus.FAILED);
            task.setErrorMessage("任务处理超时(系统自动检测)");
            taskRepository.save(task);
        }
    }
    
    /**
     * 根据任务ID获取任务信息
     */
    public Optional<VideoGenerationTask> getTaskById(Long id) {
        return taskRepository.findById(id);
    }
    
    /**
     * 根据用户ID获取任务列表
     */
    public List<VideoGenerationTask> getTasksByUserId(String userId) {
        return taskRepository.findByUserIdOrderByCreatedAtDesc(userId);
    }
}

4.5 REST控制器实现

package com.example.aivisionplatform.controller;

import com.example.aivisionplatform.entity.VideoGenerationTask;
import com.example.aivisionplatform.service.TaskManagementService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@RestController
@RequestMapping("/api/video")
@Slf4j
public class VideoGenerationController {
    
    private final TaskManagementService taskService;
    
    @Autowired
    public VideoGenerationController(TaskManagementService taskService) {
        this.taskService = taskService;
    }
    
    /**
     * 创建视频生成任务
     */
    @PostMapping("/generate")
    public ResponseEntity<?> generateVideo(
            @RequestParam String model,
            @RequestParam String prompt,
            @RequestParam(defaultValue = "true") Boolean promptOptimizer,
            @RequestParam(required = false) MultipartFile firstFrameImage,
            @RequestParam(required = false) MultipartFile[] subjectReferences) {
        
        try {
            // 获取当前用户ID(实际应用中应从认证信息中获取)
            String userId = getCurrentUserId();
            
            // 简化处理:实际应用中需要处理文件上传和转换
            VideoGenerationTask task = taskService.createTask(
                userId, model, prompt, promptOptimizer, 
                firstFrameImage != null ? firstFrameImage.getOriginalFilename() : null,
                subjectReferences != null ? String.valueOf(subjectReferences.length) : null
            );
            
            // 异步处理任务
            taskService.processTaskAsync(task);
            
            Map<String, Object> response = new HashMap<>();
            response.put("success", true);
            response.put("task_id", task.getTaskId());
            response.put("message", "视频生成任务已创建");
            
            return ResponseEntity.ok(response);
            
        } catch (Exception e) {
            log.error("创建视频生成任务失败", e);
            
            Map<String, Object> errorResponse = new HashMap<>();
            errorResponse.put("success", false);
            errorResponse.put("message", "创建任务失败: " + e.getMessage());
            
            return ResponseEntity.badRequest().body(errorResponse);
        }
    }
    
    /**
     * 查询任务状态
     */
    @GetMapping("/status/{taskId}")
    public ResponseEntity<?> getTaskStatus(@PathVariable String taskId) {
        try {
            // 这里简化处理,实际应根据taskId查询数据库
            // 这里返回模拟数据
            
            Map<String, Object> response = new HashMap<>();
            response.put("task_id", taskId);
            response.put("status", "Processing");
            response.put("progress", 60);
            
            return ResponseEntity.ok(response);
            
        } catch (Exception e) {
            log.error("查询任务状态失败", e);
            
            Map<String, Object> errorResponse = new HashMap<>();
            errorResponse.put("success", false);
            errorResponse.put("message", "查询任务状态失败: " + e.getMessage());
            
            return ResponseEntity.badRequest().body(errorResponse);
        }
    }
    
    /**
     * 获取用户的任务历史
     */
    @GetMapping("/history")
    public ResponseEntity<?> getTaskHistory() {
        try {
            String userId = getCurrentUserId();
            List<VideoGenerationTask> tasks = taskService.getTasksByUserId(userId);
            
            return ResponseEntity.ok(tasks);
            
        } catch (Exception e) {
            log.error("获取任务历史失败", e);
            
            Map<String, Object> errorResponse = new HashMap<>();
            errorResponse.put("success", false);
            errorResponse.put("message", "获取任务历史失败: " + e.getMessage());
            
            return ResponseEntity.badRequest().body(errorResponse);
        }
    }
    
    /**
     * 获取当前用户ID(模拟实现)
     */
    private String getCurrentUserId() {
        // 实际应用中应从Spring Security上下文中获取
        return "user-" + System.currentTimeMillis(); // 模拟用户ID
    }
}

4.6 全局异常处理

package com.example.aivisionplatform.handler;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.multipart.MaxUploadSizeExceededException;

import java.util.HashMap;
import java.util.Map;

@ControllerAdvice
public class GlobalExceptionHandler {
    
    /**
     * 处理文件大小超过限制异常
     */
    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public ResponseEntity<Map<String, Object>> handleMaxSizeException(
            MaxUploadSizeExceededException exc) {
        
        Map<String, Object> response = new HashMap<>();
        response.put("success", false);
        response.put("message", "文件大小超过限制");
        
        return ResponseEntity.status(HttpStatus.PAYLOAD_TOO_LARGE).body(response);
    }
    
    /**
     * 处理所有其他异常
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity<Map<String, Object>> handleGlobalException(Exception ex) {
        Map<String, Object> response = new HashMap<>();
        response.put("success", false);
        response.put("message", "服务器内部错误: " + ex.getMessage());
        
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
    }
}

五、系统集成与部署

5.1 应用配置管理

# application.yml
server:
  port: 8080
  servlet:
    context-path: /api
    multipart:
      max-file-size: 10MB
      max-request-size: 10MB

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/ai_vision_platform?useSSL=false&serverTimezone=UTC
    username: root
    password: password
    driver-class-name: com.mysql.cj.jdbc.Driver
  
  jpa:
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL8Dialect
        format_sql: true
    show-sql: true
  
  redis:
    host: localhost
    port: 6379
  
  mail:
    host: smtp.gmail.com
    port: 587
    username: your-email@gmail.com
    password: your-app-password
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true

maas:
  base:
    url: https://maas-api.lanyun.net
  api:
    key: your-maas-api-key-here

logging:
  level:
    com.example.aivisionplatform: DEBUG
  file:
    name: logs/application.log
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"

# 自定义配置
app:
  task:
    timeout: 300000    # 任务超时时间(5分钟)
    max-retries: 3     # 最大重试次数
  file:
    upload-dir: uploads # 文件上传目录

5.2 Docker容器化部署

Dockerfile 配置
# 后端Dockerfile
FROM openjdk:11-jre-slim

# 设置工作目录
WORKDIR /app

# 复制JAR文件
COPY target/ai-vision-platform-1.0.0.jar app.jar

# 创建非root用户
RUN groupadd -r spring && useradd -r -g spring spring
USER spring:spring

# 暴露端口
EXPOSE 8080

# 启动应用
ENTRYPOINT ["java", "-jar", "app.jar"]
Docker Compose 配置
version: '3.8'

services:
  # 后端服务
  backend:
    build: 
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    environment:
      - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/ai_vision_platform
      - SPRING_DATASOURCE_USERNAME=root
      - SPRING_DATASOURCE_PASSWORD=password
      - SPRING_REDIS_HOST=redis
      - MAAS_BASE_URL=https://maas-api.lanyun.net
      - MAAS_API_KEY=${MAAS_API_KEY}
    depends_on:
      - mysql
      - redis
    networks:
      - ai-network

  # MySQL数据库
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: ai_vision_platform
    volumes:
      - mysql_data:/var/lib/mysql
    networks:
      - ai-network

  # Redis缓存
  redis:
    image: redis:6-alpine
    volumes:
      - redis_data:/data
    networks:
      - ai-network

  # Nginx反向代理
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./frontend:/usr/share/nginx/html
      - ./ssl:/etc/ssl/certs
    depends_on:
      - backend
    networks:
      - ai-network

volumes:
  mysql_data:
  redis_data:

networks:
  ai-network:
    driver: bridge
Nginx 配置
# nginx.conf
events {
    worker_connections 1024;
}

http {
    upstream backend {
        server backend:8080;
    }

    server {
        listen 80;
        server_name your-domain.com;
        
        # 重定向HTTP到HTTPS
        return 301 https://$server_name$request_uri;
    }

    server {
        listen 443 ssl http2;
        server_name your-domain.com;
        
        ssl_certificate /etc/ssl/certs/your-domain.crt;
        ssl_certificate_key /etc/ssl/certs/your-domain.key;
        
        # 静态文件服务
        location / {
            root /usr/share/nginx/html;
            index index.html;
            try_files $uri $uri/ /index.html;
        }
        
        # API代理
        location /api/ {
            proxy_pass http://backend/api/;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            
            # WebSocket支持
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }
        
        # 文件上传大小限制
        client_max_body_size 10M;
    }
}

5.3 安全配置与API防护

package com.example.aivisionplatform.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Arrays;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .cors().and()
            .csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
            .antMatchers("/api/video/generate").authenticated()
            .antMatchers("/api/video/history").authenticated()
            .antMatchers("/api/**").permitAll()
            .anyRequest().authenticated()
            .and()
            .oauth2ResourceServer().jwt();
        
        return http.build();
    }
    
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000", "https://your-domain.com"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type", "X-Requested-With"));
        configuration.setExposedHeaders(Arrays.asList("Authorization"));
        configuration.setAllowCredentials(true);
        
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

六、性能优化与监控

6.1 数据库性能优化

package com.example.aivisionplatform.repository;

import com.example.aivisionplatform.entity.VideoGenerationTask;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.time.LocalDateTime;
import java.util.List;

@Repository
public interface VideoTaskRepository extends JpaRepository<VideoGenerationTask, Long> {
    
    // 添加索引优化查询性能
    List<VideoGenerationTask> findByUserIdOrderByCreatedAtDesc(String userId);
    
    Page<VideoGenerationTask> findByUserId(String userId, Pageable pageable);
    
    List<VideoGenerationTask> findByStatusAndCreatedAtBefore(
        VideoGenerationTask.TaskStatus status, LocalDateTime createdAt);
    
    // 使用原生SQL进行复杂查询优化
    @Query(value = "SELECT * FROM video_generation_tasks WHERE " +
           "user_id = :userId AND created_at >= :startDate " +
           "ORDER BY created_at DESC LIMIT :limit", 
           nativeQuery = true)
    List<VideoGenerationTask> findRecentTasksByUser(
        @Param("userId") String userId, 
        @Param("startDate") LocalDateTime startDate,
        @Param("limit") int limit);
    
    // 统计用户任务数量
    @Query("SELECT COUNT(t) FROM VideoGenerationTask t WHERE t.userId = :userId")
    Long countByUserId(@Param("userId") String userId);
    
    // 统计成功任务数量
    @Query("SELECT COUNT(t) FROM VideoGenerationTask t WHERE t.userId = :userId AND t.status = 'SUCCESS'")
    Long countSuccessByUserId(@Param("userId") String userId);
}

6.2 Redis缓存优化

package com.example.aivisionplatform.service;

import com.example.aivisionplatform.entity.VideoGenerationTask;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class CacheService {
    
    private final RedisTemplate<String, Object> redisTemplate;
    private static final String TASK_CACHE_PREFIX = "task:";
    private static final long TASK_CACHE_TTL = 3600; // 1小时
    
    @Autowired
    public CacheService(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    
    /**
     * 缓存任务信息
     */
    public void cacheTask(VideoGenerationTask task) {
        String key = TASK_CACHE_PREFIX + task.getTaskId();
        try {
            redisTemplate.opsForValue().set(key, task, TASK_CACHE_TTL, TimeUnit.SECONDS);
            log.debug("任务已缓存: {}", task.getTaskId());
        } catch (Exception e) {
            log.error("缓存任务失败: {}", task.getTaskId(), e);
        }
    }
    
    /**
     * 从缓存获取任务信息
     */
    public VideoGenerationTask getCachedTask(String taskId) {
        String key = TASK_CACHE_PREFIX + taskId;
        try {
            return (VideoGenerationTask) redisTemplate.opsForValue().get(key);
        } catch (Exception e) {
            log.error("从缓存获取任务失败: {}", taskId, e);
            return null;
        }
    }
    
    /**
     * 删除缓存的任务信息
     */
    public void evictTaskCache(String taskId) {
        String key = TASK_CACHE_PREFIX + taskId;
        try {
            redisTemplate.delete(key);
            log.debug("任务缓存已清除: {}", taskId);
        } catch (Exception e) {
            log.error("清除任务缓存失败: {}", taskId, e);
        }
    }
    
    /**
     * 缓存API调用结果(防止重复调用)
     */
    public void cacheApiResult(String apiKey, Object result, long ttlSeconds) {
        try {
            redisTemplate.opsForValue().set(apiKey, result, ttlSeconds, TimeUnit.SECONDS);
        } catch (Exception e) {
            log.error("API结果缓存失败: {}", apiKey, e);
        }
    }
    
    /**
     * 获取缓存的API结果
     */
    public Object getCachedApiResult(String apiKey) {
        try {
            return redisTemplate.opsForValue().get(apiKey);
        } catch (Exception e) {
            log.error("获取API缓存结果失败: {}", apiKey, e);
            return null;
        }
    }
}

6.3 应用监控与日志

package com.example.aivisionplatform.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

@Aspect
@Component
@Slf4j
public class PerformanceMonitorAspect {
    
    @Around("execution(* com.example.aivisionplatform.service..*(..))")
    public Object monitorServicePerformance(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().toShortString();
        StopWatch stopWatch = new StopWatch();
        
        stopWatch.start();
        Object result = joinPoint.proceed();
        stopWatch.stop();
        
        long executionTime = stopWatch.getLastTaskTimeMillis();
        
        if (executionTime > 1000) {
            log.warn("方法 {} 执行缓慢: {} ms", methodName, executionTime);
        } else if (executionTime > 500) {
            log.info("方法 {} 执行时间: {} ms", methodName, executionTime);
        }
        
        return result;
    }
    
    @Around("execution(* com.example.aivisionplatform.controller..*(..))")
    public Object monitorControllerPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().toShortString();
        StopWatch stopWatch = new StopWatch();
        
        stopWatch.start();
        Object result = joinPoint.proceed();
        stopWatch.stop();
        
        long executionTime = stopWatch.getLastTaskTimeMillis();
        log.info("API {} 响应时间: {} ms", methodName, executionTime);
        
        return result;
    }
}

七、测试与质量保障

7.1 单元测试与集成测试

package com.example.aivisionplatform.service;

import com.example.aivisionplatform.entity.VideoGenerationTask;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
@ActiveProfiles("test")
public class TaskManagementServiceTest {
    
    @Autowired
    private TaskManagementService taskService;
    
    @Test
    public void testCreateTask() {
        VideoGenerationTask task = taskService.createTask(
            "test-user", 
            "I2V-01", 
            "测试提示词", 
            true, 
            null, 
            null
        );
        
        assertNotNull(task);
        assertNotNull(task.getTaskId());
        assertEquals("test-user", task.getUserId());
        assertEquals("I2V-01", task.getModelName());
        assertEquals("测试提示词", task.getPrompt());
        assertTrue(task.getPromptOptimizer());
        assertEquals(VideoGenerationTask.TaskStatus.PENDING, task.getStatus());
    }
    
    @Test
    public void testGetTasksByUserId() {
        // 创建测试任务
        taskService.createTask("test-user", "I2V-01", "测试1", true, null, null);
        taskService.createTask("test-user", "T2V-01", "测试2", false, null, null);
        
        // 查询任务
        var tasks = taskService.getTasksByUserId("test-user");
        
        assertNotNull(tasks);
        assertTrue(tasks.size() >= 2);
        
        // 验证按时间倒序排列
        for (int i = 0; i < tasks.size() - 1; i++) {
            assertTrue(tasks.get(i).getCreatedAt()
                      .isAfter(tasks.get(i + 1).getCreatedAt()));
        }
    }
}

7.2 API接口测试

package com.example.aivisionplatform.controller;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureMockMvc
public class VideoGenerationControllerTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Test
    public void testGenerateVideo() throws Exception {
        MockMultipartFile imageFile = new MockMultipartFile(
            "firstFrameImage", 
            "test.jpg", 
            "image/jpeg", 
            "test image content".getBytes()
        );
        
        mockMvc.perform(MockMvcRequestBuilders.multipart("/api/video/generate")
                .file(imageFile)
                .param("model", "I2V-01")
                .param("prompt", "测试视频生成")
                .param("promptOptimizer", "true")
                .contentType(MediaType.MULTIPART_FORM_DATA))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true))
                .andExpect(jsonPath("$.task_id").exists());
    }
    
    @Test
    public void testGetTaskStatus() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/api/video/status/test-task-id")
                .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.task_id").value("test-task-id"))
                .andExpect(jsonPath("$.status").exists());
    }
}

7.3 性能压力测试

package com.example.aivisionplatform.test;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

@SpringBootTest
@ActiveProfiles("test")
public class LoadTest {
    
    @Autowired
    private TaskManagementService taskService;
    
    @Test
    public void testConcurrentTaskCreation() throws InterruptedException {
        int threadCount = 10;
        int tasksPerThread = 5;
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        AtomicInteger successCount = new AtomicInteger(0);
        AtomicInteger failureCount = new AtomicInteger(0);
        
        // 创建并发任务
        for (int i = 0; i < threadCount; i++) {
            final int threadId = i;
            executor.submit(() -> {
                for (int j = 0; j < tasksPerThread; j++) {
                    try {
                        VideoGenerationTask task = taskService.createTask(
                            "load-test-user", 
                            "I2V-01", 
                            "压力测试任务 " + threadId + "-" + j, 
                            true, 
                            null, 
                            null
                        );
                        successCount.incrementAndGet();
                    } catch (Exception e) {
                        failureCount.incrementAndGet();
                    }
                }
            });
        }
        
        // 等待所有任务完成
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.MINUTES);
        
        System.out.println("压力测试结果:");
        System.out.println("成功任务数: " + successCount.get());
        System.out.println("失败任务数: " + failureCount.get());
        System.out.println("总任务数: " + (successCount.get() + failureCount.get()));
        
        assertTrue(successCount.get() > 0, "至少应有部分任务成功创建");
    }
}

八、实际应用案例与扩展

8.1 电商营销视频生成案例

package com.example.aivisionplatform.application;

import com.example.aivisionplatform.service.TaskManagementService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
public class EcommerceVideoService {
    
    @Autowired
    private TaskManagementService taskService;
    
    /**
     * 生成商品展示视频
     */
    public String generateProductVideo(String productId, String productName, 
                                      String productDescription, String imageUrl) {
        // 构建优化后的提示词
        String prompt = buildProductPrompt(productName, productDescription);
        
        // 创建视频生成任务
        var task = taskService.createTask(
            "ecommerce-system", 
            "I2V-01-Director", 
            prompt, 
            true, 
            imageUrl, 
            null
        );
        
        // 返回任务ID用于后续查询
        return task.getTaskId();
    }
    
    /**
     * 构建商品视频提示词
     */
    private String buildProductPrompt(String productName, String productDescription) {
        return String.format(
            "生成一个商品展示视频,展示%s产品。%s。视频风格要求专业、高端,适合电商平台使用。" +
            "使用[推进]镜头展示产品细节,然后[拉远]展示全貌,最后[右移]展示不同角度。",
            productName, productDescription
        );
    }
    
    /**
     * 生成促销活动视频
     */
    public String generatePromotionVideo(String promotionTitle, String promotionDetails, 
                                        String[] imageUrls) {
        String prompt = String.format(
            "创建促销活动视频:%s。活动详情:%s。视频要充满活力,色彩鲜艳,包含动态文字和特效。" +
            "使用[晃动]镜头增加动感,[变焦推近]强调重点信息。",
            promotionTitle, promotionDetails
        );
        
        // 创建视频生成任务
        var task = taskService.createTask(
            "ecommerce-system", 
            "I2V-01-Director", 
            prompt, 
            true, 
            imageUrls[0], 
            String.join(",", imageUrls)
        );
        
        return task.getTaskId();
    }
}

8.2 教育培训视频生成案例

package com.example.aivisionplatform.application;

import com.example.aivisionplatform.service.TaskManagementService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class EducationVideoService {
    
    @Autowired
    private TaskManagementService taskService;
    
    /**
     * 生成教学讲解视频
     */
    public String generateLessonVideo(String lessonTitle, String lessonContent, 
                                     String diagramImageUrl) {
        String prompt = String.format(
            "创建教学视频讲解'%s'。内容:%s。视频风格要求清晰、专业,适合教育用途。" +
            "使用[固定]镜头保持内容稳定,[推进]强调重点部分,[右移]展示相关内容。",
            lessonTitle, lessonContent
        );
        
        var task = taskService.createTask(
            "education-system", 
            "I2V-01-Director", 
            prompt, 
            true, 
            diagramImageUrl, 
            null
        );
        
        return task.getTaskId();
    }
    
    /**
     * 生成科学实验演示视频
     */
    public String generateExperimentVideo(String experimentName, String procedure, 
                                         String[] referenceImages) {
        String prompt = String.format(
            "生成科学实验'%s'的演示视频。实验步骤:%s。视频要准确展示实验过程,清晰可见。" +
            "使用[跟随]镜头跟踪实验过程,[推进]展示细节,[上升]展示整体效果。",
            experimentName, procedure
        );
        
        var task = taskService.createTask(
            "education-system", 
            "I2V-01-Director", 
            prompt, 
            true, 
            referenceImages[0], 
            String.join(",", referenceImages)
        );
        
        return task.getTaskId();
    }
}

九、总结与展望

9.1 项目总结

本文详细介绍了基于蓝耘元生代MaaS平台开发视觉模型应用的完整流程,涵盖了以下关键方面:

  1. 平台接入与配置:完成了MaaS平台的注册、API密钥获取和环境配置
  2. 前端界面开发:设计了响应式用户界面,实现了视频生成参数设置、任务状态监控和结果展示
  3. 后端服务实现:构建了完整的Spring Boot服务,包括任务管理、API调用、文件处理和数据库操作
  4. 系统集成部署:实现了Docker容器化部署,配置了Nginx反向代理和SSL证书
  5. 性能优化监控:引入了Redis缓存、数据库优化和应用性能监控
  6. 测试质量保障:编写了单元测试、集成测试和压力测试,确保系统稳定性
  7. 实际应用案例:提供了电商和教育领域的实际应用示例

9.2 技术亮点

  1. 前后端分离架构:采用现代Web开发架构,提高系统可维护性和扩展性
  2. 异步任务处理:实现了高效的异步任务处理机制,提升用户体验
  3. 完整的错误处理:设计了全面的异常处理和数据验证机制
  4. 性能优化:通过缓存、数据库优化和代码优化提升系统性能
  5. 容器化部署:使用Docker和Docker Compose实现快速部署和环境一致性

9.3 未来展望

随着AI技术的不断发展,视觉模型应用还有巨大的发展空间:

  1. 模型能力增强:支持更高分辨率、更长时长和更复杂场景的视频生成
  2. 实时生成能力:实现近实时的视频生成,支持交互式应用场景
  3. 多模态融合:结合文本、图像、音频等多种模态的生成能力
  4. 个性化定制:根据用户偏好和历史行为生成个性化内容
  5. 行业深度应用:在医疗、建筑、娱乐等垂直领域的深度应用

通过本文提供的完整解决方案,开发者可以快速构建基于蓝耘元生代MaaS平台的视觉模型应用,为各行业提供强大的AI视频生成能力。


参考资源

  1. 蓝耘元生代MaaS平台文档
  2. Spring Boot官方文档
  3. OpenAI API文档
  4. Bootstrap 5文档
  5. Docker官方文档
  6. Redis官方文档
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐