微服务开发参考手册
# 分页
# mapper接口分页方式 自定义sql 自动分页
// 方式1
// GenTable 需要继承SupperDTO
public PageResult<GenTable> selectGenTableList(GenTable genTable) {
PageResult<GenTable> pageInfo = PageKit.pageInfo(genTable, () -> genTableMapper.selectGenTableList(genTable));
return pageInfo;
}
//方式2
// GenTableDTO 需要继承SupperDTO
public PageResult<GenTableDTO> selectGenTableList(GenTableDTO genTableDTO) {
PageResult<GenTableDTO> pageInfo = PageKit.pageInfo(genTable, () -> genTableMapper.selectGenTableList(genTableDTO), GenTableDTO.class);
return pageInfo;
}
//方式3
// GenTable 需要继承SupperDTO
public PageResult<GenTable> selectGenTableList(GenTable genTable) {
PageKit.startPage(genTable)
return PageKit.pageInfo(genTableMapper.selectGenTableList(genTable));
}
# 高级过滤
# mapper.xml 方式使用:
java
public PageResult<CacheDTO> queryCachePageList(CacheDTO cacheDTO) {
PageResult<CacheDTO> pages = new PageResult<CacheDTO>();
// 高级过滤,第二个参数普通搜索条件(如果没用配置我的方案则执行普通搜索)
Map<String, Object> param = WrappersUtil.querySqlStr(cacheDTO, ()->BeanCopyUtils.transferValueToMap(cacheDTO));
// TODO 如果高级和普通都有必要查询条件参数则直接 对返回的 param 直接设置条件,同时再xml 将固定条件(如当前用户id)放在WHERE 最前面
// pages.pus("userId",cacheDTO.getUserId());
int total = cacheMapper.queryCacheListCount(param);
pages.setTotal(Long.valueOf(total));
if(total > 0) {
//查询缓存集合
List<Cache> list = cacheMapper.queryCacheListPage(param);
pages.setRows(BeanCopyUtils.listTransfer(list, CacheDTO.class));
}
return pages;
}
xml
<select id="queryCacheListPage" parameterType="map"
resultMap="BaseResultMap">
SELECT * FROM biz_cache
<WHERE>
<choose>
<!-- 判断是否走高级方案搜索 -->
<when test="queryScheme">
${customSqlSegment}
</when>
<!-- 否则走普通sql 搜索 -->
<otherwise>
<if test="cacheKey != null and cacheKey != ''">
and cache_key like concat('%',#{cacheKey},'%')
</if>
<if test="cacheType != null">
and cache_type = #{cacheType}
</if>
and alive_flag = 1
</otherwise>
</choose>
</WHERE>
</select>
# 条件构造器方式:
public PageResult<CacheDTO> queryCachePageList(CacheDTO cacheDTO) {
// 如果没用我的方案 则 执行第二个接口函数(普通查询条件)
QueryWrapper<Cache> queryWrapper = WrappersUtil.queryWrapper(cacheDTO,wrapper->{
wrapper.eq("cache_key", cacheDTO.getCacheKey())
});
// TODO 如果高级和普通都有必要查询条件参数则直接 对返回的 queryWrapper 直接设置条件
// queryWrapper.eq("user_id",cacheDTO.getUserId());
return PageKit.page(this, cacheDTO, queryWrapper,CacheDTO.class);
}
# 自定义注解用法
# @MoneyFormat 金额格式化
value 值 支持BigDecimal、BigInteger、Number等类型
@Data
public class xxxDTP{
/**
* 格式化double对 DecimalFormat 做封装
* Parameters:
* pattern 格式 格式中主要以 # 和 0 两种占位符号来指定数字长度。0 表示如果位数不足则以 0 填充,# 表示只要有可能就把数字拉上这个位置。
* • 0 =》 取一位整数
* • 0.00 =》 取一位整数和两位小数
* • 00.000 =》 取两位整数和三位小数
* • # =》 取所有整数部分
* • #.##% =》 以百分比方式计数,并取两位小数
* • #.#####E0 =》 显示为科学计数法,并取五位小数
* • ,### =》 每三位以逗号进行分隔,例如:299,792,458
* • 光速大小为每秒,###米 =》 将格式嵌入文本
* • 默认值: ,###.##
* value 值,支持BigDecimal、BigInteger、Number等类型
* Returns:
* 格式化后的值 类型为String
*/
@MoneyFormat(pattern=",###.##")
private BigDecimal money;
}
# @TimestampFormat 时间戳转日期
可以将 数值类型的javabean 属性字段 加上注解 指定格式 转换为日期
@Data
public class xxxDTP{
/**
* value: 指定转换日期格式 调用常量类 {@link DatePattern} 默认 yyyy-MM-dd HH:mm:ss
* unit: false 秒,true 毫秒 默认false
*/
@TimestampFormat(value=DatePattern.NORM_DATETIME_PATTERN,unit=false)
private Integer timsetamp;
}
# @MobileHide 数据脱敏
# 数据脱敏
数据脱敏是指对某些敏感信息通过脱敏规则进行数据变形,实现敏感隐私数据的可靠保护。此处给出的脱敏规则为通用产品规范,遇到数据安全性较强的业务场景,可根据业务场景自行调整。
# 全部脱敏
一般用于金额、时间等特别重要敏感的信息,需要对所有数字进行脱敏。数据用一个「***」代替
# 部分脱敏
一般用于需要部分信息进行识别的状况,只需要对部分信息进行脱敏处理,但数字真实位数保留。数据脱敏部分用「*」代替
# 使用说明
@FieldHide、@MobileHide、@CitizenIdHide 三种注解使用介绍
栗子:
public class DemoDTO{
...
@FieldHide(value=3,endExclude=7)
private String custName;
@FieldHide(1)
private String name;
@FieldHide(value=2,searchChar='@')
private String email;
//手机号
@MobileHide
private String mobile;
//身份证
@CitizenIdHide
private String citizenId
...
}
@GetMappen('/getinfo')
public DemoDTO getInfo(){
DemoDTO demoDTO = new DemoDTO();
demoDTO.setName("王小二");
demoDTO.setCustName("安徽同徽信息责任有限公司");
demoDTO.setEmail("liguoping@itonghui.org");
demoDTO.setMobile("13111111111");
demoDTO.setCitizenId("340111198004052320");
return demoDTO;
}
输出
{
"name":"王**",
"custName":"安徽同****任有限公司",
"email":"li*******@itonghui.org",
"mobile":"131****1111",
"citizenId":"340************320"
}
注解 | 参数 | 说明 |
---|---|---|
@FieldHide | value:开始位置 endExclude:结束位置(不包含) searchChar:被查找的字符 最后的位置 indexOf | 自定义脱敏 注: endExclude 优先于 searchChar |
@MobileHide | 标准11位手机号 保留手机号码前 3 位与后 4 位 | 手机号脱敏 |
@CitizenIdHide | 按标准18位身份证号码 保留前三位与后三位,其余「*」表示,仅能识别该人的省市与是男是女 | 身份证脱敏 |
# @Lock 分布式锁
注解方式
@Lock(keys = "#orderDTO.orderId", keyConstant = "testd")
public Object saveOrder(OrderDTO orderDTO){
...
}
keys: 如果keys有多个,如果不设置,则使用 联锁 支持spel表达式 获取函数中的参数值
keyConstant:key的静态常量:当key的spel的值是LIST,数组时使用+号连接将会被spel认为这个变量是个字符串,只能产生一把锁,达不到我们的目的,而我们如果又需要一个常量的话.这个参数将会在拼接在每个元素的后面
代码方式
// 你要锁住 {key}
RedisLock redisLock = new RedisLock("{key}");
try {
redisLock.lock();
// TODO 执行你的业务逻辑
} finally {
redisLock.unlock();
}
批量组合锁
批量组合锁 该方法用于在执行特定业务逻辑时,对多个资源进行加锁,确保并发情况下数据的一致性 它首先尝试获取所有指定资源的锁,成功后执行业务逻辑,完成后释放所有锁 如果在执行过程中发生异常,会记录错误日志并抛出运行时异常 在任何情况下,方法最后都会确保释放所有已获取的锁。
private static final String LOCK_PRODUCT = "LOCK_PRODUCT_"
@PostMapper("updateProductStock")
public Result<ProductDTO> updateProductStock(@RequestBody ProductDTO productDTO) {
// 多个 key 拼接常量固定前缀 格式要求 : LOCK_{业务标识}_+ {key}
List<String> productIdKeys = ToolUtil.toList(productDTO.getProductList(), item-> LOCK_PRODUCT + item.getProductId());
// 批量组合锁
Result<ProductDTO> result = LockKit.locksGroup(()->
// 批量处理业务代码
productService.updateProductStock(productDTO), productIdKeys);
return result;
}
# @EnumSerialize 枚举序列化
/** 定义枚举类 */
public enum MenuTypeEnum implements SupEnum {
MENU(0, "菜单"), SUB_MENU(1, "子菜单"), BUTTON(2, "按钮权限");
private int code;
private String value;
MenuTypeEnum(int code, String value) {
this.code = code;
this.value = value;
}
/** 必须实现 valueOf 方法 序列化时会调用该方法 */
public static MenuTypeEnum valueOf(Integer code) {
if (ToolUtil.isNotEmpty(code)) {
MenuTypeEnum[] values = MenuTypeEnum.values();
for (MenuTypeEnum menuTypeEnum : values) {
if (code.equals(menuTypeEnum.getCode())) {
return menuTypeEnum;
}
}
}
return null;
}
...
/** DTO */public class MenuDTO{
// 指定 enum类
// 可以设置isCode参数: @EnumSerialize(value = MenuTypeEnum.class,isCode = true)
// 设置isCode 则 序列化会带上原始值 "{value:明文,code:0}"
@EnumSerialize(MenuTypeEnum.class)
private Integer menuType;
}
@EnumSerialize(MenuTypeEnum.class)
响应报文结果:
{"menuType": "菜单"}
设置 isCode = true @EnumSerialize(value = MenuTypeEnum.class,isCode = true)
返回如下内容:
{"menuType": {"value":"菜单","code":0}}
# 枚举反序列化
public class DemoDTO{
@JSONField(deserializeUsing=EnumDeserializer.class)
private Integer menuType;
}
注意:在 menuType 序列化格式必须是如下格式:
{"menuType": {"value":"菜单","code":0}}
json格式 进行反序列化才有效
# office 操作
# 添加依赖jar包:
<dependency>
<groupId>com.ith.common</groupId>
<artifactId>ith-common-office</artifactId>
</dependency>
# excel导出与导入
# 导出
默认导出采用普通方式导出List< Bean >,适合小数据量场景,如数据量很大-10万+,建议使用DefaultStreamExcelBuilder,避免内存占用过多。
导出需使用到如下注解
- @ExcelModel(includeAllField,excludeParent,workbookType,sheetName,useFieldNameAsTitle,defaultValue)(可选,用于全局设定,一般情况下只需要使用sheetName)
- @IgnoreColumn(可选,用于排出不需要导出的字段)
- @ExcelColumn(title,order,format,groups,defaultValue,style)
对应注解详情请见:注解
默认导出默认计算宽度、斑马线背景色,若无需上述样式,请调用
noStyle()
方法
附件导出示例:
@GetMapping("/default/excel/example")
public void defaultBuild(HttpServletResponse response) throws Exception {
List<ArtCrowd> dataList = this.getDataList();
Workbook workbook = DefaultExcelBuilder.of(ArtCrowd.class)
.build(dataList);
AttachmentExportUtil.export(workbook, "艺术生信息", response);
}
附件加密导出示例:
@GetMapping("/default/excel/example")
public void defaultBuild(HttpServletResponse response) throws Exception {
List<ArtCrowd> dataList = this.getDataList();
Workbook workbook = DefaultExcelBuilder.of(ArtCrowd.class)
.build(dataList);
AttachmentExportUtil.encryptExport(workbook, "艺术生信息", response,"123456");
}
文件导出示例:
List<ArtCrowd> dataList = this.getDataList();
Workbook workbook = DefaultExcelBuilder.of(ArtCrowd.class)
.build(dataList);
FileExportUtil.export(workbook, new File("/User/demo.xlsx"));
文件加密导出示例:
List<ArtCrowd> dataList = this.getDataList();
Workbook workbook = DefaultExcelBuilder.of(ArtCrowd.class)
.build(dataList);
FileExportUtil.encryptExport(workbook, new File("/User/demo.xlsx"),"123456");
数据获取:
private List<ArtCrowd> getDataList() {
List<ArtCrowd> dataList = new ArrayList<>(1000);
for (int i = 0; i < 1000; i++) {
ArtCrowd artCrowd = new ArtCrowd();
artCrowd.setName("李四");
artCrowd.setAge(18);
artCrowd.setGender("Woman");
artCrowd.setPaintingLevel("一级证书");
artCrowd.setDance(true);
artCrowd.setAssessmentTime(LocalDateTime.now());
artCrowd.setHobby("钓鱼");
dataList.add(artCrowd);
}
return dataList;
}
@ExcelModel(sheetName = "艺术生")
public class ArtCrowd {
@ExcelColumn(order = 0, title = "姓名")
private String name;
@ExcelColumn(order = 1, title = "年龄")
private Integer age;
@ExcelColumn(order = 2, title = "性别")
private String gender;
@ExcelColumn(order = 3,title = "绘画等级")
private String paintingLevel;
@ExcelColumn(order = 4, title = "是否会跳舞")
private boolean dance;
@ExcelColumn(order = 5, title = "考核时间", format = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime assessmentTime;
@IgnoreColumn
private String hobby;
}
# 动态导出
动态导出分为三种:
- 动态指定标题、字段顺序;
- 字段分组;
- Map导出;
# 1.动态指定标题、字段顺序
// title
List<String> titles = new ArrayList<>();
titles.add("姓名");
titles.add("年龄");
// field display order
List<String> order = new ArrayList<>();
order.add("name");
order.add("age");
// display data
List<TestDO> dataList = this.getData();
Workbook workbook = DefaultExcelBuilder.of(TestDO.class)
.sheetName("default example")
.titles(titles)
.fieldDisplayOrder(order)
.build(dataList);
private List<TestDO> getData(){
TestDO testDO = new TestDO();
testDO.setName("张三");
TestDO testDO1 = new TestDO();
testDO1.setName("李四");
TestDO testDO2 = new TestDO();
testDO2.setName("王五");
testDO2.setAge(15);
TestDO testDO3 = new TestDO();
testDO3.setName("陈六");
testDO3.setAge(25);
List<TestDO> dataList = new ArrayList<>();
dataList.add(testDO);
dataList.add(testDO1);
dataList.add(testDO2);
dataList.add(testDO3);
return dataList;
}
# 2.字段分组
该种方式基于注解@ExcelColumn的groups属性
@ExcelColumn(title="姓名",groups={People.class})
String name;
@ExcelColumn(title="年龄")
String age;
DefaultExcelBuilder.of(ArtCrowd.class).build(People.class);
上述示例将只导出姓名
字段
# 3.Map导出
Map<String, String> headerMap = new HashMap<>();
headerMap.put("a", "测试A");
headerMap.put("b", "测试B");
List<Map> dataMapList = new ArrayList<>();
Map<String, Object> v1 = new HashMap<>();
v1.put("a", "数据a1");
v1.put("b", 3);
Map<String, Object> v2 = new HashMap<>();
v2.put("a", "数据a2");
v2.put("b", 5);
dataMapList.add(v1);
dataMapList.add(v2);
List<String> titles = new ArrayList(headerMap.values());
List<String> orders = new ArrayList(headerMap.keySet());
Workbook workbook = DefaultExcelBuilder.of(Map.class)
.sheetName("sheet1")
.titles(titles)
.widths(10,20)
.fieldDisplayOrder(orders)
.build(dataMapList);
FileExportUtil.export(workbook, new File("/tmp/zz.xlsx"));
Map导出默认不支持格式化,需自行格式化,如需指定Map中value类型,如超链接等,value需设置如下:
import com.github.liaochong.myexcel.core.constant.LinkUrl;
import com.github.liaochong.myexcel.core.container.Pair;
Map<String, Object> obj = new HashMap<>();
obj.put("2", Pair.of(LinkUrl.class,"http://www.baidu.com"));
# thymeleaf模板构建导出
/**
* use non-default-style excel builder
* 模板文件放置在resources下
*
* @param response response
*/
@GetMapping("/thymeleaf/example")
public void build(HttpServletResponse response) {
Map<String, Object> dataMap = this.getDataMap();
try (ExcelBuilder excelBuilder = new ThymeleafExcelBuilder()) {
Workbook workbook = excelBuilder
// fileTemplate(dirPath,fileName)
.classpathTemplate("/templates/thymeleafToExcelExample.html")
.build(dataMap);
AttachmentExportUtil.export(workbook, "thymeleaf_excel", response);
}
}
private Map<String, Object> getDataMap() {
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("sheetName", "freemarker_excel_example");
List<String> titles = new ArrayList<>();
titles.add("Category");
titles.add("Product Name");
titles.add("Count");
dataMap.put("titles", titles);
List<Product> data = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Product product = new Product();
if (i % 2 == 0) {
product.setCategory("蔬菜");
product.setName("小白菜");
product.setCount(100);
} else {
product.setCategory("电子产品");
product.setName("ipad");
product.setCount(999);
}
data.add(product);
}
dataMap.put("data", data);
return dataMap;
}
# 模板示例
Thymeleaf 模板语法自行参考网络资源
<table>
<caption>${sheetName}</caption>
<thead>
<tr style="background-color: #6495ED">
<th colspan="3" style="text-align: center;vertical-align: middle;font-weight: bold;font-size: 14px;">产品介绍</th>
</tr>
<tr>
<#list titles as title>
<th>${title}</th>
</#list>
</tr>
</thead>
<tbody>
<#list data as item>
<tr>
<td>${item.category}</td>
<td>${item.name}</td>
<td>${item.count}</td>
<td url>百度地址</td>
</tr>
</#list>
</tbody>
</table>
# 导入
@PostMapping("imports")
public Object imports(@RequestParam("file") MultipartFile multipartFile){
if (multipartFile.isEmpty()) {
return ResultUtil.errorMsg("请上传excel文件");
}
String extension = FilenameUtils.getExtension(multipartFile.getOriginalFilename());
if (!StrUtil.containsAnyIgnoreCase(extension, "xls","xlsx") ) {
return ResultUtil.errorMsg("请上传xls、xlsx 类型的excel文件");
}
List<BusMaterialDoor> result = DefaultExcelReader.of(BusMaterialDoor.class)
.sheet(0) // 0代表第一个,如果为0,可省略该操作,也可sheet("名称")读取
.rowFilter(row -> row.getRowNum() > 0)
.beanFilter((o)->ToolUtil.isNotEmpty(o.getTotalNo()))
.read(multipartFile.getInputStream());
if(ToolUtil.isEmpty(result)){
return ResultUtil.errorMsg("excel文件数据不能为空");
}
//插入数据库
materialDoorService.saveBatch(result);
return ResultUtil.success();
}
// 方式一:全部读取后处理
List<ArtCrowd> result = DefaultExcelReader.of(ArtCrowd.class)
.sheet(0) // 0代表第一个,如果为0,可省略该操作,也可sheet("名称")读取
.rowFilter(row -> row.getRowNum() > 0) // 如无需过滤,可省略该操作,0代表第一行
.beanFilter(ArtCrowd::isDance) // bean过滤
.startSheet(sheet->System.out.println(sheet.getName())) // 在开始读取sheet前执行指定操作
.read(path.toFile());// 可接收inputStream
// 方式二:读取一行处理一行,可自行决定终止条件
// readThen有两种重写接口,返回Boolean型接口允许在返回False情况下直接终止读取
DefaultExcelReader.of(ArtCrowd.class)
.sheet(0) // 0代表第一个,如果为0,可省略该操作,也可sheet("名称")读取
.rowFilter(row -> row.getRowNum() > 0) // 如无需过滤,可省略该操作,0代表第一行
.beanFilter(ArtCrowd::isDance) // bean过滤
.readThen(path.toFile() ,artCrowd -> {System.out.println(artCrowd.getName);});// 可接收inputStream
public class ArtCrowd {
// index代表列索引,从0开始
@ExcelColumn(index = 0)
private String name;
@ExcelColumn(index = 1)
private String age;
@ExcelColumn(index = 2,format="yyyy-MM-dd")
private Date birthday;
}
# 注解介绍
注解(Annotation) | 位置(Position) | 方法(Method) | 说明(Desc) |
---|---|---|---|
@ExcelModel | Class | includeAllField:是否导出该类的所有字段,默认为trueexcludeParent:是否排除父类字段,默认为falseworkbookType:设置工作簿类型,默认WorkbookType.SXLSX,不建议修改sheetName:设置导出的工作簿sheet名称useFieldNameAsTitle:设置是否直接使用字段名称作为导出标题,默认falsedefaultValue:设置全局导出时,字段为Null时的默认值,如字段也包含defaultValue,则进行覆盖wrapText:是否开启自动换行,默认开启 |
dateFormat:设置全局LocalDate格式,默认为yyyy-MM-dd
dateTimeFormat:设置全局Date\LocalDateTime格式,默认为yyyy-MM-dd HH:mm:ss
ignoreStaticFields:是否忽略静态字段导出,默认忽略
titleSeparator:标题分离器,用于多级标题,默认为“->”
|设置导出Excel的公共属性 @ExcelColumn|Field|
title:导出时使用,该字段对应导出列标题
order:导出时使用,该字段对应的导出展示顺序,初始为0,建议按列顺序设置,如字段顺序就是列顺序,可不设置
format:导入导出时使用,当字段类型为时间类型、金钱类型时生效,用于设置该时间类型字段格式化,如yyyy-MM-dd HH:mm:ss
groups:导出时使用,当前字段所属分组,构建时根据传入的分组选择导出的字段
index:导入时使用,用于标志该字段对应的Excel列,从0开始,不允许重复
defaultValue:导出时使用,设置导出时字段为Null时的默认值
width:导出时使用,用于自定义单元格宽度,配合AutoWidthStrategy.CUSTOM_WIDTH使用
style:自定义样式
|设置导入、导出字段属性 @IgnoreColumn|Field|-|排除该字段的导出
# word 导出
- 模板格式:
- 代码示例:
Map<String, Object> model = new HashMap<String, Object>();
model.put("name", "波波");
model.put("users", Arrays.asList(ImmutableMap.of("name","小波波","sex",23,"iphone","13111111111")
,ImmutableMap.of("name","小波波2","sex",33,"iphone","13122222222")));
model.put("account", Arrays.asList(ImmutableMap.of("name","小波波","sex",23,"iphone","13111111111")
,ImmutableMap.of("name","小波波2","sex",33,"iphone","13122222222")));
WordUtil.words("E:\\test.docx", "E:\\test2.docx", model, "users");
注意
WordUtil.words 方法最后一个参数是可变参数 指明你提供的数据对象中哪些key 是List集合填充至表格中的
表格中的 "{ { users } }" 用于识别 在数据对象中的这个key是一个List,并在当前行的下一行动态插入
下一行注意每个列的属性都以中括号表达式 识别当前这些属性都属于"{ { users } }" 的集合中的每个对象
- 模板填充结果
# word 转换 PDF文件
- 环境准备
下载安装 “LibreOffice” (opens new window) 或 “OpenOffice 4” (opens new window)
部署环境 linux 请统一安装指定目录:/usr/lib/libreoffice 或 /usr/lib/OpenOffice
注意
如果部署环境 linux 不在这指定目录下 则请配置环境变量:
office.home = /usr/lib/libreoffice/program/soffice.bin
或
office.home = /usr/lib/OpenOffice/program/soffice.bin
配置参数
在 "xxx-web.jar" 项目中“application.yml” 里添加如下配置参数:
jodconverter: local: enabled: true
代码示例
// 转换并保持至本地
PdfUtil.convertPdf("E:/中国电子云供应商安全评估.docx", "E:/中国电子云供应商安全评估.pdf");
// 转换并下载PDF
PdfUtil.export("E:/中国电子云供应商安全评估.docx", "中国电子云供应商安全评估.pdf", response);
说明
所有转换的方法原文件可以是输入流:“InputStream”, 或 String 的path 全路径 参数类型
# MQ消息
# 依赖jar
<dependency>
<groupId>com.ith.starter</groupId>
<artifactId>mq-ith-boot-starter</artifactId>
<version>1.0.1-SNAPSHOT</version>
</dependency>
# bootstrap.yml配置
spring:
cloud:
nacos:
config:
shared-configs:
- jms-mq-${spring.profiles.active}.yaml
# 队列模式
# queue发送者
@Autowired
private QueueSender queueSender;
public void test() {
// send(key, value可以使对象或字符串);
queueSender.send("queue", value);
}
# delayQueue(延时队列)发送者
应用场景
生产者将消息发送到消息队列后,并不期望立马投递这条消息,而是推迟到某个时间点之后将消息投递给消费者进行消费
例如
1、抢了一个小米手机,超过30分钟未支付,订单自动作废
2、会员即将到期了
3、招投标期限、团购期限、秒杀活动期限等...
同时延时消息也可以广泛应用于信息提醒等比较通用的场景
@Autowired
private QueueSender queueSender;
public void test() {
// send(key, value可以使对象或字符串,延迟发送时间 单位毫秒);
queueSender.delaySend("queue", value, 60*1000);
}
注意
1、打开 activemq 服务目录 “apache-activemq-5.14.5\conf”
2、打开 “activemq.xml” 配置文件在 broker 标签里新增属性:schedulerSupport="true"
3、重启 activemq 服务
# queue消费者
@JmsListener(destination = "queue", containerFactory = QueueSender.QUEUE_FACTORY)
public void receiveMsgdcc(String text) {
//返回的json字符串 需要自己进行反序列化成对象
System.out.println("<<<<<<============ queue 收到消息: " + text);
}
# 订阅模式
# topic 发送者
@Autowired
private TopicSender topicSender;
public void test() {
// send(key, value可以使对象或字符串);
topicSender.send("topic", value);
}
# topic 消费者
@JmsListener(destination = "topic", containerFactory = TopicSender.TOPIC_FACTORY)
public void receiveMsgd(String text) {
//返回的json字符串 需要自己进行反序列化成对象
System.out.println("<<<<<<============ topic 收到消息: " + text);
}
# OpenFeign 远程API调用
# 服务提供者
maven::xxx-web.jar
包路径:com.itonghui.web.feign.UserFeign.java
@RestController
@RequestMapping(ServiceNameConstants.SYSTEM_SERVICE + "/user")
public class UserFeign {
@GetMapping("/getUser3")
public User getUser3(@RequestBody User user) throws InterruptedException {
return user;
}
@GetMapping("/getUser2")
public User getUser2(User user) throws InterruptedException {
return user;
}
@GetMapping("/getUsers")
public String[] getUsers(String ids) throws InterruptedException {
return ids.split(",");
}
}
# 实现服务调用
创建一个接口
添加
@FeignClient(contextId = "sys-auth", name = "ith-service-system", path = ServiceNameConstants.SYSTEM_SERVICE + "/user")
contextId 如果提供的api有多个则需用定义唯一id区分注入bean, name 是服务名, path 与服务提供者url保持一致。
编写接口方法
接口方法跟服务提供者的Controller代码基本一致,但参数绑定会有些不同,具体看代码注释
定义请求方式
定义参数
定义返回值
maven:xxx-api.jar
包路径:com.itonghui.user.api.UserFeign.java
`@FeignClient(contextId = "sys-auth", name = "ith-service-system", path = ServiceNameConstants.SYSTEM_SERVICE + "/user")`
public interface UserFeign {
//必须用@RequestParam注解,否则提供者接收不到
@GetMapping("/getUsers")
String[] get(@RequestParam("ids") String ids);
//传递对象参数用@SpringQueryMap注解
@GetMapping("/getUser2")
User get2(@SpringQueryMap User user);
//传递Json参数用@RequestBody注解
@GetMapping("/getUser3")
User get3(@RequestBody User user);
}
接口定义好以后就可以直接使用了,不需要实现类,下面Controller层调用它
//注入上面定义的接口
@Autowired
private UserFeign userFeign;
//Controller层方法
@GetMapping("/getUsers")
public String[] getUsers(String ids){
//调用接口的方法
return userFeign.get(ids);
}
# RPC Feign 远程调用接口URL 命名规范
再 "ith-common-domain"项目 包 “com.itonghui.domain.ServiceNameConstants” 定义 业务中心的feign url
// 定义规则 /feign/{业务名称}/{功能名称}
/**
* All right reserved,Designed By www.itonghui.com
*
* @ClassName: ProductAppFeign
* @Description: App商品对外PC Feign 远程接口 实现业务逻辑处理类
* @author: chenye@itonghui,org
* @date: 2021/3/9 13:46
* @version: V1.0
*/
@RestController
@RequestMapping(ServiceNameConstants.PRODUCT_SERVICE + "/productapp")
public class ProductAppFeign {}
/**
* All right reserved,Designed By www.itonghui.com
*
* @ClassName: ProductAppFeign
* @Description: App商品对外RPC Feign 远程接口调用
* @author: chenye@itonghui,org
* @date: 2021/3/9 13:46
* @version: V1.0
*/
@FeignClient(contextId = "product-productapp", name = "ith-service-product",
path = ServiceNameConstants.PRODUCT_SERVICE + "/productapp")
public interface ProductAppFeign{}
# 多种数据库兼容
MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库 databaseId 属性的所有语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。
# sql 兼容模式
为支持多厂商特性只要在“xxxxMappen.xml ”像下面这样:
<!-- 兼容mysql数据库 sql -->
<select id="selectByTables" parameterType="map"
resultType="java.util.Map" databaseId="mysql">
SELECT table_name as tableName ,table_comment as comments FROM
information_schema.tables
WHERE table_schema=#{tableSchema}
<if test="tableName!=null and tableName!=''">
and table_name like concat('%',#{tableName},'%')
</if>
</select>
<!-- 兼容oracle数据库 sql -->
<select id="selectByTables" parameterType="map" resultType="java.util.Map" databaseId="oracle">
select table_name as tableName,comments from user_tab_comments
<where>
<if test="tableName!=null and tableName!=''">
table_name like '%' || #{tableName} || '%'
</if>
</where>
</select>
主要通过 databaseId="oracle"
属性指明该sql 是哪种数据的 sql 语句
# JDBC 多厂商数据库配置信息
数据库 | url | DriverCLass | 信息描述 |
---|---|---|---|
MySql | jdbc:mysql://{0}:{1}/{2} | com.mysql.cj.jdbc.Driver | |
Oracle | jdbc:oracle:thin:@{0}:{1}:{2} | oracle.jdbc.driver.OracleDriver | |
SqlServer | jdbc:sqlserver://{0}:{1};DatabaseName={2} | com.microsoft.sqlserver.jdbc.SQLServerDriver | |
达梦数据库 | jdbc:dm://{0}:{1}?compatibleMode=mysql | dm.jdbc.driver.DmDriver | |
PostgreSql | jdbc:postgresql://{0}:{1}/{2} | org.postgresql.Driver |
说明:"{0}" ip地址 "{1}" 端口号 "{2}" 库名
# 附件查询工具类
在此之前,我们每次查询附件信息时,都需要写重复的代码,太过冗余,于是在每个业务服务中建了附件的查询工具类,便于我们业务中附件的查询。
//调用例子,以资产闲置明细中查询资产档案图片为例:
FileFeignUtil.queryFileList(purchaseAssetIdleItemDTOS,PurchaseAssetIdleItemDTO::getAssetId,AssetConstant.ASSET_IMAGE_TYPE,"fild");
里面暂时写了两个方法,每个字段的传参也进行了备注
public class FileFeignUtil {
private static FileFeign fileFeign = ApplicationContextHolder.getBean(FileFeign.class);
/**
* 功能描述: 查询附件工具类
* @param t 业务model
* @param function 图片bizid业务名称
* @param bizName 附件bizType业务类型
* @param parentField 赋值字段
* @return void
* @author zhengnana@itonghui.org
* @last update date: 2022/2/22 11:31
*/
public static<T,R> void queryFile(T t, Function<? super T, ? extends R> function, String bizName, String parentField) {
queryFileList(Arrays.asList(t),function,bizName,parentField);
}
/**
* 功能描述: 批量查询附件工具类
* @param itermList 业务集合
* @param function 图片bizid业务名称
* @param bizName 附件bizType业务类型
* @param parentField 赋值字段
* @return void
* @author zhengnana@itonghui.org
* @last update date: 2022/2/22 11:31
*/
public static<T,R> void queryFileList(List<T> itermList, Function<? super T, ? extends R> function, String bizName, String parentField) {}
}