ARTICLE DETAIL

资讯详情

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

Django实战:从零搭建同人小说创作与在线阅读分享平台

Django实战:从零搭建同人小说创作与在线阅读分享平台 从零搭建一个同人小说创作与在线阅读分享平台Django实战全记录写这篇东西的起因是前段时间接了个私活——帮一个同人创作社团搭建作品发布与在线阅读平台。需求听着不算复杂作者能发布自己的同人小说读者能在线阅读、收藏、点赞后台能管理作品和用户。但这个项目真正做下来涉及的东西远比想象中多用户体系、富文本编辑、权限控制、阅读体验优化、部署上线……整套流程走完踩了不少坑也沉淀了不少经验。今天就把整个项目的设计思路、核心实现和那些实操中才会遇到的细节问题完整梳理一遍。如果你正准备用Python和Django开发一个内容创作类平台或者想了解一个完整的Django项目从设计到部署到底要经历哪些环节这篇文章应该能帮你省下不少摸索的时间。1. 项目整体设计与技术选型思路1.1 需求拆解同人小说平台到底要做什么同人小说平台和普通博客、资讯站最大的区别在于内容组织方式和用户行为模型。普通博客的文章是孤立的而小说平台天然存在“作品”和“章节”的嵌套关系用户的行为链路也更长发现作品、阅读章节、收藏追更、点赞评论、关注作者。我从需求方那边拿到的初始需求清单拆解下来主要有这几块作者端创建作品、管理章节、设置作品封面和简介、查看作品数据读者端浏览作品列表、搜索作品、阅读章节、收藏/点赞/评论管理端用户管理、作品审核、评论管理、数据统计扩展需求标签体系、排行榜、阅读历史这个需求规模用Django来做可以说是恰到好处。Django自带的ORM、Admin后台、认证系统和模板引擎能覆盖大部分基础能力。尤其是Admin后台在项目初期做内容管理时简直能省掉一半开发时间——需求方需要的内容审核、用户管理Django Admin开箱即用。1.2 为什么选Django而不是Flask或FastAPI选型的时候我其实纠结过一阵子。Flask轻量灵活FastAPI性能好且自带API文档但最终我还是选了Django核心原因有三个第一内置Admin后台。需求方需要一个管理界面来审核作品和用户Django Admin在Django中属于内置功能几行配置就能出一个功能完整的管理后台。如果用Flask这套后台得自己从头写工作量直接翻倍。第二ORM的成熟度。同人小说平台的数据模型有明确的外键关系作品关联作者、章节关联作品、评论关联章节。Django ORM对这些关联查询的支持非常成熟select_related和prefetch_related可以轻松解决N1查询问题。第三自带安全防护。平台类项目最怕XSS攻击和SQL注入。Django的模板系统默认转义所有变量ORM使用参数化查询这等于在框架层面帮你挡掉了两类最常见的攻击。当然FastAPI的性能优势确实存在但同人小说平台的瓶颈在数据库查询和页面渲染不在接口吞吐量。对这类以内容展示为核心的场景Django的“全家桶”效率优势更明显。1.3 项目目录规划与数据模型设计项目结构上我采用了Django标准的项目布局但把应用拆得更加清晰novel_platform/ ├── manage.py ├── config/ # 项目配置 │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── apps/ │ ├── users/ # 用户模块 │ ├── novels/ # 作品模块 │ ├── chapters/ # 章节模块 │ └── interactions/ # 评论/点赞/收藏模块 ├── static/ # 静态资源 ├── media/ # 上传文件 └── templates/ # 模板文件应用拆分的原则是“按业务域划分”而不是“按功能类型划分”。因为用户在扩展阶段很可能会加“排行榜”功能如果所有模型挤在一个app里后期维护成本会很高。数据模型是整个项目的核心这块设计的时候我花了比较多心思。用户模型直接继承Django自带的AbstractUser扩展了avatar头像、bio个人简介和followers粉丝数缓存三个字段。这里有个经验不要在用户表上放太多业务字段像“关注关系”应该单独建表用户表只放冗余的统计字段做展示用。作品Novel模型是核心中的核心字段设计如下title作品名称author外键关联用户description作品简介cover封面图category分类如原作衍生、原创等tags标签使用django-taggit库status连载状态连载中/已完结/停更view_count浏览量favorite_count收藏量like_count点赞量章节Chapter模型novel外键关联作品title章节标题content章节正文TextField存储富文本order章节排序号word_count正文字数created_at创建时间这里重点说一下order字段。同人小说章节经常需要调整顺序比如作者写完了再往前插入章节所以不能直接用自增id当排序依据而是用一个独立的order字段。初始值可以按创建时间生成后插入的章节可以指定插入位置代码里再对后续章节统一做重排。2. 核心功能模块实现与实操要点2.1 用户认证与权限控制用户系统直接用了Django内置的认证模块但有几个地方需要定制。注册接口需要扩展用户字段我推荐用Django内置的UserCreationForm做基础再通过继承的方式增加自定义字段。注册流程做了邮箱验证用django.core.mail发送激活链接激活链接里带一个带签名的token这样能防止恶意注册。权限控制分三层游客只能浏览和阅读登录用户可以评论、点赞、收藏、发布作品作者可以管理自己的作品和章节Django自带的login_required装饰器和ModelForm能满足大部分场景。但有一个细节要注意作者的权限校验不止要判断“是否登录”还要判断“这个作品是不是属于当前用户”。所以我在作品详情视图里加了这样一段判断def get_object(self, querysetNone): obj super().get_object(queryset) if obj.author ! self.request.user: raise PermissionDenied(您没有权限操作该作品) return obj类似这样的权限细节如果不做就会出现一个严重的逻辑漏洞任何登录用户都能通过直接输入URL的方式编辑别人的作品。这个坑在开发阶段一定要覆盖到。2.2 富文本编辑器选型与XSS防护同人小说的章节内容需要支持排版、插图、引用块等富文本格式所以不能简单地用textarea处理。我对比了三种方案Markdown编辑器对写代码的人友好但对普通作者不友好CKEditor功能全定制性强wangEditor轻量中文文档好最终选了wangEditor原因很简单需求方要求“像写Word一样”的体验而wangEditor的界面最接近。但用富文本编辑器就引出了另一个关键问题——XSS攻击防护。Django模板虽然默认转义但富文本内容存储的是HTML渲染时要用safe过滤器等于主动关闭了转义这就给了恶意脚本可乘之机。我的处理方案是使用bleach库过滤HTMLimport bleach ALLOWED_TAGS [ p, br, strong, em, u, s, h1, h2, h3, blockquote, img, a, ul, ol, li ] ALLOWED_ATTRIBUTES { img: [src, alt, width, height], a: [href, title, target] } def clean_html(content): cleaned bleach.clean( content, tagsALLOWED_TAGS, attributesALLOWED_ATTRIBUTES, stripTrue ) return cleaned这段代码的效果是只允许白名单内的标签和属性所有script、iframe、onclick之类的属性会被全部剥离。我在保存章节内容的视图里统一调用clean_html做过滤彻底掐掉XSS脚本入口。读到这里你可能会想那作者插入的图片怎么办这里的处理逻辑是插图先通过文件上传接口上传到服务器返回一个本站图片URL编辑器直接插入这个URL。这样既避免了外链图片的防盗链问题也绕开了外链图床的安全隐患。2.3 阅读页的排版与交互优化在线阅读体验是同人小说平台的核心卖点。这个模块我调试了很久主要解决三个层面的问题。阅读页的排版。小说阅读的场景是“长时间盯着屏幕”所以页面设计要非常克制——背景用柔和的米色#faf8f2正文用16px以上的字号行高1.8到2.0每行最多容纳35到40个字符。这些数据不是随便拍的而是参考了主流阅读App的设计规范。为了实现“宽屏下像书页、窄屏下像手机阅读”我给正文容器设置了max-width: 720px再配合媒体查询适配移动端。章节切换的方式。常见的处理是文末放“上一章”“下一章”链接但我额外加了一个底部导航栏把目录、收藏、点赞按钮都放进去。实测下来这个设计能显著降低读者的跳出率——阅读过程中想操作不需要再滚回页面顶部。阅读进度的自动保存。用localStorage存储用户阅读到的章节锚点下次进入作品时自动跳转并且弹窗提示“继续阅读”。这里有个性能细节值得说一下阅读章节页面的查询一定要用select_related把作品信息和作者信息一次性查出来否则每个页面会产生三次以上数据库查询高频访问下压力会非常大。chapter Chapter.objects.select_related(novel__author).get( idchapter_id, novel_idnovel_id )3. 实操过程从创建应用到部署上线3.1 环境准备与Django项目初始化整个开发环境我是在Windows上做的Python用的3.10版本。新手有一个高频错误直接装了最新版的Python结果第三方库还没跟上报各种奇怪的坑。所以我的建议是如果你用的是Django 4.x或5.xPython版本控制在3.9到3.11之间最稳妥。虚拟环境是必须的我用venv创建隔离环境python -m venv venv venv\Scripts\activate # Windows source venv/bin/activate # Linux/Mac然后安装核心依赖pip install django5.0.2 pip install pillow # 图片处理 pip install django-taggit # 标签系统 pip install bleach # HTML清洗 pip install django-ckeditor # 富文本编辑器备用Django项目初始化的命令不多但有一个点值得注意——不要把应用直接放在项目根目录。我习惯先把所有业务应用放到apps目录下并在settings.py里做这样的配置import sys from pathlib import Path BASE_DIR Path(__file__).resolve().parent.parent sys.path.insert(0, str(BASE_DIR / apps))加了这行代码之后所有应用可以直接用users、novels这种短名字注册不用写apps.users这样的长路径import的时候也省心。3.2 作品发布流程的实现细节作者创建一本新作品的流程是这样的填写作品基本信息标题、简介、分类、标签→ 上传封面 → 创建成功进入作品管理页 → 开始写第一章。作品表单用ModelForm实现其中封面图字段用了ClearableFileInput控件模板里再配合cropper.js做前端裁剪裁完把最终图片上传到media/novel_covers/目录。这里有个容易踩的坑文件上传大小限制。Django默认没有限制请求体大小但服务器比如Nginx默认有1MB的上传限制。小说封面一般要求不超过2MB所以部署上线时一定要修改Nginx的客户端请求体大小配置client_max_body_size 5M;如果不改这个配置用户上传稍微大点的封面图就会得到413错误而且排查起来很隐蔽——开发环境正常生产环境就挂。章节编辑和保存的过程我设计了自动保存草稿的逻辑。作者在编辑章节时每3分钟自动通过AJAX把内容提交到一个save_draft接口。这个功能在竞品平台里很常见实际使用率也很高——写了两千字突然关浏览器如果没有自动保存心态真的会崩。3.3 搜索与筛选功能的实现同人小说平台的搜索和一般站内搜索不太一样用户经常带着特定目的来想看某个原作比如某部动漫的同人或者想看某个CP配对的作品。所以光靠一个搜索框不够我做了组合筛选功能。基础搜索用的Django的icontainsnovels Novel.objects.filter( Q(title__icontainskeyword) | Q(description__icontainskeyword) | Q(tags__name__icontainskeyword) ).distinct()组合筛选通过URL参数实现例如/novels/?category衍生status连载中sorthot。这里我自定义了一个sort参数支持按最新、最热、收藏数三种方式排序。用ORM的order_by实现但注意多字段排序时要在前面加负号if sort hot: novels novels.order_by(-view_count) elif sort favorite: novels novels.order_by(-favorite_count) else: novels novels.order_by(-created_at)搜索功能受限于数据量一开始就没上Elasticsearch这类重型方案。如果后期数据量超过十万条建议再迁移到HaystackWhoosh或直接上Elasticsearch。现阶段MySQL前缀索引完全够用。3.4 服务器部署宝塔面板uWSGINginx部署这块我直接用的宝塔面板BT Panel因为它把Nginx配置、Python环境管理、进程守护都做好了图形化界面对中小型项目来说非常省事。部署流程大致分这么几步第一步安装Python环境。宝塔面板的软件商店可以直接装Python项目管理器或者用系统自带的Python 3.x。注意装好之后把pip源换成国内镜像否则安装依赖的速度会让人崩溃。pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple第二步上传项目代码。代码上传到服务器的/opt/novel_platform/目录然后在项目根目录创建虚拟环境并安装依赖。第三步配置WSGI服务。我选用uWSGI作为Django的Web服务器接口。宝塔的Python项目管理器可以一键启动uWSGI但配置文件建议自己写宝塔生成的默认配置过于简单。我的uWSGI配置核心部分是这样的[uwsgi] chdir /opt/novel_platform module config.wsgi:application master true processes 4 threads 2 socket 127.0.0.1:8000 vacuum true max-requests 5000 buffer-size 65535这里有个容易忽视的参数buffer-size。如果章节内容比较大默认的4096缓冲区会报写入超时或数据截断的错误所以一定要调大。第四步配置Nginx反向代理。server { listen 80; server_name yourdomain.com; client_max_body_size 5M; location /static/ { alias /opt/novel_platform/static/; } location /media/ { alias /opt/novel_platform/media/; } location / { include uwsgi_params; uwsgi_pass 127.0.0.1:8000; uwsgi_read_timeout 60s; } }第五步收集静态文件和迁移数据库。python manage.py collectstatic --noinput python manage.py makemigrations python manage.py migrate到这里部署基本就完成了。如果上线后访问出现502错误排查顺序一般是uWSGI是否启动→端口是否监听→Nginx配置是否生效→项目目录权限。我遇到过一次坑是/opt目录下的项目文件属主是root但uWSGI以www用户运行导致无法读写media目录静态图片全部404。解决方法很简单chown -R www:www /opt/novel_platform4. 常见问题与排查技巧实录4.1 N1查询问题导致首页加载缓慢现象作品列表页只有20部作品但数据库查询次数超过60次页面加载耗时2秒以上。原因模板中展示每部作品的作者信息时Django的懒加载特性会逐条执行查询。我查了debug_toolbar的SQL记录20条作品数据用了21条查询——1条查作品列表20条查作者信息。解决方案在视图里用select_related预加载外键关联。novels Novel.objects.select_related(author).all()同样的问题也出现在章节列表页用prefetch_related处理反向关联novels Novel.objects.prefetch_related(chapters).all()处理完之后查询从60多次降到5次页面加载时间从2.1秒降到0.3秒。这个优化是立竿见影的所有做Django开发的人都应该养成“写完视图先查一下SQL次数”的习惯。4.2 富文本编辑器上传图片接口404现象wangEditor中上传图片时请求报404错误。原因编辑器默认把图片上传到/upload/image这个URL但我的项目里根本没有这个路由。而且wangEditor的前端代码写死了上传地址不能直接通过后端配置修改。解决方案在前端初始化编辑器时指定自定义的上传地址和服务端返回格式。const editor new wangEditor(#editor-container); editor.config.uploadImgServer /api/upload/image; editor.config.uploadFileName file; editor.config.uploadImgMaxSize 2 * 1024 * 1024; editor.create();后端对应视图csrf_exempt def upload_image(request): if request.method POST: file request.FILES.get(file) # 保存文件到 media/upload/ return JsonResponse({errno: 0, data: {url: url}})这里有个CSRF的坑wangEditor的AJAX请求默认不带CSRF token所以要么在后端加csrf_exempt装饰器不推荐要么在前端正确配置CSRF token。我在项目里的做法是在后端视图里读取自定义header里的X-CSRFTokenrequire_POST def upload_image(request): if not request.user.is_authenticated: return JsonResponse({errno: 401, message: 请先登录}, status401) # 处理文件上传...上传功能必须加登录校验否则会变成恶意文件上传的入口。4.3 分页翻页时报错或数据重复现象作品列表翻到第3页时偶尔会出现重复数据或者漏数据的情况。原因排序字段不是唯一的。比如按view_count排序如果有10部作品的view_count都是100它们的相对顺序在数据库查询中是不确定的翻页时同一部作品可能出现在不同页。解决方案排序字段末尾追加一个唯一字段通常是id兜底。novels novels.order_by(-view_count, -id)这个问题在数据量小的时候不容易发现一旦数据量上来会非常明显。记住这个规律只要排序字段不唯一就一定要加id作为次级排序。4.4 登录状态下访问其他用户作品管理入口现象用户A登录后手输/novels/3/edit/这个URL成功进入了对作品3的编辑页面而作品3是用户B创建的。原因编辑视图里只用了login_required判断登录状态没有判断作品归属权。解决方案在视图开头加归属校验不通过直接403def novel_edit(request, pk): novel get_object_or_404(Novel, pkpk) if request.user ! novel.author: return HttpResponseForbidden(无权操作) # ...这类越权漏洞在Django项目里很常见。开发时要形成条件反射所有涉及“修改”“删除”的视图除了登录校验还要做对象归属校验。5. 性能优化与安全加固5.1 缓存策略让数据库喘口气平台上线一周后我观察到一个现象首页和排行榜的接口每天被请求上千次数据库压力陡增。虽然数据量不大但每次都走完整查询链路浪费是明显的。Django对这类场景提供了cache框架。我的做法是对首页作品列表和排行榜做10分钟缓存from django.core.cache import cache def home(request): novels cache.get(home_novels) if not novels: novels list(Novel.objects.select_related(author)[:12]) cache.set(home_novels, novels, 600) return render(request, home.html, {novels: novels})缓存的粒度按数据更新频率分两档首页作品列表10分钟过期排行榜1小时更新一次。作品发布后不会立即展示在首页但对读者来说这个延迟可以接受——因为内容平台最重要的是一致性而不是实时性。要注意的是缓存内容里不能包含当前用户相关数据。如果你缓存了“登录用户的收藏状态”那A用户看到的数据会错误地展示给B用户。所以我的做法是页面级缓存只放公共数据用户相关数据单独查询。5.2 数据库层面的优化以下是几个我实践后觉得收益明显的数据库优化手段添加数据库索引。搜索、排序、状态筛选这些高频查询场景对应的外键字段和状态字段都加了db_indexTrue。Django迁移时自动创建索引不需要手动写SQL。class Novel(models.Model): category models.CharField(max_length50, db_indexTrue) status models.CharField(max_length20, db_indexTrue) created_at models.DateTimeField(auto_now_addTrue, db_indexTrue)批量数据操作。点赞、收藏数据的统计字段如果在每次请求时都重新聚合计算数据库会非常吃力。我选择在用户点击时直接对Novel表的favorite_count做原子操作。from django.db.models import F Novel.objects.filter(idnovel_id).update(favorite_countF(favorite_count) 1)F表达式能避免并发覆盖问题而且只需要一条SQL性能极高。这个思路也适用于浏览量统计。防刷的判断逻辑比如同一用户只能点赞一次放在业务层数据准确性和性能可以兼顾。5.3 Django安全配置安全加固这块我总结为五个必做项设置SECRET_KEY为环境变量不要提交到代码仓库生产环境必须开DEBUGFalse否则会暴露完整的堆栈信息给访问者配置ALLOWED_HOSTS防止HTTP Host头注入攻击开启Django内置的CSRF防护并且所有POST表单都带上{% csrf_token %}设置SESSION_COOKIE_SECURE和CSRF_COOKIE_SECURE为True确保Cookie只通过HTTPS传输SECRET_KEY os.environ.get(DJANGO_SECRET_KEY) DEBUG False ALLOWED_HOSTS [yourdomain.com, www.yourdomain.com] SESSION_COOKIE_SECURE True CSRF_COOKIE_SECURE True SECURE_SSL_REDIRECT True # 注意需要配合Nginx开启HTTPS加上Django安全响应头SECURE_CONTENT_TYPE_NOSNIFF True SECURE_BROWSER_XSS_FILTER True X_FRAME_OPTIONS DENY这些配置在开发环境可以先关掉但上线前必须全部打开。我曾经因为忘记关DEBUG模式被安全扫描工具标记过多条告警虽然没造成实际损失但真正被黑了再补救就太被动了。6. 个人项目复盘与实际运维经验同人小说平台这个项目从设计到上线前后花了两周多时间。如果用一句话总结开发心得那就是“Django让开发效率很高但安全性和性能优化必须认真对待”。对新手朋友有几点经验可以参考第一先做数据模型设计再写视图和模板。我做项目前期习惯把ER图画清楚各个模型间的关联关系理清了后面写ORM查询非常顺畅。反过来如果模型设计不清晰后期改表结构的成本会成倍增加。第二多关注数据库执行的SQL语句。Django有个debug_toolbar工具启用后可以在页面右侧看到每次请求的SQL耗时。开发过程开着它能及时发现隐藏的N1查询问题。第三权限验证的逻辑要在一开始就想清楚。是“登录用户都能操作”还是“只有作者能操作”这两者在代码里是完全不同的实现。最好给每个视图函数做权限检查清单上线前逐个功能测试一遍越权场景。第四运维方面建议给服务器配置自动备份。我的做法是用crontab每天凌晨3点备份MySQL数据库保留最近7天的备份文件。小说平台的内容是作者的心血丢数据是不可接受的。最后再分享一个小技巧项目上线后在Nginx层面开启gzip压缩对文本类内容HTML、CSS、JS的传输体积能压掉60%以上阅读页的加载速度会明显改善。配置只需要几行代码gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xmlrss text/javascript; gzip_min_length 1k;这一步的收益几乎为零成本但上线初期从“能用”到“好用”的体验差距往往就体现在这些细节上。这个项目后续还可以继续扩展的方向不少增加章节评论的楼中楼回复、基于用户阅读历史的推荐系统、通过Celery做定时任务统计每日热度榜单……每一条路径都能让平台体验更完善。但核心功能跑通、稳定运行才是一个内容型平台最扎实的起点。
返回列表