Web开发全栈实践:构建一个带用户管理的人脸检测展示网站

张开发
2026/4/11 17:30:24 15 分钟阅读

分享文章

Web开发全栈实践:构建一个带用户管理的人脸检测展示网站
Web开发全栈实践构建一个带用户管理的人脸检测展示网站你是不是也想过那些能上传图片、自动识别人脸、还能记录你操作历史的网站是怎么做出来的今天我们就来动手搭建一个。这不仅仅是个简单的“Hello World”项目而是一个麻雀虽小五脏俱全的AI应用有用户能注册登录能上传图片进行人脸检测能查看自己的历史记录甚至还有积分消耗这样的业务逻辑。听起来有点复杂别担心我们会用最主流、最清晰的技术栈来一步步实现它。前端用Vue因为它灵活又现代后端用Python的Flask框架轻量又好上手AI能力则直接调用现成的MogFace人脸检测模型。最后我们还会把这个完整的应用部署到云平台上让它真正能对外访问。整个过程我会像朋友聊天一样把每一步为什么这么做、可能会遇到什么坑都跟你讲清楚。目标就是让你看完之后不仅能复现这个项目更能理解一个完整Web应用的骨架是怎么搭起来的。1. 项目蓝图我们要做一个什么样的网站在动手写代码之前我们先得把想法画成图纸。这个网站的核心功能其实就围绕两个角色用户和AI能力。想象一下一个用户来到我们的网站他需要先注册一个账号然后登录。登录后他进入主页面这里可以上传一张包含人脸的图片。点击“检测”按钮网站会把图片发给后端的AI模型。模型识别出图片中有几张脸并把它们用框标出来。处理后的图片和结果比如检测到3个人会展示给用户看。同时这次检测记录会被保存下来用户可以在“我的历史”页面里查看所有过往的检测包括原图、结果图和消耗的积分。我们还可以设计一个简单的积分系统比如新用户注册送100积分每次检测消耗10积分积分不足就无法继续使用。所以我们的项目结构自然就分成了三大块前端 (Vue)负责所有用户能看到的界面比如登录页、上传图片的按钮、展示结果的区域。它就像餐厅的服务员和菜单。后端 (Flask)负责处理业务逻辑比如验证用户密码、保存图片、调用AI模型、管理数据库。它就像后厨和收银台。AI服务 (MogFace)提供最核心的人脸检测能力。它就像后厨里那位技艺高超的厨师。为了让它们能互相沟通我们需要定义好“菜谱”API接口。比如前端要告诉后端“用户A想检测这张图”后端处理完后再告诉前端“检测完了结果是3个人这是框好的图”。2. 搭建后厨用Flask构建后端API后端是我们整个应用的大脑和记忆中枢。我们先从它开始因为API定义好了前端才知道该怎么调用。2.1 初始化Flask项目与环境首先确保你的电脑上安装了Python建议3.8以上版本。然后我们创建一个新的项目文件夹比如叫face-detection-saas并在里面初始化后端。# 创建项目文件夹并进入 mkdir face-detection-saas cd face-detection-saas # 创建后端专属文件夹 mkdir backend cd backend # 创建虚拟环境避免包冲突 python -m venv venv # 激活虚拟环境 # 在Windows上 venv\Scripts\activate # 在Mac/Linux上 source venv/bin/activate # 安装核心依赖 pip install flask flask-cors flask-sqlalchemy flask-login flask-bcrypt pillow这里简单解释一下这几个包是干嘛的flask: Web框架本体。flask-cors: 解决前端和后端在不同地址域名/端口访问时的跨域问题。flask-sqlalchemy: 让我们能用Python对象的方式来操作数据库非常方便。flask-login: 帮我们管理用户的登录状态不用自己从头写Session和Cookie逻辑。flask-bcrypt: 用来加密用户的密码存到数据库里的不是明文更安全。pillow: Python里处理图片的库等会儿用来处理用户上传的图片。2.2 设计数据库与用户模型我们的数据需要存起来这里用最简单的SQLite数据库一个文件就是一个数据库。在backend文件夹里创建一个app.py文件开始编写核心代码。# backend/app.py import os from datetime import datetime from flask import Flask, request, jsonify from flask_sqlalchemy import SQLAlchemy from flask_bcrypt import Bcrypt from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user from flask_cors import CORS # 初始化Flask应用 app Flask(__name__) app.config[SECRET_KEY] your-secret-key-here # 生产环境要用更复杂的随机字符串 app.config[SQLALCHEMY_DATABASE_URI] sqlite:///site.db # 数据库文件位置 app.config[SQLALCHEMY_TRACK_MODIFICATIONS] False # 初始化扩展 db SQLAlchemy(app) bcrypt Bcrypt(app) login_manager LoginManager(app) login_manager.login_view login # 设置未登录时跳转的视图函数名 CORS(app) # 允许跨域请求 # 定义数据模型 class User(db.Model, UserMixin): id db.Column(db.Integer, primary_keyTrue) username db.Column(db.String(20), uniqueTrue, nullableFalse) email db.Column(db.String(120), uniqueTrue, nullableFalse) password_hash db.Column(db.String(60), nullableFalse) credits db.Column(db.Integer, default100) # 用户积分默认100 detection_history db.relationship(DetectionRecord, backrefauthor, lazyTrue) def set_password(self, password): self.password_hash bcrypt.generate_password_hash(password).decode(utf-8) def check_password(self, password): return bcrypt.check_password_hash(self.password_hash, password) class DetectionRecord(db.Model): id db.Column(db.Integer, primary_keyTrue) original_image_path db.Column(db.String(200), nullableFalse) result_image_path db.Column(db.String(200)) face_count db.Column(db.Integer, nullableFalse) credits_used db.Column(db.Integer, nullableFalse) timestamp db.Column(db.DateTime, nullableFalse, defaultdatetime.utcnow) user_id db.Column(db.Integer, db.ForeignKey(user.id), nullableFalse) # 创建数据库表首次运行 with app.app_context(): db.create_all() login_manager.user_loader def load_user(user_id): return User.query.get(int(user_id))这段代码做了几件关键事创建了两个“表”User存用户信息和DetectionRecord存检测记录。定义了它们之间的关系一个用户可以有多个检测记录detection_history。为用户密码提供了加密和验证的方法set_password,check_password。设置了login_manager如何根据用户ID加载用户对象。2.3 实现核心API用户与检测接下来我们来实现最关键的几个API接口注册、登录、检测图片、查看历史。# backend/app.py (续) # 用户注册接口 app.route(/api/register, methods[POST]) def register(): data request.get_json() username data.get(username) email data.get(email) password data.get(password) if User.query.filter_by(usernameusername).first(): return jsonify({message: 用户名已存在}), 400 if User.query.filter_by(emailemail).first(): return jsonify({message: 邮箱已被注册}), 400 user User(usernameusername, emailemail) user.set_password(password) db.session.add(user) db.session.commit() return jsonify({message: 注册成功}), 201 # 用户登录接口 app.route(/api/login, methods[POST]) def login(): data request.get_json() user User.query.filter_by(usernamedata.get(username)).first() if user and user.check_password(data.get(password)): login_user(user, rememberTrue) return jsonify({message: 登录成功, user: {id: user.id, username: user.username, credits: user.credits}}) return jsonify({message: 用户名或密码错误}), 401 # 用户登出接口 app.route(/api/logout) login_required def logout(): logout_user() return jsonify({message: 已登出}) # 获取当前用户信息 app.route(/api/current_user) login_required def get_current_user(): return jsonify({id: current_user.id, username: current_user.username, credits: current_user.credits}) # 人脸检测接口 (核心) app.route(/api/detect, methods[POST]) login_required def detect_faces(): # 1. 检查积分 if current_user.credits 10: return jsonify({message: 积分不足请充值或联系管理员}), 403 # 2. 检查并保存上传的图片 if image not in request.files: return jsonify({message: 未上传图片}), 400 file request.files[image] if file.filename : return jsonify({message: 未选择文件}), 400 # 为每个用户创建独立的文件夹存放图片 user_upload_dir os.path.join(uploads, str(current_user.id)) os.makedirs(user_upload_dir, exist_okTrue) original_filename foriginal_{datetime.utcnow().strftime(%Y%m%d%H%M%S)}.jpg original_path os.path.join(user_upload_dir, original_filename) file.save(original_path) # 3. 调用MogFace进行人脸检测 (这里用伪代码示意) # 假设我们有一个函数 detect_faces_with_mogface(image_path) # 它会返回检测到的人脸数量并把带框的结果图保存到另一个路径 result_filename fresult_{datetime.utcnow().strftime(%Y%m%d%H%M%S)}.jpg result_path os.path.join(user_upload_dir, result_filename) # 伪代码实际调用AI模型 # face_count detect_faces_with_mogface(original_path, result_path) # 为了演示我们模拟一个结果 face_count 3 # 假设检测到3张脸 # 模拟生成结果图实际应调用模型 # 这里简单复制原图作为示意实际项目中替换为模型处理 from PIL import Image, ImageDraw img Image.open(original_path) draw ImageDraw.Draw(img) # 模拟画几个框 draw.rectangle([50, 50, 150, 150], outlinered, width3) draw.rectangle([200, 80, 300, 180], outlinered, width3) draw.rectangle([350, 120, 450, 220], outlinered, width3) img.save(result_path) # 4. 扣除积分并保存记录 current_user.credits - 10 record DetectionRecord( original_image_pathoriginal_path, result_image_pathresult_path, face_countface_count, credits_used10, user_idcurrent_user.id ) db.session.add(record) db.session.commit() # 5. 返回结果给前端 # 注意实际部署时图片需要通过网络URL访问这里返回相对路径前端需拼接完整URL return jsonify({ message: 检测成功, face_count: face_count, original_image: f/uploads/{current_user.id}/{original_filename}, result_image: f/uploads/{current_user.id}/{result_filename}, remaining_credits: current_user.credits }) # 获取用户历史记录 app.route(/api/history) login_required def get_history(): records DetectionRecord.query.filter_by(user_idcurrent_user.id).order_by(DetectionRecord.timestamp.desc()).all() history_list [] for record in records: history_list.append({ id: record.id, face_count: record.face_count, credits_used: record.credits_used, timestamp: record.timestamp.isoformat(), original_image: f/uploads/{current_user.id}/{os.path.basename(record.original_image_path)}, result_image: f/uploads/{current_user.id}/{os.path.basename(record.result_image_path)} }) return jsonify(history_list) # 提供一个静态文件路由让前端能访问上传的图片 app.route(/uploads/path:filename) def uploaded_file(filename): from flask import send_from_directory return send_from_directory(uploads, filename) if __name__ __main__: # 创建uploads目录 os.makedirs(uploads, exist_okTrue) app.run(debugTrue, port5000)代码要点解析login_required这个装饰器就像门卫确保只有登录的用户才能访问检测和历史接口。积分检查在检测前先判断用户积分是否足够。文件处理保存用户上传的图片到以用户ID命名的文件夹避免文件混乱。AI调用部分这里是伪代码。在实际项目中你需要集成MogFace WebUI的API或直接调用其模型。核心是得到一个face_count和一张处理后的result_image。历史记录查询数据库按时间倒序返回并把图片路径转换成前端可以访问的URL。后端的基础骨架这就搭好了。你可以先运行python app.py试试看看Flask服务能否正常启动。接下来我们去构建前端的界面。3. 打造门面用Vue构建前端界面前端负责和用户交互并把用户的请求通过API发给后端。我们用Vue 3的组合式API来写代码会更清晰。3.1 初始化Vue项目与路由我们使用Vite来快速创建Vue项目它比传统的Vue CLI更快更轻量。# 回到项目根目录 face-detection-saas cd .. # 使用Vite创建Vue项目项目名设为 frontend npm create vuelatest frontend # 创建过程中只需选择安装 Vue Router 即可其他如Pinia、测试工具等按需选择或直接回车跳过。 cd frontend npm install npm install axios --save # 用于发送HTTP请求到后端 npm install element-plus --save # 使用Element Plus UI组件库让界面更美观创建好后我们来规划一下前端路由也就是我们网站的几个主要页面/login登录页/register注册页/dashboard主面板检测页面登录后才能访问/history历史记录页登录后才能访问修改src/router/index.js文件或src/router.js来配置路由。// src/router/index.js import { createRouter, createWebHistory } from vue-router import Login from ../views/Login.vue import Register from ../views/Register.vue import Dashboard from ../views/Dashboard.vue import History from ../views/History.vue const routes [ { path: /, redirect: /login // 默认跳转到登录页 }, { path: /login, name: Login, component: Login }, { path: /register, name: Register, component: Register }, { path: /dashboard, name: Dashboard, component: Dashboard, meta: { requiresAuth: true } // 需要登录认证 }, { path: /history, name: History, component: History, meta: { requiresAuth: true } // 需要登录认证 } ] const router createRouter({ history: createWebHistory(), routes }) // 路由守卫检查哪些页面需要登录 router.beforeEach((to, from, next) { const isAuthenticated !!localStorage.getItem(userToken) // 简单用token判断实际应与后端状态同步 if (to.meta.requiresAuth !isAuthenticated) { next(/login) // 如果未登录且要去需认证的页面则跳转到登录页 } else { next() } }) export default router3.2 创建核心页面组件我们需要创建四个主要的页面组件。为了简洁这里展示每个组件的核心逻辑和结构。首先创建一个统一的API请求工具src/utils/request.js方便管理后端地址和请求头。// src/utils/request.js import axios from axios const request axios.create({ baseURL: http://localhost:5000/api, // 后端API地址开发时是本地5000端口 timeout: 10000 }) // 请求拦截器为需要认证的请求自动添加token request.interceptors.request.use( config { const token localStorage.getItem(userToken) if (token) { config.headers.Authorization Bearer ${token} // 假设使用Bearer Token需与后端匹配 } return config }, error { return Promise.reject(error) } ) // 响应拦截器统一处理错误 request.interceptors.response.use( response response.data, error { console.error(API请求错误:, error.response?.data || error.message) return Promise.reject(error) } ) export default request然后创建登录页src/views/Login.vue。!-- src/views/Login.vue -- template div classlogin-container el-card classlogin-box h2人脸检测系统登录/h2 el-form :modelform :rulesrules refloginFormRef el-form-item propusername el-input v-modelform.username placeholder请输入用户名 prefix-iconUser / /el-form-item el-form-item proppassword el-input v-modelform.password typepassword placeholder请输入密码 prefix-iconLock keyup.enterhandleLogin / /el-form-item el-form-item el-button typeprimary clickhandleLogin :loadingloading stylewidth:100%登录/el-button /el-form-item /el-form div classfooter span还没有账号/span el-link typeprimary click$router.push(/register)立即注册/el-link /div /el-card /div /template script setup import { ref, reactive } from vue import { useRouter } from vue-router import { ElMessage } from element-plus import request from /utils/request const router useRouter() const loginFormRef ref() const loading ref(false) const form reactive({ username: , password: }) const rules { username: [{ required: true, message: 请输入用户名, trigger: blur }], password: [{ required: true, message: 请输入密码, trigger: blur }] } const handleLogin async () { await loginFormRef.value.validate() loading.value true try { const response await request.post(/login, form) ElMessage.success(response.message) // 假设登录成功后后端返回了用户信息和token我们存起来 localStorage.setItem(userToken, 模拟Token) // 实际应从response中取 localStorage.setItem(userInfo, JSON.stringify(response.user)) router.push(/dashboard) } catch (error) { ElMessage.error(error.response?.data?.message || 登录失败) } finally { loading.value false } } /script style scoped .login-container { display: flex; justify-content: center; align-items: center; height: 100vh; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } .login-box { width: 400px; padding: 30px; } .footer { text-align: center; margin-top: 20px; color: #666; } /style接着创建主面板检测页src/views/Dashboard.vue。这是最复杂的页面包含上传、预览、检测和结果显示。!-- src/views/Dashboard.vue -- template div classdashboard-container el-header classheader span classtitle人脸检测中心/span div classuser-info span欢迎{{ userInfo.username }}/span span classcredits积分{{ userInfo.credits }}/span el-button typetext click$router.push(/history)我的历史/el-button el-button typetext clickhandleLogout退出/el-button /div /el-header el-main el-row :gutter20 el-col :span12 el-card classupload-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 classpreview v-ifimagePreview img :srcimagePreview alt预览 / /div div classaction el-button typeprimary :disabled!selectedFile || detecting clickhandleDetect :loadingdetecting {{ detecting ? 检测中... : 开始检测 (消耗10积分) }} /el-button el-button clickclearSelection :disableddetecting清空/el-button /div /el-card /el-col el-col :span12 el-card classresult-card template #header span检测结果/span /template div v-ifdetectionResult div classresult-info p检测到 strong{{ detectionResult.face_count }}/strong 张人脸/p p剩余积分strong{{ detectionResult.remaining_credits }}/strong/p /div div classimage-comparison div h4原图/h4 img :srchttp://localhost:5000${detectionResult.original_image} alt原图 / /div div h4结果图/h4 img :srchttp://localhost:5000${detectionResult.result_image} alt结果图 / /div /div /div div v-else classempty-result el-iconpicture //el-icon p上传图片并点击检测结果将显示在这里/p /div /el-card /el-col /el-row /el-main /div /template script setup import { ref, reactive, onMounted } from vue import { useRouter } from vue-router import { ElMessage, ElMessageBox } from element-plus import { UploadFilled, Picture } from element-plus/icons-vue import request from /utils/request const router useRouter() const selectedFile ref(null) const imagePreview ref() const detecting ref(false) const detectionResult ref(null) const userInfo reactive({ username: , credits: 0 }) onMounted(() { const info localStorage.getItem(userInfo) if (info) { Object.assign(userInfo, JSON.parse(info)) } }) const handleFileChange (file) { selectedFile.value file.raw const reader new FileReader() reader.onload (e) { imagePreview.value e.target.result } reader.readAsDataURL(file.raw) } const clearSelection () { selectedFile.value null imagePreview.value detectionResult.value null } const handleDetect async () { if (!selectedFile.value) return detecting.value true const formData new FormData() formData.append(image, selectedFile.value) try { const result await request.post(/detect, formData, { headers: { Content-Type: multipart/form-data } }) detectionResult.value result // 更新本地用户积分 userInfo.credits result.remaining_credits localStorage.setItem(userInfo, JSON.stringify(userInfo)) ElMessage.success(result.message) } catch (error) { ElMessage.error(error.response?.data?.message || 检测失败) } finally { detecting.value false } } const handleLogout async () { try { await request.get(/logout) } catch (e) { // 忽略错误 } localStorage.removeItem(userToken) localStorage.removeItem(userInfo) router.push(/login) } /script style scoped .dashboard-container { height: 100vh; display: flex; flex-direction: column; } .header { display: flex; justify-content: space-between; align-items: center; background-color: #409EFF; color: white; } .title { font-size: 1.5rem; font-weight: bold; } .user-info { display: flex; align-items: center; gap: 15px; } .credits { font-weight: bold; color: #ffd700; } .upload-card, .result-card { height: 600px; } .preview img, .image-comparison img { max-width: 100%; max-height: 300px; border: 1px solid #ddd; border-radius: 4px; margin-top: 15px; } .image-comparison { display: flex; justify-content: space-around; margin-top: 20px; } .image-comparison div { text-align: center; } .empty-result { display: flex; flex-direction: column; justify-content: center; align-items: center; height: 400px; color: #999; } .empty-result .el-icon { font-size: 80px; margin-bottom: 20px; } .action { margin-top: 20px; text-align: center; } /style注册页(Register.vue)和历史页(History.vue)的逻辑类似主要是调用对应的后端API并展示数据这里限于篇幅不展开你可以在注册页调用/api/register在历史页调用/api/history并循环展示记录列表。3.3 前后端联调与运行现在我们有了一个能跑起来的前后端应用。启动后端在backend目录下运行python app.py。你应该看到Flask服务在http://127.0.0.1:5000启动。启动前端在frontend目录下运行npm run dev。Vite通常会启动在http://localhost:5173。联调打开浏览器访问http://localhost:5173。首先注册一个账号。然后登录进入Dashboard。上传一张带人脸的图片点击检测。如果一切顺利你会看到模拟的检测结果三个红框和积分扣除。点击“我的历史”可以看到刚才的检测记录。注意前端访问后端图片时如http://localhost:5000/uploads/...因为端口不同属于跨域。我们已经在后端通过CORS(app)解决了API跨域但静态文件服务可能还需要配置。上面的uploaded_file路由已经提供了静态文件访问。如果遇到图片不显示检查浏览器控制台网络请求。4. 集成AI核心连接MogFace人脸检测前面的演示中我们用画红框模拟了检测结果。现在我们来接入真正的AI能力。MogFace是一个优秀的人脸检测模型我们假设你已经通过其WebUI或API服务的形式部署好了它。集成方式通常有两种HTTP API调用如果MogFace提供了WebUI或REST API我们的Flask后端就充当一个中间层接收前端图片转发给MogFace服务拿到结果后再处理返回。本地库调用如果MogFace是一个Python库可以直接在Flask应用中导入并调用。这里以更常见的HTTP API调用为例。假设MogFace服务运行在http://localhost:7860例如通过Gradio启动并有一个/detect接口接收图片返回人脸框坐标。我们需要修改后端的detect_faces函数中的AI调用部分# backend/app.py (detect_faces函数部分修改) import requests def detect_faces_with_mogface(image_path, result_path): 调用MogFace API进行人脸检测 mogface_url http://localhost:7860/detect # MogFace服务地址 try: with open(image_path, rb) as img_file: files {image: img_file} response requests.post(mogface_url, filesfiles, timeout30) if response.status_code 200: result response.json() # 假设返回格式{faces: [[x1, y1, x2, y2], ...]} face_boxes result.get(faces, []) face_count len(face_boxes) # 在原图上绘制检测框 from PIL import Image, ImageDraw img Image.open(image_path) draw ImageDraw.Draw(img) for box in face_boxes: # box格式可能是 [x1, y1, x2, y2] draw.rectangle(box, outlinered, width3) img.save(result_path) return face_count else: print(fMogFace API调用失败: {response.status_code}) return 0 except Exception as e: print(f调用MogFace时发生错误: {e}) return 0 # 在原来的detect_faces接口中替换掉模拟代码 # face_count 3 # 删除这行 face_count detect_faces_with_mogface(original_path, result_path) # 使用这行这样后端就真正具备了人脸检测的能力。你需要确保MogFace服务已经启动并运行在指定端口。5. 部署上线将完整应用发布到星图平台本地开发测试没问题后我们就可以考虑把它部署到公网让其他人也能访问。CSDN星图平台提供了便捷的一键部署能力。部署全栈应用通常有两种思路前后端分离部署将前端构建出的静态文件HTML, JS, CSS和后端Python服务分开部署。前端可以使用Nginx等Web服务器托管后端使用Gunicorn等WSGI服务器。一体化部署将前端静态文件作为Flask应用的一部分来服务。这样只需要部署一个Flask应用即可。为了简化我们采用第二种方式并编写适合星图平台的部署配置文件。5.1 准备生产环境首先我们需要冻结后端的依赖包列表并调整前端配置。# 在后端目录下 cd backend pip freeze requirements.txt编辑requirements.txt确保包含了所有必要的包flask, gunicorn, pillow等。然后构建前端静态文件。# 在前端目录下 cd ../frontend npm run build这会在frontend/dist目录下生成优化后的静态文件。5.2 修改Flask以服务前端静态文件我们需要修改backend/app.py让Flask在生产环境下也能提供前端页面。# backend/app.py (在文件末尾if __name__ __main__: 之前添加) from flask import send_from_directory # ... 其他代码 ... # 在生产环境下服务前端静态文件 app.route(/, defaults{path: }) app.route(/path:path) def serve_frontend(path): if path and os.path.exists(os.path.join(../frontend/dist, path)): # 如果请求的是前端dist目录下存在的文件如JS、CSS return send_from_directory(../frontend/dist, path) else: # 否则返回前端入口HTML文件由Vue Router处理路由 return send_from_directory(../frontend/dist, index.html)同时需要修改前端路由为历史模式我们之前已经用了createWebHistory并且在前端构建时设置正确的公共路径publicPath。修改frontend/vite.config.js// frontend/vite.config.js import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ plugins: [vue()], base: ./, // 设置为相对路径这样静态资源引用不会出错 })5.3 创建星图平台部署配置文件星图平台通常需要一个描述如何启动应用的配置文件比如Dockerfile或docker-compose.yml。这里我们创建一个简单的Dockerfile来定义运行环境。# backend/Dockerfile # 使用Python官方镜像 FROM python:3.9-slim # 设置工作目录 WORKDIR /app # 复制依赖文件并安装 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 复制后端应用代码 COPY . . # 复制构建好的前端静态文件 COPY ../frontend/dist ./frontend-dist # 创建上传目录 RUN mkdir -p uploads # 暴露端口Flask默认5000根据平台要求调整 EXPOSE 5000 # 使用Gunicorn启动应用更适合生产环境 CMD [gunicorn, -w, 4, -b, 0.0.0.0:5000, app:app]这个Dockerfile做了几件事基于Python镜像安装依赖复制前后端代码设置工作目录最后用Gunicorn启动Flask应用。5.4 在星图平台部署准备代码仓库将整个项目包含前后端代码和Dockerfile推送到Git仓库如GitHub、Gitee。登录星图平台进入CSDN星图镜像广场或相关部署页面。创建新应用选择“从Dockerfile构建”或“从代码仓库部署”。配置信息关联你的代码仓库。指定构建路径例如./backend因为Dockerfile在那里。设置端口映射将容器内的5000端口映射到平台提供的外网端口。配置环境变量如SECRET_KEY生产环境务必设置一个强随机字符串。启动部署平台会根据你的Dockerfile自动构建镜像并运行容器。访问应用部署成功后平台会提供一个访问地址。通过这个地址你就可以访问你完整的人脸检测SaaS网站了。6. 回顾与展望走完这一趟我们从零搭建了一个具备用户系统、AI能力和积分业务逻辑的完整Web应用。这个过程涵盖了现代Web开发的核心环节前端界面交互、后端API与业务逻辑、数据库设计、第三方服务AI模型集成以及最终的生产环境部署。实际用下来这个项目骨架已经相当扎实了。你完全可以基于它进行扩展比如加入更复杂的积分商城、分享功能、检测结果分析报表或者替换成其他更强大的AI模型如目标检测、图像分割。部署到星图平台后你还能享受到弹性伸缩、负载均衡等云服务的便利。开发这类全栈项目最大的收获不是记住了多少API而是理解了数据如何在前端、后端、数据库和外部服务之间流动。下次当你再看到任何一个Web应用你大概都能在心里勾勒出它背后的技术轮廓了。如果你对其中某个环节特别感兴趣比如如何优化Flask的性能、如何用Vue 3的Composition API写出更优雅的代码或者如何更专业地部署和监控你的应用那都是值得深入探索的绝佳方向。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。

更多文章