“基于python+django老年人社区健康互助平台”这个题目,我看一眼就很有共鸣。题目标号里那串my63z30q,懂的都懂,多半是内部仓库或者学校/机构项目管理系统自动生成的编号,我最近替街道社区做的一套“乐龄互助”平台,代码仓库名也是这种鬼样子,一长串随机字符串,活脱脱像个数据库主键。这种项目的核心从来不是技术炫技,而是怎么用一套简单可靠的信息系统,把“老人有需求、志愿者有意愿、社区要管理”这三件事串成一个能跑起来的闭环。本文就当一次完整复盘,从需求拆解到数据库设计,再到关键功能实现和部署踩坑,把我能想到的细节全部铺开,给准备用Python和Django做类似平台的同学一份可以直接参考的实战底稿。
1. 先拆需求:这个互助平台到底在解决什么问题
做任何系统之前,最忌一上来就建表、写代码。老年人社区互助这个场景,听起来很温情,但落到业务流程上,比普通电商后台要复杂得多。它的核心不是“卖货”,而是“服务调度”加“健康追踪”,再加上一层老人特有的紧急状况兜底逻辑。
先说用户角色。我梳理下来至少需要四类账号:平台管理员、社区工作人员、志愿者、老年人本人,还有一个容易被忽略但很重要的角色——老人家属。你可能会问,老人不会用智能手机怎么办?实际项目里,大多数“老人”账号其实是子女帮忙注册的,或者老人通过社区触屏终端、电话热线,由工作人员代为下单。所以我们的权限模型里,必须允许一个家属账号关联多位老人,也能代老人发起服务申请。
再说核心业务域。我把它拆成五个板块:
- 需求发布与服务接单:老人发起助餐、助医、陪伴、代买等需求,志愿者抢单或社区派单。
- 健康档案管理:记录老人的基础疾病、过敏史、近期体检结果,以及每天血压、血糖这类自测数据。
- 活动招募与管理:社区经常组织义诊、健身操、智能手机教学等线下活动,老人/家属在线报名。
- 亲属关怀:老人健康指标异常或超时未下单、未打卡时,系统要能提醒家属。
- 服务评价与回访:每次互助服务完成后,需要有评价和社区回访记录,形成管理闭环。
想清楚这一层后,我对这个项目的判断是:它本质上是一个带业务调度的角色化信息平台,但数据模型比普通信息发布系统多了一个“社交与服务流转”的副属性。所以Django的Admin后台从一开始就不够用,必须写独立的视图和接口给不同角色使用,这一点要提前跟需求方对齐,不要指望原生后台能搞定。
另外,项目即便只是一个毕业设计或者练手项目,也建议按照实际MVP(最小可行产品)标准来做,把“老人紧急求助SOS”这种概率小但致命的需求放进去。毕竟系统做出来是要给人用的,不能只写简历里的CRUD。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术栈没有悬念,但选型理由要讲清楚
用Python和Django,其实没什么好纠结的。老年人社区互助平台在国内最流行的方案就是Python + Django + MySQL(或者PostgreSQL),前端可以服务端渲染配合少量Vue/jQuery,也可以用Django REST Framework做前后端分离。我这次选的是后端的Django 4.x + DRF + JWT认证,前端用原生HTML + Bootstrap + Vue3(CDN模式) + ECharts画健康图表,不搞node构建链,因为这种项目的主要维护人很可能是社区信息化中心的大哥大姐,搭建复杂前端工程会变成灾难。
为什么不用Flask?不是不能,而是这个项目有明显的“多模块、多角色、多状态”特征。Django自带的Admin、Auth、ORM、Session、CSRF、中间件、迁移机制,能省掉好几周重复造轮子的时间。比如权限上,我不想手写RBAC,直接用Django内置Group加自定义权限就能覆盖百分之九十的规则。再比如管理后台,虽然我说原生Admin不够用,但开发初期Admin看数据、查问题、补测试数据是真的香,可以边开发边验证业务逻辑。
这里给个更具体的选型理由表:
| 维度 | 选择 | 理由 |
|---|---|---|
| Web框架 | Django 4.2 LTS | 自带ORM/Admin/Auth,稳定且社区活跃 |
| API方案 | Django REST Framework | 前后端协作方便,序列化器省大量时间 |
| 数据库 | MySQL 8.0(生产)/ SQLite(本地) | 部署环境用MySQL,开发联调用SQLite切换成本低 |
| 前端 | Bootstrap + Vue3 CDN + ECharts | 不依赖Node构建,降低交付环境门槛 |
| 消息推送 | 阿里云短信服务 + 邮件 | 老人紧急情形下短信最可靠 |
| 部署 | Nginx + uWSGI/Daphne | 同步业务为主,异步只需简单场景时可临时加Celery |
有个争议点是:这种平台是否要上Celery做异步任务?我最终的答案是:先不上。主要异步任务无非是“服务结束后自动发送满意度问卷”“定时提醒家属老人今日未活动”,这类任务用Django管理命令加系统cron就能跑,没必要为了区分同步异步而引入消息队列。真要后续做实时语音、实时派单,再考虑Django Channels。项目上线后,我遇到过一次Redis挂掉导致全站短信通知积压的事故,排查下来发现我们只是用Redis做缓存和Celery broker,并不是核心存储;如果早期就把实时通信和核心业务绑在消息队列上,事故影响面会大得多。
3. 数据库表设计:把业务变成Django模型
数据库设计是这种平台项目的灵魂。前期我把表结构设计得足够清晰,后面写代码几乎是顺水推舟。下面重点讲五个容易出错或者容易被忽略的表:用户档案、老人档案、服务需求单、健康记录、服务订单。
3.1 用户与扩展档案:别直接改Django自带的User
很多新手喜欢在auth_user表上加字段,或者自己建一张user_profile表用OneToOne关联。前者的教训是迁移升级和部分第三方包默认查询会出问题;后者的问题是查询要多一次JOIN。我采用的是扩展AbstractUser,新增phone、user_type(老人/家属/志愿者/社工/管理员)、avatar、wx_openid等字段,并把USERNAME_FIELD设为手机号登录。
python复制from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
USER_TYPE_CHOICES = (
('elder', '老人'),
('family', '家属'),
('volunteer', '志愿者'),
('staff', '社区工作人员'),
('admin', '平台管理员'),
)
user_type = models.CharField('用户类型', max_length=20, choices=USER_TYPE_CHOICES, default='family')
phone = models.CharField('手机号', max_length=11, unique=True)
avatar = models.ImageField('头像', upload_to='avatars/', blank=True, null=True)
wx_openid = models.CharField('微信OpenID', max_length=64, blank=True, db_index=True)
last_active_at = models.DateTimeField('最后活跃时间', null=True, blank=True)
USERNAME_FIELD = 'phone'
REQUIRED_FIELDS = ['username']
注意一个细节:REQUIRED_FIELDS里保留username是因为Django源码中很多地方依赖username字段,虽然你用手机号登录,不填username会在createsuperuser时必填,所以干脆保留,不为难自己。
3.2 老人档案与健康信息:一对多还是一对一
老人与用户的关系不是简单的“老人就是用户”。一位老人可以没有自己的账号,全由家属维护;一个家属也可以绑定多位老人。所以需要单独建ElderProfile表,主键就是老人ID,用户外键改成创建者。这类设计在评审时常被问,提前想好理由就不慌:
python复制class ElderProfile(models.Model):
name = models.CharField('老人姓名', max_length=50)
gender = models.CharField('性别', max_length=10, choices=(('M', '男'), ('F', '女')))
birth_date = models.DateField('出生日期', null=True, blank=True)
id_card = models.CharField('身份证号', max_length=18, blank=True)
address = models.CharField('居住地址', max_length=255)
longitude = models.DecimalField('经度', max_digits=9, decimal_places=6, null=True, blank=True)
latitude = models.DecimalField('纬度', max_digits=9, decimal_places=6, null=True, blank=True)
emergency_contact = models.CharField('紧急联系人', max_length=50)
emergency_phone = models.CharField('紧急联系电话', max_length=20)
medical_history = models.TextField('既往病史', blank=True)
allergy = models.TextField('过敏史', blank=True)
creator = models.ForeignKey(User, on_delete=models.PROTECT, related_name='created_elders', verbose_name='创建者')
family_members = models.ManyToManyField(User, related_name='bound_elders', blank=True, verbose_name='已绑定的家属')
created_at = models.DateTimeField(auto_now_add=True)
longitude和latitude需要配套一个“服务范围”逻辑,后面会讲。老人档案并不是一次录入就完了,医学史会变化,所以我会建议把病史、过敏史单独拆历史表,但从实际使用看,社区录入人员根本懒得维护历史变化,保留上一次结果更新即可,真正做到需要完整时间轴的是下面的健康记录。
3.3 服务需求单:一张表用状态机区分生命周期
需求单是平台最重要的流转实体。常见误区是为了区分“代买”“助医”“陪聊”就建一堆子表,结果光映射关系就写了三百行。我的做法是主表加service_type字段,JSON字段存具体内容,后续要统计再反规范化一把:
python复制class ServiceRequest(models.Model):
STATUS_CHOICES = (
('pending', '待接单'),
('accepted', '已接单'),
('in_progress', '服务中'),
('completed', '已完成'),
('cancelled', '已取消'),
('expired', '已超时'),
)
elder = models.ForeignKey(ElderProfile, on_delete=models.CASCADE, verbose_name='老人')
creator = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='created_requests', verbose_name='发布人')
service_type = models.CharField('服务类型', max_length=20, choices=SERVICE_TYPES)
title = models.CharField('需求标题', max_length=100)
description = models.TextField('需求描述', blank=True)
address = models.CharField('服务地址', max_length=255, blank=True)
longitude = models.DecimalField('经度', max_digits=9, decimal_places=6, null=True, blank=True)
latitude = models.DecimalField('纬度', max_digits=9, decimal_places=6, null=True, blank=True)
expected_time = models.DateTimeField('期望服务时间')
priority = models.IntegerField('优先级', default=3, help_text='1-5,越高越紧急')
status = models.CharField('状态', max_length=20, choices=STATUS_CHOICES, default='pending', db_index=True)
volunteer = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='accepted_requests', verbose_name='接单志愿者')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
状态流转这里,我强烈建议把状态判断逻辑收敛到一个方法里,不要散落在各个视图,否则后面改需求会想哭:
python复制def can_transition(self, from_status, to_status, user):
if user.user_type != 'staff' and user != self.volunteer:
return False, '没有权限操作此订单'
allowed = {
'pending': {'accepted', 'cancelled', 'expired'},
'accepted': {'in_progress', 'cancelled'},
'in_progress': {'completed', 'cancelled'},
'completed': set(),
'cancelled': set(),
'expired': {'accepted'},
}
if to_status not in allowed.get(from_status, set()):
return False, f'状态不允许从{from_status}变为{to_status}'
return True, 'ok'
3.4 健康记录:单独一张表做趋势分析
血压、血糖、心率这类指标,关键是记录时间、记录人、数值和单位。设计上最大的坑是“一张表存所有指标”,导致每条记录都有十几个空字段。如果不想做EAV(实体-属性-值)模型,又希望扩展指标,可以折中为常用指标字段加一个JSON扩展:
python复制class HealthRecord(models.Model):
elder = models.ForeignKey(ElderProfile, on_delete=models.CASCADE, related_name='health_records')
record_time = models.DateTimeField('测量时间', db_index=True)
systolic = models.IntegerField('收缩压(mmHg)', null=True, blank=True)
diastolic = models.IntegerField('舒张压(mmHg)', null=True, blank=True)
heart_rate = models.IntegerField('心率(次/分)', null=True, blank=True)
blood_glucose = models.DecimalField('血糖(mmol/L)', max_digits=5, decimal_places=2, null=True, blank=True)
spo2 = models.IntegerField('血氧饱和度(%)', null=True, blank=True)
note = models.CharField('备注', max_length=255, blank=True)
source = models.CharField('数据来源', max_length=10, choices=(('device', '设备上传'), ('manual', '手动录入'), ('import', '历史导入')), default='manual')
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, verbose_name='记录人')
这里我踩过的坑是:血压记录中有收缩压但没有舒张压极为常见,数据校验不能一刀切。后来做前端表单,收缩压必填、舒张压选填,给护士录入留出容错。还有单位问题,血糖有人记录的是mg/dL,有人是mmol/L,早期统一不了,好在按国内社区标准,直接固定用mmol/L并在录入端做联动换算。
3.5 服务订单、评价与活动报名:别忘了级联策略
服务订单本质是绑定需求和志愿者,如果没有复杂计费,也可以不需要单独表,但我的习惯是单独建ServiceOrder,原因是在未来的回访、投诉跟进、绩效考核里会频繁查“谁在什么时间给谁干了什么”,最好有一张只读快照表。订单表把需求标题、服务地址、老人姓名等冗余保存下来,即使原需求被改了,历史也不会脏。
活动表和报名表就简单了,活动只需要标题、时间、地点、人数上限、状态;报名唯一约束是(activity, elder),同一个老人不能重复报名同一个活动。很多人会漏掉的字段是“是否需要家属陪同”,这个对线下活动组织很重要,建议在活动表加上need_escort布尔位。
4. 核心业务实现:服务闭环与派单逻辑
这个平台最核心的交互就是“需求发布—服务完成”。在这个环节里,Django的视图、DRF序列化器、消息通知怎么配合,我直接给出实战代码和当时的思考过程。
4.1 发布需求:校验、定位与短信通知
老人或者家属在前端填一张表单,后端接到的核心工作是:校验老人绑定关系、写需求单、然后给附近志愿者发通知。注意顺序,一定要先落库再发通知,不要把第三方服务挂在关键路径前面。
python复制# api/views/request_views.py
class ServiceRequestCreateView(APIView):
permission_classes = [IsAuthenticated]
def post(self, request):
data = request.data
elder_id = data.get('elder_id')
elder = get_object_or_404(ElderProfile, pk=elder_id)
# 校验:家属只能为自己绑定的老人发布
if not (request.user.user_type == 'staff'
or request.user == elder.creator
or request.user in elder.family_members.all()):
return Response({'detail': '无权为该老人发布需求'}, status=403)
serializer = ServiceRequestCreateSerializer(data=data)
serializer.is_valid(raise_exception=True)
req = serializer.save(creator=request.user)
# 记录日志、通知志愿者(异步化处理)
notify_nearby_volunteers.delay(req.id)
return Response(ServiceRequestDetailSerializer(req).data, status=201)
有些朋友会把“校验关系”写在FilterSet或ViewSet的queryset里,这样也不是不行,但直观度和可控性都不如视图内显式校验。代码就是要让人一眼看懂业务约束,而不是藏在某个通用机制里。
消息通知我用了DRF + Celery,但刚才说过,如果没接Celery,也可以用threading.Thread或者直接塞进一个本地队列,再搭配管理命令周期发送。真要说更稳妥,我在生产环境对短信这种强实时通知就是单独起了一个notify_processor.py脚本,通过Redis List做队列,短信通道独立于主应用,失败自动重试,绝不因为短信接口超时拖垮整个下单请求。
4.2 附近志愿者推荐:别一上来就上PostGIS
你要找附近能接单的志愿者,看网上很多方案上来就是GeoDjango的空间索引。其实对社区互助场景,只需要以老人定位为中心,遍历志愿者表和活动轨迹表,做简单的球面距离计算即可。全国其实没多少用户量,先拿经纬度算一个矩形,再精确过滤就能扛住万级活跃用户。
python复制import math
def haversine(lat1, lng1, lat2, lng2):
r = 6371.0
p1, p2 = math.radians(lat1), math.radians(lat2)
dphi = math.radians(lat2 - lat1)
dlambda = math.radians(lng2 - lng1)
a = math.sin(dphi / 2) ** 2 + math.cos(p1) * math.cos(p2) * math.sin(dlambda / 2) ** 2
return 2 * r * math.asin(math.sqrt(a))
# 查询附近5公里内的志愿者
candidates = User.objects.filter(
user_type='volunteer', is_active=True,
volunteer_profile__latitude__range=(min_lat, max_lat),
volunteer_profile__longitude__range=(min_lng, max_lng),
)
距离计算不是追求经纬度十一位小数,而是业务上能区分“同一个小区”和“隔壁街道”两种尺度就可以。上面SQLite/MySQL都能直接跑,虚拟环境里也不需要装一堆GIS库。
4.3 超时未接单的自动升级机制
需求挂太久没人接,就会让老人觉得平台无响应。我用状态机和定时任务做了一个升级规则:
- 0-10分钟,App内推送给社区常驻志愿者。
- 10-20分钟,短信通知社区工作人员,由系统调度人工客服介入电话联系。
- 30分钟以上仍然pending,状态自动改为expired,同时通知家属,请家属自行确认或改成线下求助。
这个规则在当前Django项目里是放在一个管理命令里的,配合cron每5分钟扫描一次:
bash复制# cron
*/5 * * * * cd /opt/community_health && /usr/bin/python3 manage.py escalate_requests
执行内容节选逻辑:
python复制def handle(self, *args, **options):
now = timezone.now()
expired_after = now - timedelta(minutes=30)
pending_requests = ServiceRequest.objects.filter(status='pending', created_at__lte=expired_after)
for req in pending_requests:
req.status = 'expired'
req.save(update_fields=['status', 'updated_at'])
notify_family_about_expired(req)
self.stdout.write(f'需求 {req.id} 已超时关闭')
这里提醒一个细节:很多定时任务会把这个扫描周期设成一分钟,没问题,但万一某次扫描任务因为网络阻塞重叠执行,会导致重复发短信。我给这个管理命令头上加了文件锁,也就是判断临时文件是否存在,存在则直接退出。
4.4 服务完成与评价回访
服务完成后,志愿者和老人家属都会收到一个评价链接,评价表跟需求单是一对一关系。当初做回访模块不是为了“打分好玩”,而是社区购买服务的验收依据。所以评价表必须包含几个主观的问题项目和客观的服务时长、到场时间等字段。
社区回访由社工在后台操作,状态字段设为未回访、已回访、已闭环。回访内容会生成到服务记录时间轴上。这个环节没有特别技术含量,但一定得做,直接决定了平台能不能从“工具”变成“服务监管平台”。
5. 高价值模块:健康档案与数据可视化
任何一个老年社区互助平台,如果只做了助餐接单,那就跟外卖app没区别。真正有壁垒和价值的是健康数据。把老人的日常健康数据管理起来,一方面给家属安全感,另一方面为社区慢病管理提供依据。
5.1 自动识别异常血压并触发提醒
我写了一段规则引擎(不吹牛,其实就是if-else):
python复制def analyze_blood_pressure(record):
messages = []
if record.systolic and record.systolic >= 180:
messages.append('收缩压≥180mmHg,处于高血压危象阈值,建议立即联系医生或拨打120')
elif record.systolic and record.systolic >= 140:
messages.append('收缩压偏高,建议复测并咨询社区医生')
if record.diastolic and record.diastolic >= 110:
messages.append('舒张压≥110mmHg,存在高血压急症风险,建议尽快就医')
# 存储异常标记,便于后台列表筛选
return messages
判断规则我全部写在后端Python里,而不是放进数据库。原因:一旦社区医生给出新标准,我可以直接发版而不需要做数据订正。但为了前端列表方便,我会在保存时计算出is_abnormal布尔字段,并同步到记录表。
5.2 健康趋势图:直接上ECharts
前端画折线图,我一般使用最简单的方式:后端往模板传一个按日期分组后的列表对象,页面用ECharts的line图展示。血压、血糖、心率可以三张图,也可以放到同一张图的多个yAxis,但考虑到老人家属观看场景,建议还是分开,清晰第一。
后端关键输出:
python复制# 获取近30天早晚血压平均值
rows = HealthRecord.objects.filter(
elder=elder,
record_time__gte=now - timedelta(days=30)
).values('record_time__date').annotate(
avg_sys=Avg('systolic'),
avg_dia=Avg('diastolic')
).order_by('record_time__date')
data = {
'dates': [r['record_time__date'].strftime('%m-%d') for r in rows],
'sys_values': [r['avg_sys'] for r in rows],
'dia_values': [r['avg_dia'] for r in rows],
}
return JsonResponse(data)
这里值得吐槽的一点:Django的values().annotate()写起来很舒服,但日期按UTC还是本地时间分组经常会差8小时,导致上午的血压被算到前一天。这个问题坑了我一天,后来统一在settings里设置USE_TZ = False,并且所有服务器时区调成Asia/Shanghai,才彻底消停。如果你必须保留USE_TZ = True,那就要注意用TruncDate配合timezone.localtime做转换。我在生产环境的方式是保持USE_TZ = True,但查询时显式使用TruncDate('record_time', tzinfo=ZoneInfo('Asia/Shanghai'))。
6. 页面与后台管理:既要好用,也要好管
这种平台的前端不能做太复杂。老人家属端主要用手机浏览器打开H5,社工端是PC网页。我没有单独开发App,更没有做小程序,一套响应式Web就够MVP。
6.1 模板渲染还是前后端分离
我在项目里同时用了两种模式:
- 社区后台和社工端:使用Django模板+少量原生JS。
- 家属端和志愿者端的核心操作:单独做了一套Vue3单文件式页面(直接CDN方式),通过DRF接口读写。
这样做的原因是,后台以表格、筛选、跳转为主,模板渲染对后端来说效率最高;前台操作有大量动态交互(发布时间选择、地图选点、分类筛选、实时校验),用独立API更顺手。
如果你也是自己一个人开发,不建议前后端彻底分离。你真的要为两个端写两套登录逻辑吗?Django模板渲染配合少量fetch调用,绝大多数场景都能满足,并且开发和部署成本至少低一半。
6.2 Django Admin还是Xadmin
如果你用原生Admin管理这个项目,会发现角色管理像一场灾难:客服人员可以在后台直接删用户、改套餐、看所有老人隐私,这绝对不允许。我的方案是:
- 用Django原生Admin创建账号和分配角色,不给客服使用。
- 在项目内部写了独立管理后台页面,路由以
/staff/开头,权限校验基于user.user_type和自定义权限编码。 - 列表页都要重写get_queryset,保证社工只能看到自己所属社区的老人,而不是全部。
后台页面的筛选器是刚需。需求单列表必须支持按服务类型、状态、紧急度、发布时间范围、所属社区筛选。Django原生的list_filter不支持“范围”双日期,不过有django-admin-rangefilter之类的第三方扩展,用起来还挺省心。后来我把后台交互改成Ajax提交筛选表单,并且把常用筛选条件存入Cookie,方便社工每天一进入就按同样的条件监控。
6.3 用DRF写家属端API的技巧
以“绑定老人”为例,家属通过输入老人档案上的“绑定码”关联老人。这个绑定码我设计成6位随机字符串,一次性有效,由社工在后台生成并打印给家属。绑定流程要用一个事务:校验绑定码有效、更新ElderProfile的family_members字段、标记绑定码已使用。
这种操作在传统Django模板里用POST表单就可实现,但在DRF里要注意把CSRF关掉或者在请求头带X-CSRFToken,我一般设置JWT认证,对前端就没有这个烦恼。日志记录必须做,谁在几点绑定了解绑了哪位老人,需要留痕,一是为了出纠纷时追溯,二是社区监管会查。
7. 部署、数据安全与常见问题排查
终于到了最容易劝退初学者的环节:部署。很多人本地跑得好好的,一上服务器就各种404、静态文件找不到、无法访问、发送短信超时。这里列几个我在这个项目里实际遇到的问题,都挺典型的。
7.1 部署形态与静态文件处理
环境:Ubuntu 22.04 + Nginx + uWSGI + MySQL 8 + Supervisor。
先强调:Django的DEBUG在生产环境必须关掉,否则会直接暴露你的settings信息。DEBUG关闭后,静态文件Django默认不再托管,所以必须执行python manage.py collectstatic,然后让Nginx指向这个静态文件目录。很多人404不是代码问题,是Nginx的root路径配错了。
bash复制location /static/ {
alias /opt/community_health/staticfiles/;
expires 7d;
}
uWSGI的socket文件权限一定要保证Nginx进程能访问,否则会报“connection refused”,原因是socket文件默认权限不对,我当时还傻乎乎排查了半天防火墙。
7.2 MySQL时区与表情符号入库问题
如果你在后端保存用户填写的备注、昵称带了emoji,但在MySQL里报错Incorrect string value,那是因为MySQL表字符集不是utf8mb4。Django项目settings里建议配置:
python复制DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'community_health',
'USER': 'your_user',
'PASSWORD': 'your_password',
'HOST': '127.0.0.1',
'PORT': '3306',
'OPTIONS': {
'charset': 'utf8mb4',
},
'CONN_MAX_AGE': 60,
}
}
老数据库表要改字符集:
sql复制ALTER TABLE <表名> CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
别问我为什么知道,上线第三天运营同事就贴了一个带自定义表情的备注,直接导致接口500。
7.3 短信通知在测试环境反复触发的坑
开发时用的短信服务是免费的测试签名,连发几条没事,但有一次我把需求单重复提交的逻辑没加防抖,导致一个老人反复点击了五次“一键求助”,短信平台瞬间给家属发了五条“求助”。这种问题如果发生在真线上服务,家属和社工都会被吓到。
解决方案:
- 前端按钮提交后置灰并带倒计时。
- 后端同一老人同一类型的需求,在5分钟内存在pending记录时,直接返回“你已提交相似需求”,而不是再次落库。
- 短信发送逻辑一律走独立队列,且每条短信带唯一业务ID,服务商端做幂等。
7.4 常见问题速查
| 现象 | 排查思路 | 解决方案 |
|---|---|---|
| 页面能打开但CSS全丢 | DEBUG关掉后静态文件未收集 | 执行collectstatic并检查Nginx alias路径 |
| 迁移时报字段冲突 | 之前用models改过字段但没迁移干净 | 检查django_migrations表,必要时重置开发库 |
| 内存占用上涨 | uWSGI进程数配置过高 | 按服务器内存调整processes和threads |
| 短信偶尔收不到 | 签名或模板未审核、手机号被拦截 | 确保用已审核模板,保留日志便于查 |
| 图片上传失败 | 服务器/media目录无写权限 | 给nginx用户或uwsgi用户授权 |
| ValueError: naive datetime | 时区不统一导致的代码异常 | 统一timezone.now()处理,避免datetime.now() |
7.5 数据备份与隐私合规
这种平台涉及老人姓名、身份证号、健康数据,属于敏感个人信息的范畴。每天凌晨两点全量备份MySQL,同时把上传的图片附件按日期归档。我对接的社区建议是日志保留180天,误删或数据异常时可以回溯。
我不建议也不允许有越过权限批量导出老人数据的“隐藏后门”,因为一旦发生数据泄露,平台的口碑全毁。Django的信号机制很适合做敏感操作审计,例如有社工导出数据时,记录操作者、时间和范围。当然这类操作要避免把用户原始敏感信息打大日志,日志里只留ID不落内容。
8. 开发完成后一个容易忽略的环节:测试和演示
无论你是交课程设计还是给社区交付,至少给出一份冒烟测试用例。我在这个项目里建了一个demo账号矩阵,覆盖四种角色,每个角色准备了两条测试数据。
- 老人:测试发布普通需求和紧急求助。
- 家属:测试绑定老人、查看健康趋势、收到异常提醒。
- 志愿者:测试接单、状态流转、完成服务。
- 社工:测试派单、回访、关闭需求。
自动化测试我用的Django自带的TestCase加DRF的APITestCase。重点不是覆盖100%分支,而是把状态机流转和权限隔离这两个核心风险锁死。记得当时给ServiceRequest的状态流转写了5个用例,后面加了新角色“社区护工”之后一度忘了更新权限,测试直接帮我把这个漏铜抓了出来。
如果你做的是毕业设计,答辩时老师很喜欢问“你的核心业务逻辑里最难的点是什么”。那你就可以重点讲状态机、超时升级、健康异常判断这三件有业务深度的事情,千万别只说“我用了Django”。懂行的人一听就知道你是不是真正写过完整项目。
现在回想这个老人社区健康互助平台,它确实没有什么高并发的技术亮点,难的是“把复杂社区业务模型理清楚,用Django优雅地实现角色权限,以及做到每一次救援和求助都有迹可循”。如果你正想复现类似项目,建议不要再网上找那些只带三张表的教学代码了,先把角色权限图谱和状态机画出来,再动手写,可能思路会清晰很多。
