系统架构设计文档
文档基础信息
- 适用项目:grove/reed系统
- 核心用途:明确grove/reed系统整体架构设计方案、技术选型、模块划分、交互逻辑及部署架构,为项目开发、测试、运维提供顶层设计依据,保障系统具备高可用、高并发、易扩展、易维护的特性,贴合业务长期发展需求。
架构设计总览
核心设计原则
- 分层解耦:系统分层清晰,各层级职责单一,降低模块耦合度,便于独立开发、测试与维护
- 高可用性:规避单点故障,支持故障快速自愈,保障系统7×24小时稳定运行
- 可扩展性:支持横向扩容、功能模块化插拔,适配业务规模与需求迭代
- 性能最优:优化数据流转、接口调用逻辑,减少冗余操作,提升系统响应速度
- 安全可控:构建全链路安全防护体系,保障数据安全、访问安全、运行安全
整体架构模式
本系统采用前后端分离+多层架构设计模式,结合微服务/单体应用(按需选型)架构,拆分业务模块、数据模块、运维模块,实现请求接入、业务处理、数据存储、运维管控的全流程闭环,兼顾开发效率与系统性能。
技术架构选型
结合项目业务需求、性能要求与团队技术栈,选用成熟稳定、生态完善的技术体系,核心技术选型如下,统一版本避免兼容性问题:
后端技术栈
- 核心框架:SpringBoot
- ORM框架:自定义简易版ORM
- API规范:RESTfulAPI
- JSON组件:Fastjson2
- 工具类:Lombok
依赖版本开发阶段不定期更新到最新
数据存储层
- 关系型数据库:PostgreSQL
- 文件存储:本地存储
中间件与运维工具
- 反向代理/负载均衡:Nginx
- 服务器系统:Linux+Docker
- 项目管理:Git、Maven
系统分层架构设计
系统自上而下分为四层,各层级单向依赖,禁止跨层调用,明确职责边界,保障架构规整性:
接入层(前端展示层)
负责用户交互与页面渲染,承接用户操作请求,完成页面展示、数据校验、请求转发,适配PC端、移动端等多终端,通过RESTfulAPI与后端交互,实现前后端数据互通。
网关层(请求接入层)
统一请求入口,负责请求路由、负载均衡、权限校验、限流熔断、日志记录,拦截非法请求,实现请求流量管控,减轻后端业务层压力,保障后端服务安全稳定。
业务逻辑层
系统核心层,拆分基础通用模块与核心业务模块,处理业务逻辑、接口实现、数据校验、事务管控,调用数据层接口完成数据读写,封装业务能力供接入层调用,是系统功能实现的核心载体。
数据持久层
负责数据的存储、读取与维护,对接各类数据库,完成数据增删改查操作,优化数据查询逻辑,保障数据一致性、完整性,屏蔽数据存储细节,为业务层提供数据支撑。
时序图
功能模块架构划分
按照业务归属与功能特性,将系统拆分为三大核心模块组,各模块独立开发、低耦合高内聚,可独立迭代维护,具体划分如下:
基础支撑模块
- bean:bean/factory组件。
- boot:启动模块,配置文件等。
- console:控制台模块,自动
crud功能。 - controller:控制器组件。
- crypto:加解密组件。
- dao:DAO封装组件,包含数据库连接,数据表维护,简易ORM封装。
- icon:Icon模块,可动态替换前端icon。
- langpack:多语言模块,用于增减语言资源。
- message:消息模块。
- office:Office相关封装。
- scheduling:定时器组件。
- setting:设置/配置模块。
- user:用户模块。
- util:工具包组件。
核心业务模块
- ai:AI模块。
- boot:启动模块,配置文件等。
- coin:虚拟币/钱包模块。
- guidline:玩法说明模块。
- h5app:社区app参数模块。
- luckywheel:幸运转盘模块。
- message:消息模块。
- payment:c2c支付模块。
- player:玩家信息模块。
- prizewheel:转盘模块。
- reedenveloperrain:红包雨模块。
- redpacketrain:红包雨模块。
- silentlyrsync:数据抓取/同步模块。
- tenant:租户模块。
数据库脚本
系统每次启动时会检测各模块下的以下脚本文件,并自动执行脚本创建和修改:
create.sql用于保存创建脚本,及初始化数据,表结构存在后就不会再执行此文件alter.sql用于保存修改脚本,如果执行create.sql时已存在alter.sql,则里面的脚本将被自动标记为已执行,防止重复执行
所有数据库的变动都必须更新到这两个文件中,以确保所有变更都正常在开发环境、测试环境中正确运行,防止更新到正式环境后出现表结构不同步问题。
持久化操作
grove-dao提供了
crud工具用于基础的增删改查操作,简化orm映射及防止sql注入攻击sql工具用于完全的sql操作,但建议优先使用crud- 原则上不允许使用多表关联查询,若确实需要请与技术负责人讨论
多语言配置
- 每个模块可在
langpack.{lang}.json或langpack/{lang}.json中配置资源 - 前端
langpacks/{lang}.json可以配置资源 - 项目运行后,管理员可进入后台【设置】|【语言包】修改资源
资源使用优先级:
3>2>1
基础增删改查
- 可在模块下配置
crud.json,console后台可自动处理基础增删改查操作,减少重复代码 crud.json中配置的参数校验规则,会同时在前端和api进行校验
启动服务
- 运行
bin/boot.sh
模块创建工具
修改参数,运行后可自动生成基础的模块文件。
package org.example.javatest;
import java.io.File;
import java.nio.file.Files;
public class Module {
private static final String WORK = "/home/developer/work/";
public static void main(String[] args) throws Throwable {
String project = "reed";
String name = "Notification"; // 主模块
String sub = "Message"; // 子模块,可空
String[][] properties = new String[][] {
{ "appId", "String", "App ID" },
{ "platform", "String", "平台" },
{ "token", "String", "设备Token" },
{ "user", "String", "用户ID" },
{ "session", "String", "Session ID" },
{ "title", "String", "标题" },
{ "body", "String", "内容" },
{ "sound", "String", "声音" },
{ "status", "int", "状态:0-待发;1-成功;2-失败" },
{ "time", "Timestamp", "时间" },
};
String[] path = path(project, name, sub);
model(project, path[0], name, sub, properties);
service(project, path[0], name, sub);
create(path[1], name, sub, properties);
langpack(path[1], properties);
crud(path[1], name, sub, properties);
if (sub.isBlank()) {
pom(project, name);
}
}
private static String[] path(String project, String name, String sub) {
String[] path = new String[] {
String.format("%s%s/%s-%s/src/main/java/io/delta/%s/%s/%s", WORK, project, project, name.toLowerCase(),
project, name.toLowerCase(), sub.toLowerCase()),
String.format("%s%s/%s-%s/src/main/resources/io/delta/%s/%s/%s", WORK, project, project,
name.toLowerCase(), project, name.toLowerCase(), sub.toLowerCase()) };
for (int i = 0; i < path.length; i++) {
if (!path[i].endsWith("/"))
path[i] += "/";
boolean ok = new File(path[i]).mkdirs();
if (!ok) {
System.out.println("创建目录失败:" + path[i]);
}
}
return path;
}
private static void model(String project, String path, String name, String sub, String[][] properties)
throws Throwable {
boolean date = false;
boolean time = false;
for (String[] property : properties) {
if ("Date".equals(property[1]))
date = true;
else if ("Timestamp".equals(property[1]))
time = true;
}
StringBuilder sb = new StringBuilder(String.format("""
package io.delta.%s.%s%s;
import jakarta.persistence.Table;
import io.delta.grove.dao.orm.ModelSupport;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
""", project, name.toLowerCase(), sub.isBlank() ? "" : ("." + sub.toLowerCase())));
if (date || time) {
sb.append('\n');
if (date)
sb.append("import java.sql.Date;\n");
if (time)
sb.append("import java.sql.Timestamp;\n");
}
sb.append(String.format("""
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Component(%sModel.NAME)
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
@Table(name = "t%s%s")
public class %sModel extends ModelSupport {
static final String NAME = "%s.%s%s";
""", sub.isBlank() ? name : sub, format(name, '_'), sub.isBlank() ? "" : format(sub, '_'),
sub.isBlank() ? name : sub,
project, format(name, '-').substring(1), sub.isBlank() ? "" : ("." + format(sub, '-').substring(1))));
if (properties.length > 0) {
sb.append("\n");
for (String[] property : properties) {
sb.append(String.format(" private %s %s; // %s\n", property[1].replaceAll("Id|Text", "String"),
property[0], property[2]));
}
}
sb.append("}");
Files.writeString(new File(path + (sub.isBlank() ? name : sub) + "Model.java").toPath(), sb.toString().trim());
}
private static void service(String project, String path, String name, String sub) throws Throwable {
String className = sub.isBlank() ? name : sub;
String string = String.format("""
package io.delta.%s.%s%s;
public interface %sService {
}
""", project, name.toLowerCase(), sub.isBlank() ? "" : ("." + sub.toLowerCase()), className);
Files.writeString(new File(path + (sub.isBlank() ? name : sub) + "Service.java").toPath(), string);
string = String.format("""
package io.delta.%s.%s%s;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController(%sModel.NAME + ".service")
@RequestMapping("/%s%s/")
public class %sServiceImpl implements %sService {
}
""", project, name.toLowerCase(), sub.isBlank() ? "" : ("." + sub.toLowerCase()), className,
format(name, '-').substring(1),
sub.isBlank() ? "" : ("/" + format(sub, '-').substring(1)), className, className);
Files.writeString(new File(path + (sub.isBlank() ? name : sub) + "ServiceImpl.java").toPath(), string);
}
private static void create(String path, String name, String sub, String[][] properties) throws Throwable {
StringBuilder sb = new StringBuilder()
.append(String.format("CREATE TABLE t%s%s (\n c_id CHAR(32) PRIMARY KEY NOT NULL",
format(name, '_'), sub.isBlank() ? "" : format(sub, '_')));
for (String[] property : properties) {
sb.append(String.format(",\n c_%s %s", format(property[0], '_'), switch (property[1]) {
case "Id" -> "CHAR(32)";
case "String" -> "VARCHAR(255)";
case "long" -> "BIGINT";
case "Timestamp" -> "TIMESTAMP WITH TIME ZONE";
default -> property[1].toUpperCase();
}));
}
sb.append("\n);");
Files.writeString(new File(path + "create.sql").toPath(), sb.toString().trim());
}
private static void langpack(String path, String[][] properties) throws Throwable {
StringBuilder sb = new StringBuilder().append("{");
for (String[] property : properties) {
if (sb.length() > 1) {
sb.append(',');
}
sb.append(String.format("\n \"%s\": \"%s\"", property[0], property[2]));
}
sb.append("\n}");
Files.writeString(new File(path + "langpack.zh.json").toPath(), sb.toString().trim());
}
private static void crud(String path, String name, String sub, String[][] properties) throws Throwable {
StringBuilder sb = new StringBuilder().append(String.format("""
{
"mapping": "/%s%s/",
"properties": [
""".trim(), format(name, '-').substring(1),
sub.isBlank() ? "" : ("/" + format(sub, '-').substring(1))));
for (int i = 0; i < properties.length; i++) {
if (i > 0)
sb.append(',');
switch (properties[i][1]) {
case "Text" ->
sb.append(String.format("\n {\n \"name\": \"%s\",\n \"type\": \"textarea\"\n }",
properties[i][0]));
case "Date" ->
sb.append(String.format("\n {\n \"name\": \"%s\",\n \"type\": \"date\"\n }",
properties[i][0]));
case "Timestamp" ->
sb.append(String.format("\n {\n \"name\": \"%s\",\n \"type\": \"datetime\"\n }",
properties[i][0]));
default -> sb.append(String.format("\n {\n \"name\": \"%s\"\n }", properties[i][0]));
}
}
sb.append("""
\s
],
"query": {
"toolbar": [
{
"type": "create"
}
],
"actions": [
{
"type": "modify"
},
{
"type": "delete"
}
]
}
}
""");
Files.writeString(new File(path + "crud.json").toPath(), sb.toString().trim());
}
private static String format(String name, char ch) {
StringBuilder sb = new StringBuilder();
for (char c : name.toCharArray()) {
if (Character.isUpperCase(c)) {
sb.append(ch).append(Character.toLowerCase(c));
} else {
sb.append(c);
}
}
return sb.toString();
}
private static void pom(String project, String name) throws Throwable {
name = name.toLowerCase();
String xml = """
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>%s</artifactId>
<groupId>io.delta.%s</groupId>
<version>1.0</version>
</parent>
<artifactId>%s-%s</artifactId>
<version>1.0</version>
<name>%s-%s</name>
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>io.delta.grove</groupId>
<artifactId>grove-controller</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</project>
""";
xml = String.format(xml, project, project, project, name, project, name);
Files.writeString(
new File(String.format("%s%s/%s-%s/pom.xml", WORK, project, project, name)).toPath(),
xml.trim());
}
}部署架构设计
多环境部署架构
遵循环境隔离原则,分为开发、测试、生产三大环境,各环境独立部署、配置分离,避免相互干扰,适配不同阶段项目运作需求,具体部署规范贴合部署流程文档要求。
集群与扩容设计
- 生产环境采用集群部署模式,后端服务多实例部署,结合Nginx实现负载均衡,规避单点故障
- 支持横向扩容,用户量增长、业务并发提升时,可快速新增服务实例,提升系统承载能力
网络架构规划
内外网隔离部署,外网仅开放80、443端口供用户访问;配置防火墙、安全组规则,拦截恶意网络请求,筑牢网络安全防线。
非功能架构设计
性能设计
- 热点数据存入缓存,降低数据库查询压力,核心接口响应时长控制在3秒内
- 优化SQL语句、建立合理索引,避免慢查询,提升数据读写效率
- 接口限流、异步处理,应对高并发场景,防止系统拥堵崩溃
安全设计
- 身份认证:采用Token令牌认证,登录超时自动失效,防范非法登录
- 数据安全:敏感数据加密存储、传输,隐私信息脱敏展示,防止数据泄露
- 防护机制:加入SQL注入、XSS攻击、CSRF攻击防护,拦截恶意请求
可维护性设计
- 统一日志规范,全链路日志追踪,便于问题排查定位
- 代码遵循标准化规范,注释完善,便于后续迭代维护
- 支持配置热更新,无需重启服务即可修改核心配置
架构风险与应对措施
- 单点故障风险:核心服务集群部署、主备切换,避免单点失效导致系统瘫痪
- 数据丢失风险:定期数据库备份、日志备份,支持故障后快速数据恢复
- 性能瓶颈风险:提前压测,优化架构与代码,预留扩容空间,应对流量峰值
- 安全攻击风险:多层安全防护,定期漏洞扫描,及时修复安全隐患