data_core/models.py

胡勇
2026-06-18 06:19
阅读 254

聊聊我远程在家用Django手搓网站的踩坑日常

上周末刚参加完本地的技术分享会,听了几个大佬讲云原生和eBPF,听得我热血沸腾。周一早上醒来,外面下着阴雨,作为远程办公的打工人,我穿着睡衣,泡了杯挂耳咖啡,看着家里的橘猫在机械键盘上踩来踩去,突然心血来潮,想搞点不一样的。

我本是搞硬件出身的,以前天天跟示波器、电烙铁打交道,写C/C++调寄存器,为了省几十字节的RAM能熬个通宵。后来实在受不了那种“改一行代码等半小时编译”还要去板子上烧录的日子,痛定思痛转了Go。现在主力语言是Go,平时写写高并发微服务,goroutine乱飞,爽是爽。但有时候想快速搞个带后台管理的内部小工具,或者帮非技术的朋友做个数据看板,用Go去配模板或者搞前后端分离总觉得不够“快”。

想起以前在老东家,那帮写Java的兄弟,搞个简单的CRUD都要建一堆Controller、Service、DAO、Entity,各种注解满天飞,还要配一堆XML。Java那套虽然工程化严谨,但对于我这种只想周末花两天时间快速验证个想法的人来说,实在太重了。于是,我决定换个口味,用Python和Django来搭建我的第一个Python网站,体验一把“电池 included”的快感。

环境准备与初体验

在家办公的一大好处就是可以按自己的节奏来。我打开Warp终端,讲真,自从换了Warp,我敲命令的心情都变好了。那现代化的UI、自带的AI命令补全,还有可以直接分享命令块的功能,比当年在Ubuntu下用纯黑框Terminal强太多,简直是远程办公神器。

pip install django djangorestframework
django-admin startproject smart_tool
cd smart_tool
python manage.py startapp data_core

看着目录结构瞬间生成,我心里暗爽。不用像写Go那样自己去go mod init然后一个个go get路由和ORM库,Django直接把全家桶给你端上来了。

数据库设计与ORM的“挥霍”

以前搞硬件,内存都是按字节抠的,指针乱飞是常态。现在用Django的ORM,感觉简直像在挥霍。我需要在数据库里存一些设备的运行日志和分析结果。

from django.db import models

class Device(models.Model):
    name = models.CharField(max_length=100, verbose_name="设备名称")
    location = models.CharField(max_length=200, verbose_name="部署位置")
    status = models.IntegerField(default=0, verbose_name="状态")

class DeviceLog(models.Model):
    device = models.ForeignKey(Device, on_delete=models.CASCADE, related_name="logs")
    timestamp = models.DateTimeField(auto_now_add=True)
    cpu_usage = models.FloatField()
    memory_usage = models.FloatField()
    error_msg = models.TextField(blank=True, null=True)

写完models.py,直接python manage.py makemigrationsmigrate,数据库表就建好了。不用手写DDL,不用去Navicat里点点点,这种效率对于习惯了底层开发的我来说,冲击力挺大的。

接口开发与让人头秃的N+1

后台数据总得给前端提供接口,我直接上了Django REST framework (DRF)。写Serializer和ViewSet的过程非常丝滑,基本上就是定义好字段,剩下的增删改查DRF都帮你包揽了。

但好景不长,在写设备日志列表接口时,我习惯性地看了一眼SQL日志,差点没背过气去。查询100条日志,它居然给我发了101条SQL!这就是经典的N+1查询问题。

讲真,这问题在Go里用GORM也会遇到,但在Django里如果不注意,ORM会默默地帮你挖坑。我习惯性地打开Perplexity查资料。不得不说,现在查这种框架级别的报错,Perplexity比传统的搜索引擎好使太多了。它没有给我塞满SEO垃圾和广告,而是直接总结了官方文档和StackOverflow的高赞回答,明确告诉我需要用select_relatedprefetch_related

我立刻修改了View里的QuerySet:

# data_core/views.py
from rest_framework import viewsets
from .models import DeviceLog
from .serializers import DeviceLogSerializer

class DeviceLogViewSet(viewsets.ReadOnlyModelViewSet):
    serializer_class = DeviceLogSerializer

    def get_queryset(self):
        # 使用 select_related 解决外键带来的 N+1 问题
        return DeviceLog.objects.select_related('device').all().order_by('-timestamp')

改完一看,SQL语句瞬间变成了一条带JOIN的查询,世界终于清净了。

性能优化:老本行不能丢

虽然Django是个重型框架,Python也有GIL的限制,但作为对性能优化有执念的人,我实在受不了慢吞吞的接口。既然单线程跑不快,那就在架构和缓存上下功夫。

首先是数据库索引。Django的ORM虽然方便,但不会自动给你加索引。我在DeviceLogtimestampdevice字段上加了db_index=True,并在联合查询时使用了index_together(新版推荐用indexes),查询速度直接起飞。

其次是引入Redis做缓存。对于首页的设备状态统计这种读多写少的数据,我写了一个简单的装饰器来缓存结果:

# utils/cache.py
from django.core.cache import cache
from functools import wraps

def cache_result(timeout=60):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            cache_key = f"{func.__name__}_{args}_{kwargs}"
            result = cache.get(cache_key)
            if result is None:
                result = func(*args, **kwargs)
                cache.set(cache_key, result, timeout)
            return result
        return wrapper
    return decorator

加上缓存后,那些复杂的聚合查询接口响应时间从800ms直接降到了20ms以内。看着压测工具里漂亮的响应曲线,我满意地摸了摸旁边的橘猫。

蹭个热点:接入大模型

既然现在大模型这么火,不给网站加点AI元素怎么好意思发朋友圈?我利用周末的时间,调了个大模型的API,做了一个“智能日志分析助手”。

思路很简单:前端传入一段设备的报错日志,后端把日志丢给大模型,让它分析可能的原因并给出排查建议。

# ai_service/views.py
import requests
from django.conf import settings
from rest_framework.decorators import api_view
from rest_framework.response import Response

@api_view(['POST'])
def analyze_error_log(request):
    log_content = request.data.get('log_content', '')
    if not log_content:
        return Response({'error': '日志内容不能为空'}, status=400)

    prompt = f"你是一个资深硬件与系统运维专家。请分析以下设备报错日志,给出可能的原因和排查步骤:\n{log_content}"
    
    # 调用大模型API (伪代码)
    ai_response = call_llm_api(settings.LLM_API_KEY, prompt)
    
    return Response({'analysis': ai_response})

这里有个小坑,大模型的接口响应通常比较慢(动辄几秒),如果直接放在同步请求里,很容易导致Worker线程被耗尽。好在Django从3.1开始原生支持异步视图。我把这个视图改成了async def,并用httpx替换了requests,配合ASGI服务器,彻底解决了阻塞问题。

运维部署:告别裸奔

以前搞嵌入式,代码编译完直接烧进Flash里就完事了。现在搞Web,部署简直是一门玄学。一开始我傻乎乎地用python manage.py runserver跑在生产环境,结果被测试小哥吐槽“这网站怎么动不动就卡死”。

赶紧查文档,老老实实上了Gunicorn + Nginx的经典组合。为了环境一致性,我顺手写了个Dockerfile:

FROM python:3.10-slim

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

COPY . .

RUN python manage.py collectstatic --noinput

EXPOSE 8000

CMD ["gunicorn", "smart_tool.asgi:application", "-k", "uvicorn.workers.UvicornWorker", "-b", "0.0.0.0:8000", "-w", "4"]

这里注意,因为我用了异步视图,所以Gunicorn的worker类我换成了uvicorn.workers.UvicornWorker,这样才能真正发挥ASGI的异步性能。

最后,在Nginx里配置好反向代理,把静态文件交给Nginx直接处理,减轻Python进程的压力。看着docker-compose up -d后,几个容器稳稳地跑在服务器上,通过域名顺利访问到那个带着AI功能的后台,长舒了一口气。

总结与碎碎念

折腾了这一个周末,从建项目、写ORM、搞接口,到死磕N+1性能问题,再到接入大模型和Docker部署,总算是把这个Django网站给搞上线了。

回过头来看,虽然我是写Go出身,习惯了强类型和高并发,但Python和Django在快速构建业务原型方面的优势确实是无可替代的。它就像是一把瑞士军刀,虽然砍大树不如Go和Java锋利,但用来削苹果、拆快递(快速建站、写内部工具)简直不要太好用。

当然,Django也有它的缺点,比如框架太重,学习曲线对于纯新手来说有点陡,而且如果业务逻辑复杂到一定程度,ORM的抽象反而会成为性能瓶颈。但对于我目前的诉求来说,它完美地完成了任务。

不说了,产品经理又在群里@我,说那个AI分析助手的提示词不够精准,让我再调调。远程办公就是这样,工作和生活的时间边界总是模糊的。我喝了一口已经凉掉的咖啡,打开Warp,准备继续改Bug。

希望这篇踩坑记录能给同样想尝试Django,或者从其他语言转过来的兄弟一点参考。如果大家在性能优化或者异步Django方面有什么骚操作,欢迎在评论区交流,下次技术分享会咱们接着聊!

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝