该平台定位为服务海南大学在校生的专属交友场景,核心实现用户认证、资料管理、爱好匹配等基础功能,后端采用Flask框架搭配SQLite数据库,兼顾开发效率与轻量部署需求。在开发过程中,从环境配置到前后端联调,遇到了多个典型的踩坑点,尤其是页面跳转和接口联调环节,耗费了较多时间排查。本文将详细梳理后端开发的完整推进流程,拆解关键踩坑点及解决方案,附上核心代码片段,为同类校园项目开发提供参考。
一、项目前期准备与技术选型
在项目启动初期,结合校园交友平台的需求特点(用户量适中、功能聚焦、部署便捷),确定了后端技术栈选型,核心思路是"轻量、高效、易维护",具体选型如下:
-
框架:Flask,轻量级Python Web框架,上手快、灵活度高,适合小型项目快速开发,且可通过扩展实现跨域、数据库连接等功能。
-
数据库:SQLite,无需单独部署数据库服务,文件型数据库适配小型项目,降低开发和部署成本,后期可根据用户量扩展为MySQL。
-
扩展工具:Flask-SQLAlchemy(ORM工具,简化数据库操作)、Flask-CORS(解决跨域问题)、Werkzeug(密码加密,保障用户信息安全)。
-
前后端交互:采用RESTful API规范,前端通过Fetch发送请求,后端返回统一格式的JSON响应,确保交互流畅。
项目核心需求拆解:用户注册/登录、个人资料管理、爱好管理、首页数据展示,后端需提供对应的API接口,同时控制页面路由跳转,保障未登录用户无法访问主页等受保护页面。前期准备阶段,重点梳理了数据库表结构和API接口文档,为后续开发奠定基础,避免因需求模糊导致返工。
二、项目推进流程拆解
2.1 第一步:环境搭建与数据库初始化
首先完成本地开发环境搭建,安装所需依赖包,核心依赖如下(requirements.txt简化版):
python
flask==2.3.3
flask-sqlalchemy==3.1.1
flask-cors==4.0.0
werkzeug==2.3.7
环境搭建完成后,进行数据库模型设计,核心分为用户表(users)和用户爱好表(user_hobbies),采用Flask-SQLAlchemy实现ORM映射,简化数据库操作。初始化数据库时,通过自定义函数自动创建数据表,避免手动执行SQL语句,提升开发效率。
2.2 第二步:核心API接口开发
后端API接口按功能模块划分,主要包括用户认证模块(注册、登录、登出)、用户资料模块(获取、修改资料)、用户爱好模块(获取、修改爱好)、首页数据模块,所有接口返回统一的JSON格式,便于前端统一处理。
接口开发遵循RESTful规范,采用对应的HTTP请求方法:POST请求用于提交数据(注册、登录、修改资料),GET请求用于获取数据(获取资料、获取爱好),确保接口语义清晰、易于维护。同时,添加登录验证装饰器,保护需要登录才能访问的接口,未登录用户访问时返回401未授权提示。
2.3 第三步:页面路由配置
平台后端需提供两个核心页面的路由:登录注册页(login_register.html)和主页(home.html),路由配置需满足两个核心需求:一是未登录用户访问主页时,自动重定向到登录页;二是确保前端能通过正确的路径访问页面,避免出现404错误。
2.4 第四步:前后端联调与问题排查
接口和路由开发完成后,与前端进行联调,重点测试用户注册、登录、页面跳转等核心流程,排查接口请求失败、跳转异常等问题,这也是本次开发中踩坑最多的环节。联调完成后,进行简单的功能测试,确保所有核心功能正常运行,数据交互无误。
2.5 第五步:项目优化与部署准备
联调通过后,对后端代码进行优化:简化冗余代码、添加错误处理机制、完善接口注释,提升代码可读性和可维护性。同时,整理项目文档,包括API接口文档、数据库表结构说明,为后续部署和维护提供参考。目前项目已完成本地开发测试,下一步将部署到服务器,实现校园内可访问。
三、后端开发核心踩坑实录(重点!)
本次后端开发中,最耗时的环节是前后端联调中的页面跳转和接口请求问题,共遇到4个典型踩坑点,均为Flask开发中高频出现的问题,现将踩坑过程、问题原因及解决方案详细拆解,帮助大家避坑。
踩坑点1:页面路由配置错误,导致手动访问页面404
【问题现象】:后端启动成功后,手动在浏览器输入http://127.0.0.1:5000/login和http://127.0.0.1:5000/home,均提示"URL拼写可能存在错误,请检查",无法访问页面。
【排查过程】:首先检查app.py中的页面路由配置,发现初期路由使用了send_from_directory方法,但未正确配置文件路径,且未添加根路由重定向;同时,确认app.py、login_register.html、home.html三个文件是否在同一目录,通过终端dir命令排查,发现文件位置正确,排除路径问题。
【问题原因】:路由配置存在两个错误:一是未配置根路由(/),导致访问127.0.0.1:5000时无对应路由;二是send_from_directory方法未使用绝对路径,导致Flask无法正确找到HTML文件。
【解决方案】:添加根路由,重定向到登录页;修改send_from_directory方法,使用绝对路径配置,确保Flask能找到HTML文件;同时,优化路由命名,避免路由冲突。具体核心代码见下文。
踩坑点2:前端跳转路径错误,登录后无法进入主页
【问题现象】:手动访问/login和/home能正常打开,但登录成功后,前端自动跳转时出现404错误,提示"invalid link",跳转路径显示为home.html,而非后端配置的/home路由。
【排查过程】:查看前端JS代码,发现登录成功后的跳转语句写为window.location.href = 'home.html',这是本地文件的跳转方式,而Flask项目中,页面跳转必须通过后端配置的路由(/home),而非直接访问HTML文件。
【问题原因】:前端开发者混淆了本地静态页面跳转和Flask项目页面跳转的区别,将跳转路径写为HTML文件名,而后端未配置/home.html对应的路由,导致跳转失败。
【解决方案】:修改前端JS跳转路径,将window.location.href = 'home.html'改为window.location.href = '/home',与后端路由保持一致;同时,检查注册成功后的跳转逻辑,确保所有页面跳转均使用路由路径,而非HTML文件名。
踩坑点3:跨域问题导致session失效,登录状态无法保持
【问题现象】:登录接口请求成功,后端返回200状态码,但跳转至/home时,仍被重定向到登录页,排查发现session中未保存用户登录状态,后端判定用户未登录。
【排查过程】:查看前端JS中的API基础路径配置,发现API_BASE被写死为http://127.0.0.1:5000/api,导致前端发送请求时出现跨域问题,浏览器无法保存后端返回的session cookie,进而导致登录状态无法保持。
【问题原因】:跨域请求时,浏览器默认不保存第三方cookie,若API基础路径写死为完整URL,会触发跨域限制,导致session cookie无法生效,登录状态无法持久化。
【解决方案】:将前端API基础路径从http://127.0.0.1:5000/api改为/api,使用相对路径发送请求,避免跨域;同时,在后端Flask配置中,确保CORS扩展开启supports_credentials=True,支持跨域请求携带cookie,确保session正常生效。
踩坑点4:数据库接口联调失败,注册/登录无响应
【问题现象】:前端发送注册、登录请求时,提示"网络错误",后端终端未打印请求日志,排查发现接口请求未到达后端,或后端接口返回格式与前端预期不一致。
【排查过程】:检查前端Fetch请求配置,发现部分请求未添加credentials: 'include'参数,导致无法携带session cookie;同时,检查后端接口的请求方法和参数接收方式,发现注册接口中,前端传参为name,而后端接收参数时误写为username,导致参数校验失败,接口返回500错误。
【问题原因】:一是前端请求未携带cookie,导致后端无法识别请求身份;二是前后端参数名不一致,导致数据无法正常传递;三是未添加异常捕获机制,接口报错时未返回明确提示,难以排查问题。
【解决方案】:在所有前端Fetch请求中添加credentials: 'include'参数,确保携带session cookie;统一前后端参数名,前端传参与后端接收参数保持一致;在后端接口中添加try-except异常捕获,返回统一格式的错误提示,便于排查问题。
四、后端核心代码片段(简化版)
以下为app.py核心代码片段,保留关键功能(数据库模型、核心路由、核心接口),省略冗余代码和注释,便于复制参考;前端登录/注册核心请求代码单独列出,重点标注修改后的关键配置。
4.1 后端核心代码(app.py简化版)
python
from flask import Flask, request, jsonify, session, send_from_directory, redirect
from flask_cors import CORS
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime
import os
# 应用配置
app = Flask(__name__)
app.config['SECRET_KEY'] = 'hainanu-friends-secret-key-2024'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///hainanu.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# 初始化扩展
db = SQLAlchemy(app)
CORS(app, supports_credentials=True) # 支持跨域和cookie
# 数据库模型(简化版)
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
student_id = db.Column(db.String(20), unique=True, nullable=False, index=True)
username = db.Column(db.String(50), nullable=False)
password_hash = db.Column(db.String(255), nullable=False)
wechat = db.Column(db.String(50), default='')
qq = db.Column(db.String(20), default='')
phone = db.Column(db.String(20), default='')
create_time = db.Column(db.DateTime, default=datetime.now)
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
class UserHobby(db.Model):
__tablename__ = 'user_hobbies'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
student_id = db.Column(db.String(20), db.ForeignKey('users.student_id'), nullable=False)
hobby1 = db.Column(db.String(50), default='')
hobby2 = db.Column(db.String(50), default='')
hobby3 = db.Column(db.String(50), default='')
# 工具函数:统一API响应格式
def api_response(code=200, msg='操作成功', data=None):
response = {'code': code, 'msg': msg, 'data': data if data is not None else {}}
return jsonify(response), code
# 登录验证装饰器
def login_required(f):
from functools import wraps
@wraps(f)
def decorated_function(*args, **kwargs):
if 'student_id' not in session:
return api_response(401, '请先登录')
return f(*args, **kwargs)
return decorated_function
# 核心接口(简化版)
# 登录接口
@app.route('/api/login', methods=['POST'])
def login():
try:
data = request.get_json()
student_id = data.get('student_id', '').strip()
password = data.get('password', '')
user = User.query.filter_by(student_id=student_id).first()
if not user or not user.check_password(password):
return api_response(401, '学号或密码错误')
session['student_id'] = user.student_id
session['username'] = user.username
return api_response(200, '登录成功', {'student_id': student_id, 'username': user.username})
except Exception as e:
return api_response(500, f'登录失败: {str(e)}')
# 注册接口
@app.route('/api/register', methods=['POST'])
def register():
try:
data = request.get_json()
student_id = data.get('student_id', '').strip()
password = data.get('password', '')
name = data.get('name', '').strip()
if User.query.filter_by(student_id=student_id).first():
return api_response(409, '该学号已注册')
new_user = User(student_id=student_id, username=name)
new_user.set_password(password)
db.session.add(new_user)
db.session.commit()
return api_response(200, '注册成功', {'student_id': student_id, 'username': name})
except Exception as e:
db.session.rollback()
return api_response(500, f'注册失败: {str(e)}')
# 页面路由(关键修改版)
@app.route('/')
def root():
return redirect('/login')
@app.route('/login')
def login_page():
# 绝对路径配置,避免文件找不到
return send_from_directory(os.path.dirname(os.path.abspath(__file__)), 'login_register.html')
@app.route('/home')
def home_page():
# 未登录重定向到登录页
if 'student_id' not in session:
return redirect('/login')
return send_from_directory(os.path.dirname(os.path.abspath(__file__)), 'home.html')
# 数据库初始化
def init_database():
with app.app_context():
db.create_all()
print("✅ 数据库初始化完成")
# 启动服务
if __name__ == '__main__':
init_database()
print("🚀 服务器启动成功!")
print("📍 登录: http://127.0.0.1:5000/login")
print("📍 主页: http://127.0.0.1:5000/home")
app.run(debug=True, host='127.0.0.1', port=5000)
4.2 前端登录/注册核心请求代码(修改后版)
重点标注两处关键修改:API基础路径改为相对路径、跳转路径改为路由路径、添加credentials: 'include'参数,确保登录状态正常、跳转无误。
javascript
// API基础配置(关键修改:改为相对路径,避免跨域)
const API_BASE = '/api';
// 登录按钮点击事件
document.querySelector('#login-page .btn-primary').addEventListener('click', async (e) => {
e.preventDefault();
const studentId = document.getElementById('login-student-id').value.trim();
const password = document.getElementById('login-password').value;
// 客户端验证
if (!studentId) {
alert('请输入学号');
return;
}
if (!password) {
alert('请输入密码');
return;
}
try {
const response = await fetch(`${API_BASE}/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include', // 重要:支持session cookie(关键修改)
body: JSON.stringify({
student_id: studentId,
password: password
})
});
const data = await response.json();
if (data.code === 200) {
// 登录成功,显示欢迎信息并跳转到主页(关键修改:跳转路径改为路由)
alert(`欢迎回来,${data.data.username || '同学'}!`);
window.location.href = '/home';
} else {
// 登录失败,提示错误
alert(data.msg || '学号或密码错误');
}
} catch (error) {
console.error('登录请求失败:', error);
alert('网络错误,请检查服务器是否运行');
}
});
// 注册表单提交事件
document.getElementById('register-form').addEventListener('submit', async (e) => {
e.preventDefault();
const pwd = document.getElementById('register-password').value;
const confirmPwd = document.getElementById('register-confirm-password').value;
const errorTip = document.getElementById('pwd-error');
// 判断密码是否一致
if (pwd !== confirmPwd) {
errorTip.classList.add('visible');
return;
}
errorTip.classList.remove('visible');
// 获取表单数据
const studentId = document.getElementById('register-student-id').value.trim();
const name = document.getElementById('register-name').value.trim();
const wechat = document.getElementById('register-wechat').value.trim();
const qq = document.getElementById('register-qq').value.trim();
const phone = document.getElementById('register-phone').value.trim();
// 验证必填项
if (!studentId) {
alert('请输入学号');
return;
}
if (!name) {
alert('请输入姓名');
return;
}
if (!pwd || pwd.length < 6) {
alert('密码不能少于6位');
return;
}
try {
const response = await fetch(`${API_BASE}/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include', // 重要:支持session cookie(关键修改)
body: JSON.stringify({
student_id: studentId,
password: pwd,
name: name,
wechat: wechat,
qq: qq,
phone: phone
})
});
const data = await response.json();
if (data.code === 200) {
alert('🎉 注册成功!欢迎加入海南大学校园交友平台');
// 清空表单
document.getElementById('register-form').reset();
// 跳转到登录页
switchPage('login-page');
} else {
alert(data.msg || '注册失败');
}
} catch (error) {
console.error('注册请求失败:', error);
alert('网络错误,请检查服务器是否运行');
}
});
五、数据库展示(预留图片粘贴位置)
本次项目采用SQLite数据库,核心包含两张表:用户表(users)和用户爱好表(user_hobbies),表结构清晰,关联关系简单,以下为数据库表结构展示和数据示例,预留图片粘贴位置,可插入数据库可视化工具(如Navicat、DB Browser for SQLite)的截图。
5.1 数据库表结构展示
(此处预留图片位置,粘贴数据库表结构截图,建议包含表名、字段名、字段类型、主键/外键等信息)
示例说明:users表为主表,存储用户核心信息,student_id为唯一索引,作为用户唯一标识;user_hobbies表为从表,通过student_id与users表关联,存储用户的爱好信息,最多支持5个爱好。
5.2 数据库数据示例
数据库对于基础个人数据的记录

数据库对于个人爱好字段的记录
后续将会通过读取相关字段使用算法计算学生之间的匹配度
示例说明:测试数据包含3-5条用户记录,涵盖不同的学号、姓名、联系方式,以及对应的爱好信息,验证数据库插入、查询功能正常。
六、项目总结与后续规划
本次海南大学校园交友平台后端开发,从环境搭建到前后端联调,历时数日,核心完成了用户认证、资料管理、爱好管理等基础功能,同时解决了页面跳转、跨域、session失效等多个典型问题。通过本次开发,深刻体会到Flask框架的灵活性,也意识到前后端联调中"细节决定成败"------一个简单的跳转路径错误、一个缺失的请求参数,都可能导致整个流程失效。
本次开发的核心收获:一是掌握了Flask框架的核心用法,包括路由配置、ORM数据库操作、跨域处理、session管理;二是学会了排查前后端联调中的常见问题,积累了踩坑经验;三是理解了RESTful API规范的实际应用,掌握了前后端数据交互的核心逻辑。
后续项目规划:
-
功能优化:新增用户匹配、消息通知等核心功能,丰富平台交友场景;
-
性能优化:将SQLite数据库替换为MySQL,提升数据存储和查询效率,适配更多用户;
-
部署上线:将项目部署到云服务器,配置域名和HTTPS,实现海南大学校园内可访问;
-
安全优化:加强用户密码加密、接口权限控制,防止恶意请求和数据泄露;
-
前端完善:配合前端开发者,优化页面交互效果,提升用户体验。
对于正在开发校园类Web项目的开发者,建议在项目启动初期,明确前后端交互规范,统一接口参数和跳转路径,避免因沟通不畅导致返工;同时,在开发过程中,及时进行单元测试和联调,发现问题尽早解决,提升开发效率。关注我,后续我也会持续更新项目开发进度,分享更多Python全栈开发相关的实战经验。