从 0 到 1:Vue3+Django打造现代化宠物商城系统(含AI智能顾问)
🐾 从 0 到 1:Vue3+Django打造现代化宠物商城系统(含AI智能顾问)
项目亮点:前后端分离架构 + AI智能咨询 + 完整电商功能 + 现代化UI设计
技术栈:Vue3 + Element Plus + Django + MySQL + LongCat AI
GitHub地址:https://github.com/Smartloe/PetMarketplaceSystem
📖 前言
作为一名全栈开发者,我一直想做一个集成AI功能的电商项目来实践现代Web开发技术。经过几个月的开发,终于完成了这个吉祥宠物商城系统。
这不仅仅是一个普通的电商网站,更是一个融合了AI智能顾问的现代化宠物服务平台。用户可以在购买宠物用品的同时,获得专业的AI宠物护理建议。
🎯 项目概览
核心功能特色
- 🤖 AI宠物顾问:基于LongCat模型,24小时在线提供专业宠物咨询
- 🛍️ 完整电商功能:商品浏览、购物车、订单管理、支付集成
- 🎨 现代化UI:Element Plus + 自定义设计系统,支持响应式布局
- 🔐 安全认证:JWT无状态认证 + 权限控制
- 📊 数据可视化:销售统计、用户行为分析
技术架构
前端:Vue3 + Element Plus + Vuex + Vue Router
后端:Django + DRF + JWT + MySQL
AI服务:LongCat API集成
支付:支付宝/微信/银联
🏗️ 系统架构设计
整体架构图
项目目录结构
PetMarketplaceSystem/
├── frontstage/pet_shop/ # Vue3前端
│ ├── src/
│ │ ├── views/ # 页面组件
│ │ │ ├── Home.vue # 首页
│ │ │ ├── AIPetExpert.vue # AI顾问
│ │ │ ├── CommodityList.vue # 商品列表
│ │ │ └── ShoppingCart.vue # 购物车
│ │ ├── components/ # 公共组件
│ │ ├── store/ # Vuex状态管理
│ │ └── api/ # API接口
│ └── package.json
│
└── backstage/pet_shop/ # Django后端├── accounts/ # 用户管理├── commodity/ # 商品管理├── trade/ # 交易订单├── customer_operation/ # 用户操作├── index/ # AI咨询└── pyproject.toml
💻 核心功能实现
1. AI智能顾问实现
这是项目的最大亮点,我们集成了LongCat AI模型来提供专业的宠物咨询服务。
后端AI接口实现
# index/views.py
import json
import requests
from django.http import StreamingHttpResponse
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated@api_view(['POST'])
@permission_classes([IsAuthenticated])
def ai_pet_consult(request):"""AI宠物顾问咨询接口"""user_message = request.data.get('message', '')conversation_id = request.data.get('conversation_id', '')# 构建AI请求headers = {'Authorization': f'Bearer {settings.LONGCAT_API_KEY}','Content-Type': 'application/json'}payload = {"model": "longchat-7b-16k","messages": [{"role": "system","content": "你是一位专业的宠物顾问,专门为宠物主人提供护理建议..."},{"role": "user", "content": user_message}],"stream": True,"max_tokens": 1000}def generate_response():"""流式响应生成器"""try:response = requests.post(settings.LONGCAT_API_URL,headers=headers,json=payload,stream=True,timeout=30)for line in response.iter_lines():if line:line_str = line.decode('utf-8')if line_str.startswith('data: '):data_str = line_str[6:]if data_str.strip(':yield f"data: {json.dumps({'type': 'end'})}\n\n"breaktry:data = json.loads(data_str)content = data['choices'][0]['delta'].get('content', '')if content:yield f"data: {json.dumps({'type': 'message', 'content': content})}\n\n"except json.JSONDecodeError:continueexcept Exception as e:yield f"data: {json.dumps({'type': 'error', 'message': str(e)})}\n\n"return StreamingHttpResponse(generate_response(),content_type='text/event-stream')
前端AI对话组件
<!-- AIPetExpert.vue -->
<template><div class="ai-chat-container"><div class="chat-header"><h2>🤖 AI宠物顾问</h2><p>专业的宠物护理建议,24小时在线服务</p></div><div class="chat-messages" ref="messagesContainer"><div v-for="(message, index) in messages" :key="index":class="['message', message.type]"><div class="message-content"><div class="message-text" v-html="formatMessage(message.content)"></div><div class="message-time">{{ message.timestamp }}</div></div></div><!-- AI正在输入指示器 --><div v-if="isAITyping" class="message ai typing"><div class="typing-indicator"><span></span><span></span><span></span></div></div></div><div class="chat-input"><el-inputv-model="userInput"placeholder="请输入您的问题,比如:我的猫咪不吃饭怎么办?"@keyup.enter="sendMessage":disabled="isLoading"><template #append><el-button @click="sendMessage" :loading="isLoading"type="primary">发送</el-button></template></el-input></div></div>
</template><script>
import { ref, reactive, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import api from '@/api'export default {name: 'AIPetExpert',setup() {const userInput = ref('')const messages = reactive([])const isLoading = ref(false)const isAITyping = ref(false)const messagesContainer = ref(null)// 发送消息const sendMessage = async () => {if (!userInput.value.trim() || isLoading.value) returnconst userMessage = userInput.value.trim()// 添加用户消息messages.push({type: 'user',content: userMessage,timestamp: new Date().toLocaleTimeString()})userInput.value = ''isLoading.value = trueisAITyping.value = truetry {// 调用AI接口const response = await fetch('/api/ai-pet-consult/', {method: 'POST',headers: {'Content-Type': 'application/json','Authorization': `Bearer ${localStorage.getItem('access_token')}`},body: JSON.stringify({message: userMessage,conversation_id: generateConversationId()})})const reader = response.body.getReader()let aiMessage = {type: 'ai',content: '',timestamp: new Date().toLocaleTimeString()}messages.push(aiMessage)isAITyping.value = false// 处理流式响应while (true) {const { done, value } = await reader.read()if (done) breakconst chunk = new TextDecoder().decode(value)const lines = chunk.split('\n')for (const line of lines) {if (line.startsWith('data: ')) {try {const data = JSON.parse(line.slice(6))if (data.type === 'message') {aiMessage.content += data.contentawait nextTick()scrollToBottom()} else if (data.type === 'end') {isLoading.value = falsereturn}} catch (e) {console.error('解析AI响应失败:', e)}}}}} catch (error) {console.error('AI咨询失败:', error)ElMessage.error('AI服务暂时不可用,请稍后重试')messages.pop() // 移除失败的消息} finally {isLoading.value = falseisAITyping.value = false}}// 滚动到底部const scrollToBottom = () => {nextTick(() => {if (messagesContainer.value) {messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight}})}// 格式化消息内容const formatMessage = (content) => {return content.replace(/\n/g, '<br>').replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>').replace(/\*(.*?)\*/g, '<em>$1</em>')}// 生成对话IDconst generateConversationId = () => {return 'conv_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9)}return {userInput,messages,isLoading,isAITyping,messagesContainer,sendMessage,formatMessage}}
}
</script><style scoped>
.ai-chat-container {max-width: 800px;margin: 0 auto;height: 600px;display: flex;flex-direction: column;border: 1px solid #e4e7ed;border-radius: 8px;overflow: hidden;
}.chat-header {background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);color: white;padding: 20px;text-align: center;
}.chat-messages {flex: 1;overflow-y: auto;padding: 20px;background: #f8f9fa;
}.message {margin-bottom: 15px;display: flex;
}.message.user {justify-content: flex-end;
}.message.ai {justify-content: flex-start;
}.message-content {max-width: 70%;padding: 12px 16px;border-radius: 18px;position: relative;
}.message.user .message-content {background: #409eff;color: white;
}.message.ai .message-content {background: white;border: 1px solid #e4e7ed;
}.typing-indicator {display: flex;gap: 4px;padding: 12px 16px;
}.typing-indicator span {width: 8px;height: 8px;border-radius: 50%;background: #409eff;animation: typing 1.4s infinite ease-in-out;
}.typing-indicator span:nth-child(2) {animation-delay: 0.2s;
}.typing-indicator span:nth-child(3) {animation-delay: 0.4s;
}@keyframes typing {0%, 80%, 100% {transform: scale(0.8);opacity: 0.5;}40% {transform: scale(1);opacity: 1;}
}.chat-input {padding: 20px;background: white;border-top: 1px solid #e4e7ed;
}
</style>
2. 商品管理系统
后端商品模型设计
# commodity/models.py
from django.db import modelsclass CommodityCategories(models.Model):"""商品分类模型"""name = models.CharField(max_length=30, verbose_name="分类名")code = models.CharField(max_length=30, verbose_name="分类code")desc = models.TextField(verbose_name="分类描述")category_type = models.IntegerField(choices=((1, "一级类目"), (2, "二级类目"), (3, "三级类目")))parent_category = models.ForeignKey("self", null=True, blank=True, on_delete=models.CASCADE)is_tab = models.BooleanField(default=False, verbose_name="是否导航")add_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间")class Meta:verbose_name = "商品类别"verbose_name_plural = verbose_nameclass CommodityInfos(models.Model):"""商品信息模型"""category = models.ForeignKey(CommodityCategories, on_delete=models.CASCADE, verbose_name="商品类目")goods_sn = models.CharField(max_length=50, default="", verbose_name="商品唯一货号")name = models.CharField(max_length=100, verbose_name="商品名")click_num = models.IntegerField(default=0, verbose_name="点击数")sold_num = models.IntegerField(default=0, verbose_name="商品销售量")fav_num = models.IntegerField(default=0, verbose_name="收藏数")goods_num = models.IntegerField(default=0, verbose_name="库存数")market_price = models.FloatField(default=0, verbose_name="市场价格")shop_price = models.FloatField(default=0, verbose_name="本店价格")goods_brief = models.TextField(max_length=500, verbose_name="商品简短描述")goods_desc = models.TextField(verbose_name="商品详情")ship_free = models.BooleanField(default=True, verbose_name="是否承担运费")goods_front_image = models.ImageField(upload_to="goods/images/", null=True, blank=True, verbose_name="封面图")is_new = models.BooleanField(default=False, verbose_name="是否新品")is_hot = models.BooleanField(default=False, verbose_name="是否热销")add_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间")class Meta:verbose_name = '商品信息'verbose_name_plural = verbose_name
前端商品列表组件
<!-- CommodityList.vue -->
<template><div class="commodity-list"><!-- 筛选栏 --><div class="filter-bar"><el-row :gutter="20"><el-col :span="6"><el-select v-model="filters.category" placeholder="选择分类" @change="loadProducts"><el-option label="全部分类" value=""></el-option><el-option v-for="category in categories" :key="category.id":label="category.name" :value="category.id"></el-option></el-select></el-col><el-col :span="6"><el-select v-model="filters.ordering" placeholder="排序方式" @change="loadProducts"><el-option label="默认排序" value=""></el-option><el-option label="价格从低到高" value="shop_price"></el-option><el-option label="价格从高到低" value="-shop_price"></el-option><el-option label="销量最高" value="-sold_num"></el-option><el-option label="最新上架" value="-add_time"></el-option></el-select></el-col><el-col :span="12"><el-inputv-model="filters.search"placeholder="搜索商品名称"@keyup.enter="loadProducts"><template #append><el-button @click="loadProducts" icon="Search">搜索</el-button></template></el-input></el-col></el-row></div><!-- 商品网格 --><div class="products-grid" v-loading="loading"><div v-for="product in products" :key="product.id"class="product-card"@click="goToDetail(product.id)"><div class="product-image"><img :src="getImageUrl(product.goods_front_image)" :alt="product.name"><div class="product-badges"><span v-if="product.is_new" class="badge new">新品</span><span v-if="product.is_hot" class="badge hot">热销</span></div></div><div class="product-info"><h3 class="product-name">{{ product.name }}</h3><p class="product-brief">{{ product.goods_brief }}</p><div class="product-price"><span class="current-price">¥{{ product.shop_price }}</span><span v-if="product.market_price > product.shop_price" class="original-price">¥{{ product.market_price }}</span></div><div class="product-stats"><span>销量: {{ product.sold_num }}</span><span>库存: {{ product.goods_num }}</span></div><div class="product-actions"><el-button type="primary" size="small"@click.stop="addToCart(product)":loading="addingToCart[product.id]">加入购物车</el-button><el-button size="small"@click.stop="toggleFavorite(product)":class="{ 'is-favorited': product.is_favorited }"><el-icon><Star /></el-icon></el-button></div></div></div></div><!-- 分页 --><div class="pagination-wrapper"><el-paginationv-model:current-page="currentPage":page-size="pageSize":total="total"layout="prev, pager, next, jumper, total"@current-change="handlePageChange"/></div></div>
</template><script>
import { ref, reactive, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { Star } from '@element-plus/icons-vue'
import api from '@/api'export default {name: 'CommodityList',components: { Star },setup() {const router = useRouter()const products = ref([])const categories = ref([])const loading = ref(false)const currentPage = ref(1)const pageSize = ref(12)const total = ref(0)const addingToCart = reactive({})const filters = reactive({category: '',ordering: '',search: ''})// 加载商品列表const loadProducts = async () => {loading.value = truetry {const params = {page: currentPage.value,page_size: pageSize.value,...filters}const { data } = await api.get('/commodity/goods/', { params })products.value = data.resultstotal.value = data.count} catch (error) {ElMessage.error('加载商品失败')} finally {loading.value = false}}// 加载分类const loadCategories = async () => {try {const { data } = await api.get('/commodity/categories/')categories.value = data.results} catch (error) {console.error('加载分类失败:', error)}}// 添加到购物车const addToCart = async (product) => {addingToCart[product.id] = truetry {await api.post('/trade/shopping-carts/', {goods: product.id,nums: 1})ElMessage.success('已添加到购物车')} catch (error) {ElMessage.error('添加失败,请先登录')} finally {addingToCart[product.id] = false}}// 切换收藏const toggleFavorite = async (product) => {try {if (product.is_favorited) {await api.delete(`/customer-operation/user-favs/${product.fav_id}/`)product.is_favorited = falseElMessage.success('已取消收藏')} else {const { data } = await api.post('/customer-operation/user-favs/', {goods: product.id})product.is_favorited = trueproduct.fav_id = data.idElMessage.success('已添加收藏')}} catch (error) {ElMessage.error('操作失败,请先登录')}}// 跳转到详情页const goToDetail = (productId) => {router.push(`/commodity/detail/${productId}`)}// 获取图片URLconst getImageUrl = (imagePath) => {if (!imagePath) return '/img/default-product.png'return imagePath.startsWith('http') ? imagePath : `${api.defaults.baseURL}${imagePath}`}// 分页处理const handlePageChange = (page) => {currentPage.value = pageloadProducts()}onMounted(() => {loadCategories()loadProducts()})return {products,categories,loading,currentPage,pageSize,total,filters,addingToCart,loadProducts,addToCart,toggleFavorite,goToDetail,getImageUrl,handlePageChange}}
}
</script><style scoped>
.commodity-list {padding: 20px;max-width: 1200px;margin: 0 auto;
}.filter-bar {margin-bottom: 30px;padding: 20px;background: white;border-radius: 8px;box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}.products-grid {display: grid;grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));gap: 20px;margin-bottom: 30px;
}.product-card {background: white;border-radius: 12px;overflow: hidden;box-shadow: 0 4px 12px rgba(0,0,0,0.1);transition: transform 0.3s ease, box-shadow 0.3s ease;cursor: pointer;
}.product-card:hover {transform: translateY(-5px);box-shadow: 0 8px 25px rgba(0,0,0,0.15);
}.product-image {position: relative;height: 200px;overflow: hidden;
}.product-image img {width: 100%;height: 100%;object-fit: cover;
}.product-badges {position: absolute;top: 10px;left: 10px;
}.badge {display: inline-block;padding: 4px 8px;border-radius: 12px;font-size: 12px;color: white;margin-right: 5px;
}.badge.new {background: #67c23a;
}.badge.hot {background: #f56c6c;
}.product-info {padding: 15px;
}.product-name {font-size: 16px;font-weight: 600;margin: 0 0 8px 0;color: #303133;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;
}.product-brief {font-size: 14px;color: #909399;margin: 0 0 12px 0;height: 40px;overflow: hidden;display: -webkit-box;-webkit-line-clamp: 2;-webkit-box-orient: vertical;
}.product-price {margin-bottom: 10px;
}.current-price {font-size: 18px;font-weight: 600;color: #f56c6c;
}.original-price {font-size: 14px;color: #909399;text-decoration: line-through;margin-left: 8px;
}.product-stats {font-size: 12px;color: #909399;margin-bottom: 15px;
}.product-stats span {margin-right: 15px;
}.product-actions {display: flex;justify-content: space-between;align-items: center;
}.is-favorited {color: #f56c6c;
}.pagination-wrapper {display: flex;justify-content: center;margin-top: 30px;
}
</style>
3. 购物车与订单系统
购物车状态管理
// store/modules/cart.js
import api from '@/api'const state = {items: [],total: 0,loading: false
}const mutations = {SET_CART_ITEMS(state, items) {state.items = itemsstate.total = items.reduce((sum, item) => sum + (item.goods.shop_price * item.nums), 0)},UPDATE_ITEM_QUANTITY(state, { itemId, quantity }) {const item = state.items.find(item => item.id === itemId)if (item) {item.nums = quantitystate.total = state.items.reduce((sum, item) => sum + (item.goods.shop_price * item.nums), 0)}},REMOVE_ITEM(state, itemId) {state.items = state.items.filter(item => item.id !== itemId)state.total = state.items.reduce((sum, item) => sum + (item.goods.shop_price * item.nums), 0)},SET_LOADING(state, loading) {state.loading = loading}
}const actions = {// 获取购物车async fetchCart({ commit }) {commit('SET_LOADING', true)try {const { data } = await api.get('/trade/shopping-carts/')commit('SET_CART_ITEMS', data.results)} catch (error) {console.error('获取购物车失败:', error)} finally {commit('SET_LOADING', false)}},// 添加到购物车async addToCart({ dispatch }, { goodsId, quantity = 1 }) {try {await api.post('/trade/shopping-carts/', {goods: goodsId,nums: quantity})await dispatch('fetchCart')return true} catch (error) {throw error}},// 更新数量async updateQuantity({ commit }, { itemId, quantity }) {try {await api.patch(`/trade/shopping-carts/${itemId}/`, {nums: quantity})commit('UPDATE_ITEM_QUANTITY', { itemId, quantity })} catch (error) {throw error}},// 删除商品async removeItem({ commit }, itemId) {try {await api.delete(`/trade/shopping-carts/${itemId}/`)commit('REMOVE_ITEM', itemId)} catch (error) {throw error}},// 清空购物车async clearCart({ commit }) {try {await api.delete('/trade/shopping-carts/clear/')commit('SET_CART_ITEMS', [])} catch (error) {throw error}}
}const getters = {cartItemCount: state => state.items.reduce((sum, item) => sum + item.nums, 0),cartTotal: state => state.total,cartItems: state => state.items
}export default {namespaced: true,state,mutations,actions,getters
}
🎨 UI设计系统
设计理念
我们采用了温暖的宠物主题色彩,营造亲切友好的购物氛围:
/* design-system.css */
:root {/* 主色调 - 温暖橙色系 */--primary-color: #ff6b35;--primary-light: #ff8c69;--primary-dark: #e55a2b;/* 辅助色 */--secondary-color: #4ecdc4;--accent-color: #45b7d1;--success-color: #96ceb4;--warning-color: #feca57;--error-color: #ff6b6b;/* 中性色 */--text-primary: #2c3e50;--text-secondary: #7f8c8d;--text-light: #bdc3c7;--background: #f8f9fa;--surface: #ffffff;--border: #e9ecef;/* 阴影 */--shadow-sm: 0 2px 4px rgba(0,0,0,0.1);--shadow-md: 0 4px 12px rgba(0,0,0,0.15);--shadow-lg: 0 8px 25px rgba(0,0,0,0.2);/* 圆角 */--radius-sm: 4px;--radius-md: 8px;--radius-lg: 12px;--radius-xl: 16px;/* 间距 */--spacing-xs: 4px;--spacing-sm: 8px;--spacing-md: 16px;--spacing-lg: 24px;--spacing-xl: 32px;
}/* 通用按钮样式 */
.pet-btn {padding: var(--spacing-sm) var(--spacing-md);border: none;border-radius: var(--radius-md);font-weight: 500;cursor: pointer;transition: all 0.3s ease;display: inline-flex;align-items: center;gap: var(--spacing-xs);
}.pet-btn-primary {background: var(--primary-color);color: white;
}.pet-btn-primary:hover {background: var(--primary-dark);transform: translateY(-2px);box-shadow: var(--shadow-md);
}/* 卡片样式 */
.pet-card {background: var(--surface);border-radius: var(--radius-lg);box-shadow: var(--shadow-sm);overflow: hidden;transition: all 0.3s ease;
}.pet-card:hover {transform: translateY(-4px);box-shadow: var(--shadow-lg);
}/* 玻璃拟态效果 */
.glass-effect {background: rgba(255, 255, 255, 0.25);backdrop-filter: blur(10px);border: 1px solid rgba(255, 255, 255, 0.18);
}/* 渐变背景 */
.gradient-bg {background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
}/* 响应式网格 */
.responsive-grid {display: grid;grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));gap: var(--spacing-lg);
}/* 动画效果 */
@keyframes fadeInUp {from {opacity: 0;transform: translateY(30px);}to {opacity: 1;transform: translateY(0);}
}.fade-in-up {animation: fadeInUp 0.6s ease-out;
}/* 加载动画 */
@keyframes pulse {0%, 100% {opacity: 1;}50% {opacity: 0.5;}
}.loading-pulse {animation: pulse 2s infinite;
}
🔐 安全与认证
JWT认证实现
# authentication.py
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework_simplejwt.exceptions import InvalidToken, TokenError
from django.contrib.auth.models import AnonymousUserclass CustomJWTAuthentication(JWTAuthentication):"""自定义JWT认证"""def authenticate(self, request):header = self.get_header(request)if header is None:return Noneraw_token = self.get_raw_token(header)if raw_token is None:return Nonetry:validated_token = self.get_validated_token(raw_token)user = self.get_user(validated_token)return (user, validated_token)except TokenError:return Nonedef get_user(self, validated_token):"""获取用户信息"""try:user_id = validated_token['user_id']user = User.objects.get(id=user_id)return userexcept User.DoesNotExist:return AnonymousUser()
权限控制
# permissions.py
from rest_framework import permissionsclass IsOwnerOrReadOnly(permissions.BasePermission):"""只有所有者可以编辑"""def has_object_permission(self, request, view, obj):# 读权限对所有人开放if request.method in permissions.SAFE_METHODS:return True# 写权限只给所有者return obj.user == request.userclass IsAuthenticatedOrReadOnlyLimited(permissions.BasePermission):"""未登录用户只能查看有限内容"""def has_permission(self, request, view):if request.method in permissions.SAFE_METHODS:return Truereturn request.user and request.user.is_authenticateddef has_object_permission(self, request, view, obj):if request.method in permissions.SAFE_METHODS:# 未登录用户只能查看部分商品if not request.user.is_authenticated:return hasattr(obj, 'is_preview_allowed') and obj.is_preview_allowedreturn Truereturn request.user and request.user.is_authenticated
📊 性能优化
前端优化策略
- 路由懒加载
const routes = [{path: '/',name: 'Home',component: () => import('@/views/Home.vue')},{path: '/ai-expert',name: 'AIPetExpert',component: () => import('@/views/AIPetExpert.vue')}
]
- 图片懒加载
<template><img v-lazy="product.image" :alt="product.name"class="product-image"/>
</template>
- 组件缓存
<template><keep-alive include="CommodityList,UserCenter"><router-view /></keep-alive>
</template>
后端优化策略
- 数据库查询优化
# 使用select_related减少查询次数
def get_queryset(self):return CommodityInfos.objects.select_related('category').prefetch_related('images').filter(is_active=True)
- 分页优化
# settings.py
REST_FRAMEWORK = {'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination','PAGE_SIZE': 12
}
- 缓存策略
from django.core.cache import cachedef get_hot_products():cache_key = 'hot_products'products = cache.get(cache_key)if products is None:products = CommodityInfos.objects.filter(is_hot=True).order_by('-sold_num')[:6]cache.set(cache_key, products, 300) # 缓存5分钟return products
🚀 部署与运维
Docker部署
# Dockerfile
FROM python:3.10-slimWORKDIR /app# 安装系统依赖
RUN apt-get update && apt-get install -y \gcc \default-libmysqlclient-dev \&& rm -rf /var/lib/apt/lists/*# 安装Python依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt# 复制项目文件
COPY . .# 收集静态文件
RUN python manage.py collectstatic --noinputEXPOSE 8000CMD ["gunicorn", "pet_shop.wsgi:application", "--bind", "0.0.0.0:8000"]
# docker-compose.yml
version: '3.8'services:db:image: mysql:8.0environment:MYSQL_DATABASE: pet_shopMYSQL_ROOT_PASSWORD: passwordvolumes:- mysql_data:/var/lib/mysqlports:- "3306:3306"backend:build: ./backstage/pet_shopdepends_on:- dbenvironment:- MYSQL_HOST=db- MYSQL_PASSWORD=passwordports:- "8000:8000"frontend:build: ./frontstage/pet_shopports:- "80:80"volumes:mysql_data:
Nginx配置
server {listen 80;server_name your-domain.com;# 前端静态文件location / {root /var/www/pet-shop/dist;try_files $uri $uri/ /index.html;}# 后端APIlocation /api/ {proxy_pass http://127.0.0.1:8000;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;}# 媒体文件location /media/ {alias /var/www/pet-shop/media/;expires 30d;}# 静态文件location /static/ {alias /var/www/pet-shop/static/;expires 30d;}
}
📈 项目亮点与创新
1. AI集成创新
- 流式响应:实现了类似ChatGPT的打字机效果
- 上下文管理:智能截断历史消息,避免token超限
- 错误容错:网络异常时的优雅降级处理
2. 用户体验优化
- 响应式设计:完美适配桌面和移动端
- 加载状态:丰富的loading动画和骨架屏
- 交互反馈:hover效果、点击反馈、状态提示
3. 技术架构优势
- 模块化设计:前后端完全分离,便于团队协作
- 可扩展性:支持水平扩展和微服务改造
- 安全性:JWT认证 + 权限控制 + 输入验证
4. 开发效率提升
- 代码复用:组件化开发,提高开发效率
- API文档:Swagger自动生成,便于前后端协作
- 环境配置:Docker一键部署,简化运维工作
💡 技术总结
通过这个项目,我深度实践了现代Web开发的最佳实践:
- 前后端分离:Vue3 + Django REST API的组合提供了极佳的开发体验
- AI集成:学会了如何在Web应用中集成第三方AI服务
- 状态管理:Vuex的使用让复杂状态管理变得简单
- UI设计:Element Plus + 自定义设计系统打造了现代化界面
- 性能优化:从前端路由懒加载到后端数据库查询优化
- 部署运维:Docker容器化部署简化了运维工作
这个项目不仅是技术的实践,更是对电商业务流程的深度理解。希望能为同样在学习全栈开发的朋友们提供一些参考和启发。
🔗 相关链接
- GitHub仓库:https://github.com/Smartloe/PetMarketplaceSystem
- 在线演示:(待部署)
- 技术文档:项目README.md
- API文档:http://localhost:8000/swagger/
如果这个项目对你有帮助,欢迎给个Star⭐️支持一下!
有任何问题或建议,欢迎在评论区交流讨论!
