Janus-Pro-7B项目实战:构建一个简易的AI绘画分享社区网站 Janus-Pro-7B项目实战构建一个简易的AI绘画分享社区网站最近和几个做独立开发的朋友聊天发现大家都有个共同的兴趣点想自己动手搭一个能玩AI绘画的网站。想法很简单就是让用户能在线生成图片然后像发朋友圈一样分享出来互相点赞评论。听起来挺酷但真要动手从用户注册登录到调用AI模型再到管理作品和互动这一整套流程下来技术栈还真不少。正好Janus-Pro-7B这个模型在图像生成上表现不错我们就用它作为核心的“画师”。今天这篇文章我就带你一步步把这个想法变成现实从零开始用前后端分离的思路搭建一个简易但功能完整的AI绘画分享社区。整个过程我会尽量讲得直白即使你之前没怎么接触过全栈开发跟着做下来也能有个清晰的框架。1. 项目蓝图我们要做一个什么样的网站在动手写代码之前我们先得把网站的样子和功能想清楚。这个社区的核心是“创作”和“分享”所以功能要围绕这两点展开。首先用户得能注册登录这是所有社区的基础。登录之后用户进入创作中心这里有一个简单的文本框用来输入对画面的描述比如“一只戴着宇航员头盔的猫在月球表面看地球”。用户点击生成网站的后台就会去调用Janus-Pro-7B模型把这段文字变成一张图片。图片生成后用户可以选择保存到自己的作品集也可以直接发布到社区的公共画廊。画廊就像一个大展厅所有用户发布的画作都会在这里按时间顺序展示。每幅画下面都有点赞和评论的功能用户可以在这里交流创作心得或者单纯地表达喜欢。对于网站管理者来说还需要一个后台能看到所有用户和作品进行一些基本的管理。整个项目的技术栈我们选择比较通用的组合前端用Vue 3界面清爽开发快后端用Python的FastAPI轻量且异步支持好适合处理AI模型调用这类耗时任务数据库用PostgreSQL稳定可靠最后把Janus-Pro-7B模型通过API服务的形式封装起来供后端调用。2. 搭建开发环境与项目骨架工欲善其事必先利其器。我们先来把开发环境准备好并把项目的基本目录结构搭起来。2.1 环境准备你需要确保电脑上已经安装了Python建议3.8以上版本和Node.js。数据库我们使用Docker来运行PostgreSQL这样最省事。# 1. 拉取并运行PostgreSQL容器 docker run --name ai-art-db -e POSTGRES_PASSWORDyourpassword -p 5432:5432 -d postgres:15 # 2. 创建项目根目录并进入 mkdir ai-art-community cd ai-art-community2.2 创建后端项目结构后端我们使用FastAPI。在项目根目录下创建一个backend文件夹并初始化虚拟环境。# 创建后端目录 mkdir backend cd backend # 创建Python虚拟环境Windows用户用 python -m venv venv python3 -m venv venv # 激活虚拟环境 # Mac/Linux: source venv/bin/activate # Windows: # venv\Scripts\activate # 安装核心依赖 pip install fastapi uvicorn sqlalchemy psycopg2-binary pydantic python-jose[cryptography] passlib[bcrypt] python-multipart requests接下来创建后端的主要文件和目录结构backend/ ├── app/ │ ├── __init__.py │ ├── main.py # FastAPI应用入口 │ ├── database.py # 数据库连接配置 │ ├── models.py # SQLAlchemy数据模型 │ ├── schemas.py # Pydantic数据验证模型 │ ├── crud.py # 数据库增删改查操作 │ ├── api/ │ │ ├── __init__.py │ │ ├── users.py # 用户相关路由 │ │ ├── artworks.py # 作品相关路由 │ │ └── ai.py # AI绘画生成路由 │ └── utils/ │ ├── __init__.py │ ├── security.py # 密码哈希、JWT令牌 │ └── janus_client.py # 调用Janus-Pro-7B的客户端 ├── requirements.txt └── .env # 环境变量配置文件2.3 创建前端项目结构前端我们使用Vue 3和Vite。回到项目根目录创建前端项目。cd .. # 回到项目根目录 # 使用Vite官方模板创建Vue项目 npm create vuelatest frontend # 创建过程中除了选择Vue和TypeScript其他选项按需选择或直接回车默认。 cd frontend npm install # 安装一些额外有用的UI库和工具 npm install axios pinia vue-router这样一个包含前后端基础骨架的项目就准备好了。接下来我们从数据库和后端核心逻辑开始。3. 构建后端核心用户、作品与AI集成后端是整个社区的大脑负责处理数据、业务逻辑和最重要的——与AI模型对话。3.1 设计数据库与模型我们先在app/models.py里定义两张核心表用户表和作品表。# backend/app/models.py from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey from sqlalchemy.orm import relationship from sqlalchemy.sql import func from app.database import Base class User(Base): __tablename__ users id Column(Integer, primary_keyTrue, indexTrue) username Column(String(50), uniqueTrue, indexTrue, nullableFalse) email Column(String(100), uniqueTrue, indexTrue, nullableFalse) hashed_password Column(String(200), nullableFalse) avatar_url Column(String(300), default/default-avatar.png) created_at Column(DateTime(timezoneTrue), server_defaultfunc.now()) # 关联关系一个用户拥有多幅作品 artworks relationship(Artwork, back_populatesauthor) class Artwork(Base): __tablename__ artworks id Column(Integer, primary_keyTrue, indexTrue) title Column(String(100), nullableFalse) description Column(Text) # 用户输入的绘画描述 image_url Column(String(500), nullableFalse) # 生成图片的存储地址 author_id Column(Integer, ForeignKey(users.id), nullableFalse) like_count Column(Integer, default0) created_at Column(DateTime(timezoneTrue), server_defaultfunc.now()) # 关联关系一幅作品属于一个用户 author relationship(User, back_populatesartworks)接着在app/database.py中配置数据库连接。# backend/app/database.py from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker import os from dotenv import load_dotenv load_dotenv() # 加载.env文件中的环境变量 DATABASE_URL os.getenv(DATABASE_URL, postgresql://postgres:yourpasswordlocalhost:5432/ai_art_db) engine create_engine(DATABASE_URL) SessionLocal sessionmaker(autocommitFalse, autoflushFalse, bindengine) Base declarative_base() # 依赖项用于在请求中获取数据库会话 def get_db(): db SessionLocal() try: yield db finally: db.close()记得在项目根目录创建.env文件配置你的数据库连接字符串和其他密钥。3.2 实现用户认证系统用户系统是社区的基石。我们实现注册、登录和JWT令牌认证。首先在app/utils/security.py中编写密码哈希和令牌创建的函数。# backend/app/utils/security.py from passlib.context import CryptContext from datetime import datetime, timedelta from jose import JWTError, jwt from typing import Optional import os SECRET_KEY os.getenv(SECRET_KEY, your-secret-key-change-in-production) ALGORITHM HS256 ACCESS_TOKEN_EXPIRE_MINUTES 30 pwd_context CryptContext(schemes[bcrypt], deprecatedauto) def verify_password(plain_password, hashed_password): return pwd_context.verify(plain_password, hashed_password) def get_password_hash(password): return pwd_context.hash(password) def create_access_token(data: dict, expires_delta: Optional[timedelta] None): to_encode data.copy() if expires_delta: expire datetime.utcnow() expires_delta else: expire datetime.utcnow() timedelta(minutesACCESS_TOKEN_EXPIRE_MINUTES) to_encode.update({exp: expire}) encoded_jwt jwt.encode(to_encode, SECRET_KEY, algorithmALGORITHM) return encoded_jwt然后在app/api/users.py中创建用户相关的API端点。# backend/app/api/users.py from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from app import crud, schemas from app.database import get_db from app.utils.security import get_password_hash, create_access_token from datetime import timedelta router APIRouter(prefix/users, tags[users]) router.post(/register, response_modelschemas.User) def register(user: schemas.UserCreate, db: Session Depends(get_db)): # 检查用户名和邮箱是否已存在 db_user_by_username crud.get_user_by_username(db, usernameuser.username) if db_user_by_username: raise HTTPException(status_code400, detailUsername already registered) db_user_by_email crud.get_user_by_email(db, emailuser.email) if db_user_by_email: raise HTTPException(status_code400, detailEmail already registered) # 创建新用户 return crud.create_user(dbdb, useruser) router.post(/login) def login(form_data: schemas.UserLogin, db: Session Depends(get_db)): # 验证用户 user crud.authenticate_user(db, form_data.username, form_data.password) if not user: raise HTTPException( status_codestatus.HTTP_401_UNAUTHORIZED, detailIncorrect username or password, headers{WWW-Authenticate: Bearer}, ) # 创建访问令牌 access_token_expires timedelta(minutes30) access_token create_access_token( data{sub: user.username}, expires_deltaaccess_token_expires ) return {access_token: access_token, token_type: bearer, user: user}这里的crud.py和schemas.py需要你根据模型去实现具体的数据库操作和Pydantic验证模型代码逻辑比较常规主要是增删改查和密码验证这里就不展开全部代码了。3.3 集成Janus-Pro-7B模型服务这是最核心也最有意思的部分。我们假设你已经通过某种方式比如使用预置的镜像部署好了Janus-Pro-7B的API服务它提供了一个接收文本描述、返回生成图片的接口。我们在app/utils/janus_client.py中创建一个简单的客户端来调用它。# backend/app/utils/janus_client.py import requests import base64 from io import BytesIO from PIL import Image import os import uuid JANUS_API_URL os.getenv(JANUS_API_URL, http://localhost:7860/sdapi/v1/txt2img) OUTPUT_DIR ./static/generated def generate_image_from_prompt(prompt: str, negative_prompt: str ): 调用Janus-Pro-7B API生成图片。 参数: prompt: 正面描述词 negative_prompt: 负面描述词不希望出现的元素 返回: 保存的图片文件名 payload { prompt: prompt, negative_prompt: negative_prompt, steps: 20, width: 512, height: 512, cfg_scale: 7.5, } try: response requests.post(JANUS_API_URL, jsonpayload) response.raise_for_status() r response.json() # API通常返回base64编码的图片 image_b64 r[images][0] image_data base64.b64decode(image_b64) # 将图片保存到本地静态文件目录 os.makedirs(OUTPUT_DIR, exist_okTrue) filename f{uuid.uuid4().hex}.png filepath os.path.join(OUTPUT_DIR, filename) with open(filepath, wb) as f: f.write(image_data) # 返回相对于静态服务的URL路径 return f/static/generated/{filename} except requests.exceptions.RequestException as e: # 处理网络或API错误 raise Exception(fFailed to call Janus API: {e}) except KeyError as e: raise Exception(fUnexpected response format from Janus API: {e})接着在app/api/ai.py中创建一个API端点供前端调用生成图片。# backend/app/api/ai.py from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from app.database import get_db from app.utils.janus_client import generate_image_from_prompt from app import schemas router APIRouter(prefix/ai, tags[ai]) router.post(/generate) def generate_artwork(request: schemas.ArtworkGenerate, db: Session Depends(get_db)): 接收用户绘画描述调用AI生成图片并返回图片URL。 这里暂时不关联用户和保存到数据库由前端决定是否发布。 try: image_url generate_image_from_prompt( promptrequest.prompt, negative_promptrequest.negative_prompt ) return {image_url: image_url, prompt: request.prompt} except Exception as e: raise HTTPException(status_code500, detailstr(e))3.4 实现作品发布与画廊功能有了图片生成能力接下来就是让用户能发布和展示作品。我们在app/api/artworks.py中实现相关接口。# backend/app/api/artworks.py from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from typing import List from app import crud, schemas from app.database import get_db from app.utils.security import get_current_user router APIRouter(prefix/artworks, tags[artworks]) router.post(/, response_modelschemas.Artwork) def create_artwork( artwork: schemas.ArtworkCreate, db: Session Depends(get_db), current_user: schemas.User Depends(get_current_user) ): 发布一幅新作品。需要用户登录。 # 可以在这里添加更多逻辑比如验证image_url是否有效等 return crud.create_artwork(dbdb, artworkartwork, author_idcurrent_user.id) router.get(/, response_modelList[schemas.Artwork]) def read_artworks(skip: int 0, limit: int 100, db: Session Depends(get_db)): 获取画廊中的所有作品支持分页。 artworks crud.get_artworks(db, skipskip, limitlimit) return artworks router.get(/{artwork_id}, response_modelschemas.Artwork) def read_artwork(artwork_id: int, db: Session Depends(get_db)): 获取单幅作品的详细信息。 db_artwork crud.get_artwork(db, artwork_idartwork_id) if db_artwork is None: raise HTTPException(status_code404, detailArtwork not found) return db_artwork router.post(/{artwork_id}/like) def like_artwork( artwork_id: int, db: Session Depends(get_db), current_user: schemas.User Depends(get_current_user) ): 给作品点赞。这里简化处理每次调用点赞数1。 实际项目中可能需要防止重复点赞。 return crud.increment_like(db, artwork_idartwork_id)后端的主要逻辑就这些。你需要运行数据库迁移可以用Alembic来创建表然后使用uvicorn app.main:app --reload启动后端服务。4. 打造前端界面从创作到画廊后端API准备好了现在我们来构建用户直接交互的界面。前端我们使用Vue 3配合一些基础组件。4.1 配置路由与状态管理首先在frontend/src/router/index.ts中配置页面路由。// frontend/src/router/index.ts import { createRouter, createWebHistory } from vue-router import HomeView from ../views/HomeView.vue import GalleryView from ../views/GalleryView.vue import CreateView from ../views/CreateView.vue import LoginView from ../views/LoginView.vue import RegisterView from ../views/RegisterView.vue const router createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: /, name: home, component: HomeView }, { path: /gallery, name: gallery, component: GalleryView }, { path: /create, name: create, component: CreateView }, { path: /login, name: login, component: LoginView }, { path: /register, name: register, component: RegisterView } ] }) export default router然后使用Pinia来管理用户登录状态。创建frontend/src/stores/user.ts。// frontend/src/stores/user.ts import { defineStore } from pinia import { ref } from vue import axios from axios export const useUserStore defineStore(user, () { const token ref(localStorage.getItem(token) || ) const username ref(localStorage.getItem(username) || ) const isAuthenticated ref(!!token.value) async function login(userCredentials: { username: string; password: string }) { try { const response await axios.post(http://localhost:8000/users/login, userCredentials) const { access_token, user } response.data token.value access_token username.value user.username isAuthenticated.value true // 存储到本地 localStorage.setItem(token, access_token) localStorage.setItem(username, user.username) axios.defaults.headers.common[Authorization] Bearer ${access_token} return true } catch (error) { console.error(Login failed:, error) return false } } function logout() { token.value username.value isAuthenticated.value false localStorage.removeItem(token) localStorage.removeItem(username) delete axios.defaults.headers.common[Authorization] } return { token, username, isAuthenticated, login, logout } })4.2 实现核心页面组件创作页面 (CreateView.vue)这是社区的核心功能页用户在这里输入描述生成并发布作品。!-- frontend/src/views/CreateView.vue -- template div classcreate-container h1开始你的创作/h1 div classinput-area textarea v-modelprompt placeholder描述你心中的画面越详细越好... 例如一只在咖啡馆看书的小狐狸温暖的阳光水彩风格/textarea textarea v-modelnegativePrompt placeholder可选描述你不希望出现在画面中的元素.../textarea button clickgenerateImage :disabledisGenerating {{ isGenerating ? 生成中... : 生成画作 }} /button /div div v-ifgeneratedImageUrl classpreview-area h3生成结果/h3 img :srcgeneratedImageUrl alt生成的画作 classpreview-image / div classaction-buttons input v-modelartworkTitle placeholder给你的作品起个名字 / button clickpublishArtwork :disabled!artworkTitle发布到画廊/button button clickregenerate重新生成/button /div /div /div /template script setup langts import { ref } from vue import axios from axios import { useUserStore } from /stores/user const prompt ref() const negativePrompt ref() const generatedImageUrl ref() const artworkTitle ref() const isGenerating ref(false) const userStore useUserStore() async function generateImage() { if (!prompt.value.trim()) { alert(请输入描述) return } isGenerating.value true try { const response await axios.post(http://localhost:8000/ai/generate, { prompt: prompt.value, negative_prompt: negativePrompt.value }) generatedImageUrl.value http://localhost:8000${response.data.image_url} } catch (error) { console.error(生成失败:, error) alert(生成失败请稍后重试。) } finally { isGenerating.value false } } async function publishArtwork() { if (!userStore.isAuthenticated) { alert(请先登录) return } try { await axios.post(http://localhost:8000/artworks/, { title: artworkTitle.value, description: prompt.value, image_url: generatedImageUrl.value.replace(http://localhost:8000, ) // 存相对路径 }) alert(发布成功) // 清空表单跳转到画廊 prompt.value negativePrompt.value generatedImageUrl.value artworkTitle.value } catch (error) { console.error(发布失败:, error) alert(发布失败。) } } function regenerate() { generatedImageUrl.value } /script style scoped .create-container { max-width: 800px; margin: 0 auto; padding: 2rem; } textarea { width: 100%; height: 100px; margin-bottom: 1rem; padding: 0.5rem; } .preview-image { max-width: 512px; max-height: 512px; border: 1px solid #ccc; margin: 1rem 0; } /style画廊页面 (GalleryView.vue)这里是社区的内容中心展示所有用户的作品。!-- frontend/src/views/GalleryView.vue -- template div classgallery-container h1社区画廊/h1 div v-ifloading加载中.../div div v-else classartworks-grid div v-forartwork in artworks :keyartwork.id classartwork-card img :srchttp://localhost:8000 artwork.image_url :altartwork.title / div classartwork-info h3{{ artwork.title }}/h3 p classdescription{{ artwork.description }}/p p classauthor作者: {{ artwork.author?.username }}/p div classmeta span❤️ {{ artwork.like_count }}/span span{{ formatDate(artwork.created_at) }}/span /div button clicklikeArtwork(artwork.id)点赞/button /div /div /div /div /template script setup langts import { ref, onMounted } from vue import axios from axios interface Artwork { id: number title: string description: string image_url: string like_count: number created_at: string author: { username: string } } const artworks refArtwork[]([]) const loading ref(true) onMounted(async () { await fetchArtworks() }) async function fetchArtworks() { try { const response await axios.get(http://localhost:8000/artworks/) artworks.value response.data } catch (error) { console.error(获取作品失败:, error) } finally { loading.value false } } async function likeArtwork(artworkId: number) { try { await axios.post(http://localhost:8000/artworks/${artworkId}/like) // 点赞成功后更新本地数据 const artwork artworks.value.find(a a.id artworkId) if (artwork) { artwork.like_count 1 } } catch (error) { console.error(点赞失败:, error) alert(请先登录) } } function formatDate(dateString: string) { return new Date(dateString).toLocaleDateString() } /script style scoped .artworks-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 2rem; } .artwork-card { border: 1px solid #eee; border-radius: 8px; overflow: hidden; } .artwork-card img { width: 100%; height: 300px; object-fit: cover; } .artwork-info { padding: 1rem; } .description { color: #666; font-size: 0.9rem; margin: 0.5rem 0; } .meta { display: flex; justify-content: space-between; font-size: 0.8rem; color: #999; margin-top: 1rem; } /style登录、注册和首页的组件实现起来相对标准这里就不占用过多篇幅了。它们主要负责用户认证和导航。5. 项目总结与展望走完这一趟一个简易AI绘画社区的核心骨架就搭起来了。从后端的用户认证、数据库设计、AI模型集成到前端的页面交互和状态管理我们串联起了一个全栈应用的基本流程。实际用下来你会发现FastAPI和Vue 3的组合响应很快开发体验也比较顺畅。Janus-Pro-7B的集成是关键一步把它封装成一个独立的API客户端让后端的业务逻辑和复杂的模型调用解耦后期维护或更换模型都会方便很多。当然这只是一个起点。真要作为一个产品来运营还有很多可以打磨的地方。比如图片生成通常比较耗时可以考虑引入任务队列比如Celery和WebSocket让生成过程变成异步的用户提交任务后可以去干别的生成好了再通知他。再比如现在的画廊只是简单列表可以加入分类、标签、搜索甚至根据点赞和热度排序。用户互动方面评论功能、收藏夹、关注系统都能让社区更有活力。部署的时候也要考虑更多比如用Nginx做反向代理和静态文件服务用Docker Compose把前后端和数据库打包管理对于生成的大量图片最好用对象存储服务比如MinIO或云服务商的对象存储而不是直接存在服务器硬盘上。这个项目更像一个“技术原型”它验证了想法是可行的。你可以基于它根据自己的需求添加任何功能。AI生成内容的社区玩法还有很多比如举办主题创作比赛、让用户对生成参数进行投票等等想象力是唯一的限制。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。