第5章〓构建多模块项目 教务辅助管理系统(Teaching Assistant Management System,TAMS)用于学生教师日常教学事务的辅助管理。本章主要介绍了项目的技术架构以及复合构建,并基于多模块方式分别创建了项目的三大部分: 公共项目、前端项目、服务端项目。本章项目的顺利完成,为后续章节各模块的成功实现做好了项目准备。 5.1教务辅助管理系统项目概述 教务辅助管理系统是一个基于B/S模式,用于学生、教师一些常规事项管理的项目。为了便于理解,这里对系统进行了功能调整、简化处理,使其业务逻辑简单、更易于理解,但又能满足响应式前后端开发技术的完整展现。 5.1.1系统功能简介 系统功能主要包括9个部分,这里做简要说明。①首页: 显示主界面及导航菜单。②用户登录: 登录成功后,会生成JWT令牌以备其他功能验证使用。③用户注册: 根据用户名、密码、Email、用户角色进行注册。用户名唯一。④消息推送: 后台利用WebClient、Sinks.Many等技术向登录的用户推送通知、公告等短消息。⑤学院风采: 展示各学院的文字介绍及视频介绍。各学院的视频文件,使用视频流分段加载。⑥学生查询: 登录后用户可输入学生姓名或班级关键字模糊查询,并使用分布式内存网格Hazelcast进行数据共享。⑦招生一览: 可查询指定学院某专业近5年招生数据情况,基于gRPC+ECharts显示招生图表。⑧资料上传: 登录过的用户可上传资料文件,并使用Spring WebFlux文件流进行传送。⑨畅论空间: 采用Apache Kafka+WebSocket技术进行消息传递,提供登录用户的讨论、学习交流场所。 5.1.2系统技术架构 前端主要使用RxJS+Vue 3,后端基于Spring WebFlux,数据库采用PostgreSQL,并以Netty为服务器引擎。整个系统的技术架构如图51所示。 图51系统技术架构 图52项目整体结构 整个系统包含两个部分: 招生系统(Enroll)、教务辅助管理系统(TAMS)。Enroll对外暴露招生数据接口,以供外部访问。TAMS则通过gRPC(google Remote Procedure Call,谷歌远程过程调用),从招生系统获取到招生数据。二者都在Docker容器内运行,并通过Spring Cloud Vault,安全地获取R2DBC连接信息,据此访问Docker容器外部的数据库服务器PostgreSQL。 TAMS利用Hazelcast实时数据分发集群平台,在服务器结点之间分布式缓存、共享数据,以提高查询效率。同时,采用外部Apache Kafka作为消息处理平台。 5.1.3系统的复合构建结构 整个系统采用复合构建的形式,根项目tams由三大各自独立的子项目构成: 公共项目appcommon、前端项目appview、服务端项目appserver,如图52所示。 Dockerfile是Docker脚本文件,用于将项目发布到Docker容器。settings.gradle配置了项目各组成模块之间的依赖关系。而cacert文件夹则存放了发布项目时需要用到的安全证书、私钥文件。这些文件,后续会适时详细介绍。 5.2创建响应式根项目TAMS 新建Spring Reactive Web项目tams(请参阅1.6节),作为系统的根项目,并定义相关信息如下。 Name(项目名称): tamsLocation(存放位置): E:\rworks\chapter05 Language(语言): Java Type(构建类别): GradleGroovy Group(组名): com.tamsArtifact(打包声明): tams Package name(包名): com.tams JDK: JDK 17 Java: 17Packaging(打包方式): jar Spring Boot版本: 3.1.4依赖项: Lombok、Spring Reactive Web 删除项目中的build.gradle文件。作为根项目,这里并不需要该文件,而是在各子项目内配置各自的build.gradle。 5.3添加公共项目appcommon appcommon主要用来配置整个系统的公共配置,包括以下三大部分。 server.common.gradle: 用来设置服务端模块的公共构建脚本。 view.common.gradle: 用来设置前端各模块的公共构建脚本。 reactor.grpc.gradle: 用于设置与远程招生系统(Enroll)有关的构建脚本。 添加公共项目appcommon的具体步骤如下。 (1) 创建文件夹“appcommon”。 在项目名称tams上单击鼠标右键,在弹出菜单中选择New→Directory,输入文件夹名“appcommon”即可。 (2) 修改根项目tams配置文件settings.gradle的内容。 rootProject.name = 'tams' includeBuild 'app-common'//引用app-common中的构建逻辑、依赖关系 再单击Load Gradle Changes按钮(请参阅图130)重载更改。 (3) 在appcommon上新建文件夹src,在src下创建子文件夹main,再在main下创建文件夹groovy。 (4) 在appcommon下新建build.gradle文件,其内容为 plugins { id 'groovy-gradle-plugin' } 这里使用的groovygradleplugin插件,可为项目src/main中的脚本提供构建支持。所以,build.gradle文件不要错误地放置在appcommon/src文件夹下,否则,无法对src/main/groovy下的构建脚本进行解析构建。 5.3.1服务端构建脚本server.common.gradle 服务端构建脚本server.common.gradle主要是设置服务模块的全局公共配置信息,例如,各模块打包成jar文件时的文件名、版本号、资源仓库地址、Java编译版本、公共依赖项、编译时的字符编码等。在appcommon/src/main/groovy文件夹下新建server.common.gradle,添加构建脚本: plugins {//使用Gradle内置的java、base插件 id 'java' id 'base' } java { sourceCompatibility = JavaLanguageVersion.of(17) //Java版本 } group = 'com.tams.server' //项目的组名 version = '1.0' //项目版本号 def springVer = '3.1.4' // spring-boot-starter版本号 def lombokVer = '1.18.28' // lombok版本号 base { /*定义所有子模块打包后的jar文件名格式:tams.项目名.server 打包后会附加版本号,例如:tams.chat-room.server-1.0.jar */ archivesName.set('tams.'+project.name+'.server') //设置最终打包后的jar文件存放的文件夹为tams-jars libsDirectory.set(layout.buildDirectory.dir('tams-jars') as Provider<? extends Directory>) } configurations { compileOnly { extendsFrom annotationProcessor //compileOnly继承自注解解释器lombok } } repositories { //配置资源仓库,优先使用阿里云 maven { url 'https://maven.aliyun.com/repository/google' } mavenLocal() mavenCentral() } dependencies { //配置项目的依赖项 implementation "org.springframework.boot:spring-boot-starter-data-r2dbc: ${springVer}" implementation "org.springframework.boot:spring-boot-starter-webflux:${springVer}" implementation "org.springframework.cloud:spring-cloud-starter-vault-config:4.0.1" implementation "com.hazelcast:hazelcast-spring:5.3.6"//hazelcast分布式内存网格 implementation "io.projectreactor.kafka:reactor-kafka:1.3.22" //Kafka消息处理器 runtimeOnly "org.postgresql:r2dbc-postgresql:1.0.2.RELEASE" //数据库PostgreSQL compileOnly "org.projectlombok:lombok:${lombokVer}" annotationProcessor "org.projectlombok:lombok:${lombokVer}" //注解处理器 implementation "org.modelmapper.extensions:modelmapper-spring:3.2.0" testImplementation "org.springframework.boot:spring-boot-starter-test:${springVer}" testImplementation "io.projectreactor:reactor-test:3.5.9" } tasks.withType(JavaCompile).configureEach { //编译任务 options.encoding = "UTF-8" //编译时的编码统一设置为UTF-8 } tasks.withType(Copy).configureEach { //设置复制策略 duplicatesStrategy = DuplicatesStrategy.EXCLUDE //若重复则忽略 } tasks.named('test', Test) { //测试任务 useJUnitPlatform() } 5.3.2前端构建脚本view.common.gradle 前端构建脚本view.common.gradle用来配置前端模块的全局公共配置信息,例如,各前端模块打包成jar文件时的文件名、版本号、公共依赖项(例如,HTML页面文件所依赖的vue.global.prod.min.js、rxjs.umd.min.js等)。在appcommon/src/main/groovy文件夹下新建构建文件view.common.gradle,添加构建脚本: plugins { id 'java' id 'base' } group = 'com.tams.view' version = '1.0' base { archivesName.set('tams.'+project.name+'.view') } dependencies { implementation project(':app-view:public')//依赖于app-view项目的public子模块 } 由于这时候appview项目的public子模块还没来得及创建,因此会提示构建错误,先不用理睬,继续下一步操作。 5.3.3gRPC构建脚本reactor.grpc.gradle gRPC构建脚本reactor.grpc.gradle,用来配置与另外一个招生系统(Enroll)进行远程过程调用的脚本代码。在appcommon/src/main/groovy文件夹下新建reactor.grpc.gradle,添加构建脚本: plugins { id 'java' id 'base' } group = 'com.tams.grpc' version = '1.0' def ioGrpcVer = '1.57.2' // io.grpc版本号 def reactorGrpcVer = '1.2.4' // reactor grpc版本号 base { archivesName.set('tams.'+project.name+'.grpc') } dependencies { //gRPC所需的依赖项 implementation "io.grpc:grpc-netty-shaded:${ioGrpcVer}" implementation "io.grpc:grpc-protobuf:${ioGrpcVer}" implementation "io.grpc:grpc-stub:${ioGrpcVer}" implementation "com.salesforce.servicelibs:reactor-grpc:${reactorGrpcVer}" implementation "com.salesforce.servicelibs:grpc-spring:0.8.1" implementation "com.salesforce.servicelibs:reactor-grpc-stub:${reactorGrpcVer}" } 现在,appcommon项目结构如图53所示。 图53appcommon项目结构 5.4添加前端项目appview 前端项目appview,基于HTML、CSS、RxJS、Vue.js、Pinia、ECharts等技术,用于构建教务辅助管理系统的人机交互页面。 5.4.1新建appview 在根项目tams下创建文件夹“appview”,修改根项目tams的配置文件settings.gradle并重载更改: ootProject.name = 'tams' includeBuild 'app-common'//引用app-common中的构建逻辑、依赖关系 include 'app-view' //将app-view设置为根项目tams构建生成的一部分 然后,在appview下新建文件settings.gradle,通过该文件设置appview的名称,其内容非常简单: rootProject.name = 'appview' 5.4.2添加子模块home 现在,可添加appview项目的各子模块了。首先添加子模块home,该模块定义了TAMS项目的前端页面的入口主页。添加子模块home的步骤如下。 (1) 新建子模块。在appview上单击鼠标右键,选择New→Module,弹出New Module对话框,如图54所示。选择左边的New Module,填写相应信息: Name(模块名称): homeLocation(保存位置): E:\rworks\chapter05\tams\appview Parent(父项目): tamsGroupId(分组ID): com.tams ArtifactId(打包ID): home 图54New Module 对话框 单击Create按钮,完成创建过程。 (2) 配置home/build.gradle。删除自动生成的内容,修改其内容为 plugins { id 'view.common' //引入app-common中view.common.gradle定义的内容 } 图55home子模块结构 (3) 删除不再需要的home/src/main/java文件夹、home/src/test模块,并创建好其他后续需要的文件夹及文件,例如static文件夹、image文件夹、home.html等。最终准备好的项目结构如图55所示。 (4) 在resources/static下新建home.html,先将内容简单设置为 <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>教务辅助管理系统--官方主页</title> </head> <body> 教务辅助管理系统,建设中... </body></html> 5.4.3添加子模块public 子模块public用于配置前端项目的公共资源,例如,JS支撑文件、插件文件夹、状态管理文件夹等。 图56public子模块结构 (1) 创建子模块public。按照5.4.1节的方法,新建子模块public,同样准备好各文件夹及JS支撑文件。子模块public的最终结构如图56所示。 image文件夹: 存放前端公共图片,例如,公用进度处理图片doing.gif。 lib文件夹: 存放RxJS、Vue.js、Pinia、vue3sfcloader等JS支持文件。请添加好这些JS支持文件。 modules文件夹: 存放公共SFC组件。 plugins文件夹: 存放自定义Vue插件。 store文件夹: 存放用于Pinia状态管理的JS文件。 (2) 修改build.gradle。删除大部分自动生成的内容,只需要保留: plugins { id 'java' } (3) 删除不再需要的public/src/main/java文件夹、public/src/test模块。 5.4.4添加其他子模块 按照上述处理思路,添加其他前端子模块。表51列出了需要添加的前端各子模块的主要配置。 表51各子模块配置 模块名 模块文件夹结构 build.gradle 模 块 说 明 chatroom plugins { id 'view.common' } 畅论空间 collegelist plugins { id 'view.common' } 学院风采 enrollchart plugins { id 'view.common' } 招生一览 fileservice plugins { id 'view.common' } 资料上传 studentquery plugins { id 'view.common' } 学生查询 userservice plugins { id 'view.common' } 用户服务(用户登录、用户注册) 最终appview项目结构如图57所示。 图57appview项目结构 5.5添加服务端项目appserver 服务端项目appserver基于Spring WebFlux技术,实现并提供教务辅助管理系统后端的各种业务逻辑处理服务。 5.5.1新建appserver 与添加前端项目appview类似,同样在根项目tams下创建文件夹“appserver”,并在根项目tams的settings.gradle文件中添加: include 'app-server'//将app-server设置为根项目tams构建生成的一部分 记得重载更改!然后,在appserver下新建文件settings.gradle设置服务端项目名称: rootProject.name = 'app-server' 5.5.2添加子模块appboot 现在,可添加appserver项目的各子模块了。先添加子模块appboot,该模块定义了TAMS项目的服务端应用入口,以及相应的依赖项。添加子模块appboot的步骤如下。 (1) 新建子模块appboot。利用New Module对话框(请参阅5.4.1节),定义appboot相应信息如下。 Name(模块名称): appbootLocation(保存位置): E:\rworks\chapter05\tams\appserver Parent(父项目): tamsGroupId(分组ID): com.tams ArtifactId(打包ID): appboot 图58appserver项目结构 (2) 删除不再需要的根项目tams的src文件夹。 至于具体的服务端应用入口类以及build.gradle构建脚本,将在第6章中详细介绍。 5.5.3添加其他子模块 按照上述方法,继续添加其他子模块,具体如下。 appdomain: 实体类子模块apputil: 工具类子模块 chatroom: 畅论空间子模块collegelist: 学院风采子模块 enrollchart: 招生一览子模块fileservice: 资料上传子模块 notemsg: 消息推送子模块studentquery: 学生查询子模块 userservice: 用户服务子模块 各模块的配置,后续章节具体实现时再详细介绍,这里暂时采用默认设置。现在,appserver项目结构如图58所示。 5.6最终的配置文件settings.gradle 经过优化后的根项目tams的配置文件settings.gradle内容如下。 rootProject.name = 'tams' includeBuild 'app-common' include 'app-view:home' findProject(':app-view:home')?.name = 'home' include 'app-view:public' findProject(':app-view:public')?.name = 'public' include 'app-view:chat-room' findProject(':app-view:chat-room')?.name = 'chat-room' include 'app-view:college-list' findProject(':app-view:college-list')?.name = 'college-list' include 'app-view:enroll-chart' findProject(':app-view:enroll-chart')?.name = 'enroll-chart' include 'app-view:file-service' findProject(':app-view:file-service')?.name = 'file-service' include 'app-view:student-query' findProject(':app-view:student-query')?.name = 'student-query' include 'app-view:user-service' findProject(':app-view:user-service')?.name = 'user-service' include 'app-server:app-boot' findProject(':app-server:app-boot')?.name = 'app-boot' include 'app-server:app-domain' findProject(':app-server:app-domain')?.name = 'app-domain' include 'app-server:app-util' findProject(':app-server:app-util')?.name = 'app-util' include 'app-server:chat-room' findProject(':app-server:chat-room')?.name = 'chat-room' include 'app-server:college-list' findProject(':app-server:college-list')?.name = 'college-list' include 'app-server:enroll-chart' findProject(':app-server:enroll-chart')?.name = 'enroll-chart' include 'app-server:file-service' findProject(':app-server:file-service')?.name = 'file-service' include 'app-server:note-msg' findProject(':app-server:note-msg')?.name = 'note-msg' include 'app-server:student-query' findProject(':app-server:student-query')?.name = 'student-query' include 'app-server:user-service' findProject(':app-server:user-service')?.name = 'user-service' 5.7项目打包后的模块结构 至此,整个项目的结构已经建立起来。按照这样的复合结构思想设计后,当项目各模块最终编写完成、打包后,tamsjars文件夹下会构建生成tams.appboot.server1.0.jar文件,该文件的BOOTINF/lib文件夹下会包含各前后端模块打包后生成的jar文件,例如: tams.user-service.server-1.0.jar tams.user-service.view-1.0.jar tams.chat-room.server-1.0.jar tams.chat-room.view-1.0.jar 模块文件的构成结构清晰,非常方便管理,如图59所示。 图59打包后的项目文件