0

0

实现Angular-Flask应用中的用户数据个性化功能

心靈之曲

心靈之曲

发布时间:2025-11-13 13:12:19

|

941人浏览过

|

来源于php中文网

原创

实现angular-flask应用中的用户数据个性化功能

本教程详细介绍了如何在Angular前端与Flask后端应用中构建用户数据个性化功能,以展示用户专属的汽车租赁预订信息。内容涵盖了从后端会话管理和用户ID传递,到前端服务层的数据获取与组件渲染的完整流程,并纠正了在用户登录后未能正确将用户ID传递至前端的关键问题,确保用户只能查看自己的预订记录。

在现代Web应用中,为用户提供个性化体验是至关重要的。这意味着当用户登录后,他们应该只能看到与自己账户相关的数据,例如个人资料、订单历史或预订记录。本文将以一个Angular前端与Flask后端集成的汽车租赁网站为例,详细讲解如何实现这一功能,确保用户数据的隔离性和准确性。

核心概念与技术

本教程涉及的技术栈包括:

  • 前端: Angular (用于构建用户界面和处理HTTP请求)
  • 后端: Flask (Python Web框架,处理业务逻辑和API请求)
  • 数据库: SQLite (轻量级关系型数据库,用于存储用户和预订数据)
  • 会话管理: Flask session (用于在服务器端存储用户状态,如用户ID)
  • 跨域请求: Flask-CORS (处理前端与后端之间的跨域通信)

实现用户数据个性化的关键在于:

  1. 用户认证: 验证用户身份,确保只有合法用户才能访问系统。
  2. 会话管理: 在用户登录后,在服务器端存储用户的唯一标识(如用户ID)。
  3. 用户ID传递: 将用户ID安全地传递给前端,或在后端通过会话机制自动关联。
  4. 数据过滤: 在后端根据用户ID查询数据库,只返回属于当前用户的数据。

后端实现:Flask与SQLite

后端负责用户认证、会话管理以及根据用户ID过滤数据。

1. 数据库结构

我们需要两个核心表:users 表存储用户信息,reservations 表存储预订信息,并通过 user_id 外键与 users 表关联。

import sqlite3
import hashlib
from flask import Flask, request, jsonify, session
from flask_cors import CORS

app = Flask(__name__)
CORS(app)
app.config['SECRET_KEY'] = 'your_secret_key_here' # 生产环境请使用更复杂的密钥

def hash_password(password):
    return hashlib.sha256(password.encode()).hexdigest()

def create_users_table():
    with sqlite3.connect('rental-users.db') as conn:
        cursor = conn.cursor()
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS users (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                firstname TEXT NOT NULL,
                lastname TEXT NOT NULL,
                email TEXT UNIQUE NOT NULL,
                mobile TEXT NOT NULL,
                gender TEXT NOT NULL,
                hashed_password TEXT NOT NULL
            )
        ''')
        conn.commit()

def create_reservations_table():
    with sqlite3.connect('rental-users.db') as conn:
        cursor = conn.cursor()
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS reservations (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                user_id INTEGER,
                brand TEXT NOT NULL,
                from_location TEXT NOT NULL,
                to_location TEXT NOT NULL,
                FOREIGN KEY (user_id) REFERENCES users (id)
            )
        ''')
        conn.commit()

create_users_table()
create_reservations_table()

2. 用户注册与登录

注册功能负责创建新用户。登录功能则验证用户凭据,并在成功后将用户ID存储到Flask的会话中。

关键修正: login_user 接口在成功登录后,需要将用户的ID返回给前端,以便前端能够知晓当前登录用户是谁。原始代码只返回了成功消息,导致前端无法获取用户ID。

# ... (previous code) ...

@app.route('/register', methods=['POST'])
def register_user():
    data = request.json
    required_fields = ['firstname', 'lastname', 'email', 'mobile', 'gender', 'pwd']
    for field in required_fields:
        if field not in data:
            return jsonify({'error': f'Missing required field: {field}'}), 400

    with sqlite3.connect('rental-users.db') as conn:
        cursor = conn.cursor()
        cursor.execute('SELECT * FROM users WHERE email = ?', (data['email'],))
        existing_user = cursor.fetchone()
    if existing_user:
        return jsonify({'error': 'Email is already registered'}), 400

    hashed_password = hash_password(data['pwd'])
    with sqlite3.connect('rental-users.db') as conn:
        cursor = conn.cursor()
        cursor.execute('''
            INSERT INTO users (firstname, lastname, email, mobile, gender, hashed_password)
            VALUES (?, ?, ?, ?, ?, ?)
        ''', (data['firstname'], data['lastname'], data['email'], data['mobile'], data['gender'], hashed_password))
        conn.commit()
    return jsonify({'message': 'Registration successful'})

@app.route('/login', methods=['POST'])
def login_user():
    data = request.json
    required_fields = ['email', 'pwd']
    for field in required_fields:
        if field not in data:
            return jsonify({'error': f'Missing required field: {field}'}), 400

    with sqlite3.connect('rental-users.db') as conn:
        cursor = conn.cursor()
        cursor.execute('SELECT * FROM users WHERE email = ?', (data['email'],))
        user = cursor.fetchone()

    if user:
        hashed_password = hash_password(data['pwd'])
        if hashed_password == user[6]:  # Assuming hashed_password is at index 6
            session['user_id'] = user[0] # Store user ID in session
            # 关键修正:返回用户ID给前端
            return jsonify({'message': 'Login successful', 'user_id': user[0], 'firstname': user[1], 'lastname': user[2], 'email': user[3]})
        else:
            return jsonify({'error': 'Invalid password'}), 401
    else:
        return jsonify({'error': 'User not found'}), 404

@app.route('/logout', methods=['POST'])
def logout_user():
    session.pop('user_id', None)
    return jsonify({'message': 'Logout successful'})

3. 预订与获取用户专属预订

make_reservation 接口接收用户ID和预订详情,并将其存储到数据库。get_user_reservations 接口则根据传入的 user_id 从数据库中查询并返回该用户的所有预订记录。

# ... (previous code) ...

@app.route('/make-reservation/', methods=['POST'])
def make_reservation(user_id):
    data = request.json
    with sqlite3.connect('rental-users.db') as conn:
        cursor = conn.cursor()
        cursor.execute('''
            INSERT INTO reservations (user_id, brand, from_location, to_location)
            VALUES (?, ?, ?, ?)
        ''', (user_id, data.get('brand'), data.get('from_location'), data.get('to_location')))
        conn.commit()
    return jsonify({'message': 'Reservation successful'})

@app.route('/user-reservations/', methods=['GET'])
def get_user_reservations(user_id):
    with sqlite3.connect('rental-users.db') as conn:
        cursor = conn.cursor()
        # 注意:sqlite3.Cursor.execute 期望参数为元组或列表,即使只有一个参数
        # 原始代码 (user_id,) 是正确的。如果将其改为 user_id,可能会导致类型错误。
        cursor.execute('SELECT * FROM reservations WHERE user_id = ?', (user_id,))
        reservations = [
            {'id': row[0], 'user_id': row[1], 'brand': row[2], 'from_location': row[3], 'to_location': row[4]}
            for row in cursor.fetchall()
        ]
    return jsonify(reservations)

if __name__ == '__main__':
    app.run(debug=True)

关于SQLite参数传递的说明:sqlite3.Cursor.execute 方法的第二个参数期望是一个序列(如元组或列表),即使只有一个参数也应如此。例如,cursor.execute('SELECT * FROM users WHERE id = ?', (user_id,)) 是正确的写法。如果写成 cursor.execute('SELECT * FROM users WHERE id = ?', user_id),当 user_id 是一个整数时,Python会尝试迭代这个整数,从而引发 TypeError。因此,原始代码中 (user_id,) 的写法是符合 sqlite3 库要求的。

WeWedding婚纱影楼小程序
WeWedding婚纱影楼小程序

婚纱影楼小程序提供了一个连接用户与影楼的平台,相当于影楼在微信的官网。它能帮助影楼展示拍摄实力,记录访客数据,宣传优惠活动。使用频率高,方便传播,是影楼在微信端宣传营销的得力助手。功能特点:样片页是影楼展示优秀摄影样片提供给用户欣赏并且吸引客户的。套系页是影楼根据市场需求推出的不同套餐,用户可以按照自己的喜好预定套系。个人中心可以查看用户预约的拍摄计划,也可以获取到影楼的联系方式。

下载

前端实现:Angular

前端负责用户界面的交互、调用后端API以及展示个性化数据。

1. 认证服务 (AuthorizationService)

该服务管理用户的登录状态和用户ID。

关键修正: loginUser 方法在接收到后端返回的用户ID后,需要将其存储在服务内部,以便其他组件可以访问。

import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { HttpClient } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class AuthorizationService {
  private apiUrl = 'http://localhost:5000';
  // 存储用户ID和基本信息
  private user: { id: number | null; firstname: string; lastname: string; email: string } = { id: null, firstname: '', lastname: '', email: '' }; 
  private logged = false;
  private signedUp = false;

  get isLoggedIn(): boolean {
    return this.logged;
  }

  hasSignedUp(): boolean {
    return this.signedUp;
  }

  loginUser(loginData: any): Observable {
    const url = `${this.apiUrl}/login`;
    return this.http.post(url, loginData).pipe(
      tap((response: any) => { // 修正:接收后端返回的完整响应
        if (response && response.user_id) { // 检查后端是否返回了user_id
          this.user.id = response.user_id;
          this.user.firstname = response.firstname; // 根据后端返回的数据更新
          this.user.lastname = response.lastname;
          this.user.email = response.email;
          this.logged = true;
          // 可以在这里添加导航到首页的逻辑
          this.router.navigate(['/home']);
        } else {
          // 如果后端没有返回user_id,则视为登录失败或不完整
          this.logged = false;
          console.error('Login successful, but user ID not received from backend.');
        }
      }),
      catchError((error) => {
        this.logged = false;
        this.user = { id: null, firstname: '', lastname: '', email: '' }; // 清空用户信息
        return throwError(error);
      })
    );
  }

  logout(): Observable {
    const url = `${this.apiUrl}/logout`;
    return this.http.post(url, {}).pipe(
      tap(() => {
        this.user = { id: null, firstname: '', lastname: '', email: '' }; 
        this.logged = false;
        this.router.navigate(['/login']);
      }),
      catchError((error) => {
        console.error('Logout error:', error);
        return throwError(error);
      })
    );
  }

  registerUser(formData: any): Observable {
    const url = `${this.apiUrl}/register`;
    return this.http.post(url, formData).pipe(
      tap((response: any) => {
        // 注册成功通常不需要立即获取user_id,除非是自动登录
        this.signedUp = true;
      }),
      catchError((error) => {
        this.signedUp = false;
        return throwError(error);
      })
    );
  }

  getUserId(): number | null {
    return this.user.id;
  }

  constructor(private router: Router, private http: HttpClient) {}
}

2. 预订服务 (ReservationService)

该服务负责与后端预订相关的API进行交互。

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root',
})
export class ReservationService {
  private apiUrl = 'http://localhost:5000';

  constructor(private http: HttpClient) {}

  makeReservation(userId: number, selectedCar: string, fromLocation: string, toLocation: string): Observable {
    const url = `${this.apiUrl}/make-reservation/${userId}`;
    const reservationData = {
      brand: selectedCar,
      from_location: fromLocation,
      to_location: toLocation,
    };
    return this.http.post(url, reservationData);
  }

  getUserReservations(userId: number): Observable {
    const url = `${this.apiUrl}/user-reservations/${userId}`;
    return this.http.get(url);
  }
}

3. 我的预订组件 (MyReservationsComponent)

此组件负责显示用户的预订列表,并在用户进行新预订时调用相应服务。

import { Component, OnInit } from '@angular/core';
import { AuthorizationService } from '../authorization.service';
import { ReservationService } from '../reservation.service';
import { ToastrService } from 'ngx-toastr';

@Component({
  selector: 'app-my-reservations',
  templateUrl: './my-reservations.component.html',
  styleUrls: ['./my-reservations.component.css'],
})
export class MyReservationsComponent implements OnInit {
  reservationData = {
    brand:  '',
    from_location: '',
    to_location: '',
  };

  reservations: any[] = [];

  constructor(
    private reservationService: ReservationService,
    private authService: AuthorizationService,
    private toastr: ToastrService,
  ) {}

  ngOnInit() {
    this.loadReservationHistory();
  }

  makeReservation() {
    const userId = this.authService.getUserId();
    if (userId === null) {
      this.toastr.error('Please log in to make a reservation.', 'Error');
      return;
    }

    console.log('Reservation Payload:', {
      userId: userId,
      brand: this.reservationData.brand,
      from_location: this.reservationData.from_location,
      to_location: this.reservationData.to_location,
    });

    this.reservationService
      .makeReservation(
        userId, // 确保传递的是有效的用户ID
        this.reservationData.brand,
        this.reservationData.from_location,
        this.reservationData.to_location
      )
      .subscribe(
        (res) => {
          console.log(res);
          this.toastr.success('Reservation successful!', 'Success');
          this.loadReservationHistory(); // 重新加载预订历史
          // 清空表单
          this.reservationData = { brand: '', from_location: '', to_location: '' };
        },
        (err) => {
          console.error(err);
          this.toastr.error('Error making reservation', 'Error');
        }
      );
  }

  loadReservationHistory() {
    const userId = this.authService.getUserId();
    if (userId === null) {
      this.reservations = []; // 未登录则清空预订列表
      // this.toastr.info('Please log in to view your reservations.', 'Info'); // 可选提示
      return;
    }

    this.reservationService.getUserReservations(userId).subscribe(
      (res) => {
        console.log('User Reservations:', res);
        this.reservations = res;
      },
      (err) => {
        console.error('Error loading reservations:', err);
        this.toastr.error('Failed to load reservations.', 'Error');
      }
    );
  }
}

4. 登录组件 (LoginComponent)

登录组件负责用户登录,并在成功后通过 AuthorizationService 更新登录状态。

import { Component, OnInit } from '@angular/core';
import { AuthorizationService } from '../authorization.service';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr'

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit { // 实现OnInit接口
 constructor(private authService: AuthorizationService, private router: Router, private toastr: ToastrService) {}

  loginForm = new FormGroup({
    email: new FormControl("", [Validators.required, Validators.email]),
    pwd: new FormControl("", [Validators.required, Validators.minLength(6), Validators.maxLength(15)])
  });

  loginSubmitted() {
    if (this.loginForm.invalid) {
      this.toastr.error('Please enter valid email and password.', 'Validation Error');
      return;
    }

    this.authService.loginUser(this.loginForm.value).subscribe(
      res => {
        console.log(res);
        this.showToasterSuccess(); // 登录成功提示
        // 导航逻辑已在AuthorizationService中处理,这里可以省略或添加其他逻辑
      },
      err => {
        console.error(err);
        const errorMessage = err.error && err.error.error ? err.error.error : 'Login failed. Please check your credentials.';
        this.toastr.error(errorMessage, 'Login Error');
      }
    );
  }
  get Email(): FormControl {
    return this.loginForm.get('email') as FormControl;
  }

  get PWD(): FormControl {
    return this.loginForm.get('pwd') as FormControl;
  }

  ngOnInit(): void {
    // 可以在这里添加检查是否已登录的逻辑,如果已登录则重定向
    if (this.authService.isLoggedIn) {
      this.router.navigate(['/home']);
    }
  }

  showToasterSuccess(){
    this.toastr.success("Logged in successfully !!", "Enjoy!!",{
    easing:"ease-in",
    easeTime: 1000
    });
  }
}

总结与注意事项

通过以上修改,我们成功地实现了Angular-Flask应用中的用户数据个性化功能。核心在于确保用户ID在后端登录成功后能够正确地传递给前端,并在前端服务中存储,以便后续的API请求能够携带此ID来获取用户专属数据。

关键点回顾:

  1. 后端登录接口 (/login) 必须返回用户ID:这是前端获取当前用户身份的关键。
  2. 前端 AuthorizationService 负责存储用户ID:在服务中维护用户状态(如 user.id 和 logged 状态),方便在不同组件间共享。
  3. 前端组件在发起请求前获取用户ID:例如 MyReservationsComponent 在调用 makeReservation 或 getUserReservations 前,通过 authService.getUserId() 获取当前用户ID。
  4. **后端

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Python Flask框架
Python Flask框架

本专题专注于 Python 轻量级 Web 框架 Flask 的学习与实战,内容涵盖路由与视图、模板渲染、表单处理、数据库集成、用户认证以及RESTful API 开发。通过博客系统、任务管理工具与微服务接口等项目实战,帮助学员掌握 Flask 在快速构建小型到中型 Web 应用中的核心技能。

86

2025.08.25

Python Flask Web框架与API开发
Python Flask Web框架与API开发

本专题系统介绍 Python Flask Web框架的基础与进阶应用,包括Flask路由、请求与响应、模板渲染、表单处理、安全性加固、数据库集成(SQLAlchemy)、以及使用Flask构建 RESTful API 服务。通过多个实战项目,帮助学习者掌握使用 Flask 开发高效、可扩展的 Web 应用与 API。

72

2025.12.15

session失效的原因
session失效的原因

session失效的原因有会话超时、会话数量限制、会话完整性检查、服务器重启、浏览器或设备问题等等。详细介绍:1、会话超时:服务器为Session设置了一个默认的超时时间,当用户在一段时间内没有与服务器交互时,Session将自动失效;2、会话数量限制:服务器为每个用户的Session数量设置了一个限制,当用户创建的Session数量超过这个限制时,最新的会覆盖最早的等等。

315

2023.10.17

session失效解决方法
session失效解决方法

session失效通常是由于 session 的生存时间过期或者服务器关闭导致的。其解决办法:1、延长session的生存时间;2、使用持久化存储;3、使用cookie;4、异步更新session;5、使用会话管理中间件。

749

2023.10.18

cookie与session的区别
cookie与session的区别

本专题整合了cookie与session的区别和使用方法等相关内容,阅读专题下面的文章了解更详细的内容。

92

2025.08.19

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1126

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

192

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

1618

2025.12.29

俄罗斯Yandex引擎入口
俄罗斯Yandex引擎入口

2026年俄罗斯Yandex搜索引擎最新入口汇总,涵盖免登录、多语言支持、无广告视频播放及本地化服务等核心功能。阅读专题下面的文章了解更多详细内容。

158

2026.01.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Sass 教程
Sass 教程

共14课时 | 0.8万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3万人学习

CSS教程
CSS教程

共754课时 | 24.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号