ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Django全栈实战:从零构建相亲系统,详解架构设计与部署优化

Django全栈实战:从零构建相亲系统,详解架构设计与部署优化 1. 这不是一个普通的交友网站而是一次Django全栈练手先说说为什么做这个项目。我是做后端开发的平时工作主要围绕业务系统、接口设计这类偏“务实”的东西说实话有点腻了。一直想找个能覆盖全栈、又有真实业务复杂度、做完还能拿去演示的项目练手。看了一圈电商系统太俗博客系统太简单最后选了“相亲系统”——你别说这东西看起来只是“注册、筛选、聊天”实际上涉及的模块比想象中多得多会员体系、个人资料、照片管理、匹配规则、即时聊天、支付会员服务、后台审核、行为风控……几乎把一个互联网产品的常见模块都包圆了。至于“帅小伙网络相亲系统”这个名字纯属朋友起哄。当时项目做完要起个项目名大家说既然是演示项目不如起得生动点“帅小伙”三个字反而成了记忆点每次给别人演示都被调侃一嘴“这系统是不是只允许帅小伙注册”。后来我也懒得改了反正Gitee上的项目名挺重要一个好记的名字确实更容易被点开。实际做的时候我加了性别字段男女都能用核心逻辑是一样的。这个系统适合谁如果你是刚学完Django基础、想找个有深度的实战项目的学生或者工作中只用Django做各种内部管理系统、想试试面向公开用户的产品型项目怎么做这个项目的技术选型和实现思路都很有参考价值。文章里我会把所有踩过的坑、取舍逻辑都讲一遍不是那种照着教程敲一遍就完事的水文。技术栈先摆这里Python 3.10 Django 4.1 MySQL 5.7 Redis缓存和在线状态 Vue 3前端但核心逻辑放在后端前端只是展示层。开发环境是Win11生产环境用CentOS 7 Nginx Gunicorn。整套东西从零到能对外访问我一个业余时间断断续续做了大概三周。2. 为什么选了Django而不是Spring Boot或Flask以及整体架构怎么搭2.1 技术选型三个框架里Django最合适这个项目立项之前我认真比较过Python系的三条路Flask、FastAPI、Django顺带也看了下Java那边用Spring Boot做同类系统的路子。先排除Flask。相亲系统的核心痛点是数据模型之间关系复杂用户有Profile、有照片、有身高体重学历收入这些标签、有关注列表、有匹配记录、有聊天记录还要有浏览日志。Flask的ORM虽然可以用SQLAlchemy凑合但双方都得自己拼折腾下来一半时间都在写基础设施。FastAPI适合纯API服务异步性能是亮点但相亲系统的后台管理、模板渲染、用户认证这些它都不直接管得自己搭等于从零造轮子。Django最舒服的地方是“全家桶”自带的这些能力恰好都是这个项目需要的自带Admin后台给运营和审核用不用自己写管理端、自带User模型和认证体系扩展一下就能当会员系统用、自带ORM迁移管理、外键关系直接省掉一大半SQL、自带的CSRF防护和XSS过滤对面向公众的产品太重要了。有个老哥跟我说Spring Boot做这种系统也很成熟Java生态确实强但用Python做原型验证、改业务逻辑的速度快太多而且我这个项目本身也有个人学习价值没必要为了所谓的“企业级”去上Java那套重装备。至于SQLite开发时用没问题上线前我换成了MySQL原因后面部署章节会说。2.2 项目结构Django的应用拆分思路很多Django新手喜欢把啥都塞进一个app里models、views、forms全堆一起刚开始很爽等到要加功能时就是一场灾难。我这次按业务域拆成了四个应用sjmatch/ # 项目配置目录settings/urls/wsgi apps/ ├── accounts/ # 会员注册、登录、资料管理、相册 ├── matching/ # 匹配推荐、搜索、关注、喜欢 ├── chat/ # 私信聊天、在线状态、消息通知 └── admin_ops/ # 后台运营辅助、审核任务、数据报表accounts这个app是整个系统的地基因为它管的是User模型和Profile模型。Django自带的User模型字段太少只有用户名、邮箱、密码这些相亲系统需要的生日、性别、城市、职业这些都没有。我的做法是扩展一个UserProfile模型跟User做OneToOne关联然后在User的post_save信号里自动创建对应Profile。matching管匹配推荐逻辑是整个系统的核心卖点。chat管私信和在线状态。admin_ops管审核因为实名制相关的项目必须有人工审核环节不能全靠机器。settings.py里我把AUTH_USER_MODEL配置成了自定义的User继承AbstractUser这个时机很重要——如果你已经执行过第一次migrate再改会比较麻烦。所以建议做新项目时第一时间配这个后面所有多对多、外键关系都以自定义用户模型为准。3. 核心数据模型设计相亲系统的“骨架”怎么搭3.1 用户资料模型每个字段都带着业务逻辑相亲系统和普通社交网站最大的不同是用户资料的信息密度高而且这些字段直接决定匹配和搜索能不能做。我设计UserProfile时是这么想的——既要够用又不能堆一堆用不上的字段。class UserProfile(models.Model): GENDER_CHOICES ((M, Male), (F, Female)) MARITAL_CHOICES ((S, 未婚), (D, 离异), (W, 丧偶), (O, 其他)) user models.OneToOneField( settings.AUTH_USER_MODEL, on_deletemodels.CASCADE, related_nameprofile ) gender models.CharField(max_length2, choicesGENDER_CHOICES) birth_date models.DateField(nullTrue, blankTrue) city models.CharField(max_length64, db_indexTrue) height models.PositiveSmallIntegerField(nullTrue, blankTrue) weight models.PositiveSmallIntegerField(nullTrue, blankTrue) education models.CharField(max_length32, blankTrue) # 学历 occupation models.CharField(max_length64, blankTrue) # 职业 income_level models.CharField(max_length16, blankTrue) # 收入档位 marital_status models.CharField(max_length8, choicesMARITAL_CHOICES, defaultS) self_intro models.TextField(max_length500, blankTrue) # 自我介绍 ...身高体重为什么用PositiveSmallIntegerField而不是CharField因为后面要支持筛选比如“身高175到185之间”用整数才能走范围查询。存成字符串“175cm”这种筛选时就得写一堆乱七八糟的字符串解析逻辑纯粹给自己找事。还有两个重要字段is_verified和profile_score。前者是实名认证标记后台人工审核通过后置True只有认证用户在匹配列表里才靠前后者是资料完整度评分。这两个字段在匹配推荐、排序时都会用到。3.2 匹配与互动模型多对多关系别偷懒“喜欢”这个动作我用一个专门的LikeRecord模型来实现而不是在用户表里搞个ManyToManyField完事。为什么因为业务上我需要记录“谁在什么时候喜欢了谁”还要防止重复喜欢。ManyToManyField虽然方便但中间表是自动生成的没法挂额外的业务字段。class LikeRecord(models.Model): from_user models.ForeignKey(settings.AUTH_USER_MODEL, on_deletemodels.CASCADE, related_namelikes_sent) to_user models.ForeignKey(settings.AUTH_USER_MODEL, on_deletemodels.CASCADE, related_namelikes_received) is_mutual models.BooleanField(defaultFalse) # 是否互相喜欢 created_at models.DateTimeField(auto_now_addTrue) class Meta: unique_together (from_user, to_user) # 防止重复喜欢 indexes [models.Index(fields[to_user, is_mutual])] # 查询“谁喜欢了我”时提速is_mutual这个字段是整张表的核心当两个人的喜欢记录互为反向时它会被置成True同时系统自动通知双方“你们互相喜欢了可以开始聊天”这就是相亲场景里的“配对成功”。这个操作在写LikeRecord的时候要做一次事务内检查保证一致性。聊天模型我偷了个懒没有做成复杂的会话列表而是直接用“私信会话Room”的方式。Room表记录对话双方Message表记录每一条消息。为什么不用WebSocket做实时聊天因为初期为了控制复杂度我用了轮询每10秒拉一次新消息后面优化时再换Django Channels这个取舍后面单说。3.3 为什么说信号Signal是Django的隐形功臣这个项目里信号用得比预期多但都控制得很克制。最典型的例子就是“创建用户就自动创建UserProfile”receiver(post_save, senderUser) def create_profile(sender, instance, created, **kwargs): if created: UserProfile.objects.create(userinstance)很多人说信号会让代码“隐式执行”不好调试。但用在这个场景里是合理的——你不能保证每个注册入口前台注册、后台新增运营账号都记得去手动创建Profile信号就是一个兜底机制。生产环境踩过坑后我才发现真正的炸点不在信号里面而在信号里面写了耗时操作。有一次我在post_save信号里顺手发了邮件验证结果注册接口慢了两秒多。后来把所有邮件、短信这类外部IO改成走Celery异步任务队列接口响应时间才回到正常水平。所以信号可以用但千万不要在信号里做耗时的网络请求。4. 核心功能模块的实现注册登录到匹配推荐每个环节的取舍4.1 注册登录session还是JWT这是个绕不开的问题。Django默认用session做登录状态模板渲染配起来确实简单。但这个项目的前后端是分离的前端Vue调RESTful API所以认证机制选哪个直接决定前端怎么存登录态。我最终选的是DRFDjango REST Framework JWT认证。理由有两个一是移动端和Web端共用一套APIJWT无状态扩展起来方便二是JWT的payload里可以塞用户ID和昵称前端不用每次请求都去查一遍用户资料。实现上用djangorestframework-simplejwt这个库配置也不复杂# settings.py from datetime import timedelta SIMPLE_JWT { ACCESS_TOKEN_LIFETIME: timedelta(minutes30), REFRESH_TOKEN_LIFETIME: timedelta(days7), ROTATE_REFRESH_TOKENS: True, BLACKLIST_AFTER_ROTATION: True, UPDATE_LAST_LOGIN: True, }前端登录后拿到access token存localStorage每次请求带在Authorization头里。这里有个安全细节是我后来才想明白的access token生命周期必须短30分钟够了refresh token才能给7天不然token泄露等于账号被盗。密码加密这块不用操心Django自带PBKDF2-HMAC-SHA256。注册时还要接一个验证码逻辑我用的腾讯云短信开发阶段用django-debug-toolbar直接看邮件内容配置EMAIL_BACKEND为console backend连短信都不用真发调试效率高很多。4.2 匹配推荐从最简单的到真正能用的匹配算法是我在这个项目里花时间最多的部分。“帅小伙网络相亲系统”听起来是个玩票项目但匹配推荐的核心体验直接决定用户留不留得住。第一版我做得极其简单按城市年龄性别筛选按最近登录时间排序SQL里一条order_by就搞定上线一礼拜用户反馈“推荐的人不精准”、“翻几页就没新人了”。第二版我重新设计了分数评分体系。每个候选用户计算一个“匹配分”按分数降序排列def calculate_match_score(user_a, user_b): score 0 # 基础条件分目标城市 年龄区间 if user_a.profile.city user_b.profile.city: score 30 age_gap abs(user_a.profile.age() - user_b.profile.age()) if age_gap 3: score 20 elif age_gap 6: score 10 # 互补条件身高差女矮男高组合加分学历匹配度 if user_a.profile.gender M and user_a.profile.height user_b.profile.height: score 15 if user_a.profile.education user_b.profile.education: score 10 # 资料完整度加成 score min(user_a.profile.profile_score, 100) // 10 score min(user_b.profile.profile_score, 100) // 10 # 热度微调最近活跃加分 if user_b.is_recently_active(): score 5 return score这个打分逻辑明显不是最优的但它有个好处每一分都能解释清楚“为什么给你推荐这个人”用户端的推荐理由就写“你们同城年龄差2岁学历相当”。后来我研究了一下真实相亲App的推荐机制主流做法是协同过滤或者向量召回但那种方案需要大量行为数据和离线计算。我这个项目数据量撑不起协同过滤规则打分反而是最可靠的。实现上有个细节如果对每个候选人实时计算分数100个候选人就要算100次再叠加数据库查询页面会很慢。我的优化思路是预计算——每天凌晨用Celery定时任务把所有用户的两两匹配分算好存进一张MatchScore表推荐时直接查表。当天注册的新用户算不出来就先按基础排序第二次刷新再走预计算表。这个方案在几万用户量级下完全够用真正的大厂方案是向量相似度召回粗排精排系统复杂度完全不是一个量级。4.3 搜索筛选直接上Q对象组合查询搜索页的条件有城市、性别、年龄范围、身高范围、学历、收入、最近活跃时间。这些条件都是可选的我用Django ORM的Q对象动态拼接def search_users(request): params [] if request.query_params.get(city): params.append(Q(profile__cityrequest.query_params[city])) if request.query_params.get(gender): params.append(Q(profile__genderrequest.query_params[gender])) age_min request.query_params.get(age_min) age_max request.query_params.get(age_max) if age_min: params.append(Q(profile__birth_date__ltecalculate_birthday(int(age_min)))) if age_max: params.append(Q(profile__birth_date__gtecalculate_birthday(int(age_max)))) ... combined_q params.pop(0) if params else Q() for p in params: combined_q p results get_user_model().objects.filter( combined_q, is_activeTrue, profile__is_verifiedTrue ).select_related(profile).order_by(-last_login)[:100]search_users这个函数唯一容易出错的地方是年龄转生日的逻辑。用户筛“25到30岁”你不能直接拿出生年份去比因为边界年份还涉及月份最稳妥的做法是以当前年份减目标年龄得到出生年份区间再用__lte和__gte两个方向做范围过滤——注意这里方向容易搞反我踩过一次小于最小年龄的人出生日期更晚所以birth_date__lte对应的是年龄下限。搜索性能方面profile表的city和birth_date都建了索引这个量级下走索引很快。如果城市数据量再大可以换Elasticsearch但那是后话。4.4 聊天短期轮询的长远代价聊天的第一版我用了“短轮询”——前端每10秒请求一次新消息接口。优点是Django原生的视图就能搞定不用引入WebSocket协议栈缺点是延迟10秒体验一般而且大量轮询对服务器压力不小。我估了一下按1000个在线用户、每人10秒拉一次来算平均每秒100个请求。每个请求查一次数据库MySQL扛得住但通信量和服务器负载都会指数增长。如果用户量到1万这个方案肯定先崩。这就是为什么我先用轮询再做Channels的原因——把功能跑通的关键不是技术选型多炫而是控制每一步的风险。轮询版本上线后实际上也发现了一个体验问题当用户A给用户B发消息B必须等下一轮轮询到来才能知道有新消息碰上轮询正好在请求间隙延迟就是10秒。后来我升级了WS协议用Django Channels的WebSocket做实时推送体验一下到了毫秒级。但Channels的部署比普通Django复杂——需要ASGI服务器Daphne或Uvicorn、Redis做channel layer和Nginx的WebSocket代理配置也有讲究。如果只想跑通流程轮询完全可以想拿去演示还是建议上Channels。5. 安全与审核机制相亲系统不能踩的雷区5.1 实名与审核机器审核为主人工兜底相亲系统的特殊性在于一旦出现虚假信息或者诈骗贴子用户的信任感会瞬间崩塌。我做了一个简单的实名认证流程用户上传身份证正面照手持照后台审核列表里可以看到待审图片管理员人工判断后在admin_ops里做“通过/驳回”操作。这个流程没法靠纯自动判断只能机器辅助人工兜底。图片上传这块我用了Django自带的FileField加自定义上传路径def upload_id_photo(instance, filename): ext filename.split(.)[-1] return fid_photos/{instance.user.id}/id_{time.time()}.{ext}注意一个安全问题上传的证件照属于敏感隐私不能放到静态资源目录直接通过URL访问。我的做法是把media根目录配置成不参与Nginx静态服务而是通过一个带身份校验的视图来读取照片只有本人和管理员才能访问。5.2 敏感操作防滥用限流与验证注册接口、短信验证码接口、搜索接口这些都是容易被薅的攻击面。我用django-ratelimit做了接口级限流ratelimit(keyip, rate5/m, methodPOST) def send_sms_code(request): ...这里的key是按IP限注册接口限制5次/分钟。如果同一台设备有多个账号注册羊毛党操作光肉眼看request body里的手机号容易漏。所以我还加了“同IP注册量检测”连续30分钟内同一IP注册超过3个账号自动进到审核冻结名单需要人工解封。这个功能是我上线后被人薅了一波之后才加的。CSRF防护这块DRF默认在SessionAuthentication下有CSRF强制但我用的是JWT认证JWT不会自动带CSRF Token——准确说CSRF的问题在JWT下主要是防“自动携带Cookie”的攻击方式JWT放Header里不自动带CSRF风险天然就低。实际做的时候完全没处理也还行但为了稳妥还是保留每次写操作校验Content-Type和Origin头的习惯。5.3 Photo类的版权与隐私问题相册模块本来是让用户传生活照的结果上线第一天就有用户传了别人可能是明星的照片。我直接加了一条机器审核逻辑图片先存OSS然后提交到腾讯云的“内容安全接口”做检测检测不通过的直接不发到公开相册审核结果标记在记录里。个人开发者的项目没有条件上大厂的审核系统的话至少也要做“上传后先不公开、管理员在后台审核后才展示”的流程否则光靠举报迟早出问题。这条是给所有做UGC用户生成内容产品的人提个醒。6. 部署与性能优化从Dev环境到对外可访问完整复盘6.1 本地开发环境与生产环境分离开发阶段我用的SQLite生产换成MySQL这中间其实有一个坑Django的ORM会在Python层面处理很多数据库差异但有些地方不会——比如MySQL的JSONField用法和SQLite不一样日期范围查询的边界条件也不同。所以我建议如果你是用Django做新品开发机就装个MySQL用Docker起一个MySQL 8容器数据库统一绝大多数跨库的“怪问题”可以提前规避。配置分离我用的是几套settings模块settings/ ├── base.py # 公共配置日志、缓存、中间件 ├── dev.py # 开发DEBUGTrue本地DB邮件后端打印 └── prod.py # 生产DEBUGFalseMySQLRedis邮件真实发送环境变量管理是Python Django项目的死角。很多初学者把数据库密码直接写settings.py里提交到GitHub属实名泄露现场。我用的是django-environ这个库生产环境变量放在服务器.env文件里settings.py读取环境变量。这样代码库里只有模板没有实际密码。6.2 Gunicorn Nginx部署经典三件套生产部署组合是Nginx反向代理 Gunicorn Redis缓存和Channel Layer。核心配置# gunicorn.conf.py bind 127.0.0.1:8000 workers 4 # 不是越多越好一般 CPU核数×21 worker_class gthread # 同步worker并发量太低用线程模式 threads 8 timeout 120worker数量我之前犯过错拿到一台4核机器直接写16个worker结果内存直接拉满。Gunicorn每个worker会加载完整的Django应用一个worker大约占100MB内存4核8G机器开4个worker 每个worker开8个线程就够了。Nginx需要关注两个端口的转发80端口转发到Django API880端口转发到前端Vue项目的静态文件。但更关键的是静态文件和media文件不能走Django——Nginx直接alias指向磁盘目录否则每个图片请求都要走一遍Python进程性能差得离谱。还有WebSocket的升级头要配location /ws/ { proxy_pass http://127.0.0.1:8000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_read_timeout 3600s; }这个proxy_read_timeout 3600s是Channels部署最常见的坑不配这个WebSocket连接每60秒被Nginx掐断表现就是聊天一会儿断线一会儿重连状态极其诡异。6.3 数据库瓶颈与缓存策略搜索接口在数据量过1万条以后还是会出现偶尔的慢查询。我给这几个高频接口加了Redis缓存缓存策略很简单key为“用户ID筛选条件”value为搜索结果的用户ID列表过期时间5分钟。这个策略解决了80%的重复查询压力。MySQL本身的优化就没那么麻烦了在my.cnf里调整innodb_buffer_pool_size到机器物理内存的一半左右重启后综合查询性能明显变化。还有一个被我忽略很久的坑是Django ORM的懒加载——查出来的UserProfile如果没有用select_related(user)循环打印每个人时会触发N1次查询日志里一堆重复SQL。我统一在列表接口加了select_related和prefetch_related这个是Django性能优化最简单有效的一步很多人挂在N1上还以为是网络问题。7. 踩坑实录排查过程中我学到的那些教训7.1 “删不掉的外键”约束冲突与数据一致性注册时我已经做了UniqueConstraint防止重复Like但后来出现了让人头疼的情况运营后台想删掉某个违规用户结果外键关联了太多表LikeRecord、ChatRoom、Photo都是外键指向User删除时直接报IntegrityError。第一反应是给外键加on_deleteCASCADE这样删用户时所有关联数据自动清理。但CASCADE也有反噬有一次手滑删了一个运营测试账号结果这个账号名下上百条聊天记录一瞬间全没了用户投诉说消息消失了。后来痛定思痛把聊天记录这类“可能需要审计找回”的数据改成了“软删除”。实现软删除最简单的方式是给User模型加一个is_deleted字段默认False。删除时只是置True不清数据同时登录逻辑里过滤掉is_deletedTrue的用户。真正的物理清理放到一个月一次的定时任务里做。这套方案既满足了合规删除的要求又保留了数据可追溯性。7.2 Django版本和第三方库的兼容性地狱2023年做项目时用的是Django 4.1但Channels的最新版本已经更新到4.x而Channels 4要求Django 4.2以上于是pip install时自动把Django升级到了4.2结果数据库迁移文件全乱套。具体表现是makemigrations检测到一堆模型变更一migrate就报字段不存在或者表已存在。排查链路是这样的——先看django版本pip show Django发现版本变了。再检查迁移文件有没有被自动rebase。解决方案不复杂但也踩了半天把全局Python环境清理掉改用virtualenv或者Poetry做项目隔离然后在requirements.txt里锁死版本Django4.1.7 djangorestframework3.14.0 channels4.0.0 django-channels-redis4.2.0很多人对requirements.txt只有“记录用什么包”这个认识实际上它最大的价值是“锁定版本”。一旦有人跑pip install -U django把版本升上去项目可能直接崩掉。从那次以后我所有项目的依赖都用pip freeze requirements.txt固定并且用一个独立的Python 3.10虚拟环境管理不跟系统的Py环境混用。另一个类似的坑是MySQLdb驱动。Django连MySQL需要mysqlclient或者PyMySQLWindows上mysqlclient编译经常出问题很多人转投PyMySQL。但PyMySQL在Django 4.2以后兼容性开始出岔子我最后选择了在服务器上用mysqlclientCentOS上编译顺利开发环境Windows上干脆用Docker里的MySQL加本机mysqlclient省去动态库编译的麻烦。这个选择背后没有高深的技术只有一句经验能用编译好的包就别自己编译尤其是在Windows上。8. 产品化思考从“能跑”到“像那么回事”8.1 为什么需要会员等级和积分体系一个真正能叫“系统”的产品除了功能还得有运营抓手。我给这个相亲系统做了两级会员普通会员和VIP会员。VIP可以看到谁喜欢了我、浏览记录、无限次解锁照片。这些都是很标准的“增值服务”逻辑但重点在于Django里怎么设计这个权益模型。我把VIP状态定义为UserProfile的一个字段vip_expire_date。当这个日期大于当前时间时接口会放行VIP权限过期以后走回基础功能。这里有个业务边界要注意充值记录和到期时间不能靠定时任务去更新而是在每次请求时动态判断这样即使定时任务挂了权限也不会出问题。支付对接我用的是微信H5支付回调接口是Django视图收到回调后校验签名、更新订单状态、给用户加VIP天数。8.2 运营后台Django Admin的二次开发自带的Admin确实是最快的管理界面但直接套用到“会员列表”这种场景会把敏感字段密码哈希暴露出来。我的做法是自定义了一个Admin类重写了list_display、list_filter、search_fields并关掉了密码修改入口admin.register(User) class CustomUserAdmin(UserAdmin): list_display (username, email, profile_gender, profile_city, is_verified, is_active) list_filter (is_active, is_staff, profile__is_verified) search_fields (username, email, profile__city) def profile_gender(self, obj): return obj.profile.gender if hasattr(obj, profile) else - profile_gender.short_description 性别用Profile的关联字段做list_display需要额外查询但因为Admin页面是内部使用、访问量低问题不大。Admin里还加了一个“发送系统通知”的按钮这个是为了给用户推“有人喜欢你了”这种消息通知用的直接在Admin里配置了模板消息运营人员操作起来零门槛。8.3 前端展示层的血泪经验前端我用的Vue 3 Element Plus没有用任何复杂的工程化框架。前端的核心页面就是注册、登录、用户列表、用户详情、聊天窗口、个人中心、管理后台。但开发到一半时我发现即使前端只是个“展示层”很多错误仍然出在这层。比如跨域问题。开发时Vue跑在localhost:5173Django跑在localhost:8000请求必跨域我加django-cors-headers解决CORS_ALLOWED_ORIGINS [ http://localhost:5173, https://yourdomain.com, ]这个库第一次配置容易漏掉CORS_ALLOW_CREDENTIALS True,虽然JWT方案不需要带Cookie但为了兼容后续可能要带Cookie的场景还是配置上。为了查跨域报错耽误了整整半天最后发现不是Django响应头问题而是前端Vite代理没配好——这是前后端分离开发最折磨人的地方。给新手的建议是开发阶段用Vite的proxy机制让dev server把/api/请求代理到后端8000端口这样浏览器里看到的请求是同源的既省了跨域配置也更好调试。9. 如果重来一次想做这个系统先避免这五件事踩了这么多坑最后整理一份“如果让我重新做”会改变的五件事对新人尤其有用。第一件事先画清楚数据模型图再写代码。我第一版表结构是在开发过程中改出来的结果用户表改了三次、聊天表重建了两次。建议动手前用draw.io或者纸笔把User、Profile、Photo、Like、ChatRoom、Message之间的关系理清楚把OneToOne、ForeignKey、ManyToMany标明白后面切表结构的成本会小很多。第二件事任何上传文件必须限大小和类型。第一版我根本没限发布后有人在个人资料里传了个1.2GB的视频虽然前端限制了图片格式但移动端直接跨UI上传被绕过了服务器磁盘直接爆掉。现在我在Nginx层就拦client_max_body_size 10m同时在Django验证文件扩展名和MIME类型。第三件事起步就该用Docker Compose。数据库、Redis、后端服务、前端全部用容器化部署。我开发时Windows本地装MySQL和Redis折腾了整整一个晚上是当前项目里最没技术含量又最影响心情的部分。Docker Compose写好以后换机器一键起全套环境效率完全不一样。第四件事前端接口文档用drf-spectacular自动生成。我第一次给前端同事写API文档是手写Markdown字段变化后文档就过期了。DRF本身就有OpenAPI支持配好spectacular库以后自动生成Swagger文档每次代码更新文档也同步更新省掉无数「为什么这里字段没有了」的沟通成本。第五件事日志和错误监控从第一天就接好。我上线第一周根本没配日志有一次线上用户反馈“收不到验证码”我查了半天才发现是短信服务商的单日限额用完了而应用日志里什么都没有。后来接了sentry-sdk错误自动上报线上问题能第一时间定位到具体代码行。这个习惯如果一开始就有估计能省掉三分之一排查时间。10. 最后补充一些关于项目的后续设想前面聊的都是已经实现的内容。其实在相亲系统这个方向上还有很多值得玩味的扩展点没做写出来供有志者参考。第一个是“匿名片”功能——用户可以在不知道对方真实身份的情况下先交换片面的兴趣爱好标签双方都点亮了某个共同标签后系统才解锁真实头像和资料。这个功能对保护隐私和提升互动率很有帮助表现在匹配逻辑上就是多了一个“中间状态”从普通陌生人到互相关注再到显真实身份。第二个是把匹配算法升级到“向量化”。当用户量超过5万以后规则打分顶多算“能看”真正的个性化需要把用户标签身高、收入、爱好、星座、学历、城市等做embedding用内积相似度或余弦相似度做推荐。Django生态里没有现成轮子要接Faiss或者Milvus这是一个很有意思的优化方向但数据没上规模时别搞这套纯属浪费服务器。第三个是客服系统和举报闭环。相亲产品用户之间矛盾频发没有客服介入机制用户投诉会直接变差评。我做了最简单的站内举报按钮举报记录进后台运营人员处理完后回传处理结果——流程闭环了但由于是个人项目没有给运营留太多时间维护。我在整个过程中最大的体会是项目不怕名字土就怕逻辑不清。你给系统起名叫“帅小伙网络相亲系统”听着随意但内部的模块划分、数据关系、权限边界都得严肃对待。从账号体系到匹配算法到聊天通信到安全风控这个项目几乎把Web开发该有的骨架全走了一遍。如果手头有这份文章再结合Django官方文档从头复刻一个类似的系统快则两周、慢则一个月你就能把一个“能跑的项目”做成“能讲的项目”。别人的介绍都是静态的Demo我的建议是做完以后把这个项目部署到公网哪怕只开放测试账号面试或者交流时直接演示给对方看点击、登录、配对、聊天全程跑通比你说一百句“我会Django”都管用。
返回列表