讲师中心 微信公众号
AI工具推荐 视频效率加速

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

大瑶姑娘_8736

大瑶姑娘_8736

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

|

1081人浏览过

|

来源于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 从数据库中查询并返回该用户的所有预订记录。

Expanso yaml-to-json
Expanso yaml-to-json

使用 Expanso Edge 管道将 YAML 输入转换为 JSON 格式。

下载
# ... (previous code) ...

@app.route('/make-reservation/<int:user_id>', 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/<int:user_id>', 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 库要求的。

前端实现: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<any> {
    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<any> {
    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<any> {
    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<any> {
    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<any[]> {
    const url = `${this.apiUrl}/user-reservations/${userId}`;
    return this.http.get<any[]>(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工具

更多
讯飞智文

一款面向学习和办公场景的AI文档创作工具,可辅助生成PPT与Word文档,提高资料整理和内容制作效率。

花生AI
花生AI Hot

一款AI视频创作工具,主要用于B站推出的AI视频创作工具,适合需要提升相关任务效率的用户。

SkildArt
SkildArt Hot

SkildArt是一款AI文本写作工具,一站式 AI 视觉创作平台。

超级简历WonderCV

一款AI办公效率工具,主要用于免费求职简历模版下载制作,应届生职场人必备简历制作神器,适合需要提升相关任务效率的用户。

讯飞智作

讯飞智作是一款AI视频创作工具,AI文本配音工具,数字人课程、营销视频制作。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

墨刀AI
墨刀AI Hot

一款AI图像与设计工具,主要用于产品经理的专属智能体,适合需要提升相关任务效率的用户。

相关专题

更多
flask框架如何搭建
flask框架如何搭建

搭建步骤:1、安装Python和Pip;2、创建虚拟环境;3、安装Flask;4、创建Flask应用;5、运行应用;6、访问应用。想了解更多flask框架的相关内容,可以阅读本专题下面的文章。

4215

2024.06.27

Python Flask框架
Python Flask框架

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

6524

2025.08.25

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

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

336

2025.12.15

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

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

580

2023.10.17

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

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

876

2023.10.18

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

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

1966

2025.08.19

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

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

3228

2023.10.19

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

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

5349

2025.10.17

Postman身份认证怎么配置
Postman身份认证怎么配置

本专题从实际API接口调试场景出发,讲解Postman身份认证的完整配置流程,包括常见认证协议介绍、授权参数设置、访问令牌填写、OAuth 2.0授权及认证失败排查,帮助用户正确完成接口授权与访问验证。

0

2026.11.06

热门下载

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

精品课程

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

共14课时 | 1.6万人学习

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

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn