logo
kuo
  • Home
  • Pricing
logo
kuo
Copyright © 2025 kuo. Ltd.
Links
SubscribeManage Subscription
Powered by Postion - Create. Publish. Own it.
Privacy policy•Terms

Postion

项目介绍、背景与技术选型、项目架构设计与功能拆解

项目介绍、背景与技术选型、项目架构设计与功能拆解

k
by kafeihu
•Jul 19, 2025

项目介绍、背景与技术选型

1. 项目背景:从理论到实战,构建你的第一个生产级全栈应用

在学习编程的道路上,许多开发者在掌握了Java和Spring的基础语法后,常常会遇到一个共同的瓶颈:如何将这些零散的知识点串联起来,构建一个完整、真实、且遵循业界最佳实践的全栈项目?

我们知道如何写一个“Hello World”控制器,也了解数据库的增删改查,但当面对一个真实的业务需求时,问题接踵而至:

  • 用户登录和权限如何设计才安全?

  • 前端和后端的数据如何顺畅地交互?

  • 复杂的业务逻辑,比如订单状态流转,如何写出优雅而不是“意大利面条”式的代码?

  • 项目完成后,如何像专业团队一样自动化地部署上线?

本课程正是为了解决这一系列挑战而设计的。我们拒绝纸上谈兵,将以一个经典的业务场景——“航空订单后台管理系统”——为实战载体,手把手带你走过一个现代Web应用从无到有的全过程。

我们的目标不仅仅是“完成功能”,更是“做好工程”。你将学到:

  1. 全链路思维:亲手打通从前端(Angular)、后端(Spring Boot)、到自动化部署(Docker & CI/CD)的每一个环节,理解数据和请求在系统中的完整生命周期。

  2. 企业级实践:掌握使用Spring Boot构建生产级服务的核心技能,包括利用Spring Security和JWT实现无状态认证,通过Spring Data JPA高效操作数据库。

  3. 优雅代码设计:我们将深入探讨一个能显著提升代码质量的核心主题——如何用优雅的状态机模式取代冗长混乱的if-else,编写出易于维护和扩展的业务代码。

  4. 现代化运维:学会使用Docker将应用容器化,并通过GitHub Actions搭建自动化CI/CD流水线,体验高效的DevOps文化。

完成本课程后,你得到的将不仅仅是一个全栈项目,更是一套贯穿现代软件开发全生命周期的思维模式和动手能力。让我们一起,用代码构建价值!

2.最终成果展示

打开已经部署好的航空订单系统。我们可以看到一个简洁的登录界面。我们输入用户名和密码登录... 成功后,进入了订单列表页面,这里展示了所有订单的基本信息,比如订单号、旅客、状态等。我们可以点击任何一个订单,查看它的详细信息,包括关联的航班信息。我们还可以模拟业务流程,比如将一个‘待支付’的订单状态更新为‘已支付’。

3. 我们的武器库:技术栈选型解析

为了最高效地完成这个项目,我们选择了一套业界主流且强大的技术栈:

  • 后端:Spring Boot

    • 为什么是它? Spring Boot是Java领域构建微服务和Web应用的事实标准。它的核心优势是“约定大于配置”,能让我们以极快的速度搭建起一个生产级的Web服务。更重要的是,它背后有强大而成熟的Spring生态系统支持,无论是数据库操作(Spring Data JPA)、安全认证(Spring Security)还是其他任何你能想到的功能,都有现成的解决方案。它让我们专注于业务逻辑,而不是繁琐的配置。

  • 前端:Angular

    • 为什么是它? Angular是Google推出的一款前端框架,特别适合构建复杂、大型的“单页应用”(SPA)。它的优势在于其工程化和体系化。它为我们提供了清晰的项目结构、强大的依赖注入、完善的路由和表单管理。对于需要精细化管理数据和状态的后台管理系统来说,Angular的严谨性是一个巨大的优点。

  • 部署:Docker / CI/CD (GitHub Actions)

    • 为什么是它们? 这代表了现代软件的交付方式。

      • Docker 解决了“在我电脑上明明是好的”这个终极难题。它像一个集装箱,把我们的应用程序和它所需要的一切环境(比如Java运行环境、Nginx服务器)打包在一起,确保在任何地方都能以同样的方式运行。

      • CI/CD (持续集成/持续部署) 是一条自动化流水线。我们只需要把代码推送到代码仓库(GitHub),CI/CD工具(GitHub Actions)就会自动帮我们完成测试、打包、部署的全过程。这是迈向自动化、高效率运维的关键一步,也是现代软件工程师的必备技能。

项目架构与数据库设计

1. 宏观架构:数据是如何流动的?

想象一下用户操作的完整旅程:

  1. 浏览器 (Browser - Angular): 这是用户的入口,我们前端的Angular应用就运行在这里。它负责渲染漂亮的界面,捕捉用户的点击、输入等操作。

  2. Web服务器 (Web Server - Nginx): 用户请求的第一站。在这里,Nginx扮演两个角色:

    • 静态内容服务员:当用户首次访问我们的网址时,Nginx会把编译好的Angular前端代码(HTML, CSS, JS文件)快速地发送给浏览器。

    • 反向代理:当前端应用需要请求数据时(比如获取订单列表),它会向Nginx发送API请求。Nginx再把这个请求转发给真正的后端服务。这样做的好处是隐藏了后端服务的真实地址,也方便未来做负载均衡。

  3. (可选) API网关 (API Gateway): 在更复杂的微服务架构中,这里会有一个API网关。它像一个总接待,负责统一的认证、限流、路由等。在我们的项目中,为了简化,我们会将这部分职责合并到后端服务或由Nginx承担。

  4. 后端应用 (Backend - Spring Boot): 这是我们系统的大脑和心脏。所有的业务逻辑都在这里处理:用户身份验证、订单状态的更新、从数据库查询数据、调用外部API等。它接收API请求,处理后,将结果(通常是JSON格式)返回。

  5. 数据库 (Database - MySQL/PostgreSQL): 这是我们系统的长期记忆。所有需要持久化的数据,比如用户信息、订单记录、航班数据,都存储在这里。后端应用通过读写数据库来完成业务操作。
    所以,一个典型的请求流程是:用户操作 -> 浏览器(Angular) -> Nginx -> 后端(Spring Boot) -> 数据库,然后数据再反向流回,最终在浏览器上展示出来。每一层各司其职,清晰明了。

2. 微观拆解:我们要实现哪些核心功能?

明确了宏观架构,我们再把后端这个“黑盒子”打开,看看里面需要实现哪些核心功能模块。

  • 用户认证 (User Authentication)

    • 登录 (Login):我们需要一个接口,接收用户名和密码,验证成功后,返回一个“令牌”(Token),这个令牌就是用户后续访问的凭证。我们将使用目前最流行的 JWT (JSON Web Token)方案。

    • 登出 (Logout):让令牌失效。

    • 权限拦截 (Authorization):对于需要登录才能访问的接口(比如查询订单),我们要能自动检查请求中是否带有合法的令牌,没有就拒绝访问。这部分将由Spring Security来帮我们完成。

  • 订单管理 (Order Management)

    • CRUD:这是所有管理系统的基础,即增删改查。在本项目中,我们简化为 CRU (Create, Read, Update)。

    • 列表查询 (List Orders):提供一个接口,可以分页、排序、甚至根据条件(如状态)筛选订单。

    • 详情查看 (Get Order Detail):根据订单ID,查询单个订单的完整信息,这可能需要关联查询用户信息和航班信息。

  • 状态机 (State Machine)

    • 核心逻辑:这是我们项目的亮点。我们需要设计一套规则,来控制订单状态的合法流转。比如,“待支付”状态只能转向“已支付”或“已取消”,而不能直接跳到“已出票”。我们将用一种非常优雅的方式来实现这个逻辑,告别复杂的if-else。

  • 航班API (Flight API Integration)

    • 模拟对接:我们将创建一个模拟的外部接口,用来提供航班信息。

    • 服务调用:在我们的主业务逻辑中,学习如何去调用这个外部接口,并将返回的数据与我们自己的订单数据进行聚合,然后一并返回给前端。

数据库表设计 (ER图)

我们将设计三张核心的表:users(用户表)、orders(订单表)、和 flight_info(航班信息表)。

1. 用户表 (users)

这是最基础的表,用于存储系统用户的信息。

  • id (BIGINT, PRIMARY KEY, AUTO_INCREMENT): 用户的唯一标识。用BIGINT是为了应对海量用户,AUTO_INCREMENT表示数据库会自动为我们生成ID。

  • username (VARCHAR(50), NOT NULL, UNIQUE): 用户名,用于登录。它必须是唯一的,且不能为空。VARCHAR(50)限制了最大长度。

  • password (VARCHAR(255), NOT NULL): 存储加密后的密码。注意,我们绝不能明文存储密码! VARCHAR(255)是为了容纳加密后(比如用BCrypt)产生的较长字符串。

  • role (VARCHAR(20), NOT NULL): 用户角色,比如 ADMIN 或 USER。用于权限控制。

  • created_at (TIMESTAMP, DEFAULT CURRENT_TIMESTAMP): 记录创建时间,方便审计和跟踪。

2. 订单表 (orders)

这是我们业务的核心表。

  • id (BIGINT, PRIMARY KEY, AUTO_INCREMENT): 订单的唯一标识。

  • order_number (VARCHAR(100), NOT NULL, UNIQUE): 业务订单号,这个是给用户看的,通常会包含日期、随机数等信息,具有业务含义。

  • user_id (BIGINT, NOT NULL, FOREIGN KEY): 这是一个外键。它关联到users表的id字段,表明这个订单属于哪个用户。通过这个字段,我们建立了users和orders之间 “一对多” 的关系(一个用户可以有多个订单)。

  • status (VARCHAR(20), NOT NULL): 订单状态。这里可以考虑使用ENUM类型,如果我们能确定状态是固定且有限的(比如 'PENDING_PAYMENT', 'PAID', 'CANCELLED', 'TICKETED')。使用VARCHAR更灵活一些。

  • total_amount (DECIMAL(10, 2), NOT NULL): 订单总金额。使用DECIMAL类型来精确表示货币,避免使用FLOAT或DOUBLE带来的精度问题。

  • created_at (TIMESTAMP, DEFAULT CURRENT_TIMESTAMP): 订单创建时间。

  • updated_at (TIMESTAMP, DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP): 订单最后更新时间。这个设置非常有用,每次更新这行记录时,数据库会自动更新这个时间戳。

3. 航班信息表 (flight_info)

这张表存储与订单关联的航班详情。

  • id (BIGINT, PRIMARY KEY, AUTO_INCREMENT): 航班信息的唯一标识。

  • order_id (BIGINT, NOT NULL, UNIQUE, FOREIGN KEY): 这也是一个外键,关联到orders表的id字段。注意这里的UNIQUE约束,它规定了一个订单只能有一条航班信息,这就建立了orders和flight_info之间 “一对一” 的关系。

  • flight_number (VARCHAR(10), NOT NULL): 航班号,如 CA1831。

  • departure_city (VARCHAR(50)): 出发城市。

  • arrival_city (VARCHAR(50)): 到达城市。

  • departure_time (DATETIME): 出发时间。

核心业务逻辑 - 状态机实现

1. 反面教材:为什么不能用一堆 if/else?

我们先思考一下最直观,但也是最糟糕的实现方式。在OrderService的updateStatus方法里,我们可能会写出这样的代码:

Generated java

// 这是“反面教材”,不要模仿!
public void updateStatus(Long orderId, String newStatus) {
    Order order = orderRepository.findById(orderId).get();
    String currentStatus = order.getStatus();

    if (currentStatus.equals("PENDING_PAYMENT")) {
        if (newStatus.equals("PAID") || newStatus.equals("CANCELLED")) {
            order.setStatus(newStatus);
        } else {
            throw new IllegalStateException("Invalid status transition!");
        }
    } else if (currentStatus.equals("PAID")) {
        if (newStatus.equals("TICKETED")) {
            order.setStatus(newStatus);
        } else {
            throw new IllegalStateException("Invalid status transition!");
        }
    }
    // ... 后面还有更多的 else if ...
    orderRepository.save(order);
}

大家看,这种代码有什么问题?

  • 难以阅读和维护:随着状态和转换规则的增多,这个if/else链会变得无限长,逻辑错综复杂,像一碗意大利面,没人敢动。

  • 容易出错:每增加一个新状态,你可能需要修改多个地方,很容易遗漏或改错。

  • 职责不清:状态转换的规则,本应是订单自身的核心逻辑,现在却散落在Service层的一个方法里,违反了单一职责原则。

2. 正面引导:用「状态机模式」优雅地解决问题

为了解决这个问题,我们引入状态机模式。

什么是状态机?简单来说,它是一个模型,描述了一个对象在它的生命周期中所经历的各种状态,以及触发状态转换的事件和条件。

核心思想是:把状态和它允许的转换规则,封装在一起。 每个状态自己知道它可以转换到哪些其他状态。

3. 我们的实现:强大的 Java Enum

在Java中,Enum(枚举)是实现简单状态机的绝佳工具。它天生就是类型安全的、有限的、单例的。

我们将创建一个OrderStatus的枚举,它不仅仅是几个静态常量,它将拥有行为。

我们的OrderStatus.java会是这样的:

Generated java

public enum OrderStatus {
    PENDING_PAYMENT {
        @Override
        public boolean canTransitionTo(OrderStatus nextStatus) {
            return nextStatus == PAID || nextStatus == CANCELLED;
        }
    },
    PAID {
        @Override
        public boolean canTransitionTo(OrderStatus nextStatus) {
            return nextStatus == TICKETED;
        }
    },
    CANCELLED, // 终态,不能转换
    TICKETED;  // 终态,不能转换

    // 定义一个抽象方法,强制每个状态都必须实现自己的转换逻辑
    public boolean canTransitionTo(OrderStatus nextStatus) {
        return false; // 默认不允许转换
    }
}

看到了吗?我们将转换逻辑内聚到了每个枚举实例内部。PENDING_PAYMENT自己声明:“我只能变成PAID或者CANCELLED。”

现在,我们的OrderService里的代码就变得异常清晰和健壮:

Generated java

// 这是优雅的实现方式
public void updateStatus(Long orderId, OrderStatus newStatus) {
    Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new EntityNotFoundException("Order not found"));

    // 直接调用状态自身的逻辑来判断
    if (order.getStatus().canTransitionTo(newStatus)) {
        order.setStatus(newStatus);
        orderRepository.save(order);
    } else {
        throw new IllegalStateException("Cannot transition from " + order.getStatus() + " to " + newStatus);
    }
}

总结一下这种方式的好处:

  • 高内聚,低耦合:状态转换逻辑属于状态自身,Service层只负责调用,职责清晰。

  • 易于扩展:如果未来要增加一个“退款中”的状态,我们只需要在Enum中增加一个REFUNDING实例,并实现它的canTransitionTo方法即可,对现有代码的侵入性极小。

  • 代码自解释:代码本身就清晰地表达了业务规则,可读性极强。

这就是我们常说的,用面向对象和设计模式来编写高质量代码的一个典型例子。


Comments (0)

Continue Reading

企业级航空订单管理系统-课程培训材料

Published Jul 18, 2025

张阔 - 个人简介

Published Jul 18, 2025

企业级航空订单管理系统-讲师准备清单

Published Jul 19, 2025

企业级航空订单管理系统-课前准备材料

Published Jul 19, 2025

Spring Data JPA / Hibernate 配置

Published Jul 25, 2025