# 基于 SpringBoot 全家桶搭建项目后端工程

使用 IDEA 引导创建名为 dmallSpringBoot 项目,选中必要的 web 等模块,在此项目下以 'Module' 的形式创建多个工程,项目基础结构如下:

|— dmall // 项目根目录
| |— dmall-common // 公用接口类、工具模块、公用业务逻辑
| |— dmall-user // 用户服务模块
| |— dmall-product // 产品服务模块
| |— doc // 集成过程文档
| |— script // 脚本文件
| |— pom.xml // 组织 pom
| |— README.md 项目文档

# 项目环境

项目区分为 devprd 两个环境,分别用于本地开发、调试和生产。

# 打包参数
mvn package -P dev
# spring启动参数
spring.profiles.active=dev

# 基础 pom 配置

# 根目录下 pom

  • 修改打包方式为 pom
  <packaging>pom</packaging>
  • 组织 module
  <modules>
    <module>dmall-common</module>
    <module>dmall-user</module>
    <module>dmall-product</module>
  </modules>
  • 修改依赖方式 使用 <dependencyManagement> 标签包裹原有依赖,标识子模块中的各依赖如果不标明版本号,则使用此 pom 中的版本号(为了统一基础包版本,多个模块公用的 jar 包都需要在父 pom 中声明版本号)。
  <dependencyManagement>
    <dependencies>
    <!-- -->
    </dependencies>
  </dependencyManagement>

# 子工程 pom

  • dmall 项目作为 parent 依赖
  <parent>
    <artifactId>dmall</artifactId>
    <groupId>com.wch</groupId>
    <version>2.0-SNAPSHOT</version>
    <relativePath>../pom.xml</relativePath>
  </parent>
  • 定义 profile 本项目使用 devprd 两个环境,在有些情况下可能需要使用不同的配置,比如开发和生产环境用于连接数据库的域名是不同的,在这种情况下将两个域名写在统一个配置文件是不明智的,建议区分文件保存。本项目针对区分环境的文件应用到 Maven 的配置文件中(PS:SpringBoot 实际支持 application.properties 配置文件区分环境,但是本项目为了囊括这个知识点使用了 Maven 进行区分):
  <!-- 定义两个profile -->
  <profiles>
    <profile>
      <id>dev</id>
      <properties>
        <profiles.active>dev</profiles.active>
      </properties>
      <activation>
        <!-- 默认激活 dev -->
        <activeByDefault>true</activeByDefault>
      </activation>
    </profile>
    <profile>
      <id>prd</id>
      <properties>
        <profiles.active>prd</profiles.active>
      </properties>
    </profile>
  </profiles>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
    <resources>
      <resource>
        <!-- 打包所有文件 -->
        <directory>src/main/resources/</directory>
        <!-- 排除环境专用文件 -->
        <excludes>
          <exclude>config/dev/</exclude>
          <exclude>config/prd/</exclude>
        </excludes>
      </resource>
      <resource>
        <!-- 打包激活环境文件 -->
        <directory>src/main/resources/config/${profiles.active}/</directory>
        <!-- 打包到 resources/config 目录下-->
        <targetPath>config</targetPath>
      </resource>
    </resources>
  </build>

使用打包命令 mvn package -P prfile 来指定打包需要环境下的配置文件。

# 使用 spring-boot-dependencies

IDEA 创建的 SpringBoot 工程默认使用 spring-boot-starter-parent 作为 parent pom,本项目拥有多个模块,组织 pom 如果使用 spring-boot-starter-parent,子模块无法继承各个包的版本,因此组织 pom 可以使用 spring-boot-dependencies 作为管理依赖,如果子模块需要添加 spring-starter-* 或一些通用包(spring-boot-dependencies 还提供一些常用类库的版本管理,方便整个项目统一依赖版本),无需在组织 pom 声明依赖管理,也不需要在引用时声明版本号。

  <properties>
    <java.version>1.8</java.version>
    <spring-boot.version>2.1.3.RELEASE</spring-boot.version>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>${spring-boot.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

PS:spring-boot-dependencies 2.1.3.RELEASE 使用 mysql-connector-java 8.0.15jdbc 连接参数需要添加 useSSL=false&serverTimezone=Asia/Shanghai

# 基础工具

# 统一响应对象

为了保持与前端交互良好的数据格式,对外的 http 接口服务设置统一响应对象 com.wch.dmall.vo.ResponseVo,其参数如下:

    /**
     * 接口调用成功标志
     */
    private boolean success;

    /**
     * 接口调用状态码
     */
    private int code;

    /**
     * 接口调用消息提示
     */
    private String msg;

    /**
     * 返回数据
     */
    private T data;

统一响应对象的枚举定义在 com.wch.dmall.enums.ResponseEnum,所有响应方式都定义在此枚举中。

# 统一业务异常

有业务含义的异常在必要时以自定义异常的形式手工抛出,统一业务异常定义在 com.wch.dmall.exception.BusinessException

# Json 工具

用于对特定数据使用 Json 的方式进行序列化和反序列化,定义在 com.wch.dmall.utils.JsonUtil

# Protostuff 工具

Protostuff 序列化/反序列化的速度很快,且生成的序列化内容小。定义在 com.wch.dmall.utils.ProtostuffUtil

# 集成 MyBatis

# 数据库访问层依赖

  • 数据访问框架:MyBatis 是一款半自动 ORM 框架,支持手写 SQL(更灵活),能与 Spring 无缝对接(配置方便),也是当下最流行的企业级 Java 应用配置(入坑资料多)。
  • 数据库:MySQL,开源、免费、轻量、好用、入坑资料多、学习成本低。

# 配置

  • 依赖 jar 包:
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
  </dependency>
  <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
  </dependency>
  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
  </dependency>
  • 数据库配置文件 jdbc.properties
url=jdbc:mysql://127.0.0.1:3306/dmall?useUnicode=true&characterEncoding=utf8&useSSL=false
username=RW_dmall
password=RW_dmall
  • MyBatis 数据源配置
@Configuration
public class DatasourceConfig {

  /**
    * 用户数据源
    *
    * @return
    * @throws Exception
    */
  @Bean
  public DataSource userDataSource() throws Exception {
    Resource resource = new PathMatchingResourcePatternResolver().getResource("classpath:config/jdbc.properties");
    Properties properties = PropertiesLoaderUtils.loadProperties(new EncodedResource(resource));
    return DruidDataSourceFactory.createDataSource(properties);
  }

  @Bean
  public org.apache.ibatis.session.Configuration mybatisConfiguration() {
    org.apache.ibatis.session.Configuration mybatisConfiguration = new org.apache.ibatis.session.Configuration();
    mybatisConfiguration.setMapUnderscoreToCamelCase(true);
    return mybatisConfiguration;
  }

  /**
    * 用户sqlSessionFactory
    *
    * @return
    * @throws Exception
    */
  @Bean
  public SqlSessionFactory userSqlSessionFactory() throws Exception {
    SqlSessionFactoryBean userSqlSessionFactoryBean = new SqlSessionFactoryBean();
    userSqlSessionFactoryBean.setDataSource(userDataSource());
    userSqlSessionFactoryBean.setConfiguration(mybatisConfiguration());
    userSqlSessionFactoryBean.setTypeAliasesPackage("com.wch.dmall.po");
    userSqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
    return userSqlSessionFactoryBean.getObject();
  }

  /**
    * 用户sqlSessionTemplate
    *
    * @return
    * @throws Exception
    */
  @Bean
  public SqlSessionTemplate userSqlSessionTemplate() throws Exception {
    return new SqlSessionTemplate(userSqlSessionFactory());
  }
}

# 集成 Redis

  • Redis 连接配置
spring.redis.host=127.0.0.1
spring.redis.password=
spring.redis.timeout=10s
spring.redis.database=0
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1s
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
  • redisTemplate 配置

spring-boot-starter-data-redis 2.x 版本使用 lettuce 替代 jedis 作为底层依赖,完成上述配置后会自动装配 redisConnectionFactory。 替换默认装配的 redisTemplate (默认使用 JdkSerializationRedisSerializer 作为序列化工具,被序列化对象必须实现 Serializable 接口,且序列化的内容长度长,不易阅读)。 在有的情况下我们希望直接将二级制数据写入 redis,结合 protostuff 是个很好的选择,ProtostuffSerializer 的实现在 com.wch.dmall.redis.ProtostuffSerializer参考资料)。

@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisConfig {

  /**
    * 对可读写要求较高的数据,其value序列化成Json格式
    *
    * @param redisConnectionFactory
    * @return
    */
  @Bean
  public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory redisConnectionFactory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setKeySerializer(new StringRedisSerializer());
    template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
    template.setConnectionFactory(redisConnectionFactory);
    return template;
  }

  /**
    * 对可读性要求不高、频繁读写、占用内存高的数据,使用protostuff序列化
    *
    * @param redisConnectionFactory
    * @return
    */
  @Bean
  public RedisTemplate<String, Object> redisBytesTemplate(LettuceConnectionFactory redisConnectionFactory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setKeySerializer(new ProtostuffSerializer());
    template.setValueSerializer(new ProtostuffSerializer());
    template.setConnectionFactory(redisConnectionFactory);
    return template;
  }
}
  • 进一步封装 RedisTemplate

RedisTemplate 提供的 API 实际很糟糕,需要进一步封装,定义在 com.wch.dmall.redis.CacheClient

# 集成 TestNG

  • 普通测试只需在类上加 @org.testng.annotations.Test 注解,此类下的每个 public 方法都可以作为单元测试执行。
  • Spring 集成测试可以编写一个测试父类,其它需要使用 Spring 的测试类只需要继承这个父类即可。
@Test
@SpringBootTest
public class SpringTestBaseConfig extends AbstractTestNGSpringContextTests {
}