微服务开发参考手册

2021/7/6 java

# 分页

# 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 数据脱敏

# 数据脱敏

数据脱敏是指对某些敏感信息通过脱敏规则进行数据变形,实现敏感隐私数据的可靠保护。此处给出的脱敏规则为通用产品规范,遇到数据安全性较强的业务场景,可根据业务场景自行调整。

# 全部脱敏

一般用于金额、时间等特别重要敏感的信息,需要对所有数字进行脱敏。数据用一个「***」代替

# 部分脱敏

一般用于需要部分信息进行识别的状况,只需要对部分信息进行脱敏处理,但数字真实位数保留。数据脱敏部分用「*」代替

image-20210429172847039

# 使用说明

@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,避免内存占用过多。

导出需使用到如下注解

  1. @ExcelModel(includeAllField,excludeParent,workbookType,sheetName,useFieldNameAsTitle,defaultValue)(可选,用于全局设定,一般情况下只需要使用sheetName)
  2. @IgnoreColumn(可选,用于排出不需要导出的字段)
  3. @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;
}

# 动态导出

动态导出分为三种:

  1. 动态指定标题、字段顺序;
  2. 字段分组;
  3. 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) {}
}