基于蓝耘元生代MaaS平台的视觉模型应用开发实战
本文介绍了基于蓝耘元生代MaaS平台开发视觉AI应用的完整流程。重点内容包括:1)视觉生成AI在内容创作、教育培训和娱乐产业中的市场需求;2)系统采用前后端分离架构,整合Nginx、Spring Boot、MySQL和Redis等技术;3)详细的环境准备与平台接入指南,包括API密钥获取和项目初始化配置。文章为开发者提供了从零开始构建视觉AI应用的实用技术方案。
·
基于蓝耘元生代MaaS平台的视觉模型应用开发实战
一、引言:视觉生成AI的技术革命
随着多模态人工智能技术的快速发展,视觉模型生成能力正成为企业创新应用的核心驱动力。蓝耘元生代MaaS平台提供的视觉模型API,使开发者能够轻松集成先进的视频生成功能到各类应用中。本文将全面解析如何基于该平台开发一个完整的LLM视觉模型应用,涵盖前端界面设计、后端服务实现以及系统集成的最佳实践。
1.1 视觉生成AI的市场需求
视觉内容在现代数字营销、教育培训和娱乐产业中占据核心地位。传统视频制作需要专业团队、昂贵设备和大量时间,而AI视频生成技术将这些门槛大幅降低:
- 内容创作:营销视频、产品演示、社交媒体内容
- 教育培训:教学视频、模拟演示、交互式学习材料
- 娱乐产业:短视频生成、虚拟偶像、互动叙事
1.2 技术架构概述
本文将实现的系统采用前后端分离架构:
二、环境准备与平台接入
2.1 注册平台账号并获取API密钥
首先,我们需要访问蓝耘元生代代码算云平台并完成注册:
- 访问 https://console.lanyun.net/#/register?source=2
- 完成企业邮箱注册和身份验证
- 进入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设计响应式用户界面,包含以下主要功能区域:
- 视频生成参数面板:用于设置生成参数
- 预览区域:显示生成的视频和进度
- 任务历史面板:展示历史生成任务
- 账户信息面板:显示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平台开发视觉模型应用的完整流程,涵盖了以下关键方面:
- 平台接入与配置:完成了MaaS平台的注册、API密钥获取和环境配置
- 前端界面开发:设计了响应式用户界面,实现了视频生成参数设置、任务状态监控和结果展示
- 后端服务实现:构建了完整的Spring Boot服务,包括任务管理、API调用、文件处理和数据库操作
- 系统集成部署:实现了Docker容器化部署,配置了Nginx反向代理和SSL证书
- 性能优化监控:引入了Redis缓存、数据库优化和应用性能监控
- 测试质量保障:编写了单元测试、集成测试和压力测试,确保系统稳定性
- 实际应用案例:提供了电商和教育领域的实际应用示例
9.2 技术亮点
- 前后端分离架构:采用现代Web开发架构,提高系统可维护性和扩展性
- 异步任务处理:实现了高效的异步任务处理机制,提升用户体验
- 完整的错误处理:设计了全面的异常处理和数据验证机制
- 性能优化:通过缓存、数据库优化和代码优化提升系统性能
- 容器化部署:使用Docker和Docker Compose实现快速部署和环境一致性
9.3 未来展望
随着AI技术的不断发展,视觉模型应用还有巨大的发展空间:
- 模型能力增强:支持更高分辨率、更长时长和更复杂场景的视频生成
- 实时生成能力:实现近实时的视频生成,支持交互式应用场景
- 多模态融合:结合文本、图像、音频等多种模态的生成能力
- 个性化定制:根据用户偏好和历史行为生成个性化内容
- 行业深度应用:在医疗、建筑、娱乐等垂直领域的深度应用
通过本文提供的完整解决方案,开发者可以快速构建基于蓝耘元生代MaaS平台的视觉模型应用,为各行业提供强大的AI视频生成能力。
参考资源:
更多推荐
所有评论(0)