I. 整体系统架构与核心组件交互
本章节旨在建立一个宏观的系统视图,明确定义平台边界、构成组件及其交互协议。一个统一的架构愿景是后续深入各组件细节的基础,确保所有开发活动都服务于一个共同的、清晰的目标。
系统上下文与组件交互流程
从最高层级看,本平台是一个典型的三方市场生态系统。其核心参与者包括:平台管理员(通过PC后台进行管理)、终端用户(包含需求方与供应方,通过小程序交互)以及潜在的第三方合作伙伴(分佣体系的受益者)。系统的外部依赖主要包括微信开放平台(提供OAuth授权登录与支付网关)以及其他基础服务,如短信网关(用于通知)和对象存储服务(如阿里云OSS,用于存储用户上传的图片等静态资源)。
为了清晰地展示各组件如何协同工作,我们可以描绘一个核心用户旅程的交互流程:
- 认证与授权:需求方用户在uni-app客户端发起登录请求。客户端调用微信接口获取临时code,并将其发送至Java后端API。
- API处理:后端API接收到code后,与微信服务器通信,换取用户的openid和session_key,完成身份验证。随后,后端生成一个符合JWT(JSON Web Token)标准的token,其中包含了用户的ID、角色和权限信息,并将其返回给客户端。
- 客户端会话:uni-app客户端安全地存储此token,并在后续所有对后端API的请求中,通过HTTP的Authorization头(Bearer <token>)携带它。
- 业务操作:需求方在小程序中浏览服务、选择供应方、创建订单。这些操作均转化为对后端RESTful API的调用。例如,创建一个订单会向/api/v1/orders端点发送一个POST请求。
- 数据持久化:后端API的业务逻辑层(Service Layer)处理这些请求,与数据库进行交互(通过Repository Layer),完成订单数据的创建、状态更新等操作。
- 管理与监控:平台管理员通过Vue 3构建的PC后台,同样通过调用后端API来查看订单、管理用户、审核供应方资质、处理财务结算等。所有前端(小程序和PC后台)与后端之间的通信均遵循统一的API规范。
通信协议与数据格式
为确保系统各部分之间通信的标准化和高效性,兹规定如下:
- 通信协议:所有客户端(Vue管理后台、uni-app小程序)与后端服务器之间的通信必须通过HTTPS协议进行,以保障数据传输过程中的机密性和完整性。
- API风格:后端需提供完全遵循RESTful设计原则的API。
- 数据格式:所有API请求和响应的数据交换格式统一采用JSON。为了规范客户端的响应处理逻辑,所有API响应都应封装在一个标准结构体中。这种设计使得客户端可以编写统一的逻辑来处理成功、失败和异常情况。示例如下:
- JSON
- { “code”: 200, // 业务状态码,例如200表明成功,50001表明特定业务错误 “message”: “操作成功”, // 对状态码的可读性描述 “data”: {… } // 实际的业务数据载体,可以是对象或数组 }
整合技术栈
下表详细列出了构建此平台所涉及的所有技术、框架和关键库的推荐选型与版本。制定此统一技术栈旨在消除环境不一致性,简化新成员的 onboarding 流程,并为持续集成/持续部署(CI/CD)流程奠定坚实基础。
|
类别 |
组件/库 |
推荐版本 |
rationale/关键角色 |
|
后端 |
JDK |
Java 17 |
使用LTS版本,利用其新特性如Records、Sealed Classes等。 |
|
Spring Boot |
3.x |
快速构建、自动配置、内嵌Web服务器,是现代Java应用的实际标准。 |
|
|
Spring Security |
6.x |
业界领先的安全框架,用于实现OAuth 2.0和RBAC权限控制。 |
|
|
Spring Data JPA |
3.x |
简化数据库访问层,提供强劲的Repository抽象。 |
|
|
MySQL |
8.0+ |
成熟、稳定、社区支持广泛的关系型数据库。 |
|
|
Mybatis-Plus |
3.5+ |
在JPA之外提供更灵活的SQL控制,简化CRUD操作。 |
|
|
auth0-java-jwt |
4.x |
强烈推荐用于JWT的生成与验证,其流式API和对多种签名算法的强劲支持使其成为首选。 |
|
|
管理后台前端 |
Node.js |
18.x |
前端项目构建与运行环境。 |
|
Vue.js |
3.x |
现代化的前端框架,以其性能和开发体验著称。 |
|
|
Vite |
4.x |
下一代前端构建工具,提供极速的冷启动和热更新。 |
|
|
Element Plus |
2.x |
专为Vue 3设计的高质量UI组件库。 |
|
|
Pinia |
2.x |
Vue 3的官方推荐状态管理库,API更简洁,对TypeScript支持更友善。 |
|
|
Axios |
1.x |
成熟可靠的HTTP客户端,用于与后端API通信。 |
|
|
小程序端 |
uni-app |
最新稳定版 |
使用Vue语法开发,可一次性编译到多个平台(微信小程序、App等)。 |
|
Vue.js |
3.x |
uni-app现已全面支持Vue 3,享受Composition API带来的开发便利。 |
|
|
uView UI / Thor UI |
– |
专为uni-app设计的高质量UI框架,加速小程序页面开发。 |
|
|
DevOps |
Maven / Gradle |
最新稳定版 |
Java项目依赖管理与构建工具。 |
|
Docker |
最新稳定版 |
实现开发、测试、生产环境的一致性,简化部署。 |
|
|
Jenkins / GitLab CI |
– |
用于实现自动化构建、测试和部署的CI/CD流水线。 |
II. 领域模型:核心实体及其相互关系
领域模型是应用程序的 концептуальное сердце,它将业务需求转化为准确的数据结构和关系。一个设计精良的领域模型不仅是数据库 schema 的基础,更是保证系统长期可维护性和可扩展性的基石。
实体-关系设计(ERD)
系统的核心实体关系可以通过一个详细的实体-关系图(ERD)来可视化。该图将清晰地展示所有主要数据表,以及它们之间通过主键和外键建立的关联。这将是数据库开发人员的首要参考文档。
核心实体详解
以下是对平台核心业务对象的详细定义:
- 用户与认证模块 (users, roles, permissions, user_roles, role_permissions)
- users: 基础用户表,存储所有角色的通用信息,如openid、昵称、头像、手机号等。关键字段是user_type(枚举类型:DEMANDER, SUPPLIER, ADMIN, PARTNER),用于区分用户基本类别。
- roles: 角色表,定义系统中的角色(如:超级管理员、普通管理员、供应方、需求方)。
- permissions: 权限表,存储原子性的操作权限,如order:read, user:delete。
- user_roles & role_permissions: 联结表,用于实现用户-角色和角色-权限的多对多关系,共同构成了RBAC(基于角色的访问控制)系统的基础。
- 服务与供应方模块 (service_categories, services, provider_profiles, provider_service_links)
- service_categories: 服务类目表,用于对服务进行分组,如“家政服务”、“陪诊服务”、“陪玩娱乐”,支持平台业务的横向扩展。
- services: 具体服务表,定义了平台提供的每一个服务项,如“日常保洁”、“三甲医院陪诊”。
- provider_profiles: 供应方档案表,存储供应方特有的信息,如实名认证状态、专业技能证书、服务评分、接单开关等。此表通过外键与users表关联。
- provider_service_links: 供应方与服务的关联表,表明某个供应方可以提供哪些具体服务。
- 订单与交易模块 (orders, order_items, transactions)
- orders: 核心订单表,记录了交易的整个生命周期。包含关键信息如订单号、需求方ID、供应方ID、订单总金额、订单状态(PENDING_PAYMENT, PAID, ACCEPTED, IN_PROGRESS, COMPLETED, CANCELLED, SETTLED)等。
- order_items: 订单项表,与orders是一对多关系,记录订单中包含的具体服务项、数量、单价。
- transactions: 交易流水表,记录所有与订单相关的支付、退款等金融交易信息。
- 财务与分佣模块 (wallets, commission_ledgers, payout_records)
- wallets: 用户钱包表,记录供应方和第三方合作伙伴的可用余额。
- commission_ledgers: 佣金分类账。这是一个至关重大的审计表,以不可变记录的形式,准确记录每一笔订单结算时平台抽成、合作伙伴分佣的具体计算过程和结果。
- payout_records: 提现记录表,记录供应方和合作伙伴发起的提现请求及其处理状态。
平台的一个核心需求是支持多种完全不同的业务类型(家政、陪诊、陪玩等)。每种业务对订单和服务有其独特的属性要求:家政服务需要“服务地址”和“房屋面积”,而陪玩服务则需要“游戏名称”和“服务时长”。如果在orders主表中为每一种可能性都添加列,将导致表结构迅速膨胀且充满大量NULL值,这是一种严重的设计缺陷,会极大地损害未来的可扩展性。
因此,一个更具前瞻性的架构决策是采用灵活的数据结构。orders和services表中应只包含所有业务类型共有的核心字段(如ID、状态、金额、用户ID等)。而将各业务特有的属性存储在一个metadata字段中,该字段在数据库层面应采用JSON或JSONB类型。这种设计模式允许平台在未来上线一个全新的服务品类——列如“宠物看护”——而完全不需要对数据库的表结构进行任何修改(migration)。平台只需在后台定义新服务所需的metadata结构,前端动态渲染相应的表单即可。这极大地提升了业务的灵敏性和系统的可扩展性。
实体-关系模型摘要表
下表是对数据库 schema 的文本化补充,为后端开发者提供了便捷的参考。
|
实体名称 |
关键属性 (及数据类型) |
关系 |
描述 |
|
User |
id: BIGINT PK, openid: VARCHAR, user_type: VARCHAR |
User (1) -> Orders (M) (as Demander) User (1) -> Orders (M) (as Supplier) User (1) -> ProviderProfile (1) |
系统的基础用户实体,所有参与者的入口。 |
|
Role |
id: INT PK, name: VARCHAR |
User (M) <-> Role (M) |
定义了用户在系统中的角色集合。 |
|
Permission |
id: INT PK, code: VARCHAR |
Role (M) <-> Permission (M) |
定义了具体的操作权限,如 order:create。 |
|
Service |
id: BIGINT PK, category_id: BIGINT, name: VARCHAR, price: DECIMAL |
ServiceCategory (1) -> Service (M) |
平台提供的具体服务项目。 |
|
ProviderProfile |
user_id: BIGINT PK/FK, status: VARCHAR, rating: DECIMAL |
User (1) -> ProviderProfile (1) |
供应方的详细档案和资质信息。 |
|
Order |
id: BIGINT PK, order_no: VARCHAR, demander_id: BIGINT, supplier_id: BIGINT, status: VARCHAR, total_amount: DECIMAL, metadata: JSON |
User (1) -> Orders (M) (Demander) User (1) -> Orders (M) (Supplier) |
核心交易实体,记录订单的完整生命周期和业务数据。 |
|
Wallet |
user_id: BIGINT PK/FK, balance: DECIMAL |
User (1) -> Wallet (1) |
存储供应方和合作伙伴的账户余额。 |
|
CommissionLedger |
id: BIGINT PK, order_id: BIGINT, type: VARCHAR, amount: DECIMAL |
Order (1) -> CommissionLedger (M) |
不可变的佣金计算审计记录。 |
III. 后端实施蓝图:基于Java 17与Spring Boot的面向服务架构
本节将详细阐述后端服务的架构设计,提供一个构建清晰、模块化、安全可靠的后端应用的规范性指南。
分层架构
为保证代码的清晰度和职责分离,后端应用将严格遵循经典的四层架构模型:
- Controller Layer (表明层): 使用@RestController注解。该层是API的入口,负责处理HTTP请求、解析参数、进行数据校验(利用jakarta.validation.constraints注解)、序列化和反序列化JSON数据。它不包含任何业务逻辑,而是将处理委托给Service层。
- Service Layer (业务逻辑层): 使用@Service注解。这是应用的核心,包含了所有的业务逻辑。它负责编排对一个或多个Repository的调用,处理复杂的业务规则,并管理事务。事务边界应通过@Transactional注解在Service方法上声明式地定义。
- Repository Layer (数据访问层): 使用@Repository注解。该层是数据持久化的抽象,通过继承Spring Data JPA的接口(如JpaRepository)来实现。它定义了对数据库的CRUD(创建、读取、更新、删除)操作以及更复杂的自定义查询。
- Domain/Entity Layer (领域模型层): 这是与数据库表映射的POJO(Plain Old Java Objects)或Java 17的Records。它们使用JPA注解(如@Entity, @Table, @Id)来定义对象-关系映射(ORM)。
Java 17 最佳实践
Java 17作为长期支持(LTS)版本,其引入的新特性可以显著提升代码质量和开发效率。特别地,record类型超级适合用于创建不可变的数据传输对象(DTOs)。DTOs是在各层之间,尤其是Controller和Service层之间传递数据的载体。使用record可以极大地减少样板代码(如构造函数、getters、equals、hashCode、toString),同时通过其固有的不可变性,增强了程序的健壮性和线程安全性,有效防止了数据在处理链中被意外修改。
应用示例:
Java
// 使用Record定义一个用于创建订单的DTO
public record OrderCreationRequest(
@NotNull Long serviceId,
@Size(max = 255) String notes,
@NotNull Long addressId
) {}
// 在Controller中使用
@PostMapping
public ResponseEntity<OrderDto> createOrder(@Valid @RequestBody OrderCreationRequest request) {
return ResponseEntity.ok(orderService.create(request));
}
模块化项目结构
为了提高代码的可维护性和内聚性,项目的文件结构将按业务功能(feature)进行组织,而非按技术分层(layer)。这种方式将与特定功能相关的所有代码(Controller, Service, Repository, DTOs)都放在同一个包下,使得开发者在开发或修改某个功能时,能够更容易地定位和理解相关代码。
com.platform.marketplace
├── config/ // Spring Boot配置类 (SecurityConfig, JacksonConfig, etc.)
├── security/ // OAuth2.0, JWT工具类, UserDetailsService实现
│ ├── jwt/
│ └── service/
├── common/ // 通用工具类, 全局异常处理, 基础枚举, 响应封装
│ ├── exception/
│ └── dto/
├── modules/ // 核心业务模块
│ ├── user/ // 用户模块
│ │ ├── UserController.java
│ │ ├── UserService.java
│ │ ├── UserRepository.java
│ │ ├── model/
│ │ │ └── User.java
│ │ └── dto/
│ │ ├── UserDto.java
│ │ └── UserProfileUpdateRequest.java
│ ├── order/ // 订单模块
│ │ ├── OrderController.java
│ │ ├── OrderService.java
│ │ ├── OrderRepository.java
│ │ ├── model/
│ │ │ └── Order.java
│ │ └── dto/
│ │ └── OrderCreationRequest.java
│ ├── payment/ // 支付模块
│ └──... // 其他业务模块, 如服务、财务等
└── MarketplaceApplication.java // Spring Boot启动类
核心RESTful API端点定义
下表定义了部分核心API端点,它将作为前后端团队之间的“契约”,确保并行开发过程中的顺利集成。
|
HTTP方法 |
URI |
描述 |
所需角色/权限 |
请求体 (DTO) |
成功响应 (DTO) |
|
POST |
/api/v1/auth/wechat-login |
微信小程序登录 |
PUBLIC |
{ “code”: “…” } |
{ “token”: “…”, “userInfo”: {… } } |
|
GET |
/api/v1/services |
获取服务列表(可带筛选/分页) |
DEMANDER, SUPPLIER |
N/A |
Page<ServiceDto> |
|
POST |
/api/v1/orders |
需求方创建订单 |
DEMANDER |
OrderCreationRequest |
OrderDetailsDto |
|
GET |
/api/v1/orders/{orderId} |
获取订单详情 |
DEMANDER (own), SUPPLIER (own), ADMIN |
N/A |
OrderDetailsDto |
|
PUT |
/api/v1/orders/{orderId}/accept |
供应方接受订单 |
SUPPLIER |
N/A |
OrderDetailsDto |
|
GET |
/api/v1/users/me |
获取当前用户信息 |
AUTHENTICATED |
N/A |
UserDto |
|
PUT |
/api/v1/provider/profile |
供应方更新个人资料 |
SUPPLIER |
ProviderProfileUpdateRequest |
ProviderProfileDto |
|
GET |
/api/v1/admin/users |
管理员获取用户列表 |
ADMIN |
N/A |
Page<UserAdminViewDto> |
IV. 平台管理控制台:Vue 3前端架构
本节将详细介绍为平台管理员设计的PC端Web界面的架构,重点关注其项目结构、状态管理,以及实现基于权限动态渲染UI的关键技术。
项目结构 (Vue 3 + Vite)
一个可扩展、易于维护的前端项目始于一个清晰的目录结构。
frontend-admin/
├── public/ // 静态资源,不会被Vite处理
├── src/
│ ├── api/ // API服务模块 (e.g., user.js, order.js),封装Axios请求
│ ├── assets/ // 项目资源 (CSS, images, fonts)
│ ├── components/ // 全局可复用UI组件 (e.g., SvgIcon, Pagination, Breadcrumb)
│ ├── layouts/ // 应用布局组件 (e.g., DefaultLayout.vue 包含侧边栏、头部和主内容区)
│ ├── router/ // Vue Router配置 (index.js, routes.js)
│ ├── store/ // Pinia状态管理模块 (e.g., user.js, permission.js)
│ ├── utils/ // 通用工具函数 (e.g., request.js 封装Axios, validation.js)
│ ├── views/ // 页面级组件,按业务模块组织
│ │ ├── dashboard/
│ │ ├── order/
│ │ └── user/
│ ├── App.vue // 根组件
│ └── main.js // 应用入口文件
├──.env.development // 开发环境变量
├──.env.production // 生产环境变量
├── package.json
└── vite.config.js
动态路由与菜单生成
实现一个根据用户权限动态展示菜单和限制页面访问的系统,是管理后台的核心需求。实则现流程如下:
- 用户登录:管理员成功登录后,后端API在返回JWT的同时,会一并返回该用户的角色列表以及一个扁平化的权限标识符数组(例如:['dashboard:view', 'order:list', 'order:edit', 'user:manage'])。
- 权限存储:前端将获取到的角色和权限信息持久化存储在Pinia的permission store中。
- 路由定义:在router/routes.js中,定义一个包含系统中所有可能路由的“全量路由表”。每个需要权限控制的路由对象都应包含一个meta字段,用于声明访问该路由所需的权限,例如:meta: { permission: 'order:list' }。
- 路由过滤:在Vue Router的全局前置守卫 (router.beforeEach) 中,执行核心的权限过滤逻辑。当用户首次登录或刷新页面时,守卫会从permission store中获取用户权限列表,然后递归地遍历全量路由表,筛选出用户有权访问的路由,生成一个“动态路由表”。
- 动态添加路由:使用router.addRoute()将生成的动态路由表添加到路由实例中。
- 菜单渲染:侧边栏菜单组件的数据源直接绑定到这个动态生成的、可访问的路由表。通过v-for指令遍历此路由表,即可渲染出用户有权访问的菜单项。
这种设计确保了用户不仅无法通过URL直接访问未授权的页面(会被路由守卫拦截),甚至在UI层面根本看不到未授权页面的入口,提供了极佳的安全性和用户体验。
不过,一个仅在登录时加载权限的系统存在一个体验上的缺陷。设想一个场景:一位在线的管理员(AdminA)正在使用系统,此时另一位超级管理员(SuperAdmin)通过后台撤销了AdminA的“用户删除”权限。在基础实现中,AdminA的界面不会有任何变化,他依旧能看到并点击“删除用户”按钮。尽管后端的API调用会由于权限不足而被拒绝(返回403 Forbidden),但这无疑造成了用户的困惑。
为了构建一个真正专业和响应式的系统,可以引入实时通信机制。当SuperAdmin修改权限时,后端可以通过WebSocket或Server-Sent Events (SSE)向所有相关的、当前处于活动状态的客户端推送一条“权限更新”的消息。前端的Vue应用在监听到此事件后,会执行以下操作:
- 主动向后端请求最新的权限信息。
- 更新Pinia中的permission store。
- 重新执行路由过滤和生成逻辑,更新动态路由表。
- 由于菜单和页面内的操作按钮(如“删除”按钮,其可见性一般由v-if或自定义指令v-permission控制)都响应式地依赖于permission store,UI会自动更新,相关的菜单项或按钮会实时消失。
- 这个优化方案无需用户重新登录或刷新页面,即可实现权限变更的即时生效,将系统从“功能完备”提升到“体验卓越”的层次。
V. 统一小程序端:基于uni-app的跨平台策略
本节将详细阐述小程序端的架构设计。作为项目中最为复杂的客户端,它需要在一个代码库中为两个截然不同的用户角色(需求方与供应方)提供服务。设计的核心在于角色管理、状态隔离和代码组织。
项目结构 (uni-app + Vue 3)
为了防止角色特定逻辑的混乱和耦合,必须采用一个经过精心组织的目录结构。
uniapp-client/
├── common/ // 全局通用资源
│ ├── js/ // 通用JS模块 (request.js, constants.js, utils.js)
│ └── style/ // 全局样式 (main.scss)
├── components/ // 可复用组件
│ ├── shared/ // 角色共享组件 (e.g., OrderCard.vue, PriceDisplay.vue)
│ ├── demander/ // 需求方专属组件 (e.g., ServiceSearch.vue, ProviderCard.vue)
│ └── supplier/ // 供应方专属组件 (e.g., OrderAcceptanceModal.vue, DashboardStat.vue)
├── pages/ // 页面文件
│ ├── common/ // 角色共用页面 (e.g., login, profile, settings)
│ ├── demander/ // 需求方专属页面 (e.g., home, search, create-order, order-list)
│ └── supplier/ // 供应方专属页面 (e.g., dashboard, my-services, order-queue, wallet)
├── static/ // 静态资源 (图片, 图标)
├── store/ // Pinia状态管理
│ ├── user.js // 用户信息、角色、Token管理
│ └── order.js // 订单列表、详情状态管理
├── App.vue // 应用生命周期
├── main.js // Vue初始化
├── manifest.json // 应用配置文件
└── pages.json // 页面路由和窗口表现配置,至关重大
基于角色的渲染与路由
为两个角色创建截然不同的用户体验,需要综合运用多种技术:
- 动态入口页面:用户登录成功后,应用会从后端获取其角色信息并存储在Pinia的user store中。随即,应用会根据userStore.role的值,调用uni.reLaunch()方法,强制跳转到对应角色的主页(例如,需求方跳转至/pages/demander/home,供应方跳转至/pages/supplier/dashboard)。reLaunch会关闭所有其他页面,只打开目标页面,确保用户进入正确的应用上下文。
- 条件化标签栏 (Tab Bar):微信小程序允许在pages.json中定义tabBar。为了给不同角色展示不同的底部导航栏,可以利用uni-app的条件编译特性。在pages.json中,可以为不同角色定义不同的list数组,并使用编译指令进行切换。一个更灵活的运行时方案是,在App.vue的onLaunch或onShow生命周期中,根据用户角色动态调用uni.setTabBarItem来设置每个标签项的文本、图标和页面路径,从而实现完全动态的tabBar。
- 组件级条件渲染:在页面和组件内部,大量使用v-if指令来根据用户角色显示或隐藏特定的UI元素。例如,在订单详情页,一个“接受订单”的按钮应该只对供应方可见:<button v-if=”userStore.isSupplier”>接受订单</button>。
单纯依赖一个全局的isSupplier布尔标志来驱动整个应用的UI和逻辑,虽然简单,但存在脆弱性。这个单一的状态点成为了整个系统的关键瓶颈,任何地方的错误状态变更都可能导致灾难性的UI错乱——例如,一个需求方用户界面上突然渲染出了供应方的接单按钮。尽管后端API的权限控制可以阻止未授权的操作,但客户端的这种混乱表现是严重的设计缺陷。
一种更为健壮和现代的设计模式是采用Vue 3的Composition API,实现“状态组合”。与其依赖一个全局标志,不如为每个角色创建专属的、可组合的函数(Composables)。
- useSupplierDashboard.js: 封装获取供应方仪表盘数据(如今日收入、待接订单数)的逻辑和响应式状态。
- useDemanderOrderFlow.js: 封装需求方创建和跟踪订单的完整流程,包括服务选择、价格计算等状态和方法。
然后,在页面组件中,根据用户角色来决定调用哪个Composable:
代码段
// /pages/supplier/dashboard.vue
import { useSupplierDashboard } from '@/composables/useSupplierDashboard';
export default {
setup() {
const { stats, loading, fetchDashboardData } = useSupplierDashboard();
onMounted(fetchDashboardData);
return { stats, loading };
}
}
这种方法将角色特定的逻辑和状态内聚在各自的模块中,使得代码更加清晰、易于测试,并且极大地降低了因单一状态突变而引发的级联失败风险。它充分利用了Composition API的优势,是构建复杂、多角色应用的更优选择。
VI. 安全与访问控制:实施OAuth 2.0与精细化RBAC框架
对于一个处理用户数据和金融交易的平台,安全架构是其生命线。本节将深入探讨认证和授权的实现细节。
认证流程 (基于微信的OAuth 2.0)
用户的身份认证将通过与微信开放平台集成的OAuth 2.0授权码模式(Authorization Code Grant)来完成,具体流程如下:
- 获取临时登录凭证:uni-app客户端调用uni.login(),微信客户端会返回一个有时效性且一次性的code。
- 后端换取用户标识:客户端将此code发送至后端自定义的登录接口,例如/api/v1/auth/wechat-login。
- 服务器端验证:后端服务器收到code后,携带appid和appsecret,向微信的auth.code2Session接口发起请求,用code换取用户的openid(用户在小程序内的唯一标识)和session_key(会话密钥)。
- 用户身份绑定/创建:后端使用openid在users表中查询用户。如果用户已存在,则检索其信息;如果不存在,则为其创建一个新的用户记录,并可能标记为初始状态。
- 签发平台Token:身份确认后,后端服务器生成一个自定义的JWT。此JWT的Payload中应包含关键信息,如userId、openid、roles(角色数组)和permissions(权限数组)。
- 客户端持久化会话:客户端接收到JWT后,通过uni.setStorageSync将其安全地存储在本地。在后续的每一次API请求中,客户端都必须在HTTP Authorization头部附带此token,格式为Bearer <JWT>。
后端授权 (Spring Security)
Spring Security 6+版本推荐使用基于组件的配置方式,通过定义SecurityFilterChain类型的Bean来构建安全策略,这种方式比旧版的
WebSecurityConfigurerAdapter更模块化、更清晰。
实现步骤:
- 配置SecurityFilterChain: 创建一个@Configuration类,定义一个或多个@Bean方法返回SecurityFilterChain。在此配置链中,可以:
- 禁用CSRF(由于我们使用JWT,而非基于session的认证)。
- 配置CORS(跨域资源共享)。
- 设置会话管理策略为无状态(STATELESS)。
- 定义哪些URL路径是公开的(如登录、注册接口),哪些需要认证。
- 添加一个自定义的JWT认证过滤器。
- JWT认证过滤器: 这个自定义过滤器将继承OncePerRequestFilter。它的核心职责是:
- 从Authorization头中提取JWT。
- 使用预先配置的密钥和算法验证JWT的签名和时效性。
- 如果验证通过,从JWT的Payload中解析出用户信息(userId, roles, permissions)。
- 将这些信息封装成一个UsernamePasswordAuthenticationToken(或其他Authentication实现),并设置到SecurityContextHolder.getContext()中。
- 这样,后续的业务代码和安全框架就能从安全上下文中获取到当前认证用户的信息。
- 方法级别安全: 仅在过滤器层面进行认证是不够的,还需要精细化的授权。通过在主配置类上添加@EnableMethodSecurity注解,可以启用方法级别的安全控制。然后,可以在Service层的方法上使用强劲的注解来进行授权决策:
- @PreAuthorize(“hasRole('ADMIN')”): 只允许拥有ADMIN角色的用户调用。
- @PreAuthorize(“hasAuthority('order:create')”): 只允许拥有order:create权限的用户调用。
- @PreAuthorize(“#order.userId == authentication.principal.id”): 这是一个更复杂的SpEL(Spring Expression Language)表达式,用于检查当前用户是否是某个对象(如此处的order)的所有者,实现数据级别的权限控制。
将授权逻辑下沉到Service层,并使用声明式注解,比在Controller层编写大量if-else判断要安全、清晰和易于维护得多。
RBAC角色-权限矩阵示例
下表是一个简化的角色-权限矩阵,它是在设计阶段明确各方职责、与产品经理和测试团队沟通的关键工具。这个过程有助于在编码前发现潜在的权限设计漏洞。
|
权限 (Permission) |
需求方 (DEMANDER) |
供应方 (SUPPLIER) |
第三方伙伴 (PARTNER) |
管理员 (ADMIN) |
超级管理员 (SUPER_ADMIN) |
|
service:list |
✅ |
✅ |
❌ |
✅ |
✅ |
|
order:create |
✅ |
❌ |
❌ |
❌ |
❌ |
|
order:list:own |
✅ |
✅ |
❌ |
❌ |
❌ |
|
order:accept |
❌ |
✅ |
❌ |
❌ |
❌ |
|
profile:edit:own |
✅ |
✅ |
✅ |
❌ |
❌ |
|
wallet:view:own |
❌ |
✅ |
✅ |
❌ |
❌ |
|
payout:request |
❌ |
✅ |
✅ |
❌ |
❌ |
|
user:list:all |
❌ |
❌ |
❌ |
✅ |
✅ |
|
user:edit:any |
❌ |
❌ |
❌ |
✅ |
✅ |
|
role:manage |
❌ |
❌ |
❌ |
❌ |
✅ |
|
finance:settle |
❌ |
❌ |
❌ |
✅ |
✅ |
VII. 交易与分佣引擎:金融工作流设计
这是系统中风险最高、对精度要求最苛刻的部分。设计必须将准确性、可审计性和操作原子性置于首位。
金融状态机
订单的status字段是驱动所有金融操作的状态机。一个清晰的状态流转是保证逻辑正确性的前提:
PENDING_PAYMENT (待支付) -> PAID (已支付) -> SERVICE_COMPLETED (服务完成) -> SETTLED (已结算)
金融交易(如资金冻结、解冻、分账)必须严格绑定在这些状态转换的事件上。
原子化结算流程
当一个订单被标记为SERVICE_COMPLETED后,触发的结算流程必须是一个不可分割的原子操作。这意味着,流程中的所有数据库更新要么全部成功,要么在任何一步失败时全部回滚到初始状态,绝不允许出现资金已划拨但订单状态未更新的中间状态。Spring框架的@Transactional注解为实现这种声明式事务管理提供了强劲的支持。
一个典型的settleOrder(orderId)方法的事务性操作序列如下:
- 前置校验: 在事务开始时,锁定并重新查询该Order记录,严格校验其状态是否为SERVICE_COMPLETED。
- 佣金计算:
- 根据订单总金额和平台预设的抽成规则,计算平台佣金。
- 计算供应方应得收入(订单总金额 – 平台佣金)。
- 如果订单关联了第三方合作伙伴(partnerId不为空),则根据分佣规则计算合作伙伴的佣金。
- 账务处理:
- 从一个虚拟的“平台在途资金”账户中,借记(Debit)订单总金额。
- 向供应方的Wallet中,贷记(Credit)其应得收入。
- 向合作伙伴的Wallet中,贷记(Credit)其佣金。
- 向平台的“收入”账户中,贷记(Credit)平台佣金。
- 审计记录: 为上述每一笔资金变动,在commission_ledgers表中创建一条或多条详细的、不可变的记录,内容包括订单ID、关联用户、变动类型(平台费、供应商收入等)、金额、计算时间等。
- 状态变更: 最后,将Order的状态更新为SETTLED。
所有这些操作都封装在同一个被@Transactional注解的方法中,确保了整个结算过程的数据一致性。
接口幂等性
金融系统的API必须具备幂等性。这意味着客户端(无论是前端应用还是后台定时任务)因网络抖动等缘由重复调用同一个接口时,系统应能保证业务结果与单次调用完全一致,绝不能出现重复结算或扣款的情况。
实现结算接口幂等性的常用方法是引入唯一的交易ID。例如,每次结算尝试都可以生成一个唯一的settlement_transaction_id。在settleOrder业务方法的开始,第一检查是否存在具有此ID的成功结算记录。如果已存在,则直接返回成功结果,而不执行任何业务逻辑。
财务模型的深度考量
在设计财务系统时,一个常见的误区是仅使用一个Wallet表来记录用户的余额。这种设计虽然简单,但存在致命缺陷:它只记录了账户的当前状态,却丢失了所有导致这个状态的历史过程。当出现账目纠纷时,管理员将无法追溯和审计任何一笔资金的来龙去脉。
一个健壮的财务系统必须借鉴复式记账法的核心思想,即同时维护“状态”和“流水”。
- Wallet表依然存在,用于快速查询用户当前可用余额,优化性能。
- 但系统的“实际之源”(Source of Truth)必须是一个不可变的、只增不减的TransactionLedger(交易总账)表。
每一次金融事件——无论是用户支付、平台抽成、供应商结算,还是退款——都必须在Ledger表中生成至少两条记录(一条借方,一条贷方),并确保同一笔交易的所有分录总金额为零。Wallet表中的balance字段,在逻辑上应被视为Ledger表中该用户所有交易流水的聚合结果。系统可以定期或在需要时,通过聚合Ledger记录来重新计算和校验Wallet的余额。这种设计提供了完全的审计能力,是任何处理资金的系统都不可或缺的。
VIII. 扩展性战略:面向未来的可伸缩性设计
本节将提供前瞻性的指导,确保平台在用户量和业务范围增长时,无需进行颠覆性的架构重构。
异步化操作
对于那些不需要立即返回结果给用户的非核心、耗时操作,应采用异步处理模型。通过引入消息队列(如RabbitMQ或Kafka),可以将这些任务与主应用流程解耦。
- 适用场景:
- 发送短信或App推送通知。
- 生成复杂的日/月度财务报表。
- 用户完成服务后,触发结算流程(可以将orderId放入队列,由专门的消费者进程处理)。
- 优势:
- 提升API响应速度: 主API线程可以立即向用户返回202 Accepted响应,而将实际工作交由后台处理,极大改善用户体验。
- 系统解耦与削峰填谷: 消息队列作为缓冲区,可以平滑处理突发的高并发请求,提高系统的整体稳定性和弹性。
数据库性能优化
随着数据量的增长,数据库将成为首要的性能瓶瓶颈。
- 索引策略: 必须制定主动的、有预见性的索引策略。除了主键和外键自动创建的索引外,还应为所有常常出目前WHERE子句中的列(如orders.status, users.openid)、以及用于排序(ORDER BY)和分组(GROUP BY)的列创建索引。
- 读写分离: 当平台发展到必定规模,读操作的压力将远超写操作。届时,应规划引入数据库读写分离架构。通过设置一个或多个只读副本(Read Replicas),可以将所有查询密集型操作(如浏览服务、查看历史订单、后台报表查询)都路由到副本库,从而将主库的资源完全释放给写操作,显著提升整个系统的吞吐量。
新业务垂直领域的扩展性
本方案在第二章领域模型设计中提出的核心思想——在orders和services表中使用JSON类型的metadata字段——是平台未来业务灵敏性的关键所在。它为平台低成本、快速地进入新业务领域铺平了道路。
以下是添加一个全新的“宠物看护”服务品类的路线图,它清晰地展示了该架构的威力:
- 后台定义: 平台管理员在管理后台的“服务类目”管理中,新增一个名为“宠物看护”的类目。
- 元数据 schema 定义: 管理员通过一个动态表单构建器UI,为“宠物看护”定义其特有的业务字段,例如:pet_type: string(宠物类型)、duration_days: integer(看护天数)、needs_special_care: boolean(是否需要特殊照顾)。这个表单定义本身以JSON schema的形式存储在数据库中。
- 小程序动态渲染: 当需求方在小程序中选择“宠物看护”服务并下单时,下单页面会第一从后端获取该服务的JSON schema,然后动态地渲染出对应的输入表单(一个文本框、一个数字输入框、一个开关)。
- 后端存储: 用户提交表单后,这些业务数据作为一个JSON对象,被完整地存储在orders表的metadata字段中。
整个过程,从定义新业务到用户可下单,完全不需要后端开发人员修改任何Java代码,也无需数据库管理员执行任何ALTER TABLE操作。这种基于元数据驱动的设计,是平台能够适应快速变化的市场需求、保持长期竞争力的核心技术保障。















暂无评论内容