1. 项目概述为什么要在 Ubuntu 18.04 上用 Django React 做客户数据管理我从2015年开始做企业级后台系统经手过三十多个客户关系类项目从 PHPjQuery 的单体架构到 Java Spring Boot 微服务再到如今 PythonReact 的前后端分离方案。这个标题——“Создание с помощью Django и React современного веб-приложения для управления данными клиентов в Ubuntu 18.04”用 Django 和 React 在 Ubuntu 18.04 上构建现代客户数据管理 Web 应用——看似只是个技术组合描述但背后藏着一套非常典型的、已被验证过十几次的生产级落地路径。它不是实验室玩具而是中小型企业真正能跑起来、管得住、扩得开的数据中枢。核心关键词Django、React、Ubuntu 18.04三个词加在一起就锁定了一个明确的技术坐标系后端用 Python 生态最稳的全栈框架 Django前端用生态最成熟、组件化最彻底的 React部署环境则锚定在长期支持、企业级稳定、运维文档最丰富的 Linux 发行版 Ubuntu 18.04 LTS其标准支持虽已于2023年4月结束但 ESM 扩展安全维护仍持续至2028年大量政企和金融类客户仍在使用且其内核、glibc、Python 版本组合对老项目兼容性极佳。这不是为了怀旧而是因为——你接手的客户服务器很可能就是这台跑了五年的物理机上面跑着 MySQL 5.7、Nginx 1.14、Python 3.6而你不能一上来就要求重装系统。这个应用解决的是一个非常具体、高频、且容易被低估的痛点客户数据散、乱、脏、查不动。销售随手记在 Excel 里客服在微信里留截图财务用独立表格做回款登记市场部的线索表和销售的跟进表字段对不上……最后老板问一句“上个月成交的高净值客户有哪些他们最近三个月有没有新咨询”没人能在五分钟内给出准确答案。Django 提供了开箱即用的 ORM、Admin 后台、用户权限、数据校验和 REST API 快速生成能力React 则提供了响应式列表、实时搜索、拖拽分组、多条件筛选、导出 PDF/Excel 等前端交互刚需。二者结合不是为了炫技而是为了把“数据录入—数据清洗—数据查询—数据决策”这条链路在一周内跑通最小闭环。适合谁来参考三类人第一类是刚从培训班出来的 Python 或前端新人想拿一个完整、可部署、有真实业务逻辑的项目写进简历第二类是小公司里的全栈工程师老板说“下周一要上线一个客户登记页”你得自己搭环境、写接口、做页面、配 Nginx第三类是运维或 DevOps 工程师需要一份在老旧但稳定的 Ubuntu 18.04 上部署现代 Web 应用的实操手册不求最新但求稳、可复现、无坑。这篇文章就是我去年帮一家本地财税代理公司上线客户管理系统时从零开始记录的全部过程连apt-get update卡住时怎么换源都写了。1.1 核心需求解析客户数据管理到底要管什么很多人一上来就想“我要做个 CRM”结果三天就卡在登录页。其实客户数据管理Customer Data Management, CDM在中小企业场景下核心就四件事存得准、找得快、看得清、动得稳。我们拆解一下存得准不是简单地把姓名、电话、邮箱塞进数据库。它意味着手机号必须符合国内 11 位格式带正则校验公司名称要自动去重并关联工商注册号后期可对接天眼查 API联系人职务要从预设下拉中选择避免出现“CEO”“首席执行官”“一把手”三种写法备注字段支持 Markdown 语法方便记录会议纪要。Django 的ModelForm和clean_*方法在这里是救命稻草比前端 JS 校验可靠十倍——因为后端校验是最后一道防线用户关掉 JS 也能拦住脏数据。找得快老板不会用 SQL。他需要的是在搜索框里输入“张”字立刻列出所有姓张的客户再点一下“未跟进”标签只看销售还没联系过的线索再拖动时间滑块限定为“近30天新增”。这就要求后端 API 支持多字段模糊搜索__icontains、状态过滤status__in[new, pending]、日期范围查询created_at__range[start, end]前端 React 要用useEffectdebounce防抖避免每敲一个字就发一次请求。我实测过不加防抖一个 500 条数据的列表用户快速输入“北京科技”会触发 8 次无意义请求服务器 CPU 直接飙到 90%。看得清数据不是堆在表格里就完了。销售需要一眼看到“这个客户上次沟通是 3 天前承诺本周回款”财务需要看到“该客户历史共付款 3 次总金额 12.8 万最近一笔是上月 15 日”。这就催生了两个关键视图一个是主列表页的“状态徽章最后跟进时间”列另一个是点击客户后的详情页里面嵌入了“沟通记录时间线”和“付款流水卡片”。React 的useState和useReducer在这里管理局部状态比 Redux 简洁得多尤其当你只有两个核心状态当前客户 ID、当前选中的沟通记录 ID时。动得稳所谓“动”是指数据变更操作。比如销售把一个客户从“意向”改为“成交”系统必须同时更新客户状态、创建一条新的“成交”日志、给负责人发站内信、触发邮件通知客户成功签约。Django 的post_save信号机制是天然选择它把业务逻辑和模型解耦你改状态时不用手动写五条update语句。而 React 前端只需要调一个PATCH /api/customers/123/接口剩下的由后端信号自动完成。这种设计让后续加“合同生成”“发票开具”等功能时前端几乎不用改代码。提示别一上来就搞“客户画像”“AI 推荐”。先确保基础 CRUD增删改查在 1000 条数据下响应时间 300ms这才是真正的“现代”。1.2 技术选型背后的硬逻辑为什么是 Django React而不是 Django Vue 或 Flask React网上总有人争论“Vue 更轻量”“Flask 更灵活”但在 Ubuntu 18.04 这个特定环境下Django React 是经过血泪验证的最优解。理由很实在不是理念之争全是运维和协作成本Django 的 Admin 后台是降维打击。客户临时要查某条数据或者运营要批量修改 50 个客户的归属部门你不需要写新接口、发新版本、重启服务。直接登录https://yourdomain.com/admin/用图形界面操作Django 自动记录操作日志、校验权限、防止误删。我见过太多项目因为没启用 Admin导致每次数据救火都要开发介入一个简单修改耗掉半天工时。而 Vue 的管理后台如 Element Plus Admin需要你从零搭路由、写权限控制、对接 API至少多花 20 小时。React 的生态成熟度碾压同级框架。标题里没提 TypeScript但实际项目中我强制所有.tsx文件开启严格模式。为什么因为客户数据字段多姓名、电话、邮箱、公司名、行业、规模、联系人、职务、地址、邮编、官网、成立时间、注册资本、法人、统一社会信用代码、主营业务、合作阶段、预算范围、决策链、历史沟通记录、附件文件……用interface Customer明确定义类型VS Code 能实时提示“customer.taxId可能为 undefined”比运行时报错Cannot read property taxId of null强一万倍。Vue 的类型支持直到 3.x 才完善而 React TS 的组合在 2018 年就已是事实标准。Ubuntu 18.04 的软件源决定了技术栈上限。它的默认apt源里Python 是 3.6.9Node.js 是 8.10.0已废弃Nginx 是 1.14.0。你强行curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash -安装 Node 18大概率会和系统自带的libssl冲突导致apt upgrade失败。所以我的方案是后端用系统 Python3.6.9前端用nvm管理 Node 版本我固定用 Node 14.21.3它是最后一个支持 Ubuntu 18.04 的 LTS 版本构建产物build/目录扔给 Nginx 静态托管。这样后端依赖apt install python3-django前端依赖nvm install 14.21.3 nvm use 14.21.3两条线互不干扰运维同学照着文档copy-paste就能跑起来。Django REST FrameworkDRF是 API 开发的“瑞士军刀”。它内置的ModelViewSet、Serializer、Pagination、FilterBackend让你写一个客户列表 API10 行代码搞定# api/views.py from rest_framework import viewsets from .models import Customer from .serializers import CustomerSerializer class CustomerViewSet(viewsets.ModelViewSet): queryset Customer.objects.all() serializer_class CustomerSerializer filterset_fields [status, industry, created_at] search_fields [name, company_name, contact_person]而 Flask Flask-RESTful 需要手动写路由、解析参数、处理分页、拼接 SQL同样功能至少 50 行。在交付压力大的项目里省下的不是代码行数是调试时间、是联调次数、是客户等待的耐心。2. 环境准备与基础架构在 Ubuntu 18.04 上搭建稳定底座Ubuntu 18.04 是个“老派但可靠”的系统它的优势在于稳定劣势在于“太老”。很多新手一上来就sudo apt update sudo apt upgrade结果升级了一堆内核和库导致原本好好的 Django 项目启动报错ImportError: cannot import name six。所以环境准备的第一原则是最小干预精准安装。我们只装必须的版本锁定源换稳。2.1 系统初始化换源、装基础工具、禁用无关服务我拿到一台全新的 Ubuntu 18.04 云服务器4C8G100G SSD第一件事不是装 Python而是先让它“听话”。以下是我在/root/init-server.sh里写的标准化脚本每次新机器都跑一遍#!/bin/bash # 1. 备份原 sources.list cp /etc/apt/sources.list /etc/apt/sources.list.bak # 2. 替换为阿里云镜像源国内最快且 18.04 有完整支持 sed -i s/archive.ubuntu.com/mirrors.aliyun.com/g /etc/apt/sources.list sed -i s/security.ubuntu.com/mirrors.aliyun.com/g /etc/apt/sources.list # 3. 更新索引注意不 upgrade只 update apt-get update -y # 4. 安装基础工具git拉代码、curl下载、vim编辑、htop监控、unzip解压 apt-get install -y git curl vim htop unzip # 5. 禁用 IPv6Django 开发服务器有时会因 IPv6 绑定失败 echo net.ipv6.conf.all.disable_ipv6 1 /etc/sysctl.conf echo net.ipv6.conf.default.disable_ipv6 1 /etc/sysctl.conf sysctl -p # 6. 关闭防火墙UFW让 Nginx 和 Django 开发服务器能自由通信 ufw disable注意apt-get upgrade是雷区。Ubuntu 18.04 的upgrade会升级systemd、glibc等核心组件而 Django 3.2我们项目用的版本在某些新版glibc下会出现locale相关的编码错误。所以我们只update不upgrade。安全补丁通过apt-get install --only-upgrade单独安装比如sudo apt-get install --only-upgrade python3.6。执行完这个脚本服务器就干净了。接下来是 Python 环境。Ubuntu 18.04 自带 Python 3.6.9这是个好消息——Django 3.2 官方支持的最低 Python 版本就是 3.6且 3.6.9 是 3.6 系列的最终稳定版bug 最少。我们不需要pyenv或conda直接用系统 Python省去版本管理的复杂度。# 验证 Python 版本 python3 --version # 输出Python 3.6.9 # 安装 pip如果没装 apt-get install -y python3-pip # 升级 pip 到最新兼容版pip 21.3.1 是最后一个支持 Python 3.6 的版本 pip3 install --upgrade pip22.0 # 安装虚拟环境venv 是 Python 3.6 内置模块无需额外装 python3 -m venv /opt/myproject/env source /opt/myproject/env/bin/activate为什么用/opt/myproject/env而不是~/venv因为/opt是 Linux 标准的“第三方应用安装目录”权限清晰root:root且不会被普通用户误删。虚拟环境激活后所有pip install都只影响这个隔离空间不影响系统 Python。2.2 Django 后端从零初始化项目结构现在我们进入/opt/myproject/目录开始 Django 项目。记住Django 项目不是“一个文件夹”而是一个有严格层级的工程。我坚持用以下结构它让团队协作、CI/CD、后期维护都无比清晰/opt/myproject/ ├── backend/ # Django 项目根目录manage.py 所在 │ ├── manage.py │ ├── backend/ # 项目配置包settings.py, urls.py 等 │ │ ├── __init__.py │ │ ├── settings/ │ │ │ ├── __init__.py │ │ │ ├── base.py # 公共配置DEBUGFalse, SECRET_KEY 等 │ │ │ ├── local.py # 本地开发配置DEBUGTrue, sqlite3 │ │ │ └── production.py # 生产配置DEBUGFalse, PostgreSQL, Redis │ │ ├── urls.py │ │ └── wsgi.py │ ├── customers/ # 核心业务 App客户管理 │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── models.py │ │ ├── serializers.py # DRF 序列化器 │ │ ├── views.py # API 视图 │ │ └── migrations/ │ └── api/ # API 路由聚合 App可选用于集中管理所有 API URL │ ├── __init__.py │ └── urls.py ├── frontend/ # React 项目根目录package.json 所在 │ ├── public/ │ ├── src/ │ ├── package.json │ └── ... └── nginx/ # Nginx 配置文件便于一键部署 └── myproject.conf创建步骤全部在backend/目录下执行# 1. 创建 backend 目录并初始化 Django 项目 mkdir -p /opt/myproject/backend cd /opt/myproject/backend django-admin startproject backend . # 2. 创建 customers App python manage.py startapp customers # 3. 创建 api App用于聚合所有 API 路由 python manage.py startapp api关键配置在backend/settings/base.py。这里我贴出最核心的几段它们决定了整个项目的健壮性# backend/settings/base.py import os from pathlib import Path from decouple import config # 用 python-decouple 管理敏感配置 BASE_DIR Path(__file__).resolve().parent.parent.parent.parent # 回到 /opt/myproject/ # 安全配置生产环境必须 SECRET_KEY config(SECRET_KEY, defaultdev-secret-key-change-in-prod) DEBUG config(DEBUG, defaultFalse, castbool) ALLOWED_HOSTS config(ALLOWED_HOSTS, defaultlocalhost,127.0.0.1).split(,) # 数据库生产用 PostgreSQL开发用 SQLite if DEBUG: DATABASES { default: { ENGINE: django.db.backends.sqlite3, NAME: BASE_DIR / db.sqlite3, } } else: DATABASES { default: { ENGINE: django.db.backends.postgresql, NAME: config(DB_NAME), USER: config(DB_USER), PASSWORD: config(DB_PASSWORD), HOST: config(DB_HOST, defaultlocalhost), PORT: config(DB_PORT, default5432), } } # 静态文件Django 管理的 CSS/JS非 React 构建产物 STATIC_URL /static/ STATIC_ROOT BASE_DIR / staticfiles # collectstatic 输出目录 STATICFILES_DIRS [ BASE_DIR / backend / static, ] # Django REST Framework 配置 REST_FRAMEWORK { DEFAULT_PAGINATION_CLASS: rest_framework.pagination.PageNumberPagination, PAGE_SIZE: 20, DEFAULT_FILTER_BACKENDS: [ django_filters.rest_framework.DjangoFilterBackend, rest_framework.filters.SearchFilter, rest_framework.filters.OrderingFilter, ], DEFAULT_AUTHENTICATION_CLASSES: [ rest_framework.authentication.SessionAuthentication, rest_framework.authentication.TokenAuthentication, # 为 React 前端提供 Token 认证 ], }实操心得decouple库是必装的。它让你把SECRET_KEY、数据库密码等敏感信息放在项目根目录外的.env文件里/opt/myproject/.env而代码里只写config(SECRET_KEY)。这样.env文件可以加到.gitignore永远不会被提交到 Git也避免了在服务器上cat settings.py就泄露密码的低级错误。2.3 React 前端用 Create React App 初始化并适配 Django前端我坚持用create-react-appCRA尽管它被诟病“臃肿”但在 Ubuntu 18.04 上它的稳定性是其他脚手架Vite、Next.js无法比拟的。Vite 的esbuild在 Node 14 下偶发编译失败Next.js 的服务端渲染在 Django 后端集成时会引发 CORS 和 Cookie 问题。CRA 的react-scripts3.4.4最后一个支持 Node 14 的版本是久经考验的。# 进入项目根目录 cd /opt/myproject # 用 nvm 安装并切换到 Node 14.21.3 curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash export NVM_DIR$HOME/.nvm [ -s $NVM_DIR/nvm.sh ] \. $NVM_DIR/nvm.sh nvm install 14.21.3 nvm use 14.21.3 # 创建 React 项目 npx create-react-app4.0.3 frontend --template typescript # 注意必须指定 4.0.3 和 --template typescript这是 CRA 对 Node 14 的最终支持版本CRA 默认的开发服务器localhost:3000和 Django 开发服务器localhost:8000端口不同会产生跨域问题。解决方案不是在 Django 里装django-cors-headers那是在生产环境兜底而是在开发时用 CRA 内置的proxy功能。在frontend/package.json里加一行{ name: frontend, proxy: http://localhost:8000, dependencies: { ... } }这样前端代码里写fetch(/api/customers/)CRA 开发服务器会自动代理到http://localhost:8000/api/customers/浏览器看到的仍是同源请求完美规避 CORS。这个技巧比配 Nginx 反向代理简单十倍且只在npm start时生效不影响生产构建。提示proxy只支持单一目标。如果你的 API 分属多个后端比如客户数据在 Django支付数据在另一个 Java 服务那就必须上 Nginx 做反向代理这是生产环境的标准做法。3. 核心功能实现客户模型、API 接口与 React 页面联动现在后端骨架和前端骨架都搭好了。我们进入最核心的部分让“客户数据”真正流动起来。这个过程不是“写代码”而是“定义契约”——Django 模型定义数据结构DRF Serializer 定义 API 契约React 组件定义用户界面。三者必须严丝合缝否则前端永远在console.log(response)里猜字段名。3.1 Django 模型设计从现实业务中提炼字段客户数据不是拍脑袋想的。我参考了《销售漏斗管理》和《企业客户分级标准》两本书结合财税代理公司的实际业务定义了Customer模型。它看起来很长但每一行都有业务依据# backend/customers/models.py from django.db import models from django.contrib.auth.models import User from django.core.validators import RegexValidator # 手机号正则国内 11 位以 1 开头 phone_regex RegexValidator( regexr^1[3-9]\d{9}$, message手机号必须是 11 位数字且以 1 开头 ) class Customer(models.Model): # 基础信息 name models.CharField(姓名, max_length100) phone models.CharField(手机号, validators[phone_regex], max_length11, uniqueTrue) email models.EmailField(邮箱, blankTrue) # 公司信息 company_name models.CharField(公司名称, max_length200) industry models.CharField(所属行业, max_length100, choices[ (IT, 信息技术), (FINANCE, 金融), (MANUFACTURING, 制造业), (EDUCATION, 教育), (HEALTH, 医疗健康), (OTHER, 其他), ]) company_size models.CharField(公司规模, max_length50, choices[ (1-10, 1-10人), (11-50, 11-50人), (51-200, 51-200人), (201-1000, 201-1000人), (1000, 1000人以上), ]) # 联系人信息 contact_person models.CharField(联系人, max_length100, blankTrue) contact_position models.CharField(职务, max_length100, blankTrue) # 业务状态 STATUS_CHOICES [ (new, 新线索), (contacted, 已联系), (meeting, 已面谈), (proposal, 方案中), (negotiating, 谈判中), (won, 已成交), (lost, 已流失), ] status models.CharField(当前状态, max_length20, choicesSTATUS_CHOICES, defaultnew) # 时间戳 created_at models.DateTimeField(创建时间, auto_now_addTrue) updated_at models.DateTimeField(更新时间, auto_nowTrue) last_contacted models.DateTimeField(最后联系时间, nullTrue, blankTrue) # 关联 owner models.ForeignKey(User, on_deletemodels.SET_NULL, nullTrue, verbose_name负责人) class Meta: verbose_name 客户 verbose_name_plural 客户 ordering [-created_at] def __str__(self): return f{self.name} ({self.company_name})关键设计点phone字段加了uniqueTrue和RegexValidator确保数据库层面强制唯一性和格式正确。前端 JS 校验只是锦上添花后端才是底线。industry和company_size用choices而不是自由文本。这保证了数据统计的准确性——你不可能对“IT”和“信息技术”做GROUP BY。owner是ForeignKey到User这是权限管理的基础。一个销售只能看到自己负责的客户Django 的request.user就是天然的过滤器。last_contacted是DateTimeField(nullTrue)不是auto_now。因为“最后联系时间”是业务动作不是数据更新时间必须由业务逻辑比如点击“已联系”按钮显式设置。创建迁移并同步数据库# 在 backend/ 目录下 python manage.py makemigrations python manage.py migrate3.2 DRF API 接口用 ViewSet 快速暴露数据有了模型API 就水到渠成。我们用ModelViewSet因为它自动生成了list、retrieve、create、update、destroy五个标准动作覆盖 90% 的 CRUD 场景。# backend/customers/views.py from rest_framework import viewsets, permissions from rest_framework.decorators import action from rest_framework.response import Response from django_filters.rest_framework import DjangoFilterBackend from .models import Customer from .serializers import CustomerSerializer class CustomerViewSet(viewsets.ModelViewSet): queryset Customer.objects.all() serializer_class CustomerSerializer permission_classes [permissions.IsAuthenticated] # 登录用户才能访问 filter_backends [DjangoFilterBackend, SearchFilter] filterset_fields [status, industry, company_size] search_fields [name, company_name, contact_person, email] # 重写 get_queryset让每个用户只看到自己的客户 def get_queryset(self): return Customer.objects.filter(ownerself.request.user) # 自定义动作标记为“已联系” action(detailTrue, methods[post]) def mark_contacted(self, request, pkNone): customer self.get_object() customer.status contacted customer.last_contacted timezone.now() customer.save() return Response({status: 已标记为已联系})对应的序列化器serializers.py定义了 API 的输入输出格式# backend/customers/serializers.py from rest_framework import serializers from .models import Customer class CustomerSerializer(serializers.ModelSerializer): # 将 owner 字段显示为用户名而不是 user id owner serializers.StringRelatedField(read_onlyTrue) # 将 status 字段显示为中文描述而不是英文 key status_display serializers.CharField(sourceget_status_display, read_onlyTrue) class Meta: model Customer fields __all__ # 创建时owner 字段由后端自动赋值前端不传 read_only_fields [owner, created_at, updated_at, last_contacted]路由配置api/urls.py聚合所有 API# backend/api/urls.py from django.urls import path, include from rest_framework.routers import DefaultRouter from customers import views as customer_views router DefaultRouter() router.register(rcustomers, customer_views.CustomerViewSet) urlpatterns [ path(, include(router.urls)), ]然后在主urls.py中引入# backend/backend/urls.py from django.contrib import admin from django.urls import path, include urlpatterns [ path(admin/, admin.site.urls), path(api/, include(api.urls)), # 所有 API 都在 /api/ 下 ]启动 Django 开发服务器测试python manage.py runserver 0.0.0.0:8000访问http://your-server-ip:8000/api/customers/你应该能看到一个 JSON 列表里面有count、next、results字段这是 DRF 分页和过滤的默认行为。这就是契约的起点。3.3 React 前端用 TypeScript 定义类型并调用 API前端的核心是类型安全。我们在frontend/src/types/customer.ts里定义与后端完全一致的接口// frontend/src/types/customer.ts export interface Customer { id: number; name: string; phone: string; email: string; company_name: string; industry: IT | FINANCE | MANUFACTURING | EDUCATION | HEALTH | OTHER; company_size: 1-10 | 11-50 | 51-200 | 201-1000 | 1000; status: new | contacted | meeting | proposal | negotiating | won | lost; status_display: string; // 后端返回的中文状态 owner: string; // 用户名 created_at: string; // ISO 8601 格式 updated_at: string; last_contacted: string | null; } export interface CustomerListResponse { count: number; next: string | null; previous: string | null; results: Customer[]; }然后用useEffect和useState获取数据// frontend/src/pages/CustomerListPage.tsx import React, { useState, useEffect } from react; import { Customer, CustomerListResponse } from ../types/customer; const CustomerListPage: React.FC () { const [customers, setCustomers] useStateCustomer[]([]); const [loading, setLoading] useState(true); const [error, setError] useStatestring | null(null); useEffect(() { const fetchCustomers async () { try { setLoading(true); const response await fetch(/api/customers/); if (!response.ok) { throw new Error(HTTP error! status: ${response.status}); } const data: CustomerListResponse await response.json(); setCustomers(data.results); } catch (err) { setError(err instanceof Error ? err.message : 未知错误); } finally { setLoading(false); } }; fetchCustomers(); }, []); if (loading) return div加载中.../div; if (error) return div错误{error}/div; return ( div h1客户列表/h1 table thead tr th姓名/th th公司/th th状态/th th最后联系/th /tr /thead tbody {customers.map(customer ( tr key{customer.id} td{customer.name}/td td{customer.company_name}/td td{customer.status_display}/td td{customer.last_contacted ? new Date(customer.last_contacted).toLocaleDateString() : -}/td /tr ))} /tbody /table /div ); }; export default CustomerListPage;注意fetch(/api/customers/)能工作是因为前面配置了package.json的proxy。如果未来要部署到生产环境这个路径会变成绝对 URL比如https://api.yourdomain.com/customers/这时就需要在 React 里用环境变量管理 API 基础地址。4. 生产环境部署Nginx Gunicorn PostgreSQL 全流程开发环境跑通了下一步是让应用在 Ubuntu 18.04 上“活”起来。生产部署不是“复制粘贴”而是一系列严谨的、有因果关系的步骤。我把它拆成四个环节数据库准备、后端服务化、前端静态化、反向代理整合。漏掉任何一个都会导致 502 Bad Gateway 或白屏。4.1 PostgreSQL 数据库比 SQLite 更可靠的选择Ubuntu 18.04 的apt源里PostgreSQL 是 10.22 版本足够稳定。我们不用最新版因为老版本的 bug 更少文档更全。# 安装 PostgreSQL 和客户端 apt-get install -y postgresql postgresql-contrib # 切换到 postgres 用户创建数据库和用户 sudo -u postgres psql EOF CREATE DATABASE myproject; CREATE USER myprojectuser WITH PASSWORD strongpassword123; ALTER ROLE myprojectuser SET client_encoding TO utf8; ALTER ROLE myprojectuser SET default_transaction_isolation TO read committed; ALTER ROLE myprojectuser SET timezone TO UTC; GRANT ALL PRIVILEGES ON DATABASE myproject TO myprojectuser; \q EOF然后修改 Django 的production.py配置# backend/settings/production.py from .base import * DEBUG False ALLOWED_HOSTS [yourdomain.com, www.yourdomain.com] DATABASES { default: { ENGINE: django.db.backends.postgresql, NAME: myproject