3个坑搞定用电查询,完整示例助你毕业不背锅
看了一堆教程还是不会写项目?别急,这通常是因为你只看了语法,没跑通业务闭环。今天直接上【用电查询】的完整示例,带你从零搭一个能落地的后端服务。这不是玩具代码,而是模拟真实电力业务场景的实战项目,专治“懂语法不会干活”的毛病。
项目目标与业务场景拆解
很多应届生入行第一周就懵,因为文档里的代码跑通了,但不知道这东西在业务里到底干啥。咱们先聊清楚【用电查询】到底在查什么。在电力行业,这不是简单查个余额,核心是查“户号”对应的实时用电状态、历史账单、甚至异常告警。
为什么选这个做练手?因为它涉及典型的 CRUD(增删改查)加上一点状态机逻辑。你写好了这个,换到查快递、查话费、查订单,逻辑是通用的。
核心痛点直击:教程里的代码往往依赖本地 Mock 数据,一上真实环境就崩。
不知道接口该怎么设计,参数传什么,返回什么。
遇到“用户查不到数据”这种边界情况,完全不知道怎么处理。我们的目标很明确:用 Python + FastAPI + SQLite(为了简化部署,生产环境换 MySQL/PostgreSQL)搭建一个标准的用电查询服务。你要能看懂每一行代码为什么这么写,而不是复制粘贴。
目录结构:工程化思维入门
别一上来就写 main.py 把所有代码堆在一起。那是脚本,不是项目。去翻翻 FastAPI 的官方源码仓库或者 Django 的文档,你会发现成熟项目都是分层设计的。
我们采用经典的三层架构:
power_query_service/
├── app/
│ ├── __init__.py
│ ├── main.py # 入口文件,注册路由
│ ├── config.py # 配置文件,管理数据库连接等
│ ├── models/
│ │ ├── __init__.py
│ │ ├── user.py # 数据库模型定义
│ │ └── schemas.py # Pydantic 数据校验模型
│ ├── services/
│ │ ├── __init__.py
│ │ └── query_service.py # 核心业务逻辑
│ └── api/
│ ├── __init__.py
│ └── routes.py # API 路由定义
├── requirements.txt
└── README.md为什么要这么分?models 层只负责和数据库打交道,定义表结构。
schemas 层负责数据校验,比如用户传进来的户号必须是数字,长度限制等。
services 层是核心,写业务逻辑。如果以后要换数据库,或者加个缓存,只改这里,不动接口层。
api 层只负责接收请求,调用 services,返回结果。这种分离,是你从“学生”变成“工程师”的第一步。以后面试被问到“如何保证代码可维护性”,你拿出这个结构,比背八股文管用。
核心代码实现:逐行拆解
这里不贴大段代码,而是拆解关键部分。我会标注哪些是“坑”,哪些是“最佳实践”。
1. 定义数据模型 (models/user.py)
from sqlalchemy import Column, Integer, String, Float, DateTime
from sqlalchemy.orm import declarative_base
import datetimeBase = declarative_base()class PowerUser(Base):__tablename__ = 'power_users'id = Column(Integer, primary_key=True, index=True)user_no = Column(String(50), unique=True, index=True, nullable=False) # 户号,唯一索引name = Column(String(100), nullable=False)current_power = Column(Float, default=0.0) # 当前用电量balance = Column(Float, default=0.0) # 余额status = Column(String(20), default='active') # 状态:active, suspendedcreated_at = Column(DateTime, default=datetime.datetime.utcnow)避坑点:unique=True:户号必须唯一,数据库层面加约束,比在代码里查一遍再插入靠谱得多。
index=True:查询频率高的字段加索引,不然数据量大了查询会慢到怀疑人生。2. 定义 API 数据校验 (models/schemas.py)
很多新手喜欢直接用 SQLAlchemy 模型返回给前端,这是大忌。数据库字段可能包含敏感信息(如密码哈希),或者格式不符合前端要求。
from pydantic import BaseModel, Field
from datetime import datetimeclass PowerUserBase(BaseModel):user_no: str = Field(..., min_length=1, max_length=50)name: strclass PowerUserCreate(PowerUserBase):passclass PowerUserOut(PowerUserBase):id: intcurrent_power: floatbalance: floatstatus: strcreated_at: datetimeclass Config:orm_mode = True关键点:Field(..., min_length=1):强制校验非空。
orm_mode = True:允许直接从数据库对象转为 Pydantic 对象,简化代码。3. 核心业务逻辑 (services/query_service.py)
这是最核心的部分。我们要实现“根据户号查询用电信息”。
from sqlalchemy.orm import Session
from app.models.user import PowerUser
from app.models.schemas import PowerUserOut
from fastapi import HTTPExceptiondef get_power_by_user_no(db: Session, user_no: str) - PowerUserOut:# 1. 查询数据库user = db.query(PowerUser).filter(PowerUser.user_no == user_no).first()# 2. 处理边界情况:查不到怎么办?if not user:raise HTTPException(status_code=404, detail=用户不存在或户号错误)# 3. 业务逻辑:如果状态是 suspended,可能需要返回特定提示# 这里简化处理,直接返回数据,前端根据 status 显示# 4. 转换为 API 输出格式return PowerUserOut.from_orm(user)深度解析:为什么用 HTTPException 而不是返回 None?因为 API 需要明确的错误码。404 是标准 HTTP 状态码,前端可以统一捕获。
from_orm 是 Pydantic v1 的写法,v2 版本要用 model_validate。注意你用的库版本,这是很多新手报错的原因。4. 路由定义 (api/routes.py)
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from typing import List
from app import models, schemas, services
from app.database import get_dbrouter = APIRouter()@router.get(/power/{user_no}, response_model=schemas.PowerUserOut)
def read_power(user_no: str, db: Session = Depends(get_db)):根据户号查询用电详情return services.get_power_by_user_no(db, user_no)@router.get(/power/search, response_model=List[schemas.PowerUserOut])
def search_power(name: str = Query(..., min_length=1), db: Session = Depends(get_db)):模糊搜索用户名(注意:生产环境慎用,容易慢)users = db.query(models.user.PowerUser).filter(models.user.PowerUser.name.like(f%{name}%)).all()return [schemas.PowerUserOut.from_orm(u) for u in users]对比式思维:GET /power/{user_no} 是精确查询,适合详情页。
GET /power/search 是模糊查询,适合列表页搜索。
两者逻辑不同,不要混在一个接口里。很多新手喜欢做一个“万能接口”,传不同参数做不同事,这是反模式,会导致代码难以测试和维护。运行与测试:别只跑主程序
很多教程让你跑 uvicorn main:app,然后 curl 一下就算完事。这是不专业的表现。你需要写单元测试。
# tests/test_query.py
import pytest
from fastapi.testclient import TestClient
from app.main import app
from app.database import SessionLocal, Base, engineBase.metadata.create_all(bind=engine)client = TestClient(app)def test_get_power_success():# 准备测试数据db = SessionLocal()test_user = models.user.PowerUser(user_no=TEST123, name=测试用户, balance=100.0)db.add(test_user)db.commit()# 发起请求response = client.get(/power/TEST123)# 断言assert response.status_code == 200data = response.json()assert data[user_no] == TEST123assert data[balance] == 100.0# 清理数据db.delete(test_user)db.commit()db.close()def test_get_power_not_found():response = client.get(/power/NOT_EXIST)assert response.status_code == 404为什么必须写测试?回归保障:以后你改了代码,跑一下测试,就知道有没有把之前的功能搞坏。
文档作用:测试用例就是最真实的接口文档,比 Swagger 更准确。
面试加分:应届生能写出可运行的单元测试,比只会写 CRUD 强十倍。优化扩展:从能用到好用
代码跑通了,只是及格。怎么让它更接近生产环境?分页查询:
如果查询结果有几万条,一次性返回会把前端卡死。必须加分页。
@router.get(/power/list)
def list_power(page: int = 1, size: int = 10, db: Session = Depends(get_db)):offset = (page - 1) * sizeusers = db.query(models.user.PowerUser).offset(offset).limit(size).all()return [schemas.PowerUserOut.from_orm(u) for u in users]注意:size 要有上限,比如最大 100,防止恶意请求拖垮数据库。缓存策略:
用电数据变化不频繁,可以用 Redis 缓存查询结果。Key: power:user:{user_no}
TTL: 5分钟
逻辑:先查 Redis,没有再查 DB,查到后写入 Redis。
避坑:缓存失效策略要选好,是定时刷新还是主动失效?用电数据一般定时刷新即可。日志记录:
别用 print。用 logging 模块。
import logging
logger = logging.getLogger(__name__)# 在查询失败时
logger.warning(fUser not found: {user_no})生产环境日志要结构化,方便 ELK 收集分析。安全加固:用户认证:加 JWT Token,每个请求都要验证身份。
权限控制:用户只能查自己的户号,不能查别人的。这个逻辑要在 service 层做,而不是前端隐藏。小结与互动
这个【用电查询】项目,看似简单,实则涵盖了后端开发的几个核心点:分层架构、数据校验、异常处理、测试驱动、性能优化。
你不需要把每个点都做到极致,但你要知道它们存在,知道什么时候该用。应届生最大的优势不是技术多深,而是学习能力强和代码习惯好。从今天开始,别只抄代码,要问自己“为什么这么写”。
最后抛个问题给你:
在实际工作中,你发现很多老项目的代码是一团乱麻,没有分层,没有测试,全是面条代码。面对这种情况,你是选择“先重构再开发”,还是“新功能按规范写,旧代码不动”?你公司项目里是怎么处理的?欢迎评论区聊聊你的真实经验,看看大家都是怎么在烂代码里生存的。
企业数字化 ERP 产品动态
相关推荐
消息通知选型保姆级教程:版本升级API全变?5分钟搞清4大方案 消息通知选型保姆级教程:版本升级API全变?5分钟搞清4大方案 刚把项目里的消息模块从 v1.2 升到 v2.0,结果发现 send() 方法直接没了,参数结构全改,文档还写得像天书。这种“版本升级后 API… · 2026/9/22 21:37:36
3个浏览器版本坑点,一文搞懂兼容性与降级方案 3个浏览器版本坑点,一文搞懂兼容性与降级方案 官方文档堆成山,翻半天还是不知道哪里出了问题?别慌,这种“文档读不懂、代码跑不通”的绝望感,每个做前端的都经历过。今天咱们不背概念,直接上实战。这篇文章就是为了解决你项目里那些因 浏览器版本… · 2026/9/22 21:37:30
3招搞定俄罗斯歌手数据查询性能优化面试 3招搞定俄罗斯歌手数据查询性能优化面试 面试官盯着你问:“这个接口为什么慢?”你答不上来,冷汗直流。别慌,今天用俄罗斯歌手数据实战拆解性能优化,让你面试不再卡壳。 项目目标… · 2026/9/22 21:37:24
vsco下载实战:5个坑点教你写个高效爬虫 vsco下载实战:5个坑点教你写个高效爬虫 官方文档翻了三遍还是没搞懂请求头怎么抓?别急,这份避坑指南直接上代码,3分钟跑通 vsco 下载全流程。 项目目标与痛点拆解 很多新手做图片下载,盯着官方 API… · 2026/9/22 22:19:50
# Presto 查询引擎内核详解:AddExchanges——基于物理属性的全局数据分布规划 AddExchanges — Global Data Distribution Planning Based on Physical Properties 引言
在 Presto 的分布式执行引擎中,查询优化器在将逻辑计划转换为物理执行计划时,面临一个核心问题:如何确保每个算子都能获得符合其执行要求的数据分布&… · 2026/9/22 22:19:32
3步读懂 adiaos 源码:附完整示例避坑指南 3步读懂 adiaos 源码:附完整示例避坑指南 堆栈溢出、空指针异常、回调地狱……当屏幕上一堆红色的 StackTrace 像天书一样砸过来,你的第一反应是不是想关掉… · 2026/9/22 22:19:25
Maya教程环境配置踩坑全解含完整示例 Maya教程环境配置踩坑全解含完整示例 刚拿到Maya教程资料,打开安装包就卡半天?别急,这不是你的问题,是90%的人没看清依赖项。很多开发者文档里藏着的细节,官方安装器根本不会主动提醒你。今天咱们不整虚的,直接拆解Maya环境配置中最容易… · 2026/9/22 22:19:25
5个真实血泪教训:联想风云环境搭建避坑指南 5个真实血泪教训:联想风云环境搭建避坑指南 配置环境就卡半天,这种痛谁懂? 刚接手新项目,对着文档敲了三小时,终端里全是红字报错。 别急,这份避坑指南能帮你省下至少两小时的抓狂时间。… · 2026/9/22 22:19:25
3分钟吃透78.cm源码解析,面试不再被问倒 3分钟吃透78.cm源码解析,面试不再被问倒 官方文档动辄几百页,翻两页就晕头转向?别急,今天咱们不啃大部头,直接上干货。 很多新人拿到【78.cm】这个需求,第一反应是去查官方Wiki,结果发现配置项多如牛毛,逻辑绕得像迷宫。其实,… · 2026/9/22 22:19:19
5个电影海报图片处理坑,新手避坑指南 5个电影海报图片处理坑,新手避坑指南 刚写完代码,一运行屏幕直接炸了。满屏红色的 StackTrace 滚得比弹幕还快,什么 NullPointerException 、 ImageIO.read() returned null 、… · 2026/9/22 0:00:07
注册微信公众账号:一文搞懂从0到1全流程 注册微信公众账号:一文搞懂从0到1全流程 复制来的代码跑不通,报错信息满屏飞,到底卡在哪?别急,咱们先停下手里的调试。很多开发者觉得注册微信公众账号只是填个表单、传个身份证那么简单,真上手才发现坑深不见底。今天这篇 一文搞懂… · 2026/9/22 0:00:07