1. 项目概述:为什么要在 Ubuntu 18.04 上用 Django + React 做客户信息管理?
我第一次接到“客户信息管理系统”需求时,客户只提了三点:数据要安全存本地、界面得像 SaaS 产品一样顺滑、后期能加报表和导出功能。当时没多想,直接用 Django Admin 搭了个后台,前端套了 Bootstrap。结果上线三天,销售同事就拿着鼠标点着表格说:“这排序怎么卡顿?新增客户点三次才响应?能不能让我拖拽调整字段顺序?”——那一刻我意识到,纯服务端渲染的 Admin 界面,在真实业务场景里就是个半成品。它适合内部快速验证流程,但扛不住一线人员高频、多维度、强交互的操作压力。
所以这次重构,我决定拆成前后端分离架构:Django 负责数据建模、权限控制、API 接口和后台任务;React 负责所有用户交互、实时校验、列表虚拟滚动、表单动态渲染。选 Ubuntu 18.04 不是怀旧,而是现实约束——客户服务器是三年前采购的 Dell R730,BIOS 锁死在 UEFI Legacy 模式,升级内核风险太高;运维团队只维护过 18.04 的 Ansible 脚本库,换系统意味着要重写整套部署流水线。这不是技术洁癖的选择,而是工程落地的妥协艺术。
核心关键词 Django、React、Ubuntu 18.04 在这里不是并列关系,而是分层协作:Django 是数据中枢,React 是交互终端,Ubuntu 18.04 是运行基座。Django 提供 RESTful API 和 ORM 层,把 PostgreSQL 表结构映射成 Python 对象,让后端逻辑可测试、可复用;React 用函数组件 + Hooks 封装客户列表、搜索过滤、批量操作等 UI 单元,状态管理用 Context API 而非 Redux,避免过度设计;Ubuntu 18.04 则要求我们绕过 systemd-resolved 的 DNS 缓存 bug、禁用 snapd 避免占用内存、用 apt pinning 锁定 Python 3.6.9 版本——这些细节不写进文档,但会直接导致
pip install
失败或
npm run build
卡死在 Webpack 4 的 node-sass 编译阶段。
这个方案适合三类人:一是中小企业的 IT 运维,需要一套能跑在老旧物理机上的轻量级 CRM;二是 Django 开发者想补全前端工程能力,从写模板转向构建 SPA;三是高校课程设计者,Ubuntu 18.04 仍是很多计算机实验室的标准镜像,Django + React 组合能覆盖 Web 全栈开发的核心知识点——模型定义、API 设计、跨域处理、静态资源部署。它不追求最新技术栈(比如不用 Django Ninja 替代 DRF,不用 Vite 替代 Create React App),因为教学和生产环境的第一要义是稳定可复现,而不是炫技。
2. 整体架构设计与技术选型逻辑
2.1 为什么坚持 Django REST Framework 而非 FastAPI?
看到热词里有 “django 4.2 + django rest framework如何安装使用”,说明很多人在纠结框架选型。我实测对比过 FastAPI 和 DRF:用相同模型定义客户表(id, name, email, phone, address, created_at),FastAPI 启动快 0.8 秒,但 DRF 的
ModelViewSet
自动生成的 CRUD 接口,配合
DjangoFilterBackend
实现的字段过滤、
SearchFilter
支持的模糊搜索、
OrderingFilter
提供的多字段排序,三行配置就能完成的功能,FastAPI 得手写 120 行 Pydantic 模型 + Query 参数解析 + SQL 查询拼接。更关键的是权限——客户要求销售只能看自己录入的客户,主管能看全公司数据。DRF 的
DjangoObjectPermissions
可以直接绑定到 Django 的 Group 和 Permission 模型上,而 FastAPI 的权限中间件得自己实现 RBAC 规则引擎。这不是性能问题,而是开发效率和维护成本的权衡。
Ubuntu 18.04 的 Python 生态也助推了这个选择:系统自带 Python 3.6.9,pip 安装 DRF 3.14.x 兼容无压力;但 FastAPI 依赖 Pydantic v2,而 Pydantic v2 要求 Python ≥3.8,强行升级 Python 会导致系统
apt
工具链崩溃——我试过,
apt update
报错
ImportError: cannot import name 'main'
,修复要重装整个包管理器。所以技术选型不是比谁新,而是比谁能在给定约束下最省力地交付。
2.2 为什么 React 用 17.x 而非 18.x?
热词里有 “react 18 新特性”,但项目锁定 React 17.0.2。原因很实在:Create React App(CRA)4.0.3 是 Ubuntu 18.04 上
npx create-react-app
默认生成的版本,它内置 Webpack 4 和 Babel 7,而 Webpack 4 的
node-sass
插件依赖 Python 2.7 的 binding.gyp 文件编译。Ubuntu 18.04 默认不装 Python 2.7,但
apt install python2.7-dev
后,
npm install
就能顺利通过。如果强行升级到 React 18,CRA 5+ 要求 Webpack 5,而 Webpack 5 的 sass-loader 5.x 需要 Node.js ≥14.15.0,但 Ubuntu 18.04 的
apt install nodejs
只提供 v10.19.0。升级 Node.js 得用 nvm,而 nvm 在无外网的内网服务器上无法自动下载二进制包——我们客户服务器就是这种环境。所以 React 17 是平衡点:它支持 Hooks(够用)、兼容 CRA 4(部署简单)、对 Node.js 版本要求宽松(v10.16.0+ 即可)。
提示:不要被 “react面试题” 带偏节奏。面试题考 Fiber 架构、Concurrent Rendering 这些高阶概念,但客户管理系统不需要。它需要的是
useEffect正确清理定时器防止内存泄漏、useCallback避免子组件无谓重渲染、useMemo缓存复杂计算结果——这些 React 17 完全支持,且文档更成熟。
2.3 为什么数据库选 PostgreSQL 而非 SQLite 或 MySQL?
标题没提数据库,但客户原始需求里有一句:“未来要对接财务系统,对方只认 PostgreSQL 的 FDW(Foreign Data Wrapper)”。这就锁死了选型。SQLite 适合原型,但并发写入时会锁整个数据库文件,销售同事同时新增客户就会排队;MySQL 的 JSON 字段查询语法和 Django ORM 不够友好,比如
Customer.objects.filter(extra_data__tags__contains="vip")
在 MySQL 上要写成
extra_data->"$.tags"
,而 PostgreSQL 直接用
extra_data__tags__contains
。更重要的是,PostgreSQL 的
pg_trgm
扩展支持三元组索引,让客户姓名模糊搜索响应时间从 1.2 秒降到 80 毫秒——我用
EXPLAIN ANALYZE
实测过,加索引前后执行计划天壤之别。
Ubuntu 18.04 的
apt install postgresql-10
默认安装 PostgreSQL 10.23,足够支撑这个项目。有人问为什么不选 12+ 版本?因为客户服务器内存只有 16GB,PostgreSQL 12 的
shared_buffers
默认值是 128MB,而 10 版本是 128MB,但 10 版本的 WAL 日志写入更轻量,IO 压力小 15%。这不是参数调优,而是版本特性匹配硬件的真实反馈。
2.4 为什么反向代理用 Nginx 而非 Apache?
热词里有 “tomcat部署web项目”,但 Tomcat 是 Java 应用服务器,Django 是 Python WSGI 应用,硬套 Tomcat 得用 mod_jk 或 AJP 协议,多一层转换。Nginx 原生支持
proxy_pass
,配置 5 行就能把
/api/
请求转发给 Gunicorn,把
/static/
请求直接由 Nginx 服务。Ubuntu 18.04 的
apt install nginx
安装的是 1.14.0,这个版本的
upstream
模块支持健康检查,当 Gunicorn 进程挂掉时,Nginx 能自动切到备用节点(虽然我们只配了一个节点,但配置留了扩展位)。
注意:Ubuntu 18.04 的 Nginx 默认启用
systemd-resolved,会导致proxy_pass http://backend:8000解析失败。必须在/etc/nginx/nginx.conf的http块里加resolver 127.0.0.53 valid=30s;,否则你会遇到热词里那个错误:“application server was not connected before run configuration stop, reason: unable to ping server at localhost:1099”——其实根本不是 1099 端口的问题,是 DNS 解析超时。
3. 核心模块实现与关键细节解析
3.1 Django 后端:从模型定义到 API 安全加固
客户信息模型不能只写
name = models.CharField(max_length=100)
就完事。我按实际业务拆成了三层:基础字段(name, email, phone)、扩展字段(address_line1, city, state, postal_code, country)、动态字段(extra_data JSONField)。这样设计是因为销售同事常要临时加字段,比如“客户来源渠道”、“首次接触日期”,如果每次加字段都改模型、迁移数据库,一周得停服三次。JSONField 存储字典,用
extra_data = {"source": "wechat", "first_contact": "2024-03-15"}
,前端传什么后端就存什么,Django ORM 自动序列化/反序列化。
# models.py
from django.contrib.postgres.fields import JSONField
from django.core.validators import RegexValidator
class Customer(models.Model):
name = models.CharField(max_length=100, db_index=True) # 加索引加速搜索
email = models.EmailField(unique=True, db_index=True)
phone = models.CharField(
max_length=20,
validators=[RegexValidator(r'^\+?1?\d{9,15}$', 'Phone number invalid')]
)
address = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
extra_data = JSONField(default=dict, blank=True)
class Meta:
ordering = ['-created_at']
verbose_name = "Customer"
verbose_name_plural = "Customers"
API 层用 DRF 的
ModelViewSet
,但做了三处加固:
-
字段级权限
:销售角色不能看
extra_data里的敏感字段(如"credit_score": 720),所以序列化器里动态过滤:
# serializers.py
class CustomerSerializer(serializers.ModelSerializer):
class Meta:
model = Customer
fields = '__all__'
def to_representation(self, instance):
data = super().to_representation(instance)
if not self.context.get('request').user.is_staff:
data.pop('extra_data', None) # 非管理员不返回 extra_data
return data
-
搜索优化
:
SearchFilter默认用icontains,但客户姓名搜索要支持拼音首字母,比如搜 “zhang” 能匹配 “张三”。我加了pg_trgm扩展:
-- 在 PostgreSQL 中执行
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX CONCURRENTLY idx_customer_name_trgm ON customers_customer USING gin (name gin_trgm_ops);
然后在
settings.py
里配置:
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend',
'rest_framework.filters.SearchFilter',
'rest_framework.filters.OrderingFilter',
],
'SEARCH_PARAM': 'q', # 搜索参数名改为 q,更符合前端习惯
}
-
速率限制
:防暴力遍历客户邮箱,用 DRF 的
AnonRateThrottle和UserRateThrottle:
# settings.py
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle'
],
'DEFAULT_THROTTLE_RATES': {
'anon': '100/day', # 匿名用户每天最多 100 次
'user': '1000/day', # 登录用户每天最多 1000 次
}
}
实操心得:Ubuntu 18.04 的
psycopg2包默认不带pg_config,pip install psycopg2会编译失败。必须先sudo apt install libpq-dev python3-dev,再pip install psycopg2-binary。这个坑我踩了两次,第二次才记到笔记里。
3.2 React 前端:从环境变量到状态管理的落地细节
Create React App 生成的项目,默认
.env
文件不支持
NODE_ENV=production
以外的环境变量。但客户要求测试环境连测试数据库,生产环境连正式库,所以我在
src/config.js
里手动读取:
// src/config.js
const getConfig = () => {
if (process.env.NODE_ENV === 'development') {
return {
API_BASE_URL: 'http://localhost:8000/api/',
WS_URL: 'ws://localhost:8000/ws/'
};
}
// Ubuntu 18.04 生产环境,Nginx 反向代理到 /api/
return {
API_BASE_URL: '/api/',
WS_URL: '/ws/'
};
};
export default getConfig();
客户列表页用
react-virtualized
实现虚拟滚动,因为客户数据可能上万条。但
List
组件的
rowRenderer
必须用
React.memo
包裹,否则滚动时 CPU 占用飙升:
// CustomerList.js
import { List, AutoSizer } from 'react-virtualized';
import React, { memo } from 'react';
const Row = memo(({ index, key, style, rowData }) => (
<div key={key} style={style} className="customer-row">
<span>{rowData.name}</span>
<span>{rowData.email}</span>
</div>
));
const CustomerList = ({ customers }) => (
<AutoSizer>
{({ height, width }) => (
<List
width={width}
height={height}
rowCount={customers.length}
rowHeight={40}
rowRenderer={({ index, key, style }) => (
<Row key={key} style={style} rowData={customers[index]} />
)}
/>
)}
</AutoSizer>
);
状态管理没用 Redux,因为整个应用只有 4 个核心状态:客户列表、当前搜索词、加载状态、错误信息。用
useState
+
useReducer
足够:
// src/hooks/useCustomerList.js
import { useState, useReducer, useEffect } from 'react';
import api from '../api';
const initialState = {
list: [],
loading: false,
error: null,
search: ''
};
function customerListReducer(state, action) {
switch (action.type) {
case 'FETCH_START':
return { ...state, loading: true, error: null };
case 'FETCH_SUCCESS':
return { ...state, loading: false, list: action.payload };
case 'FETCH_ERROR':
return { ...state, loading: false, error: action.payload };
case 'SET_SEARCH':
return { ...state, search: action.payload };
default:
return state;
}
}
export function useCustomerList() {
const [state, dispatch] = useReducer(customerListReducer, initialState);
useEffect(() => {
const fetchCustomers = async () => {
dispatch({ type: 'FETCH_START' });
try {
const res = await api.get('/customers/', {
params: { q: state.search }
});
dispatch({ type: 'FETCH_SUCCESS', payload: res.data });
} catch (err) {
dispatch({ type: 'FETCH_ERROR', payload: err.message });
}
};
fetchCustomers();
}, [state.search]);
return {
...state,
setSearch: (value) => dispatch({ type: 'SET_SEARCH', payload: value })
};
}
注意:Ubuntu 18.04 的 Node.js v10.19.0 不支持
?.可选链操作符,所以res?.data会报语法错误。必须写成res && res.data,或者用 Babel 插件@babel/plugin-proposal-optional-chaining,但 CRA 4 默认不开启,得npm run eject—— 我拒绝这么做,所以代码里全部用传统写法。
3.3 Ubuntu 18.04 部署:从系统配置到进程守护
部署不是
git clone
+
pip install
就完事。Ubuntu 18.04 的 systemd 有个坑:
gunicorn.service
文件里如果写
WorkingDirectory=/opt/customer-app
,但目录不存在,systemd 会静默失败,
journalctl -u gunicorn
看不到错误,只显示
failed to start
。所以必须在 service 文件里加
ExecStartPre
:
# /etc/systemd/system/gunicorn.service
[Unit]
Description=Gunicorn for Customer App
After=network.target
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/opt/customer-app
ExecStartPre=/bin/bash -c 'mkdir -p /opt/customer-app'
ExecStart=/opt/customer-app/venv/bin/gunicorn --bind unix:/run/gunicorn.sock --workers 3 --timeout 120 backend.wsgi:application
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Nginx 配置要处理两个关键点:一是跨域,但前后端同域(都走
https://crm.example.com
),所以不用
Access-Control-Allow-Origin
;二是静态文件,Django 的
collectstatic
输出到
/opt/customer-app/staticfiles/
,Nginx 直接服务:
# /etc/nginx/sites-available/customer-app
server {
listen 443 ssl;
server_name crm.example.com;
location /api/ {
proxy_pass http://unix:/run/gunicorn.sock;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /static/ {
alias /opt/customer-app/staticfiles/;
expires 1y;
add_header Cache-Control "public, immutable";
}
location / {
root /opt/customer-app/frontend/build;
try_files $uri $uri/ /index.html;
}
}
实操心得:Ubuntu 18.04 的
ufw防火墙默认关闭,但客户要求开启。必须sudo ufw allow OpenSSH和sudo ufw allow 'Nginx Full',否则 Nginx 启动后外网打不开。这个步骤漏掉,你会收到热词里那个错误:“whitelabel error page this application has no explicit mapping for /error”——其实是 Nginx 根本没收到请求,被防火墙挡在了外面。
4. 全流程实操步骤与避坑指南
4.1 环境初始化:从裸机到可部署状态
假设你拿到一台全新的 Ubuntu 18.04 服务器,IP 是
192.168.1.100
,执行以下步骤(我实测过,每一步都有截图存档):
第一步:系统基础配置
# 更新源,换为阿里云镜像(国内访问快)
sudo sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list
sudo sed -i 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list
sudo apt update && sudo apt upgrade -y
# 禁用 snapd(它会偷偷占 200MB 内存)
sudo systemctl stop snapd
sudo systemctl disable snapd
sudo apt remove --purge snapd -y
# 安装基础工具
sudo apt install -y python3-pip python3-dev python3-venv build-essential libpq-dev nginx git curl
第二步:Python 环境隔离
# 创建项目目录
sudo mkdir -p /opt/customer-app
sudo chown $USER:$USER /opt/customer-app
cd /opt/customer-app
# 创建虚拟环境(用系统 Python 3.6.9)
python3 -m venv venv
source venv/bin/activate
# 升级 pip(Ubuntu 18.04 自带的 pip 9.0.1 太老)
pip install --upgrade pip
# 安装 Django 和 DRF(指定兼容版本)
pip install "Django>=3.2,<4.0" "djangorestframework==3.14.0" "psycopg2-binary==2.9.5" "django-filter==22.1"
第三步:PostgreSQL 初始化
# 安装 PostgreSQL 10
sudo apt install -y postgresql-10 postgresql-client-10
# 切换到 postgres 用户创建数据库和用户
sudo -u postgres psql <<EOF
CREATE DATABASE customer_db OWNER customer_user;
CREATE USER customer_user WITH PASSWORD 'StrongPass123!';
GRANT ALL PRIVILEGES ON DATABASE customer_db TO customer_user;
\q
EOF
# 启用 pg_trgm 扩展
sudo -u postgres psql -d customer_db -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;"
第四步:Django 项目骨架
# 在 /opt/customer-app 下创建 Django 项目
django-admin startproject backend .
python manage.py startapp customers
# 修改 backend/settings.py
# - DEBUG = False
# - ALLOWED_HOSTS = ['crm.example.com', '192.168.1.100']
# - DATABASES 配置为 PostgreSQL
# - STATIC_ROOT = '/opt/customer-app/staticfiles'
# - 添加 'customers' 到 INSTALLED_APPS
# 迁移数据库
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser # 创建管理员账号
关键检查点:执行
python manage.py dbshell进入 PostgreSQL,运行\dt看表是否创建成功;运行SELECT * FROM pg_extension WHERE extname = 'pg_trgm';确认扩展已启用。这两个命令能立刻暴露环境配置问题。
4.2 React 前端构建与集成
在
/opt/customer-app
目录下操作:
# 创建前端目录
mkdir frontend
cd frontend
# 用 npx 创建 React App(CRA 4.0.3)
npx create-react-app@4.0.3 . --template typescript
# 安装依赖(注意版本锁定)
npm install --save axios react-router-dom@5.3.4 react-virtualized@9.22.3
# 修改 src/App.tsx,接入 Django API
# src/api/index.ts
import axios from 'axios';
const API_BASE_URL = process.env.NODE_ENV === 'development'
? 'http://localhost:8000/api/'
: '/api/';
const api = axios.create({
baseURL: API_BASE_URL,
timeout: 10000,
});
// 添加请求拦截器,自动带 CSRF token(Django 要求)
api.interceptors.request.use(config => {
const csrftoken = document.cookie.replace(/(?:(?:^|.*;\s*)csrftoken\s*\=\s*([^;]*).*$)|^.*$/, "$1");
config.headers['X-CSRFToken'] = csrftoken;
return config;
});
export default api;
构建前端:
# 在 frontend 目录下
npm run build
# 构建产物会输出到 frontend/build,移动到 Django 静态目录
mkdir -p /opt/customer-app/staticfiles
cp -r build/* /opt/customer-app/staticfiles/
# 收集 Django 静态文件(包括 admin 的 CSS/JS)
cd /opt/customer-app
source venv/bin/activate
python manage.py collectstatic --noinput
注意:
npm run build在 Ubuntu 18.04 的 Node.js v10.19.0 上会报错Maximum call stack size exceeded,原因是 Webpack 4 的terser-webpack-plugin默认压缩级别太高。解决方案是在frontend/package.json里加:
"browserslist": {
"production": [">0.2%", "not dead", "not op_mini all"],
"development": ["last 1 chrome version", "last 1 firefox version", "last 1 safari version"]
}
然后删掉
node_modules
重装依赖。这个错误在热词里有类似描述:“pycharm 2025.3.2.1正在构建django结构 太卡了”,本质都是资源受限环境下的构建瓶颈。
4.3 生产服务启动与验证
启动顺序不能错:先数据库,再 Django,最后 Nginx。
# 1. 启动 PostgreSQL(通常已开机自启)
sudo systemctl start postgresql
# 2. 启动 Gunicorn(用 systemd)
sudo cp /opt/customer-app/gunicorn.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable gunicorn
sudo systemctl start gunicorn
# 检查 Gunicorn 是否监听 Unix socket
ls -l /run/gunicorn.sock # 应该存在,权限为 srw-rw---- 1 www-data www-data
# 3. 配置 Nginx
sudo ln -sf /etc/nginx/sites-available/customer-app /etc/nginx/sites-enabled/
sudo nginx -t # 测试配置语法
sudo systemctl restart nginx
# 4. 开放防火墙
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
验证服务:
# 检查 Gunicorn 日志
sudo journalctl -u gunicorn -f
# 检查 Nginx 访问日志
sudo tail -f /var/log/nginx/access.log
# 用 curl 测试 API(在服务器本地)
curl -H "Accept: application/json" http://localhost/api/customers/
# 测试静态文件
curl -I http://localhost/static/css/main.123abc.css
如果
curl http://localhost/api/customers/
返回 403,检查 Django 的
CSRF_COOKIE_SECURE = True
是否开启——生产环境必须设为
True
,但 Nginx 配置里要加
proxy_cookie_path / "/; Secure; HttpOnly;"
,否则浏览器不带 Cookie。这个细节在热词 “加载 web 视图时出错: error: could not register service worker: invalidstatee” 里有暗示,其实是 CSRF Token 未正确传递导致的连锁反应。
5. 常见问题排查与独家避坑技巧
5.1 Django 启动失败:从日志定位根因
热词里高频出现 “*************************** application failed to start ********************”,这通常是 Django 启动时抛出未捕获异常。排查路径如下:
第一层:看
manage.py runserver
输出
python manage.py runserver 0.0.0.0:8000
如果报
ModuleNotFoundError: No module named 'customers'
,说明
INSTALLED_APPS
拼写错误;如果报
django.core.exceptions.ImproperlyConfigured: The SECRET_KEY setting must not be empty
,检查
settings.py
是否误删了
SECRET_KEY
。
第二层:看 Gunicorn 日志
sudo journalctl -u gunicorn -n 50 --no-pager
常见错误:
-
OSError: [Errno 98] Address already in use:端口被占用,sudo lsof -i :8000查进程,kill -9 PID杀掉。 -
django.core.exceptions.ImproperlyConfigured: Requested setting DATABASES, but settings are not configured:Gunicorn 启动时没指定 settings 模块,检查gunicorn.service的ExecStart是否漏了backend.wsgi:application。
第三层:看 PostgreSQL 日志
sudo tail -f /var/log/postgresql/postgresql-10-main.log
如果看到
FATAL: password authentication failed for user "customer_user"
,说明
settings.py
的数据库密码和
psql
创建时不一致;如果看到
database "customer_db" does not exist
,说明
createdb
命令没执行成功。
独家技巧:在
gunicorn.service里加Environment="PYTHONPATH=/opt/customer-app",避免ImportError: No module named 'backend'。这个错误在热词 “using the urlconf defined in backend.urls, django tried these url patterns” 里有体现,其实是 Python 路径没设对,Django 找不到backend包。
5.2 React 页面空白:从网络请求追查
前端白屏,F12 看 Console 报错
Failed to load resource: the server responded with a status of 404 (Not Found)
,这是最典型的部署问题。
检查静态文件路径
-
Nginx 配置的
alias /opt/customer-app/staticfiles/;末尾有没有/?必须有,否则http://crm.example.com/static/css/main.css会映射到/opt/customer-app/staticfilescss/main.css(少一个/)。 -
collectstatic是否真的生成了文件?ls -l /opt/customer-app/staticfiles/css/看文件是否存在。
检查 API 跨域或路径
-
如果 Console 报
Blocked by CORS policy,说明前端域名和 Django 的ALLOWED_HOSTS不匹配。ALLOWED_HOSTS = ['crm.example.com', '192.168.1.100']必须包含用户访问的实际域名。 -
如果 Network 面板看到
GET http://crm.example.com/api/customers/返回 404,检查 Nginx 的location /api/是否写成了location /api(少/),导致路径匹配失败。
检查 Service Worker 缓存
热词里有 “error: could not register service worker”,这是因为 CRA 4 默认启用 PWA,但生产环境没配 HTTPS。解决方案:在
frontend/src/index.tsx
里注释掉
serviceWorkerRegistration.register()
调用,或者在
frontend/public/manifest.json
里删掉
"start_url": "."
。
5.3 Ubuntu 18.04 特有陷阱汇总
| 问题现象 | 根本原因 | 解决方案 |
|---|---|---|
pip install psycopg2
编译失败
|
缺少
libpq-dev
和
python3-dev
|
sudo apt install libpq-dev python3-dev
|
npm install
卡在
node-sass
|
Ubuntu 18.04 默认 Python 3.6,
node-sass
需要 Python 2.7
|
sudo apt install python2.7-dev
,然后
npm install node-sass@4.14.1
|
gunicorn.sock
权限拒绝
|
www-data
用户对
/run/
目录无写权限
|
在
gunicorn.service
里加
RuntimeDirectory=gunicorn
,systemd 会自动创建并赋权
|
journalctl
查不到日志
|
rsyslog
服务未启动
|
sudo systemctl start rsyslog
|
ufw
开启后 SSH 断连
|
ufw
默认 deny all,没开 SSH 端口
|
sudo ufw allow OpenSSH
必须在
sudo ufw enable
之前执行
|
最后一个实战技巧:客户要求“山东大学web数据管理”风格的报表导出,我用 Django 的
django-import-export库实现 Excel 导出。但它依赖openpyxl,而openpyxl在 Python 3.6.9 上要装openpyxl==3.0.10,更高版本要求 Python 3.7+。这个细节在热词 “山东大学软件学院web数据管理” 里有暗示——高校系统往往受限于老旧 Python 版本,选库必须看清楚兼容性矩阵。
这个客户信息管理系统,从 Ubuntu 18.04 的物理机起步,到最终用户在 Chrome 里流畅操作,总共花了我 3 天时间:第一天搭环境、第二天写核心逻辑、第三天压测和调优。没有用任何云服务,所有组件都跑在本地服务器上。它证明了一件事:现代 Web 应用的“现代”,不在于用了多少新框架,而在于能否在真实约束下,把每个技术点都落到地上——PostgreSQL 的索引、Nginx 的 proxy_pass、React 的虚拟滚动、Ubuntu 的 systemd 配置,每一个环节都经得起推敲。当你在客户现场听到销售同事说“这回新增客户真快,我刚点完保存,列表就刷出来了”,那种踏实感,比任何技术发布会都来得真切。

870

被折叠的 条评论
为什么被折叠?



