# 项目介绍 Tagtag Starter 是一个基于 Java 和 Spring Boot 3 构建的通用、模块化后台管理框架。它旨在为构建商业应用提供一个轻量、坚实的基础,帮助开发者快速搭建高质量的后台管理系统。 ![Tagtag Starter - Analytics](https://tagtag.cn/images/docs/1.getting-started/analytics.png) Tagtag Starter 的定位非常明确:**做最适合新项目的起步脚手架**。我们不追求大而全的复杂功能,而是专注于提供一个**功能简洁、上手简单**的开发底座,让开发者能够专注于业务逻辑的实现,而不是和配置文件斗智斗勇。 ## 核心特性 - **模块化架构**:系统被拆分为多个功能明确的模块(`common`、`framework`、`kernel`、`contract`、`module` 等),结构清晰,易于维护和扩展。就像乐高积木一样,想怎么搭就怎么搭。 - **契约优先设计**:使用 `contract` 模块将 API 定义与实现分离,代码结构更规范,便于前后端协作和接口管理。前后端再也不用因为接口定义吵架了。 - **完备的权限体系**:内置基于 RBAC(基于角色的访问控制)的权限管理系统,支持菜单权限、按钮权限和数据权限。谁该看什么、能做什么,一清二楚。 - **丰富的核心功能**:内置操作日志、接口限流、字典管理、文件存储等核心功能,满足大多数后台系统的需求。开箱即用,不用自己造轮子。 - **现代技术栈**:后端基于 Spring Boot 3、MyBatis Plus 和 Redis 构建,前端基于 Vue 3 + TypeScript + Vite 构建,采用 Vben Admin 5 框架。用最新的技术,写最优雅的代码。 - **灵活的扩展机制**:模块化设计使得新增业务功能只需创建一个新的 Maven 模块,不会污染现有核心代码。想加什么功能就加什么,不用担心把核心搞坏了。 ## 为什么选择 Tagtag Starter? 在众多的后台管理框架中,Tagtag 具有以下独特优势: ### 1. 极致简洁,快速上手 - **拒绝过度设计**:没有复杂的微服务架构负担,没有难以理解的抽象层。Tagtag 保持了代码的纯净与直观,您可以在几分钟内跑通项目,而不是花几天时间研究配置。毕竟,我们的目标是写业务代码,不是研究框架源码。 - **新项目的最佳选择**:当您需要开启一个新的后台项目时,Tagtag 提供了最纯粹的起点。它没有预装成吨的冗余代码,让您能够轻装上阵,专注于业务逻辑的实现。就像买新车一样,不需要先拆掉一堆用不上的配件。 - **清晰的文档**:提供完整、清晰的文档,帮助开发者快速上手和深入理解框架。我们相信,好的文档比好的代码更重要(当然,我们的代码也不错)。 ### 2. 恰到好处的功能集 - **只给您最需要的**:我们仅内置了几乎所有后台系统都必须的基础功能——用户管理、角色权限、菜单配置、操作日志、接口限流、字典管理和文件存储。不多不少,刚刚好。 - **无复杂集成**:没有预装一大堆可能永远用不上的中间件和第三方库。保持代码库的轻盈,意味着更少的维护成本和更低的各种依赖冲突风险。毕竟,谁也不想因为一个不常用的依赖把整个项目搞崩。 - **可按需扩展**:您可以根据项目需求,轻松扩展新的功能模块。就像点外卖一样,想吃啥就加啥,不用强制消费。 ### 3. 清晰的代码结构 - **易于理解与扩展**:基于标准的 Spring Boot 结构,结合模块化设计,使得代码逻辑一目了然。即使是新手开发者也能快速看懂并上手开发,方便团队在此基础上进行二次定制。不用像读天书一样研究代码。 - **规范的开发模式**:内置了统一的响应封装、异常处理和日志规范,为团队协作建立了一套标准化的开发模版。团队成员再也不用因为代码风格问题争论不休了。 - **优秀的代码质量**:遵循严格的代码规范,代码结构清晰,注释完善,便于维护和扩展。我们相信,好的代码是会说话的。 ### 4. 拥抱现代技术栈 - **后端技术栈**:基于 Spring Boot 3 + JDK 17 构建,集成 MyBatis Plus、Redis、Spring Security、JWT 等主流技术。用最新的技术,写最稳的代码。 - **前端技术栈**:基于 Vue 3 + TypeScript + Vite 构建,采用 Vben Admin 5 框架,使用 Tailwind CSS 和 Shadcn UI 进行界面开发。界面美观,交互流畅,用户体验一级棒。 - **持续更新**:我们会持续关注和采用最新的技术栈,确保框架始终保持现代化。毕竟,技术发展这么快,我们可不能掉队。 ### 5. 灵活的模块化扩展 - **按需扩展**:得益于清晰的模块划分,新增业务功能只需创建一个新的 Maven 模块,不会污染现有核心代码。就像搭积木一样,想加什么功能就加什么。 - **即插即用**:各模块职责分明,耦合度低。无论是集成新的第三方服务,还是拆分现有业务逻辑,模块化的设计都能让扩展变得轻松自如。不用牵一发而动全身。 - **松耦合设计**:模块之间通过契约层进行通信,降低了模块间的依赖关系,提高了系统的灵活性和可维护性。改一个模块,不用担心把其他模块搞坏。 ## 使用场景 Tagtag 适合以下类型的项目: - **企业内部管理系统**:如 CRM、ERP、OA 等企业内部管理系统。让企业运营更高效,让员工工作更轻松。 - **SaaS 平台**:适合构建多租户 SaaS 平台的后台管理系统。一套系统,服务多个客户,省钱又省心。 - **电商后台**:适合构建电商平台的后台管理系统。商品管理、订单处理、数据分析,一应俱全。 - **内容管理系统**:适合构建各种内容管理系统。文章、图片、视频,想管理什么就管理什么。 - **API 管理平台**:适合构建 API 管理和监控平台。接口文档、接口测试、接口监控,一站式搞定。 - **其他后台管理系统**:适合构建各种类型的后台管理系统。只要是后台管理系统,Tagtag 都能胜任。 Tagtag 不适合以下类型的项目: - **高并发、高流量的互联网应用**:如社交网络、直播平台等。这种级别的项目,还是用专业的分布式架构吧。 - **复杂的微服务架构项目**:如果您的项目需要复杂的微服务架构,Tagtag 可能不是最佳选择。我们专注于单体应用,简单才是美。 - **需要大量定制化 UI 的项目**:如果您需要高度定制化的 UI 设计,可能需要对前端代码进行大量修改。不过,我们的 UI 已经很漂亮了,您可能根本不需要定制。 ## 快速开始 想要快速上手 Tagtag Starter?请查看我们的 [快速开始指南](https://tagtag.cn/getting-started/quick-start),您可以在几分钟内跑通整个项目。相信我,比煮一碗泡面还快。 ## 架构设计 Tagtag 采用前后端分离的架构设计,后端基于 Java 和 Spring Boot 3 构建,前端基于 Vue 3 + TypeScript + Vite 构建。想要了解更多关于 Tagtag 的架构设计,请查看我们的 [架构文档](https://tagtag.cn/architecture/overview)。我们的架构设计就像搭积木一样清晰,保证你看完就能懂。 ## 社区与贡献 Tagtag 是一个开源项目,我们欢迎社区的贡献和反馈。如果您有任何问题或建议,欢迎在 GitHub 上提交 Issue 或 Pull Request。我们承诺,每一个 Issue 都会认真对待,每一个 PR 都会仔细 review。 - **GitHub 仓库**:{rel="nofollow"} - **文档地址**:{rel="nofollow"} --- 感谢您对 Tagtag Starter 的关注和支持!我们相信,Tagtag Starter 会成为您构建后台管理系统的得力助手。选择 Tagtag Starter,就是选择简单、高效、优雅的开发体验。让我们一起,用最少的代码,实现最大的价值。 # 快速上手 本指南将帮助您在本地机器上搭建 Tagtag Starter 的后端服务和前端应用。别担心,我们会一步一步带您完成整个过程,就像教小朋友走路一样简单。 ## 环境要求 确保您已安装以下软件。如果没有,赶紧去安装吧,别磨蹭了。 ### 后端 - **Java JDK 17+** - 没错,我们用的是最新的 Java,跟上时代潮流 - **Maven 3.8+** - 依赖管理的好帮手,不用自己手动下载 jar 包了 - **MySQL 8.0+** - 数据存储的基石,数据都靠它 - **Redis 6.0+** - 缓存加速的神器,让系统飞起来 ### 前端 - **Node.js >= 20.12.0** - 前端运行的基础环境 - **pnpm >= 10.0.0** - 比 npm 更快的包管理器,用过都说好 ## 安装步骤 ### 1. 克隆仓库 ```bash git clone https://github.com/tagtag-dev/tagtag-starter.git cd tagtag-starter ``` 这一步应该不会出问题吧?如果出问题了,检查一下您的网络连接,或者换个姿势试试。 ### 2. 初始化数据库 (后端) 1. **创建数据库**:在 MySQL 中创建一个名为 `tagtag` 的数据库。 ```sql CREATE DATABASE tagtag CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ``` 记住,数据库名字必须是 `tagtag`,别自己发挥改成别的名字,不然系统会找不到数据库的。 2. **自动初始化**:Tagtag Starter 使用 Spring Boot 的自动初始化功能,会在应用启动时自动执行数据库脚本。没错,就是这么智能,不用您手动导入 SQL 文件了。 **脚本位置**: - 表结构脚本:各模块下的 `src/main/resources/db/schema.sql` - 初始数据脚本:各模块下的 `src/main/resources/db/data/` 目录 **自动加载配置**(已在 `backend/tagtag-start/src/main/resources/application-dev.yml` 中配置): ```yaml spring: datasource: init: mode: always schema-locations: - classpath*:db/schema.sql data-locations: - classpath*:db/data/**.sql ``` 3. **初始数据说明**: - 管理员账号:`admin` / `admin123` - 超级管理员,拥有所有权限 - 普通用户账号:`user` / `user123` - 普通用户,权限有限 - 初始角色:超级管理员、管理员、普通用户 - 权限分级,各司其职 - 初始菜单:系统管理、用户管理、角色管理、菜单管理等 - 该有的都有了 记住这些初始账号密码,别到时候登录不上又来找我们。 ### 3. 配置应用 (后端) 编辑 `backend/tagtag-start/src/main/resources/application.yml` 以匹配您的本地环境(数据库凭据、Redis 主机)。 ```yaml spring: datasource: url: jdbc:mysql://localhost:3306/tagtag?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: your_password data: redis: host: localhost port: 6379 ``` 把 `your_password` 替换成您真实的数据库密码。别用默认密码,不然被黑客攻破了可别怪我们没提醒您。 ### 4. 启动后端服务 ```bash cd backend mvn clean package -DskipTests java -jar tagtag-start/target/tagtag-start-0.0.1-SNAPSHOT.jar ``` 或者,直接在您的 IDE 中运行 `TagtagApplication.java`。如果您用的是 IDEA,直接右键运行就行,不用我们教了吧? 等一会儿,看到控制台输出一堆日志,最后出现类似 "Started TagtagApplication in X.XXX seconds" 的信息,就说明启动成功了。恭喜您,后端服务已经跑起来了! ### 5. 启动前端应用 1. 进入前端目录: ```bash cd frontend ``` 2. 安装依赖: ```bash pnpm install ``` 这一步可能需要一点时间,毕竟要下载那么多依赖包。耐心等待,别急,好饭不怕晚。 3. 启动开发服务器: ```bash pnpm run dev ``` 看到类似 "Local: {rel="nofollow"}" 的信息,就说明前端也启动成功了。太棒了,前后端都跑起来了! ## 验证 启动成功后,您可以访问以下地址来验证系统是否正常运行: - **前端应用**: 访问 {rel="nofollow"} - 登录界面,用 admin/admin123 登录试试 - **后端 API 文档**: 访问 {rel="nofollow"} (Scalar) - 看看系统提供了哪些 API 接口 - **健康检查**: 访问 {rel="nofollow"} - 检查后端服务是否健康 如果这些地址都能正常访问,恭喜您,您已经成功搭建了 Tagtag Starter 系统!给自己鼓个掌吧! ## 常见问题与解决方案 如果不幸遇到了问题,别慌,我们准备了一份常见问题清单,看看能不能帮到您。 ### 1. 数据库连接失败 **问题描述**:应用启动时出现数据库连接错误。 **解决方案**: - 检查 MySQL 服务是否正常运行 - 先确认服务没挂 - 确认数据库名称、用户名和密码是否正确 - 别输错了 - 检查数据库地址和端口是否正确 - 默认是 localhost:3306 - 确认数据库用户具有足够的权限 - 给它足够的权限,别太抠门 ### 2. Redis 连接失败 **问题描述**:应用启动时出现 Redis 连接错误。 **解决方案**: - 检查 Redis 服务是否正常运行 - 确认 Redis 没挂 - 确认 Redis 地址和端口是否正确 - 默认是 localhost:6379 - 检查 Redis 密码(如果有设置) - 别忘了密码 - 确认 Redis 实例是否允许远程连接 - 检查一下防火墙设置 ### 3. 前端无法访问后端 API **问题描述**:前端应用无法连接到后端 API。 **解决方案**: - 检查后端服务是否正常运行 - 后端没跑起来,前端当然访问不了 - 确认前端配置的 API 地址是否正确 - 检查一下配置文件 - 检查 CORS 配置是否正确 - 跨域问题是个常见坑 - 检查防火墙设置,确保端口未被阻止 - 别让防火墙挡住了您的路 ### 4. 启动时出现端口冲突 **问题描述**:应用启动时出现端口已被占用的错误。 **解决方案**: - 修改后端端口:编辑 `application.yml` 中的 `server.port` 配置 - 换个端口试试 - 修改前端端口:编辑 `tagtag-ui/apps/tagtag/.env` 中的 `PORT` 配置 - 前端也可以换端口 - 查找并终止占用端口的进程 - 谁占用了端口,把它干掉 ### 5. Maven 依赖下载失败 **问题描述**:执行 `mvn clean package` 时出现依赖下载失败。 **解决方案**: - 检查网络连接是否正常 - 网络不通,什么都下载不了 - 清理 Maven 本地仓库:删除 `~/.m2/repository` 目录,重新下载依赖 - 有时候缓存会出问题 - 配置 Maven 镜像:在 `settings.xml` 中配置国内镜像源 - 国内下载慢,用镜像加速 ### 6. pnpm 依赖安装失败 **问题描述**:执行 `pnpm install` 时出现依赖安装失败。 **解决方案**: - 检查网络连接是否正常 - 网络问题,老生常谈 - 清理 pnpm 缓存:执行 `pnpm store prune` - 清理一下缓存试试 - 升级 pnpm 版本:执行 `npm install -g pnpm` - 用最新版总没错 - 配置 pnpm 镜像:在 `.npmrc` 中配置国内镜像源 - 国内下载,镜像加速是王道 ### 7. 编译错误 **问题描述**:编译过程中出现错误。 **解决方案**: - 检查 JDK 版本是否符合要求(JDK 17+) - 别用旧版本了 - 检查 Node.js 版本是否符合要求(Node.js >= 20.12.0) - 前端也得跟上 - 清理编译缓存:执行 `mvn clean` 或 `pnpm run clean` - 清理一下缓存 - 检查代码是否存在语法错误或类型错误 - 自己写的代码,自己检查 ### 8. 登录失败 **问题描述**:使用初始账号登录失败。 **解决方案**: - 确认账号和密码是否正确(初始账号:`admin` / `admin123`) - 别输错了 - 检查后端服务是否正常运行 - 后端没跑起来,当然登录不了 - 检查数据库中是否存在用户数据 - 数据库初始化了吗? - 检查 JWT 配置是否正确 - JWT 配置错了,登录肯定失败 ## 下一步 现在您已经成功启动了 Tagtag 应用,您可以: 1. 登录系统,体验各项功能 - 先玩玩看,熟悉一下系统 2. 查看 API 文档,了解系统提供的接口 - 看看有哪些 API 可以用 3. 阅读开发指南,学习如何开发新功能 - 开始您的开发之旅 4. 查看架构文档,深入了解系统设计 - 了解系统的设计思想 祝您使用愉快!如果遇到问题,欢迎在 GitHub 上提 Issue,我们会尽快回复。当然,更欢迎您直接提交 PR,帮助我们改进 Tagtag Starter。 # 架构概览 Tagtag Starter 采用现代化的前后端分离架构,前端负责用户交互与界面展示,后端负责业务逻辑与数据处理。系统设计遵循模块化、高内聚、低耦合的原则,便于扩展和维护。简单来说,就是把事情分清楚,各司其职,别乱来。 ## 总体架构 ### 架构图 ```text ┌─────────────────────────────────────────────────────────────────────────┐ │ 客户端 (Browser) │ └─────────────────────────────────────────────────────────────────────────┘ │ HTTPS/HTTP ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 前端应用 (frontend) │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────────────┐ │ │ │ 路由层 │ │ 视图层 │ │ 组件层 │ │ 状态层 │ │ API 客户端层 │ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────────────┘ │ └─────────────────────────────────────────────────────────────────────────┘ │ RESTful API (JSON) ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 后端应用 (Spring Boot) │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────────────┐ │ │ │ 控制层 │ │ 服务层 │ │ 业务层 │ │ 数据层 │ │ 基础设施层 │ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────────────┘ │ └─────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 数据存储层 │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ MySQL │ │ Redis │ │ MinIO │ │ 其他存储 │ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ └─────────────────────────────────────────────────────────────────────────┘ ``` Tagtag Starter 系统由以下两个主要部分组成: - **前端 (Frontend)**: 位于 `frontend` 目录。基于 Vue 3 + Vite + TypeScript 构建的现代化单页应用 (SPA),采用了 Vben Admin 5 框架,使用 Tailwind CSS 和 Shadcn UI 进行界面开发。界面美观,交互流畅,用户体验一级棒。 - **后端 (Backend)**: 位于 `backend` 目录下的多个 Java 模块。基于 Spring Boot 构建的模块化应用,负责提供 RESTful API 和数据持久化服务。稳定可靠,性能卓越,就像老黄牛一样任劳任怨。 ### 交互方式 - **通信协议**: 前后端通过 HTTP/HTTPS 协议进行通信,采用 RESTful API 风格。简单、标准、易理解。 - **数据格式**: 统一使用 JSON 格式进行数据交换。轻量、易读、易解析。 - **认证机制**: 使用 JWT (JSON Web Token) 进行无状态身份验证。安全、高效、无状态。 ### 前后端交互流程 1. **用户认证流程**: - 用户访问前端应用,输入用户名和密码 - 前端调用登录 API,发送认证请求 - 后端验证用户名和密码,生成 JWT 令牌 - 前端存储 JWT 令牌,后续请求携带令牌 :br这个过程就像去银行取钱,先验证身份,拿到凭证,然后就可以凭凭证办理业务了。 2. **API 请求流程**: - 前端发起 API 请求,携带 JWT 令牌 - 后端过滤器验证令牌有效性 - 控制器接收请求,调用服务层处理业务逻辑 - 服务层调用数据层访问数据库 - 数据层返回结果,服务层封装业务响应 - 控制器返回 JSON 响应给前端 - 前端处理响应,更新界面 :br这个流程就像点外卖,您下单(前端请求),商家接单(控制器),厨师做饭(服务层),打包(数据层),配送(返回响应),最后您收到外卖(前端处理)。 3. **WebSocket 实时通信**(可选): - 前端建立 WebSocket 连接,携带认证信息 - 后端验证并建立连接 - 双方通过 WebSocket 进行实时数据交换 :br这就像打电话,建立连接后,双方可以随时说话,不用每次都重新拨号。 ### 技术选型理由 #### 后端技术栈 | 技术 | 版本 | 选型理由 | | --------------- | --- | ----------------------------------------------------------- | | Spring Boot | 3.x | 现代化 Java 应用开发框架,提供自动配置、内嵌服务器等特性,简化开发流程。让开发者专注于业务逻辑,而不是配置文件。 | | Java JDK | 17 | LTS 版本,提供更好的性能、安全性和新特性支持。用最新的技术,写最稳的代码。 | | MyBatis Plus | 3.x | 简化 MyBatis 开发,提供代码生成、分页插件、逻辑删除等功能。少写代码,多摸鱼(开玩笑的)。 | | Redis | 6.x | 用于缓存、会话管理和分布式锁,提高系统性能。让系统飞起来。 | | Spring Security | 6.x | 强大的安全框架,支持 JWT 认证和权限控制。安全第一,别让黑客有机可乘。 | | MySQL | 8.x | 稳定可靠的关系型数据库,适合存储结构化数据。数据存储的基石,数据都靠它。 | #### 前端技术栈 | 技术 | 版本 | 选型理由 | | ------------ | ---- | ----------------------------------------------------- | | Vue 3 | 3.x | 响应式前端框架,提供 Composition API,更好的类型支持和性能。用最新的框架,写最优雅的代码。 | | TypeScript | 5.x | 静态类型检查,提高代码质量和开发效率。让 bug 无处遁形。 | | Vite | 5.x | 现代化前端构建工具,提供快速的开发体验和优化的生产构建。开发体验一级棒,启动速度嗖嗖的。 | | Pinia | 2.x | Vue 3 官方状态管理库,简洁易用,支持 TypeScript。状态管理,就这么简单。 | | Vue Router | 4.x | Vue 官方路由管理库,支持组件级路由和嵌套路由。页面跳转,轻轻松松。 | | Tailwind CSS | 3.x | 实用优先的 CSS 框架,提高样式开发效率,支持响应式设计。不用写一堆 CSS 文件了。 | | Shadcn UI | 最新 | 可定制的 UI 组件库,基于 Tailwind CSS,提供现代化的设计风格。界面美观,交互流畅。 | | pnpm | 10.x | 高效的包管理器,支持 Monorepo 结构,节省磁盘空间和安装时间。比 npm 更快,用过都说好。 | ## 后端架构 Tagtag Starter 后端遵循严格的多模块架构,以分离基础设施、业务逻辑和 API 定义。这种设计使得系统各部分职责清晰,便于维护和扩展。简单来说,就是把事情分清楚,各司其职,别乱来。 **注意**:以下描述均在 `cd backend` 后的目录结构基础上进行说明。 ### 后端模块依赖关系 ```text ┌─────────────────────────────────────────────────────────────────────────┐ │ 启动层 (tagtag-start) │ └─────────────────────────┬─────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 模块层 (tagtag-module-*) │ └─────────────────────────┬─────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 契约层 (tagtag-contract-*) │ └─────────────────────────┬─────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────┼─────────────────────────────────────────────────┐ │ 核心层 (tagtag-kernel) │ └─────────────────────────┬─────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────┼─────────────────────────────────────────────────┐ │ 框架层 (tagtag-framework) │ └─────────────────────────┬─────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────┼─────────────────────────────────────────────────┐ │ 通用层 (tagtag-common) │ └─────────────────────────────────────────────────────────────────────────┘ ``` ### 后端分层设计 后端项目组织为以下几个层级: #### 1. 通用层 (`tagtag-common`) 纯工具类,**无 Spring 依赖**,可独立于 Spring 框架使用。 - 常量定义 - 异常类 - 基础模型 (PageResult, Result) - 工具类 (BeanUtils, StringUtils, DateUtils) :br 这一层就像工具箱,里面装着各种工具,谁都可以拿来用,不用担心依赖问题。 #### 2. 框架层 (`tagtag-framework`) 基础设施集成。此层依赖于 Spring Boot 和其他第三方库,为上层提供技术支持。 - MyBatis Plus 配置与扩展 - Redis 配置与工具类 - Spring Security & JWT 认证机制 - JWT 服务与策略 - 权限守卫 - 认证上下文管理 - 全局异常处理 - 切面定义 (RateLimit) - 全局响应封装 - MapStruct 配置 - Web 配置 (CORS, TraceId) :br 这一层就像地基,为上层提供稳定的技术支持。没有这一层,上层就建不起来。 #### 3. 核心层 (`tagtag-kernel`) 核心业务支持逻辑。提供业务开发所需的通用支持和扩展点。 - 自定义注解 (`@RateLimit`, `@RequirePerm`, `@RequireRole`) - 枚举定义 (Status, Gender) - 业务常量与错误码 - 安全相关常量和声明 :br 这一层就像核心业务规则,定义了业务开发的基本规范和约束。 #### 4. 契约层 (`tagtag-contract`) 定义模块间的接口和数据结构,采用契约优先设计。 - **DTOs**: 用于 API 输入/输出的数据传输对象。 - **API 接口**: 定义服务契约的 Java 接口。 - **VO**: 视图对象,用于前端展示的数据封装。 - **枚举**: 业务相关的枚举定义。 :br 这一层就像合同,定义了各方(模块)之间的接口和数据结构。有了合同,大家就知道该怎么合作了。 #### 5. 模块层 (`tagtag-module`) 实际的业务实现,每个模块对应一个具体的业务域。 - **控制器 (Controllers)**: 处理 HTTP 请求,调用服务层。 - **服务实现 (Service Implementations)**: 实现业务逻辑。 - **实体类 (Entities)**: 映射到数据库表的实体。 - **映射器 (Mappers)**: 数据库操作接口。 - **业务模型**: 领域模型和业务规则。 :br 这一层就像各个部门,每个部门负责自己的业务,各司其职。 #### 6. 启动层 (`tagtag-start`) 应用入口点。聚合所有模块并提供配置文件。 - Spring Boot 主应用类 - 配置文件 (`application.yml`) - 环境变量配置 - 依赖管理 :br 这一层就像公司大门,所有模块都从这里进出。 ### 后端模块设计原则 1. **高内聚,低耦合**:每个模块只负责一个业务域,模块间通过契约层通信。就像各个部门,各司其职,通过合同(契约层)合作。 2. **依赖倒置**:高层模块依赖于抽象,不依赖于具体实现。就像老板依赖合同,而不是依赖具体的员工。 3. **开闭原则**:对扩展开放,对修改关闭。就像软件升级,可以加新功能,但不能改旧功能。 4. **单一职责**:每个类和方法只负责一个功能。就像一个人只负责一件事,不会什么都干。 5. **分层设计**:严格遵循分层架构,不跨层调用。就像公司层级,不能越级汇报。 ## 前端架构 Tagtag Starter 前端采用现代化的 Monorepo 结构,基于 **Vben Admin 5** 框架构建,旨在提供高效、可扩展的前端开发体验。简单来说,就是把前端代码组织得井井有条,方便管理和维护。 **注意**:以下描述均在 `cd frontend` 后的目录结构基础上进行说明。 ### 前端架构图 ```text ┌─────────────────────────────────────────────────────────────────────────┐ │ 应用层 (apps) │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ 路由层 │ │ 视图层 │ │ 组件层 │ │ 状态层 │ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ └─────────────────────────┬─────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 核心包层 (packages) │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ UI组件 │ │ Hooks │ │ 工具类 │ │ 服务层 │ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ └─────────────────────────┬─────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 内部工具层 (internal) │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ 构建工具│ │ 代码规范│ │ 配置文件│ │ 脚本工具│ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ └─────────────────────────────────────────────────────────────────────────┘ ``` ### 核心技术栈 | 技术 | 版本 | 用途 | | ------------ | ---- | ------------------------------------------ | | Vue 3 | 3.x | 响应式前端框架,提供 Composition API。用最新的框架,写最优雅的代码。 | | TypeScript | 5.x | 静态类型检查,提高代码质量。让 bug 无处遁形。 | | Vite | 5.x | 现代化前端构建工具,提供快速的开发体验。开发体验一级棒,启动速度嗖嗖的。 | | Pinia | 2.x | Vue 3 官方状态管理库。状态管理,就这么简单。 | | Vue Router | 4.x | Vue 官方路由管理库。页面跳转,轻轻松松。 | | Tailwind CSS | 3.x | 实用优先的 CSS 框架,提高样式开发效率。不用写一堆 CSS 文件了。 | | Shadcn UI | 最新 | 可定制的 UI 组件库,基于 Tailwind CSS。界面美观,交互流畅。 | | pnpm | 10.x | 高效的包管理器,支持 Monorepo 结构。比 npm 更快,用过都说好。 | ### 模块划分 前端项目主要分为三个层级: #### 1. 应用层 (`apps`) 包含具体的应用实现,每个子目录对应一个独立的前端应用。 - **`apps/tagtag`**: 主应用程序,包含完整的业务逻辑和界面。 - `src/api`: API 请求定义和封装。 - `src/views`: 页面视图组件。 - `src/router`: 路由配置和守卫。 - `src/store`: Pinia 状态管理。 - `src/layouts`: 布局组件。 - `src/components`: 业务组件。 :br这一层就像具体的业务部门,负责实现具体的业务功能。 #### 2. 核心包层 (`packages`) 提供可复用的核心功能和组件,供应用层使用。 - **`packages/@core`**: 核心共享库。 - **`base`**: 基础设计系统、图标和共享工具。 - **`ui-kit`**: 封装的 UI 组件库。 - `shadcn-ui`: 基于 Shadcn UI 的基础组件。 - `form-ui`: 表单组件封装。 - `layout-ui`: 布局组件封装。 - `popup-ui`: 弹窗、抽屉等组件封装。 - **`effects`**: 业务副作用与逻辑封装。 - `access`: 权限控制逻辑。 - `hooks`: 通用 Hooks (如 `useTabs`, `useRefresh`)。 - `request`: 请求库封装。 - `plugins`: 第三方插件集成 (Echarts, Motion, Vxe-table)。 - **`preferences`**: 应用偏好设置管理。 :br这一层就像公共设施,各个业务部门都可以使用,提高代码复用率。 #### 3. 内部工具层 (`internal`) 提供构建、开发和维护所需的工具和配置。 - **`internal/lint-configs`**: ESLint, Prettier, Stylelint 等代码规范配置。 - **`internal/vite-config`**: Vite 构建配置封装。 - **`internal/tailwind-config`**: Tailwind CSS 配置封装。 - **`internal/tsconfig`**: TypeScript 配置文件。 :br 这一层就像后勤部门,为开发提供各种工具和配置支持。 ### 前端设计原则 1. **组件化设计**: 将 UI 拆分为可复用的组件,提高代码复用率。就像乐高积木,想怎么搭就怎么搭。 2. **关注点分离**: 业务逻辑、UI 展示和状态管理分离。各司其职,互不干扰。 3. **类型安全**: 充分利用 TypeScript 提供的类型检查。让 bug 无处遁形。 4. **响应式设计**: 适配不同屏幕尺寸的设备。手机、平板、电脑,都能用。 5. **性能优化**: 代码分割、懒加载、虚拟滚动等优化手段。让应用跑得飞快。 6. **可访问性**: 确保应用对所有用户可访问,包括残障用户。人人都能用,才是好应用。 7. **国际化支持**: 支持多语言切换。中文、英文、日文,想用什么语言就用什么语言。 ### Monorepo 优势 1. **代码共享**: 多个应用可以共享组件、工具类和配置。不用重复造轮子。 2. **统一依赖管理**: 所有应用使用相同版本的依赖,避免版本冲突。版本统一,少出问题。 3. **统一构建工具**: 共享构建配置和脚本,提高开发效率。一套工具,多个项目。 4. **更好的协作**: 团队成员可以更方便地共享和复用代码。协作更顺畅。 5. **简化版本管理**: 统一版本号管理,简化发布流程。版本管理,轻松搞定。 # 项目结构 Tagtag Starter 项目采用前后端分离架构,包含后端 Java 模块化应用、前端 Vue 3 Monorepo 应用以及完善的文档系统。这种结构设计旨在实现高内聚、低耦合的系统架构,便于维护和扩展。 ## 目录概览 ```text tagtag-starter ├── docs # 文档项目 (Nuxt Content) │ ├── app # 文档应用源码 │ ├── content # 文档内容 │ ├── public # 静态资源 │ └── nuxt.config.ts # Nuxt 配置 ├── backend # 后端项目 │ ├── tagtag-common # 通用工具层(纯 Java 工具,无 Spring 依赖) │ ├── tagtag-framework # 基础设施层(Spring Boot, Security, MyBatis Plus) │ ├── tagtag-kernel # 核心业务支持(注解, 枚举, 常量, 上下文) │ ├── tagtag-contract # API 定义与契约 │ │ ├── tagtag-contract-auth # 认证契约 │ │ ├── tagtag-contract-iam # 身份与访问管理契约 │ │ ├── tagtag-contract-storage # 存储契约 │ │ └── tagtag-contract-system # 系统契约 │ ├── tagtag-module # 业务实现 │ │ ├── tagtag-module-auth # 认证模块实现 │ │ ├── tagtag-module-iam # 身份与访问管理模块实现 │ │ ├── tagtag-module-storage # 存储模块实现 │ │ └── tagtag-module-system # 系统模块实现 │ ├── tagtag-start # 应用启动入口与配置 │ └── pom.xml # 根 Maven POM 文件 └── frontend # 前端项目 (Vue 3 + Vite, Monorepo) ├── apps # 应用入口 │ └── tagtag # 主应用 ├── packages # 核心包与 UI 组件库 └── internal # 构建与工具脚本 ``` ## 后端模块依赖关系 ```text ┌─────────────────────────────────────────────────────────────────────────┐ │ tagtag-start │ └─────────────────────────┬─────────────────────────────────────────────────┘ │ ┌─────────────────────────┼─────────────────────────────────────────────────┐ │ tagtag-module-auth tagtag-module-iam tagtag-module-storage tagtag-module-system │ └─────────────────────────┬─────────────────────────────────────────────────┘ │ ┌─────────────────────────┼─────────────────────────────────────────────────┐ │ tagtag-contract-auth tagtag-contract-iam tagtag-contract-storage tagtag-contract-system │ └─────────────────────────┬─────────────────────────────────────────────────┘ │ ┌─────────────────────────┼─────────────────────────────────────────────────┐ │ tagtag-kernel │ └─────────────────────────┬─────────────────────────────────────────────────┘ │ ┌─────────────────────────┼─────────────────────────────────────────────────┐ │ tagtag-framework │ └─────────────────────────┬─────────────────────────────────────────────────┘ │ ┌─────────────────────────┼─────────────────────────────────────────────────┐ │ tagtag-common │ └─────────────────────────────────────────────────────────────────────────┘ ``` ## 后端模块详解 ### 1. 通用工具层 (`tagtag-common`) 纯 Java 工具类库,不依赖 Spring 框架,提供最基础的工具支持。 **主要职责**: - 提供通用工具类(字符串处理、日期处理、Bean 转换等) - 定义基础数据模型(PageResult, Result, Exception 等) - 提供基础常量定义 - 提供验证分组定义 **核心组件**: - `constant/`: 基础常量(GlobalConstants) - `enums/`: 基础枚举(CodeEnum) - `exception/`: 异常类定义(BusinessException, ErrorCode, AssertUtils) - `model/`: 基础数据模型(PageQuery, PageRequest, PageResult, Result 等) - `util/`: 工具类(DateTimeUtil, EnumUtil, PageUtil, TreeUtil) - `validation/`: 验证分组(CreateGroup, UpdateGroup) ### 2. 框架层 (`tagtag-framework`) 基础设施集成层,依赖 Spring Boot 和其他第三方库,为上层提供技术支持。 **主要职责**: - 集成 Spring Boot 生态组件 - 配置数据库、缓存、安全等基础设施 - 提供全局异常处理和响应封装 - 实现 AOP 切面(日志、限流、操作日志等) **核心组件**: - `aop/`: 切面实现(RateLimitAspect) - `config/`: 配置类(CacheConfig, JacksonConfig, RedisConfig, SecurityConfig 等) - `mapstruct/`: MapStruct 配置 - `mybatis/`: MyBatis Plus 配置和字段自动填充 - `security/`: Spring Security 配置和 JWT 实现 - `config/`: JWT 认证配置 - `context/`: 认证上下文管理 - `filter/`: 自定义过滤器 - `guard/`: 权限守卫(PermissionGuard, RoleGuard) - `handler/`: 自定义认证和授权处理器 - `model/`: 安全相关模型 - `service/`: JWT 服务和 Token 版本服务 - `strategy/`: JWT 解码和签名策略 - `util/`: JWT 工具类 - `util/`: 工具类(PageResults, Pages, WebUtil) - `web/`: 全局异常处理、CORS 配置、TraceId 过滤器 - `exception/`: 各种异常处理器 - `CorsConfig.java`: CORS 配置 - `FilterConfig.java`: 过滤器配置 - `TraceIdFilter.java`: TraceId 过滤器 ### 3. 核心层 (`tagtag-kernel`) 核心业务支持层,提供业务开发所需的通用支持和扩展点。 **主要职责**: - 定义自定义注解(限流等) - 提供业务常量和枚举类 - 实现用户上下文管理 - 提供业务 AOP 切面 - 实现安全相关的模型和工具 **核心组件**: - `annotation/`: 自定义注解(@RateLimit, @RequirePerm, @RequireRole) - `constant/`: 业务常量(AppMessages, CacheConstants, Permissions, Roles, SecurityClaims, SecurityConstants) - `enums/`: 业务枚举(GenderEnum, StatusEnum) ### 4. 契约层 (`tagtag-contract`) API 定义与契约层,采用契约优先设计,将 API 定义与实现分离。 **主要职责**: - 定义 API 接口和数据传输对象(DTO) - 定义视图对象(VO) - 定义业务枚举和常量 - 定义服务接口 **子模块**: #### `tagtag-contract-auth` - 认证相关 API 定义 - 登录、登出、刷新令牌等接口 - 认证相关 DTO 和 VO #### `tagtag-contract-iam` - 身份与访问管理 API 定义 - 用户、角色、菜单、部门管理接口 - IAM 相关 DTO 和 VO #### `tagtag-contract-storage` - 存储服务 API 定义 - 文件上传、下载、删除等接口 - 存储相关 DTO 和 VO #### `tagtag-contract-system` - 系统管理 API 定义 - 字典、消息、系统配置等接口 - 系统相关 DTO 和 VO ### 5. 模块层 (`tagtag-module`) 业务实现层,每个模块对应一个具体的业务域,实现契约层定义的接口。 **主要职责**: - 实现业务逻辑 - 访问数据库 - 调用其他服务 - 实现控制器 **子模块**: #### `tagtag-module-auth` - 认证业务逻辑实现 - 登录、登出、刷新令牌等功能 - JWT 令牌生成和验证 #### `tagtag-module-iam` - 身份与访问管理业务逻辑 - 用户、角色、菜单、部门管理实现 - 权限校验和数据权限控制 #### `tagtag-module-storage` - 存储业务逻辑实现 - 文件上传、下载、删除等功能 - 支持本地存储和云存储 #### `tagtag-module-system` - 系统管理业务逻辑 - 字典、消息、系统配置等功能 - 系统监控和统计 ### 6. 启动层 (`tagtag-start`) 应用启动入口,聚合所有模块并提供配置文件。 **主要职责**: - Spring Boot 主应用类 - 应用配置文件管理 - 环境变量配置 - 依赖管理 **核心组件**: - `TagtagApplication.java`: 主应用类 - `resources/application.yml`: 主配置文件 - `resources/application-dev.yml`: 开发环境配置 - `resources/application-prod.yml`: 生产环境配置 - `resources/logback-spring.xml`: 日志配置 ## 前端模块详解 `tagtag-ui` 采用 Monorepo 结构管理,使用 pnpm 作为包管理器,实现代码共享和依赖管理的优化。 ### Monorepo 设计理念 Monorepo 是一种将多个项目代码存储在单个仓库中的软件架构策略。Tagtag 采用 Monorepo 结构的主要优势包括: 1. **代码共享**:多个应用可以共享组件、工具类和配置 2. **统一依赖管理**:所有应用使用相同版本的依赖,避免版本冲突 3. **统一构建工具**:共享构建配置和脚本,提高开发效率 4. **更好的协作**:团队成员可以更方便地共享和复用代码 5. **简化版本管理**:统一版本号管理,简化发布流程 ### 1. 应用层 (`apps`) 包含具体的应用实现,每个子目录对应一个独立的前端应用。 #### `apps/tagtag` - 主应用 **核心组件**: - `src/api/`: API 请求定义和封装 - `core/`: 核心 API(认证、用户、菜单) - `modules/`: 模块 API(IAM、系统、存储) - `src/views/`: 页面视图组件 - `_core/`: 核心页面(登录、个人中心、404 等) - `dashboard/`: 仪表盘页面 - `modules/`: 模块页面(IAM、系统、存储) - `src/router/`: 路由配置 - `routes/`: 路由定义 - `access.ts`: 路由守卫 - `guard.ts`: 权限守卫 - `src/store/`: Pinia 状态管理 - `src/layouts/`: 布局组件 - `src/components/`: 业务组件 - `src/locales/`: 国际化配置 ### 2. 核心包层 (`packages`) 提供可复用的核心功能和组件,供应用层使用。 #### `packages/@core` - 核心共享库 **子模块**: - **`base`**: 基础设计系统、图标和共享工具 - `design/`: 设计系统和设计令牌 - `icons/`: 图标库 - `shared/`: 共享工具类 - **`ui-kit`**: 封装的 UI 组件库 - `shadcn-ui`: 基于 Shadcn UI 的基础组件 - `form-ui`: 表单组件封装 - `layout-ui`: 布局组件封装 - `popup-ui`: 弹窗、抽屉等组件封装 - `tabs-ui`: 标签页组件封装 - `menu-ui`: 菜单组件封装 - **`effects`**: 业务副作用与逻辑封装 - `access`: 权限控制逻辑 - `hooks`: 通用 Hooks(useTabs, useRefresh, usePagination) - `request`: 请求库封装 - `plugins`: 第三方插件集成(Echarts, Motion, Vxe-table) - **`preferences`**: 应用偏好设置管理 #### `packages/effects` - 业务副作用与逻辑 - `access/`: 权限控制实现 - `common-ui/`: 通用 UI 组件 - `hooks/`: 业务 Hooks - `layouts/`: 布局组件 - `plugins/`: 插件集成 ### 3. 内部工具层 (`internal`) 提供构建、开发和维护所需的工具和配置。 **核心组件**: - `lint-configs/`: ESLint, Prettier, Stylelint 等代码规范配置 - `vite-config/`: Vite 构建配置封装 - `tailwind-config/`: Tailwind CSS 配置封装 - `tsconfig/`: TypeScript 配置文件 - `node-utils/`: Node.js 工具函数 ## 配置文件详解 ### 后端配置 #### 主配置文件 (`tagtag-start/src/main/resources/application.yml`) ```yaml spring: application: name: tagtag-admin # 应用名称 version: 1.0.0 # 应用版本 profiles: active: dev # 激活的配置文件 jackson: date-format: yyyy-MM-dd HH:mm:ss # 日期格式 time-zone: GMT+8 # 时区 servlet: multipart: max-file-size: 50MB # 单个文件最大大小 max-request-size: 50MB # 请求最大大小 server: port: 8080 # 服务端口 servlet: context-path: / # 上下文路径 logging: config: classpath:logback-spring.xml # 日志配置文件 mybatis-plus: global-config: banner: false # 关闭 MyBatis Plus banner # 存储配置 storage: local: base-path: uploads # 本地存储基础路径 ``` ### 前端配置 #### 应用配置 (`tagtag-ui/apps/tagtag/vite.config.mts`) 基于内部封装的 Vite 配置,提供统一的构建配置。 #### 环境配置 (`tagtag-ui/apps/tagtag/.env`) ```env # 开发环境 VITE_APP_API_BASE_URL=http://localhost:8080 VITE_APP_TITLE=Tagtag Admin VITE_APP_SHORT_NAME=Tagtag ``` #### Tailwind 配置 (`tagtag-ui/internal/tailwind-config/src/index.ts`) 统一的 Tailwind CSS 配置,包含主题、插件和自定义工具类。 ## 文档系统 文档系统基于 Nuxt Content 构建,提供现代化的文档阅读体验。 **核心组件**: - `docs/app/`: Nuxt 应用源码 - `docs/content/`: 文档内容(Markdown 文件) - `docs/nuxt.config.ts`: Nuxt 配置 **文档结构**: - `1.getting-started/`: 入门指南 - `2.architecture/`: 架构文档 - `3.developer-guide/`: 开发者指南 - `3.modules/`: 模块文档 - `en/`: 英文文档 ## 最佳实践 ### 后端开发最佳实践 1. **遵循分层设计**:严格按照通用层、框架层、核心层、契约层、模块层的顺序依赖 2. **契约优先**:先定义 API 契约,再实现业务逻辑 3. **使用自定义注解**:利用 @OperationLog, @DataPermission 等注解简化开发 4. **统一响应格式**:使用 Result 封装 API 响应 5. **异常处理**:统一使用自定义异常,由全局异常处理器处理 ### 前端开发最佳实践 1. **组件化开发**:将 UI 拆分为可复用的组件 2. **类型安全**:充分利用 TypeScript 提供的类型检查 3. **状态管理**:合理使用 Pinia 管理应用状态 4. **API 封装**:统一封装 API 请求,处理错误和认证 5. **国际化**:支持多语言切换,使用 locales 目录管理翻译 6. **代码规范**:遵循 ESLint、Prettier 等代码规范 ### 目录结构最佳实践 1. **按功能组织代码**:将相关功能放在同一个目录下 2. **命名规范**:使用清晰、一致的命名约定 3. **避免深层嵌套**:目录层级不宜过深,建议不超过 4 层 4. **保持代码精简**:避免不必要的文件和代码 5. **文档化**:为重要组件和功能编写文档 ## 扩展建议 ### 后端扩展 1. **添加新模块**: - 在 `tagtag-contract` 下创建新的契约模块 - 在 `tagtag-module` 下创建对应的实现模块 - 在 `tagtag-start` 中添加依赖 2. **集成新功能**: - 在 `tagtag-framework` 中集成新的基础设施 - 在 `tagtag-kernel` 中添加对应的支持 - 在 `tagtag-contract` 中定义 API 契约 - 在 `tagtag-module` 中实现业务逻辑 ### 前端扩展 1. **添加新组件**: - 在 `packages/@core/ui-kit` 中添加新组件 - 在应用中导入使用 2. **添加新页面**: - 在 `apps/tagtag/src/views` 中添加新页面 - 在 `apps/tagtag/src/router` 中配置路由 - 在 `apps/tagtag/src/api` 中添加 API 定义 3. **添加新功能**: - 在 `packages/effects` 中添加新的副作用逻辑 - 在 `packages/@core/hooks` 中添加新的 Hooks - 在应用中使用新功能 # 前端架构 Tagtag Starter 前端采用现代化的架构设计,基于 Vue 3 + TypeScript + Vite 构建,使用 Monorepo 结构管理代码。本文档将详细介绍前端架构的设计理念、技术栈、核心组件和最佳实践。 ## 1. 架构设计理念 Tagtag Starter 前端架构遵循以下设计理念: ### 1.1 组件化设计 将 UI 拆分为可复用的组件,每个组件负责一个独立的功能,提高代码复用率和可维护性。 ### 1.2 关注点分离 业务逻辑、UI 展示和状态管理分离,使代码结构清晰,易于测试和维护。 ### 1.3 类型安全 充分利用 TypeScript 提供的静态类型检查,提高代码质量和开发效率。 ### 1.4 响应式设计 适配不同屏幕尺寸的设备,提供良好的用户体验。 ### 1.5 性能优化 采用代码分割、懒加载、虚拟滚动等优化手段,提高应用性能。 ### 1.6 可扩展性 设计灵活的扩展机制,便于添加新功能和集成第三方库。 ## 2. 技术栈 | 技术 | 版本 | 用途 | | ------------ | ---- | ---------------------------- | | Vue 3 | 3.x | 响应式前端框架,提供 Composition API。 | | TypeScript | 5.x | 静态类型检查,提高代码质量。 | | Vite | 5.x | 现代化前端构建工具,提供快速的开发体验。 | | Pinia | 2.x | Vue 3 官方状态管理库。 | | Vue Router | 4.x | Vue 官方路由管理库。 | | Tailwind CSS | 3.x | 实用优先的 CSS 框架,提高样式开发效率。 | | Shadcn UI | 最新 | 可定制的 UI 组件库,基于 Tailwind CSS。 | | pnpm | 10.x | 高效的包管理器,支持 Monorepo 结构。 | | Axios | 1.x | HTTP 客户端,用于 API 请求。 | | Echarts | 5.x | 数据可视化库。 | ## 3. Monorepo 结构 Tagtag Starter 前端采用 Monorepo 结构管理代码,使用 pnpm 作为包管理器。这种结构的优势包括: ### 3.1 代码共享 多个应用可以共享组件、工具类和配置,减少重复代码。 ### 3.2 统一依赖管理 所有应用使用相同版本的依赖,避免版本冲突。 ### 3.3 统一构建工具 共享构建配置和脚本,提高开发效率。 ### 3.4 更好的协作 团队成员可以更方便地共享和复用代码。 ### 3.5 简化版本管理 统一版本号管理,简化发布流程。 ## 4. 目录结构 **注意**:以下描述均在 `cd frontend` 后的目录结构基础上进行说明。 ```text frontend ├── apps # 应用层 │ └── tagtag # 主应用 │ ├── public # 静态资源 │ └── src # 源代码 ├── packages # 核心包层 │ ├── @core # 核心共享库 │ │ ├── base # 基础设计系统、图标和共享工具 │ │ ├── composables # 通用组合式函数 │ │ ├── preferences # 应用偏好设置管理 │ │ └── ui-kit # UI 组件库 │ ├── constants # 常量定义 │ └── effects # 业务副作用与逻辑封装 └── internal # 内部工具层 ├── lint-configs # 代码规范配置 ├── node-utils # Node.js 工具函数 ├── tailwind-config # Tailwind CSS 配置 ├── tsconfig # TypeScript 配置文件 └── vite-config # Vite 构建配置 ``` ## 5. 核心模块 ### 5.1 应用层 (`apps`) 包含具体的应用实现,每个子目录对应一个独立的前端应用。 #### 5.1.1 主应用 (`apps/tagtag`) **核心组件**: - `src/api/`: API 请求定义和封装 - `core/`: 核心 API(认证、用户、菜单) - `modules/`: 模块 API(IAM、系统、存储) - `request.ts`: Axios 实例配置 - `src/views/`: 页面视图组件 - `_core/`: 核心页面(登录、个人中心、404 等) - `dashboard/`: 仪表盘页面 - `modules/`: 模块页面(IAM、系统、存储) - `src/router/`: 路由配置 - `routes/`: 路由定义 - `access.ts`: 路由守卫 - `guard.ts`: 权限守卫 - `src/store/`: Pinia 状态管理 - `auth.ts`: 认证状态管理 - `src/layouts/`: 布局组件 - `auth.vue`: 认证布局 - `basic.vue`: 基础布局 - `src/components/`: 业务组件 - `src/locales/`: 国际化配置 - `langs/`: 语言包 ### 5.2 核心包层 (`packages`) 提供可复用的核心功能和组件,供应用层使用。 **子模块**: #### 5.2.1 核心共享库 (`packages/@core`) **子模块**: - **`base`**: 基础设计系统、图标和共享工具 - `design/`: 设计系统和设计令牌 - `icons/`: 图标库 - `shared/`: 共享工具类 - `typings/`: 类型定义 - **`composables`**: 通用组合式函数 - `use-is-mobile.ts`: 移动端检测 - `use-layout-style.ts`: 布局样式管理 - `use-namespace.ts`: 命名空间管理 - `use-scroll-lock.ts`: 滚动锁定管理 - `use-sortable.ts`: 排序功能 - **`preferences`**: 应用偏好设置管理 - 主题设置 - 布局设置 - 其他用户偏好 - **`ui-kit`**: 封装的 UI 组件库 - `shadcn-ui`: 基于 Shadcn UI 的基础组件 - `form-ui`: 表单组件封装 - `layout-ui`: 布局组件封装 - `popup-ui`: 弹窗、抽屉等组件封装 - `tabs-ui`: 标签页组件封装 - `menu-ui`: 菜单组件封装 #### 5.2.2 常量定义 (`packages/constants`) - 核心常量定义 - 业务常量定义 #### 5.2.3 业务副作用与逻辑封装 (`packages/effects`) - **`access`**: 权限控制逻辑 - 权限指令 - 权限组件 - 权限管理 - **`common-ui`**: 通用 UI 组件 - 关于页面 - fallback 页面 - 个人中心组件 - **`hooks`**: 通用 Hooks - `use-app-config.ts`: 应用配置管理 - `use-content-maximize.ts`: 内容最大化管理 - `use-design-tokens.ts`: 设计令牌管理 - `use-hover-toggle.ts`: 悬停切换管理 - `use-pagination.ts`: 分页管理 - `use-refresh.ts`: 刷新功能 - `use-tabs.ts`: 标签页管理 - `use-watermark.ts`: 水印功能 - **`layouts`**: 布局组件 - 基础布局 - 认证布局 - iframe 布局 - **`plugins`**: 第三方插件集成 - `echarts`: ECharts 图表集成 - `motion`: 动画集成 - `vxe-table`: 表格集成 ### 5.3 内部工具层 (`internal`) 提供构建、开发和维护所需的工具和配置。 **核心组件**: - `lint-configs/`: ESLint, Prettier, Stylelint 等代码规范配置 - `node-utils/`: Node.js 工具函数 - `tailwind-config/`: Tailwind CSS 配置封装 - `tsconfig/`: TypeScript 配置文件 - `base.json`: 基础 TypeScript 配置 - `library.json`: 库 TypeScript 配置 - `node.json`: Node.js TypeScript 配置 - `web-app.json`: Web 应用 TypeScript 配置 - `web.json`: Web TypeScript 配置 - `vite-config/`: Vite 构建配置封装 - 应用配置 - 库配置 - 插件配置 ## 6. 数据流设计 ### 6.1 状态管理 使用 Pinia 进行状态管理,将应用状态划分为多个模块,每个模块负责一个独立的功能域。 **状态管理流程**: 1. 组件通过 `useStore` 钩子获取状态 2. 组件调用 action 修改状态 3. Action 可以调用 API 获取数据 4. Mutation 修改状态(在 Pinia 中,action 可以直接修改状态) 5. 状态变化触发组件重新渲染 ### 6.2 API 请求流程 1. 组件调用 API 函数 2. API 函数使用封装的 Axios 实例发送请求 3. 请求拦截器添加认证信息和其他头部 4. 后端处理请求,返回响应 5. 响应拦截器处理响应,统一处理错误 6. API 函数返回响应数据 7. 组件处理响应数据,更新状态或 UI ### 6.3 路由流程 1. 用户访问某个路由 2. 路由守卫检查用户权限 3. 权限通过后,加载对应的组件 4. 组件获取数据,渲染页面 ## 7. 权限控制 Tagtag Starter 前端实现了基于角色的权限控制(RBAC),包括: ### 7.1 路由权限 - 动态路由生成:根据用户角色生成可访问的路由 - 路由守卫:检查用户是否有权限访问某个路由 ### 7.2 组件权限 - 权限指令:使用 `v-access` 指令控制组件的显示和隐藏 - 权限组件:使用 `AccessControl` 组件控制子组件的显示和隐藏 ### 7.3 按钮权限 - 按钮权限指令:使用 `v-access` 指令控制按钮的显示和隐藏 ## 8. 国际化支持 Tagtag Starter 前端支持多语言切换,使用 Vue I18n 实现。 **国际化配置**: - `src/locales/langs/`: 语言包文件 - `src/locales/index.ts`: 国际化配置 **使用方式**: ```vue ``` ## 9. 构建和部署 ### 9.1 构建流程 1. 安装依赖:`pnpm install` 2. 开发模式:`pnpm run dev` 3. 生产构建:`pnpm run build` 4. 预览生产构建:`pnpm run preview` ### 9.2 构建优化 - 代码分割:根据路由和组件分割代码 - 懒加载:按需加载路由和组件 - Tree Shaking:移除未使用的代码 - 压缩:压缩 JavaScript、CSS 和 HTML ### 9.3 部署方式 - 静态文件部署:将构建生成的静态文件部署到 CDN 或 Web 服务器 - Docker 部署:使用 Docker 容器部署应用 - Kubernetes 部署:使用 Kubernetes 管理容器化应用 ## 10. 最佳实践 ### 10.1 组件开发 - 组件命名:使用 PascalCase 命名组件 - 组件职责:每个组件负责一个独立的功能 - 组件通信:使用 Props 和 Emits 进行组件通信 - 组件测试:为组件编写单元测试 ### 10.2 API 开发 - API 命名:使用 RESTful API 风格 - API 封装:统一封装 API 请求,处理错误和认证 - API 文档:使用 Swagger 或其他工具生成 API 文档 ### 10.3 状态管理 - 状态设计:保持状态简洁,避免冗余 - 状态划分:根据功能域划分状态模块 - 异步处理:在 action 中处理异步逻辑 ### 10.4 性能优化 - 避免不必要的渲染:使用 `v-memo` 和 `v-once` 指令 - 虚拟滚动:处理大量数据时使用虚拟滚动 - 图片优化:使用适当的图片格式和大小 - 懒加载:按需加载路由和组件 ### 10.5 代码规范 - 遵循 ESLint 和 Prettier 配置 - 使用 TypeScript 严格模式 - 注释规范:为复杂代码添加注释 ## 11. 开发工具和流程 ### 11.1 开发工具 - VS Code:推荐的代码编辑器 - Volar:Vue 3 开发插件 - ESLint:代码质量检查 - Prettier:代码格式化 ### 11.2 开发流程 1. 克隆代码仓库 2. 安装依赖 3. 启动开发服务器 4. 编写代码 5. 运行测试 6. 提交代码 7. 代码审查 8. 合并代码 9. 构建和部署 ## 12. 测试策略 ### 12.1 单元测试 使用 Vitest 进行单元测试,测试组件、工具函数和状态管理。 ### 12.2 集成测试 测试组件之间的交互和 API 调用。 ### 12.3 E2E 测试 使用 Cypress 进行端到端测试,测试完整的用户流程。 ## 13. 监控和日志 ### 13.1 应用监控 - 使用 Sentry 或其他工具监控应用错误 - 收集用户行为数据,分析应用使用情况 ### 13.2 日志记录 - 记录应用错误和警告 - 记录关键操作日志 - 使用日志分析工具分析日志 ## 14. 未来规划 - 支持微前端架构 - 增强 TypeScript 类型支持 - 优化性能和用户体验 - 增加更多的 UI 组件和模板 - 支持更多的国际化语言 # 总结 Tagtag Starter 前端架构采用现代化的设计理念和技术栈,基于 Vue 3 + TypeScript + Vite 构建,使用 Monorepo 结构管理代码。这种架构设计具有良好的可扩展性、可维护性和性能,适合构建复杂的企业级应用。 通过本文档的介绍,您应该对 Tagtag Starter 前端架构有了全面的了解。如果您想深入了解某个具体的模块或组件,可以查看相关的源代码和文档。 # 实战教程:开发 CRUD 功能 本教程将以开发一个简单的 **"文章管理 (Post)"** 功能为例,带您完整体验 Tagtag Starter 的前后端开发流程。 我们将实现以下功能: 1. 文章列表的分页查询(支持条件筛选) 2. 新增文章(包含表单验证) 3. 编辑文章(支持部分字段更新) 4. 删除文章(支持单个和批量删除) 5. 文章状态管理(启用/禁用) 6. 批量操作(批量删除、批量更新状态) --- ## 第一步:后端开发 (Backend) ### 1. 数据库设计 首先,在数据库中创建 `sys_post` 表。 ```sql CREATE TABLE `sys_post` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `title` varchar(100) NOT NULL COMMENT '文章标题', `content` text COMMENT '文章内容', `status` tinyint(4) DEFAULT 1 COMMENT '状态: 1-发布, 0-草稿', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `create_by` bigint(20) DEFAULT NULL COMMENT '创建人', `update_by` bigint(20) DEFAULT NULL COMMENT '更新人', `deleted` tinyint(4) DEFAULT 0 COMMENT '删除标识: 0-未删除, 1-已删除', PRIMARY KEY (`id`), KEY `idx_title` (`title`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章表'; ``` ### 2. 定义契约 (Contract) 在 `tagtag-contract` 模块中定义 DTO (Data Transfer Object)。建议新建 `PostDTO.java` 和 `PostQueryDTO.java`。 **PostDTO.java (用于新增/修改)** ```java package dev.tagtag.contract.system.dto; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Data; @Data public class PostDTO { private Long id; @NotBlank(message = "标题不能为空") private String title; private String content; @NotNull(message = "状态不能为空") private Integer status; } ``` **PostQueryDTO.java (用于查询)** ```java package dev.tagtag.contract.system.dto; import lombok.Data; @Data public class PostQueryDTO { private String title; private Integer status; } ``` **PostOperationRequest.java (用于批量操作)** ```java package dev.tagtag.contract.system.dto; import jakarta.validation.constraints.NotEmpty; import lombok.Data; @Data public class PostOperationRequest { @NotEmpty(message = "ID列表不能为空") private java.util.List ids; @NotNull(message = "状态不能为空") private Integer status; private String password; } ``` ### 3. 持久层实现 (Mapper & Entity) 在 `tagtag-module-system` 模块中创建实体类和 Mapper。 **PostEntity.java** ```java package dev.tagtag.module.system.entity; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableLogic; import com.baomidou.mybatisplus.annotation.TableName; import dev.tagtag.framework.mybatis.entity.BaseEntity; import lombok.Data; import lombok.EqualsAndHashCode; @Data @EqualsAndHashCode(callSuper = true) @TableName("sys_post") public class PostEntity extends BaseEntity { private String title; private String content; private Integer status; @TableLogic private Integer deleted; } ``` **PostMapper.java** ```java package dev.tagtag.module.system.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.metadata.IPage; import dev.tagtag.contract.system.dto.PostQueryDTO; import dev.tagtag.module.system.entity.Post; import dev.tagtag.module.system.entity.vo.PostVO; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @Mapper public interface PostMapper extends BaseMapper { /** * XML 分页查询(由 MyBatis XML 构建 WHERE/ORDER BY) * * @param page 分页对象(MyBatis Plus 拦截器识别) * @param q 查询条件 DTO * @return 分页结果 */ IPage selectPage(IPage page, @Param("q") PostQueryDTO q); } ``` **PostVO.java (视图对象)** ```java package dev.tagtag.module.system.entity.vo; import lombok.Data; import java.time.LocalDateTime; @Data public class PostVO { private Long id; private String title; private String content; private Integer status; private LocalDateTime createTime; private LocalDateTime updateTime; } ``` ### 4. 业务层实现 (Service) **PostService.java** ```java package dev.tagtag.module.system.service; import dev.tagtag.common.model.PageQuery; import dev.tagtag.common.model.PageResult; import dev.tagtag.contract.system.dto.PostDTO; import dev.tagtag.contract.system.dto.PostQueryDTO; import java.util.List; public interface PostService { /** * 文章分页查询 * @param query 查询条件DTO * @param pageQuery 分页参数 * @return 分页结果 */ PageResult page(PostQueryDTO query, PageQuery pageQuery); /** * 获取文章详情 * @param id 文章ID * @return 文章DTO */ PostDTO getById(Long id); /** * 创建文章 * @param post 文章DTO */ void create(PostDTO post); /** * 更新文章(忽略源对象中的空值) * @param post 文章DTO */ void update(PostDTO post); /** * 删除文章 * @param id 文章ID */ void delete(Long id); /** * 批量删除文章 * @param ids 文章ID列表 */ void batchDelete(List ids); /** * 更新单个文章状态 * @param id 文章ID * @param status 状态值 */ void updateStatus(Long id, Integer status); /** * 批量更新文章状态 * @param ids 文章ID列表 * @param status 状态值 */ void batchUpdateStatus(List ids, Integer status); } ``` **PostServiceImpl.java** ```java package dev.tagtag.module.system.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import dev.tagtag.common.model.PageQuery; import dev.tagtag.common.model.PageResult; import dev.tagtag.common.util.BeanUtil; import dev.tagtag.contract.system.dto.PostDTO; import dev.tagtag.contract.system.dto.PostQueryDTO; import dev.tagtag.module.system.entity.Post; import dev.tagtag.module.system.entity.vo.PostVO; import dev.tagtag.module.system.mapper.PostMapper; import dev.tagtag.module.system.service.PostService; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; @Service @RequiredArgsConstructor public class PostServiceImpl implements PostService { private final PostMapper postMapper; @Override public PageResult page(PostQueryDTO query, PageQuery pageQuery) { IPage page = new Page<>(pageQuery.getPage(), pageQuery.getSize()); IPage result = postMapper.selectPage(page, query); return PageResult.of( result.getRecords().stream() .map(vo -> BeanUtil.copyProperties(vo, PostDTO.class)) .toList(), result.getTotal() ); } @Override public PostDTO getById(Long id) { Post post = postMapper.selectById(id); if (post == null) { throw new RuntimeException("文章不存在"); } return BeanUtil.copyProperties(post, PostDTO.class); } @Override @Transactional(rollbackFor = Exception.class) public void create(PostDTO post) { Post entity = BeanUtil.copyProperties(post, Post.class); postMapper.insert(entity); } @Override @Transactional(rollbackFor = Exception.class) public void update(PostDTO post) { Post entity = BeanUtil.copyProperties(post, Post.class); postMapper.updateById(entity); } @Override @Transactional(rollbackFor = Exception.class) public void delete(Long id) { postMapper.deleteById(id); } @Override @Transactional(rollbackFor = Exception.class) public void batchDelete(List ids) { postMapper.deleteBatchIds(ids); } @Override @Transactional(rollbackFor = Exception.class) public void updateStatus(Long id, Integer status) { Post entity = new Post(); entity.setId(id); entity.setStatus(status); postMapper.updateById(entity); } @Override @Transactional(rollbackFor = Exception.class) public void batchUpdateStatus(List ids, Integer status) { ids.forEach(id -> updateStatus(id, status)); } } ``` ### 5. 控制层实现 (Controller) **PostController.java** ```java package dev.tagtag.module.system.controller; import dev.tagtag.common.model.PageQuery; import dev.tagtag.common.model.PageResult; import dev.tagtag.common.model.Result; import dev.tagtag.contract.system.dto.PostDTO; import dev.tagtag.contract.system.dto.PostQueryDTO; import dev.tagtag.module.system.service.PostService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/posts") @Tag(name = "文章管理") @RequiredArgsConstructor public class PostController { private final PostService postService; /** * 分页查询文章 */ @GetMapping @Operation(summary = "分页查询文章") @PreAuthorize("@ss.hasPermission('system:post:query')") public Result> page(PostQueryDTO query, PageQuery pageQuery) { return Result.ok(postService.page(query, pageQuery)); } /** * 获取文章详情 */ @GetMapping("/{id}") @Operation(summary = "获取文章详情") @PreAuthorize("@ss.hasPermission('system:post:query')") public Result getById(@PathVariable Long id) { return Result.ok(postService.getById(id)); } /** * 创建文章 */ @PostMapping @Operation(summary = "创建文章") @PreAuthorize("@ss.hasPermission('system:post:create')") public Result create(@Valid @RequestBody PostDTO dto) { postService.create(dto); return Result.okMsg("创建成功"); } /** * 更新文章 */ @PutMapping @Operation(summary = "更新文章") @PreAuthorize("@ss.hasPermission('system:post:update')") public Result update(@Valid @RequestBody PostDTO dto) { postService.update(dto); return Result.okMsg("更新成功"); } /** * 删除文章 */ @DeleteMapping("/{id}") @Operation(summary = "删除文章") @PreAuthorize("@ss.hasPermission('system:post:delete')") public Result delete(@PathVariable Long id) { postService.delete(id); return Result.okMsg("删除成功"); } /** * 批量删除文章 */ @DeleteMapping("/batch") @Operation(summary = "批量删除文章") @PreAuthorize("@ss.hasPermission('system:post:delete')") public Result batchDelete(@RequestBody List ids) { postService.batchDelete(ids); return Result.okMsg("批量删除成功"); } /** * 更新文章状态 */ @PutMapping("/{id}/status") @Operation(summary = "更新文章状态") @PreAuthorize("@ss.hasPermission('system:post:update')") public Result updateStatus(@PathVariable Long id, @RequestParam Integer status) { postService.updateStatus(id, status); return Result.okMsg("状态更新成功"); } /** * 批量更新文章状态 */ @PutMapping("/status/batch") @Operation(summary = "批量更新文章状态") @PreAuthorize("@ss.hasPermission('system:post:update')") public Result batchUpdateStatus(@RequestBody List ids, @RequestParam Integer status) { postService.batchUpdateStatus(ids, status); return Result.okMsg("批量状态更新成功"); } } ``` **关键要点:** - 使用 `@PreAuthorize` 注解进行权限控制 - 使用 `@Operation` 注解提供 API 文档说明 - 所有修改操作(创建、更新、删除)都需要事务支持 - 批量操作提供统一的入口,提高前端调用效率 - 状态管理提供单独的更新接口,便于前端实现启用/禁用功能 --- ## 第二步:前端开发 (Frontend) ### 1. 定义 API 在 `frontend/apps/tagtag/src/api/modules/system/post.ts` 中定义请求。 **post.ts** ```typescript import { request } from '@/api/request'; export interface PostDTO { id?: number; title: string; content?: string; author?: string; status?: number; categoryId?: number; tags?: string[]; coverUrl?: string; summary?: string; } export interface PostQueryDTO { title?: string; author?: string; status?: number; categoryId?: number; startDate?: string; endDate?: string; } export interface PageResult { records: T[]; total: number; current: number; size: number; } /** * 分页查询文章 */ export function getPostPage(params: PostQueryDTO & { page: number; size: number }) { return request.get>({ url: '/posts', params }); } /** * 获取文章详情 */ export function getPostById(id: number) { return request.get({ url: `/posts/${id}` }); } /** * 创建文章 */ export function createPost(data: PostDTO) { return request.post({ url: '/posts', data }); } /** * 更新文章 */ export function updatePost(data: PostDTO) { return request.put({ url: '/posts', data }); } /** * 删除文章 */ export function deletePost(id: number) { return request.delete({ url: `/posts/${id}` }); } /** * 批量删除文章 */ export function batchDeletePost(ids: number[]) { return request.delete({ url: '/posts/batch', data: ids }); } /** * 更新文章状态 */ export function updatePostStatus(id: number, status: number) { return request.put({ url: `/posts/${id}/status`, params: { status } }); } /** * 批量更新文章状态 */ export function batchUpdatePostStatus(ids: number[], status: number) { return request.put({ url: '/posts/status/batch', data: ids, params: { status } }); } ``` **关键要点:** - 定义清晰的 TypeScript 接口类型 - 所有 API 方法都有明确的类型注解 - 批量操作提供单独的接口方法 - 状态管理提供独立的更新方法 ### 2. 创建列表页面 创建 `frontend/apps/tagtag/src/views/modules/system/post/index.vue`。 ```vue ``` ### 3. 创建表单弹窗组件 创建 `frontend/apps/tagtag/src/views/modules/system/post/FormModal.vue`。 ```vue ``` ### 3. 配置菜单 1. 启动项目,登录管理员账号。 2. 进入 **系统管理 -> 菜单管理**。 3. 新增菜单 “文章管理”,路由地址填 `/system/post`,组件路径填 `modules/system/post/index.vue`。 4. 刷新页面,即可看到新功能。 --- ## 第三步:测试用例编写 ### 1. 后端单元测试 为了确保代码质量和功能正确性,我们需要为后端服务编写单元测试。Tagtag Starter 使用 JUnit 5 和 Mockito 进行单元测试。 #### 1.1 准备测试环境 首先,确保在 `pom.xml` 中添加了必要的测试依赖: ```xml org.junit.jupiter junit-jupiter-api test org.junit.jupiter junit-jupiter-engine test org.mockito mockito-core test org.mockito mockito-junit-jupiter test ``` #### 1.2 编写 Service 层测试 创建 `PostServiceImplTest.java` 文件,位于 `src/test/java` 目录下: ```java import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; import static org.assertj.core.api.Assertions.assertThat; @ExtendWith(MockitoExtension.class) class PostServiceImplTest { @Mock private PostMapper postMapper; @InjectMocks private PostServiceImpl postService; @Test void testPage() { // 准备测试数据 PostQuery query = new PostQuery(); query.setPage(1); query.setSize(10); query.setTitle("测试"); // 模拟分页结果 Page page = new Page<>(1, 10); List records = new ArrayList<>(); records.add(new PostEntity(1L, "测试文章", "内容", 1)); page.setRecords(records); page.setTotal(1); // 设置 Mock 行为 when(postMapper.selectPage(any(Page.class), any(LambdaQueryWrapper.class))).thenReturn(page); // 执行测试 PageResult result = postService.page(query); // 验证结果 assertThat(result).isNotNull(); assertThat(result.getTotal()).isEqualTo(1); assertThat(result.getRecords()).hasSize(1); assertThat(result.getRecords().get(0).getTitle()).isEqualTo("测试文章"); } @Test void testSave() { // 准备测试数据 PostDTO dto = new PostDTO(); dto.setTitle("测试文章"); dto.setContent("内容"); dto.setStatus(1); // 设置 Mock 行为 when(postMapper.insert(any(PostEntity.class))).thenReturn(1); // 执行测试 postService.save(dto); // 验证结果(可以添加更多验证) Mockito.verify(postMapper, times(1)).insert(any(PostEntity.class)); } @Test void testUpdate() { // 准备测试数据 PostDTO dto = new PostDTO(); dto.setId(1L); dto.setTitle("更新测试文章"); dto.setContent("更新内容"); dto.setStatus(0); // 设置 Mock 行为 when(postMapper.updateById(any(PostEntity.class))).thenReturn(1); // 执行测试 postService.update(dto); // 验证结果 Mockito.verify(postMapper, times(1)).updateById(any(PostEntity.class)); } @Test void testDelete() { // 准备测试数据 List ids = Arrays.asList(1L, 2L, 3L); // 设置 Mock 行为 when(postMapper.deleteBatchIds(ids)).thenReturn(3); // 执行测试 postService.delete(ids); // 验证结果 Mockito.verify(postMapper, times(1)).deleteBatchIds(ids); } } ``` ### 2. 前端测试 前端测试包括单元测试和集成测试,Tagtag Starter 使用 Vitest 进行单元测试,使用 Cypress 进行 E2E 测试。 #### 2.1 编写 API 测试 创建 `post.test.ts` 文件,位于 `frontend/apps/tagtag/src/api/modules/system/__tests__` 目录下: ```typescript import { describe, it, expect, vi } from 'vitest'; import { getPostPage, addPost, updatePost, deletePost } from '../post'; import { request } from '@/api/request'; // 模拟 request 模块 vi.mock('@/api/request', () => ({ request: { get: vi.fn(), post: vi.fn(), put: vi.fn(), delete: vi.fn() } })); describe('Post API', () => { it('getPostPage should call request.get with correct params', async () => { // 准备测试数据 const params = { page: 1, size: 10, title: 'test' }; const mockResponse = { data: { records: [], total: 0 } }; // 设置模拟返回值 (request.get as vi.Mock).mockResolvedValue(mockResponse); // 执行测试 const result = await getPostPage(params); // 验证结果 expect(request.get).toHaveBeenCalledWith({ url: '/posts', params }); expect(result).toEqual(mockResponse); }); it('addPost should call request.post with correct data', async () => { // 准备测试数据 const data = { title: 'test', content: 'test content', status: 1 }; const mockResponse = { data: { success: true } }; // 设置模拟返回值 (request.post as vi.Mock).mockResolvedValue(mockResponse); // 执行测试 const result = await addPost(data); // 验证结果 expect(request.post).toHaveBeenCalledWith({ url: '/posts', data }); expect(result).toEqual(mockResponse); }); it('updatePost should call request.put with correct data', async () => { // 准备测试数据 const data = { id: 1, title: 'updated', content: 'updated content', status: 0 }; const mockResponse = { data: { success: true } }; // 设置模拟返回值 (request.put as vi.Mock).mockResolvedValue(mockResponse); // 执行测试 const result = await updatePost(data); // 验证结果 expect(request.put).toHaveBeenCalledWith({ url: '/posts', data }); expect(result).toEqual(mockResponse); }); it('deletePost should call request.delete with correct ids', async () => { // 准备测试数据 const ids = [1, 2, 3]; const mockResponse = { data: { success: true } }; // 设置模拟返回值 (request.delete as vi.Mock).mockResolvedValue(mockResponse); // 执行测试 const result = await deletePost(ids); // 验证结果 expect(request.delete).toHaveBeenCalledWith({ url: '/posts', data: ids }); expect(result).toEqual(mockResponse); }); }); ``` #### 2.2 编写组件测试 创建 `index.test.ts` 文件,位于 `frontend/apps/tagtag/src/views/modules/system/post/__tests__` 目录下: ```typescript import { describe, it, expect, vi, beforeEach } from 'vitest'; import { mount } from '@vue/test-utils'; import PostIndex from '../index.vue'; import { useTable } from '@vben/plugins/vxe-table'; // 模拟依赖 vi.mock('@vben/plugins/vxe-table', () => ({ useTable: vi.fn(() => [ // 模拟 Grid 组件 { template: '
' }, // 模拟 gridApi { refresh: vi.fn(), getSelectedRows: vi.fn(() => []) } ]) })); describe('PostIndex Component', () => { it('should render correctly', () => { const wrapper = mount(PostIndex); expect(wrapper.exists()).toBe(true); }); it('should call handleAdd when add button is clicked', async () => { // 模拟 handleAdd 方法 const handleAdd = vi.fn(); const wrapper = mount(PostIndex, { global: { stubs: { Grid: { template: '
' }, FormModal: { template: '
' } } } }); // 触发工具栏点击事件 await wrapper.findComponent({ name: 'Grid' }).vm.$emit('toolbar-click', { code: 'add' }); // 验证 handleAdd 被调用 // 这里可以根据实际实现添加更详细的验证 expect(wrapper.vm.modalVisible).toBe(true); }); }); ``` ## 总结 通过以上步骤,您已经成功开发了一个包含前后端交互的完整 CRUD 模块,并编写了相应的测试用例。Tagtag Starter 的模块化设计让这一切变得清晰且规范。 ### 开发流程回顾 1. **后端开发**: - 数据库设计 - 定义契约(DTO、Query) - 持久层实现(Entity、Mapper) - 业务层实现(Service) - 控制层实现(Controller) - 编写单元测试 2. **前端开发**: - 定义 API 请求 - 创建列表页面 - 创建表单弹窗组件 - 配置菜单 - 编写 API 测试和组件测试 3. **部署和验证**: - 启动前后端服务 - 登录系统验证功能 - 执行测试用例 通过遵循这个流程,您可以快速、高效地开发出高质量的 CRUD 功能模块。 # 代码规范与最佳实践 良好的代码规范是保证代码质量、提高开发效率、减少维护成本的重要手段。本文档总结了 Tagtag Starter 项目的代码规范和最佳实践,包括 Java 后端和 TypeScript 前端的编码规范、命名约定和代码审查标准。 ## 1. 通用原则 无论使用哪种编程语言,以下通用原则都应遵循: ### 1.1 代码可读性优先 - 代码是写给人看的,不是写给机器看的 - 保持代码简洁、清晰、易于理解 - 使用有意义的命名,避免缩写和隐晦的名称 - 适当添加注释,但不要过度注释 ### 1.2 一致性 - 遵循项目已有的代码风格 - 保持缩进、换行、空格等格式一致 - 统一使用一种命名风格 ### 1.3 模块化 - 将代码拆分为可复用的模块 - 每个模块职责单一 - 模块间依赖关系清晰 ### 1.4 测试优先 - 编写可测试的代码 - 为核心功能编写单元测试 - 遵循测试驱动开发(TDD)原则 ### 1.5 安全性 - 避免硬编码敏感信息 - 注意 SQL 注入、XSS 攻击等安全问题 - 遵循最小权限原则 ## 2. Java 代码规范 Tagtag Starter 后端使用 Java 语言开发,遵循以下代码规范: ### 2.1 命名约定 - **类名**:使用 PascalCase,例如 `UserService`、`PostController` - **方法名**:使用 camelCase,例如 `getUserById`、`savePost` - **变量名**:使用 camelCase,例如 `userName`、`pageSize` - **常量名**:使用 UPPER\_CASE\_WITH\_UNDERSCORES,例如 `MAX_PAGE_SIZE`、`DEFAULT_STATUS` - **包名**:使用小写字母,例如 `dev.tagtag.common.util` - **接口名**:使用 PascalCase,例如 `UserService`、`PostMapper` **实际项目示例:** ```java // 类名示例 @Service public class UserServiceImpl implements UserService { } @RestController @RequestMapping("/users") public class UserController { } // 方法名示例 public UserDTO getUserById(Long userId) { } public PageResult pageUsers(UserQuery query) { } public void createUser(UserDTO user) { } public void updateUser(UserDTO user) { } public void deleteUser(Long userId) { } // 常量示例 private static final int MAX_PAGE_SIZE = 100; private static final String DEFAULT_AVATAR = "/default-avatar.png"; private static final Integer STATUS_ACTIVE = 1; ``` ### 2.2 代码格式 - **缩进**:使用 4 个空格进行缩进 - **换行**:每行不超过 120 个字符 - **空行**:在方法之间、代码块之间添加适当的空行 - **括号**:左括号 `{` 与语句同一行,右括号 `}` 单独一行 - **空格**: - 运算符前后添加空格,例如 `a = b + c` - 逗号、分号后添加空格,例如 `method(a, b, c)` - 括号内不添加空格,例如 `method(a, b)` 而非 `method( a, b )` ### 2.3 注释规范 - **类注释**:使用 Javadoc 注释,说明类的用途、作者、创建日期等 ```java /** * 用户服务类,负责用户相关业务逻辑 * * @author Tagtag Starter Team * @since 2024-01-01 */ @Service public class UserService { // 类实现 } ``` - **方法注释**:使用 Javadoc 注释,说明方法的用途、参数、返回值、异常等 ```java /** * 根据用户 ID 获取用户信息 * * @param userId 用户 ID * @return 用户信息 * @throws BusinessException 如果用户不存在 */ public UserDTO getUserById(Long userId) { // 方法实现 } ``` - **行注释**:使用 `//` 注释,说明复杂逻辑或特殊处理 ```java // 处理特殊情况:当用户未设置头像时,使用默认头像 if (user.getAvatar() == null) { user.setAvatar(DEFAULT_AVATAR); } ``` ### 2.4 最佳实践 - **使用 Lombok 简化代码**:使用 `@Data`、`@NoArgsConstructor`、`@AllArgsConstructor` 等注解简化 POJO 类 ```java @Data @NoArgsConstructor @AllArgsConstructor public class UserDTO { private Long id; private String username; private String email; private Integer status; } ``` - **避免魔法数字**:将常量定义为静态常量,例如 `private static final int MAX_RETRY = 3` ```java // 不推荐 if (user.getStatus() == 1) { } // 推荐 private static final Integer STATUS_ACTIVE = 1; if (user.getStatus().equals(STATUS_ACTIVE)) { } ``` - **使用 Optional 处理 null**:避免直接使用 null,使用 Optional 包装可能为 null 的值 ```java // 不推荐 User user = userMapper.selectById(userId); if (user != null) { return user.getName(); } return null; // 推荐 return Optional.ofNullable(userMapper.selectById(userId)) .map(User::getName) .orElse(null); ``` - **使用 try-with-resources 管理资源**:自动关闭资源,避免内存泄漏 ```java try (InputStream is = new FileInputStream(file)) { // 处理文件 } catch (IOException e) { log.error("文件读取失败", e); } ``` - **遵循 SOLID 原则**: - 单一职责原则:一个类只负责一个功能 - 开放封闭原则:对扩展开放,对修改封闭 - 里氏替换原则:子类可以替换父类 - 接口隔离原则:使用多个专门的接口,而不是一个总接口 - 依赖倒置原则:依赖抽象,不依赖具体实现 ### 2.5 Spring Boot 最佳实践 - **使用构造函数注入**:避免使用字段注入,提高可测试性 ```java @Service @RequiredArgsConstructor public class UserServiceImpl implements UserService { private final UserMapper userMapper; private final RoleService roleService; @Override public UserDTO getUserById(Long userId) { User user = userMapper.selectById(userId); return BeanUtil.copyProperties(user, UserDTO.class); } } ``` - **使用 @RestController 替代 @Controller + @ResponseBody**:简化 REST 接口开发 ```java @RestController @RequestMapping("/users") @RequiredArgsConstructor public class UserController { private final UserService userService; @GetMapping("/{id}") @Operation(summary = "获取用户详情") @PreAuthorize("@ss.hasPermission('system:user:query')") public Result getById(@PathVariable Long id) { return Result.ok(userService.getById(id)); } } ``` - **使用 @ExceptionHandler 处理异常**:统一处理异常,提高代码可读性 ```java @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(BusinessException.class) public Result handleBusinessException(BusinessException e) { log.error("业务异常", e); return Result.fail(e.getMessage()); } @ExceptionHandler(Exception.class) public Result handleException(Exception e) { log.error("系统异常", e); return Result.fail("系统错误,请联系管理员"); } } ``` - **使用 @Validated 进行参数校验**:自动校验请求参数,减少重复代码 ```java @PostMapping @Operation(summary = "创建用户") public Result create(@Valid @RequestBody UserDTO dto) { userService.create(dto); return Result.okMsg("创建成功"); } // DTO 定义 @Data public class UserDTO { @NotBlank(message = "用户名不能为空") private String username; @Email(message = "邮箱格式不正确") private String email; @NotNull(message = "状态不能为空") private Integer status; } ``` - **使用 @Transactional 管理事务**:明确事务边界,确保数据一致性 ```java @Override @Transactional(rollbackFor = Exception.class) public void createUserWithRoles(UserDTO user, List roleIds) { // 创建用户 userService.create(user); // 分配角色 if (CollectionUtils.isNotEmpty(roleIds)) { userRoleService.assignRoles(user.getId(), roleIds); } } ``` ### 2.6 MyBatis-Plus 最佳实践 - **使用 LambdaQueryWrapper 避免硬编码字段名**:提高代码可维护性 ```java // 不推荐 LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq("username", username); // 推荐 LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(User::getUsername, username); ``` - **使用分页插件**:统一分页查询 ```java @Override public PageResult page(UserQuery query, PageQuery pageQuery) { IPage page = new Page<>(pageQuery.getPage(), pageQuery.getSize()); IPage result = userMapper.selectPage(page, query); return PageResult.of( result.getRecords().stream() .map(vo -> BeanUtil.copyProperties(vo, UserDTO.class)) .toList(), result.getTotal() ); } ``` - **使用 @TableLogic 实现逻辑删除**:避免物理删除数据 ```java @Data @TableName("sys_user") public class User { @TableId private Long id; private String username; @TableLogic private Integer deleted; } ``` ## 3. TypeScript 代码规范 Tagtag Starter 前端使用 TypeScript 语言开发,遵循以下代码规范: ### 3.1 命名约定 - **组件名**:使用 PascalCase,例如 `PostList`、`FormModal` - **变量名**:使用 camelCase,例如 `userName`、`isLoading` - **函数名**:使用 camelCase,例如 `getUserInfo`、`handleSubmit` - **常量名**:使用 UPPER\_CASE\_WITH\_UNDERSCORES,例如 `MAX_PAGE_SIZE`、`API_BASE_URL` - **类型名**:使用 PascalCase,例如 `UserDTO`、`PostQuery` - **接口名**:使用 PascalCase,例如 `UserService`、`PostApi` **实际项目示例:** ```typescript // 组件名示例 export default defineComponent({ name: 'UserList' }); // 变量名示例 const userName = ref(''); const isLoading = ref(false); const userList = ref([]); // 函数名示例 function getUserInfo() { } function handleSubmit() { } function handleDelete() { } // 常量名示例 const MAX_PAGE_SIZE = 100; const API_BASE_URL = '/api'; const STATUS_ACTIVE = 1; // 类型名示例 interface UserDTO { id?: number; username: string; email?: string; status?: number; } interface PostQuery { title?: string; status?: number; page: number; size: number; } ``` ### 3.2 代码格式 - **缩进**:使用 2 个空格进行缩进 - **换行**:每行不超过 120 个字符 - **空行**:在函数之间、代码块之间添加适当的空行 - **括号**:左括号 `{` 与语句同一行,右括号 `}` 单独一行 - **空格**: - 运算符前后添加空格,例如 `a = b + c` - 逗号、分号后添加空格,例如 `method(a, b, c)` - 括号内不添加空格,例如 `method(a, b)` 而非 `method( a, b )` ### 3.3 注释规范 - **组件注释**:使用 JSDoc 注释,说明组件的用途、属性、事件等 ```typescript /** * 文章列表组件,展示文章列表并提供筛选、排序功能 * * @component * @param {Object} props - 组件属性 * @param {Function} onEdit - 编辑文章回调函数 * @param {Function} onDelete - 删除文章回调函数 */ export default defineComponent({ // 组件实现 }); ``` - **函数注释**:使用 JSDoc 注释,说明函数的用途、参数、返回值等 ```typescript /** * 获取文章列表 * * @param {PostQuery} params - 查询参数 * @returns {Promise>} 文章列表 */ export function getPostList(params: PostQuery): Promise> { // 函数实现 } ``` - **行注释**:使用 `//` 注释,说明复杂逻辑或特殊处理 ```typescript // 处理特殊情况:当文章状态为 0 时,显示为草稿 const statusText = status === 1 ? '已发布' : '草稿'; ``` ### 3.4 最佳实践 - **使用 TypeScript 严格模式**:启用 `strict: true` 配置,提高类型安全性 ```typescript // tsconfig.json { "compilerOptions": { "strict": true, "noImplicitAny": true, "strictNullChecks": true } } ``` - **避免 any 类型**:尽量使用具体类型,提高代码可靠性 ```typescript // 不推荐 function getUser(id: any): any { return request.get({ url: `/users/${id}` }); } // 推荐 function getUser(id: number): Promise { return request.get({ url: `/users/${id}` }); } ``` - **使用接口定义数据结构**:明确数据类型,提高代码可读性 ```typescript interface UserDTO { id?: number; username: string; email?: string; status?: number; createTime?: string; } interface PageResult { records: T[]; total: number; current: number; size: number; } interface UserQuery { username?: string; status?: number; page: number; size: number; } ``` - **使用 const 替代 let**:除非需要重新赋值,否则使用 const ```typescript // 不推荐 let userName = 'admin'; let status = 1; // 推荐 const userName = 'admin'; const status = 1; // 需要重新赋值时使用 let let currentPage = 1; currentPage++; ``` - **使用箭头函数**:简化函数定义,避免 this 指向问题 ```typescript // 不推荐 const handleClick = function() { console.log('clicked'); }; // 推荐 const handleClick = () => { console.log('clicked'); }; ``` - **使用解构赋值**:简化变量赋值,提高代码可读性 ```typescript // 不推荐 const user = response.data; const id = user.id; const name = user.name; // 推荐 const { id, name } = response.data; // 数组解构 const [first, second] = [1, 2, 3]; ``` - **使用模板字符串**:简化字符串拼接 ```typescript // 不推荐 const url = '/api/users/' + userId + '/roles/' + roleId; // 推荐 const url = `/api/users/${userId}/roles/${roleId}`; ``` - **使用可选链操作符**:安全访问嵌套属性 ```typescript // 不推荐 const userName = user && user.profile && user.profile.name; // 推荐 const userName = user?.profile?.name; ``` - **使用空值合并操作符**:提供默认值 ```typescript // 不推荐 const userName = user.name || 'Unknown'; // 推荐 const userName = user.name ?? 'Unknown'; ``` ### 3.5 Vue 3 最佳实践 - **使用 Composition API**:利用 setup 语法糖,提高代码组织性和复用性 ```vue ``` - **使用 defineProps 和 defineEmits**:简化组件属性和事件定义 ```typescript const props = defineProps<{ title: string; visible: boolean; formData?: UserDTO; }>(); const emit = defineEmits<{ (e: 'update:visible', value: boolean): void; (e: 'save', data: UserDTO): void; (e: 'cancel'): void; }>(); // 使用 props console.log(props.title); // 触发事件 emit('save', formData); ``` - **使用 watch 和 watchEffect 监听数据变化** ```typescript // watch:监听特定数据源 watch( () => props.visible, (newVal) => { if (newVal) { // 弹窗打开时的处理 } } ); // watchEffect:自动追踪依赖 watchEffect(() => { console.log(`用户列表长度: ${userList.value.length}`); }); // 监听多个数据源 watch([page, size], ([newPage, newSize]) => { loadData(); }); ``` - **使用 provide/inject 进行组件间通信**:避免 props 层层传递 ```typescript // 父组件 provide('userService', { getUserList, createUser, updateUser }); // 子组件 const userService = inject('userService'); await userService?.getUserList({ page: 1, size: 10 }); ``` - **使用 Teleport 处理模态框**:优化模态框定位和样式 ```vue ``` - **使用 Suspense 处理异步组件**:提供加载状态和错误处理 ```vue ``` - **使用 vben-table 表格组件**:统一表格开发规范 ```typescript import { useTable } from '@vben/plugins/vxe-table'; const [Grid, gridApi] = useTable({ api: getUserList, columns: [ { type: 'checkbox', width: 50 }, { field: 'username', title: '用户名' }, { field: 'email', title: '邮箱' }, { field: 'status', title: '状态', slots: { default: 'status' } }, { title: '操作', slots: { default: 'action' }, width: 150 } ], formConfig: { items: [ { field: 'username', label: '用户名', component: 'Input' }, { field: 'status', label: '状态', component: 'Select' } ] } }); ``` - **使用 Pinia 进行状态管理**:替代 Vuex,简化状态管理 ```typescript // stores/user.ts import { defineStore } from 'pinia'; export const useUserStore = defineStore('user', { state: () => ({ currentUser: null as UserDTO | null, token: '' }), getters: { isLoggedIn: (state) => !!state.token, userName: (state) => state.currentUser?.username || '' }, actions: { async login(username: string, password: string) { const result = await login({ username, password }); this.token = result.token; this.currentUser = result.user; }, logout() { this.token = ''; this.currentUser = null; } } }); // 使用 store const userStore = useUserStore(); await userStore.login('admin', '123456'); console.log(userStore.userName); ``` - **使用 composables 复用逻辑**:提取可复用的逻辑 ```typescript // composables/useTableData.ts export function useTableData(api: Function) { const data = ref([]); const loading = ref(false); const total = ref(0); async function loadData(params: any) { loading.value = true; try { const result = await api(params); data.value = result.records; total.value = result.total; } finally { loading.value = false; } } return { data, loading, total, loadData }; } // 使用 composable const { data, loading, total, loadData } = useTableData(getUserList); ``` ## 4. 命名约定 ### 4.1 包/模块命名 - **Java 包名**:使用小写字母,例如 `dev.tagtag.common.util` - **TypeScript 模块名**:使用小写字母,例如 `@/api/modules/system/post` - **目录名**:使用 kebab-case,例如 `user-management`、`post-list` ### 4.2 类/接口命名 - **Java 类名**:使用 PascalCase,例如 `UserService`、`PostController` - **TypeScript 类型名**:使用 PascalCase,例如 `UserDTO`、`PostQuery` - **接口名**:使用 PascalCase,例如 `UserRepository`、`PostService` ### 4.3 方法命名 - **获取数据**:使用 `get` 或 `find` 前缀,例如 `getUserById`、`findPosts` - **保存数据**:使用 `save` 或 `create` 前缀,例如 `saveUser`、`createPost` - **更新数据**:使用 `update` 前缀,例如 `updateUser`、`updatePost` - **删除数据**:使用 `delete` 前缀,例如 `deleteUser`、`deletePost` - **查询列表**:使用 `list` 或 `page` 前缀,例如 `listUsers`、`pagePosts` - **处理事件**:使用 `handle` 前缀,例如 `handleSubmit`、`handleDelete` **实际项目示例:** ```java // 后端方法命名示例 public interface UserService { UserDTO getUserById(Long userId); PageResult pageUsers(UserQuery query); List listActiveUsers(); void createUser(UserDTO user); void updateUser(UserDTO user); void deleteUser(Long userId); void batchDeleteUsers(List ids); void updateUserStatus(Long userId, Integer status); boolean existsByUsername(String username); } ``` ```typescript // 前端方法命名示例 export function getUserById(id: number): Promise { } export function getUserList(params: UserQuery): Promise> { } export function createUser(data: UserDTO): Promise { } export function updateUser(data: UserDTO): Promise { } export function deleteUser(id: number): Promise { } export function batchDeleteUsers(ids: number[]): Promise { } export function updateUserStatus(id: number, status: number): Promise { } // 事件处理方法命名示例 function handleSubmit() { } function handleEdit(row: UserDTO) { } function handleDelete(id: number) { } function handleBatchDelete(ids: number[]) { } function handleStatusChange(id: number, status: number) { } function handlePageChange(page: number) { } function handleSearch() { } function handleReset() { } ``` ### 4.4 变量命名 - **布尔变量**:使用 `is` 或 `has` 前缀,例如 `isLoading`、`hasPermission` - **数组变量**:使用复数形式,例如 `users`、`posts` - **集合变量**:使用具体集合类型,例如 `userList`、`postMap` - **常量变量**:使用 UPPER\_CASE\_WITH\_UNDERSCORES,例如 `MAX_PAGE_SIZE`、`DEFAULT_AVATAR` **实际项目示例:** ```java // 后端变量命名示例 private boolean isLoading; private boolean hasPermission; private boolean isDeleted; private boolean isActive; private List users; private List posts; private List roles; private Map userMap; private Map roleMap; private static final int MAX_PAGE_SIZE = 100; private static final int DEFAULT_PAGE_SIZE = 10; private static final String DEFAULT_AVATAR = "/default-avatar.png"; private static final Integer STATUS_ACTIVE = 1; private static final Integer STATUS_INACTIVE = 0; ``` ```typescript // 前端变量命名示例 const isLoading = ref(false); const hasPermission = ref(false); const isModalVisible = ref(false); const isFormValid = ref(false); const users = ref([]); const posts = ref([]); const roles = ref([]); const selectedUserIds = ref([]); const currentPage = ref(1); const pageSize = ref(10); const MAX_PAGE_SIZE = 100; const DEFAULT_PAGE_SIZE = 10; const API_BASE_URL = '/api'; const STATUS_ACTIVE = 1; const STATUS_INACTIVE = 0; ``` ## 5. 代码审查标准 代码审查是保证代码质量的重要环节,以下是 Tagtag Starter 项目的代码审查标准: ### 5.1 审查内容 - **功能正确性**:代码是否实现了预期功能 - **代码规范**:是否遵循项目的代码规范 - **代码可读性**:代码是否易于理解和维护 - **性能**:是否存在性能问题,例如循环嵌套过深、不必要的计算等 - **安全性**:是否存在安全漏洞,例如 SQL 注入、XSS 攻击等 - **测试覆盖率**:是否为核心功能编写了测试用例 - **依赖关系**:模块间依赖关系是否清晰,是否存在循环依赖 ### 5.2 审查流程 1. **提交代码前**: - 运行本地测试,确保所有测试通过 - 运行代码格式化工具,例如 Prettier - 运行静态代码分析工具,例如 ESLint、SonarQube 2. **提交代码后**: - 创建 Pull Request (PR) - 指定至少一位 reviewer - 填写清晰的 PR 描述,包括功能、修改内容、测试情况等 3. **Reviewer 审查**: - 检查代码是否符合规范 - 提出修改建议 - 批准或拒绝 PR 4. **修改代码**: - 根据 reviewer 的建议修改代码 - 重新运行测试和代码分析 - 提交修改 5. **合并代码**: - 所有 reviewer 批准后,合并 PR - 删除临时分支 ### 5.3 审查标准 - **必须修复的问题**: - 语法错误 - 逻辑错误 - 安全漏洞 - 性能问题 - 违反基本设计原则 **实际项目示例:** ```java // 必须修复:SQL 注入漏洞 // 不推荐 String sql = "SELECT * FROM user WHERE username = '" + username + "'"; Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery(sql); // 推荐 String sql = "SELECT * FROM user WHERE username = ?"; PreparedStatement stmt = connection.prepareStatement(sql); stmt.setString(1, username); ResultSet rs = stmt.executeQuery(); ``` ```java // 必须修复:性能问题 - N+1 查询 // 不推荐 List users = userMapper.selectList(null); for (User user : users) { List roles = roleMapper.selectByUserId(user.getId()); user.setRoles(roles); } // 推荐 List users = userMapper.selectUsersWithRoles(); ``` ```typescript // 必须修复:XSS 漏洞 // 不推荐 div.innerHTML = userInput; // 推荐 div.textContent = userInput; // 或使用 DOMPurify 等库进行清理 div.innerHTML = DOMPurify.sanitize(userInput); ``` - **建议修复的问题**: - 代码可读性问题 - 命名不规范 - 注释不足 - 重复代码 - 可以优化的代码 **实际项目示例:** ```java // 建议修复:命名不规范 // 不推荐 public class Usr { } public void getUsrInfo() { } // 推荐 public class User { } public void getUserInfo() { } ``` ```java // 建议修复:重复代码 // 不推荐 public void createUser(UserDTO user) { if (user.getUsername() == null || user.getUsername().isEmpty()) { throw new BusinessException("用户名不能为空"); } if (user.getEmail() == null || user.getEmail().isEmpty()) { throw new BusinessException("邮箱不能为空"); } } public void updateUser(UserDTO user) { if (user.getUsername() == null || user.getUsername().isEmpty()) { throw new BusinessException("用户名不能为空"); } if (user.getEmail() == null || user.getEmail().isEmpty()) { throw new BusinessException("邮箱不能为空"); } } // 推荐 private void validateUser(UserDTO user) { if (StringUtils.isBlank(user.getUsername())) { throw new BusinessException("用户名不能为空"); } if (StringUtils.isBlank(user.getEmail())) { throw new BusinessException("邮箱不能为空"); } } public void createUser(UserDTO user) { validateUser(user); } public void updateUser(UserDTO user) { validateUser(user); } ``` ### 5.4 审查技巧 - **专注于代码逻辑**:不要纠结于排版等细节问题,使用自动化工具解决 - **提出具体建议**:不要只说 "这个地方不好",要说明具体问题和改进方案 - **保持建设性**:使用积极的语言,避免指责 - **尊重作者**:理解作者的设计思路,不要轻易否定 - **学习他人代码**:从审查中学习好的设计和实现 ## 6. Git 最佳实践 ### 6.1 分支管理 - **主分支 (main)**:稳定的生产分支,只接收合并请求 - **开发分支 (develop)**:开发分支,包含最新的开发代码 - **功能分支 (feature/xxx)**:开发新功能的分支,从 develop 分支创建 - **修复分支 (fix/xxx)**:修复 bug 的分支,从 main 分支创建 - **发布分支 (release/xxx)**:准备发布的分支,从 develop 分支创建 ### 6.2 提交规范 - **提交信息清晰**:使用简洁明了的提交信息,说明修改内容 - **提交信息格式**: ```text ():