GME-Qwen2-VL-2B Web开发全栈实战:从零搭建智能图片分享社区

张开发
2026/4/8 8:14:49 15 分钟阅读

分享文章

GME-Qwen2-VL-2B Web开发全栈实战:从零搭建智能图片分享社区
GME-Qwen2-VL-2B Web开发全栈实战从零搭建智能图片分享社区最近在捣鼓一些AI和Web结合的小项目发现把多模态大模型塞进一个完整的Web应用里这事儿还挺有意思的。特别是像图片分享社区这种场景用户上传一张图系统不仅能存起来还能“看懂”它自动配上描述、打上标签甚至能让你用文字搜到相关的图片整个体验一下子就智能了不少。这次我们就来动手搞一个。我会带你用现在比较主流的Vue 3和Spring Boot这套技术栈从零开始一步步搭起一个完整的图片分享社区。核心玩法就是用户上传图片后后端会悄悄调用GME-Qwen2-VL-2B这个轻量级的视觉语言模型让它来分析图片内容自动生成描述和标签。之后你不仅能看到这些AI生成的“注解”还能用自然语言比如“一只在晒太阳的橘猫”来搜索相关的图片系统会根据语义相似度给你推荐内容。整个过程会覆盖用户注册登录、图片上传管理、AI能力集成、语义搜索这些核心模块。代码我都会给出来你可以跟着做也可以根据自己的想法调整。咱们的目标是做出一个能跑起来、有实际功能的项目而不仅仅是纸上谈兵。1. 项目整体设计与技术选型在动手写代码之前咱们先花几分钟把整个项目要做什么、用什么技术来做心里有个谱。这样后面敲代码的时候思路会更清晰。1.1 我们要做一个什么样的社区简单说就是一个“能看懂图片”的图片分享站。和传统图床或者相册最大的区别在于“智能”二字。用户侧体验用户注册登录后可以上传自己的图片。上传成功后页面不仅显示图片还会立刻展示出由AI自动生成的图片描述和几个关键词标签。在浏览社区时用户可以在搜索框里输入任何文字比如“城市夜景”、“搞笑动物”系统会理解这些文字的含义然后找出内容上相关的图片而不是仅仅匹配文件名。系统核心能力这一切智能体验的背后依赖于一个多模态大模型我们选用GME-Qwen2-VL-2B。它的核心任务就是“图生文”即理解图片的视觉内容并用自然语言描述出来。我们利用这个能力自动化了内容标注打标签和内容理解建立语义索引这两个原本非常耗费人力的环节。核心功能清单用户系统注册、登录、个人主页、我的上传。内容管理图片上传、预览、AI描述与标签展示、图片列表与分页。AI集成后端调用GME-Qwen2-VL-2B模型实现图片内容分析。智能搜索基于AI生成的描述文本实现语义向量搜索支持用自然语言查找图片。1.2 技术栈怎么选选择的标准是主流、高效、适合快速开发和前后端分离。下面是我们用到的“全家福”。前端 (Frontend)Vue 3 Composition API当前最流行的前端框架之一响应式开发体验好生态丰富。我们用Composition API的写法逻辑组织更灵活。Element Plus基于Vue 3的UI组件库。用它能快速搭建出美观、交互一致的页面比如表单、按钮、表格、上传组件等省去大量写基础样式的时间。Axios处理HTTP请求的库用于前端和后端API通信。Vue Router管理前端页面路由。PiniaVue的官方状态管理库比Vuex更简单用来管理用户登录状态、全局配置等信息。后端 (Backend)Spring Boot 3.xJava领域事实上的微服务开发标准框架能极大简化配置和开发。我们用它快速构建RESTful API。Spring Security JWT处理用户认证登录和授权权限控制。用户登录后后端颁发一个JWT令牌给前端前端后续请求都带着这个令牌后端据此识别用户身份。MyBatis-Plus强大的数据持久层框架内置了很多通用CRUD操作能极大减少操作数据库的样板代码。MySQL关系型数据库用来存储用户信息、图片元数据如路径、描述、标签、上传者。MinIO一个高性能、兼容S3协议的对象存储服务。我们不用把图片文件直接存数据库而是存到MinIO里数据库只保存文件的访问路径。这比存数据库更专业、性能更好。GME-Qwen2-VL-2B主角登场。这是一个轻量级的视觉语言模型参数量为20亿。相比动辄上百亿的大模型它更节省资源部署和推理速度更快非常适合集成到我们这种Web应用中在常规服务器上就能跑起来。AI能力集成后端服务会通过HTTP API或SDK的方式调用部署好的GME-Qwen2-VL-2B模型服务。上传图片后后端将图片发送给模型获取文本描述再从中提取或让模型直接生成标签。整个系统的数据流是这样的用户从前端上传图片 - 后端Spring Boot接收 - 图片存入MinIO - 调用AI模型分析图片 - 将图片URL、描述、标签等信息存入MySQL - 前端获取数据并展示。搜索时用户输入文本 - 后端将文本与图片描述进行语义匹配 - 返回相关图片列表。2. 后端核心Spring Boot服务搭建后端是整个系统的大脑负责数据处理、业务逻辑和AI能力调度。我们先从它开始。2.1 初始化项目与基础依赖用你喜欢的IDE比如IntelliJ IDEA或者Spring Initializr网站创建一个新的Spring Boot项目。这里的关键是选对依赖。!-- pom.xml 关键依赖 -- dependencies !-- Web 基础 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- 安全认证 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-security/artifactId /dependency !-- JWT 支持 -- dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-impl/artifactId version0.11.5/version scoperuntime/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-jackson/artifactId version0.11.5/version scoperuntime/scope /dependency !-- 数据库相关 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version /dependency !-- MinIO 客户端 -- dependency groupIdio.minio/groupId artifactIdminio/artifactId version8.5.9/version /dependency !-- 工具类 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies在application.yml里配置数据库、MinIO连接等信息。# application.yml server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/ai_image_community?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 10MB max-request-size: 20MB minio: endpoint: http://localhost:9000 accessKey: your-minio-access-key secretKey: your-minio-secret-key bucketName: image-bucket # JWT 密钥生产环境请使用更安全的方式管理 jwt: secret: yourSuperSecretKeyForJWT1234567890 expiration: 86400000 # 24小时单位毫秒2.2 数据库设计与实体类我们先设计两张核心表user用户表和image_post图片帖子表。-- 用户表 CREATE TABLE user ( id bigint NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL UNIQUE COMMENT 用户名, password varchar(255) NOT NULL COMMENT 加密后的密码, nickname varchar(50) DEFAULT NULL COMMENT 昵称, avatar_url varchar(500) DEFAULT NULL COMMENT 头像链接, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 图片帖子表 CREATE TABLE image_post ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL COMMENT 上传用户ID, image_url varchar(500) NOT NULL COMMENT 图片在MinIO中的访问地址, description text COMMENT AI生成的图片描述, tags varchar(255) DEFAULT NULL COMMENT AI生成的标签用逗号分隔, like_count int DEFAULT 0 COMMENT 点赞数, view_count int DEFAULT 0 COMMENT 浏览数, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_create_time (create_time DESC) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;在Java代码中我们用MyBatis-Plus的注解来定义对应的实体类。// User.java Data TableName(user) public class User { TableId(type IdType.AUTO) private Long id; private String username; private String password; // 存储加密后的密码 private String nickname; private String avatarUrl; private LocalDateTime createTime; private LocalDateTime updateTime; } // ImagePost.java Data TableName(image_post) public class ImagePost { TableId(type IdType.AUTO) private Long id; private Long userId; private String imageUrl; private String description; private String tags; // 例如风景,日落,海滩 private Integer likeCount; private Integer viewCount; private LocalDateTime createTime; // 非数据库字段用于关联查询用户昵称等 TableField(exist false) private String authorName; TableField(exist false) private String authorAvatar; }2.3 用户认证与JWT处理安全是基础。我们使用Spring Security配置登录和权限校验并用JWT作为无状态令牌。// JwtUtil.java - JWT工具类 Component public class JwtUtil { Value(${jwt.secret}) private String secret; Value(${jwt.expiration}) private Long expiration; public String generateToken(String username) { Date now new Date(); Date expiryDate new Date(now.getTime() expiration); return Jwts.builder() .setSubject(username) .setIssuedAt(now) .setExpiration(expiryDate) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } public String getUsernameFromToken(String token) { Claims claims Jwts.parserBuilder() .setSigningKey(secret) .build() .parseClaimsJws(token) .getBody(); return claims.getSubject(); } // 省略验证token有效性的方法... } // SecurityConfig.java - 安全配置 Configuration EnableWebSecurity public class SecurityConfig { Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf().disable() // 禁用CSRF因为使用JWT .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态会话 .and() .authorizeHttpRequests(auth - auth .requestMatchers(/api/auth/**, /api/images/public/**).permitAll() // 登录注册和公开图片接口放行 .anyRequest().authenticated() // 其他所有请求都需要认证 ) .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); // 添加JWT过滤器 return http.build(); } Bean public JwtAuthenticationFilter jwtAuthenticationFilter() { return new JwtAuthenticationFilter(); } }我们还需要一个自定义的过滤器JwtAuthenticationFilter用来从HTTP请求头中解析JWT令牌并设置用户认证信息到Spring Security上下文中。同时实现UserDetailsService来从数据库加载用户信息用于登录验证。2.4 文件上传与MinIO集成图片文件不能直接存数据库。我们用MinIO来存储。// MinioConfig.java Configuration public class MinioConfig { Value(${minio.endpoint}) private String endpoint; Value(${minio.accessKey}) private String accessKey; Value(${minio.secretKey}) private String secretKey; Bean public MinioClient minioClient() { return MinioClient.builder() .endpoint(endpoint) .credentials(accessKey, secretKey) .build(); } } // MinioService.java Service public class MinioService { Autowired private MinioClient minioClient; Value(${minio.bucketName}) private String bucketName; /** * 上传文件 * param file 文件 * param objectName 对象名路径文件名 * return 文件访问URL */ public String uploadFile(MultipartFile file, String objectName) throws Exception { // 检查存储桶是否存在不存在则创建 boolean found minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()); if (!found) { minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()); } // 上传 minioClient.putObject( PutObjectArgs.builder() .bucket(bucketName) .object(objectName) .stream(file.getInputStream(), file.getSize(), -1) .contentType(file.getContentType()) .build() ); // 返回访问地址这里假设是公开可读的生产环境可能需要签名URL return String.format(%s/%s/%s, endpoint, bucketName, objectName); } }2.5 集成GME-Qwen2-VL-2B让应用“看懂”图片这是最核心的一步。我们需要调用模型服务来获取图片描述。假设模型已经通过API服务的形式部署好了例如使用Xinference、vLLM等框架部署提供一个HTTP接口。// AiService.java Service public class AiService { // 假设模型服务的地址 private static final String AI_MODEL_API http://your-ai-model-server:port/v1/vision/completion; Autowired private RestTemplate restTemplate; /** * 调用视觉模型分析图片并生成描述和标签 * param imageUrl 可公开访问的图片URL * return 包含描述和标签的DTO对象 */ public AiImageAnalysisResult analyzeImage(String imageUrl) { // 构建请求体格式需根据模型API的具体要求调整 MapString, Object requestBody new HashMap(); requestBody.put(model, qwen2-vl-2b); requestBody.put(image_url, imageUrl); // 或者将图片转为base64 requestBody.put(prompt, 请详细描述这张图片的内容并生成3-5个用逗号分隔的关键词标签。); HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntityMapString, Object request new HttpEntity(requestBody, headers); try { ResponseEntityMap response restTemplate.postForEntity(AI_MODEL_API, request, Map.class); MapString, Object responseBody response.getBody(); // 解析响应这里需要根据模型返回的实际JSON结构来调整 String fullResponse (String) responseBody.get(choices[0].message.content); // 简单示例假设模型返回“描述内容。标签标签1,标签2,标签3” AiImageAnalysisResult result new AiImageAnalysisResult(); // 这里需要写一个简单的解析逻辑将完整回复拆分为描述和标签 // 例如按句号分割最后一个句子处理标签 // 此处为示例逻辑实际需适配模型输出 result.setDescription(parseDescription(fullResponse)); result.setTags(parseTags(fullResponse)); return result; } catch (Exception e) { // 记录日志并返回一个默认结果或抛出业务异常 return getDefaultResult(); } } // 省略解析方法和默认结果生成方法... } // AiImageAnalysisResult.java Data public class AiImageAnalysisResult { private String description; private String tags; // 逗号分隔的字符串 }2.6 核心业务接口实现最后我们把上面的组件组装起来实现图片上传和搜索的Controller。// ImageController.java RestController RequestMapping(/api/images) public class ImageController { Autowired private ImagePostService imagePostService; Autowired private MinioService minioService; Autowired private AiService aiService; PostMapping(/upload) public Result uploadImage(RequestParam(file) MultipartFile file, RequestParam(value title, required false) String title, HttpServletRequest request) { // 1. 获取当前登录用户 Long userId getCurrentUserId(request); // 2. 上传文件到MinIO String objectName images/ UUID.randomUUID() _ file.getOriginalFilename(); String imageUrl minioService.uploadFile(file, objectName); // 3. 调用AI服务分析图片 AiImageAnalysisResult aiResult aiService.analyzeImage(imageUrl); // 4. 保存图片帖子信息到数据库 ImagePost post new ImagePost(); post.setUserId(userId); post.setImageUrl(imageUrl); post.setDescription(aiResult.getDescription()); post.setTags(aiResult.getTags()); imagePostService.save(post); // 5. 返回结果 return Result.success(上传成功, post); } GetMapping(/search) public Result searchImages(RequestParam(keyword) String keyword, RequestParam(value page, defaultValue 1) Integer page, RequestParam(value size, defaultValue 20) Integer size) { // 这里实现语义搜索。简单版在图片描述和标签中进行SQL模糊查询。 // 进阶版可以将描述文本向量化使用向量数据库如Milvus, pgvector进行相似度搜索。 PageImagePost resultPage imagePostService.searchByKeyword(keyword, page, size); return Result.success(resultPage); } // 获取当前用户ID的辅助方法 private Long getCurrentUserId(HttpServletRequest request) { // 从SecurityContext或JWT中解析 // 省略具体实现... } }3. 前端核心Vue 3管理界面开发后端API准备好了现在我们来构建用户交互的界面。前端使用Vue 3配合Element Plus组件库开发效率会很高。3.1 项目初始化与路由配置先用Vite创建一个新的Vue项目。npm create vuelatest ai-image-community-frontend # 按照提示选择 Vue Router, Pinia cd ai-image-community-frontend npm install npm install element-plus axios npm install element-plus/icons-vue在src/router/index.js中配置基本路由。import { createRouter, createWebHistory } from vue-router import HomeView from ../views/HomeView.vue import LoginView from ../views/LoginView.vue import RegisterView from ../views/RegisterView.vue import ProfileView from ../views/ProfileView.vue import UploadView from ../views/UploadView.vue const routes [ { path: /, name: home, component: HomeView }, { path: /login, name: login, component: LoginView }, { path: /register, name: register, component: RegisterView }, { path: /profile, name: profile, component: ProfileView, meta: { requiresAuth: true } }, { path: /upload, name: upload, component: UploadView, meta: { requiresAuth: true } }, ] const router createRouter({ history: createWebHistory(), routes, }) // 全局前置守卫检查登录状态 router.beforeEach((to, from, next) { const isAuthenticated localStorage.getItem(token) // 简单判断实际应更复杂 if (to.meta.requiresAuth !isAuthenticated) { next(/login) } else { next() } }) export default router3.2 用户认证与状态管理使用Pinia来管理全局的用户登录状态和Token。// stores/user.js import { defineStore } from pinia import { ref } from vue import axios from axios export const useUserStore defineStore(user, () { const token ref(localStorage.getItem(token) || ) const userInfo ref(JSON.parse(localStorage.getItem(userInfo)) || null) const login async (username, password) { try { const response await axios.post(/api/auth/login, { username, password }) const { data } response.data // 假设后端返回 { code: 200, data: { token, user } } token.value data.token userInfo.value data.user // 存储到本地 localStorage.setItem(token, data.token) localStorage.setItem(userInfo, JSON.stringify(data.user)) // 设置axios默认请求头 axios.defaults.headers.common[Authorization] Bearer ${data.token} return true } catch (error) { console.error(登录失败, error) return false } } const logout () { token.value userInfo.value null localStorage.removeItem(token) localStorage.removeItem(userInfo) delete axios.defaults.headers.common[Authorization] } return { token, userInfo, login, logout } })在main.js中全局引入Element Plus和Pinia。import { createApp } from vue import App from ./App.vue import router from ./router import { createPinia } from pinia import ElementPlus from element-plus import element-plus/dist/index.css import * as ElementPlusIconsVue from element-plus/icons-vue const app createApp(App) const pinia createPinia() app.use(router) app.use(pinia) app.use(ElementPlus) // 注册所有图标 for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component) } app.mount(#app)3.3 图片上传与AI结果展示页面这是用户的核心操作页面。我们使用Element Plus的Upload组件。!-- UploadView.vue -- template div classupload-container el-card template #header span分享你的图片/span /template el-upload classupload-demo drag action# // 我们自定义上传逻辑所以这里设为# :auto-uploadfalse :on-changehandleFileChange :show-file-listfalse acceptimage/* el-icon classel-icon--uploadupload-filled //el-icon div classel-upload__text拖拽图片到此处或 em点击选择/em/div /el-upload div v-ifpreviewImageUrl classpreview-section h3图片预览/h3 el-image :srcpreviewImageUrl fitcontain stylemax-height: 300px; / /div div v-ifuploading classuploading el-progress :percentageuploadProgress :statusuploadStatus / p{{ uploadMessage }}/p /div div v-ifaiResult classai-result h3AI识图结果/h3 el-descriptions :column1 border el-descriptions-item label描述{{ aiResult.description }}/el-descriptions-item el-descriptions-item label标签 el-tag v-fortag in aiResult.tags.split(,) :keytag stylemargin-right: 5px;{{ tag.trim() }}/el-tag /el-descriptions-item /el-descriptions /div div classaction-buttons el-button typeprimary :loadinguploading clicksubmitUpload :disabled!selectedFile 上传并分享 /el-button el-button clickreset重置/el-button /div /el-card /div /template script setup import { ref } from vue import { ElMessage } from element-plus import { UploadFilled } from element-plus/icons-vue import axios from axios const selectedFile ref(null) const previewImageUrl ref() const uploading ref(false) const uploadProgress ref(0) const uploadMessage ref() const uploadStatus ref() const aiResult ref(null) const handleFileChange (file) { selectedFile.value file.raw // 创建本地预览URL previewImageUrl.value URL.createObjectURL(file.raw) // 重置AI结果 aiResult.value null } const submitUpload async () { if (!selectedFile.value) { ElMessage.warning(请先选择图片) return } uploading.value true uploadMessage.value 正在上传图片... uploadStatus.value const formData new FormData() formData.append(file, selectedFile.value) try { // 调用我们后端的上传接口 const response await axios.post(/api/images/upload, formData, { headers: { Content-Type: multipart/form-data }, onUploadProgress: (progressEvent) { const percentCompleted Math.round((progressEvent.loaded * 100) / progressEvent.total) uploadProgress.value percentCompleted } }) uploadMessage.value 图片上传成功AI正在分析... uploadStatus.value success // 假设后端返回的数据中包含AI分析结果 aiResult.value { description: response.data.data.description, tags: response.data.data.tags } ElMessage.success(分享成功) } catch (error) { console.error(上传失败, error) uploadStatus.value exception uploadMessage.value 上传失败请重试 ElMessage.error(上传失败) } finally { uploading.value false // 稍后重置进度条 setTimeout(() { uploadProgress.value 0 }, 1000) } } const reset () { selectedFile.value null previewImageUrl.value aiResult.value null uploadProgress.value 0 } /script3.4 首页图片流与智能搜索首页需要展示图片流并提供一个智能搜索框。!-- HomeView.vue -- template div classhome-container div classsearch-bar el-input v-modelsearchKeyword placeholder输入任何描述来搜索图片比如‘一只可爱的猫’或‘城市夜景’ classsearch-input keyup.enterhandleSearch template #append el-button :iconSearch clickhandleSearch / /template /el-input /div div v-ifloading classloading el-skeleton :rows6 animated / /div div v-else div v-ifimageList.length 0 classempty el-empty description暂无图片快去上传一张吧 / /div el-row :gutter20 classimage-grid el-col v-forimage in imageList :keyimage.id :xs24 :sm12 :md8 :lg6 classimage-col el-card :body-style{ padding: 0px } shadowhover el-image :srcimage.imageUrl :preview-src-list[image.imageUrl] fitcover classimage-item lazy / div stylepadding: 14px; p classimage-description{{ truncate(image.description, 80) }}/p div classimage-tags el-tag v-fortag in image.tags.split(,) :keytag sizesmall typeinfo stylemargin-right: 5px; margin-bottom: 5px; {{ tag.trim() }} /el-tag /div div classimage-meta spanel-iconUser //el-icon {{ image.authorName }}/span spanel-iconClock //el-icon {{ formatTime(image.createTime) }}/span /div /div /el-card /el-col /el-row div classpagination el-pagination v-model:current-pagecurrentPage v-model:page-sizepageSize :page-sizes[12, 24, 48, 96] layouttotal, sizes, prev, pager, next, jumper :totaltotal size-changehandleSizeChange current-changehandleCurrentChange / /div /div /div /template script setup import { ref, onMounted } from vue import { Search, User, Clock } from element-plus/icons-vue import axios from axios import { ElMessage } from element-plus const searchKeyword ref() const imageList ref([]) const loading ref(false) const currentPage ref(1) const pageSize ref(12) const total ref(0) const fetchImages async (page 1, keyword ) { loading.value true try { const params { page, size: pageSize.value } if (keyword) params.keyword keyword const response await axios.get(/api/images/search, { params }) imageList.value response.data.data.records total.value response.data.data.total } catch (error) { console.error(获取图片失败, error) ElMessage.error(加载图片失败) } finally { loading.value false } } const handleSearch () { currentPage.value 1 fetchImages(1, searchKeyword.value) } const handleSizeChange (val) { pageSize.value val fetchImages(currentPage.value, searchKeyword.value) } const handleCurrentChange (val) { fetchImages(val, searchKeyword.value) } // 工具函数 const truncate (text, length) { if (!text) return return text.length length ? text.substring(0, length) ... : text } const formatTime (timeStr) { // 简单的时间格式化可以使用dayjs等库 return new Date(timeStr).toLocaleDateString() } onMounted(() { fetchImages() }) /script4. 项目部署与效果体验代码写完了最后一步就是让它跑起来看看实际效果。4.1 环境准备与启动后端确保安装了Java 17或以上版本Maven。启动MySQL创建数据库ai_image_community并运行我们提供的SQL建表语句。启动MinIO服务。可以从官网下载并运行或者使用Dockerdocker run -p 9000:9000 -p 9001:9001 minio/minio server /data --console-address :9001。然后登录管理界面默认端口9001创建image-bucket存储桶。部署GME-Qwen2-VL-2B模型服务。这需要一定的AI部署知识可以使用像Xinference这样的工具进行本地部署并暴露一个HTTP API端点。将后端AiService中的AI_MODEL_API地址修改为你的模型服务地址。修改application.yml中的数据库、MinIO连接信息。在项目根目录运行mvn spring-boot:run启动Spring Boot应用。前端确保安装了Node.js和npm。进入前端项目目录运行npm install安装依赖。在vite.config.js中配置后端API代理避免跨域问题。export default defineConfig({ // ... 其他配置 server: { proxy: { /api: { target: http://localhost:8080, // 你的后端地址 changeOrigin: true, } } } })运行npm run dev启动开发服务器。4.2 核心功能演示启动所有服务后打开浏览器访问前端地址通常是http://localhost:5173。注册登录首先注册一个新账号并登录。上传图片进入“上传”页面拖拽或选择一张本地图片比如一张风景照。点击上传后你会看到上传进度稍等片刻页面下方就会展示出AI模型自动生成的描述如“一片宁静的湖泊倒映着远处的雪山和蓝天白云湖边有几棵松树。”和标签如“湖泊雪山风景宁静”。浏览与搜索回到首页可以看到刚刚上传的图片以及AI生成的描述和标签。在顶部的搜索框尝试输入一些语义化的词语比如“有水的地方”或“山”系统应该能返回包含湖泊、雪山的图片即使你的描述里没有完全匹配“湖泊”这个词。这就是基于语义的搜索在起作用。4.3 可能的优化方向这个项目是一个完整的起点但还有很多可以深化和优化的地方语义搜索升级目前的搜索是基于文本模糊匹配。可以引入文本向量模型如BGE、text2vec将图片描述和搜索关键词都转化为向量存入向量数据库如Milvus、Weaviate或PostgreSQL的pgvector扩展实现真正的语义相似度搜索效果会好很多。性能与体验图片上传后调用AI模型分析可能是耗时操作。可以考虑采用异步处理上传后立即返回通过WebSocket或轮询通知用户AI分析完成。前端也可以做更优雅的加载状态。功能丰富增加图片点赞、收藏、评论功能完善用户个人主页展示上传历史、获赞数增加图片分类、热门排行榜等。模型调优针对图片分享社区的场景可以微调GME-Qwen2-VL-2B的提示词Prompt让它生成的描述更生动标签更符合社区分类习惯。部署上线将前后端分别打包Spring Boot打Jar包Vue项目构建静态文件使用Nginx做反向代理和静态资源服务通过Docker Compose编排所有服务Spring Boot App, MySQL, MinIO, 模型服务实现一键部署。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。

更多文章