海南大学交友平台登录页开发实战day6(覆写接口+Flask 本地链接正常访问)

该平台定位为服务海南大学在校生的专属交友场景,核心实现用户认证、资料管理、爱好匹配等基础功能,后端采用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全栈开发相关的实战经验。

相关推荐
Shorasul2 小时前
如何防御SQL注入的SQL畸形查询_利用语法分析器检测
jvm·数据库·python
WHS-_-20222 小时前
Pycharm 使用经验
ide·python·pycharm
花椒技术2 小时前
从 1.5 秒到 660ms,直播间首屏秒开是怎么做出来的?
人工智能·后端·全栈
Highcharts.js2 小时前
抉择之巅:从2029年回望2026年——企业可视化“战略分水岭”?
前端·javascript·信息可视化·编辑器·echarts·highcharts
沙振宇2 小时前
【Web】使用Vue3+PlayCanvas开发3D游戏(十)让人物动起来
前端·游戏·3d·人物·
Rust研习社2 小时前
深入 Rust 引用计数智能指针:Rc 与 Arc 从入门到实战
开发语言·后端·rust
m0_640309302 小时前
c++如何判断两个文件路径是否物理指向同一个磁盘文件_equivalent【详解】
jvm·数据库·python
树獭叔叔2 小时前
OpenCLI:让任何网站成为你的命令行工具
后端·aigc·openai
数智工坊3 小时前
深度拆解AnomalyAny:异常检测新工作,利用Stable Diffusion生成真实多样异常样本!
人工智能·pytorch·python·stable diffusion