Skip to content

springboot扩展组件:基础工具、多数据源、mybatis增强、redis扩展、web配置、token

Notifications You must be signed in to change notification settings

lj-example/springboot2

Repository files navigation

写在前面

  • 任何一个对技术稍有追求的人都不愿意在复制粘贴中来消耗自己对编码的热情。编程注定是一个长期学习的过程,一个项目组的基础代码库的建设正好给后入项目的一个学习的机会,同时也是建设者自我提升的一个过程。
  • 自工作以来,前后经历了不少项目,见识了技术大牛也遇见了复制粘贴的小白。不论实现方式如何,每个人的代码都有值得借鉴的地方,奈何个人不同阶段学习能力不足,无法逐一理解记录,遂期望于通过记录的方式以便日后学习。

项目启动请访问http://localhost:8080/t/doc.html,

配置中心地址http://106.13.74.65:8070/,账号/密码:apollo/admin

目录

详细信息

结构描述

项目分为三个子项目:base-parentcommon-utilstarts

base-parent

描述

  1. 定义了所有maven包的版本信息、maven-plugin常用的组件信息、maven配置信息。
  2. 所有的工具包的maven-parent
  3. 定义了web-base-parent,该prrentweb项目继承的maven-parent模板。
  4. 统一的maven版本管理,有效的避免不同maven组件包版本冲突、不同maven组件包版本不一致导致不必要包引用。

规约

  1. 系统中所有出现的公共组件maven版本信息 都应当在该目录中声明。
  2. 所有项目pom都应继承base-parentweb-base-parent
  3. 应当避免在业务项目模块中二次声明maven组件版本信息。

common-util

描述

  1. 基于springBoot实现了项目开发中常用工具。
  2. 版本信息定义在base-parent,所有组件引用应该避免二次声明maven组件版本信息。

规约

  1. 该模块中的所有子项目都以common-util-xx命名。
  2. 该模块中的所有子项目都必须是commons子模块。

starts

描述

  1. 基于springBoot 实现了项目中开发中常用组件。
  2. 版本信息定义在base-parent,所有组件引用应该避免二次声明maven组件版本信息。

规约

  1. 该模块中的所有子项目都以start-xx命名。
  2. 该模块中的所有子项目都必须是starts子模块。
  3. 该模块中的组件应当区分于commons包:开发无感知且不需要通过声明bean使用的组件。

commons

common-util-base

常用工具

描述

  1. 常用的util工具,该模块下的工具一般都是静态方法。
collection

实现了一个简单的HashMap.builder工具。

HashMap<String, String> hashMap = HashMapBuilder.<String, String>newBuilder()
                    .put("key", "value").build();
date

常用的时间格式化工具

System.out.println(DateFormatUtil.DEFAULT_FORMAT.format(new Date()));
--- 2019-07-15 18:48:48
enumUtil

枚举工具

  • EnumUtilInterface 定义了接口,使用该工具的枚举类必须实现该接口。

        @AllArgsConstructor
        @Getter
        enum TestEnum implements EnumUtilInterface {
            TestEnumOne(1, "name-1"),
            TestEnumTwo(2, "name-2");
            private Integer code;
            private String name;
    
            @Override
            public int getKey() {
      	  return code;
            }
    
            @Override
            public String getValue() {
      	  return name;
            }
        }
    
        ---
        TestEnum anEnum = EnumUtil.getEnum(1, TestEnum.class);
        Assert.assertTrue(TestEnum.TestEnumOne.equals(anEnum)); --true
        Assert.assertTrue(anEnum.is(1)); -- true
phone
  • 基于谷歌的开源包封装的手机号归属定查询、手机号验证工具。
  • 有关手机号的强规则验证,建议使用该工具包。
    PhoneUtilBo phoneBoInfo = PhoneUtil.getPhoneBoInfo("15120052168");
    ---
    PhoneUtilBo(provinceName=北京市, cityName=北京市, carrier=中国移动)
poi
  • List直接转化成Excel工具。
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class PoiModel {
        @ExcelField(name = "名称", column = "A")
        private String name;
    
        @ExcelField(name = "年龄", column = "B")
        private Integer age;
    }
    
    ---
    ArrayList<PoiModel> poiModelArrayList = Lists.newArrayList(
            new PoiModel("name-1", 1),
            new PoiModel("name-2", 2));
    ExcelUtil<PoiModel> poiModelExcelUtil = new ExcelUtil<>(PoiModel.class);
    File file = new File("/Users/xxx/test.xls");
    //导出
    try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
        poiModelExcelUtil.exportExcel(poiModelArrayList, "sheet1", fileOutputStream);
    }
    
    //导入
    try(FileInputStream fileInputStream = new FileInputStream(file)){
        List<PoiModel> poiModelList = poiModelExcelUtil.importBatch(fileInputStream);
        System.out.println(poiModelArrayList.toString());
    }
    
    ---
    //web接口 文件上传 excel 示例
    @PostMapping
    public void test(@RequestParam("file") MultipartFile file) throws Exception {
        try (InputStream inputStream = file.getInputStream()) {
            ExcelUtil<PoiModel> modelExcelUtil = new ExcelUtil<>(PoiModel.class);
            List<PoiModel> poiModelList = modelExcelUtil.importExcel(inputStream);
            System.out.println(poiModelList.toString());
        }
    }

common-util-dataSource

基于spring-boot配置的多数据源自动注入、mybatis组件自动装配、Druid监控自动装配

描述

  • dataSource 提供了基于spring-boot配置的多自动数据源注入、mybatis组件自动装配、Druid监控自动装配。
  • 该模块依赖com.alibaba.druid:阿里的数据库连击工具。
  • 该模块依赖tk.mybatis.mapper-spring-boot-startermybatis增强工具,如果引入该模块之后不使用mybatis自动装配可排除。

使用

使用该组件强烈建议排除spring-DataSourceAutoConfiguration

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

yml加入如下最简配置:

spring:
  datasource:
    ### 数据源
    autoDataSource: enable
    ### 监控
    monitor:
      enable: false
    ### mybatis自动配置
    mybatis:
      enable: true
    url: xx
    username: xx
    password: xx
    dynamicDataSource:
      read:
        url: xx
        username: xxx
        password: xx
      write:
        url: xxx
        username: xxx
        password: xxx

代码调用

@DataSourceType("read")
public List<Demo> selectFromReadDataSource(String name) {
return demoMapper.customizeSqlSelectByName(name);
}
---
"read" 对应配置文件中的名称,此处建议声明为静态常量。
  1. autoDataSource : enable:是否开启dataSource自动注入,默认true开启,开启该配置,会在启动时候根据配置的数据源信息注入两个bean

    • DataSource(name = masterDataSource)DruidDataSource对象,对应配置信息为spring.datasource.url指定的数据源信息。
    • MultiDataSources(name = multiDataSources):包含一个字段Map<String,DataSource>keydynamicDataSource 中数据源名称,DatsSourceDruidDataSource对象,对应配置信息为各个数据源指定的配置信息。
  2. monitor : enable:是否DruidDatsSource监控,默认true开启,非true 关闭,开启该配置,会在项目中提供druid的监控服务,包括sql监控、web请求监控,由于该信息写在内存中,所以会随项目启动被清除,也可以扩展接入日志持久化,具体信息可以参考Druid官方。

    • 开启之后访问路径为:http://host:port/druid
    • 该配置提供自定义配置文件,对应类信息为:MultiDruidProperties.MonitorProperties,配置信息如下所示:
    spring:
      datasource:
        monitor:
          enable: true
          ### 白名单
          allow: xxxx
          ...

    强烈建议在线上服务中配置 白名单、黑名单,不要把该地址在对公网开放

  3. mybatis : enable:是否开启mybatis自动装备,默认true开启,开启该配置,会在启动时候根据配置信息注入mybatis-SqlSessionFactorymybatis-SqlSessionTemplatemybatis-DataSourceTransactionManager

    • 该配置只有在开启了autoDataSource才会生效。
    • 该配置会注册一个基于AbstractRoutingDataSource实现的多数据源bean, DynamicDataSource(name = dynamicDataSource)
    • 该配置会注册一个Aop,拦截对象为@interface DataSourceType
    • 存储本地dataSourceThreadLocalDynamicHandler

扩展

  1. 获取当前系统中的注册的dataSource对象。
//获取 主数据源bean
@Autowired @Qualifier(value = Common.MASTER_DATA_SOURCE_NAME)
private DataSource dataSource;

//获取 多数据源配置信息
@Autowired @Qualifier(value = Common.MULTI_DATA_SOURCE_NAME)
private MultiDataSources multiDataSources;

//获取 Mybatis配置信息
...
---
CommondataSource.common.Common
  1. 不注入mybatis组件,即mybatis: false,依旧想使用DynamicDataSource
@Import(value = {SelfDynamicAutoConfiguration.class, SelfDynamicAutoConfiguration.AspectComponent.class})
@Configuration
public class XxxxAutoConfiguration {
      ...
}
  1. 扩展DynamicDataSource多数据源aop
@Component
@Aspect
public class DynamicAspectTemplateComponent extends DynamicAspectTemplate {
    ...
}

特别注意:mybatis使用的数据源bean为 DynamicDataSource(nam = "dynamicDataSource"),该 数据源 基于AbstractRoutingDataSource,获取key方法如下

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        final String dataType = DynamicHandler.get();
        return dataType;
    }
}

如果 重写 Aop,务必保证在执行主体方法之前 DynamicHandler中已经设置了DataSourceKey


common-util-mybatis

基于tk.mybatis.mapper.starter 简单sql工具、pagehelper-spring-boot-starter分页工具

描述

  • 提供基于tk.mybatis.mapper.starter 简单sql工具、pagehelper-spring-boot-starter分页工具。
  • 建议配合common-util-dataSource一起使用,可以快速实现多数据源、持久层集成。
  • 该组件未提供mybatis相关的bean装配信息,可以自行定义。

使用

该工具提供了两套工具,可以根据当前的业务需求自行选择。

  • 实体Entity extend BaseEntityOnlyId:该类型,工具只会自动处理idcreateTime:创建时间、modifyTime:修改时间。
  • 实体Entity extend BaseEntity:该类型,工具会额外维护status: 数据状态、 creatorId:创建人IDmodifyId:修改人ID
  • 如果项目中只有逻辑删除,不做物理删除,请使用BaseEntity,提供了一套完整的 逻辑CRUD操作
  1. 定义mybatis-mapper扫描包路径
@Configuration
@MapperScan(basePackages = {DataSourceCommon.MAPPER_PATH, DynamicMapperPackage.DYNAMIC_PACKAGE_PATH})
public class DataSourceCommon {
    /**
     * 定义Mapper包路径
     */
    public static final String MAPPER_PATH = "xxx.mapper";
}
---
DynamicMapperPackage.DYNAMIC_PACKAGE_PATH 为 自定义扩展的 sql 工具,此处必须声明。
  1. 声明Entity
@Data
@EqualsAndHashCode(callSuper = true)
@Table(name = "demo")
public class Demo extends BaseEntity {
    /**
     * 测试 - 名称 当数据库字段与实体字段不一致时候
     */
    @Column(name = "demo_name")
    private String name;

    /**
     * 示例 - 字符 当数据库字段与实体字段一致
     */
    private Integer demoNum;
}
  1. 声明Mapper
@Repository
public interface DemoMapper extends Mapper<Demo> {
}
  1. 声明service
public interface DemoService extends BaseDecoratorService<Demo> {
}
  1. 声明serviceImpl
@Service
@RequiredArgsConstructor
public class DemoServiceImpl extends BaseDecoratorServiceImpl<Demo> implements DemoService {
}

至此,在注入@Autowired DemoService demoService,即可调用基础的CRUD方法。

扩展

  1. 自定义mybatis 相关配置:根据官方自动配置即可,需要在@MapperScan中额外声明DynamicMapperPackage.DYNAMIC_PACKAGE_PATH路径。此处建议使用tk.mybatis.spring.annotation.MapperScan

  2. 自定义sql语句

    • 定义SqlProvider
  public class DemoSqlProvider {
	/**
	 * 自定义sql 根据 `name` 模糊查询
	 */
	public String customizeSqlSelectByName(@Param("name") final String name) {
	    String table = SqlHelper.getDynamicTableName(Demo.class, Demo.class.getAnnotation(Table.class).name());
	    String allColumns = SqlHelper.getAllColumns(Demo.class);
	    return new SQL() {{
		SELECT(allColumns);
		FROM(table);
		WHERE("demo_name like CONCAT('%',#{name},'%')");
	    }}.toString();
	}
      }
  • Mapper声明方法。
     @Repository
     public interface DemoMapper extends Mapper<Demo> {
         /**
          * 自定义sql 根据 `name` 模糊查询
          */
         @SelectProvider(type = DemoSqlProvider.class, method = "customizeSqlSelectByName")
         List<Demo> customizeSqlSelectByName(@Param("name") String name);
     }
  1. 模糊查询语法支持和分页语法支持 如果自定义了mybatis组件信息,最好手动声明PageHelper组件注册,注册示例请参照common-util-dataSource.SelfMybatisAutoConfiguration
    WeekendSqls<Demo> demoWeekendSql = WeekendSqls.<Demo>custom()
           .andLike(Demo::getName, "%" + name + "%");
    Example example = Example.builder(Demo.class)
           .andWhere(demoWeekendSql)
           .build();
    PageInfo<Demo> pageInfo = PageHelper.startPage(pageNum, pageSize)
           .doSelectPageInfo(() -> demoService.selectByExample(example));

common-util-redis

基于spring-boot-starter-data-redis工具,实现了key服务隔离

描述

  • 提供基于spring-boot-starter-data-redis工具,实现了key服务隔离。
  • 提供基于reids实现的分布式锁。

使用

  1. 配置redis配置信息,与spring-boot-starter-data-redis官方配置相同。
  2. 配置key隔离前缀,优先匹配spring.redis.prefix,如果不存在,会使用application-name,如果依旧不存在会使用**unknown**。
spring:
  redis:
    prefix: prefix
  application:
    name: serverName
  1. 使用redisTemplate
    @Autowired private RedisTemplate redisTemplate
    ValueOperations valueOperations = redisTemplate.opsForValue();
    valueOperations.set(key, value);
    ---
    redis DB 存储信息为: prefix.key : value
  2. 使用不带有prefixredisTemplate
@Autowired @Qualifier(Common.REDIS_TEMPLATE_WITHOUT_PREFIX_NAME)
private RedisTemplate redisTemplate;
---
Common为:common-util-redis.common.Common
  1. 使用redis
@Autowired private DistributedLock distributedLock;

final String lockKey = "lockKey";
distributedLock.lock(lockKey);
.....
distributedLock.releaseLock(lockKey);

加锁、释放锁必须在同一线程中进行,否则会释放锁失败

扩展

暂无


common-util-i18n

基于spring-message的多语言解决方案

描述

  • 提供了基于spring-message的多语言解决方案。
  • 扩展了spring-message语言检索策略。
  • 提供了正常业务枚举类数据字典 多语言工具。
  • 提供了一套多语言文件分包配置的解决方案。
  • 提供了一套多人配置key冲突解决方案,可通过配置优先级来保证当前业务模块的配置code与其他模块冲突后优先使用该模块对应的配置信息。该功能,在多人协同开发中特别好用!!!
  • 该模块的设计的初衷是为了让业务开发时,不用去关注语言信息,避免在业务代码中显示声明语言类型导致业务代码污染,也不利于后续代码的扩展与迁移,故此提供一套基于Aop的多语言工具。

使用

  1. 配置文件中开启message注入,默认开启,此步骤可以跳过。
spring:
  i18n: enable
  1. 声明多语言文件信息:固定目录 resources/i18n

    • 2.1 在该目录下声明多语言配置文件:i18n.properties
    i18n.useCodeAsDefaultMessage = true
    i18n.headKey = LOCALE
    i18n.defaultLocale = zh_CN
    i18n.file-path = demo

    useCodeAsDefaultMessage:当在多语言文件中没有找到语言信息时是否把code值作为返回值,默认为 false,系统会在找不到对应值之后抛出异常。 headKey: 请求head中存放语言信息的key,默认:LOCALEdefaultLocale: 默认语言信息,如果从头信息中没有获取到语言信息使用该值,默认:zh_CNfile-path:多语言文件目录,必须配置

  • 2.2 在该目录下声明各个业务多语言配置文件信息。如:新建文件夹resources/i18n/demo。新建文件messages.propertiesmessages_en_US.propertiesmessages_zh_CN.propertiesxxx.properties中配置如下:

    test = testValue  -- en_US
    test = 测试 -- zh_CN
  1. 在请求头中 添加LOCALE:zh_CN
  2. 正常业务有关多语言调用
I18nSourceUtil.INSTANCE.getMessage(key);

key 为 语言文件中配置的 key,
  1. 枚举类有关多语言调用
    @AllArgsConstructor
    @Getter
    public enum TestI18nEnum implements I18nEnumInterface {
    	TEST_ONE("testOne"),
    	TEST_TWO("testTwo");
    
    	private String name;
    
    	@Override
    	public String getI18nCode() {
    	  return name;
    	}
    
    	@Override
    	public String getI18nKey() {
    	  return name;
    	}
    }
    
    
    TestI18nEnum.TEST_ONE.getI8nMessage(); -- 可以获取 语言文件中 getI18nKey 返回 code 对应的 value 值。
    
    I18nEnumInterface.as18nList(TestI18nEnum.class); -- 可以把该枚举转换成 key-valuelist,常用于返回给客户端下拉列表。
  2. 数据字典有关多语言调用。 由于各个不同的业务数据字典设计可能各不相同,在此只是个人的一种设计,不一定是最优方法。 在数据字典中,常用的存储结构是codename。但是当出现多语言后,可能会配置多个name,但是这种情况不太利于代码扩展,且在连表查询中有非常大的困难。个人设想是 所有name全部存储在一个字段中,在获取到name之后再解析出与当前请求的语言信息相对应的name
I18nFormatValue messageChina = I18nFormatValue.of(Locale.CHINA, "信息");
I18nFormatValue messageUS = I18nFormatValue.of(Locale.US, "message");
String format = I18nDBUtil.INSTANCE.format(messageChina, messageUS);
//在 name 中存储 该 format 信息。

---
//获取对应的语言信息
I18nDBUtil.INSTANCE.getI18nValue(format);

扩展

  1. 该模块通过注册了一个拦截器在head中获取的语言信息,可以自定义拦截器来覆盖默认拦截器。请务必保证重写的beanNameCommon.I18N_INTERCEPTOR_NAME,务必保证进入方法之前I18nResourceHandler有语言信息。
    	@Component(value = Common.I18N_INTERCEPTOR_NAME)
    	public class I18nInterceptorConfiguration implements HandlerInterceptor {
    
    	    @Override
    	    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
    		I18nResourceHandler.setInfo(Common.DEFAULT_LOCALE);
    		//此处必须返回true 保证后续继续可执行
    		return true;
    	    }
    
    	    @Override
    	    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
    		I18nResourceHandler.clean();
    	    }
    	}
  2. 该模块通过Aop来确认当前线程优先检索哪个语言文件,**如果想保证优先检索某个文件,处理在重名情况下优先使用该目录信息,务必关注。spring 会默认从第一个文件开始查找,找到即返回。**默认的Aop切面为@annotation(xxx.i18n.core.I18nHandler.I18nFolderName)。不扩展此Aop写法为:
@GetMapping
@Override
@I18nFolderName("demo")
public Result getMessageByKey(String key) {
    String message = I18nSourceUtil.INSTANCE.getMessage(key);
    return ResultSuccess.of(message);
}

@I18nFolderName("demo") 也可以直接配置当前类上面。 配置代码如下: java @I18nFolderName("demo") public class I18nController(){} 但是这种情况配置默认的Aop就无法生效,建议按照如下扩展:

```java
    @Aspect
    @Component
    public class I18nAspectTemplateComponent extends AbstractI18nAspectTemplate {
	@Override
	@Pointcut("execution(* xxx.controller.*.*(..))")
	public void enablePath() { }
    }
```

common-util-mail

基于spring-boot-starter-mail封装的邮件发送工具

描述

  • 提供了基于spring-boot-starter-mail封装的邮件发送工具。
  • 该工具使用了全异步处理事件。
  • 提供了基础邮件发送、模板邮件发送、附件类邮件发送、文件流类型附件发送工具方法。

使用

  1. 在yml文件配置邮件发送者信息,完整配置信息参考mail.config.MailProperties,最小配置如下:
spring:
    mail:
      ## 邮箱地址
      username: xxx
      ## 邮箱密码
      password: xxxx
  1. 注入当前发送邮件bean
@Autowired  MailSenderTemplate mailSenderTemplate;
  1. 发送简单邮件
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
simpleMailMessage.setToUserList(Lists.newArrayList("xxxx"));
simpleMailMessage.setContent("内容");
simpleMailMessage.setSubject("主题");
mailSenderTemplate.sendSimpleMail(simpleMailMessage);
  1. 发送模板邮件,该功能模板使用的是springFreeMark模板引擎,具体配置参考mail.config.MailProperties.MailFreeMarkConfigurer,默认模板目录:classpath:/templates/ 4.1. 配置模板信息:message.ftl
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>消息通知</title>
    </head>
    <body>
    <div>
        <h2>邮件消息通知1</h2>
        <p>${username}</p>
    </div>
    </body>
    </html>
    4.2. 发送模板邮件
    TemplateSimpleMailMessage templateSimpleMailMessage = new TemplateSimpleMailMessage();
    templateSimpleMailMessage.setToUserList(Lists.newArrayList("xxxx"));
    templateSimpleMailMessage.setContent("内容");
    templateSimpleMailMessage.setSubject("主题");
    templateSimpleMailMessage.setTemplateName("message.ftl");
    HashMap<String, Object> data = HashMapBuilder.<String, Object>newBuilder()
            .put("username", "示例名称")
            .build();
    templateSimpleMailMessage.setData(data);
    mailSenderTemplate.sendTemplateMail(templateSimpleMailMessage);
  2. 发送附件邮件,此处通过文件流的形式发送 附件excel
AttachmentStreamMailMessage streamMailMessage = new AttachmentStreamMailMessage();
streamMailMessage.setToUserList(Lists.newArrayList("xxxx.com"));
streamMailMessage.setContent("内容");
streamMailMessage.setSubject("主题");
//构建文件流
ArrayList<MailBo> mailBoArrayList = Lists.newArrayList(
        new MailBo("name-1", 1),
        new MailBo("name-2", 2));
ExcelUtil<MailBo> mailBoExcelUtil = new ExcelUtil<>(MailBo.class);

try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(1024)) {
    mailBoExcelUtil.exportExcel(mailBoArrayList, "sheetName", byteArrayOutputStream);
    InputStreamSource inputStreamSource = new ByteArrayResource(byteArrayOutputStream.toByteArray());
    //构建邮件发送需要的文件流
    AttachmentStreamMailMessage.AttachmentStream attachmentStream = AttachmentStreamMailMessage.AttachmentStream.builder()
            .fileName("附件名称.xls")
            .inputStreamSource(inputStreamSource)
            .build();
    streamMailMessage.setAttachmentStreamList(Lists.newArrayList(attachmentStream));
} catch (IOException e) {
    e.printStackTrace();
}
mailSenderTemplate.sendAttachmentStreamMail(streamMailMessage);

扩展

  1. 在一个项目中可能会存在多个邮件发送业务,这些业务会用到不同的通知邮箱,系统配置只提供了一个bean,这种情况需要再额外生成bean。构建`bean 的方式如下:
@Bean(Common.JAVA_MAIL_FREEMARKER_CONFIGURER)
public FreeMarkerConfigurer freeMarkerConfigurer() {
    MailProperties.MailFreeMarkConfigurer freeMark = mailProperties.getFreeMark();
    FreeMarkerConfigurer freeMarkerConfigurer = new FreeMarkerConfigurer();
    freeMarkerConfigurer.setTemplateLoaderPath(freeMark.getTemplateLoaderPath());
    freeMarkerConfigurer.setDefaultEncoding(freeMark.getCharset());
    return freeMarkerConfigurer;
}
---
@Bean(Common.MAIL_SENDER_TEMPLATE)
public MailSenderTemplate mailSenderTemplate(
        @Qualifier MailProperties mailProperties,
        @Qualifier(Common.JAVA_MAIL_SENDER) JavaMailSender javaMailSender,
        @Qualifier(Common.JAVA_MAIL_FREEMARKER_CONFIGURER) FreeMarkerConfigurer freeMarkerConfigurer) {
    return new MailSenderTemplateImpl(mailProperties, javaMailSender, freeMarkerConfigurer);
}

FreeMarkerConfigurer 可以根据业务需求决定是否生成。MailSenderTemplate为邮件发送工具bean。使用如上构造方法构建bean即可注入一个其他 配置的bean


starts

starts-web-tool

spring-web项目的基本信息配置

描述

  • 提供了spring-web项目的基本信息配置。
  • 统一异常处理器。
  • 统一参数返回包装器。
  • 配置统一序列化信息。
  • 提供基础restTemplate
  • 该包在各个项目组中应该在项目开始时就定义好所有信息,且不建议该包进行扩展配置。

使用

  1. 直接引入该包。
  2. controller 中返回正常的Object对象会被包装成
{
    "code": 1000000,
    "message": "操作成功",
    "data": {}
}
  1. controller 调用void方法,返回。
ResultSuccess.defaultResultSuccess();
  1. 业务异常处理
throw BizException.builder().message("错误信息").resultFail(ResultFail.of(500)).build();

扩展

  1. 自定义异常处理。异常处理提供了两个扩展beanExceptionResolver异常信息接受器,该bean可以注册多个,捕获异常后会根据order排序依次调用所有bean的处理方法。ExceptionResultHandler 异常结果处理器,该bean只可以注册一 个,在所有ExceptionResolver处理完成之后 调用该bean方法。系统现在默认提供:

    • DefaultExceptionResultHandler:默认异常结果处理,bean不建议覆盖

    • DefaultBizExceptionResolver:业务异常接收器。

    • DefaultRuntimeExceptionResolver:系统异常接收器。

    自定义异常接收器:

@Configuration
@Slf4j
public class ExceptionLog implements ExceptionResolver {
    @Override
    public void resolve(HttpServletRequest request, Exception exception) {
      String prefix = exception instanceof BizException ? "【业务异常】" : "【系统异常】";
        log.error(prefix + ExceptionUtils.getStackTrace(exception));
    }

    @Override
    public boolean canResolve(HttpServletRequest request, Exception exception, HttpStatus httpStatus) {
        return exception instanceof BizException || exception instanceof RuntimeException;
    }
}
  1. 扩展异常处理器,如果系统提供的异常处理规则不满足 当前项目时候可以扩展处理,也可以通过扩展ExceptionResultHandler来达到相同的效果。
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE + 101)
public class ExceptionAdviceConfiguration extends ExceptionAdvice {

    @ExceptionHandler({HttpRequestMethodNotSupportedException.class, NoHandlerFoundException.class})
    @Override
    public Result notFoundHandler(HttpServletRequest request, Exception exception) {
        return ResultSuccess.defaultResultSuccess();
    }
}
  1. 扩展参数解析器,如果当前项目中集成了其他的web项目,例如swagger,统一的增强结果封装也会封装这类的请求接口从而导致web组件失效,此时可以通过扩展参数解析来解决。
@RestControllerAdvice
public class ResponseAdviceConfiguration extends AbstractResponseAdviceTemplate {

    //定义需要 返回结果需要被封装的 包路径 建议直接返回`controller`路径
    private static final List<String> SUPPORT_PATH = Lists.newArrayList("com.xx.controller");

    @Override
    public List<String> supportPath() {
        return SUPPORT_PATH;
    }
}


如果如上依旧不能满足需求,可以考虑重写 supports 方法

starts-web-user

基于redis的token无状态用户登录信息状态管理

描述

  • 提供了一个基于redistoken无状态用户登录信息状态管理。
  • 提供了基于注解的userToken信息转换。
  • 提供了userToken自动校验、过期处理、单点互踢等功能。
  • 实现了一个基于lettuce的简单redisTemplate

使用

  1. 考虑在生成环境中用户登录信息redis一般会与业务分开,在yml文件中配置user-redis使用的redis信息。
user-sessions:
  redis:
    password:
    cluster:
      nodes: 

user-sessions.redis.tokenExpireSecondtoken 生存周期,默认 90天。 user-sessions.redis.oldTokenExpireSecond:当前token被踢后在系统中保留时间,默认 7 天。

  1. 在业务登录、登出接口中植入相关的user-redis-session代码

    • 2.1 登录逻辑
    //处理正常登录逻辑之后之后
    UserBusinessBo businessBo = UserBusinessBo.builder()
            .mobile(mobile)
            .userId(userId)
            .build();
    String token = userLoginSessionService.buildUserTokenAfterLogin(businessBo);
    • 2.2 登出逻辑
    //处理正常登出逻辑之后
    userLoginSessionService.cleanUserTokenAfterLoginOut(token);
  2. Controller获取用户信息

    • 3.1 简单获取userId
      test(@UserToken Long userId){}
      ---
      可以直接获取 userId.
  • 3.2 获取用户信息UserSession

    test(@UserToken UserSession userSession){}
    ---
    可以直接获取 UserSession.对象

    不建议在业务代码中直接从本地线程中获取userId,个人建议在service中显示声明,保证接口的语义明确

扩展

  1. 扩展异常提示信息,组件默认提供了不同情况下的异常信息提示,可以根据实际情况自定义异常信息提示。
  @Slf4j
  @Component
  @RequiredArgsConstructor
  public class UserIllegalTokenHandleDefaultServiceImpl implements UserIllegalTokenHandleService {

  /**
   * Head 头中没有 token 信息
   */
  @Override
  public void assertAndHandleNoTokenHead() {
    throw new TokenBizException("head miss token info");
  }

  /**
   * 当前 token 不存在
   */
  @Override
  public void assertAndHandleNoTokenInfo() {
    throw new TokenBizException("Token information is lost");
  }

  /**
   * 当前 token 已经过期
   * @param oldUserSession 过期用户信息
   */
  @Override
  public void assertAndHandOldToken(UserSessionRedis oldUserSession) {
      throw new TokenBizException("Your account is logged in elsewhere");
    }
  }

starts-web-version

基于注解的Api接口版本管理,实现不同客户端接口隔离

描述

  • 提供了基于注解的接口版本控制器功能。
  • 实现了多终端接口隔离。
  • 提供多种可选注解,可以灵活自由搭配。
  • 该实现中需要使用Head公共信息,Head公共信息在HeadCommon中与客户端约定好。

使用

  1. 开启版本控制,spring.version.enable:true默认开启。
  2. 类级别控制。
@RestController
@RequestMapping("apiVersion")
@RequiredArgsConstructor
@ApiVersion("v2")
public class ApiVersionController(){
    ...
}
  • 添加注解 @ApiVersion("v2")
  • 设置当前类下所有接口访问的最低版本信息,与方法上的配置 为 "&&"关系,如例所示: 该类最低版本未"V2",对应:VERSION 参数 version.compareTo("2") >= 0
  • 该配置会在请求路径中自动生成版本信息,因此该类的请求路径为 apiVersion/xx -> v2/apiVersion/xx。
  • 兼容模式:2 、V2 等价:V2。
  1. 方法级别控制
/**
 * 方法版本控制
 * 该接口只能在 渠道为 1,3 的客户端生效
 */
@GetMapping("apiForMethod")
@ApiClientInfo(channel = @ApiClientChannel(value = "1,3", operator = VersionOperator.IN))
public Result apiForMethod() {
	return ResultSuccess.defaultResultSuccess();
}
/**
 * 方法版本控制
 * 该接口只有在 客户端版本 > 2.2,渠道号为 1001、1002 ,来源非 IOS 情况下生效
 */
@GetMapping("apiForTypeAndMethod")
@ApiClientInfo(
	version = @ApiClientVersion(value = "2.2", operator = VersionOperator.GT),
	channel = @ApiClientChannel(value = "1001,1002", operator = VersionOperator.IN),
	platform = @ApiClientPlatform(value = "ios", operator = VersionOperator.NE)
)
public Result apiForTypeAndMethod() {
	return ResultSuccess.defaultResultSuccess();
}
  • 该配置为方法上版本、渠道、来源限制。三个条件为 "&&" 关系
  • version: 配置该接口版本信息,与类上配置 "&&" 关系,如例所示,该接口实际对应版本信息为: >2.2
  • channel: 配置该接口渠道信息,如例所示,该接口对应渠道为:1001 || 1002, 不区分大小写
  • platform:配置该接口来源信息,如例所示,该接口对应来源为:ios,不区分大小写

扩展

暂无

About

springboot扩展组件:基础工具、多数据源、mybatis增强、redis扩展、web配置、token

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published