Springboot 多模块项目结构(子父工程结构)搭建
引言
使用多模块项目结构在Spring Boot中具有显著的优势,特别是在大型项目中,能够显著提高开发效率和代码管理的灵活性。通过模块化开发、统一依赖管理、代码复用、构建优化以及独立部署和版本控制,多模块结构为项目的开发和维护提供了强有力的支持(主要是想在之前的兔子系统里面加一个即时通讯的功能,然后发现很多模块加在一起会很乱,就想着这样改造了)。
一、项目搭建(后端)
1、创建项目
New Project。
直接下一步,具体需要的依赖可以之后在 pom 文件中添加。
这就是刚建好的项目结构,可能有人会问为什么我这里这么这么简洁,可以给大家看一下怎么设置的(右键设置,然后选择想要显示哪些,怎么显示,这里仅供参考):
删除父工程下面的 src 文件。
2、修改父 pom.xml 文件
这里提供我的作为参考(注释加的很详细了,如果你们在这一步有点小问题,可以暂时忽略,因为我在下面提供了完整能跑的 pom,但是应该是没问题的):
org.springframework.boot spring-boot-starter-parent 2.6.13 4.0.0 com.zero-ecological-chain zero-ecological-chain 1.0.0 零式生态链 一个简易的生态链系统 pom 11 UTF-8 UTF-8 aliyunmaven 阿里云公共仓库 https://maven.aliyun.com/repository/public org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-jdbc org.mybatis.spring.boot mybatis-spring-boot-starter 2.2.2 com.oracle.database.jdbc ojdbc11 23.3.0.23.09 org.projectlombok lombok true org.apache.commons commons-lang3 3.12.0 log4j log4j 1.2.16
3、创建子工程 zec-api
这个子工程是用来对外提供接口的模块,进行前后端交互或其他服务的请求。
右击项目,选择 New --> Module。
填写子工程信息,点击 Next。
直接点 Create。
创建成功之后,修改子父工程的 pom 文件,把他们关联起来。
4、创建子工程 zec-system-management
这个子工程是系统管理模块,这个这一步就跟上面的一样,就不重复展示了。
test 文件没用,可以删掉,再把子工程 zec-system-management 的启动类删除,目前的项目结构就是这样了。
5、创建项目基本结构
创建每个子工程模块对应的功能包。
这里面的 yml 配置文件我放在下面给大家参考一下:
## 指定web容器访问端口号 rabbit: name: 零式生态链 version: v-1.0.0 ## web容器端口号 server: port: 8088 ## 配置数据库连接 spring: servlet: multipart: max-file-size: 40MB max-request-size: 40MB ## 设置单个文件上传的最大内存 datasource: driver-class-name: oracle.jdbc.OracleDriver url: jdbc:oracle:thin:@//localhost:1521/ORCL username: 用户名 password: 密码 ## 配置mybatis中mapper.xml文件扫描 mybatis: type-aliases-package: com.*.pojo.sel.* mapper-locations: classpath:mapper/*.xml # mapper.xml文件映射
6、连接数据库
使用最右边的 Database 连接 Oracle 数据库。
输入连接信息。
输入完成后,点击下面的 TestConnection,测试是是否正常连接,如果出现这样的就表示成功了,点击 Apply。
然后在 IDEA 右侧就能看到数据库信息了。
7、数据库创建表
建一个测试用的用户表,测试数据可以自己加上。
-- 用户表 CREATE TABLE zero_ecological_chain.CUSTOMER ( ID VARCHAR2(32) NOT NULL, ACCOUNT VARCHAR2(9) NOT NULL, NICK_NAME VARCHAR2(32) NOT NULL, BIRTHDAY Date, SEX VARCHAR(1), CREATE_TIME Date DEFAULT SYSDATE NOT NULL, UPDATE_TIME Date DEFAULT SYSDATE NOT NULL, IS_DELETE varchar(1) DEFAULT 'N' NOT NULL );
建好之后,在 idea 右侧点一下刷新,就能看到刚才建好的表。
8、生成实体类
利用 idea 自带的功能,可以自动生成实体类。
右击刚才看到的表。
选择实体类生成的位置,点击 OK。
生成好之后对实体类做一些改造,使用 @Data 注解替换原本的 getter、setter 方法。
9、开始写代码
我这里是都写好了,给大家看一下完整的能跑的项目结构。
代码我从上到下依次展示:
子工程 zec-api
AjaxResult
工具类,用于向前端发送 RESTful API 风格的数据。
package com.zecApi.utils; import java.util.HashMap; /** * 操作消息提醒 */ public class AjaxResult extends HashMap { private static final long serialVersionUID = 1L; /** * 状态码 */ public static final String CODE_TAG = "code"; /** * 返回内容 */ public static final String MSG_TAG = "msg"; /** * 数据对象 */ public static final String DATA_TAG = "data"; /** * 数据总数量 */ public static final String DATA_COUNT = "count"; /** * 状态类型 */ public enum Type { /** * 成功 */ SUCCESS(200), /** * 警告 */ WARN(301), /** * 校验失败 */ FAILED(-1), /** * 错误 */ ERROR(500); private final int value; Type(int value) { this.value = value; } public int value() { return this.value; } } /** * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。 */ public AjaxResult() { } /** * 初始化一个新创建的 AjaxResult 对象 * * @param type 状态类型 * @param msg 返回内容 */ public AjaxResult(Type type, String msg) { super.put(CODE_TAG, type.value); super.put(MSG_TAG, msg); } /** * 初始化一个新创建的 AjaxResult 对象 * * @param type 状态类型 * @param msg 返回内容 * @param data 数据对象 */ public AjaxResult(Type type, String msg, Object data) { super.put(CODE_TAG, type.value); super.put(MSG_TAG, msg); if (StringUtils.isNotNull(data)) { super.put(DATA_TAG, data); } } /** * 初始化一个新创建的 AjaxResult 对象 * * @param data 数据对象 */ public AjaxResult(Object data) { if (StringUtils.isNotNull(data)) { super.put(DATA_TAG, data); } } /** * 初始化一个新创建的 AjaxResult 对象 * @param type 状态类型 * @param msg 返回内容 * @param data 数据对象 * @param count 数据总数量 */ public AjaxResult(Type type, String msg, Object data,Integer count) { super.put(CODE_TAG, type.value); super.put(MSG_TAG, msg); if (StringUtils.isNotNull(data)) { super.put(DATA_TAG, data); } if(count!=null){ super.put(DATA_COUNT,count); } } /** * 方便链式调用 * * @param key 键 * @param value 值 * @return 数据对象 */ @Override public AjaxResult put(String key, Object value) { super.put(key, value); return this; } /** * 返回成功消息 * * @return 成功消息 */ public static AjaxResult success() { return AjaxResult.success("操作成功"); } /** * 返回成功数据 * * @return 成功消息 */ public static AjaxResult success(Object data) { return AjaxResult.success("操作成功", data); } /** * 返回成功消息 * * @param msg 返回内容 * @return 成功消息 */ public static AjaxResult success(String msg) { return AjaxResult.success(msg, null); } /** * 返回成功消息 * * @param msg 返回内容 * @param data 数据对象 * @return 成功消息 */ public static AjaxResult success(String msg, Object data) { return new AjaxResult(Type.SUCCESS, msg, data); } /** * 返回警告消息 * * @param msg 返回内容 * @return 警告消息 */ public static AjaxResult warn(String msg) { return AjaxResult.warn(msg, null); } /** * 返回警告消息 * * @param msg 返回内容 * @param data 数据对象 * @return 警告消息 */ public static AjaxResult warn(String msg, Object data) { return new AjaxResult(Type.WARN, msg, data); } /** * 返回错误消息 * * @return */ public static AjaxResult error() { return AjaxResult.error("操作失败"); } /** * 返回错误消息 * * @param msg 返回内容 * @return 警告消息 */ public static AjaxResult error(String msg) { return AjaxResult.error(msg, null); } /** * 返回错误消息 * * @param msg 返回内容 * @param data 数据对象 * @return 警告消息 */ public static AjaxResult error(String msg, Object data) { return new AjaxResult(Type.ERROR, msg, data); } }
StringUtils
字符串工具类。
package com.zecApi.utils; import java.util.Collection; import java.util.Map; /** * 字符串工具类 */ public class StringUtils extends org.apache.commons.lang3.StringUtils { /** * 空字符串 */ private static final String NULLSTR = ""; /** * 下划线 */ private static final char SEPARATOR = '_'; /** * 获取参数不为空值 * * @param value defaultValue 要判断的value * @return value 返回值 */ public static T nvl(T value, T defaultValue) { return value != null ? value : defaultValue; } /** * * 判断一个Collection是否为空, 包含List,Set,Queue * * @param coll 要判断的Collection * @return true:为空 false:非空 */ public static boolean isEmpty(Collection coll) { return isNull(coll) || coll.isEmpty(); } /** * * 判断一个Collection是否非空,包含List,Set,Queue * * @param coll 要判断的Collection * @return true:非空 false:空 */ public static boolean isNotEmpty(Collection coll) { return !isEmpty(coll); } /** * * 判断一个对象数组是否为空 * * @param objects 要判断的对象数组 * * @return true:为空 false:非空 */ public static boolean isEmpty(Object[] objects) { return isNull(objects) || (objects.length == 0); } /** * * 判断一个对象数组是否非空 * * @param objects 要判断的对象数组 * @return true:非空 false:空 */ public static boolean isNotEmpty(Object[] objects) { return !isEmpty(objects); } /** * * 判断一个Map是否为空 * * @param map 要判断的Map * @return true:为空 false:非空 */ public static boolean isEmpty(Map map) { return isNull(map) || map.isEmpty(); } /** * * 判断一个Map是否为空 * * @param map 要判断的Map * @return true:非空 false:空 */ public static boolean isNotEmpty(Map map) { return !isEmpty(map); } /** * * 判断一个字符串是否为空串 * * @param str String * @return true:为空 false:非空 */ public static boolean isEmpty(String str) { return isNull(str) || NULLSTR.equals(str.trim()); } /** * * 判断一个字符串是否为非空串 * * @param str String * @return true:非空串 false:空串 */ public static boolean isNotEmpty(String str) { return !isEmpty(str); } /** * * 判断一个对象是否为空 * * @param object Object * @return true:为空 false:非空 */ public static boolean isNull(Object object) { return object == null; } /** * * 判断一个对象是否非空 * * @param object Object * @return true:非空 false:空 */ public static boolean isNotNull(Object object) { return !isNull(object); } /** * * 判断一个对象是否是数组类型(Java基本型别的数组) * * @param object 对象 * @return true:是数组 false:不是数组 */ public static boolean isArray(Object object) { return isNotNull(object) && object.getClass().isArray(); } /** * 去空格 */ public static String trim(String str) { return (str == null ? "" : str.trim()); } /** * 截取字符串 * * @param str 字符串 * @param start 开始 * @return 结果 */ public static String substring(final String str, int start) { if (str == null) { return NULLSTR; } if (start str.length()) { return NULLSTR; } return str.substring(start); } /** * 截取字符串 * * @param str 字符串 * @param start 开始 * @param end 结束 * @return 结果 */ public static String substring(final String str, int start, int end) { if (str == null) { return NULLSTR; } if (end str.length()) { end = str.length(); } if (start > end) { return NULLSTR; } if (start * 此方法只是简单将占位符 {} 按照顺序替换为参数
* 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
* 例:
* 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
* 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a
* 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
* * @param template 文本模板,被替换的部分用 {} 表示 * @param params 参数值 * @return 格式化后的文本 */ /* public static String format(String template, Object... params) { if (isEmpty(params) || isEmpty(template)) { return template; } return StrFormatter.format(template, params); }*/ /** * 下划线转驼峰命名 */ public static String toUnderScoreCase(String str) { if (str == null) { return null; } StringBuilder sb = new StringBuilder(); // 前置字符是否大写 boolean preCharIsUpperCase = true; // 当前字符是否大写 boolean curreCharIsUpperCase = true; // 下一字符是否大写 boolean nexteCharIsUpperCase = true; for (int i = 0; i 0) { preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1)); } else { preCharIsUpperCase = false; } curreCharIsUpperCase = Character.isUpperCase(c); if (i HelloWorld * * @param name 转换前的下划线大写方式命名的字符串 * @return 转换后的驼峰式命名的字符串 */ public static String convertToCamelCase(String name) { StringBuilder result = new StringBuilder(); // 快速检查 if (name == null || name.isEmpty()) { // 没必要转换 return ""; } else if (!name.contains("_")) { // 不含下划线,仅将首字母大写 return name.substring(0, 1).toUpperCase() + name.substring(1); } // 用下划线将原始字符串分割 String[] camels = name.split("_"); for (String camel : camels) { // 跳过原始字符串中开头、结尾的下换线或双重下划线 if (camel.isEmpty()) { continue; } // 首字母大写 result.append(camel.substring(0, 1).toUpperCase()); result.append(camel.substring(1).toLowerCase()); } return result.toString(); } /** * 驼峰式命名法 * 例如:user_name->userName */ public static String toCamelCase(String s) { if (s == null) { return null; } if (s.indexOf(SEPARATOR) == -1) { return s; } s = s.toLowerCase(); StringBuilder sb = new StringBuilder(s.length()); boolean upperCase = false; for (int i = 0; iCustomerController
用户控制类,跟前端交互用的。
package com.zecApi.zecSystemManagement.controller; import com.zecApi.utils.AjaxResult; import com.zecApi.zecSystemManagement.pojo.sel.CustomerSelPojo; import com.zecApi.zecSystemManagement.service.CustomerService; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import java.util.List; @RestController @RequestMapping("ZecSystemManagement/CustomerController") /** * 用户的控制层 */ public class CustomerController { /** * 调用业务访问层对象 */ @Resource private CustomerService customerService; /** * PostMapping:处理 POST 请求 * 查询所有用户信息的方法 */ @PostMapping("/selCustomers") public AjaxResult selCustomers() { List customerSelPojoList = customerService.selCustomers(); String msg = customerSelPojoList!= null? "查询成功!" : "查询失败!"; return new AjaxResult(customerSelPojoList!= null? AjaxResult.Type.SUCCESS : AjaxResult.Type.ERROR, msg, customerSelPojoList); } }ZecApiApplication
项目启动类。
package com.zecApi; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ZecApiApplication { public static void main(String[] args) { SpringApplication.run(ZecApiApplication.class, args); } }application.yml
项目的配置文件。
## 指定web容器访问端口号 zero-ecological-chain: name: 零式生态链 version: v-1.0.0 ## web容器端口号 server: port: 8088 ## 配置数据库连接 spring: datasource: driver-class-name: oracle.jdbc.OracleDriver url: jdbc:oracle:thin:@//localhost:1521/ORCL username: ZERO_ECOLOGICAL_CHAIN password: 1231 ## 配置mybatis中mapper.xml文件扫描 mybatis: type-aliases-package: com.zecApi.*.pojo.sel.* mapper-locations: classpath:mapper/*.xml # mapper.xml文件映射banner.txt
项目启动的时候,控制台打印的信息(个人爱好,可有可无)。
Application Name: ${zero-ecological-chain.name} Application Version: ${zero-ecological-chain.version} Spring Boot Version:${spring-boot.version} ${AnsiColor.BRIGHT_CYAN} ,--. ,----.. ,--.'| / / \ ,---,. ,----.. ,--,: : | / . : ,' .' \ ,--, / / \ ,`--.'`| ' : . / ;. \ ,---.' .' | ,'_ /|| : : | : : | |. ; / ` ; | | |: | .--. | | :. | ;. / : | \ | :; | ; \ ; | : : : /,'_ /| : . |. ; /--` | : ' '; || : | ; | ' : | ; | ' | | . .; | ; __ ' ' ;. ;. | ' ' ' : | : \| | ' | | || : |.' .' | | | \ |' ; \; / | | | . |: | | : ' ;. | '_.' : ' : | ; .' \ \ ', / ' : '; || ; ' | | '' ; : \ | | | '`--' ; : / | | | ; : | : ; ; |' | '/ .' ' : | \ \ .' | : / ' : `--' \ : / ; |.' `---` | | ,' : , .-./\ \ .' '---' `----' `--`----' `---`pom.xml
zec-api 下的 pom 文件
zero-ecological-chain com.zero-ecological-chain 1.0.0 4.0.0 zec-api zec-system-management com.zec-system-management 1.0.0子工程 zec-system-management
ComCustomerMapper
用户映射层。
package com.zecApi.zecSystemManagement.mapper; import com.zecApi.zecSystemManagement.pojo.sel.CustomerSelPojo; import org.apache.ibatis.annotations.Mapper; import java.util.List; @Mapper public interface ComCustomerMapper { List selCustomers(); }CustomerSelPojo
这个跟它下面的 CustomerPojo 是一样的,至于为什么多加一个这个,我在 springboot+vue 项目创建中说过了,这边就不多说了。
package com.zecApi.zecSystemManagement.pojo.sel; import lombok.Data; @Data public class CustomerSelPojo { private String id; private String account; private String nickName; private String birthday; private String sex; private String createTime; private String updateTime; private String isDelete; }CustomerImpl
用户服务层的实现类。
package com.zecApi.zecSystemManagement.service.impl; import com.zecApi.zecSystemManagement.mapper.ComCustomerMapper; import com.zecApi.zecSystemManagement.pojo.sel.CustomerSelPojo; import com.zecApi.zecSystemManagement.service.CustomerService; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.List; @Service public class CustomerImpl implements CustomerService { //创建数据访问层对象 @Resource private ComCustomerMapper comCustomerMapper; @Override public List selCustomers() { return comCustomerMapper.selCustomers(); } }CustomerService
用户的服务层
package com.zecApi.zecSystemManagement.service; import com.zecApi.zecSystemManagement.pojo.sel.CustomerSelPojo; import java.util.List; public interface CustomerService { /** * 查询所有用户信息 * @return */ List selCustomers(); }ResourceCustomerMapper.xml
用户的数据访问层。
SELECT T.ID, T.ACCOUNT, T.NICK_NAME, TO_CHAR(T.BIRTHDAY, 'YYYY-MM-DD') AS BIRTHDAY,T.SEX, T.CREATE_TIME, T.UPDATE_TIME, T.IS_DELETE FROM zero_ecological_chain.CUSTOMER Tpom.xml
zero-ecological-chain com.zero-ecological-chain 1.0.0 4.0.0 zec-system-management com.zec-system-management父工程 zero-ecological-chain
pom.xml
org.springframework.boot spring-boot-starter-parent 2.6.13 4.0.0 com.zero-ecological-chain zero-ecological-chain 1.0.0 零式生态链 一个简易的生态链系统 pom zec-api zec-system-management 11 UTF-8 UTF-8 aliyunmaven 阿里云公共仓库 https://maven.aliyun.com/repository/public org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-jdbc org.mybatis.spring.boot mybatis-spring-boot-starter 2.2.0 org.springframework.boot spring-boot-starter-web com.oracle.database.jdbc ojdbc11 23.3.0.23.09 org.projectlombok lombok true org.apache.commons commons-lang3 3.12.0 log4j log4j 1.2.16这里就是肉眼可见的所有项目代码了,建议的写代码顺序就是:
控制层 --> 服务层 --> 服务层实现类 --> 映射层 --> 数据访问层。实体类就是缺什么就在那个 pojo 里面加上就行了。
10、项目启动
编辑一下这个启动项目。
点击启动。
启动后,控制台出现这样的信息,就没问题了。
11、接口测试
我这里使用的是 postman,如果能正常查到数据,就没问题了。
如果要再加别的子工程的话,就跟子工程 zec-system-management 一样,然后控制层在 zec-api里面写就行了。
二、Vue 的构建
后端的数据能够正常查询并返回就没问题了。
前端就是把数据渲染上去,可以参考我之前写的 SpringBoot + Vue 项目创建详细步骤。
如果有问题的话,可以在评论区问,也可以私聊,我看到了会回的。