diff --git a/server/src/main/java/com/aisino/iles/common/config/HttpClientConfig.java b/server/src/main/java/com/aisino/iles/common/config/HttpClientConfig.java new file mode 100644 index 0000000..22f0e55 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/config/HttpClientConfig.java @@ -0,0 +1,27 @@ +package com.aisino.iles.common.config; + +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.FormHttpMessageConverter; +import org.springframework.web.client.RestTemplate; + +/** + * http客户端配置 + * @author hx + * @since 20250303 + */ +@Configuration +public class HttpClientConfig { + /** + * 提供默认支持的rest http调用客户端 + * @param builder http客户端构造器 + * @return http调用客户端 + */ + @Bean + public RestTemplate restTemplate(RestTemplateBuilder builder) { + return builder + .additionalMessageConverters(new FormHttpMessageConverter()) // 支持在RequestEntity body设置form urlencoded参数信息 + .build(); + } +} diff --git a/server/src/main/java/com/aisino/iles/common/controller/DynamicLogSwitcherController.java b/server/src/main/java/com/aisino/iles/common/controller/DynamicLogSwitcherController.java new file mode 100644 index 0000000..b89295f --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/controller/DynamicLogSwitcherController.java @@ -0,0 +1,45 @@ +package com.aisino.iles.common.controller; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.LoggerContext; +import cn.hutool.core.util.StrUtil; +import com.aisino.iles.common.util.Constants; +import com.aisino.iles.common.model.Ok; +import com.aisino.iles.common.model.Result; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 公共/动态日志输出开关 + * @author hx + * @since 2023-07-14 + */ +@RestController +@RequestMapping(value = Constants.NO_AUTH_API_PREFIX, produces = "application/json;charset=UTF-8") +@Slf4j +public class DynamicLogSwitcherController { + /** + * 日志开关接口地址 + */ + private final String LOG_SWITCHER_URI = "/log-switcher"; + // 获取日志工厂会话 + private final LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + + /** + * 日志开关 + * @param logger 日志 + * @param level 日志级别 + */ + @PostMapping(LOG_SWITCHER_URI) + public Result logSwitcher(String logger, String level){ + // 调整目标日志级别 + String msg = StrUtil.format("调整{}的日志级别为{}",logger,level); + log.info(msg); + loggerContext.getLogger(logger).setLevel(Level.toLevel(level)); + return Ok.of(msg, 0); + } + +} diff --git a/server/src/main/java/com/aisino/iles/common/controller/FileManagerController.java b/server/src/main/java/com/aisino/iles/common/controller/FileManagerController.java new file mode 100644 index 0000000..a83751a --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/controller/FileManagerController.java @@ -0,0 +1,134 @@ +package com.aisino.iles.common.controller; + +import cn.hutool.core.io.file.FileNameUtil; +import cn.hutool.core.util.StrUtil; +import com.aisino.iles.common.util.Constants; +import com.aisino.iles.common.model.Ok; +import com.aisino.iles.common.model.Result; +import com.aisino.iles.common.model.enums.IndustryCategoryForFile; +import com.aisino.iles.common.service.FtpService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.time.LocalDate; +import java.util.HashMap; +import java.util.Optional; +import java.util.UUID; + +@Slf4j +@Validated +@RestController +@RequestMapping(Constants.NO_AUTH_API_PREFIX) +public class FileManagerController { + + private final FtpService ftpService; + /** + * 获取下载地址 + */ + private static final String DOWNLOAD_URL = "/download-url/**"; + /** + * 下载数据 + */ + private static final String DOWNLOAD_DATA = "/download-data/**"; + + public FileManagerController(FtpService ftpService) { + this.ftpService = ftpService; + } + + @PostMapping(value = "/file/getFileUrl") + public Ok getFileUrl( + @NotBlank(message = "key不能为空") + @RequestParam(required = false) + String key + ) { + return Ok.of(ftpService.getFileUrl(key)); + } + + /** + * 上传单个流文件 + * + * @param data 文件数据 + * @return 上传结果 + */ + @PostMapping(value = "/file/upload", produces = "application/json;charset=UTF-8") + public Ok> uploadFileBinary( + @NotEmpty(message = "文件内容不能为空") + @RequestBody(required = false) + byte[] data, + @NotBlank(message = "文件名不能为空") + @RequestHeader(required = false) + String fileName, + @NotBlank(message = "行业类别不能为空") + @RequestHeader(name = "industry-category", required = false) + String industryCategory + ) { + try { + InputStream is = new ByteArrayInputStream(data); + String key; + if (fileName.matches("^A/\\d{8}/.+")) { + key = fileName; + } else { + key = FtpService.generateKey( + IndustryCategoryForFile.getIndustryCategoryByCode(industryCategory), + LocalDate.now(), + UUID.randomUUID().toString(), + Optional.of(fileName) + .map(StrUtil::trimToNull) + .map(FileNameUtil::extName) + .map(StrUtil::trimToNull) + .filter(s -> s.length() <= 4) + .map("."::concat) + .orElse("") + ); + } + String url = ftpService.uploadFileWithFileName(key, is, true); + return Ok.of(new HashMap() {{ + put("saveName", key); + put("url", url); + }}); + } catch (Exception e) { + throw new RuntimeException("上传流文件异常", e); + } + } + + /** + * 获取文件下载连接 + * + * @return 下载地址(在data属性里) + */ + @GetMapping(DOWNLOAD_URL) + public Result downloadUrl(HttpServletRequest req) { + String key = req.getRequestURI().split(Constants.NO_AUTH_API_PREFIX + DOWNLOAD_URL.split("\\*\\*")[0])[1]; + return Ok.of(ftpService.getFileUrl(key)); + } + + /** + * 下载数据内容 + * + * @param req 请求 + */ + @GetMapping(DOWNLOAD_DATA) + public void downloadData(HttpServletRequest req, HttpServletResponse res) { + String key = req.getRequestURI().split(Constants.NO_AUTH_API_PREFIX + DOWNLOAD_DATA.split("\\*\\*")[0])[1]; + res.setContentType("application/octet-stream"); + try (InputStream inputStream = ftpService.getFileAsStream(key); + OutputStream outputStream = res.getOutputStream()) { + byte[] buffer = new byte[8192]; + int len; + while ((len = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, len); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + +} diff --git a/server/src/main/java/com/aisino/iles/common/controller/FilesUploadController.java b/server/src/main/java/com/aisino/iles/common/controller/FilesUploadController.java new file mode 100644 index 0000000..826ab18 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/controller/FilesUploadController.java @@ -0,0 +1,191 @@ +package com.aisino.iles.common.controller; + +import java.util.*; + +import jakarta.validation.constraints.NotNull; + +import com.aisino.iles.common.util.Constants; +import com.aisino.iles.common.model.Result; +import com.aisino.iles.common.model.UploadFile; +import com.aisino.iles.common.model.enums.IndustryCategoryForFile; +import com.aisino.iles.common.service.FtpService; +import com.aisino.iles.common.util.Base64Util; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.util.MultiValueMap; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest; + +/** + * 接收上传文件转发到ftp服务器 + */ +@RestController +@RequestMapping(Constants.API_PREFIX) +@Validated +public class FilesUploadController { + private final FtpService ftpService; + private final String[] allowFileType = new String[] {"JPG","GIF","PNG","JPEG","BMP","DOC","DOCX","XLS" + ,"XLSX","PDF","PPT","PPTX","TXT","CVS","TIF","WEBP","TIFF","ZIP","RAR","7Z","MP4","AVI","WMV" + ,"MOV","JSON","MD","PSD","EXE"}; + + public FilesUploadController(FtpService ftpService) { + this.ftpService = ftpService; + } + + @PostMapping("/uploads") + public Map uploads(StandardMultipartHttpServletRequest request) { + Map res = new HashMap<>(); + res.put("result", "success"); + String industryCategory = request.getHeader("industry-category"); + IndustryCategoryForFile ic = IndustryCategoryForFile.getIndustryCategoryByCode(industryCategory); + if (ic == null) { + res.put("result", "failed"); + res.put("code", 1); + res.put("success", false); + res.put("msg", "上传失败,请求头参数industry-category为空或无效"); + return res; + } + MultiValueMap multiFileMap = request.getMultiFileMap(); + Map nameMap = new HashMap<>(); + for (String key : multiFileMap.keySet()) { + List multipartFiles = multiFileMap.get(key); + for (MultipartFile file : multipartFiles) { + try { + String fileName = file.getOriginalFilename(); + String[] fileSplit = fileName.split("\\."); + if(fileSplit.length==2 && + Arrays.stream(allowFileType).filter(o -> o.equals(fileSplit[1].toUpperCase())).count()==1) { + nameMap.put("fileName", fileName); + String savePathName = ftpService.uploadTempFile(ic, fileName, file.getInputStream()); + if (!"".equals(savePathName)) { // 上传成功 + nameMap.put("savePathName", savePathName); + nameMap.put("url", ftpService.getFileUrl(savePathName)); + nameMap.put("downloadUrl", ftpService.getFileDownloadUrl(savePathName)); + res.put("result", "success"); + res.put("code", 0); + res.put("success", true); + res.put("msg", "上传成功"); + } else { + nameMap.put("savePathName", ""); + nameMap.put("url", ""); + res.put("result", "failed"); + res.put("code", 1); + res.put("success", false); + res.put("msg", "上传失败"); + } + } else { + res.put("success", false); + res.put("result", "failed"); + res.put("code", 1); + res.put("msg", fileName + "为不允许的文件类型,上传失败"); + break; + } + } catch (Exception e) { + e.printStackTrace(); + nameMap.put("savePathName", ""); + res.put("result", "failed"); + res.put("code", 1); + res.put("success", false); + res.put("msg", "上传异常"); + } + break; + } + } + res.put("data", nameMap); + return res; + } + + @PostMapping("/multipleUpload") + public Map multipleUpload(@RequestParam("files") MultipartFile[] multipartFiles, + @RequestHeader(value = "industry-category", required = false) String industryCategory) { + Map res = new HashMap<>(); + Map> nameMap = new HashMap<>(); + IndustryCategoryForFile ic = IndustryCategoryForFile.getIndustryCategoryByCode(industryCategory); + if (ic == null) { + res.put("code", 1); + res.put("result", "failed"); + res.put("success", false); + res.put("msg", "上传失败,请求头参数industry-category为空或无效"); + return res; + } + List fileNameList = new ArrayList<>(); + List savePathNameList = new ArrayList<>(); + List fileAccessPaths = new ArrayList<>(); + if (ArrayUtils.isNotEmpty(multipartFiles)) { + for (MultipartFile file : multipartFiles) { + try { + String fileName = file.getOriginalFilename(); + String savePathName = ftpService.uploadTempFile(ic, fileName, file.getInputStream()); + String fileAccessPath = ftpService.getFileUrl(savePathName); + if (StringUtils.isNotBlank(savePathName)) { // 上传成功 + fileNameList.add(fileName); + savePathNameList.add(savePathName); + fileAccessPaths.add(fileAccessPath); + res.put("result", "success"); + res.put("code", 0); + res.put("success", true); + res.put("msg", "上传成功"); + } else { + res.put("result", "failed"); + res.put("code", 1); + res.put("success", false); + res.put("msg", "文件:" + fileName + ",上传失败!"); + } + } catch (Exception e) { + e.printStackTrace(); + res.put("result", "failed"); + res.put("code", 1); + res.put("success", false); + res.put("msg", "上传异常"); + } + } + nameMap.put("fileNames", fileNameList); + nameMap.put("savePathNames", savePathNameList); + nameMap.put("fileAccessPaths", fileAccessPaths); + } + res.put("data", nameMap); + return res; + } + + @GetMapping("/downloadAsBase64/{savePathName}") + public String downloadAsBase64(@PathVariable String savePathName) { + String fileSeverUrl = ftpService.getFileUrl(savePathName); + return Base64Util.imageBase64FromUrl(fileSeverUrl); + } + + /** + * 多文件上传base64版本 + */ + @PostMapping("/multiple-upload/base64") + public Result> multipleUploadFromBase64(@NotNull(message = "上传文件不能为空")@RequestBody List uploadFiles,@NotNull(message = "请求头:industry-category行业类别代码不能为空") @RequestHeader(value = "industry-category", required = false) String industryCategory) { + IndustryCategoryForFile ic = IndustryCategoryForFile.getIndustryCategoryByCode(industryCategory); + if (ic == null) { + return Result.of(1, false, "上传失败,请求头参数industry-category为空或无效", null); + } + return Optional.ofNullable(ftpService.uploadFilesWithBase64(uploadFiles, ic)) + .map(ufs->Result.of(0, true, "上传成功", ufs, null)) + .orElseGet(() -> Result.of(1, false, "上传失败,上传文件为空", null)); + } + + /** + * 删除文件 + * @param names 文件key列表 + */ + @DeleteMapping("/deleteFiles") + public void deleteFiles(@RequestBody List names) { + names.forEach(ftpService::deletePathFile); + } + + /** + * 删除文件post 方式 + * @param names 文件key列表 + */ + @PostMapping("/deleteFiles") + public void deleteBypostFiles(@RequestBody List names) { + names.forEach(ftpService::deletePathFile); + } + +} diff --git a/server/src/main/java/com/aisino/iles/common/controller/ImgTransformController.java b/server/src/main/java/com/aisino/iles/common/controller/ImgTransformController.java new file mode 100644 index 0000000..cac474c --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/controller/ImgTransformController.java @@ -0,0 +1,45 @@ +package com.aisino.iles.common.controller; + +import com.aisino.iles.common.util.Constants; +import com.aisino.iles.common.model.Ok; +import com.aisino.iles.common.model.Result; +import com.aisino.iles.common.service.FtpService; +import com.aisino.iles.common.util.Base64Util; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import lombok.extern.slf4j.Slf4j; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; + +@RestController +@RequestMapping(Constants.NO_AUTH_API_PREFIX) +@Slf4j +public class ImgTransformController { + + private static final String OSS_IMG_URI = "/hycsmobile/bfile/hycszaglxx*/**"; + private static final Pattern imgRegex = Pattern.compile("/hycsmobile/bfile/hycszaglxx\\d*/(.*)"); + + @Resource + private FtpService ftpService; + + /** + * 图片转base64 + * + * @return JSON格式图片信息 + */ + @GetMapping(OSS_IMG_URI) + public Result imgTransformBase64(HttpServletRequest request) { + Matcher imgMatcher = imgRegex.matcher(request.getRequestURI()); + imgMatcher.find(); + log.debug("savePathName = {}", imgMatcher.group(1)); + String savePathName = imgMatcher.group(1); + String base64 = Base64Util.FileBase64FromUrl(ftpService.getFileUrl(savePathName, true)); + return Ok.of(base64); + } +} diff --git a/server/src/main/java/com/aisino/iles/common/iface/ExportExcelHelper.java b/server/src/main/java/com/aisino/iles/common/iface/ExportExcelHelper.java new file mode 100644 index 0000000..db03c6e --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/iface/ExportExcelHelper.java @@ -0,0 +1,223 @@ +package com.aisino.iles.common.iface; + +import com.aisino.iles.common.model.ExcelSheetStruct; +import com.aisino.iles.common.model.ExportExcel; +import com.aisino.iles.common.util.ExportExcelUtil; +import com.aisino.iles.common.util.InvokeUtility; +import jxl.format.Alignment; +import jxl.format.Border; +import jxl.format.BorderLineStyle; +import jxl.format.VerticalAlignment; +import jxl.write.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; + +import java.io.IOException; +import java.util.*; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + * excel表格导出工具 + * @param 需要导出的数据结构 + */ +@FunctionalInterface +public interface ExportExcelHelper extends Logger { + /** + * 数据分堆提取方法 + * @param page 页数 + * @param pageSize 数据限制数 + * @param total 总数据数 + * @return 已分页的数据 + */ + Page exportData(Integer page, Integer pageSize, Integer total); + + /** + * 默认数据提取方法生成器(简化模式) + * @return 一个由已分堆数据和剩余待分堆数据方法 + */ + default Map, Supplier> exportDataGenerator() { + int page = 1, pageSize = 2000; + return this.exportDataGenerator(page, pageSize, null); + } + + /** + * 实际分堆数据提取方法生成器 + * @param page 页数 + * @param pageSize 数据限制数 + * @param totalNumber 总数据数 + * @return 一个由已分堆数据和剩余待分堆数据方法 + */ + default Map, Supplier> exportDataGenerator(Integer page, Integer pageSize, Integer totalNumber) { + Page pager = exportData(page, pageSize, totalNumber); + Map, Supplier> resultMap = new HashMap<>(); + if (pager.isEmpty()) { + return resultMap; + } else if (pager.hasNext()) { + resultMap.put(pager.getContent(), () -> exportDataGenerator(page + 1, pageSize, ((int) (pager.getTotalElements())))); + } else { + resultMap.put(pager.getContent(), null); + } + return resultMap; + + } + + /** + * 写入数据到工作区 + * @param book 工作区 + * @param labels 表格显示列,和表格字段一一对应 + * @param props 表格字段,和表格显示列一一对应 + * @param fileName 表头 + * @throws WriteException 表格写入异常 + * @throws IOException IO异常 + */ + default void writeToBook(WritableWorkbook book, List labels, List props, String fileName) throws WriteException, IOException { + Integer rowLimit = 30000; //单个表单无法超过65535行 + //内容 + WritableFont wf2 = new WritableFont(WritableFont.createFont("宋体"), 10); + WritableCellFormat wcf2 = new WritableCellFormat(wf2); + wcf2.setAlignment(Alignment.CENTRE); + wcf2.setVerticalAlignment(VerticalAlignment.CENTRE); + + Map, Supplier> dataGeneratorMap = this.exportDataGenerator(); + writeDataMapToBook(dataGeneratorMap, 0, 0, rowLimit, exportExcel -> { + List data = exportExcel.getData(); +// // 尝试数据加密 +// data = data.stream().peek(SM4Util::objectSm4Encrypt).collect(Collectors.toList()); 暂时去掉加密 + int dataIdx = exportExcel.getDataIdx(), sheetIdx = exportExcel.getSheetIdx(), limit = rowLimit; + WritableSheet sheet = null; + try { + sheet = book.getSheet(sheetIdx - 1); + } catch (IndexOutOfBoundsException ignored) { + } + try { + if (dataIdx + 1 > limit || dataIdx == 0) { + dataIdx = 0; + ExcelSheetStruct sheetStruct = createSheetAndInit(book, labels, props, fileName, sheetIdx, dataIdx); + sheet = sheetStruct.getSheet(); + sheetIdx = sheetStruct.getSheetIdx(); + dataIdx = sheetStruct.getDataIdx(); + } + dataIdx = writeDataToSheet(sheet, wcf2, labels, props, data, dataIdx); + } catch (WriteException e) { + logger().error(e.getMessage(), e); + } + + return ExportExcel.builder() + .sheetIdx(sheetIdx) + .dataIdx(dataIdx) + .build(); + }); + book.write(); + book.close(); + } + + /** + * 创建一个新的工作表并且设置好表头以及列信息 + * @param book 工作区 + * @param labels 表格显示列信息同 {@link ExportExcelHelper#writeToBook(WritableWorkbook, List, List, String)} + * @param props 表格字段信息同 {@link ExportExcelHelper#writeToBook(WritableWorkbook, List, List, String)} + * @param fileName 表头信息同 {@link ExportExcelHelper#writeToBook(WritableWorkbook, List, List, String)} + * @param sheetIdx 当前工作表索引 + * @param dataIdx 当前工作表数据行索引 + * @return 已经初始化的工作表 + * @throws WriteException 写入异常 + */ + default ExcelSheetStruct createSheetAndInit(WritableWorkbook book, List labels, List props, String fileName, Integer sheetIdx, Integer dataIdx) throws WriteException { + int dIdx = dataIdx; + //表头 + WritableFont wf = new WritableFont(WritableFont.createFont("宋体"), 15, WritableFont.BOLD); + WritableCellFormat wcf = new WritableCellFormat(wf); + wcf.setAlignment(Alignment.CENTRE); + wcf.setBorder(Border.ALL, BorderLineStyle.THIN); + //标题 + WritableFont wf1 = new WritableFont(WritableFont.createFont("宋体"), 10, WritableFont.BOLD); + WritableCellFormat wcf1 = new WritableCellFormat(wf1); + wcf1.setAlignment(Alignment.CENTRE); + wcf1.setVerticalAlignment(VerticalAlignment.CENTRE); + + + WritableSheet sheet = book.createSheet("第" + (sheetIdx + 1) + "页", sheetIdx); + + sheet.mergeCells(0, 0, labels.size() - 1, 0); + //表标题(第一行)(列号,行号,值,样式) + Label label = new Label(0, 0, fileName, wcf); + sheet.addCell(label); + dIdx += 1; + //表头(第二行)(列号,行号,值,样式) + IntStream.range(0, labels.size()).forEach(n -> { + try { + sheet.addCell(new Label(n, 1, labels.get(n), wcf1)); + } catch (WriteException e) { + logger().error(e.getMessage(), e); + } + }); + dIdx += 1; + + return ExcelSheetStruct.builder().dataIdx(dIdx).sheet(sheet).sheetIdx(sheetIdx + 1).build(); + } + + /** + * 把生成器数据集写入工作区 + * @param dataMap 生成器数据集 + * @param dataIdx 当前工作表数据索引 + * @param sheetIdx 当前工作表索引 + * @param limit 工作表最大数据限制 + * @param dataConsumer 把数据写入工作表的参数 {@link ExportExcel} 返回 {@link ExportExcel}的方法 + */ + default void writeDataMapToBook(Map, Supplier> dataMap, Integer dataIdx, Integer sheetIdx, Integer limit, Function, ExportExcel> dataConsumer) { + dataMap.entrySet().stream().findFirst().ifPresent(e -> { + List data = e.getKey(); + ExportExcel result = null; + int processNumber = 0, dataIndex = dataIdx, sheetIndex = sheetIdx; + while (processNumber < data.size()) { + List slice = data.stream().skip(processNumber).limit(limit).collect(Collectors.toList()); + result = dataConsumer.apply(ExportExcel.builder() + .data(slice) + .dataIdx(dataIndex) + .sheetIdx(sheetIndex) + .build()); + dataIndex = result.getDataIdx(); + sheetIndex = result.getSheetIdx(); + processNumber += slice.size(); + } +// 数据呈现为链式,可通过自我递归调用,完成数据处理 + Supplier, Supplier>> next = (Supplier, Supplier>>) e.getValue(); + if (next != null) { + assert result != null; + writeDataMapToBook(next.get(), dataIndex, sheetIndex, limit, dataConsumer); + } + }); + } + + /** + * 写入数据到工作表 + * @param sheet 工作表 + * @param wcf 数据格式 + * @param labels {@link ExportExcelHelper#writeToBook(WritableWorkbook, List, List, String)} + * @param props {@link ExportExcelHelper#writeToBook(WritableWorkbook, List, List, String)} + * @param data 数据信息 + * @param dataIdx 当前工作表数据索引 + * @return 数据写入后的数据索引 + * @throws WriteException 写入异常 + */ + default int writeDataToSheet(WritableSheet sheet, WritableCellFormat wcf, List labels, List props, List data, Integer dataIdx) throws WriteException { + //内容(列号,行号,值,样式) + for (int i = 0; i < data.size(); i++) { + Object obj = data.get(i); + for (int n = 0; n < labels.size(); n++) { + String value = InvokeUtility.getFieldValueByName(props.get(n), obj) == null ? "" : InvokeUtility.getFieldValueByName(props.get(n), obj).toString(); + sheet.addCell(new Label(n, i + dataIdx, value, wcf)); + } + } + + // 设置列宽度 + IntStream.range(0, labels.size()).mapToObj(colIdx -> new Integer[]{colIdx, Arrays.stream(sheet.getColumn(colIdx)).map(c -> ExportExcelUtil.length(c.getContents())) + .max(Comparator.comparingInt(a -> a)).orElse(0)}) + .forEach(c -> sheet.setColumnView(c[0], c[1] + 2)); + + return data.size()+ dataIdx; + } +} diff --git a/server/src/main/java/com/aisino/iles/common/iface/Logger.java b/server/src/main/java/com/aisino/iles/common/iface/Logger.java new file mode 100644 index 0000000..7dea1da --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/iface/Logger.java @@ -0,0 +1,14 @@ +package com.aisino.iles.common.iface; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public interface Logger { + /** + * 默认日志 + * @return 日志对象 + */ + default Log logger() { + return LogFactory.getLog(this.getClass()); + } +} diff --git a/server/src/main/java/com/aisino/iles/common/model/AwsConfig.java b/server/src/main/java/com/aisino/iles/common/model/AwsConfig.java new file mode 100644 index 0000000..db6a69a --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/model/AwsConfig.java @@ -0,0 +1,26 @@ +package com.aisino.iles.common.model; + +import lombok.Data; +import lombok.ToString; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ToString +@ConfigurationProperties(prefix = "custom.aws") +@Data +public class AwsConfig { + private String accessKey; + private String secretKey; + private String bucket; + private String endpoint; + private String filePath; + /** + * 用于大文件本地临时存放(多节点运行时需要配置为共享目录) + */ + private String tempPath = "files"; + /** + * 修改http访问时的url中的endpoint部分 + */ + private String alterHttp; +} diff --git a/server/src/main/java/com/aisino/iles/common/model/CreatedWire.java b/server/src/main/java/com/aisino/iles/common/model/CreatedWire.java new file mode 100644 index 0000000..daea057 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/model/CreatedWire.java @@ -0,0 +1,5 @@ +package com.aisino.iles.common.model; + +public interface CreatedWire { + void wiring(); +} diff --git a/server/src/main/java/com/aisino/iles/common/model/ExcelSheetStruct.java b/server/src/main/java/com/aisino/iles/common/model/ExcelSheetStruct.java new file mode 100644 index 0000000..69fba68 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/model/ExcelSheetStruct.java @@ -0,0 +1,13 @@ +package com.aisino.iles.common.model; + +import jxl.write.WritableSheet; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class ExcelSheetStruct { + private Integer dataIdx; + private Integer sheetIdx; + private WritableSheet sheet; +} diff --git a/server/src/main/java/com/aisino/iles/common/model/ExportExcel.java b/server/src/main/java/com/aisino/iles/common/model/ExportExcel.java new file mode 100644 index 0000000..d3dec44 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/model/ExportExcel.java @@ -0,0 +1,27 @@ +package com.aisino.iles.common.model; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +/** + * 导出excel的结构 + * @author firefoxmmx2 + */ +@Data +@Builder +public class ExportExcel { + /** + * 需要导出的数据 + */ + private List data; + /** + * 当前工作表里的数据索引 + */ + private Integer dataIdx; + /** + * 当前工作表索引 + */ + private Integer sheetIdx; +} diff --git a/server/src/main/java/com/aisino/iles/common/model/ExportExcelParam.java b/server/src/main/java/com/aisino/iles/common/model/ExportExcelParam.java new file mode 100644 index 0000000..bbc0e60 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/model/ExportExcelParam.java @@ -0,0 +1,29 @@ +package com.aisino.iles.common.model; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Builder +@Data +public class ExportExcelParam { + private List labels; + private List props; + private String fileName; + private Map params; + private T obj; + + public ExportExcelParam() { + + } + + public ExportExcelParam(List labels, List props, String fileName, Map params, T obj) { + this.labels = labels; + this.props = props; + this.fileName = fileName; + this.params = params; + this.obj = obj; + } +} diff --git a/server/src/main/java/com/aisino/iles/common/model/FaceValid.java b/server/src/main/java/com/aisino/iles/common/model/FaceValid.java new file mode 100644 index 0000000..326444f --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/model/FaceValid.java @@ -0,0 +1,13 @@ +package com.aisino.iles.common.model; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; + +@Data +@Accessors(chain = true) +public class FaceValid implements Serializable { + private Float score; + private Integer threshold; +} diff --git a/server/src/main/java/com/aisino/iles/common/model/Fail.java b/server/src/main/java/com/aisino/iles/common/model/Fail.java new file mode 100644 index 0000000..54f0e9f --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/model/Fail.java @@ -0,0 +1,35 @@ +package com.aisino.iles.common.model; + +import com.aisino.iles.common.util.Constants; + +public class Fail extends Result { + public Fail(String message, int code) { + super(); + this.success = false; + this.msg = message; + this.code = code; + } + + public Fail(String message) { + super(); + this.success = false; + this.msg = message; + this.code = Constants.Exceptions.default_error; + } + + public Fail() { + super(); + this.success = false; + this.code = Constants.Exceptions.default_error; + this.msg = "错误"; + } + public static Fail of() { + return new Fail(); + } + public static Fail of(String message) { + return new Fail(message); + } + public static Fail of(String message, int code) { + return new Fail(message, code); + } +} diff --git a/server/src/main/java/com/aisino/iles/common/model/ForJsonView.java b/server/src/main/java/com/aisino/iles/common/model/ForJsonView.java new file mode 100644 index 0000000..3231aca --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/model/ForJsonView.java @@ -0,0 +1,10 @@ +package com.aisino.iles.common.model; + +/** + * 提供给JsonView工具使用的试图 + */ +public interface ForJsonView { + interface View {} + interface PageView extends View {} + +} diff --git a/server/src/main/java/com/aisino/iles/common/model/Message.java b/server/src/main/java/com/aisino/iles/common/model/Message.java new file mode 100644 index 0000000..a024aa4 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/model/Message.java @@ -0,0 +1,19 @@ +package com.aisino.iles.common.model; + +import lombok.Data; + +@Data +public class Message { + /** + * 消息键 + */ + private Object key; + /** + * 消息值 + */ + private Object value; + /** + * 消息主题(支持多个) + */ + private String[] topics; +} diff --git a/server/src/main/java/com/aisino/iles/common/model/MessageProperties.java b/server/src/main/java/com/aisino/iles/common/model/MessageProperties.java new file mode 100644 index 0000000..402cf6b --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/model/MessageProperties.java @@ -0,0 +1,26 @@ +package com.aisino.iles.common.model; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties("messages") +@Data +public class MessageProperties { + private String loginUrl; + private String sendUrl; + private String username; + private String password; + private Topics topic; + + + @Data + public static class Topics { + private String earlyWarning; + private String alarmInfo; + private String carInfo; + private String hotelAdditionalProcess; + private String onlineHouseInfo; + } +} diff --git a/server/src/main/java/com/aisino/iles/common/model/OcrValid.java b/server/src/main/java/com/aisino/iles/common/model/OcrValid.java new file mode 100644 index 0000000..6a3a1fc --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/model/OcrValid.java @@ -0,0 +1,22 @@ +package com.aisino.iles.common.model; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +@Data +@Accessors(chain = true) +public class OcrValid implements Serializable { + private Integer cardType; + private List items = new ArrayList(); + + @Data + @Accessors(chain = true) + public static final class CardInfo implements Serializable { + private String desc; + private String content; + } +} diff --git a/server/src/main/java/com/aisino/iles/common/model/OcrValidResult.java b/server/src/main/java/com/aisino/iles/common/model/OcrValidResult.java new file mode 100644 index 0000000..5e87bcc --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/model/OcrValidResult.java @@ -0,0 +1,87 @@ +package com.aisino.iles.common.model; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; + +@Data +@Accessors(chain = true) +public class OcrValidResult implements Serializable { + /** + * 所选择的比对证件类型 + */ + private String cardType; + /** + * 证件号码 + */ + private String zjhm; + /** + * 护照号码 + */ + private String hzhm; + /** + * 头像 + */ + private String tx; + /** + * 性别 + */ + private String xb; + /** + * 性别代码 + */ + private String xbdm; + /** + * 民族 + */ + private String mz; + /** + * 民族代码 + */ + private String mzdm; + /** + * 国籍代码 + */ + private String gjdm; + /** + * 户籍地址 + */ + private String hjdz; + /** + * 姓名 + */ + private String xm; + /** + * 出生日期 + */ + private String csrq; + /** + * 签发国代码 + */ + private String qfgdm; + /** + * 英文姓 + */ + private String ywx; + /** + * 英文名 + */ + private String ywm; + /** + * 英文姓名 + */ + private String ywxm; + /** + * 签发地点 + */ + private String qfdd; + /** + * 签发机关 + */ + private String qfjg; + /** + * 行政区划 + */ + private String xzqh; +} diff --git a/server/src/main/java/com/aisino/iles/common/model/Ok.java b/server/src/main/java/com/aisino/iles/common/model/Ok.java new file mode 100644 index 0000000..a52743b --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/model/Ok.java @@ -0,0 +1,43 @@ +package com.aisino.iles.common.model; + +import java.util.Collection; + +public class Ok extends Result { + public Ok() { + this.code = 0; + this.success = true; + } + public Ok(T data) { + super(data); + this.code = 0; + this.success = true; + } + + public Ok(String msg, int code){ + super(code, true, msg, null); + this.code = code; + this.msg = msg; + this.success = true; + } + + public Ok(T data, String msg){ + this(data); + this.msg=msg; + } + + public static Ok of() { + return new Ok<>(); + } + + public static Ok of(T data) { + return new Ok<>(data); + } + + public static Ok of(T data, String msg) { + return new Ok<>(data, msg); + } + + public static Ok of(String msg, int code) { + return new Ok<>(msg, code); + } +} diff --git a/server/src/main/java/com/aisino/iles/common/model/PageResult.java b/server/src/main/java/com/aisino/iles/common/model/PageResult.java new file mode 100644 index 0000000..aa48265 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/model/PageResult.java @@ -0,0 +1,79 @@ +package com.aisino.iles.common.model; + +import com.aisino.iles.common.util.Constants; +import com.fasterxml.jackson.annotation.JsonView; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.data.domain.Page; + +import java.util.Collection; + +/** + * 分页结果集 + * + * @param 数据类型 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class PageResult extends Result> { + /** + * 总记录数 + */ + @JsonView(ForJsonView.PageView.class) + Long total; + /** + * 当前页 + */ + @JsonView(ForJsonView.PageView.class) + Integer page; + /** + * 每页记录数 + */ + @JsonView(ForJsonView.PageView.class) + Integer limit; + /** + * 总页数 + */ + @JsonView(ForJsonView.PageView.class) + Integer totalPages; + /** + * 数量关系 + */ + @JsonView(ForJsonView.PageView.class) + String relation; + + public PageResult(Page page) { + super(); + total = page.getTotalElements(); + totalPages = page.getTotalPages(); + limit = page.getSize(); + this.page = page.getNumber(); + + this.code = 0; + this.data = page.getContent(); + this.success = true; + if (total < Constants.GLOBAL_QUERY_LIMIT || total > Constants.GLOBAL_QUERY_LIMIT) { + // 等于 + this.relation = "eq"; + } else { + // 大于或者等于 + this.relation = "gte"; + } + } + + public PageResult(Long totalrow, Integer totalpages, Integer limitrow, Integer curpage, Collection datares) { + super(); + total = totalrow; + totalPages = totalpages; + limit = limitrow; + this.page = curpage; + + this.code = 0; + this.data = datares; + this.success = true; + } + + public static PageResult of(Page page) { + return new PageResult<>(page); + } +} diff --git a/server/src/main/java/com/aisino/iles/common/model/PersonnelForgotIdentifyDto.java b/server/src/main/java/com/aisino/iles/common/model/PersonnelForgotIdentifyDto.java new file mode 100644 index 0000000..8a92061 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/model/PersonnelForgotIdentifyDto.java @@ -0,0 +1,45 @@ +package com.aisino.iles.common.model; + +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * 文员忘证数据 + * @author hx + * @since 2024-01-30 + */ +@Data +public class PersonnelForgotIdentifyDto { + /** + * 人员唯一标识 + */ + @NotBlank(message = "人员唯一标识不能为空") + private String personId; + /** + * 人员姓名 + */ + @NotBlank(message = "人员姓名不能为空") + @Size(message = "人员姓名长度不能超过20",max = 20) + private String name; + /** + * 身份证号码 + */ + @NotBlank(message = "身份证号码不能为空") + @Size(message = "身份证号码不能超过18",max = 18) + private String certNumber; + /** + * 现场照片base64 + */ + @NotBlank(message = "现场照片不能为空") + private String photoStr; + + /** + * mqtt客户端id + */ + @NotBlank(message = "mqtt客户端id不能为空") + @Size(message = "mqtt客户端id不能超过20",max = 20) + private String mqttClientId; + +} diff --git a/server/src/main/java/com/aisino/iles/common/model/PkgDto.java b/server/src/main/java/com/aisino/iles/common/model/PkgDto.java new file mode 100644 index 0000000..490ed50 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/model/PkgDto.java @@ -0,0 +1,34 @@ +package com.aisino.iles.common.model; + +import com.aisino.iles.common.util.BeanUtils; + +import java.io.Serializable; +import java.lang.reflect.ParameterizedType; +import java.util.Arrays; + +public interface PkgDto { + + Serializable id(); + + T toEntity(); + + @SuppressWarnings("unchecked") + default T simpleData() { + return Arrays.stream(this.getClass().getGenericInterfaces()) + .filter(type -> type.getTypeName().startsWith(PkgDto.class.getName())) + .map(i -> (ParameterizedType) i) + .map(p -> (Class) p.getActualTypeArguments()[0]) + .findFirst() + .map(tp -> { + try { + return tp.newInstance(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + }).map(t -> { + BeanUtils.copyNoNullProperties(this, t); + return t; + }).orElseThrow(() -> new RuntimeException("类型转换错误:" + id())); + } +} diff --git a/server/src/main/java/com/aisino/iles/common/model/Result.java b/server/src/main/java/com/aisino/iles/common/model/Result.java new file mode 100644 index 0000000..fc63938 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/model/Result.java @@ -0,0 +1,53 @@ +package com.aisino.iles.common.model; + +import com.fasterxml.jackson.annotation.JsonView; +import lombok.Data; + +import java.util.ArrayList; +import java.util.Collection; + +@Data +public class Result { + @JsonView(ForJsonView.View.class) + Integer code; + @JsonView(ForJsonView.View.class) + Boolean success; + @JsonView(ForJsonView.View.class) + String msg; + @JsonView(ForJsonView.View.class) + T data; + @JsonView(ForJsonView.View.class) + Exception exception; + + public Result(T data) { + this.data = data; + } + + public Result() { + } + + public Result(Integer code, Boolean success, String msg, T data, Exception exception) { + this(code, success, msg, exception); + this.data = data; + } + + public Result(Integer code, Boolean success, String msg, Exception exception) { + this.code = code; + this.success = success; + this.msg = msg; + this.exception = exception; + } + + public static Result of(T data) { + return new Result<>(data); + } + + public static Result of(Integer code, Boolean success, String msg, T data, Exception exception) { + return new Result<>(code, success, msg, data, exception); + } + + public static Result of(Integer code, Boolean success, String msg, Exception exception) { + return new Result<>(code, success, msg, exception); + } + +} diff --git a/server/src/main/java/com/aisino/iles/common/model/SmsEnvConfig.java b/server/src/main/java/com/aisino/iles/common/model/SmsEnvConfig.java new file mode 100644 index 0000000..5f7756c --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/model/SmsEnvConfig.java @@ -0,0 +1,34 @@ +package com.aisino.iles.common.model; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; + +@Configuration +public class SmsEnvConfig { + + @ConfigurationProperties(prefix = "sms.outer") + @Bean(name = "outerSms") + public SmsData outerSmsData(){ + return new SmsData(); + } + + @ConfigurationProperties(prefix = "sms.inner") + @Bean(name = "innerSms") + public SmsData innerSmsData(){ + return new SmsData(); + } + + @Component + @Data + public static class SmsData { + private String userCode; //用户编码 + private String interfaceCode; //接口编码 + private String dataSecret; //数据公钥 + private String interfaceParamsSecret; //接口参数公钥 + private String url;//接口路径 + private String ftpLocalPath; // ftp服务的本地路径 + } +} diff --git a/server/src/main/java/com/aisino/iles/common/model/UploadFile.java b/server/src/main/java/com/aisino/iles/common/model/UploadFile.java new file mode 100644 index 0000000..00f3ed2 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/model/UploadFile.java @@ -0,0 +1,35 @@ +package com.aisino.iles.common.model; + +import java.io.InputStream; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 上传文件封装类型 + * + * @author hx + * @since 2022-03-16 + */ +@Data +@EqualsAndHashCode(of = "fileName") +public class UploadFile { + /** 文件名 */ + @NotEmpty(message="文件名不能为空") + @NotNull(message = "文件名不能为空") + private String fileName; + /** 文件保存 */ + private String path; + /** 文件访问路径 */ + private String accessUrl; + /** 文件后缀名 */ + private String ext; + /** 文件内容 base64 */ + private String contentStr; + /** 文件内容 流 */ + private InputStream content; + +} diff --git a/server/src/main/java/com/aisino/iles/common/model/enums/ContentType.java b/server/src/main/java/com/aisino/iles/common/model/enums/ContentType.java new file mode 100644 index 0000000..5294a24 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/model/enums/ContentType.java @@ -0,0 +1,66 @@ +package com.aisino.iles.common.model.enums; + +import java.util.Arrays; + +/** + * 文件类型 + */ +public enum ContentType { + wmv("video/x-ms-wmv", ".wmv"), + mpeg("video/mpg", ".mpeg"), + mpg("video/mpg", ".mpg"), + avi("video/avi", ".avi"), + mp4("video/mpeg4", ".mp4"), + pptx("application/vnd.openxmlformats-officedocument.presentationml.presentation", ".pptx"), + ppt("application/vnd.ms-powerpoint", ".ppt"), + xlsx("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ".xlsx"), + doc("application/msword", ".doc"), + docx("application/vnd.openxmlformats-officedocument.wordprocessingml.document", ".docx"), + gzip("application/gzip", ".gzip"), + zip("application/zip", ".zip"), + pdf("application/pdf", ".pdf"), + xls("application/vnd.ms-excel", ".xls"), + png("image/png", ".png"), + jpg("image/jpeg", ".jpg"), + jpe("image/jpeg", ".jpe"), + ico("image/x-icon", ".ico"), + jpeg("image/jpeg", ".jpeg"); + + private String contentTypeName; + private String fileExtendName; + + ContentType(String contentTypeName, String fileExtendName) { + this.contentTypeName = contentTypeName; + this.fileExtendName = fileExtendName; + } + + /** + * 根据扩展名获取ContentType + * + * @param fileExtendName + * @return + */ + public static String getContentTypeNameByFileExtendName(String fileExtendName) { + return Arrays.stream(ContentType.values()) + .filter(c -> c.fileExtendName.equalsIgnoreCase(fileExtendName)) + .map(ContentType::getContentTypeName) + .findFirst() + .orElse(""); + } + + public String getContentTypeName() { + return contentTypeName; + } + + public void setContentTypeName(String contentTypeName) { + this.contentTypeName = contentTypeName; + } + + public String getFileExtendName() { + return fileExtendName; + } + + public void setFileExtendName(String fileExtendName) { + this.fileExtendName = fileExtendName; + } +} diff --git a/server/src/main/java/com/aisino/iles/common/model/enums/IndustryCategoryForFile.java b/server/src/main/java/com/aisino/iles/common/model/enums/IndustryCategoryForFile.java new file mode 100644 index 0000000..6e3084b --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/model/enums/IndustryCategoryForFile.java @@ -0,0 +1,62 @@ +package com.aisino.iles.common.model.enums; + +/** + * 行业类别 + */ +public enum IndustryCategoryForFile { + pub("公共模块", "PUB"), + portal("门户", "PORTAL"), + hotel("旅馆行业", "A"), + wyf("网约房行业", "A06"), + seal("印章业", "B"), + vehiclerecycling("报废机动车", "C01"), + mechanicalrepair("机修业", "C02"), + printing("印刷业", "D"), + usedcar("二手车交易业", "E01"), + usedgoods("旧货交易业", "E02"), + pawn("典当行业", "E03"), + scrapmetal("废旧金属收购业", "E04"), + consignment("委托寄卖业", "E07"), + carrental("汽车租赁业", "F02"), + entertainment("营业性娱乐场所", "J"), + additivemanufacturing("增材制造业", "M"), + publictransittransport("公共交通运输行业", "P"), + security("保安业", "T"), + unlock("开锁业", "Y01"), + bigfile("大文件", "BF"); + + private String name; + private String code; + + IndustryCategoryForFile(String name, String code) { + this.name = name; + this.code = code; + } + + public static IndustryCategoryForFile getIndustryCategoryByCode(String code) { + if (code != null) { + for (IndustryCategoryForFile ic : IndustryCategoryForFile.values()) { + if (ic.getCode().equals(code)) { + return ic; + } + } + } + return null; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } +} diff --git a/server/src/main/java/com/aisino/iles/common/model/enums/WhetherFlag.java b/server/src/main/java/com/aisino/iles/common/model/enums/WhetherFlag.java new file mode 100644 index 0000000..18d482c --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/model/enums/WhetherFlag.java @@ -0,0 +1,23 @@ +package com.aisino.iles.common.model.enums; + +/** + * 通用删除标志 + * + * @author huxin + * @since 2020-07-03 + */ +public enum WhetherFlag { + yes("1"), // 是 + no("0"); // 否 + private String value; + + WhetherFlag(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + +} diff --git a/server/src/main/java/com/aisino/iles/common/repository/PagingAndSortingSpecificationRepository.java b/server/src/main/java/com/aisino/iles/common/repository/PagingAndSortingSpecificationRepository.java new file mode 100644 index 0000000..8b7320a --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/repository/PagingAndSortingSpecificationRepository.java @@ -0,0 +1,167 @@ +package com.aisino.iles.common.repository; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.repository.NoRepositoryBean; +import org.springframework.lang.Nullable; + +import java.util.List; +import java.util.Map; + +/** + * 公共自定义分页查询组件 + *

+ * 这个组件在查询上面做了优化分页统计,针对数据数量比较的查询来说, + * 每一次统计总条数的开销都是巨大的。组件只为第一页数据查询的时候提供统计总条数服务, + * 之后的查询不在去统计。统计和查询使用了异步的方式处理,这样可以减少一些查询等待时间。 + * + * @param 领域实例类型 + * @author huxin + * @since 2019-10-29 + */ +@NoRepositoryBean +public interface PagingAndSortingSpecificationRepository { + /** + * 分页查询 + * + * @param specification 动态查询条件 + * @param pager 分页器 + * @param total 总数据数 + * @param returnClass 返回类型 + * @param 返回类型站位符 + * @return 分页数据 + */ + Page findAll(@Nullable Specification specification, Pageable pager, long total, Class returnClass); + + /** + * 分页查询,多提供一个设置查询提示的功能 + * + * @param specification 动态查询条件 + * @param pager 分页器 + * @param total 总数据数 + * @param returnClass 返回类型 + * @param queryHints 查询提示 + * @param 返回类型占位符 + * @return 分页数据 + */ + Page findAll(@Nullable Specification specification, Pageable pager, long total, Class returnClass, Map queryHints); + + /** + * 列表查询 + * {@link PagingAndSortingSpecificationRepository#findAll } + * + * @return 返回列表数据 + */ + List findAll(@Nullable Specification specification, Class returnClass); + + /** + * {@link PagingAndSortingSpecificationRepository#findAll } + * return 返回列表数据 + */ + List findAll(@Nullable Specification specification, Class returnClass, Map queryHints); + + /** + * 统计查询总数据数 + * + * @param specification 动态查询条件 + * @return 总数据数 + */ + long count(@Nullable Specification specification); + + /** + * 带实体图的列表查询,可提高查询性能 (实体图的作用是可以管理,查询需要的属性和实体,而不是默认的字段属性) + * + * @param specification 动态查询条件 + * @param entityGraph 实体图名称 + * @return 列表数据 + */ + List findAll(@Nullable Specification specification, String entityGraph); + + /** + * 查询实体列表,提供自定义查询提示优化查询(查询提示包含实体图) + * + * @param specification 动态查询条件 + * @param queryHints 查询提示 + * @return 列表数据 + */ + List findAll(@Nullable Specification specification, Map queryHints); + + /** + * 带实体图的分页查询,可提高查询性能 (实体图的作用是可以管理,查询需要的属性和实体,而不是默认的字段属性) + * + * @param specification 动态查询条件 + * @param page 分页信息 + * @param entityGraph 实体图名称 实体图使用的抓取模式是fetch + * @return 分页数据 + */ + Page findAll(@Nullable Specification specification, Pageable page, String entityGraph); + + /** + * 分页查询,提供查询提示功能 + * + * @param specification 动态查询条件 + * @param page 分页信息 + * @param queryHints 查询提示 + * @return 分页数据 + */ + Page findAll(@Nullable Specification specification, Pageable page, Map queryHints); + + /** + * 带实体图和数据总量的分页查询,不会触发重复统计提高查询性能 (实体图的作用是可以管理,查询需要的属性和实体,而不是默认的字段属性) + * + * @param specification 动态查询条件 + * @param page 分页信息 + * @param total 数据总数 + * @param entityGraph 实体图名称 + * @return 分页数据 + */ + Page findAll(@Nullable Specification specification, Pageable page, long total, String entityGraph); + + /** + * 分页查询,提供数据总数(总数不为0不会重新去统计数量,提高查询速度),提供查询提示功能 + * + * @param specification 动态查询条件 + * @param page 分页信息 + * @param total 数据总数 + * @param queryHints 查询提示 + * @return 分页数据 + */ + Page findAll(@Nullable Specification specification, Pageable page, long total, Map queryHints); + + /** + * 带实体图和排序的列表查询 (实体图的作用是可以管理,查询需要的属性和实体,而不是默认的字段属性) + * + * @param specification 动态查询条件 + * @param sort 分页信息 + * @param entityGraph 实体图名称 + * @return 列表数据 + */ + List findAll(@Nullable Specification specification, Sort sort, String entityGraph); + + /** + * 列表查询,提供排序和查询提示功能 + * + * @param specification 动态分页条件 + * @param sort 排序 + * @param queryHints 查询提示 + * @return 列表数据 + */ + List findAll(@Nullable Specification specification, Sort sort, Map queryHints); + + /** + * 统计查询总数据数 + * + * @param specification 动态查询条件 + * @param limit 是否强制限制查询数量 + * @return 总数据数 + */ + long count(@Nullable Specification specification, boolean limit); + + Page findAll(@Nullable Specification specification, Pageable page, long total, boolean limit); + + Page findAll(@Nullable Specification specification, Pageable page, long total, boolean limit, Class returnClass, Map queryHints); + Page findAll(@Nullable Specification specification, Pageable page, long total, boolean limit, String entityGraph); + Page findAll(@Nullable Specification specification, Pageable page, long total, boolean limit, Class returnClass); +} diff --git a/server/src/main/java/com/aisino/iles/common/repository/SingleResultRepository.java b/server/src/main/java/com/aisino/iles/common/repository/SingleResultRepository.java new file mode 100644 index 0000000..e973a10 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/repository/SingleResultRepository.java @@ -0,0 +1,19 @@ +package com.aisino.iles.common.repository; + +import org.springframework.data.repository.NoRepositoryBean; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; + +import java.util.Optional; + +@NoRepositoryBean +public interface SingleResultRepository { + /** + * 带实体图通过ID获取数据信息 + * + * @param id 主键 + * @param entityGraph 实体图名称 + * @return 一个可能为空的数据信息(可能通过id无法查询到信息) + */ + Optional findById(@NonNull ID id, @Nullable String entityGraph); +} diff --git a/server/src/main/java/com/aisino/iles/common/repository/impl/CommonRepositoryImpl.java b/server/src/main/java/com/aisino/iles/common/repository/impl/CommonRepositoryImpl.java new file mode 100644 index 0000000..269e39b --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/repository/impl/CommonRepositoryImpl.java @@ -0,0 +1,293 @@ +package com.aisino.iles.common.repository.impl; + +import com.aisino.iles.common.util.Constants; +import com.aisino.iles.common.repository.PagingAndSortingSpecificationRepository; +import com.aisino.iles.common.repository.SingleResultRepository; +import com.aisino.iles.common.util.StringUtils; +import lombok.extern.slf4j.Slf4j; +import org.hibernate.CacheMode; +import org.hibernate.jpa.QueryHints; +import org.hibernate.query.Query; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.query.QueryUtils; +import org.springframework.data.jpa.repository.support.JpaEntityInformation; +import org.springframework.data.jpa.repository.support.SimpleJpaRepository; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +import jakarta.persistence.EntityGraph; +import jakarta.persistence.EntityManager; +import jakarta.persistence.TypedQuery; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import java.io.Serializable; +import java.util.*; + + +/** + * 数据访问层抽象公共实现 + * 作用于基于spring-data-jpa实现repository数据接口,例如SocialcollectDataSource之类 + *

+ * @author hx + * @since 2020-08-10 + * @param 实体类型 + * @param 主键 + */ +@Slf4j +public class CommonRepositoryImpl extends SimpleJpaRepository implements PagingAndSortingSpecificationRepository, SingleResultRepository { + private final EntityManager entityManager; + private final JpaEntityInformation entityInformation; + + public CommonRepositoryImpl(JpaEntityInformation entityInformation, EntityManager entityManager) { + super(entityInformation, entityManager); + this.entityManager = entityManager; + this.entityInformation = entityInformation; + } + + @Override + public Page findAll(Specification specification, Pageable pager, long totalNumber, Class returnClass) { + return findAll(specification, pager, totalNumber, false, returnClass, null); + } + + @Override + public Page findAll(Specification specification, Pageable pager, long total, Class returnClass, Map queryHints) { + return findAll(specification, pager, total,false, returnClass , queryHints); + } + + @Override + public List findAll(Specification specification, Class returnClass) { + return findAll(specification, Pageable.unpaged(), 0, returnClass).getContent(); + } + + @Override + public List findAll(Specification specification, Class returnClass, Map queryHints) { + return findAll(specification, Pageable.unpaged(), 0L, returnClass, queryHints).getContent(); + } + + @Override + public long count(Specification specification) { + return this.getCountQuery(specification, this.getDomainClass()).getSingleResult(); + } + + @Override + public List findAll(Specification specification, String entityGraph) { + return this.getQuery(specification, this.getDomainClass(), Sort.unsorted(), entityGraph).getResultList(); + } + + @Override + public List findAll(Specification specification, Map queryHints) { + return findAll(specification, Sort.unsorted(), queryHints); + } + + @Override + public Page findAll(Specification specification, Pageable page, String entityGraph) { + TypedQuery query = this.getQuery(specification, this.getDomainClass(), page.getSort(), entityGraph); + return page.isUnpaged() ? new PageImpl<>(query.getResultList()) : page(query, page, specification, 0); + } + + @Override + public Page findAll(Specification specification, Pageable page, Map queryHints) { + return findAll(specification, page, 0L, this.getDomainClass(), queryHints); + } + + @Override + public Page findAll(Specification specification, Pageable page, long total, String entityGraph) { + TypedQuery query = this.getQuery(specification, this.getDomainClass(), page.getSort(), entityGraph); + return page.isUnpaged() ? new PageImpl<>(query.getResultList()) : page(query, page, specification, total); + } + + @Override + public Page findAll(Specification specification, Pageable page, long total, Map queryHints) { + return findAll(specification, page, total, this.getDomainClass(), queryHints); + } + + @Override + public List findAll(Specification specification, Sort sort, String entityGraph) { + return this.getQuery(specification, this.getDomainClass(), sort, entityGraph).getResultList(); + } + + @Override + public List findAll(Specification specification, Sort sort, Map queryHints) { + return this.getQuery(specification, this.getDomainClass(), sort, null, this.getDomainClass(), queryHints).getResultList(); + } + + @Override + public long count(Specification specification, boolean limit) { + return limit ? this.getCountQueryLimit(specification,this.getDomainClass()).getResultStream().count() : this.getCountQuery(specification,this.getDomainClass()).getSingleResult(); + } + + @Override + public Page findAll(Specification specification, Pageable page, long total, boolean limit) { + return findAll(specification, page, total, limit, this.getDomainClass(), null); + } + + @Override + public Page findAll(Specification specification, Pageable page, long total, boolean limit, Class returnClass, Map queryHints) { + TypedQuery query = this.getQuery(specification, this.getDomainClass(), page.getSort(), null, returnClass, queryHints); + return page.isUnpaged() ? new PageImpl<>(query.getResultList()) : page(query, page, specification, total,limit); + } + + @Override + public Page findAll(Specification specification, Pageable page, long total, boolean limit, String entityGraph) { + TypedQuery query = this.getQuery(specification, this.getDomainClass(), page.getSort(), entityGraph); + return page.isUnpaged() ? new PageImpl<>(query.getResultList()) : page(query, page, specification, total,limit); + } + + @Override + public Page findAll(Specification specification, Pageable page, long total, boolean limit, Class returnClass) { + return findAll(specification, page, total, limit, returnClass, null); + } + + @Override + public @NonNull List findAll(Specification spec) { + return findAll(spec, ""); + } + + @Override + public Optional findById(@NonNull ID id, String entityGraph) { + Map properties = new HashMap<>(); + Optional.ofNullable(entityGraph).filter(StringUtils::isNotEmpty).map(this.entityManager::getEntityGraph).ifPresent(eg -> properties.put(QueryHints.HINT_FETCHGRAPH, eg)); + return Optional.ofNullable(this.entityManager.find(this.getDomainClass(), id, properties)); + } + + + protected PageImpl page(TypedQuery query, Pageable page, Specification specification, long count) { + return page(query, page, specification, count, false); + } + + protected PageImpl page(TypedQuery query, Pageable page, Specification specification, long count, boolean limit) { + long total = count; + if (total == 0) + total = this.count(specification,limit); + if (page.isPaged()) { + query.setFirstResult(((int) (page.getOffset()))); + query.setMaxResults(page.getPageSize()); + } + return new PageImpl<>(query.getResultList(), page, total); + } + + @NonNull + protected TypedQuery getCountQuery(@Nullable Specification spec, @NonNull Class domainClass) { + CriteriaBuilder builder = this.entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery(Long.class); + Root root = this.applySpecificationToCriteria(spec, domainClass, query); + if(query.isDistinct()) { + query.select(builder.countDistinct(root)); + } else { + query.select(builder.count(root)); + } + query.orderBy(Collections.emptyList()); + return this.entityManager.createQuery(query); + } + protected TypedQuery getCountQueryLimit(@Nullable Specification spec,@NonNull Class domainClass) { + CriteriaBuilder builder = this.entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery(this.entityInformation.getIdType()); + Root root = this.applySpecificationToCriteria(spec, domainClass, query); + query.multiselect(root.get(this.entityInformation.getIdAttribute())); + query.orderBy(Collections.emptyList()); + return this.entityManager.createQuery(query).setMaxResults(Constants.GLOBAL_QUERY_LIMIT); + } + + protected TypedQuery getQuery(@Nullable Specification spec, Class domainClass, Sort sort, String entityGraph) { + return this.getQuery(spec, domainClass, sort, entityGraph, domainClass, null); + } + + protected TypedQuery getQuery(@Nullable Specification spec, Class domainClass, Sort sort, String entityGraph, Class resultClass, @Nullable Map queryHints) { + CriteriaBuilder cb = this.entityManager.getCriteriaBuilder(); + CriteriaQuery query = cb.createQuery(resultClass); + Root root = this.applySpecificationToCriteria(spec, domainClass, query); + if (sort.isSorted()) { + query.orderBy(QueryUtils.toOrders(sort, root, cb)); + } + TypedQuery typedQuery = this.entityManager.createQuery(query); + // 处理实体图 + applyEntityGraph(typedQuery, entityGraph); + // 处理查询提示 + applyQueryHints(typedQuery, queryHints); + // 一般列表查询不缓存数据,提高查询效率 + typedQuery.setHint(QueryHints.HINT_CACHE_MODE, CacheMode.IGNORE); + return typedQuery; + } + + @NonNull + @Override + public Page findAll(@NonNull Pageable pageable) { + return this.findAll(null, pageable, (String) null); + } + + @NonNull + @Override + public Page findAll(Specification spec, @NonNull Pageable pageable) { + return this.findAll(spec, pageable, (String) null); + } + + private Root applySpecificationToCriteria(@Nullable Specification spec, Class domainClass, CriteriaQuery query) { + Assert.notNull(domainClass, "Domain class must not be null!"); + Assert.notNull(query, "CriteriaQuery must not be null!"); + Root root = query.from(domainClass); + if (spec == null) { + return root; + } else { + CriteriaBuilder builder = this.entityManager.getCriteriaBuilder(); + Predicate predicate = spec.toPredicate(root, query, builder); + if (predicate != null) { + query.where(predicate); + } + return root; + } + } + + /** + * 处理实体图 + * + * @param query 查询 + * @param entityGraph 实体图名称 + * @param 类型 + */ + private void applyEntityGraph(TypedQuery query, String entityGraph) { + if (!StringUtils.isEmpty(entityGraph)) { + EntityGraph eg = this.entityManager.getEntityGraph(entityGraph); + query.setHint(QueryHints.HINT_FETCHGRAPH, eg); // 使用fetch模式抓取 + } + } + + /** + * 处理查询提示 + * + * @param query 类型安全查询 + * @param queryHints 查询提示 + * @param 类型占位符 + */ + private void applyQueryHints(@NonNull TypedQuery query, @Nullable Map queryHints) { + Optional.ofNullable(queryHints) + .filter(hints -> !hints.isEmpty()) + .map(Map::entrySet) + .map(Set::stream) + .ifPresent(es -> es.forEach(e -> { + if (e.getKey().equals(QueryHints.HINT_FETCHGRAPH)) { + // 这里没有处理他本身为实体图的情况,通常情况下能拿到实体图需要entityManager的帮助,在service里面直接注入 + // 或者使用entityManager不是很符合封装思路 + applyEntityGraph(query, (String) e.getValue()); + } else if (e.getKey().equals(Constants.QUERY_HINT_DB_HINT)) { + // 这里使用hibernate的query,目前只考虑了这一种jpa实现,如果之后需要考虑多重jpa实现支持 + // 会在这里再做一个抽象 + Query unwrap = query.unwrap(Query.class); + if (String.class.isAssignableFrom(e.getValue().getClass())) { + unwrap.addQueryHint(e.getValue().toString()); + } else if (Collection.class.isAssignableFrom(e.getValue().getClass())) { + ((Collection) e.getValue()).forEach(unwrap::addQueryHint); + } + } else { + query.setHint(e.getKey(), e.getValue()); + } + })); + + } +} diff --git a/server/src/main/java/com/aisino/iles/common/service/AwsS3Service.java b/server/src/main/java/com/aisino/iles/common/service/AwsS3Service.java new file mode 100644 index 0000000..e25250b --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/service/AwsS3Service.java @@ -0,0 +1,242 @@ +package com.aisino.iles.common.service; + +import cn.hutool.core.util.StrUtil; +import com.aisino.iles.common.model.AwsConfig; +import com.aisino.iles.common.util.DateUtil; +import com.aisino.iles.common.util.StringUtils; +import com.amazonaws.ClientConfiguration; +import com.amazonaws.Protocol; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.client.builder.AwsClientBuilder; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import com.amazonaws.services.s3.model.*; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.TrustAllStrategy; +import org.apache.http.ssl.SSLContextBuilder; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.stereotype.Service; + +import javax.net.ssl.SSLContext; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Files; +import java.time.LocalDateTime; +import java.util.Date; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Service +@Slf4j +public class AwsS3Service implements InitializingBean { + private final AwsConfig awsConfig; + // 新key样式正则匹配,旧key样式正则匹配 + // 用来提取出路径里面的日(月中日, 1-31) + private final Pattern keyPattern; + private AmazonS3 client; + + public AwsS3Service(AwsConfig awsConfig) { + this.awsConfig = awsConfig; + keyPattern = Pattern.compile("\\w*[/_]\\d{6}(\\d{2})[/_].*"); + } + + @Override + public void afterPropertiesSet() throws Exception { + if (awsConfig.getAccessKey() != null && awsConfig.getSecretKey() != null && awsConfig.getEndpoint() != null) { + ClientConfiguration config = new ClientConfiguration(); + config.disableSocketProxy(); + if (awsConfig.getEndpoint().startsWith("https")) { + config.setProtocol(Protocol.HTTPS); + SSLContext context = + new SSLContextBuilder().loadTrustMaterial(null, + new TrustAllStrategy()).build(); + ConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory( + context, NoopHostnameVerifier.INSTANCE + ); + config.getApacheHttpClientConfig().withSslSocketFactory(sslSocketFactory); + } else { + config.setProtocol(Protocol.HTTP); + } + this.client = AmazonS3ClientBuilder + .standard() + .withClientConfiguration(config) + .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(awsConfig.getAccessKey(), awsConfig.getSecretKey()))) + .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(awsConfig.getEndpoint(), Regions.CN_NORTH_1.getName())) + .enablePathStyleAccess() + .build(); + } + } + + /** + * 执行文件上传 + * + * @param file 要上传的文件的路径 + * @param key 存储文件的路径 + * @return 文件路径 + */ + public String upload(File file, String key) { + try (InputStream stream = Files.newInputStream(file.toPath())) { + return upload(stream, key); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public String upload(InputStream stream, String key) throws IOException { + return upload(stream, null, key); + } + + /** + * @param input 文件流 + * @param contentType metaType 例:image/jpeg + * @param key 对象key + * @return 上传成功后文件路径 + */ + public String upload(InputStream input, String contentType, String key) { + Date expireDate = DateUtil.asDate(LocalDateTime.now().plusMonths(1)); + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setHttpExpiresDate(expireDate); + Optional.ofNullable(contentType) + .map(StrUtil::trimToNull) + .ifPresent(metadata::setContentType); + try { + metadata.setContentLength(input.available()); + } catch (IOException e) { + throw new RuntimeException("上传文件时设置内容长度出错:", e); + } + String bucket = getBucket(key); + client.putObject(new PutObjectRequest(bucket, key, input, metadata) + .withCannedAcl(CannedAccessControlList.Private)); + GeneratePresignedUrlRequest urlRequest = new GeneratePresignedUrlRequest(bucket, key); + URL url = client.generatePresignedUrl(urlRequest); + return url.toString(); + } + + /** + * 获取对象完整访问地址 + * + * @param key 对象名称 + * @return http(s)访问URL + */ + public String getUrl(String key) { + return getUrl(key, null); + } + + public String getUrl(String key, Date expired) { + if(StringUtils.isEmpty(key)) // 为了兼容性,如果key为空,则返回null + return null; + AtomicReference urlRef = new AtomicReference<>(); + checkExists(key, true, (bucket, k) -> { + GeneratePresignedUrlRequest urlRequest = new GeneratePresignedUrlRequest(bucket, k); + urlRequest.setExpiration(expired); + URL url = client.generatePresignedUrl(urlRequest); + urlRef.set(url.toString()); + }); + return urlRef.get(); + } + + /** + * 获取对象流 + * + * @param key 对象名称 + * @return 对象内容数据流 + */ + public InputStream getObjectStream(String key) { + AtomicReference stream = new AtomicReference<>(); + checkExists(key, true, (bucket, k) -> { + S3Object object = client.getObject(new GetObjectRequest(bucket, k)); + stream.set(object.getObjectContent()); + }); + return stream.get(); + } + + /** + * 删除对象 + * + * @param key 对象名称 + */ + public void delete(String key) { + checkExists(key, true, (bucket, k) -> client.deleteObject(bucket, k)); + } + + /** + * 获取数据桶,只按照新规则 + */ + private String getBucket(String key) { + return getBucket(key, true); + } + + /** + * 获取数据桶 + * + * @param key 对象名称 + * @param fallback 是否启用回滚模式,从返回分桶之前的数据桶 + * @return 数据桶 + */ + private String getBucket(String key, boolean fallback) { + // 识别新key和旧key两种模式选择对应的数据桶 + // BF/20220507/f3f987e49e53494ca86b664e880b19be.rar -> debug + StringBuilder bucketBuilder = new StringBuilder(awsConfig.getBucket()); + if (!fallback) { + Matcher matcher = keyPattern.matcher(key); + if (matcher.find()) { + log.debug("newkey group 1: {}", matcher.group(1)); + bucketBuilder.append(matcher.group(1)); + } + } + log.debug("bucket: {}", bucketBuilder); + return bucketBuilder.toString(); + } + + public void rename(String srcKey, String destKey) { + String srcBkt = getBucket(srcKey, true); + String destBkt = getBucket(destKey, true); + CopyObjectRequest copy = new CopyObjectRequest(srcBkt, srcKey, destBkt, destKey); + client.copyObject(copy); + } + + /** + * 根据key判断桶里的内容是否存在 + */ + public boolean exists(String key) { + AtomicBoolean exists = new AtomicBoolean(false); + checkExists(key, true, (bkt, k1) -> exists.set(true)); + return exists.get(); + } + + /** + * @param key 文件名 + * @param changeKeyMode 是否要在默认查找方式未找到对象时,进行“_”替换为"/"后补充查找 + * @param found 当最终查找到时执行函数。参数1: 找到时所在的桶 参数2: 找到的key(可能是替换后的key) + */ + private void checkExists(String key, boolean changeKeyMode, BiConsumer found) { + String bucket = getBucket(key), splitBkt = bucket; + // 分桶查询 + if (client.doesObjectExist(bucket, key)) { + found.accept(bucket, key); + } else { + bucket = getBucket(key, true); + // 旧桶查找 + if (client.doesObjectExist(bucket, key)) { + found.accept(bucket, key); + } else if (changeKeyMode) { + // key中的_替换为/后在分桶中查询 + String newKey = key.replace("_", "/"); + if (client.doesObjectExist(splitBkt, newKey)) { + found.accept(splitBkt, newKey); + } + } + } + } +} diff --git a/server/src/main/java/com/aisino/iles/common/service/DataTransfer.java b/server/src/main/java/com/aisino/iles/common/service/DataTransfer.java new file mode 100644 index 0000000..a87cb70 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/service/DataTransfer.java @@ -0,0 +1,32 @@ +package com.aisino.iles.common.service; + +import org.springframework.beans.factory.InitializingBean; + +/** + * 模块间数据传输和调用 + * 用法:在自己需要传输或者调用的模块继承这个接口,需要 + * + * @author huxin + * @since 2020-08-19 + */ +public interface DataTransfer extends InitializingBean { + /** + * 获取传输数据 + * + * @param source 来源数据 + * @return 返回数据 + */ + R getTransData(T source); + + /** + * 注册传输对象的名称或者键 + * @return 注册传输对象的名称或者键 + */ + String registerDataTransKey(); + + @Override + default void afterPropertiesSet() throws Exception { + // 启动自动注册类型 + DataTransferContext.INSTANCE.registerDataTransfer(registerDataTransKey(), this); + } +} diff --git a/server/src/main/java/com/aisino/iles/common/service/DataTransferContext.java b/server/src/main/java/com/aisino/iles/common/service/DataTransferContext.java new file mode 100644 index 0000000..47e4f28 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/service/DataTransferContext.java @@ -0,0 +1,46 @@ +package com.aisino.iles.common.service; + +import java.util.HashMap; +import java.util.Map; + + +/** + * 传输对象管理上下文 + */ +public class DataTransferContext { + private final Map DATA_TRANSFER_MAP = new HashMap<>(); + public static final DataTransferContext INSTANCE = new DataTransferContext(); + + + /** + * 获取传输对象,通过键或者名称 + * + * @param key 传输对象键、名称 + * @return 传输对象 + */ + public DataTransfer getDataTransfer(String key) { + if (!containDataTransferKey(key)) + throw new RuntimeException("没有找到注册名称为[" + key + "]的传输对象"); + return DATA_TRANSFER_MAP.get(key); + } + + /** + * 注册传输对象服务 + * + * @param key 传输对象键、名称 + * @param dataTransfer 传输对象 + */ + public void registerDataTransfer(String key, DataTransfer dataTransfer) { + DATA_TRANSFER_MAP.put(key, dataTransfer); + } + + /** + * 判断数据传输对象键是否存在 + * + * @param key 传输对象键、名称 + * @return 是否存在 + */ + public boolean containDataTransferKey(String key) { + return DATA_TRANSFER_MAP.containsKey(key); + } +} diff --git a/server/src/main/java/com/aisino/iles/common/service/FtpService.java b/server/src/main/java/com/aisino/iles/common/service/FtpService.java new file mode 100644 index 0000000..b2de925 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/service/FtpService.java @@ -0,0 +1,376 @@ +package com.aisino.iles.common.service; + +import cn.hutool.core.codec.Base64; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.file.FileNameUtil; +import cn.hutool.core.util.StrUtil; +import com.aisino.iles.common.model.AwsConfig; +import com.aisino.iles.common.model.UploadFile; +import com.aisino.iles.common.model.enums.ContentType; +import com.aisino.iles.common.model.enums.IndustryCategoryForFile; +import com.aisino.iles.common.util.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.stereotype.Service; +import org.springframework.util.StreamUtils; +import org.springframework.validation.annotation.Validated; + +import jakarta.validation.Valid; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +@Service +@Validated +public class FtpService { + private final Log log = LogFactory.getLog(this.getClass()); + private final AwsConfig awsConfig; + private final AwsS3Service awsS3Service; + + public FtpService(AwsConfig awsConfig, AwsS3Service awsS3Service) { + this.awsConfig = awsConfig; + this.awsS3Service = awsS3Service; + } + + public static String generateKey(IndustryCategoryForFile ind, LocalDate date, String uuid, String ext) { + return StrUtil.format("{}/{}/{}{}", ind.getCode(), + date.format(DateTimeFormatter.ofPattern("yyyyMMdd")), + uuid.replaceAll("-", ""), + ext); + } + + /** + * 获取文件web访问路径 + * + * @param savePathName 对象Key + * @return 对象http访问url + */ + public String getFileUrl(String savePathName) { + return getFileUrl(savePathName, false); + } + + public String getFileUrl(String key, boolean direct) { + return getFileUrl(key, null, direct); + } + + public String getFileUrl(String savePathName, Date expired) { + return getFileUrl(savePathName, expired, false); + } + + public String getFileUrl(String key, Date expired, boolean direct) { + String url = awsS3Service.getUrl(key, expired); + if (StrUtil.isNotBlank(url) && !direct && StrUtil.isNotBlank(awsConfig.getAlterHttp())) { + return url.replaceFirst(awsConfig.getEndpoint(), awsConfig.getAlterHttp()); + } + return url; + } + + /** + * 获取文件下载路径 + * + * @param savePathName 对象Key + * @return 对象http访问url + */ + public String getFileDownloadUrl(String savePathName) { + String url = awsS3Service.getUrl(savePathName); + // 当存在替换标记时,替换为替换域名 + if (StrUtil.isNotBlank(url) && StrUtil.isNotBlank(awsConfig.getAlterHttp())) { + return url.replaceFirst(awsConfig.getEndpoint(), awsConfig.getAlterHttp()); + } + return url; + } + + /** + * 上传文件 同时另存文件系统供同步使用 + * + * @param industryCategoryForFile 行业 + * @param fileName 文件名 + * @param fileBase64Str 文件内容base64 + * @return 文件key + */ + public String uploadFile(IndustryCategoryForFile industryCategoryForFile, String fileName, String fileBase64Str) { + // 将base64字符串转为数组 + byte[] bytes = Base64.decode(fileBase64Str); + // 转换为输入流 + ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes); + try { + return uploadTempFile(industryCategoryForFile, fileName, inputStream); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * 上传文件到临时目录 + * + * @param fileName 文件名称 + * @param inputStream 流对象 + * @return 对象Key + */ + public String uploadTempFile(IndustryCategoryForFile industryCategoryForFile, + String fileName, + InputStream inputStream) throws IOException { + return internalUpload(industryCategoryForFile, fileName, inputStream, true); + } + + private String internalUpload(IndustryCategoryForFile industryCategoryForFile, + String fileName, + InputStream inputStream, + boolean save + ) + throws IOException { + byte[] data = StreamUtils.copyToByteArray(inputStream); + String fileExtendName = Optional.ofNullable(fileName) + .map(StrUtil::trimToNull) + .map(FileNameUtil::extName) + .filter(s -> s.length() <= 4) + .map(StrUtil::trimToNull) + .map("."::concat) + .orElse(""); + String key = generateKey(industryCategoryForFile, fileExtendName); + String contentType = ContentType.getContentTypeNameByFileExtendName(fileExtendName); + awsS3Service.upload(new ByteArrayInputStream(data), contentType, key); + if (save) { + saveFile(key, new ByteArrayInputStream(data)); + } + return key; + } + + private String generateKey(IndustryCategoryForFile industryCategoryForFile, String extendName) { + String uuid = UUID.randomUUID().toString().replaceAll("-", ""); + return generateKey(industryCategoryForFile, LocalDate.now(), uuid, extendName); + } + + /** + * 用 {@code fileName} 作为key存储文件到OSS,本地不保存副本(用于不需要同步图片的场景) + * + * @param fileName 文件名 + * @param inputStream 流内容 + * @return 文件访问URL + * @throws IOException 网络错误 + */ + public String uploadFileWithFileName(String fileName, InputStream inputStream) throws IOException { + return uploadFileWithFileName(fileName, inputStream, false); + } + + /** + * 用 {@code fileName} 作为key存储文件到OSS + * + * @param fileName 文件名 + * @param inputStream 流内容 + * @param save 指定是否需要本地保存文件副本。true表示要保存副本,false不保存 + * @return 文件访问URL + * @throws IOException 网络错误 + */ + public String uploadFileWithFileName(String fileName, InputStream inputStream, boolean save) throws IOException { + byte[] data = StreamUtils.copyToByteArray(inputStream); + String fileExtendName = Optional.ofNullable(fileName) + .map(StrUtil::trimToNull) + .map(FileNameUtil::extName) + .filter(s -> s.length() <= 4) + .map(StrUtil::trimToNull) + .map("."::concat) + .orElse(""); + String contentType = ContentType.getContentTypeNameByFileExtendName(fileExtendName); + String url = awsS3Service.upload(new ByteArrayInputStream(data), contentType, fileName); + if (save) { + saveFile(fileName, data); + } + return url; + } + + /** + * 上传文件到oss不保存临时文件 + * @param industryCategoryForFile 行业类别 + * @param fileName 文件名 + * @param fileBase64Str 文件内容base64 + * @return 文件key + * @throws IOException 文件或者网络错误 + */ + public String uploadFileWithBase64NoSave(IndustryCategoryForFile industryCategoryForFile, String fileName, String fileBase64Str) throws IOException { + return internalUpload(industryCategoryForFile, fileName, new ByteArrayInputStream(Base64.decode(fileBase64Str)), false); + } + + /** + * @param industryCategoryForFile 行业类别 + * @param fileName 文件名 + * @param fileBase64Str 文件内容base64 + * @return 文件key + * @throws IOException 文件或者网络错误 + */ + public String updateFileWithBase64(IndustryCategoryForFile industryCategoryForFile, String fileName, String fileBase64Str) throws IOException { + return internalUpload(industryCategoryForFile, fileName, new ByteArrayInputStream(Base64.decode(fileBase64Str)), false); + } + + + public boolean deletePathFile(String nameAsKey, boolean createDeleteFile) { + awsS3Service.delete(nameAsKey); + if (createDeleteFile) { + createDeleteFile(nameAsKey); + } + return true; + } + + public boolean deletePathFile(String nameAsKey) { + return deletePathFile(nameAsKey, true); + } + + private void createDeleteFile(String key) { + String fileName = key.replaceAll("/", "_"); + try { + if (StrUtil.isNotBlank(awsConfig.getFilePath())) { + String filePathName = awsConfig.getFilePath() + File.separator + fileName + ".delete"; + Files.write(Paths.get(filePathName), fileName.getBytes(StandardCharsets.UTF_8)); + } + } catch (Exception e) { + log.error(fileName + "存储到删除文件异常:", e); + } + } + + private void saveFile(String fileName, String content) { + saveFile(fileName, content.getBytes(StandardCharsets.UTF_8)); + } + + private void saveFile(String fileName, byte[] content) { + saveFile(fileName, new ByteArrayInputStream(content)); + } + + private void saveFile(String key, InputStream input) { + String fileName = key.replaceAll("/", "_"); + try (InputStream input1 = input) { + if (StrUtil.isNotBlank(awsConfig.getFilePath())) { + String suffix = ".tmp"; + String filePathName = awsConfig.getFilePath() + File.separator + fileName + suffix; + File file = new File(filePathName); + Files.copy(input1, file.toPath()); + FileUtil.rename(file, StrUtil.removeSuffix(filePathName, suffix), true); + } + } catch (Exception e) { + log.error(fileName + "存储到文件异常:", e); + } + } + + /** + * 多文件上传base64版本 + * + * @param updateFiles 上传文件列表 + * @param ic 上传行业类别 + * @return 上传成功的文件列表 + */ + @Validated + public List uploadFilesWithBase64(@Valid List<@Valid UploadFile> updateFiles, IndustryCategoryForFile ic) { + return updateFiles.stream() + .filter(uf -> StringUtils.isNotEmpty(uf.getContentStr())) + .peek(uf -> { + uf.setPath(uploadFile(ic, uf.getFileName(), uf.getContentStr())); + uf.setAccessUrl(getFileUrl(uf.getPath())); + uf.setContent(null); + uf.setContentStr(null); + }) + .collect(Collectors.toList()); + } + + /** + * 通过Key获取对应文件的字节内容 + * + * @param key 文件key + * @return 文件字节内容或者null + */ + public byte[] getFileAsBytes(String key) { + return Optional.ofNullable(this.getFileAsStream(key)) + .map(s -> IoUtil.readBytes(s, true)) + .orElse(null); + } + + /** + * 通过Key获取对应文件流格式的文件内容 + * + * @param key 文件key + * @return 文件流内容或者null + */ + public InputStream getFileAsStream(String key) { + return Optional.ofNullable(key) + .filter(awsS3Service::exists) + .map(StrUtil::trimToNull) + .map(awsS3Service::getObjectStream) + .orElse(null); + } + + /** + * 通过Key获取对应文件的base64文件内容 + * + * @param key 文件key(名称如:A/20220501/xxxx.txt) + * @return base64编码的文件内容或者null + */ + public String getFileAsBase64(String key) { + return Optional.ofNullable(key).map(this::getFileAsStream) + .map(cn.hutool.core.codec.Base64::encode) + .orElse(null); + } + + /** + * 重命名文件,且不保存副本。 example: A/12345678/abc.jpg = > A/9876541/def.doc + * + * @param srcKey 原名称 + * @param destKey 新名称 + */ + public void renameFile(String srcKey, String destKey) { + renameFile(srcKey, destKey, false); + } + + /** + * 重命名文件 example: A/12345678/abc.jpg = > A/9876541/def.doc + * + * @param srcKey 原名称 + * @param destKey 新名称 + * @param saveCopy 是否保存副本到本地用于同步。true保存副本,false不保存 + */ + public void renameFile(String srcKey, String destKey, boolean saveCopy) { + String srcName = Optional.ofNullable(srcKey) + .map(StrUtil::trimToNull) + .orElseThrow(() -> new RuntimeException("原名称不能为空")); + String destName = Optional.ofNullable(destKey) + .map(StrUtil::trimToNull) + .orElseThrow(() -> new RuntimeException("新名称不能为空")); + if (srcName.equals(destName)) { + throw new RuntimeException("名称一样不能重命名"); + } + awsS3Service.rename(srcName, destName); + if (saveCopy) { + createDeleteFile(srcName); + saveFile(destName, awsS3Service.getObjectStream(destName)); + } + } + + /** + * 仅用于保安附件数据遗漏补传 + * + * @param key 名称 + */ + public void touchFile(String key) { + String srcName = Optional.ofNullable(key) + .map(StrUtil::trimToNull) + .orElseThrow(() -> new RuntimeException("名称不能为空")); + Optional.ofNullable(getFileAsBytes(srcName)) + .map(ByteArrayInputStream::new) + .ifPresent(is -> { + try (InputStream stream = is) { + uploadFileWithFileName(key, stream, true); + } catch (IOException e) { + log.error(e); + } + }); + } +} diff --git a/server/src/main/java/com/aisino/iles/common/util/Base64Util.java b/server/src/main/java/com/aisino/iles/common/util/Base64Util.java new file mode 100644 index 0000000..ab37e0c --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/util/Base64Util.java @@ -0,0 +1,129 @@ +package com.aisino.iles.common.util; + +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpUtil; +import com.amazonaws.util.IOUtils; +import org.apache.commons.codec.binary.Base64; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; + +public class Base64Util { + public static String imgString = "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LQAAAAYAAAAAAAE4QklNA/gAAAAAAHAAAP////////////////////////////8D6AAAAAD/////////////////////////////A+gAAAAA/////////////////////////////wPoAAAAAP////////////////////////////8D6AAAOEJJTQQAAAAAAAACAAI4QklNBAIAAAAAAAYAAAAAAAA4QklNBDAAAAAAAAMBAQEAOEJJTQQtAAAAAAAGAAEAAAALOEJJTQQIAAAAAAAQAAAAAQAAAkAAAAJAAAAAADhCSU0EHgAAAAAABAAAAAA4QklNBBoAAAAAAz8AAAAGAAAAAAAAAAAAAACAAAAAZwAAAAVnKmgHmJgALQAyAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAABnAAAAgAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAABAAAAABAAAAAAAAbnVsbAAAAAIAAAAGYm91bmRzT2JqYwAAAAEAAAAAAABSY3QxAAAABAAAAABUb3AgbG9uZwAAAAAAAAAATGVmdGxvbmcAAAAAAAAAAEJ0b21sb25nAAAAgAAAAABSZ2h0bG9uZwAAAGcAAAAGc2xpY2VzVmxMcwAAAAFPYmpjAAAAAQAAAAAABXNsaWNlAAAAEgAAAAdzbGljZUlEbG9uZwAAAAAAAAAHZ3JvdXBJRGxvbmcAAAAAAAAABm9yaWdpbmVudW0AAAAMRVNsaWNlT3JpZ2luAAAADWF1dG9HZW5lcmF0ZWQAAAAAVHlwZWVudW0AAAAKRVNsaWNlVHlwZQAAAABJbWcgAAAABmJvdW5kc09iamMAAAABAAAAAAAAUmN0MQAAAAQAAAAAVG9wIGxvbmcAAAAAAAAAAExlZnRsb25nAAAAAAAAAABCdG9tbG9uZwAAAIAAAAAAUmdodGxvbmcAAABnAAAAA3VybFRFWFQAAAABAAAAAAAAbnVsbFRFWFQAAAABAAAAAAAATXNnZVRFWFQAAAABAAAAAAAGYWx0VGFnVEVYVAAAAAEAAAAAAA5jZWxsVGV4dElzSFRNTGJvb2wBAAAACGNlbGxUZXh0VEVYVAAAAAEAAAAAAAlob3J6QWxpZ25lbnVtAAAAD0VTbGljZUhvcnpBbGlnbgAAAAdkZWZhdWx0AAAACXZlcnRBbGlnbmVudW0AAAAPRVNsaWNlVmVydEFsaWduAAAAB2RlZmF1bHQAAAALYmdDb2xvclR5cGVlbnVtAAAAEUVTbGljZUJHQ29sb3JUeXBlAAAAAE5vbmUAAAAJdG9wT3V0c2V0bG9uZwAAAAAAAAAKbGVmdE91dHNldGxvbmcAAAAAAAAADGJvdHRvbU91dHNldGxvbmcAAAAAAAAAC3JpZ2h0T3V0c2V0bG9uZwAAAAAAOEJJTQQoAAAAAAAMAAAAAj/wAAAAAAAAOEJJTQQUAAAAAAAEAAAADDhCSU0EDAAAAAATZwAAAAEAAABnAAAAgAAAATgAAJwAAAATSwAYAAH/2P/iDFhJQ0NfUFJPRklMRQABAQAADEhMaW5vAhAAAG1udHJSR0IgWFlaIAfOAAIACQAGADEAAGFjc3BNU0ZUAAAAAElFQyBzUkdCAAAAAAAAAAAAAAAAAAD21gABAAAAANMtSFAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEWNwcnQAAAFQAAAAM2Rlc2MAAAGEAAAAbHd0cHQAAAHwAAAAFGJrcHQAAAIEAAAAFHJYWVoAAAIYAAAAFGdYWVoAAAIsAAAAFGJYWVoAAAJAAAAAFGRtbmQAAAJUAAAAcGRtZGQAAALEAAAAiHZ1ZWQAAANMAAAAhnZpZXcAAAPUAAAAJGx1bWkAAAP4AAAAFG1lYXMAAAQMAAAAJHRlY2gAAAQwAAAADHJUUkMAAAQ8AAAIDGdUUkMAAAQ8AAAIDGJUUkMAAAQ8AAAIDHRleHQAAAAAQ29weXJpZ2h0IChjKSAxOTk4IEhld2xldHQtUGFja2FyZCBDb21wYW55AABkZXNjAAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAEnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYWVogAAAAAAAA81EAAQAAAAEWzFhZWiAAAAAAAAAAAAAAAAAAAAAAWFlaIAAAAAAAAG+iAAA49QAAA5BYWVogAAAAAAAAYpkAALeFAAAY2lhZWiAAAAAAAAAkoAAAD4QAALbPZGVzYwAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRlc2MAAAAAAAAALklFQyA2MTk2Ni0yLjEgRGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAALklFQyA2MTk2Ni0yLjEgRGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAAAAAAAAAAAAAAAABkZXNjAAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24gaW4gSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdmlldwAAAAAAE6T+ABRfLgAQzxQAA+3MAAQTCwADXJ4AAAABWFlaIAAAAAAATAlWAFAAAABXH+dtZWFzAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAACjwAAAAJzaWcgAAAAAENSVCBjdXJ2AAAAAAAABAAAAAAFAAoADwAUABkAHgAjACgALQAyADcAOwBAAEUASgBPAFQAWQBeAGMAaABtAHIAdwB8AIEAhgCLAJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA4ADlAOsA8AD2APsBAQEHAQ0BEwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFnAW4BdQF8AYMBiwGSAZoBoQGpAbEBuQHBAckB0QHZAeEB6QHyAfoCAwIMAhQCHQImAi8COAJBAksCVAJdAmcCcQJ6AoQCjgKYAqICrAK2AsECywLVAuAC6wL1AwADCwMWAyEDLQM4A0MDTwNaA2YDcgN+A4oDlgOiA64DugPHA9MD4APsA/kEBgQTBCAELQQ7BEgEVQRjBHEEfgSMBJoEqAS2BMQE0wThBPAE/gUNBRwFKwU6BUkFWAVnBXcFhgWWBaYFtQXFBdUF5QX2BgYGFgYnBjcGSAZZBmoGewaMBp0GrwbABtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfSB+UH+AgLCB8IMghGCFoIbgiCCJYIqgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7ChEKJwo9ClQKagqBCpgKrgrFCtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMMXAx1DI4MpwzADNkM8w0NDSYNQA1aDXQNjg2pDcMN3g34DhMOLg5JDmQOfw6bDrYO0g7uDwkPJQ9BD14Peg+WD7MPzw/sEAkQJhBDEGEQfhCbELkQ1xD1ERMRMRFPEW0RjBGqEckR6BIHEiYSRRJkEoQSoxLDEuMTAxMjE0MTYxODE6QTxRPlFAYUJxRJFGoUixStFM4U8BUSFTQVVhV4FZsVvRXgFgMWJhZJFmwWjxayFtYW+hcdF0EXZReJF64X0hf3GBsYQBhlGIoYrxjVGPoZIBlFGWsZkRm3Gd0aBBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7HKMczBz1HR4dRx1wHZkdwx3sHhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDwIRwhSCF1IaEhziH7IiciVSKCIq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4JWgllyXHJfcmJyZXJocmtyboJxgnSSd6J6sn3CgNKD8ocSiiKNQpBik4KWspnSnQKgIqNSpoKpsqzysCKzYraSudK9EsBSw5LG4soizXLQwtQS12Last4S4WLkwugi63Lu4vJC9aL5Evxy/+MDUwbDCkMNsxEjFKMYIxujHyMioyYzKbMtQzDTNGM38zuDPxNCs0ZTSeNNg1EzVNNYc1wjX9Njc2cjauNuk3JDdgN5w31zgUOFA4jDjIOQU5Qjl/Obw5+To2OnQ6sjrvOy07azuqO+g8JzxlPKQ84z0iPWE9oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlBakGsQe5CMEJyQrVC90M6Q31DwEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgFSEtIkUjXSR1JY0mpSfBKN0p9SsRLDEtTS5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBPSU+TT91QJ1BxULtRBlFQUZtR5lIxUnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9WXFapVvdXRFeSV+BYL1h9WMtZGllpWbhaB1pWWqZa9VtFW5Vb5Vw1XIZc1l0nXXhdyV4aXmxevV8PX2Ffs2AFYFdgqmD8YU9homH1YklinGLwY0Njl2PrZEBklGTpZT1lkmXnZj1mkmboZz1nk2fpaD9olmjsaUNpmmnxakhqn2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8eb3hv0XArcIZw4HE6cZVx8HJLcqZzAXNdc7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5iXnnekZ6pXsEe2N7wnwhfIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0g1eDuoQdhICE44VHhauGDoZyhteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqNMY2Yjf+OZo7OjzaPnpAGkG6Q1pE/kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+XCpd1l+CYTJi4mSSZkJn8mmia1ZtCm6+cHJyJnPedZJ3SnkCerp8dn4uf+qBpoNihR6G2oiailqMGo3aj5qRWpMelOKWpphqmi6b9p26n4KhSqMSpN6mpqhyqj6sCq3Wr6axcrNCtRK24ri2uoa8Wr4uwALB1sOqxYLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjRuUq5wro7urW7LrunvCG8m70VvY++Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbGw8dBx7/IPci8yTrJuco4yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/SwdNE08bUSdTL1U7V0dZV1tjXXNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLfKd+v4DbgveFE4cziU+Lb42Pj6+Rz5PzlhOYN5pbnH+ep6DLovOlG6dDqW+rl63Dr++yG7RHtnO4o7rTvQO/M8Fjw5fFy8f/yjPMZ86f0NPTC9VD13vZt9vv3ivgZ+Kj5OPnH+lf65/t3/Af8mP0p/br+S/7c/23////tAAxBZG9iZV9DTQAB/+4ADkFkb2JlAGSAAAAAAf/bAIQADAgICAkIDAkJDBELCgsRFQ8MDA8VGBMTFRMTGBEMDAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAENCwsNDg0QDg4QFA4ODhQUDg4ODhQRDAwMDAwREQwMDAwMDBEMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwM/8AAEQgAgABnAwEiAAIRAQMRAf/dAAQAB//EAT8AAAEFAQEBAQEBAAAAAAAAAAMAAQIEBQYHCAkKCwEAAQUBAQEBAQEAAAAAAAAAAQACAwQFBgcICQoLEAABBAEDAgQCBQcGCAUDDDMBAAIRAwQhEjEFQVFhEyJxgTIGFJGhsUIjJBVSwWIzNHKC0UMHJZJT8OHxY3M1FqKygyZEk1RkRcKjdDYX0lXiZfKzhMPTdePzRieUpIW0lcTU5PSltcXV5fVWZnaGlqa2xtbm9jdHV2d3h5ent8fX5/cRAAICAQIEBAMEBQYHBwYFNQEAAhEDITESBEFRYXEiEwUygZEUobFCI8FS0fAzJGLhcoKSQ1MVY3M08SUGFqKygwcmNcLSRJNUoxdkRVU2dGXi8rOEw9N14/NGlKSFtJXE1OT0pbXF1eX1VmZ2hpamtsbW5vYnN0dXZ3eHl6e3x//aAAwDAQACEQMRAD8A9NoooNFZNbCSxpJLR4In2fH/ANEz/NCWP/R6v6jfyIiSkf2fH/0TP80JfZ8f/RM/zQiJJKR/Z8f/AETP80JfZ8f/AETP80IiSSkf2fH/ANEz/NCX2fH/ANEz/NCIkkpH9nx/9Ez/ADQl9nx/9Ez/ADQiJJKR/Z8f/RM/zQl9nx/9Ez/NCIkkpr3UUBgIrYPewfRHdzUkS/6A/rs/6tqSSn//0PUcf+j1f1G/kREPH/o9X9Rv5ERJSkkkklKSSVTNzhiuY3YXufrMgANbrY7U/msSU20lUPUPcGMoe5zgXRLBoI1l1n8pWKXusYHObsd3bId/0mpKZpJJJKUkkkkpHf8AQH9dn/VtSSv+gP67P+rakkp//9H1HH/o9X9Rv5ERDx/6PV/Ub+RESUpJJJJSlm9apDqPUc4cem0FrIDnfnera0ur/suWkg5OO3IYGOcWw4OBbzI+KSnCP2dlV28VWA1FtQ30y0wdz2sr27n7vo7fet7HqrqpYytoaABo0QJjnRAf01j2OYbrYcCDq3v/AGFbAgAeGiSl0kkklKSSSSUjv+gP67P+raklf9Af12f9W1JJT//S9Rx/6PV/Ub+REQ8f+j1f1G/kRElKSSSSUpVM66hpayzIfju+kPT5I419litqtl5T6opoYbMiwexse0fy7H/R2tSU5tOTS99u/OvDWv21kRJEDU/oj+ctXFsrspaa7Da0ab3cmPHRqpVNOBkVts3vrdUd9jWucDaXb3uc1gd4q5h3W31ussYWAvcKwQQdgPsc5rklJ0kkklKSSSSUjv8AoD+uz/q2pJX/AEB/XZ/1bUklP//T9Rx/6PV/Ub+REQ8f+j1f1G/kRElKSSSSUpV8jMZRYyssfY94JArG7QRP/VKws3qIecusNnWqzdtcGe3dXu97vopKSs6myxu5lF7myRIZIkaH85WMe9mRS25gIa6RDhB9pLD/ANSsPFxXeswtoa8VkuZXBbwfb+tentt2fyti1ulf0Js/v2f+fHpKbaSSSSlJJJJKR3/QH9dn/VtSSv8AoD+uz/q2pJKf/9T1HH/o9X9Rv5ERDx/6PV/Ub+RESUpJJJJSlVycR91zLW2bNrHMI2h0h239/wBv5qtKr1K19OG6xhLS1zNRzBexrv8AopKQDpAbYLW2Bj2mQ5tbG/8AUhWsPHONjtpc7eQXEuiJ3Oc//vyq5HUA51PpNuAbYDZFbx7Id/J/e2q7Tcy5m9gcBMe5paf818JKSJJJJKUkkkkpHf8AQH9dn/VtSSv+gP67P+rakkp//9X1HH/o9X9Rv5ERDx/6PV/Ub+RESUpJJJJSlR6o7eyrEYf0l72xHIa0ix9n9narybaCQ4gSOD31SU5+UzJxm12DKsfNrGlrgyCHOg/RYFopnNa4Q4BwBkSJ1CdJSkkkklKSSSSUjv8AoD+uz/q2pJX/AEB/XZ/1bUklP//W9Rx/6PV/Ub+REVei+gUVg2MBDGgguHgifaMf/Ss/zgkpIkh/aMf/AErP84JfaMf/AErP84JKSJIf2jH/ANKz/OCX2jH/ANKz/OCSkiSH9ox/9Kz/ADgl9ox/9Kz/ADgkpIkh/aMf/Ss/zgl9ox/9Kz/OCSkiSH9ox/8ASs/zgl9ox/8ASs/zgkpV/wBAf12f9W1JDuvoLABYw+9h+kOzmpJKf//ZADhCSU0EIQAAAAAAVQAAAAEBAAAADwBBAGQAbwBiAGUAIABQAGgAbwB0AG8AcwBoAG8AcAAAABMAQQBkAG8AYgBlACAAUABoAG8AdABvAHMAaABvAHAAIABDAFMANgAAAAEAOEJJTQ+gAAAAAAD4bWFuaUlSRlIAAADsOEJJTUFuRHMAAADMAAAAEAAAAAEAAAAAAABudWxsAAAAAwAAAABBRlN0bG9uZwAAAAAAAAAARnJJblZsTHMAAAABT2JqYwAAAAEAAAAAAABudWxsAAAAAQAAAABGcklEbG9uZ0arwBYAAAAARlN0c1ZsTHMAAAABT2JqYwAAAAEAAAAAAABudWxsAAAABAAAAABGc0lEbG9uZwAAAAAAAAAAQUZybWxvbmcAAAAAAAAAAEZzRnJWbExzAAAAAWxvbmdGq8AWAAAAAExDbnRsb25nAAAAAAAAOEJJTVJvbGwAAAAIAAAAAAAAAAA4QklND6EAAAAAABxtZnJpAAAAAgAAABAAAAABAAAAAAAAAAEAAAAAOEJJTQQGAAAAAAAHAAgAAQABAQD/4Q3WaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjMtYzAxMSA2Ni4xNDU2NjEsIDIwMTIvMDIvMDYtMTQ6NTY6MjcgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKFdpbmRvd3MpIiB4bXA6Q3JlYXRlRGF0ZT0iMjAxNi0wMi0yNlQxMDozMTo0MSswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxNi0wMi0yNlQxMDozMTo0MSswODowMCIgeG1wOk1vZGlmeURhdGU9IjIwMTYtMDItMjZUMTA6MzE6NDErMDg6MDAiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RERFODI4MjEzMERDRTUxMUE3NzdCRjVEMkU2OEVGMTUiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RENFODI4MjEzMERDRTUxMUE3NzdCRjVEMkU2OEVGMTUiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpEQ0U4MjgyMTMwRENFNTExQTc3N0JGNUQyRTY4RUYxNSIgZGM6Zm9ybWF0PSJpbWFnZS9qcGVnIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiBwaG90b3Nob3A6SUNDUHJvZmlsZT0ic1JHQiBJRUM2MTk2Ni0yLjEiPiA8eG1wTU06SGlzdG9yeT4gPHJkZjpTZXE+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJjcmVhdGVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOkRDRTgyODIxMzBEQ0U1MTFBNzc3QkY1RDJFNjhFRjE1IiBzdEV2dDp3aGVuPSIyMDE2LTAyLTI2VDEwOjMxOjQxKzA4OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ1M2IChXaW5kb3dzKSIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6RERFODI4MjEzMERDRTUxMUE3NzdCRjVEMkU2OEVGMTUiIHN0RXZ0OndoZW49IjIwMTYtMDItMjZUMTA6MzE6NDErMDg6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKFdpbmRvd3MpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDwvcmRmOlNlcT4gPC94bXBNTTpIaXN0b3J5PiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg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"; + + /** + * 保存图片或指纹信息是将base64转换成为二进制数组 + */ + public static ByteArrayInputStream base64ToString(String imgbase64) { + String base64; + if (StringUtils.isBlank(imgbase64)) { + base64 = imgString; + } else { + base64 = imgbase64;// 这是base64字符串 + } + byte[] imageString; + imageString = Base64.decodeBase64(base64); + return new ByteArrayInputStream(imageString); + } + + + /** + * 将字符串转换成base64 + * base64编码 + * + * @param instr 待编码数据 + * @return base64编码后数据 + */ + public static String StringToBase64(String instr) { + byte[] result = Base64.encodeBase64(instr.getBytes()); + return new String(result); + } + + /** + * 将base64转换成字符串 + * base64编码 + * + * @param instr base64编码后数据 + * @return 解码后的数据 + */ + public static String Base64ToString(String instr) { + byte[] result = Base64.decodeBase64(instr.getBytes()); + return new String(result); + } + + public static String byteToStr(byte[] bytes) { + return Base64.encodeBase64String(bytes); + } + + /** + * 通过网络地址获取图片转成base64 + * + * @param imgURL 图片网络地址 + * @return 返回base64编码的图片内容 + */ + public static String imageBase64FromUrl(String imgURL) { + if (StringUtils.isNotEmpty(imgURL)) { + String result = null; + try(ByteArrayOutputStream outputStream = new ByteArrayOutputStream()){ + HttpRequest request = HttpUtil.createGet(imgURL); + request.setConnectionTimeout(5000) + .setReadTimeout(20000); + BufferedImage image = ImageIO.read( + request.execute().bodyStream()); + ImageIO.write(image, "jpg", outputStream); + result = java.util.Base64.getEncoder().encodeToString(outputStream.toByteArray()); + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } else { + return ""; + } + } + + /** + * 文件转base64 + * + * @param fileUrl + * @return + */ + public static String FileBase64FromUrl(String fileUrl) { + if (StringUtils.isNotEmpty(fileUrl)) { + String result = null; + HttpRequest request = HttpUtil.createGet(fileUrl); + request.setConnectionTimeout(5000).setReadTimeout(20000); + try(InputStream inputStream = request.execute().bodyStream()) { + byte[] bytes = IOUtils.toByteArray(inputStream); + result = java.util.Base64.getEncoder().encodeToString(bytes); + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } else { + return ""; + } + } + + /** + * 判断是否是base64 + * @param str base64字符串 + * @return 是否base64 + */ + public static boolean isBase64(String str){ + if(StringUtils.isNotBlank(str)) + return Base64.isBase64(str); + return false; + } + + /** + * 字符串到字节数组 + * @param str base64字符串 + * @return 字节数组 + */ + public static byte[] strToByte(String str){ + return Base64.decodeBase64(str); + } +} diff --git a/server/src/main/java/com/aisino/iles/common/util/BeanUtils.java b/server/src/main/java/com/aisino/iles/common/util/BeanUtils.java new file mode 100644 index 0000000..a6771ae --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/util/BeanUtils.java @@ -0,0 +1,194 @@ +package com.aisino.iles.common.util; + +import org.apache.commons.beanutils.ConvertUtils; +import org.apache.commons.beanutils.PropertyUtils; +import org.apache.commons.beanutils.converters.DateConverter; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.ArrayUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.FatalBeanException; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; + +public abstract class BeanUtils extends org.springframework.beans.BeanUtils { + + /** + * 功能 : 只复制source对象的非空属性到target对象上 + * */ + public static void copyNoNullProperties(Object source, Object target) throws BeansException { + copyNoNullProperties(source,target,null, (String[]) null); + } + + public static void copyNoNullProperties(Object source, Object target,String... ignoreProperties){ + copyNoNullProperties(source,target,null,ignoreProperties); + } + + private static void copyNoNullProperties(Object source, Object target, Class editable, String... ignoreProperties) throws BeansException { + Assert.notNull(source, "Source must not be null"); + Assert.notNull(target, "Target must not be null"); + Class actualEditable = target.getClass(); + if (editable != null) { + if (!editable.isInstance(target)) { + throw new IllegalArgumentException("Target class [" + target.getClass().getName() + "] not assignable to Editable class [" + editable.getName() + "]"); + } + + actualEditable = editable; + } + + PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable); + List ignoreList = ignoreProperties != null ? Arrays.asList(ignoreProperties) : null; + + for (PropertyDescriptor targetPd : targetPds) { + Method writeMethod = targetPd.getWriteMethod(); + if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) { + PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName()); + if (sourcePd != null) { + Method readMethod = sourcePd.getReadMethod(); + if (readMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) { + try { + if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { + readMethod.setAccessible(true); + } + + Object value = readMethod.invoke(source); + if (value != null) { + if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { + writeMethod.setAccessible(true); + } + + writeMethod.invoke(target, value); + } + } catch (Throwable var15) { + throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", var15); + } + } + } + } + } + + } + + + /** + * map转为bean + * @param map + * @param clazz bean类型 + * @param + * @return + */ + public static T mapToBean(Map map, Class clazz) { + T bean = null; + try { + bean = clazz.newInstance(); + ConvertUtils.register(new DateConverter(null), Date.class); + org.apache.commons.beanutils.BeanUtils.populate(bean, map); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return bean; + } + + /** + * map中的key大写转为小写 + * @param orgMap + * @return + */ + public static Map mapKeyToLower(Map orgMap){ + Map resultMap = new HashMap<>(); + if (orgMap == null || orgMap.isEmpty()) { + return resultMap; + } + Set keySet = orgMap.keySet(); + for (String key : keySet) { + String newKey = key.toLowerCase(); +// newKey = newKey.replace("_", ""); + resultMap.put(newKey, orgMap.get(key)); + } + return resultMap; + } + + /** + * + * @param mapList 源 + * @param t 实体类型 + * @param + * @return + */ + public static List transformList(List> mapList,Class t){ + List list=new ArrayList<>(); + if (CollectionUtils.isNotEmpty(mapList)) { + mapList.forEach(o->{ + if (MapUtils.isNotEmpty(o)) { + Map map=mapKeyToLower(o); + T rst = mapToBean(map,t); + if (rst!=null){ + list.add(rst); + } + } + }); + } + return list; + } + + /** + * map转为List + * @param map + * @param t + * @param + * @return + */ + public static List transformMapToList(Map map, Class t) { + List list = new ArrayList<>(); + if (MapUtils.isNotEmpty(map)) { + map = mapKeyToLower(map); + T rst = mapToBean(map, t); + if (rst != null) { + list.add(rst); + } + } + return list; + } + + /** + * 拷贝对象属性 (只拷贝指定的属性) + * + * @param source 源 + * @param target 目标 + * @param targetProps 要拷贝的属性 + */ + public static void copyPropertiesWithProp(Object source, Object target, String... targetProps) { + Assert.notNull(source, "Source must not be null"); + Assert.notNull(target, "Target must not be null"); + + PropertyDescriptor[] propertyDescriptors = PropertyUtils.getPropertyDescriptors(target); + for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { + String propertyName = propertyDescriptor.getName(); + Method readMethod = propertyDescriptor.getReadMethod(); + Method writeMethod = propertyDescriptor.getWriteMethod(); + if (!ArrayUtils.contains(targetProps, propertyName) || readMethod == null || writeMethod == null) { + continue; + } + try { + Object sourceValue = readMethod.invoke(source); + writeMethod.invoke(target, sourceValue); + } catch (IllegalAccessException e) { + throw new RuntimeException(e.getMessage(), e); + } catch (IllegalArgumentException e) { + throw new RuntimeException(e.getMessage(), e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + } +} diff --git a/server/src/main/java/com/aisino/iles/common/util/BufferedBlockingQueue.java b/server/src/main/java/com/aisino/iles/common/util/BufferedBlockingQueue.java new file mode 100644 index 0000000..e23ccfd --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/util/BufferedBlockingQueue.java @@ -0,0 +1,42 @@ +package com.aisino.iles.common.util; + +import lombok.extern.slf4j.Slf4j; + +import jakarta.validation.constraints.NotNull; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.function.Consumer; + +@Slf4j +public class BufferedBlockingQueue { + private final Consumer> consumer; + private final LinkedBlockingQueue queue; + + public BufferedBlockingQueue(int capacity, Consumer> consumer) { + queue = new LinkedBlockingQueue<>(capacity); + this.consumer = consumer; + } + + public void put(@NotNull T t) { + boolean success; + do { + success=queue.offer(t); + if (queue.remainingCapacity() <= 0) { + log.trace("队列已满,正在排空..."); + this.flush(); + } + }while (!success); + } + + public void flush() { + List all = new ArrayList<>(); + queue.drainTo(all); + if (!all.isEmpty()) { + consumer.accept(all); + log.trace("队列已排空。"); + } else { + log.trace("没有元素用于排空。"); + } + } +} diff --git a/server/src/main/java/com/aisino/iles/common/util/CaptchaUtils.java b/server/src/main/java/com/aisino/iles/common/util/CaptchaUtils.java new file mode 100644 index 0000000..cb0a91d --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/util/CaptchaUtils.java @@ -0,0 +1,29 @@ +package com.aisino.iles.common.util; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +@Component +public class CaptchaUtils { + @Autowired + private CacheManager cacheManager; + + public Boolean checkCaptchaCode(String captchaKey,String captchaCode) { + assert !StringUtils.isEmpty(captchaKey); + assert !StringUtils.isEmpty(captchaCode); + + final String code = cacheManager.getCache("captcha").get(captchaKey, String.class); + if(captchaCode.equals(code)){ + cacheManager.getCache("captcha").evict(captchaKey); + return true; + } + else + return false; + } + + public void putCaptchaCode(String key, String value) { + cacheManager.getCache("captcha").put(key, value); + } +} diff --git a/server/src/main/java/com/aisino/iles/common/util/Constants.java b/server/src/main/java/com/aisino/iles/common/util/Constants.java new file mode 100644 index 0000000..1229328 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/util/Constants.java @@ -0,0 +1,3342 @@ +package com.aisino.iles.common.util; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * 通用常量表 + */ +public class Constants { + /** + * 业务返回代码 + */ + public static final class BusinessResultCodes { + /** + * 微信认证数据 + */ + public static final int WECHAT_OPENID = 40001; + /** + * 短认证令牌 + */ + public static final int SHOT_TOKEN = 40002; + + /** + * 微信union id + */ + public static final int WECHAT_UNIONID = 40003; + /** + * 微信用户表id + */ + public static final int WXID = 40004; + + /** + * 人员忘证核验结果公安部限制 + */ + public static final int PERSONNEL_FORGOT_IDENTIFY_MINISTRY_PUBLIC_SECURITY_LIMIT = 50001; + /** + * 人员忘证核验结果不一致, (一致为0) + */ + public static final int PERSONNEL_FORGOT_IDENTIFY_NOT_AGREEMENT = 50000; + } + + /** + * 异常代码 + */ + public static final class Exceptions { + /** + * 一般默认异常代码 + */ + public static final int default_error = 1; + /** + * 令牌错误 + */ + public static final int token_error = 3306; + /** + * 令牌错误:验证不合格或过期 + */ + public static final int token_expired_error = 330601; + /** + * 令牌错误:不具备操作资源 + */ + public static final int token_no_resource_error = 330602; + /** + * 令牌错误:在其他设备登录 + */ + public static final int token_login_in_other_device_error = 330603; + /** + * 登录错误 + */ + public static final int login_error = 3301; + /** + * 登录密码错误 + */ + public static final int login_password_error = 330101; + /** + * 登录用户已经删除错误 + */ + public static final int login_deleted_error = 330102; + /** + * 登录错误:用户不存在 + */ + public static final int login_user_not_exist_error = 330103; + /** + * 登录错误:用户状态异常 + */ + public static final int login_user_type_abnormal_error = 330104; + /** + * 登录错误:用户状态锁定 + */ + public static final int login_user_locked_error = 330105; + /** + * 登录错误: 验证码验证失败 + */ + public static final int login_captcha_code_error = 330106; + /** + * 注册重复的用户名 + */ + public static final int register_duplicate_username = 330107; + /** + * 未知的第三方登录类型 + */ + public static final int login_error_code_unknown_provider = 330108; + /** + * 参数错误 + */ + public static final int arg_error = 3302; + /** + * 服务器错误 + */ + public static final int server_error = 500; + /** + * 用户没有关联机构信息 + */ + public static final int user_no_relate_jurisdiciton_error = 2001; + /** + * 机构信息不存在 + */ + public static final int jurisdiction_not_found_error = 2002; + /** + * 吉大正元数字证书登录错误 + */ + public static final int jitd_sign_login = 3311; + /** + * 数据打包解包错误 + */ + public static final int data_package_error = 1000; + /** + * 数据打包解包错误: 解包失败 + */ + public static final int data_package_extract_error = 10001; + /** + * 文件上传错误 + */ + public static final int upload_file_error = 5000; + /** + * 企业数据上报错误 + */ + public static final int upload_interface_error = 600099; + /** + * 企业数据上报错误 加解密失败 + */ + public static final int getUpload_interface_error_decrypt_error = 600001; + /** + * 企业数据上报错误 未找到对应的申请信息 + */ + public static final int getUpload_interface_error_no_error = 600002; + /** + * 企业数据上报错误 系统对接授权已过期 + */ + public static final int getUpload_interface_error_expired_error = 600003; + /** + * 企业数据上报错误 需要重新刷新token + */ + public static final int getUpload_interface_error_refresh_token_error = 600004; + /** + * 企业数据上报错误 token刷新失败因为当前有其他同样的信息在执行 + */ + public static final int getUpload_interface_error_locking_error = 600005; + /** + * 消息服务发送错误 + */ + public static final int message_send_error = 4000; + /** + * 消息服务错误: 发送到kafka发生错误 + */ + public static final int message_send_kafka_error = 4001; + /** + * 数据已经存在 + */ + public static final int data_exist_error = 1000001; + /** + * 数据不存在 + */ + public static final int data_not_exist_error = 1000002; + + /** + * 数据加密解密错误 + */ + public static final int data_encrypt_decrypt_error = 1000003; + /** + * 数据加密解密,密钥不存在 + */ + public static final int data_encrypt_decrypt_not_found_key = 1000004; + } + + /** + * 生成器名称 + */ + public static final class Genernators { + public static final String gen_menu_id = "gen_menu_id"; + public static final String gen_organization_id = "gen_org_id"; + public static final String gen_permission_id = "gen_permission_id"; + public static final String gen_resource_id = "gen_resource_id"; + public static final String gen_role_id = "gen_role_id"; + public static final String gen_user_id = "gen_user_id"; + public static final String gen_dict_id = "gen_dict_id"; + public static final String gen_dict_item_id = "gen_dict_item_id"; + public static final String gen_jurisdiction_id = "gen_jurisdiction_id"; + public static final String gen_usertype_id = "gen_usertype_id"; + public static final String gen_loginlog_id = "gen_loginlog_id"; + + public static final String gen_hotel_id = "gen_hotel_id";//旅馆 + public static final String gen_zddq_id = "gen_zddq_id";//重点地区 + public static final String gen_domestic_passenger_id = "gen_domestic_passenger_id"; + public static final String gen_foreign_passenger_id = "gen_foreign_passenger_id";//境外旅客 + + public static final String gen_personnel_control_id = "gen_personnel_control_id";//布控 + public static final String gen_domesticpassenger_id = "gen_domesticpassenger_id";//国内旅客查询 + + public static final String gen_alarm_information_id = "gen_alarm_information_id"; // 报警信息id + public static final String gen_domesticpassenger_photo_id = "gen_domesticpassenger_photo_id"; // 国内旅客照片id + public static final String gen_operate_log_id = "gen_operate_log_id"; // 操作日志id生成器名称 + + public static final String gen_notification_id = "gen_notification_id"; // 协查通报id生成器名称 + public static final String gen_annex_id = "gen_annex_id"; // 协查通报 附件 id生成器名称 + public static final String gen_receiving_unit_id = "gen_receiving_unit_id"; // 协查通报 接收单位 id生成器名称 + public static final String gen_reply_id = "gen_receiving_unit_id"; // 协查通报 回复情况 id生成器名称 + + public static final String gen_qyjbxx_id = "gen_qyjbxx_id"; // 企业基本信息id生成器 + public static final String gen_qyzjb_id = "gen_qyzjb_id"; // 企业证件表id生成器 + public static final String gen_qyffl_id = "gen_qyffl_id"; // 企业副分类id生成器 + public static final String gen_wgtzrxx_id = "gen_wgtzrxx_id"; // 外国投资人信息ID生成器 + public static final String gen_bafwht_id = "gen_bafwht_id"; // 保安服务合同id生成器 + public static final String gen_bldsjksb_id = "gen_bldsjksb_id"; // 闭路电视监控id生成器 + public static final String gen_aqjcsb_id = "gen_aqjcsb_id"; // 安全检查设备id生成器 + public static final String gen_baryxx_id = "gen_baryxx_id"; // 保安人员信息id生成器 + public static final String gen_bwryxx_id = "gen_bwryxx_id"; // 保卫人员信息id生成器 + public static final String gen_pmt_id = "gen_pmt_id"; // 平面图id生成器 + public static final String gen_zzcl_id = "gen_zzcl_id"; // 纸制材料id生成器 + public static final String gen_qyxxczrz_id = "gen_qyxxczrz_id"; // 纸制材料id生成器 + + public static final String gen_order_id = "t_order_id"; + public static final String gen_order_earlyguest_id = "T_order_earlyguest_id"; + public static final String gen_order_guest_id = "T_order_guest_id"; + public static final String gen_house_id = "T_house_id"; + public static final String gen_house_manager_id = "T_house_manager_id"; + public static final String gen_house_photo_id = "T_house_photo_id"; + + /** + * 地区分析id生成器 + */ + public static final String gen_analysis_regional_id = "gen_analysis_regional_id"; + + /** + * 保安业务 + */ + public static final String gen_security_bajgll_id = "gen_bajgllid";// 保安监管力量ID生成器名称 + public static final String gen_security_bajgjgxx_id = "gen_bajgjgxx";// 保安监管机构信息ID生成器名称 + public static final String gen_security_baxhxx_id = "gen_baxhxx";// 保安协会信息ID生成器 + public static final String gen_security_bacyxzgzxx_id = "gen_bacyxzgzxx";// 保安参与协作公安工作情况信息 + public static final String gen_security_baqtxx_id = "gen_baqtxx";// 保安其它信息 + public static final String gen_security_tsxx_id = "gen_tsxx";// 保安投诉信息 + public static final String gen_security_bafwfgs_id = "gen_bafwfgsjbxx_id";// + public static final String gen_security_bafwfgslsxx_id = "gen_bafwfgslsxx_id";// + public static final String gen_security_pjxx_id = "gen_pjxx_id";//保安服务分公司id生成器名称 + public static final String gen_security_slsqjbxxid = "gen_slsqjbxxid";// 保安公司受理基本信息ID生成器名称 + public static final String gen_security_pxjgjbxx_id = "gen_pxjgjbxxid";// 培训机构单位ID生成器名称 + public static final String gen_security_pxjgsqdzcl_id = "gen_pxjgsqdzclid";// 培训机构电子材料ID生成器名称 + public static final String gen_security_scdm_id = "gen_scdmid"; + public static final String gen_security_zhspxxpxjg_id = "gen_zhspxxid"; + public static final String gen_security_pxjgjbxx_lsxx_id = "gen_pxjgjblsxxid"; + public static final String gen_security_baybmjbxx_id = "gen_baybmjbxxid"; + public static final String gen_security_basxrybdrzb_id = "gen_sxrybdrzid"; + public static final String gen_security_ywwffzjlzm_fj_id = "gen_ywwffzjlzm_fjid"; + public static final String gen_security_zhaopian_id = "gen_zhaopian_zpid"; + public static final String gen_security_baycyjl_id = "gen_baycyjl_id";// 保安员从业记录 + + /** + * 操作日志大文本信息主键生成器 + */ + public static final String gen_operate_log_text_id = "gen_operate_log_text_id"; + /** + * uuid + */ + public static final String gen_uuid = "uuid+"; + + public static final String gen_domestic_passenger_untimely_incomplete_data_id = "gen_domestic_passenger_untimely_incomplete_data_id"; + /** + * 通用序列 + */ + public static final String gen_sequence = "sequence"; + /** + * ulid 一种时间顺序随机字符串 一般26位 + */ + public static final String gen_ulid = "ulid"; + + public static final String gen_house_photo_send_id = "gen_house_photo_send_id"; + public static final String gen_house_send_id = "gen_house_send_id"; + public static final String gen_practitioner_send_id = "gen_practitioner_send_id"; + } + + /** + * 序列名称 + */ + public static final class Sequences { + public static final String seq_menu_id = "seq_menu_id"; + public static final String seq_organization_id = "seq_org_id"; + public static final String seq_permission_id = "seq_permission_id"; + public static final String seq_resource_id = "seq_resource_id"; + public static final String seq_role_id = "seq_role_id"; + public static final String seq_user_id = "seq_user_id"; + public static final String seq_dict_id = "seq_dict_id"; + public static final String seq_dict_item_id = "seq_dict_item_id"; + public static final String seq_jurisdiction_id = "seq_jurisdiction_id"; + public static final String seq_usertype_id = "seq_usertype_id"; + public static final String seq_loginlog_id = "seq_loginlog_id"; + public static final String seq_hotel_id = "qiyebianma"; + + public static final String seq_domestic_passenger_id = "SEQ_GNLK"; + public static final String seq_foreign_passenger_id = "SEQ_JWLK"; + public static final String seq_zddq_id = "ZDDQID";//重点地区序列名 + public static final String seq_personnel_control_id = "bkryid";//布控序列名 + + public static final String seq_alarm_information_id = "BJXXID"; // 报警信息id序列 + public static final String seq_operate_log_id = "seq_operate_log_id"; // 操作日志ID序列 + + public static final String seq_notification_id = "Tbid"; // 协查通报ID序列 + public static final String seq_annex_id = "Xctbfjid"; // 协查通报 附件 ID序列 + public static final String seq_receiving_unit_id = "Jsdwid"; // 协查通报 接收单位 ID序列 + public static final String seq_reply_id = "hfid"; // 协查通报 回复情况 ID序列 + + public static final String seq_qyjbxx_id = "qyid"; // 企业基本信息表ID序列 + public static final String seq_qyzjb_id = "zjid"; //企业证件表id序列 + public static final String seq_qyffl_id = "qyflid"; // 企业副分类id序列 + public static final String seq_wgtzrxx_id = "wgtzrxxid"; // 外国投资人信息id序列 + public static final String seq_bafwht_id = "bafwhtid"; // 保安服务合同id序列 + public static final String seq_bldsjksb_id = "bldsjksbid"; // 保安服务合同id序列 + public static final String seq_aqjcsb_id = "anjcsbid"; // 安全检查设备id序列 + public static final String seq_baryxx_id = "baryid"; // 保安人员信息id序列 + public static final String seq_bwryxx_id = "bwryxxid"; // 保卫人员信息id序列 + public static final String seq_pmt_id = "pmtid"; // 平面图id序列 + public static final String seq_zzcl_id = "zclid"; // 纸制材料id序列 + public static final String seq_qyxxczrz_id = "qyxxczrzid"; // 企业信息操作日志id序列 + + /** + * 保安业务 + */ + public static final String seq_security_bajgll_id = "bajgllid";// 保安监管力量ID序列 + public static final String seq_security_bajgjgxx_id = "bajgjgxxid";// 保安监管机构信息ID序列 + public static final String seq_security_baxhxx_id = "baxhxxid";// 保安协会信息ID序列 + public static final String seq_security_bacyxzgzxx_id = "bacyxzgzxxid";// 保安参与协作工作信息ID序列 + public static final String seq_security_baqtxx_id = "mansyshbba.baqtxxid";// 保安其它信息ID序列 + public static final String seq_security_tsxx_id = "tsid";// 投诉信息ID序列 + public static final String getSeq_security_bafwfgsjbxx_id = "Bafwfgsjbxxid";//保安服务分公司基本信息id序列名 + public static final String getSeq_security_bafwfgslsxx_id = "bafwfgslsxxid";//保安服务分公司历史信息id序列名 + public static final String seq_security_pjxx_id = "pjxxid";// 保安公司评价信息ID序列 + public static final String seq_security_pxjgjbxx_id = "Pxjgjbxxid";//培训机构单位id序列名 + public static final String seq_security_slsqjbxxid = "slsqjbxxid";// 保安公司受理基本信息ID序列 + public static final String seq_security_pxjgsqdzcl_id = "Pxjgsqdzclid";// 培训机构电子材料ID序列 + public static final String seq_security_scdm_id = "Scdmid_ba"; + public static final String seq_security_zhspxxpxjg_id = "zhspxx_pxjgid"; + public static final String seq_security_pxjgjbxx_lsxx_id = "Pxjgjblsxxid"; + public static final String seq_security_ywwffzjlzm_fj_id = "ywwffzjlzm_fjid"; + public static final String seq_security_zhaopian_id = "Zpid"; + public static final String seq_security_qyjbxx_id = "Qyid"; //企业基本信息id + public static final String seq_security_baycyjl_id = "Baycyjlid"; //保安员从业记录id + + + public static final String seq_order_id = "t_order_id"; + public static final String seq_order_earlyguest_id = "T_order_earlyguest_id"; + public static final String seq_order_guest_id = "T_order_guest_id"; + public static final String seq_house_id = "T_house_id"; + public static final String seq_house_manager_id = "T_house_manager_id"; + public static final String seq_house_photo_id = "T_house_photo_id"; + public static final String seq_security_baybmjbxx_id = "Baybmid"; + public static final String seq_security_basxrybdrzb_id = "sxrybdrzid"; + /** + * 地区分析ID序列 + */ + public static final String seq_analysis_regional_id = "Analysis_regionalid_Seq1"; + + /** + * 操作日志大文本信息序列 + */ + public static final String seq_operate_log_text_id = "seq_operatelt_id"; + public static final String seq_domestic_passenger_untimely_incomplete_data_id = "GNLKbjssbwzsID"; + /** + * 可疑情况ID序列 + */ + public static final String seq_suspicious_situation_id = "kyqkid"; + public static final String seq_house_photo_send_id = "housePhotoSendId"; + public static final String seq_house_send_id = "houseSendId"; + public static final String seq_practitioner_send_id = "practitionerSendId"; + } + + /** + * 索引 + */ + public static final class Indexes { + public static final String idx_dictItem_value = "idx_dict_item_value"; + } + + /** + * 字典代码常量 + */ + public static final class Dicts { + /** + * 用户类别级别 + */ + public static final String userTypeLevel = "dm_user_type_level"; + /** + * 非标准住宿露营基地子分类 + */ + public static final String lyjdCategory = "dm_lyjd_zfl"; + /** + * 系统操作日志状态 + */ + public static final String systemOperateLogStatus = "dm_operate_log_status"; + /** + * ip类型 + */ + public static final String ipType = "dm_ip_type"; + /** + * 系统操作日志类型 + */ + public static final String systemOperateLogType = "dm_operate_log_type"; + /** + * 通用业务车辆照片类型 + */ + public static final String businessCarPhotoType = "dm_car_photo_type"; + /** + * 通用业务车辆号牌类型 + */ + public static final String businessCarNumberPlateType = "dm_busi_car_num_plate_type"; + /** + * 委托寄卖物品状态 + */ + public static final String consignmentGoodsType = "dm_wplx"; + /** + * 车俩盗抢布控数据类型 + */ + public static final String vehicleRobberyType = "dm_cldqbksjlx"; + /** + * 程序包类别 + */ + public static final String packageType = "dm_cxblb"; + /** + * 委托寄卖物品状态 + */ + public static final String consignmentGoodsStatus = "dm_wpzt(wtjm)"; + /** + * 通用业务人员类型 + */ + public static final String businessPersonType = "dm_busi_person_type"; + /** + * 通用是否 + */ + public static final String whether = "dm_tydm"; + /** + * 数据类别 + */ + public static final String dataCategory = "dm_data_category"; + /** + * 用户类型 + */ + public static final String userTypes = "dm_usertype"; + /** + * 用户状态 + */ + public static final String userStatus = "dm_user_status"; + /** + * 业务类别 + */ + public static final String businessCategory = "dm_ywlb"; + /** + * 旅馆星级 + */ + public static final String hotelStar = "dm_qyxj(lgy)"; + + /** + * 营业状态 + */ + public static final String businessStatus = "dm_yyzt(ylcs)"; + + /** + * 旅馆等级 + */ + public static final String hotelLevel = "dm_qydj(lgy)"; + + /** + * 企业性质 + */ + public static final String hotelQyxz = "dm_qyxz(lgy)"; + + /** + * 拟入住人员类型 + */ + public static final String orderGuestType = "dm_nrzrylx(wyf)"; + + /** + * 网络状况 + */ + public static final String hotelWlzk = "dm_lgy_wlzk"; + + /** + * 字典类型 + */ + public static final String dictTypes = "dm_dict_type"; + /** + * 性别 + */ + public static final String dictSex = "dm_xb"; + /** + * 政治面貌 + */ + public static final String zzmm = "dm_zzmm_nbdw"; + /** + * 国籍 + */ + public static final String nationality = "dm_gj(lgy)"; + /** + * 民族 + */ + public static final String nation = "dm_mz"; + /** + * 口音 + */ + public static final String accent = "dm_ky"; + /** + * 体征 + */ + public static final String sign = "dm_tmtz"; + /** + * 体态 + */ + public static final String posture = "dm_tmtz"; + /** + * 案件类别 + */ + public static final String caseCategory = "dm_ajlx"; + /** + * 行政区划 籍贯 + */ + public static final String nativePlace = "dm_xzqh"; + /** + * 布控范围 + */ + public static final String scopeControl = "dm_bkfw"; + /** + * 检查事项 + */ + public static final String lgyJcsx = "dm_jcsx_lgy"; + /** + * 旅馆业日常检查是否发现问题 + */ + public static final String lgyFxwt = "dm_fxwt_lgy"; + /** + * 旅馆业日常检查检查方式 + */ + public static final String lgyJcfs = "dm_jcfs"; + /** + * 证件类型 + */ + public static final String certificateType = "dm_zjlx"; + /** + * 境外证件类型 + */ + public static final String abroadCertificateType = "dm_zjlb"; + /** + * 签证种类 + */ + public static final String visaType = "dm_qzzl(lgy)"; + + /** + * 无效原因(报警信息) + */ + public static final String invalidReason = "dm_wxyy"; + /** + * 报警类型 + */ + public static final String alarmType = "dm_bjlx"; + /** + * 警情状态 + */ + public static final String alarmState = "dm_jqzt"; + /** + * 预警类型 + */ + public static final String earlyAlarmType = "dm_yujinglx"; + /** + * 预警状态 + */ + public static final String earlyAlarmState = "dm_yujingzt"; + /** + * 预警处警结果 + */ + public static final String earlyPoliceActionResult = "dm_yujingcjjg"; + /** + * 报警反馈类型/未抓获原因 + */ + public static final String feedbackType = "dm_cjfklb"; + /** + * 入境口岸(旅馆业) + */ + public static final String portOfEntry = "dm_rjka(lgy)"; + /** + * 签发机关(旅馆业) + */ + public static final String issueOffice = "dm_qfjg"; + /** + * 出警结果 + */ + public static final String policeActionResult = "dm_cjjg"; + /** + * 注销标志 + */ + public static final String zxbz = "dm_zxbz"; + /** + * 特行许可证状态 + */ + public static final String tzhyxkzzt = "dm_tzhyxkzzt"; + /** + * 职务 + */ + public static final String cyryzw = "dm_zw_lgy"; + + + /** + * 网约房数据状态 + */ + public static final String wyfsjzt = "dm_sjzt(wyf)"; + + /** + * 网约房经营者类型 + */ + public static final String wyfjyzlx = "dm_jyzlx(wyf)"; + + /** + * 网约房核查状态 + */ + public static final String wyfhczt = "dm_hczt(wyf)"; + + /** + * 网约房数据来源(网约房) + */ + public static final String wyfsjly = "dm_sjly(wyf)"; + /** + * 房屋来源(网约房) + */ + public static final String wyffwlx = "dm_fwlx(wyf)"; + + /** + * 经营状态(网约房) + */ + public static final String wyfjyzt = "dm_jyzt(wyf)"; + + /** + * 非标准住宿实际入住人像比对结果 + */ + public static final String rxbdjg = "dm_rxbdjg"; + + /** + * 非标准住宿网约房主企业分类 + */ + public static final String qyzflfbzzs = "dm_qyzfl_fbzzs"; + + /** + * 是否有锁(网约房) + */ + public static final String wyfsfys = "dm_sfys(wyf)"; + + /** + * 房屋来源(网约房) + */ + public static final String wyffwly = "dm_fwly(wyf)"; + + /** + * 机构机关类型 + */ + public static final String jgjglx = "dm_jgjglx_ba"; + + /** + * 布尔值 + */ + public static final String bez = "dm_bez"; + + /** + * 机构状态(保安) + */ + public static final String jgzt = "dm_jgzt_ba"; + + /** + * 协助工作类型(保安) + */ + public static final String xzgzlx = "dm_xzgzlx_ba"; + + /** + * 保安投诉处理结果 + */ + public static final String tscljg = "dm_cljg_ba"; + + /** + * 企业主分类(保安) + */ + public static final String qyzfl = "dm_qyzfl_ba"; + + /** + * 评价结果(保安) + */ + public static final String pjjg = "dm_pjjg"; + /** + * 服务对象类型(保安) + */ + public static final String fwdxlx = "dm_fwdxlx_ba"; + + public static final String bagsfwdxhylx = "dm_bagsfwdxhylx_ba"; + /** + * 是否 + */ + public static final String sf = "dm_sfxf_ks"; + /** + * 经济类型 + */ + public static final String jjlx = "dm_jjlx"; + /** + * 投诉处理结果(保安) + */ + public static final String cljg = "dm_cljg"; + /** + * 异常核查状态(保安) + */ + public static final String ychczt = "dm_ychczt"; + + /* 报考等级(保安) */ + public static final String bkdj = "dm_bkdj_ba"; + /* 自行招用保安核查结果(保安) */ + public static final String zxzybahcjg = "dm_bkdj_ba"; + /* 保安员证件状态(保安) */ + public static final String babayzjzt = "dm_zjzt_ba"; + + /* 在职状态(保安) */ + public static final String zyzt = "dm_zyzt"; + /* 派遣查看状态*/ + public static final String ckzt = "dm_ckzt_ba"; + /* 车辆状态(公交) */ + public static final String clzt_kygj = "dm_clzt(kygj)"; + /* 车辆性质(公交) */ + public static final String clxz_kygj = "dm_clxz(kygj)"; + /* 经营类别(公交) */ + public static final String jylb_kygj = "dm_jylb(kygj)"; + /* 是否有车危仪(公交)*/ + public static final String sfycwy_kygj = "dm_sfycwy(kygj)"; + /* 是否有车载人像采集 */ + public static final String sfyczrycj_kygj = "dm_sfyczrycj(kygj)"; + /* 是否有车载监控 */ + public static final String sfyczjk_kygj = "dm_sfyczjk(kygj)"; + /* 是否删除 */ + public static final String is_delete = "dm_is_delete"; + /* 运行情况 */ + public static final String yxqk_kygj = "dm_yxqk(kygj)"; + /* 是否判断代码(1:是,0:否) */ + public static final String yes_or_no = "dm_yes_or_no"; + /* 办理原因 */ + public static final String blyy_kygj = "dm_blyydm(kygj)"; + /* 办理用途 */ + public static final String blyt_kygj = "dm_blytdm(kygj)"; + /* 文档类别 */ + public static final String wdlb_kygj = "dm_wdlb(kygj)"; + /* 联合机构类型 */ + public static final String lhjglx_kygj = "dm_lhjglx(kygj)"; + /* 乘车类型 */ + public static final String cclx_kygj = "dm_cclx(kygj)"; + + /* 排查类型 */ + public static final String pclx_kygj = "dm_pclx(kygj)"; + + /* 印刷 可疑印刷品类别 */ + public static final String kylbdm_ysy = "dm_kyqklb(ysy)"; + + public static final String qycflb_jxy = "qycflb(jxy)";// 公共功能--企业惩罚类别 印章业、机修业 + + public static final String zt_znzj_zyglxt = "dm_zt_xnzj(zyglxt)"; // 资源管理系统--虚拟主机运行状态 + public static final String zt_wlzy_zyglxt = "dm_zyzt_wlzy(zyglxt)"; // 资源管理系统--物理资源状态, 0:机架 1:网元设备 2:主机设备 3:链路 + public static final String zt_wlzyyxzt_zyglxt = "dm_yxzt_wlzy(zyglxt)"; // 资源管理系统--物理资源运行状态状态, 0:运行中 1:停止 区别于虚拟机的状态 + public static final String dm_yyfl_zyglxt = "dm_yyfl(zyglxt)"; // 资源管理系统--应用分类, 0:www应用,1:FTP应用,2:TELENT应用,3:邮件应用,4:业务应用,5:其他应用 + public static final String dm_sfzydq = "dm_sfzydq"; // 身份证阅读器 + public static final String dm_zzsbsb = "dm_zzsbsb"; // 证照识别设备 + public static final String dm_wjhysb = "dm_wjhysb"; // 外接核验设备 + public static final String dm_xsdw = "dm_xsdw"; // 销售单位 + public static final String dm_djlx = "dm_djlx"; // 登记类型 + + /** + * 车辆类型(旅馆) + */ + public static final String cllx = "dm_cllx"; + + /** + * 车辆类型(旅馆) + */ + public static final String pxdwzxdxbz = "dm_pxdwzxdxbz_ba"; + /** + * 保安员报考等级 + */ + public static final String baybkdj = "dm_bkdj_ba"; + /** + * 照片/指纹 采集标志名称 + */ + public static final String baycjbz = "dm_sfcj_ba"; + /** + * 健康状况 + */ + public static final String jkzk = "dm_jkzk_ba"; + /** + * 审核结果 + */ + public static final String shjg = "dm_shjg_ba"; + /** + * 接口对接审核结果 + */ + public static final String djsq_shjg = "dm_djsq_shjg"; + + /** + * 对接申请列表审核状态 + */ + public static final String dm_djsqlb_shzt = "dm_djsqlb_shzt"; + + /** + * 审批结果 + */ + public static final String spjg = "dm_spjg_ba"; + /** + * 考试类型 + */ + public static final String kslx = "dm_kslx_ba"; + /** + * 场所(企业)状态 + */ + public static final String csjlzt = "dm_csjlzt"; + /** + * 场所(企业)数据来源 + */ + public static final String qysjly = "dm_enterprise_source"; + /** + * 印章设施设备类型 + */ + public static final String yzsslx = "dm_seal_device_type"; + /** + * 旧货流通类型 + */ + public static final String jhltlx = "dm_jhltlx"; + /** + * 行业类别 + */ + public static final String industryCategory = "dm_hylb"; + /** + * 从业人员状态 + */ + public static final String cyryzt = "dm_cyryzt(yly)"; + /** + * 公司类型 + */ + public static final String gslx = "dm_gslx_ba"; + /** + * 人员状态 + */ + public static final String jlzt = "dm_jlzt"; + /* 专长代码 */ + public static final String zcdm = "dm_zc"; + + /* 文化程度 */ + public static final String whcd = "dm_whcd"; + /** + * 比对类型 + */ + public static final String comparisonType = "dm_bdlx"; + + /** + * 检查事项类型 + */ + public static final String jcruleType = "dm_jcruletype"; + + /** + * 开锁类型 + */ + public static final String unlock_kslx = "dm_kslx_ks"; + /** + * 开锁 保险柜类型 + */ + public static final String unlock_bxglx = "dm_bxglx_ks"; + /** + * 开锁 保险柜与委托人关系类型 + */ + public static final String unlock_bxgywtrgx = "dm_bxgywtrgx_ks"; + /** + * 可疑情况类型 + */ + public static final String SUSPICIOUS_SITUATION_TYPE = "dm_kyqklx"; + /** + * 入住情况 + */ + public static final String SFTZR = "dm_rzqk"; + /** + * 监护人关系 + */ + public static final String YJHRGX = "dm_yjhrgx"; + + + /** + * 3D产品交易信息管理 產品類型 + */ + public static final String dm_cpfl = "dm_cpfl"; + /** + * 3D产品交易信息管理 產品類型 + */ + public static final String dm_sqbafwnr_ba = "dm_sqbafwnr_ba"; + /** + * 3D产品交易信息管理 材料类型 + */ + public static final String dm_zcycllx = "dm_zcycllx"; + + /** + * 车辆号码前缀 + */ + public static final String cph_dm = "cph_dm"; + /** + * 专用运钞车防护级别 + */ + public static final String dm_clfhjb_ba = "dm_clfhjb_ba"; + /** + * 专用运钞车产品分类 + */ + public static final String dm_clcpfl_ba = "dm_clcpfl_ba"; + /** + * 车牌种类 + */ + public static final String dm_hpzl = "dm_hpzl"; + /** + * 枪支型号 + */ + public static final String dm_qx_ba = "dm_qx_ba"; + /** + * 新闻分类,法律法规、考试培训等等 + */ + public static final String dm_newsType = "dm_news_type"; + /** + * 新闻状态 + */ + public static final String dm_newsStatus = "dm_news_status"; + /** + * 新闻部分类型 + */ + public static final String dm_newsSectionType = "dm_news_section_type"; + /** + * 反馈信息状态 + */ + public static final String dm_feedback_info_status = "dm_feedback_info_status"; + /** + * 保安人员培训状态 + */ + public static final String dm_barypxzt_ba = "dm_barypxzt_ba"; + /** + * 保安人员报考等级 + */ + public static final String dm_bkdj_ba = "dm_bkdj_ba"; + /** + * 自行招用保安员单位 备案证明证件状态 + */ + public static final String dm_bazmzjtz_ba = "dm_bazmzjtz_ba"; + /** + * 保安公司许可证证件状态 + */ + public static final String bagsfwxkzzjzt = "dm_bagsfwxkzzjzt_ba"; + /** + * 培训机构证件状态 + */ + public static final String bagspxxkzzjzt = "dm_bagspxxkzzjzt_ba"; + /** + * 装备器材类别 + */ + public static final String zbqclb = "dm_zbqclb"; + /** + * 印章状态 + */ + public static final String yzzt = "dm_seal_status"; + /** + * 印章订单使用状态 + */ + public static final String sealOrderStatus = "dm_seal_order_status"; + /** + * 印章使用企业状态 + */ + public static final String sealEndUserType = "dm_seal_end_user_type"; + /** + * 印章类型 + */ + public static final String sealType = "dm_seal_type"; + /** + * 印章章面材料 + */ + public static final String sealMaterials = "dm_seal_materials"; + /** + * 印章章面形状 + */ + public static final String sealShape = "dm_seal_shape"; + /** + * 印章形态 + */ + public static final String sealForm = "dm_seal_form"; + /** + * 章面尺寸 + */ + public static final String sealSize = "dm_seal_size"; + /** + * 数据来源 + */ + public static final String sealSource = "dm_seal_source"; + /** + * 印章备案状态 + */ + public static final String sealRecord = "dm_seal_record"; + /** + * 取章方式 + */ + public static final String sealDeliverType = "dm_seal_deliver_type"; + /** + * 印章变更状态 + */ + public static final String sealModifyStatus = "dm_seal_modify_status"; + /** + * 印章制作类型 + */ + public static final String sealMakeType = "dm_seal_make_type"; + /** + * 查询标志 + */ + public static final String cxbz = "dm_cxbz_ba"; + /** + * 保安员证申领原因 + */ + public static final String slyy = "dm_slyy_ba"; + + /** + * 车辆品牌 + */ + public static final String clpp = "dm_clpp"; + + /** + * 车辆性质 + */ + public static final String clxz = "dm_clxz"; + + /** + * 车身颜色 + */ + public static final String csys = "csys_dm"; + /** + * 婚姻状况 + */ + public static final String marriage = "dm_hyzk"; + /** + * 血型 + */ + public static final String bloodType = "dm_xx"; + /** + * 机修业工种代码 + */ + public static final String machineWork = "dm_gz(jxy)"; + /** + * 印章业人员类别 + */ + public static final String sealPersonnelCategory = "dm_yzyrylb"; + /** + * 网约房人员类别 + */ + public static final String onlinePersonnelCategory = "dm_rylb_wyf"; + /** + * 娱乐业人员类别 + */ + public static final String entertainmentPersonnelCategory = "dm_rylb_yly"; + /** + * 旅馆业人员类别 + */ + public static final String hotelPersonnelCategory = "dm_rylb_lgy"; + /** + * 通用人员类别 + */ + public static final String currentCategory = "dm_rylb_ty"; + + /** + * 制卡状态标识 + */ + public static final String zkztbs = "dm_zkzt"; + /** + * 企业奖励结果 + */ + public static final String qyjllb = "dm_qyjllb"; + /** + * 企业奖励结果 + */ + public static final String hylb = "dm_hylb"; + + /** + * 签到方式 + */ + public static final String qdfs = "dm_qdfs"; + + /** + * 问题从业人员发现方式 + */ + public static final String incorrectpractitionerfxfs = "dm_incorrectpractitionerfxfs"; + /** + * 异常类型 + */ + public static final String ycrylx = "dm_ycrylx"; + + /** + * 备份类型 + */ + public static final String bflx = "dm_resource_bflx"; + + /** + * 预案分类 + */ + public static final String yafl = "dm_resource_yafl"; // 资源管理系统:应急预案:预案分类(硬件类,操作系统,应用软件) + + /** + * 审计分类 + */ + public static final String lcsjfl = "dm_resource_lcsjfl"; // 资源管理系统:流程审计(定期体检,专项抽查,流程控制) + + + /** + * 是否已执行 + */ + public static final String sfyzx = "dm_resource_gzjh_sfyzx"; + + /** + * 计划类型 + */ + public static final String jhlx = "dm_resource_gzjh_zylx"; + + /** + * 运维知识分类 + */ + public static final String ywzsfl = "dm_resource_operation_type"; + /** + * 公告分类 + */ + public static final String ggfl = "dm_ggfl"; + /** + * 印章抽验状态 + */ + public static final String sealcyzt = "dm_seal_cyzt"; + /** + * 印章抽验次数 + */ + public static final String sealcycs = "dm_seal_cycs"; + /** + * 车辆报警管理-报警类型 + */ + public static final String car_alarm_bjlx = "car_alarm_bjlx"; + /** + * 车辆报警管理-警情状态 + */ + public static final String car_alarm_jqzt = "car_alarm_jqzt"; + /** + * 车辆报警管理-状态(有效,无效) + */ + public static final String car_alarm_zt = "car_alarm_zt"; + /** + * 证件状态 + */ + public static final String lyyxzj = "dm_lyyxzj(lgy)"; + /** + * 开锁人员类型 + */ + public static final String dm_ksrylxdm = "dm_ksrylxdm"; + /** + * 旅客类别 + */ + public static final String dm_lklbdm = "dm_lklbdm"; + /** + * 开锁方式 + */ + public static final String dm_ksfsdm = "dm_ksfsdm"; + /** + * 房屋性质 + */ + public static final String dm_fwxz = "dm_fwxz(wyf)"; + /** + * 从业人员在职状态 + */ + public static final String pratitionerWorkStatus = "dm_pratitioner_work_status"; + /** + * 接口对接申请 数据来源 + */ + public static final String djsq_sjly = "dm_jkdjsq_sjly"; + /** + * 异常核查 异常处置情况 + */ + public static final String dm_ycczqk = "dm_ycczqk"; + /** + * 重点人员类型 + */ + public static final String keyPersonnelType = "dm_key_personnel_type"; + /** + * 对接接口审核 + */ + public static final String dm_join_apply_reviews = "jkdjsh"; + /** + * 特殊团体授权类型 + */ + public static final String dm_tsttlx = "dm_tsttsqlx"; + /** + * 预警处置结果-情报中心 + */ + public static final String dm_yjczjg_qbzx = "dm_yjczjg_qbzx"; + /** + * 人员类别-情报中心 + */ + public static final String dm_rylb_qbzx = "dm_rylb_qbzx"; + /** + * 人员细类-情报中心 + */ + public static final String dm_ryxl_qbzx = "dm_ryxl_qbzx"; + /** + * 预警状态-情报中心 + */ + public static final String dm_yjzt_qbzx = "dm_yjzt_qbzx"; + /** + * 工作评估-情报中心 + */ + public static final String dm_gzpg_qbzx = "dm_gzpg_qbzx"; + /** + * 处置措施-情报中心 + */ + public static final String dm_czcs_qbzx = "dm_czcs_qbzx"; + /** + * 动态信息类别-情报中心 + */ + public static final String dm_dtxxlb_qbzx = "dm_dtxxlb_qbzx"; + /** + * 目标从事职业类型-情报中心 + */ + public static final String dm_mbcszylx_qbzx = "dm_mbcszylx_qbzx"; + /** + * 目标发现状态-情报中心 + */ + public static final String dm_mbfxzt_qbzx = "dm_mbfxzt_qbzx"; + /** + * 开放资源-极为特别 + */ + public static final String dm_public_kfzy = "dm_public_kfzy"; + /** + * 开放资源-值-极为特别 + */ + public static final String dm_public_kfzy_value = "dm_public_kfzy_value"; + + /** + * 限制资源-极为特别 + */ + public static final String dm_public_xzzy = "dm_public_xzzy"; + public static final String dm_jrlx = "dm_jrlx"; + } + + /** + * 字典项代码常量 + */ + public static final class DictItems { + /** + * 印章订单数据来源 + */ + public static final class SealOrderSjlydm { + /** + * 印章制作单位录入 + */ + public static final String PRODUCTION_UNIT = "0"; + + /** + * 政务大厅受理录入 + */ + public static final String GOVERNMENT_AFFAIRS_HALL = "1"; + + /** + * 印章使用单位申请录入 + */ + public static final String USE_UNIT = "2"; + /** + * 政府买单数据录入 + */ + public static final String GOVERNMENT_PAYS = "3"; + /** + * 短信验证录入 + */ + public static final String SMS_VERIFICATION = "4"; + } + + /** + * 印章流程状态 seal -- yzztdm + */ + public static final class SealProcessStatus { + /** + * -1已撤销 + */ + public static final String REVOKED = "-1"; + /** + * 0已受理 + */ + public static final String ACCEPTED = "0"; + /** + * 1已备案 + */ + public static final String FILED = "1"; + + /** + * 2已承接 + */ + public static final String HAS_BEEN_UNDERTAKEN = "2"; + + /** + * 3已制作 + */ + public static final String MADE = "3"; + /** + * 4已交付 + */ + public static final String PAID = "4"; + /** + * 5已报废 + */ + public static final String SCRAPPED = "5"; + /** + * 6已缴销 + */ + public static final String PAIDOFF = "6"; + /** + * 7已挂失 + */ + public static final String REPORTEDLOST = "7"; + /** + * 8未备案 + */ + public static final String NOTFILED = "8"; + /** + * 9已遗失 + */ + public static final String LOST = "9"; + } + + /** + * 印章订单状态 sealOrder --- yzztdm + */ + public static final class SealOrderStatus { + /** + * 0已承接 + */ + public static final String HAS_BEEN_UNDERTAKEN = "0"; + /** + * 1制作中 + */ + public static final String MAKING = "1"; + + /** + * 2已制作 + */ + public static final String MADE = "2"; + + /** + * 3已发放 + */ + public static final String ISSUED = "3"; + /** + * 4 + */ + public static final String FOUR = "4"; + /** + * 5已撤销 + */ + public static final String REVOKED = "5"; + /** + * 6已申请 + */ + public static final String ALREADY_APPLIED = "6"; + /** + * 7申请失败 + */ + public static final String APPLICATION_FAILED = "7"; + /** + * 8报废 + */ + public static final String SCRAPPED = "8"; + /** + * 99 已受理 + */ + public static final String ACCEPTED = "99"; + } + + /** + * 取章方式 + */ + public static final class SealOrderTakeChapter { + /** + * 自取 + */ + public static final String SELF_COLLECTION = "0"; + /** + * 快递 + */ + public static final String EXPRESS_DELIVERY = "1"; + /** + * 窗口 + */ + public static final String WINDOW = "2"; + + } + + /** + * 对接接口审核状态 (字典代码:dm_djsqlb_shzt) + */ + public static final class JoinApplyStatus { + /** + * 未提交 未申请 + */ + public static final String NOT_APPLY = "0"; + /** + * 已提交 已申请 + */ + public static final String APPLIED = "1"; + /** + * 区县派出所审核通过 + */ + public static final String LOCAL_POLICE_STATION_REVIEW_PASSED = "2"; + /** + * 区县派出所审核未通过 + */ + public static final String LOCAL_POLICE_STATION_REVIEW_NOT_PASS = "3"; + /** + * 公安局场所支队审核通过 + */ + public static final String PUBLIC_SECURITY_BUREAU_PLACE_DETACHMENT_REVIEW_PASSED = "4"; + /** + * 公安局场所支队审核未通过 + */ + public static final String PUBLIC_SECURITY_BUREAU_PLACE_DETACHMENT_REVIEW_NOT_PASS = "5"; + /** + * 公安局情报信息支队审核通过 + */ + public static final String PUBLIC_SECURITY_BUREAU_INTELLIGENCE_INFORMATION_DETACHMENT_REVIEW_PASSED = "6"; + /** + * 公安局情报信息支队审核未通过 + */ + public static final String PUBLIC_SECURITY_BUREAU_INTELLIGENCE_INFORMATION_DETACHMENT_REVIEW_NOT_PASS = "7"; + /** + * 公安局总队审核通过 + */ + public static final String PUBLIC_SECURITY_BUREAU_CORPS_REVIEW_PASSED = "8"; + /** + * 公安局总队审核未通过 + */ + public static final String PUBLIC_SECURITY_BUREAU_CORPS_REVIEW_NOT_PASS = "9"; + } + + /** + * 对接接口审核 (对应字典代码:jkdjsh) + */ + public static final class JoinApplyReviews { + /** + * 审核通过 + */ + public static final String REVIEW_PASSED = "2"; + /** + * 审核未通过 + */ + public static final String REVIEW_NOT_PASS = "3"; + /** + * 未审核 + */ + public static final String UNREVIEWED = "1"; + /** + * 审核中 + */ + public static final String REVIEWING = "4"; + /** + * 待审核 + */ + public static final String WAIT_REVIEW = "0"; + } + } + + /** + * 请求头 + */ + public static final class Headers { + /** + * 认证令牌 + */ + public static final String AUTH_TOKEN = "Auth-Token"; + + public static final String AUTH_TOKEN_MOBILE = AUTH_TOKEN + "-Mobile"; + + public static final String REDIS_TOKEN_NMS = "aisino-tokens"; + + public static final String REDIS_A_TOKEN_PREFIX = REDIS_TOKEN_NMS.concat(":").concat(AUTH_TOKEN); + + public static final String REDIS_M_TOKEN_PREFIX = REDIS_TOKEN_NMS.concat(":").concat(AUTH_TOKEN_MOBILE); + + /** + * 加密凭证,也用这个标记判断是不是加密请求 + */ + public static final String ENCRYPT_SIGN = "Encrypt-Sign"; + /** + * 已加密的数据,一般用来标记响应里 + */ + public static final String ENCRYPTED_DATA = "Encrypted-Data"; + /** + * 来自反向代理的客户端IP + */ + public static final String X_REAL_IP = "x-real-ip"; + } + + /** + * redis key 前缀 (以后所有的redis相关的key名称以及前缀都放在这里) + * + * @author hx + * @since 2023-05-23 + */ + public enum RedisKeyPrefix { + /** + * 二级缓存公共前缀 + */ + l2cache("hibernate."), + /** + * 请求体加密模板,格式: req.b.ept.`sign` + */ + requestBodyEncrypt("req.b.ept.{}"), + /** + * 请求体加密锁模板 + */ + requestBodyEncryptLock("req.b.ept.lock.{}"), + /** + * 消息发送key + */ + messageToken("message_token_key"), + /** + * 消息发送的加锁key + */ + messageGenerateToken("message_token_key_lock"), + /** + * 缓存全部机构 + */ + cacheJurisdictionAll("cache.jurisdictions.all"), + /** + * 缓存机构查询 + */ + cacheJurisdictionQuery("cache.jurisdictions.query"), + /** + * 缓存字典项查询 + */ + cacheDictItemQuery("cache.dict-items.query"), + /** + * 用户信息 + */ + userInfo("user_info:{}"), + /** + * 后端访问令牌信息 + */ + backendToken("backend_token:{}"); + private final String value; + + RedisKeyPrefix(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + /** + * 全局参数 + */ + public static final class GlobalParams { + /** + * 操作日志开关 + * 0:关闭 + * 1:保存到数据库 + * 2:保存到文件 + */ + public enum OperateLogSwitch { + db("1"), none("0"), file("2"); + private final String value; + public static final String code = "operate_log_switch"; + + OperateLogSwitch(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public static OperateLogSwitch fromValue(String value) { + return Arrays.stream(OperateLogSwitch.values()).filter(v -> v.getValue().equals(value)).findAny().orElse(none); // 当传递来参数值不能转换为枚举值时候,默认就关闭日志 + } + } + + /** + * 程序区域版本 + */ + public enum ApplicationVersion { + chongqing, guangxi, sichuan, weifang, heilongjiang; + public static final String code = "application_version"; + } + + /** + * 内网标志 + */ + public enum InnerNetFlag { + inner("1"), outter("0"); + private final String value; + /** + * 内网标志的代码 + */ + public final static String code = "innernet_flag"; + + InnerNetFlag(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public static InnerNetFlag fromValue(String value) { + return Arrays.stream(InnerNetFlag.values()).filter(v -> v.getValue().equals(value)).findAny().orElseThrow(() -> new RuntimeException("该值的内网标志不存在")); + } + } + + /** + * 网约房三方上报接口数据目标表:0业务表,1临时表 + */ + public static final String WYF_HOUSE_REPORT_INFO = "wyf_house_report_info"; + /** + * 网约房三方上报接口受控平台编码 + */ + public static final String WYF_HOUSE_REPORT_JYZBM = "wyf_house_report_jyzbm"; + /** + * 设备授权是否控制身份证阅读器 + */ + public static final String SBSQSFKZSFZYDQ = "sbsqsfkzsfzydq"; + /** + * 重点地区旅客查询天数 + */ + public static final String KeyRegionPassengerQueryDays = "zddqlkcxts"; + /** + * 国内旅客数据及时性以入住时间和入库时间、退房时间和最后修改时间小于N(小时) + */ + public static final String GNLKJSXJGSJ = "gnlkjsxjgsj"; + /** + * 国内旅客数据退房时间以入住后N(天)退房时间仍为空计算为不完整 + */ + public static final String GNLKWZXJGSJ = "gnlkwzxjgsj"; + /** + * 保安地区省编码 + */ + public static final String BDQXZQH_BA = "bdqxzqh_ba"; + /** + * 保安地区 + */ + public static final String AREA_BA = "area_ba"; + + /* 所属省份 */ + public static final String SSSF_BA = "sssf"; + + /* 照片base64前缀 */ + public static final String IMG_BASE64_PREFFIX = "img-base64-prefixx"; + public static final String PORTAL_INDUSTRY_SHOWCASE = "portal_industry_showcase"; + public static final String PRINTING_WTFJBRSFZZP = "printing-wtfjbrsfzzp"; + public static final String PRINTING_YRSFZZP = "printing-yrsfzzp"; + public static final String PRINTING_QYRSFZZP = "printing-qyrsfzzp"; + + /* 人口接口数据 */ + public static final String RKK_JKDZ = "rkkjkdz"; + // 旅馆迁移接口地址 + public static final String LGQYJKDZ = "lgqyjkdz"; + public static final String HOTEL_MIGRATE_TRAILED = "hotel_migrate_trailed"; + + + /** + * 保安内外网标志 + */ + public static final String NWWBZ = "nwwbz"; + public static final String SFYHQHJDPCSJK = "sfyhqhjdpcsjk"; + public static final String WEBSERVICEURL = "webservice_url"; + public static final String URLSHENHE = "url_shenhe"; + /** + * 旅馆系统企业端应用URL地址 + */ + public static final String HOTEL_SYSTEM_URL = "hotel_system_url"; + /** + * 旅馆系统新企业端应用URL地址 + */ + public static final String NEW_HOTEL_SYSTEM_URL = "new_hotel_system_url"; + /** + * 警务通扫描二维码有效分钟数 + */ + public static final String QRCODE_VALID_MINUTES = "qrcode_valid_minutes"; + /** + * 警务通企业核查有效分钟数 + */ + public static final String CHECK_EFFECTIVE_MINUTES = "check_effective_minutes"; + /** + * 警务通企业日常检查有效分钟数 + */ + public static final String EFFECTIVE_MINUTES_FORDAILY_INSPECTION = "effective_minutes_fordaily_inspection"; + /** + * 印章业区块链下链请求url + */ + public static final String BLOCKCHAIN_CHAIN_DOWN_REQUEST_URL = "blockchain_chaindown_request_url"; + /** + * 印章业区块链上链请求url + */ + public static final String BLOCKCHAIN_ONTHE_CHAIN_REQUEST_URL = "blockchain_onthechain_request_url"; + /** + * 用户自动锁定尝试次数 + */ + public static final String USER_AUTO_LOCK_ATTEMPT_TIMES = "user_auto_lock_attempt_times"; + /** + * 用户自动锁定锁定分钟数 + */ + public static final String USER_AUTO_LOCK_LOCK_MINUTES = "user_auto_lock_lock_minutes"; + /** + * 用户认证过期时间天数 + */ + public static final String AUTH_TOKEN_EXPIRE_DAYS = "auth_token_expire_days"; + /** + * 内网用户可在外网登录的用户类别代码(用户类别代码之间逗号隔开) + */ + public static final String USER_TYPE_OUTTER_LOGIN = "user_type_outter_login"; + /** + * 系统部署行政区划 + */ + public static final String SYSTEM_DEPLOY_ADMINISTRATIVE_DIVISION = "xtbsdq_xzqhjm"; + /** + * 保安员电子证扫描二维码有效分钟数 + */ + public static final String SECURITY_GUARD_E_CARD_VALID_MINUTES = "security_guard_e_card_valid_minutes"; + + /** + * 已备案通过的印章多少小时内可撤回 + */ + public static final String SEAL_REVOKE_HOUR = "seal_revoke_hour"; + /** + * 已缴销的印章多少小时内可修改资料 + */ + public static final String SEAL_HANDIN_HOUR = "seal_handin_hour"; + /** + * 印章上传印模限制分辨率数 + */ + public static final String SEAL_IMPRESSION_DPI = "seal_impression_dpi"; + + public static final String aqrz_qy_numbers = "aqrz_qy_numbers"; + + public static final String aqrz_ip_numbers = "aqrz_ip_numbers"; + /** + * 微信民宿旅客角色名称 + */ + public static final String WECHAT_BB_TRAVELER_ROLENAME = "wechat_bb_traveler_rolename"; + } + + /** + * 生成器策略 + */ + public static class GeneratorStrategy { + /** + * uuid生成器 + */ + public static final String uuid = "com.aisino.iles.core.identifiergenerator.UUIDGeneratorPlus"; +// public static final String uuid = "org.hibernate.id.UUIDGenerator"; + /** + * 序列生成器 + */ + public static final String sequence = "com.aisino.iles.core.identifiergenerator.SequenceStyleGeneratorPlus"; + /** + * ulid生成器 + */ + public static final String ulid = "com.aisino.iles.core.identifiergenerator.ULIDGenerator"; + } + + /** + * 默认的系统管理员用户名 + */ + public static final String ADMIN_USERNAME = "admin"; + /** + * 默认的 + */ + public static final String ADMIN_PASSWORD = ""; + /** + * 需要通过验证的API前缀 + */ + public static final String API_PREFIX = "/api"; + /** + * 不通过验证的API前缀 + */ + public static final String NO_AUTH_API_PREFIX = "/no-auth-api"; + /** + * + */ + public static final String CURRENT_USER_ID = "current_user_id"; + + /** + * 用户自动锁定前缀 + */ + public static final String USER_AUTO_LOCK_PREFIX = "user_auto_lock:"; + + /** + * 大数据数据初始化操作类型 + */ + public static final class BigDataDataInitOpTypes { + /** + * 插入 + */ + public static final String INSERT = "1"; + /** + * 删除再插入 + */ + public static final String DELETE_AND_INSERT = "2"; + } + + /** + * 网约房数据来源类型 + */ + public static final class WyfSjly { + /** + * 部级下载 + */ + public static final String DOWNLOAD = "0"; + + /** + * 地市上传 + */ + public static final String UPLOAD = "1"; + + /** + * 企业申报 + */ + public static final String DECLARE = "2"; + + /** + * 民警采集 + */ + public static final String COLLECTION = "3"; + + } + + /** + * 网约房数据来源类型 + */ + public static final class Sex { + /** + * 男 + */ + public static final String MAN = "1"; + + /** + * 女 + */ + public static final String WOMAN = "2"; + + } + + /** + * 网约房数据来经营状态 + */ + public static final class WyfJyzt { + /** + * 营业 + */ + public static final String BUSINESS = "1"; + + /** + * 停业 + */ + public static final String CLOSED = "0"; + + } + + /** + * 网约房核查状态 + */ + public static final class WyfHczt { + /** + * 未核查 + */ + public static final String NOCHECK = "0"; + + /** + * 已核查 + */ + public static final String ISCHECK = "1"; + + } + + /** + * 网约房注销标志 + */ + public static final class WyfZxbz { + /** + * 未注销 + */ + public static final String NOLOGOUT = "0"; + + /** + * 已注销 + */ + public static final String ISLOGOUT = "1"; + + } + + /** + * 网约房主分类 + */ + public static final class WyfZfl { + /** + * 网约民宿 + */ + public static final String WYMS = "0"; + + /** + * 农家乐 + */ + public static final String NJL = "1"; + /** + * 日租房 + */ + public static final String RZF = "2"; + /** + * 露营基地 + */ + public static final String LYJD = "3"; + } + + /** + * 网约房空间类型 + */ + public static final class WyfKjlx { + /** + * 房车 + */ + public static final String FC = "0"; + + /** + * 帐篷 + */ + public static final String ZP = "1"; + /** + * 其它房屋 + */ + public static final String QTFW = "2"; + } + + /** + * 网约房归档原因 + */ + public static final class WyfGdyy { + /** + * 部里下发更新归档 + */ + public static final String MINISTERIALDOWNLOAD = "0"; + + /** + * 民警核查归档 + */ + public static final String POLICECHECK = "1"; + + } + + /** + * 公安部预警类型 + */ + public static final class VGabCkYjxxZjkLgy { + /** + * 吸毒 + */ + public static final int DRUG = 4; + /** + * 在逃 + */ + public static final int ESCAPE = 3; + /** + * 涉稳 + */ + public static final int STABLE = 2; + /** + * 上访 + */ + public static final int PETITION = 7; + /** + * 精神病 + */ + public static final int PSYCHOTIC = 6; + } + + //保安业务常量 + public static final class SecurityConstants { + + /* ------------------------------------------- 保安服务分公司 begin---------------------------------------------- */ + /* 保安服务分公司市级审核---标志 */ + public static final String BAFWFGSSJSH = "bafwfgsSjSh"; + /* 保安服务分公司市级监管部门审核---标志 */ + public static final String BAFWFGSSJJGBMSH = "bafwfgsSjjgbmSh"; + /* 保安服务分公司信息维护标志 */ + public static final String BAFWFGSXXWH = "bafwfgsXxWh"; + /* 保安服务分公司市局/县级变更申请审核 */ + public static final String BAFWFGSSJBGSQSH = "bafwfgssjbgsqsh"; + /* 保安服务分公司监管部门变更申请审核 */ + public static final String BAFWFGSJGBMBGSQSH = "bafwfgsjgbmbgsqsh"; + /* 保安服务分公司市级监管部门审核 */ + public static final String BAFWFGSSTJGBMSH = "bafwfgsStjgbmSh"; + /* 保安服务分公司市级审核 */ + public static final String BAFWFGSSTSH = "bafwfgsStSh"; + /* ------------------------------------------- 保安服务分公司 end ---------------------------------------------- */ + + /* ------------------------------------------- 跨区域提供保安服务 begin ---------------------------------------------- */ + /* 跨区域公司派出所审核 */ + public static final String KQYBAFWGSPCSSH = "kqybafwgspcssh"; + /* 跨区域公司监管部门审核 */ + public static final String KQYBAFWGSJGBMSH = "kqybafwgsjgbmsh"; + /* 跨区域公司市局审核(县级审核) */ + public static final String KQYBAFWGSSJSH = "kqybafwgssjsh"; + /* 跨区域公司市级监管部门审核 */ + public static final String KQYBAFWGSSTJGBMSH = "kqybafwgsstjgbmsh"; + /* 跨区域公司市级审核 */ + public static final String KQYBAFWGSSTSH = "kqybafwgsstsh"; + /* ------------------------------------------- 跨区域提供保安服务 end ---------------------------------------------- */ + + + public static final String[] BKRY = {"0", "2"}; + public static final String KSBPBZ_ZCBP = "0"; + public static final String KSBPBZ_ZCKS = "1"; + public static final String KSBPBZ_BKBP = "2"; + public static final String KSBPBZ_BK = "3"; + + /** + * 删除标志 常量 (可能也适用于注销状态) + */ +// public static final String KSBPBZ_BK = "0"; +// public static final String KSBPBZ_BK = "1"; + + } + + + /** + * 旅馆状态 + */ + public static final class TavernStatus { + public static final String ON = "1"; + public static final String OFF = "0"; + } + + /** + * 注销标志 + */ + public static final class CancellationMark { + /** + * 注销 + */ + public static final String CANCELLATION = "1"; + /** + * 未注销 + */ + public static final String NOT_CANCELLATION = "0"; + } + + /** + * 删除标志 + */ + public static final class DeleteMark { + /** + * 已删除 + */ + public static final String DELETED = "1"; + /** + * 未删除 + */ + public static final String NOT_DELETED = "0"; + } + + /** + * 从业人员类型 + */ + public static final class PersonnelType { + /** + * 国外 + */ + public static final String ABROAD = "1"; + /** + * 国内 + */ + public static final String DOMESTIC = "0"; + } + + /** + * 从业人员操作类型 + */ + public static final class PersonnelOperationType { + public static final String NEWLY_ADDED = "1";//新增 + public static final String MODIFY = "2";//修改 + public static final String DELETE = "3";//删除 + public static final String QUIT = "LZ";//离职 + public static final String ON_THE_JOB = "RZ";//入职 + public static final String CANCELLATION = "ZX";//注销 + public static final String LOGOUT_RECOVERY = "ZXHF";//注销恢复 + public static final String DELETED = "SC";//删除 + } + + /** + * 从业人员是否创建用户 + */ + public static final class FrontEndOperator { + public static final String BE = "1";//是 + public static final String DENY = "0";//否 + } + + /** + * 从业人员在职状态 + */ + public static final class PersonnelJobStatus { + public static final String ON_THE_JOB = "10";//在职 + public static final String QUIT = "20";//离职 + } + + /** + * 公民身份号码是否符合校验规则标志 + */ + public static final class NumberVerification { + /** + * 符合 + */ + public static final String ACCORD_WITH = "0"; + /** + * 不符合 + */ + public static final String NON_CONFORMITY = "1"; + } + + /** + * 省级编码 + */ + public static final class Sbms { + /** + * 重庆 + */ + public static final String CHOGNQING = "50"; + /** + * 河北 + */ + public static final String HEBEI = "13"; + } + + /** + * 用户类别代码 + */ + public static final class UserTypeCode { + /** + * 综合管理公安用户 + */ + public static final String PUBLIC_POLICE_USER = "001"; + /** + * 旅馆业公安用户 + */ + public static final String HOTEL_POLICE_USER = "101"; + /** + * 旅馆场所支队公安用户 + */ + public static final String HOTEL_POLICE_PLACE_DETACHMENT_USER = "181"; + /** + * 旅馆情报信息支队公安用户 + */ + public static final String HOTEL_POLICE_INTELLIGENCE_INFORMATION_DETACHMENT_USER = "171"; + /** + * 旅馆业公安端大情报用户 + */ + public static final String HOTEL_INDUSTRY_PUBLIC_SECURITY_SIDE_BIG_INTELLIGENCE_USER = "131"; + /** + * 旅馆业公安端出入境用户 + */ + public static final String ENTRY_AND_EXIT_USERS_OF_HOTEL_INDUSTRY_PUBLIC_SECURITY_SIDE = "141"; + /** + * 旅馆总队公安用户 + */ + public static final String HOTEL_POLICE_CORPS_USER = "161"; + /** + * 旅馆业企业用户 + */ + public static final String HOTEL_ENTERPRISE_USER = "102"; + /** + * 旅馆业企业人员 + */ + public static final String HOTEL_PRACTITIONER_USER = "103"; + /** + * 网约房公安用户 + */ + public static final String ONLINE_APPOINTMENT_ROOM_POLICE_USER = "401"; + /** + * 网约房企业用户 + */ + public static final String ONLINE_APPOINTMENT_ROOM_ENTERPRISE_USER = "402"; + /** + * 网约房从业人员用户 + */ + public static final String ONLINE_APPOINTMENT_ROOM_PRACTITIONER_USER = "403"; + /** + * 网约房/民宿微信端用户 + */ + public static final String ONLINE_APPOINTMENT_ROOM_WECHAT_USER = "405"; + /** + * 增材制造业公安端县级用户类别 + */ + public static final String ADDITIVE_MANUFACTURING_XJ_USER = "1211"; + /** + * 增材制造业公安端派出所用户类别 + */ + public static final String ADDITIVE_MANUFACTURING_PCS_USER = "1221"; + /** + * 委托寄卖企业用户 + */ + public static final String CONSIGNMENT_ENTERPRISE_USER = "2002"; + /** + * 委托寄卖从业人员用户 + */ + public static final String CONSIGNMENT_PRACTITIONER_USER = "2003"; + /** + * 印章业公安用户 + */ + public static final String SEAL_POLICE_USER = "201"; + /** + * 印章使用单位 + */ + public static final String SEAL_END_USER = "212"; + /** + * 娱乐业公安用户 + */ + public static final String ENTERTAINMENT_POLICE_USER = "301"; + /** + * 系统管理用户 + */ + public static final String SYSTEM_USER = "999"; + /** + * 门户用户 + */ + public static final String PORTAL_USER = "904"; + /** + * 门户管理用户 + */ + public static final String PORTAL_ADMIN_USER = "914"; + /** + * 门户对接平台信息管理用户 + */ + public static final String PORTAL_JOIN_PLATFORM_USER = "922"; + /** + * 印章业企业人员用户 + */ + public static final String SEAL_PRACTITIONER_USER = "203"; + /** + * 保安公安用户 + */ + public static final String SECURITY_POLICE_USER = "501"; + /** + * 保安服务公司企业用户 + */ + public static final String SECURITY_SERVICE_USER = "512"; + /** + * 保安服务公司从业人员 + */ + public static final String SECURITY_SERVICE_PRACTITIONER_USER = "513"; + /** + * 保安培训单位企业用户 + */ + public static final String SECURITY_TRAINING_USER = "522"; + /** + * 保安培训单位从业人员 + */ + public static final String SECURITY_TRAINING_PRACTITIONER_USER = "523"; + /** + * 保安自行招用企业用户 + */ + public static final String SECURITY_SELF_EMPLOYMENT_USER = "532"; + /** + * 保安自行招用从业人员 + */ + public static final String SECURITY_SELF_EMPLOYMENT_PRACTITIONER_USER = "533"; + /** + * 保安跨区域企业用户 + */ + public static final String SECURITY_CROSS_REGIONAL_USER = "542"; + /** + * 保安跨区域从业人员 + */ + public static final String SECURITY_CROSS_REGIONAL_PRACTITIONER_USER = "543"; + /** + * 保安服务分公司企业用户 + */ + public static final String SECURITY_BRANCH_COMPANY_USER = "552"; + /** + * 保安服务分公司从业人员 + */ + public static final String SECURITY_BRANCH_COMPANY_PRACTITIONER_USER = "553"; + /** + * 保安员 + */ + public static final String SECURITY_GUARD_USER = "504"; + + /** + * 资源管理系统用户 + */ + public static final String BUSINESS_RESOURCE_MANAGEMENT_USER = "011"; + + /** + * 开锁从业人员 + */ + public static final String UNLOCK_PRACTITIONER_USER = "1003"; + + /** + * 企业数据上报用户类别 + */ + public static final String ENTERPRISE_DATA_REPORTING_USER = "907"; + + /** + * 公章政务大厅用户类别 + */ + public static final String SEAL_GOVERNMENT_AFFAIRS_HALL_USER = "291"; + + /** + * 旅馆政务大厅用户类别 + */ + public static final String TRAVEN_GOVERNMENT_AFFAIRS_HALL_USER = "191"; + /** + * 社区民警用户类别 + */ + public static final String COMMUNITY_USER = "991"; + + } + + /** + * 用户类别的代码分类, 这个是通过末尾数字来进行分类 + */ + public static final class UserTypeCategory { + /** + * 公安 + */ + public static final String POLICE = "1"; + /** + * 公安场所支队 + */ + public static final String POLICE_PLACE_DETACHMENT = "81"; + /** + * 公安情报支队 + */ + public static final String POLICE_INTELLIGENCE_INFORMATION_DETACHMENT = "71"; + /** + * 公安总队 + */ + public static final String POLICE_CORPS = "61"; + /** + * 企业 + */ + public static final String ENTERPRISE = "2"; + /** + * 从业人员 + */ + public static final String PRACTITIONER = "3"; + /** + * 门户 + */ + public static final String PORTAL = "4"; + /** + * 微信 + */ + public static final String WECHAT = "5"; + /** + * 政务大厅(行政服务中心) + */ + public static final String GOVERNMENT_AFFAIRS_HALL = "91"; + } + + /** + * 报警预警信息业务表信息 + */ + public static final class AlarmInformationBusinessTables { + /** + * 网约房 屋主/管理人(快照) + */ + public static final String ONLINE_APPOINTMENT_ROOM_HOUSE_MANAGER = "t_house_manager_xz"; + /** + * 网约房 订单信息(快照) + */ + public static final String ONLINE_APPOINTMENT_ROOM_ORDER = "t_order_xz"; + /** + * 网约房 订单入住顾客(快照) + */ + public static final String ONLINE_APPOINTMENT_ROOM_ORDER_GUEST = "t_order_guest_xz"; + /** + * 网约房 订单拟入住顾客(快照) + */ + public static final String ONLINE_APPOINTMENT_ROOM_ORDER_EARLY_GUEST = "t_order_earlyguest_xz"; + /** + * 旅馆 国内旅客信息(快照) + */ + public static final String HOTEL_DOMESTIC_PASSENGER_HISTORY = "t_gnlk_lsxx"; + /** + * 旅馆 国外旅客(快照) + */ + public static final String HOTEL_FOREIGN_PASSENGER_HISTORY = "t_jwlk_lsxx"; + /** + * 印章 印章表(快照) + */ + public static final String SEAL_SEAL_INFO = "t_seal_snapshot"; + /** + * 印章 印章订单(快照) + */ + public static final String SEAL_ORDER = "t_seal_order_snapshot"; + /** + * 公共 企业信息(历史信息) + */ + public static final String PUBLIC_LTD_INFO = "t_qyjbxx_ls"; + /** + * 公共 从业人员信息(历史信息) + */ + public static final String PUBLIC_PRACTITIONER_HISTORY = "t_qyryxx_ls"; + /** + * 公共 从业人员信息 + */ + public static final String PUBLIC_PRACTITIONER = "t_qyryxx"; + + /** + * 公共 业务人员信息 + */ + public static final String PUBLIC_BUSINESS_PERSON_INFO = "t_business_person_info"; + /** + * 公共 业务车辆信息 + */ + public static final String PUBLIC_BUSINESS_CAR_INFO = "t_business_car_info"; + /** + * 公共 业务物品信息 + */ + public static final String PUBLIC_BUSINESS_ITEM_INFO = "t_business_item_info"; + } + + /** + * 业务人员类型 + * 编码规则:变量名 “行业类别英文”_“类别英文” (类别或者行业名由多个单词组成也是用_连接), 值: 使用“行业类别编码”连接“自己定义的代码值”, + * 保证总体代码值是唯一,推荐使用“行业类别编码+类型变量的缩写”,例如国内旅客:Adp,网约房屋主/管理人:A06hm,印章代办人:Bagt + */ + public static final class BusinessPersonTypes { + /** + * 娱乐场所 签到人员 + */ + public static final String ENTERTAINMENT_PRACTITIONER_SIGN_IN = "Jsi"; + /** + * 印章 代办人 + */ + public static final String SEAL_AGENT = "Bagt"; + /** + * 印章 取章人 + */ + public static final String SEAL_TAKE_SEAL_PERSON = "Btsp"; + /** + * 旅馆 国内旅客 + */ + public static final String HOTEL_DOMESTIC_PASSENGER = "Adp"; + /** + * 网约房 屋主/管理人 + */ + public static final String ONLINE_APPOINTMENT_ROOM_HOUSE_MANAGER = "A06hm"; + /** + * 网约房 订单入住顾客 + */ + public static final String ONLINE_APPOINTMENT_ROOM_ORDER_GUEST = "A06og"; + /** + * 网约房 订单信息 + */ + public static final String ONLINE_APPOINTMENT_ROOM_ORDER = "A06o"; + /** + * 网约房 订单拟入住顾客 + */ + public static final String ONLINE_APPOINTMENT_ROOM_ORDER_EARLY_GUEST = "A06oeg"; + /** + * 公共 从业人员 + */ + public static final String COMMON_PRACTITIONER = "GGpra"; + /** + * 典当相关人员 + */ + public static final String PAWN_PERSON = "E03p"; + /** + * 委托寄卖相关人员 + */ + public static final String CONSIGNMENT_PERSON = "E07p"; + /** + * 印刷相关人员 委托方经办人 + */ + public static final String PRINT_PERSON_ENTRUST = "Dwt"; + /** + * 印刷相关人员 送印人 + */ + public static final String PRINT_PERSON_SEND_PRINT = "Dsy"; + /** + * 印刷相关人员 取印人 + */ + public static final String PRINT_PERSON_TAKE = "Dqy"; + /** + * 报废相关人员 + */ + public static final String SCRAP_PERSON = "C01p"; + /** + * 机修相关人员 + */ + public static final String MECHANICAL_REPAIR_PERSON_SCR = "C02scr"; + public static final String MECHANICAL_REPAIR_PERSON_QCR = "C02qcr"; + /** + * 二手车相关人员 售卖 + */ + public static final String TRANSACTION_PERSON_SELL = "E01ps"; + /** + * 二手车相关人员 购买 + */ + public static final String TRANSACTION_PERSON_BUY = "E01pb"; + /** + * 开锁 见证人员 + */ + public static final String UNLOCK_WITNESS_PERSON = "Y01wp"; + /** + * 开锁 开锁人员 + */ + public static final String UNLOCK_PICKLOCK_PERSON = "Y01pp"; + + /** + * 旧货交易 所有人员 + */ + public static final String USED_POSSESSOR_PERSON = "E02sy"; + /** + * 旧货交易 购买人员 + */ + public static final String USED_BUY_PERSON = "E02gm"; + } + + /** + * 业务表信息 + */ + public static final class BusinessTables { + /** + * 印章信息 + */ + public static final String SEAL_INFO = "t_seal"; + /** + * 娱乐场所人员打卡信息 + */ + public static final String ENTERTAINMENT_PRACTITIONER_SIGN_IN = "t_qyryqd"; + /** + * 典当业务 + */ + public static final String PAWN_INFO = "t_pawn_business"; + /** + * 废旧金属物品业务 + */ + public static final String FJJS_WP_INFO = "t_fjjswpxx"; + + /** + * 废旧金属收购业务 + */ + public static final String FJJS_SG_INFO = "t_fjjssgxx"; + /** + * 旧货信息 + */ + public static final String junk_INFO = "t_jhwpxx"; + /** + * 旧货购买信息 + */ + public static final String junk_gm_INFO = "t_jhwpgmxx"; + /** + * 印刷品信息表 + */ + public static final String presswork_INFO = "t_presswork"; + + /** + * 汽车租赁车辆信息表 + */ + public static final String RENTALCAR_INFO = "t_rentalcarinfo"; + /** + * 报废回收车辆信息表 + */ + public static final String SCRAP_INFO = "t_hscljbxx"; + /** + * 机修车辆信息表 + */ + public static final String MECHANICAL_REPAIR_INFO = "t_jxcljbxx"; + /** + * 二手车交易信息表 + */ + public static final String TRANSACTION_INFO = "t_transaction_infor"; + + /** + * 委托寄卖基本信息表 + */ + public static final String CONSIGNMENT_INFO = "t_wtjmxx"; + + /** + * 委托寄卖基本信息表 + */ + public static final String CONSIGNMENT_GOOD_INFO = "t_wtjmwpxx"; + + /** + * 汽车租赁订单信息表 + */ + public static final String CARRENTAL_ORDER_INFO = "t_rentalOrderInfo"; + + /** + * 开锁基本信息表 + */ + public static final String UNLOCK_INFO = "t_ksjbxx"; + } + + /** + * 业务用户类型业务表映射 + *

+ * 加载业务人员类型与业务表名或者报警信息表名映射,大多数情况都应该从业务表常量{@link BusinessTables}里面去关联, 旅馆、网约房、从业人员比较特殊使用历史表或者快照表信息{@link AlarmInformationBusinessTables}。 + * 如果业务表常量{@link BusinessTables} + *

+ */ + public static final Map businessPersonTypeBusinessTableMapping; + + /** + * 功能操作代码常量 + */ + public static final class FunctionCodes { + /** + * 预警信息发布 + */ + public static final String EARLY_WARNING_PUBLISH = "earlyWarningPublish"; + /** + * 从业人员网约房企业端 + */ + public static final String PRACTITIONER_HOUSE_LTD = "practitionerWyfQyd"; + /** + * 可疑情况新增 + */ + public static final String SUSPICIOUS_SITUATION_ADD = "suspiciousSituationAdd"; + } + + /** + * API行业类别前缀 + */ + public static final class ApiIndustryCategoryPrefixes { + /** + * 公共/综合管理 + */ + public static final String PUBLIC = "/publicsystem"; + /** + * 门户 + */ + public static final String PORTAL = "/portal"; + /** + * 旅馆行业 + */ + public static final String HOTEL = "/hotel"; + /** + * 典当行业 + */ + public static final String PAWN = "/pawn"; + /** + * 二手车 + */ + public static final String ESCSYS = "/escsys"; + /** + * 印刷行业 + */ + public static final String PRINTING = "/printing"; + /** + * 保安行业 + */ + public static final String SECURITY = "/security"; + /** + * 开锁行业 + */ + public static final String UNLOCK = "/unlock"; + /** + * 系统管理 + */ + public static final String SYSTEM = "/system"; + /** + * 增材制造行业 + */ + public static final String ADDITIVE_MANUFACTURING = "/additive-manufacturing"; + /** + * 网约房行业 + */ + public static final String ONLINE_APPOINTMENT_ROOM = "/wyf"; + /** + * 网约房数据上报 + */ + public static final String SECURE_UPLOAD = "/secure/upload"; + /** + * 社区民警 + */ + public static final String COMMUNITY_POLICE = "/community-police"; + /** + * 印章行业 + */ + public static final String SEAL = "/seal"; + /** + * 机修行业 + */ + public static final String MECHANICAL_REPAIR = "/mechanical-repair"; + /** + * 娱乐行业 + */ + public static final String ENTERTAINMENT = "/entertainment"; + /** + * 二手车行业 + */ + public static final String USED_CAR = "/used-car"; + /** + * 旧货交易行业 + */ + public static final String USED_GOODS = "/used-goods"; + /** + * 废旧金属与回收行业 + */ + public static final String SCRAP_METAL = "/scrap-metal"; + /** + * 公交客运行业 + */ + public static final String PUBLIC_TRANSIT_TRANSPORT = "/public-transit-transport"; + /** + * 委托寄卖行业 + */ + public static final String CONSIGNMENT = "/consignment"; + /** + * 汽车租赁业 + */ + public static final String CARRENTAL = "/carrental"; + /** + * 移动警务端 + */ + public static final String MOBILEPOLICE = "/mobile-police"; + /** + * 报废机动车回收拆解车辆类型 + */ + public static final String VEHICLERECYCLING = "/vehiclerecycling"; + /** + * 业务资源管理系统 + */ + public static final String BUSINESS_RESOURCE_MANAGER = "/busi-res-manager"; + + /** + * 消息服务 + */ + public static final String MESSAGE = "/message"; + } + + /** + * 保安子分类 + */ + public static final class SecuritySubclass { + // 服务公司 + public static final String serviceCompany = "T01"; + // 培训机构 + public static final String trainingInstitutions = "T02"; + // 自行招用 + public static final String selfEmployment = "T03"; + // 跨区域 + public static final String crossregional = "T04"; + // 分公司 + public static final String branchServiceCompany = "T05"; + } + + /** + * 自行招用电子材料类型代码 + */ + public static final class ZxzyDzclLxdm { + // 单位备案申请 + public static final String dwbasqb = "dwbasqb"; + // 保安服务管理制度 + public static final String bafwglzd = "bafwglzd"; + // 岗位责任制度 + public static final String gwzrzd = "gwzrzd"; + // 保安员管理制度 + public static final String baglzd = "baglzd"; + // 其他电子材料 + public static final String qtcl = "qtcl"; + } + + /** + * 保安子分类 用户类别 + */ + public static final class SecuritySubclassUserType { + // 服务公司 + public static final String serviceCompanyUserType = "12"; + public static final String serviceCompanyPractitionerUserType = "13"; + // 培训机构 + public static final String trainingInstitutionsUserType = "22"; + public static final String trainingInstitutionsPractitionerUserType = "23"; + // 自行招用 + public static final String selfEmploymentUserType = "32"; + public static final String selfEmploymentPractitionerUserType = "33"; + // 跨区域 + public static final String crossregionalUserType = "42"; + public static final String crossregionalPractitionerUserType = "43"; + // 分公司 + public static final String branchServiceCompanyUserType = "52"; + public static final String branchServiceCompanyPractitionerUserType = "53"; + } + + /** + * 短信模板编号 + */ + public static final class SmsTemplateType { + /** + * 新开办公章企业 + */ + public static final String newlyEstablishedOfficialSealEnterprise = "1-2"; + /** + * 重置密码 + */ + public static final String resetPassword = "1-1"; + /** + * 新办企业刻章申请 + */ + public static final String applicationForNewEnterpriseSealEngraving = "2-1"; + /** + * 公章用章单位用户绑定 + */ + public static final String OfficialSealUserBinding = "3-1"; + /** + * 修改用户手机号 + */ + public static final String modifyUserPhoneNumber = "4-1"; + /** + * 用户注册 + */ + public static final String userRegistration = "6-1"; + /** + * 关注人员关注 + */ + public static final String followers = "5-4"; + /** + * 关注人员业务登记 + */ + public static final String followPersonnelBusinessRegistration = "5-5"; + /** + * 未成年人 + */ + public static final String minors = "5-3"; + /** + * 在逃人员 + */ + public static final String fugitives = "5-7"; + } + + + /** + * 网约房订单类型 + */ + public static final class OnlineHouseOrderType { + // 拟入住订单 + public static final String earlyguestOrder = "1"; + // 实际入住盯人 + public static final String guestOrder = "2"; + } + + public static final String CERTIFICATETYPE = "11";// 常用证件类型 身份证 + public static final String WYFPTDWBM_DEFAULT_VALUE = "5000";//网约房平台单位编码默认值(重庆5000) + public static final String DEFAULT_PASSWORD = "";//默认密码 + public static final String SM4_ENCRYPT_KEY = "7193027FD0ED1B4813BC94E348276422";//SM4加密解密密钥 + public static final String SM4_ENCRYPT_KEY_JKSQGL = "D55A2A415DB8B315616BA4E1A388006A"; + public static final String USER_PREFIX = "eup";//企业数据上报用户前缀 + + /** + * 加密字段使用的加密类型或者方式 + */ + public static final class EncryptFieldType { + /** + * 国产SM4加密算法 + */ + public static final String SM4 = "sm4"; + /** + * AES 加密算法 + */ + public static final String AES = "aes"; + } + + /** + * 用户类别对应企业信息键前缀 + */ + public static final String USER_ENTERPRISE_INFORMATION_KEY_PREFIX = "user_enterprise_information_"; + + /** + * 企业主分类对应 + */ + public static final Map enterpriseTypeDictMapping; + public static final Map enterpriseImportOne = new HashMap<>(); + public static final Map enterpriseImportTwo = new HashMap<>(); + public static final Map enterpriseByEconomicType = new HashMap<>(); + public static final Map enterpriseByIndustryType = new HashMap<>(); + + static { + // 初始化加载数据 + + // 加载业务人员类型与业务表名或者报警信息表名映射,大多数情况都应该从业务表常量里面去关联, 旅馆、网约房、从业人员比较特殊使用历史表或者快照表信息。 + businessPersonTypeBusinessTableMapping = new HashMap<>(); + // 旅馆国内旅客 + businessPersonTypeBusinessTableMapping.put(BusinessPersonTypes.HOTEL_DOMESTIC_PASSENGER, AlarmInformationBusinessTables.HOTEL_DOMESTIC_PASSENGER_HISTORY); + // 网约房房主 + businessPersonTypeBusinessTableMapping.put(BusinessPersonTypes.ONLINE_APPOINTMENT_ROOM_HOUSE_MANAGER, AlarmInformationBusinessTables.ONLINE_APPOINTMENT_ROOM_HOUSE_MANAGER); + // 网约房拟住人/预约入住人 + businessPersonTypeBusinessTableMapping.put(BusinessPersonTypes.ONLINE_APPOINTMENT_ROOM_ORDER_EARLY_GUEST, AlarmInformationBusinessTables.ONLINE_APPOINTMENT_ROOM_ORDER_EARLY_GUEST); + // 网约房订单 + businessPersonTypeBusinessTableMapping.put(BusinessPersonTypes.ONLINE_APPOINTMENT_ROOM_ORDER, AlarmInformationBusinessTables.ONLINE_APPOINTMENT_ROOM_ORDER); + // 网约房实际入住人 + businessPersonTypeBusinessTableMapping.put(BusinessPersonTypes.ONLINE_APPOINTMENT_ROOM_ORDER_GUEST, AlarmInformationBusinessTables.ONLINE_APPOINTMENT_ROOM_ORDER_GUEST); + // 公共从业人员 + businessPersonTypeBusinessTableMapping.put(BusinessPersonTypes.COMMON_PRACTITIONER, AlarmInformationBusinessTables.PUBLIC_PRACTITIONER); + // 娱乐场所 + businessPersonTypeBusinessTableMapping.put(BusinessPersonTypes.ENTERTAINMENT_PRACTITIONER_SIGN_IN, BusinessTables.ENTERTAINMENT_PRACTITIONER_SIGN_IN); + // 印章经办人 + businessPersonTypeBusinessTableMapping.put(BusinessPersonTypes.SEAL_AGENT, BusinessTables.SEAL_INFO); + // 印章取章人 + businessPersonTypeBusinessTableMapping.put(BusinessPersonTypes.SEAL_TAKE_SEAL_PERSON, BusinessTables.SEAL_INFO); + // 典当相关人员 + businessPersonTypeBusinessTableMapping.put(BusinessPersonTypes.PAWN_PERSON, BusinessTables.PAWN_INFO); + // 报废相关人员 + businessPersonTypeBusinessTableMapping.put(BusinessPersonTypes.SCRAP_PERSON, BusinessTables.SCRAP_INFO); + // 机修相关人员 + businessPersonTypeBusinessTableMapping.put(BusinessPersonTypes.MECHANICAL_REPAIR_PERSON_SCR, BusinessTables.MECHANICAL_REPAIR_INFO); + businessPersonTypeBusinessTableMapping.put(BusinessPersonTypes.MECHANICAL_REPAIR_PERSON_QCR, BusinessTables.MECHANICAL_REPAIR_INFO); + // 开锁 见证人员 + businessPersonTypeBusinessTableMapping.put(BusinessPersonTypes.UNLOCK_WITNESS_PERSON, BusinessTables.UNLOCK_INFO); + // 开锁 开锁人员 + businessPersonTypeBusinessTableMapping.put(BusinessPersonTypes.UNLOCK_PICKLOCK_PERSON, BusinessTables.UNLOCK_INFO); + + enterpriseTypeDictMapping = new HashMap<>(); + enterpriseTypeDictMapping.put("A", "dm_ywlb"); + enterpriseTypeDictMapping.put("J", "dm_ylcsfl(bzh)"); + enterpriseTypeDictMapping.put("E01", "dm_qyzfl(esc)"); + enterpriseTypeDictMapping.put("M", "dm_qyzfl(zczz)"); + enterpriseTypeDictMapping.put("F02", "dm_qyzfl(qczl)"); + enterpriseTypeDictMapping.put("C01", "dm_qyzfl(jdccj)"); + enterpriseTypeDictMapping.put("C02", "dm_jxqyfl"); + enterpriseTypeDictMapping.put("E03", "dm_qyzfl(ddy)"); + enterpriseTypeDictMapping.put("B", "dm_qyzfl(yz)"); + enterpriseTypeDictMapping.put("T", "dm_qyzfl_ba"); + enterpriseTypeDictMapping.put("E04", "dm_qyzfl(fjjs)"); + enterpriseTypeDictMapping.put("P", "dm_qyzfl(gjky)"); + enterpriseTypeDictMapping.put("Y01", "dm_qyzfl(ksy)"); + enterpriseTypeDictMapping.put("E02", "dm_qyzfl(esj)"); + enterpriseTypeDictMapping.put("D", "dm_qyzfl(ysy)"); + enterpriseTypeDictMapping.put("E07", "dm_qyzfl(wtjmy)"); + enterpriseTypeDictMapping.put("A06", "dm_qyzfl_fbzzs"); + +// 航空基地在阎良区,航天基地对应的组织机构是国家民用航天产业基地。浐灞生态区和港务浐灞区合了,合完之后叫做浐灞国际港 + enterpriseImportOne.put("碑林区", "610103000000"); + enterpriseImportOne.put("莲湖区", "610104000000"); + enterpriseImportOne.put("灞桥区", "610111000000"); + enterpriseImportOne.put("未央区", "610112000000"); + enterpriseImportOne.put("雁塔区", "610113000000"); + enterpriseImportOne.put("阎良区", "610114000000"); + enterpriseImportOne.put("临潼区", "610115000000"); + enterpriseImportOne.put("长安区", "610116000000"); + enterpriseImportOne.put("高陵区", "610126000000"); + enterpriseImportOne.put("鄠邑区", "610125000000"); + enterpriseImportOne.put("蓝田县", "610122000000"); + enterpriseImportOne.put("周至县", "610124000000"); + enterpriseImportOne.put("西咸新区", "01jxhsk6aqkkr24jekm3gj0yt2"); + enterpriseImportOne.put("高新区", "01jxhsk6apb51fdgz0529t89as"); + enterpriseImportOne.put("经济技术开发区", "01jyx7spejatvpf5jnkf1tbm1t"); + enterpriseImportOne.put("曲江新区", "01jxhsk6aqqt2fsz8y0y32bcss"); + enterpriseImportOne.put("航空基地", "610114000000"); + enterpriseImportOne.put("航天基地", "01jxhsk6ar05v20p0bed8pqe9q"); + enterpriseImportOne.put("浐灞生态区", "01jyx7sp9pbk2drgfj909z3wvz"); + enterpriseImportOne.put("国际港务区", "01jxhsk6apgw6habpjmy56vqnh"); + enterpriseImportOne.put("沣东新城", "01jygf77w0s8na0732hm8f1kds"); + enterpriseImportOne.put("沣西新城", "01jzeyqymw0chcsdetkhzdr2ka"); + enterpriseImportOne.put("空港新城", "01jzeyqym2j899g5mqb5hnmz64"); + enterpriseImportOne.put("泾河新城", "01jzeyqykkd2mkbv8mgs99tp7y"); + enterpriseImportOne.put("秦汉新城", "01jzeyqywrkza2h7a18p7xmz0t"); + enterpriseImportOne.put("国家民用航天产业基地", "01jxhsk6ar05v20p0bed8pqe9q"); + enterpriseImportOne.put("园办", "01jzeyqyp6j2q4xe6sjvdffg8w"); + enterpriseImportOne.put("新城区", "610102000000"); + enterpriseImportOne.put("港务浐灞区", "01jyx7sp9pbk2drgfj909z3wvz"); + + enterpriseImportTwo.put("610103", "610103000000");//碑林区 + enterpriseImportTwo.put("610104", "610104000000");//莲湖区 + enterpriseImportTwo.put("610111", "610111000000");//灞桥区 + enterpriseImportTwo.put("610112", "610112000000");//未央区 + enterpriseImportTwo.put("610113", "610113000000");//雁塔区 + enterpriseImportTwo.put("610114", "610114000000");//阎良区 + enterpriseImportTwo.put("610115", "610115000000");//临潼区 + enterpriseImportTwo.put("610116", "610116000000");//长安区 + enterpriseImportTwo.put("610117", "610126000000");//高陵区 + enterpriseImportTwo.put("610118", "610125000000");//鄠邑区 + enterpriseImportTwo.put("610122", "610122000000");//蓝田县 + enterpriseImportTwo.put("610124", "610124000000");//周至县 + enterpriseImportTwo.put("610166", "01jxhsk6aqkkr24jekm3gj0yt2");//西咸新区 + enterpriseImportTwo.put("610161", "01jxhsk6apb51fdgz0529t89as");//高新区 + enterpriseImportTwo.put("610162", "01jyx7spejatvpf5jnkf1tbm1t");//经开区 + enterpriseImportTwo.put("610163", "01jxhsk6aqqt2fsz8y0y32bcss");//曲江新区 + enterpriseImportTwo.put("610164", "610114000000");//航空基地 + enterpriseImportTwo.put("610165", "01jxhsk6ar05v20p0bed8pqe9q");//航天基地 + enterpriseImportTwo.put("610167", "01jyx7sp9pbk2drgfj909z3wvz");//浐灞生态区 + enterpriseImportTwo.put("610168", "01jxhsk6apgw6habpjmy56vqnh");//国际港务区 + + enterpriseByEconomicType.put("国有控股","01"); + enterpriseByEconomicType.put("集体控股","02"); + enterpriseByEconomicType.put("私人控股","07"); + enterpriseByEconomicType.put("港澳台商控股","09"); + enterpriseByEconomicType.put("外商控股","11"); + enterpriseByEconomicType.put("其他","12"); + + enterpriseByIndustryType.put("纺织","6"); + enterpriseByIndustryType.put("机械","6"); + enterpriseByIndustryType.put("建材","6"); + enterpriseByIndustryType.put("轻工","6"); + enterpriseByIndustryType.put("烟草","6"); + enterpriseByIndustryType.put("冶金","6"); + enterpriseByIndustryType.put("有色","6"); + enterpriseByIndustryType.put("商贸","7"); + + } + + + /** + * 验证规则 + */ + public static final class ValidationRule { + /** + * 身份证18位正则表达式 + */ + public static final String SHENFENZHENGPATTERN = "((11|12|13|14|15|21|22|23|31|32|33|34|35|36|37|41|42|43|44|45|46|50|51|52|53|54|61|62|63|64|65|71|81|82|91)\\d{4})((((19|20)(([02468][048])|([13579][26]))0229))|((20[0-9][0-9])|(19[0-9][0-9]))((((0[1-9])|(1[0-2]))((0[1-9])|(1\\d)|(2[0-8])))|((((0[1,3-9])|(1[0-2]))(29|30))|(((0[13578])|(1[02]))31))))((\\d{3}(x|X))|(\\d{4}))"; + /** + * 手机号正则表达式 + */ + public static final String MOBILEPATTERN = "^((13[0-9])|(15[0-9])|(18[0-9])|(14[0-9]))\\d{8}$"; + } + + public static final class SecurityTabsConstant { + + /** + * 企业基本信息 + */ + public static final String ENTERPRISE_TAB = "enterpriseTab"; + /** + * 申请人信息 + */ + public static final String BAGSSQRXX_TAB = "bagsSqrxxTab"; + /** + * 申请单位信息 + */ + public static final String PXJGSQDWXX_TAB = "pxjgSqdwxxTab"; + /** + * 股东及出资金额信息 + */ + public static final String GDJCZEXX_TAB = "gdjczexxTab"; + /** + * 培训机构师资信息 + */ + public static final String PXJGSZXX_TAB = "pxjgszxxTab"; + /** + * 单位法人信息 + */ + public static final String FRXX_TAB = "frxxTab"; + /** + * 单位保安负责人信息 + */ + public static final String FZRXX_TAB = "fzrxxTab"; + /** + * 备案保安服务区域信息 + */ + public static final String BAFWQY_TAB = "bafwqyTab"; + /** + * 单位经营负责人信息 + */ + public static final String DWJYFZR_TAB = "dwjyfzrTab"; + /** + * 保安服务对象信息 + */ + public static final String BAFWDX_TAB = "bafwdxTab"; + /** + * 单位保安员信息 + */ + public static final String DWBAY_TAB = "dwbayTab"; + /** + * 主要管理人员信息 + */ + public static final String ZYGLRY_TAB = "zyglryTab"; + /** + * 电子材料信息 + */ + public static final String DZCL_TAB = "dzclTab"; + } + + /** + * 历史数据上传业务主表信息 + */ + public static final class UploadBusinessTables { + /** + * 印章刻制企业信息表 + */ + public static final String SEAL_ENGRAVING_ENT_INFO = "t_qyjbxx"; + /** + * 印章使用企业表 + */ + public static final String SEAL_END_USER_INFO = "t_seal_end_user"; + /** + * 从业人员信息表 + */ + public static final String PRACTITIONER_INFO = "t_qyryxx"; + /** + * 印章订单信息表 + */ + public static final String SEAL_ORDER_INFO = "t_seal_order"; + } + + /** + * 常用变量 + */ + public static final class CommonlyUsedVariables { + public static final String nationality = "CA02";//工商数据用到的国籍字典code + public static final String chineseCode = "CHN";//中国代码 + public static final String businessType = "CA16";//企业经济类型 + + } + + /** + * 一个通用的是否类型常量(数字) + */ + public static final class YesNoNum { + /** + * 是 + */ + public static final String YES = "1"; + /** + * 否 + */ + public static final String NO = "0"; + } + + public static final class DictDisplay { + public static final Map zjlx_map_gt = new HashMap<>(); + public static final Map zjlx_map_qy = new HashMap<>(); + public static final Map gj_map_qy = new HashMap<>(); + public static final Map yzjxbzMap = new HashMap<>(); + public static final Map hylbMap = new HashMap<>(); + public static final Map yyztMap = new HashMap<>(); + public static final Map dtfxdjMap = new HashMap<>(); + public static final Map qyfjMap = new HashMap<>();//企业分级 + public static final Map qyflMap = new HashMap<>();//企业分类 + + static { + // 证件类型 **/ + zjlx_map_qy.put("10", "99");//通行证 + zjlx_map_qy.put("09", "99");//暂住证 + zjlx_map_qy.put("05", "93");//外国(地区)护照 + zjlx_map_qy.put("13", "99");//出入境管理证 + zjlx_map_qy.put("07", "99");//回乡证 + zjlx_map_qy.put("99", "99");//其它有效身份证件 + zjlx_map_qy.put("12", "99");//台胞证 + zjlx_map_qy.put("14", "99");//香港居民身份证 + zjlx_map_qy.put("15", "99");//澳门居民身份证 + zjlx_map_qy.put("01", "11");//中华人民共和国居民身份证 + zjlx_map_qy.put("06", "99");//居民证 + zjlx_map_qy.put("03", "90");//中华人民共和国军官证 + zjlx_map_qy.put("11", "99");//工作证 + zjlx_map_qy.put("04", "91");//中华人民共和国警官证 + zjlx_map_qy.put("08", "99");//居留证 + // 个体证件类型 **/ + zjlx_map_gt.put("90", "99");//其他有效身份证件 + zjlx_map_gt.put("55", "513");//港澳居民来往内地通行证 + zjlx_map_gt.put("52", "94");//香港(永久性)居民身份证 + zjlx_map_gt.put("30", "91");//中华人民共和国警官证 + zjlx_map_gt.put("54", "94");//澳门(永久性)居民身份证 + zjlx_map_gt.put("20", "90");//中华人民共和国军官证 + zjlx_map_gt.put("10", "11");//中华人民共和国居民身份证 + zjlx_map_gt.put("53", "93");//澳门特别行政区护照 + zjlx_map_gt.put("51", "93");//香港特别行政区护照 + zjlx_map_gt.put("58", "99");//台湾农民身份有关证明 + zjlx_map_gt.put("60", "13");//户口薄 + zjlx_map_gt.put("40", "93");//外国(地区)护照 + zjlx_map_gt.put("57", "99");//台湾居民来往大陆通行证 + zjlx_map_gt.put("56", "94");//台湾居民身份证 + // 工商企业法人国籍 + gj_map_qy.put("40", "AUT");//奥地利 + gj_map_qy.put("392", "JPN");//日本 + gj_map_qy.put("100", "BGR");//保加利亚 + gj_map_qy.put("372", "IRL");//爱尔兰 + gj_map_qy.put("208", "DNK");//丹麦 + gj_map_qy.put("196", "CYP");//塞浦路斯 + gj_map_qy.put("702", "SGP");//新加坡 + gj_map_qy.put("368", "IRQ");//伊拉克 + gj_map_qy.put("158", "CTN");//中国台湾 + gj_map_qy.put("752", "SWE");//瑞典 + gj_map_qy.put("554", "NZL");//新西兰 + gj_map_qy.put("826", "GBR");//英国 + gj_map_qy.put("380", "ITA");//意大利 + gj_map_qy.put("909", "RUS");//俄罗斯 + gj_map_qy.put("764", "THA");//泰国 + gj_map_qy.put("446", "MAC");//中国澳门 + gj_map_qy.put("410", "KOR");//韩国 + gj_map_qy.put("250", "FRA");//法国 + gj_map_qy.put("344", "HKG");//中国香港 + gj_map_qy.put("156", "CHN");//中国 + gj_map_qy.put("724", "ESP");//西班牙 + gj_map_qy.put("124", "CAN");//加拿大 + gj_map_qy.put("356", "IND");//印度 + gj_map_qy.put("458", "MYS");//马来西亚 + gj_map_qy.put("144", "LKA");//斯里兰卡 + gj_map_qy.put("278", "DEU");//德国 + gj_map_qy.put("840", "USA");//美国 + gj_map_qy.put("36", "AUS");//澳大利亚 + + yzjxbzMap.put("N", "未核对"); + yzjxbzMap.put("Y", "核对通过"); + yzjxbzMap.put("Z", "核对未通过"); + dtfxdjMap.put("1", "零风险"); + dtfxdjMap.put("2", "低风险"); + dtfxdjMap.put("3", "中风险"); + dtfxdjMap.put("4", "高风险"); + yyztMap.put("11", "营业"); + yyztMap.put("12", "停业(歇业)"); + yyztMap.put("13", "关闭"); + hylbMap.put("1", "矿山"); + hylbMap.put("2", "危险化学品生产"); + hylbMap.put("3", "危险化学品经营(仓储)"); + hylbMap.put("4", "危险化学品经营(无仓储)"); + hylbMap.put("5", "加油站"); + hylbMap.put("6", "工业企业"); + hylbMap.put("7", "商贸企业"); + hylbMap.put("8", "安全生产中介机构"); + hylbMap.put("9", "安全生产检验检测机构"); + hylbMap.put("10", "安全生产培训机构"); + hylbMap.put("99", "其他"); + qyfjMap.put("1", "省级"); + qyfjMap.put("2", "市级"); + qyfjMap.put("3", "区县级"); + qyfjMap.put("4", "镇街级"); + qyflMap.put("1", "A"); + qyflMap.put("2", "B"); + qyflMap.put("3", "C"); + qyflMap.put("4", "D"); + } + } + + /** + * 保安业务类别 + */ + public static final class securityCategoryCode { + /** + * 市级 + */ + public static final String cityLevel = "501"; + /** + * 市级监管 + */ + public static final String municipalSupervision = "531"; + /** + * 县级 + */ + public static final String countyLevel = "511"; + /** + * 县级监管 + */ + public static final String countyLevelSupervision = "541"; + /** + * 出所 + */ + public static final String localPoliceStation = "521"; + } + + public static final String SEAL_KEY = "DBA5AD03AE03A8681C03441EFA150BE4";//印章密钥 + // + + /** + * 缓存键 + */ + @Deprecated + public static final class CacheKeys { + /** + * 机构信息缓存 + */ + public static final class Jurisdiction { + /** + * 全部机构信息 + */ + public static final String all = "cache.jurisdictions.all"; + public static final String query = "cache.jurisdictions.query"; + } + + /** + * 字典信息 + */ + public static final class DictItem { + /** + * 字典项查询 + */ + public static final String query = "cache.dict-items.query"; + } + } + + /** + * token uid类型 + */ + public static final class UidTypes { + /** + * 微信openid + */ + public static final String WECHAT_OPEN_ID = "WXOP_"; + /** + * 微信unionid + */ + public static final String WECHAT_UNION_ID = "WXUN_"; + /** + * 系统微信id + */ + public static final String WX_ID = "WX_"; + /** + * 系统用户 + */ + public static final String USER_ID = ""; + /** + * 虚拟用户 + */ + public static final String VIRTUAL_USER_ID = "VT_"; + + /** + * 无需验证uid类型 + */ + public static final String[] NON_VALIDATE_TYPES = {WECHAT_OPEN_ID, WECHAT_UNION_ID, VIRTUAL_USER_ID, WX_ID}; + } + + /** + * 查询提示(数据库提示) + */ + public static final String QUERY_HINT_DB_HINT = "query_hint_db_hint"; + public static final String government_exemption_type = "gzq"; + + /** + * 全局查询限制 + */ + public static final int GLOBAL_QUERY_LIMIT = 3000; + + +} diff --git a/server/src/main/java/com/aisino/iles/common/util/DateUtil.java b/server/src/main/java/com/aisino/iles/common/util/DateUtil.java new file mode 100644 index 0000000..b4ef9c0 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/util/DateUtil.java @@ -0,0 +1,294 @@ +package com.aisino.iles.common.util; + +import java.text.SimpleDateFormat; +import java.time.*; +import java.util.Calendar; +import java.util.Date; + +/** + * Created by Administrator on 2017/7/10. + */ +public class DateUtil { + /** + * 获得当天零时零分零秒 + * + * @return + */ + public static Date getCurDate() { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(new Date()); + calendar.set(Calendar.HOUR, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + return calendar.getTime(); + } + + public static Date str2Date(String dateStr, String format) { + if (dateStr == null) { + return null; + } + try { + SimpleDateFormat sdf = new SimpleDateFormat(format); + return sdf.parse(dateStr); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + public static String getCurDateTime() { + SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + return f.format(new Date()); + } + + public static String getDateStr(Date date) { + if (date == null) { + return ""; + } + SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd"); + return f.format(date); + } + + public static String date2Str(Date date, String type) { + if (date == null || type == null) { + return ""; + } + SimpleDateFormat f = new SimpleDateFormat(type); + return f.format(date); + } + + /** + * 获取最后nday天的时间范围 + * + * @param nday 如1天 + * @return 2017-10-01, 2017-10-02 (今天) + */ + public static LocalDate[] getNDayToNow1(int nday, LocalDate[] dates) { + LocalDate to = LocalDate.now(); + dates[1] = to; + dates[0] = to.minusDays(Long.valueOf(nday)); + return dates; + } + + /** + * 获取最后nday天的时间范围 + * + * @param nday 如1天 + * @return 2017-10-01 00:00:00, 2017-10-02 23:59:59(今天) + */ + public static LocalDateTime[] getNDayToNow2(int nday, LocalDateTime[] dates) { + LocalDateTime to = LocalDateTime.now(); + dates[1] = to; + dates[0] = to.minusDays(Long.valueOf(nday)); + return dates; + } + + /** + * 获取最后nday天的时间范围 + * + * @param nday 如1天 + * @return 2017-10-01 00:00:00, 2017-10-02 23:59:59(今天) + */ + public static Date[] getNDayToNow(int nday) { + Date[] dates = new Date[2]; + Date now = new Date(); + String todayDate = getDateStr(now); + String toStr = todayDate + " 23:59:59"; + dates[1] = str2Date(toStr, "yyyy-MM-dd HH:mm:ss"); + + Calendar cal = Calendar.getInstance(); + cal.setTime(now); + cal.add(Calendar.DATE, -1 * nday); + Date fromDate = cal.getTime(); + String fromdayDate = getDateStr(fromDate); + String fromStr = fromdayDate + " 00:00:00"; + dates[0] = str2Date(fromStr, "yyyy-MM-dd HH:mm:ss"); + return dates; + } + + public static Date[] getYesterdayRange() { + Date[] dates = new Date[2]; + Date now = new Date(); + Calendar cal = Calendar.getInstance(); + cal.setTime(now); + cal.add(Calendar.DATE, -1); + Date fromDate = cal.getTime(); + String fromdayDate = getDateStr(fromDate); + String fromStr = fromdayDate + " 00:00:00"; + dates[0] = str2Date(fromStr, "yyyy-MM-dd HH:mm:ss"); + String toStr = fromdayDate + " 23:59:59"; + dates[1] = str2Date(toStr, "yyyy-MM-dd HH:mm:ss"); + return dates; + } + + public static String[] getYesterdayRangeStr() { + String[] dates = new String[2]; + Date now = new Date(); + Calendar cal = Calendar.getInstance(); + cal.setTime(now); + cal.add(Calendar.DATE, -1); + Date fromDate = cal.getTime(); + String fromdayDate = getDateStr(fromDate); + String fromStr = fromdayDate + " 00:00:00"; + dates[0] = fromStr; + String toStr = fromdayDate + " 23:59:59"; + dates[1] = toStr; + return dates; + } + + /** + * 自定义格式获取当前时间 + * + * @param format + * @return + */ + public static String getCurrentDate(String format) { + return new SimpleDateFormat(format).format(new Date()); + } + + /** + * @Description 自定义格式获取三年前的那一年开始时间 + * @Author Jnno + * @Version 1.0 + **/ + public static String getThreadDate(String format) { + return convertDateToString(getCurrYearFirst(getThredYear()), format); + } + + /** + * @Description 获取三年前的年份 + * @Author Jnno + * @Version 1.0 + **/ + public static int getThredYear() { + Calendar date = Calendar.getInstance(); + + int year = date.get(Calendar.YEAR); + + return year - 2; + } + + /** + * 获取某年第一天日期 + * + * @param year 年份 + * @return Date + */ + public static Date getCurrYearFirst(int year) { + Calendar calendar = Calendar.getInstance(); + calendar.clear(); + calendar.set(Calendar.YEAR, year); + Date currYearFirst = calendar.getTime(); + return currYearFirst; + } + + /** + * 获取某年最后一天日期 + * + * @param year 年份 + * @return Date + */ + public static Date getCurrYearLast(int year) { + Calendar calendar = Calendar.getInstance(); + calendar.clear(); + calendar.set(Calendar.YEAR, year); + calendar.roll(Calendar.DAY_OF_YEAR, -1); + Date currYearLast = calendar.getTime(); + + return currYearLast; + } + + /** + * @Description 根据时间转换成制定格式的字符串 + * @Author Jnno + * @Version 1.0 + **/ + public static String convertDateToString(Date date, String format) { + try { + return new SimpleDateFormat(format).format(date); + } catch (Exception e) { + return null; + } + } + + //获得当前日期字符串 + //String style : 返回形式 例如"yyyy-MM-dd"、"yyyy-MM-dd HH:mm:ss"、"yyyy-MM-dd hh:mm:ss" + public static String getCurrentDateString(String style) { + return new SimpleDateFormat(style).format(new Date()); + } + + /** + * 按照给定的日期,转换成格式 "yyyy-MM-dd" 的字符串. + * + * @param date java.util.Date + * @return short format datetime + */ + public static String shortFmt(Date date) { + String strDate = ""; + if (date != null) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + strDate = sdf.format(date); + } + return strDate; + } + + /** + * 当前时间转换成格式 "yyyy-MM-dd HH:mm:ss" 的字符串. + * + * @param date java.util.Date + * @return short format datetime + */ + + public static String shortFmt(java.sql.Timestamp date) { + String strDate = ""; + if (date != null) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + strDate = sdf.format(date); + } + return strDate; + } + + /** + * localDateTime的日期转Date类型 + * + * @param localDateTime + * @return + */ + public static Date asDate(LocalDateTime localDateTime) { + return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); + } + + /** + * Date的日期转localDateTime类型 + * + * @param date + * @return + */ + public static LocalDateTime asLocalDateTime(Date date) { + return Instant.ofEpochMilli(date.getTime()).atZone(ZoneId.systemDefault()).toLocalDateTime(); + } + + + /** + * 计算两个日期之间的天数 + * + * @param start 开始日期 + * @param end 结束日期 + * @return 天数 + */ + public static long days(LocalDate start, LocalDate end) { + return end.toEpochDay() - start.toEpochDay(); + } + + /** + * 计算两个日期年份差 + * + * @param start 开始日期 + * @param end 结束日期 + * @return 布尔值,超过或等于years则返回true + */ + public static boolean compareDate(LocalDate start, LocalDate end, Integer years) { + return end.toEpochDay()-start.plusYears(years).toEpochDay() < 0; + } + +} diff --git a/server/src/main/java/com/aisino/iles/common/util/DepartmentUtil.java b/server/src/main/java/com/aisino/iles/common/util/DepartmentUtil.java new file mode 100644 index 0000000..99b466c --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/util/DepartmentUtil.java @@ -0,0 +1,48 @@ +package com.aisino.iles.common.util; + +public class DepartmentUtil { + /** + * 采集点用户根据不同省市处理部门编码 + * + * @param deptCode + * @return + */ + public static String doWithDepartmentCode(String deptCode) { + deptCode = StringUtils.trimEven0(deptCode); + if (deptCode != null) { + if (deptCode.indexOf("CJD") != -1) { + if (deptCode.substring(3).length() <= 4) + deptCode = deptCode.substring(3); + else { + //deptCode = StringUtil.add0(deptCode.substring(3), 4).substring(0,4); + if (!deptCode.substring(3).substring(0, 2).equals("11")//不属于四个直辖市 + && !deptCode.substring(3).substring(0, 2).equals("12") + && !deptCode.substring(3).substring(0, 2).equals("31") + && !deptCode.substring(3).substring(0, 2).equals("50")) { + deptCode = deptCode.substring(3).substring(0, 4); + } else//属于四个直辖市时,取前6位 20101018mds + { + if (deptCode.substring(3).length() < 6) { + deptCode = deptCode.substring(3).substring(0, 2); + } else { + deptCode = deptCode.substring(3).substring(0, 6); + } + } + } + } + } + return deptCode; + } + + /** + * 检验机构是否是 直辖市 + */ + public static boolean departIsZxs(String deptCode) { + boolean iszxs = false; + if (deptCode.startsWith("11") || deptCode.startsWith("12") + || deptCode.startsWith("31") || deptCode.startsWith("50")) { + iszxs = true; + } + return iszxs; + } +} \ No newline at end of file diff --git a/server/src/main/java/com/aisino/iles/common/util/ExportExcelUtil.java b/server/src/main/java/com/aisino/iles/common/util/ExportExcelUtil.java new file mode 100644 index 0000000..39933f4 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/util/ExportExcelUtil.java @@ -0,0 +1,345 @@ +package com.aisino.iles.common.util; + +import com.aisino.iles.common.iface.ExportExcelHelper; +import com.aisino.iles.common.model.ExportExcelParam; +import jxl.CellType; +import jxl.DateCell; +import jxl.Sheet; +import jxl.Workbook; +import jxl.write.*; + +import jakarta.servlet.http.HttpServletResponse; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; + +@Slf4j +public class ExportExcelUtil { + + public static ByteArrayOutputStream createExcel(List labels, List props, String fileName, List dataList) throws Exception { + if (labels.get(0) == null && props.get(0) == null) { + labels = labels.subList(1, labels.size()); + props = props.subList(1, props.size()); + } + //创建输出流 + ByteArrayOutputStream os = new ByteArrayOutputStream(); + WritableWorkbook book = Workbook.createWorkbook(os); + //表头 + WritableFont wf = new WritableFont(WritableFont.createFont("宋体"), 15, WritableFont.BOLD); + WritableCellFormat wcf = new WritableCellFormat(wf); + wcf.setAlignment(jxl.format.Alignment.CENTRE); + wcf.setBorder(jxl.format.Border.ALL, jxl.format.BorderLineStyle.THIN); + //标题 + WritableFont wf1 = new WritableFont(WritableFont.createFont("宋体"), 10, WritableFont.BOLD); + WritableCellFormat wcf1 = new WritableCellFormat(wf1); + wcf1.setAlignment(jxl.format.Alignment.CENTRE); + wcf1.setVerticalAlignment(jxl.format.VerticalAlignment.CENTRE); + //内容 + WritableFont wf2 = new WritableFont(WritableFont.createFont("宋体"), 10); + WritableCellFormat wcf2 = new WritableCellFormat(wf2); + wcf2.setAlignment(jxl.format.Alignment.CENTRE); + wcf2.setVerticalAlignment(jxl.format.VerticalAlignment.CENTRE); + //获得Excel表的第一页 + WritableSheet sheet = book.createSheet("第一页", 0); + sheet.mergeCells(0, 0, labels.size() - 1, 0); + //表标题(第一行)(列号,行号,值,样式) + Label label = new Label(0, 0, fileName, wcf); + sheet.addCell(label); + + int[] maxlengthArr = new int[labels.size()]; + //表头(第二行)(列号,行号,值,样式) + for (int n = 0; n < labels.size(); n++) { + sheet.addCell(new Label(n, 1, labels.get(n), wcf1)); + if (labels.get(n) != null) { + maxlengthArr[n] = length(labels.get(n)); + } else { + maxlengthArr[n] = 0; + } + } + //内容(列号,行号,值,样式) + for (int i = 0; i < dataList.size(); i++) { + Object obj = dataList.get(i); + for (int n = 0; n < labels.size(); n++) { + String value = InvokeUtility.getFieldValueByName(props.get(n), obj) == null ? "" : InvokeUtility.getFieldValueByName(props.get(n), obj).toString(); + sheet.addCell(new Label(n, i + 2, value, wcf2)); + if (value != null) { + maxlengthArr[n] = maxlengthArr[n] < length(value) ? length(value) : maxlengthArr[n]; + } + } + } + //设置列宽度 + for (int i = 0; i < maxlengthArr.length; i++) { + sheet.setColumnView(i, maxlengthArr[i] + 2); //设置col显示样式 + } + + book.write(); + book.close(); + return os; //book写入到输出流中关闭book将此输出流输入到输入流中返回给jsp页面一个输入流供客户端下载。 + } + + public static ByteArrayOutputStream createExcel(List labels, List props, String fileName, ExportExcelHelper exportExcelHelper) throws WriteException, IOException { + if (labels.get(0) == null && props.get(0) == null) { + labels = labels.subList(1, labels.size()); + props = props.subList(1, props.size()); + } + //创建输出流 + ByteArrayOutputStream os = new ByteArrayOutputStream(); + WritableWorkbook book = Workbook.createWorkbook(os); + exportExcelHelper.writeToBook(book, labels, props, fileName); + return os; //book写入到输出流中关闭book将此输出流输入到输入流中返回给jsp页面一个输入流供客户端下载。 + } + + public static void exportExcel(HttpServletResponse response, ExportExcelParam excelParam, ExportExcelHelper exportExcelHelper) throws IOException, WriteException { + ByteArrayOutputStream excelOutStream = createExcel(excelParam.getLabels(), excelParam.getProps(), excelParam.getFileName(), exportExcelHelper); + if (excelOutStream != null) { + response.setContentType("application/ms-excel;charset=UTF-8"); + response.setHeader("Content-Disposition", "attachment;filename=" + .concat(String.valueOf(URLEncoder.encode(excelParam.getFileName() + ".xls", "UTF-8")))); + int contentLength = excelOutStream.size(); + response.setHeader("content-length", contentLength + ""); + excelOutStream.writeTo(response.getOutputStream()); + excelOutStream.close(); + } + } + + public static void exportExcel(HttpServletResponse response, ExportExcelParam excelParam, List domesticPassengerList) { + try { + // 尝试加密数据 +// domesticPassengerList = (List) domesticPassengerList.stream().peek(SM4Util::objectSm4Encrypt).collect(Collectors.toList()); + ByteArrayOutputStream tmp = createExcel(excelParam.getLabels(), excelParam.getProps(), excelParam.getFileName(), domesticPassengerList); + OutputStream out = response.getOutputStream(); + response.setContentType("application/ms-excel;charset=UTF-8"); + response.setHeader("Content-Disposition", "attachment;filename=" + .concat(String.valueOf(URLEncoder.encode(excelParam.getFileName() + ".xls", "UTF-8")))); + Integer contentLength = tmp.size(); + response.setHeader("content-length", contentLength + ""); + out.write(tmp.toByteArray()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static int length(String s) { + if (s == null) + return 0; + char[] c = s.toCharArray(); + int len = 0; + for (int i = 0; i < c.length; i++) { + len++; + if (!isLetter(c[i])) { + len++; + } + } + return len; + } + + public static boolean isLetter(char c) { + int k = 0x80; + return c / k == 0 ? true : false; + } + + public static List parseExcelToList(InputStream stream, String fileName) throws Exception { + if (fileName.endsWith(".xlsx")) + return parseExcelToListByXlsx(stream); + else + return parseExcelToListByXls(stream); + } + + private static List parseExcelToListByXls(InputStream stream) { + List list = new ArrayList<>(); + try { + //获取Excel文件对象 + Workbook rwb = Workbook.getWorkbook(stream); + //获取文件的指定工作表 默认的第一个 + Sheet sheet = rwb.getSheet(0); + // 获取表头行,用于确定列数 + int columnCount = sheet.getColumns(); + jxl.Cell cell = null; + //行数(表头的目录不需要,从1开始) + for (int i = 0; i < sheet.getRows(); i++) { + //创建一个数组 用来存储每一列的值,根据表头列数创建数组 + Object[] str = new Object[columnCount]; + //列数 + for (int j = 0; j < columnCount; j++) { + //获取第i行,第j列的值 + cell = sheet.getCell(j, i); + if (cell.getType() == CellType.DATE) { + DateCell dc = (DateCell) cell; + str[j] = dc.getDate(); + } else { + str[j] = StringUtils.isNotEmpty(cell.getContents()) ? cell.getContents().trim() : ""; + } + } + //把刚获取的列存入list + list.add(str); + } + return list; + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("解析excel文件异常,message=" + e.getMessage()); + } + } + + /** + * 导出excel,目标数据的类型为Map + * + * @param response + * @param excelParam + * @param domesticPassengerList + */ + public static void exportExcelTheDataIsMap(HttpServletResponse response, ExportExcelParam excelParam, List domesticPassengerList) { + try { + // 尝试加密数据 + domesticPassengerList = (List) domesticPassengerList.stream().collect(Collectors.toList()); + ByteArrayOutputStream tmp = createExcelTheDataIsMap(excelParam.getLabels(), excelParam.getProps(), excelParam.getFileName(), domesticPassengerList); + OutputStream out = response.getOutputStream(); + response.setContentType("application/ms-excel;charset=UTF-8"); + response.setHeader("Content-Disposition", "attachment;filename=" + .concat(String.valueOf(URLEncoder.encode(excelParam.getFileName() + ".xls", "UTF-8")))); + Integer contentLength = tmp.size(); + response.setHeader("content-length", contentLength + ""); + out.write(tmp.toByteArray()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * @param labels + * @param props + * @param fileName + * @param dataList map类型的数据 + * @return + * @throws Exception + */ + public static ByteArrayOutputStream createExcelTheDataIsMap(List labels, List props, String fileName, List dataList) throws Exception { + if (labels.get(0) == null && props.get(0) == null) { + labels = labels.subList(1, labels.size()); + props = props.subList(1, props.size()); + } + //创建输出流 + ByteArrayOutputStream os = new ByteArrayOutputStream(); + WritableWorkbook book = Workbook.createWorkbook(os); + //表头 + WritableFont wf = new WritableFont(WritableFont.createFont("宋体"), 15, WritableFont.BOLD); + WritableCellFormat wcf = new WritableCellFormat(wf); + wcf.setAlignment(jxl.format.Alignment.CENTRE); + wcf.setBorder(jxl.format.Border.ALL, jxl.format.BorderLineStyle.THIN); + //标题 + WritableFont wf1 = new WritableFont(WritableFont.createFont("宋体"), 10, WritableFont.BOLD); + WritableCellFormat wcf1 = new WritableCellFormat(wf1); + wcf1.setAlignment(jxl.format.Alignment.CENTRE); + wcf1.setVerticalAlignment(jxl.format.VerticalAlignment.CENTRE); + //内容 + WritableFont wf2 = new WritableFont(WritableFont.createFont("宋体"), 10); + WritableCellFormat wcf2 = new WritableCellFormat(wf2); + wcf2.setAlignment(jxl.format.Alignment.CENTRE); + wcf2.setVerticalAlignment(jxl.format.VerticalAlignment.CENTRE); + //获得Excel表的第一页 + WritableSheet sheet = book.createSheet("第一页", 0); + sheet.mergeCells(0, 0, labels.size() - 1, 0); + //表标题(第一行)(列号,行号,值,样式) + Label label = new Label(0, 0, fileName, wcf); + sheet.addCell(label); + + int[] maxlengthArr = new int[labels.size()]; + //表头(第二行)(列号,行号,值,样式) + for (int n = 0; n < labels.size(); n++) { + sheet.addCell(new Label(n, 1, labels.get(n), wcf1)); + if (labels.get(n) != null) { + maxlengthArr[n] = length(labels.get(n)); + } else { + maxlengthArr[n] = 0; + } + } + //内容(列号,行号,值,样式) + for (int i = 0; i < dataList.size(); i++) { + Map map = (Map) dataList.get(i); + + for (int n = 0; n < labels.size(); n++) { + String value = map.get(props.get(n)) == null ? "" : map.get(props.get(n)).toString(); +// String value = InvokeUtility.getFieldValueByName(props.get(n), obj) == null ? "" : InvokeUtility.getFieldValueByName(props.get(n), obj).toString(); + sheet.addCell(new Label(n, i + 2, value, wcf2)); + if (value != null) { + maxlengthArr[n] = maxlengthArr[n] < length(value) ? length(value) : maxlengthArr[n]; + } + } + } + //设置列宽度 + for (int i = 0; i < maxlengthArr.length; i++) { + sheet.setColumnView(i, maxlengthArr[i] + 2); //设置col显示样式 + } + + book.write(); + book.close(); + return os; //book写入到输出流中关闭book将此输出流输入到输入流中返回给jsp页面一个输入流供客户端下载。 + } + + public static List parseExcelToListByXlsx(InputStream stream) throws Exception { + List list = new ArrayList<>(); + org.apache.poi.util.IOUtils.setByteArrayMaxOverride(200000000); + try { + org.apache.poi.ss.usermodel.Workbook workbook; + try { + workbook = new XSSFWorkbook(stream); + } catch (Exception e) { + log.error("解析excel文件异常,message=" + e.getMessage()); + workbook = new HSSFWorkbook(stream); + } + org.apache.poi.ss.usermodel.Sheet sheet = workbook.getSheetAt(0); + // 获取表头行,用于确定列数 + org.apache.poi.ss.usermodel.Row headerRow = sheet.getRow(0); + int columnCount = headerRow != null ? headerRow.getLastCellNum() : 0; + for (org.apache.poi.ss.usermodel.Row row : sheet) { + // 根据表头列数创建数组,确保所有行都有相同的列数 + Object[] rowData = new Object[columnCount]; + for (int j = 0; j < columnCount; j++) { + org.apache.poi.ss.usermodel.Cell cell = row.getCell(j); + if (cell != null) { + switch (cell.getCellType()) { + case STRING: + rowData[j] = cell.getStringCellValue().trim(); + break; + case NUMERIC: + if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) { + rowData[j] = cell.getDateCellValue(); + } else { + rowData[j] = cell.getNumericCellValue(); + } + break; + case BOOLEAN: + rowData[j] = cell.getBooleanCellValue(); + break; + case FORMULA: + rowData[j] = cell.getCellFormula(); + break; + default: + rowData[j] = null; + } + } else { + // 确保空单元格被正确设置为null,避免列位置错位 + rowData[j] = ""; + } + } + list.add(rowData); + } + workbook.close(); + } catch (Exception e) { + throw new RuntimeException("解析excel文件异常: " + e.getMessage(), e); + } + return list; + } + +} diff --git a/server/src/main/java/com/aisino/iles/common/util/HttpHeaderUtil.java b/server/src/main/java/com/aisino/iles/common/util/HttpHeaderUtil.java new file mode 100644 index 0000000..a03c2d0 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/util/HttpHeaderUtil.java @@ -0,0 +1,62 @@ +package com.aisino.iles.common.util; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import jakarta.servlet.http.HttpServletRequest; + +@Slf4j +public class HttpHeaderUtil { + public static String getIpAddr(HttpServletRequest request) { + String ip = request.getHeader("x-forwarded-for"); + if (log.isDebugEnabled()) { + log.debug("x-forwarded-for ip: {}", ip); + } + if (StringUtils.isNotBlank(ip) && !"unknown".equalsIgnoreCase(ip)) { + // 多次反向代理后会有多个ip值,第一个ip才是真实ip + if (ip.contains(",")) { + ip = ip.split(",")[0]; + } + } + if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + if (log.isDebugEnabled()) { + log.debug("Proxy-Client-IP ip: {}", ip); + } + } + if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + if (log.isDebugEnabled()) { + log.debug("WL-Proxy-Client-IP ip: {}", ip); + } + } + if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_CLIENT_IP"); + if (log.isDebugEnabled()) { + log.debug("HTTP_CLIENT_IP ip: {}", ip); + } + } + if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_X_FORWARDED_FOR"); + if (log.isDebugEnabled()) { + log.debug("HTTP_X_FORWARDED_FOR ip: {}", ip); + } + } + if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("X-Real-IP"); + if (log.isDebugEnabled()) { + log.debug("X-Real-IP ip: {}", ip); + } + } + if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + if (log.isDebugEnabled()) { + log.debug("getRemoteAddr ip: {}", ip); + } + } + if (log.isDebugEnabled()) { + log.debug("获取客户端ip: {}", ip); + } + return ip; + } +} diff --git a/server/src/main/java/com/aisino/iles/common/util/InetAddressUtil.java b/server/src/main/java/com/aisino/iles/common/util/InetAddressUtil.java new file mode 100644 index 0000000..2be75e0 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/util/InetAddressUtil.java @@ -0,0 +1,67 @@ +package com.aisino.iles.common.util; + +import jakarta.servlet.http.HttpServletRequest; +import java.util.Optional; +import java.util.regex.Pattern; + +/** + * ip等网络地址工具 + * + * @author huxin + * @since 2020-07-28 + */ +public class InetAddressUtil { + private static final Pattern IPV4_PATTERN = + Pattern.compile( + "^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}$"); + + private static final Pattern IPV6_STD_PATTERN = + Pattern.compile( + "^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$"); + + private static final Pattern IPV6_HEX_COMPRESSED_PATTERN = + Pattern.compile( + "^((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)$"); + /** + * 判断是否是ipv4地址 + */ + public static boolean isIPv4Address(final String input) { + return IPV4_PATTERN.matcher(input).matches(); + } + /** + * 判断是否是标准ipv6地址 + */ + public static boolean isIPv6StdAddress(final String input) { + return IPV6_STD_PATTERN.matcher(input).matches(); + } + /** + * 判断是否是ipv6压缩地址 + */ + public static boolean isIPv6HexCompressedAddress(final String input) { + return IPV6_HEX_COMPRESSED_PATTERN.matcher(input).matches(); + } + /** + * 判断是否ipv6地址 + */ + public static boolean isIPv6Address(final String input) { + return isIPv6StdAddress(input) || isIPv6HexCompressedAddress(input); + } + /** + * 获取客户端ip地址 + * @param request 请求 + * @return ip地址 + */ + public static String getIpAddress(HttpServletRequest request) { + return Optional.ofNullable(request.getHeader(Constants.Headers.X_REAL_IP)) + .filter(StringUtils::isNotEmpty) + .orElseGet(request::getRemoteAddr); + } + /** + * 获取客户端mac地址 + * @param request 请求 + * @return mac地址 + */ + public static String getMacAddress(HttpServletRequest request) { + return null; + } +} diff --git a/server/src/main/java/com/aisino/iles/common/util/InvokeUtility.java b/server/src/main/java/com/aisino/iles/common/util/InvokeUtility.java new file mode 100644 index 0000000..1a0ffce --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/util/InvokeUtility.java @@ -0,0 +1,84 @@ +package com.aisino.iles.common.util; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; + +public class InvokeUtility { + private static DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + /** + * 根据属性名获取属性值 + * */ + public static Object getFieldValueByName(String fieldName, Object o) { + try { + return Arrays.stream(fieldName.split("\\.")) + .reduce(o, (obj,property)->{ + String firstLetter = property.substring(0, 1).toUpperCase(); + String getter = "get" + firstLetter + property.substring(1); + Object value = null; + try { + Method method = obj.getClass().getMethod(getter, new Class[] {}); + value = method.invoke(obj, new Object[] {}); + } catch (Exception e) { + return null; + } + if (value != null && value instanceof LocalDateTime) { + return df.format((LocalDateTime) value); + } + return value; + },(o1, o2) -> o2); +// String firstLetter = fieldName.substring(0, 1).toUpperCase(); +// String getter = "get" + firstLetter + fieldName.substring(1); +// Method method = o.getClass().getMethod(getter, new Class[] {}); +// Object value = method.invoke(o, new Object[] {}); +// return value; + } catch (Exception e) { + return null; + } + } + + /** + * 获取属性名数组 + * */ + public static String[] getFiledName(Object o){ + Field[] fields=o.getClass().getDeclaredFields(); + String[] fieldNames=new String[fields.length]; + for(int i=0;i s.contains(name)) + .isPresent(); + } +} diff --git a/server/src/main/java/com/aisino/iles/common/util/KmsServer.java b/server/src/main/java/com/aisino/iles/common/util/KmsServer.java new file mode 100644 index 0000000..e22120e --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/util/KmsServer.java @@ -0,0 +1,98 @@ +package com.aisino.iles.common.util; +import com.koal.kms.sdk.ed.KmsSdkException; +import com.koal.kms.sdk.ed.KmsUtil; +import com.koal.kms.sdk.ed.Mode; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; + +import java.util.Base64; +import java.util.Optional; + +@Slf4j +public class KmsServer { + + /** + * 数据加密 + * originData 待加密数据 + */ + public static String kmsEncrypt(String originData){ + log.info("原始数据={}",originData); + if(originData!=null && !originData.isEmpty()){ + + try { + byte[] encryptData = KmsUtil.encrypt(originData.getBytes(), Mode.CBC); + // 若要保存密文,使用Base64做编码处理 + String saveEncryptData = Base64.getEncoder().encodeToString(encryptData); + log.info("加密数据={}",saveEncryptData); + return saveEncryptData; + } catch (KmsSdkException e) { + log.error("加密失败={}",e.getMessage()); + return ""; + } + }else{ + log.info("待加密数据为空串"); + return ""; + } + } + + /** + * 数据解密 + * encryptData 加密后的数据 + */ + public static String kmsDencrypt(String encryptData) { + log.info("待解密数据={}",encryptData); + if(encryptData!=null && !encryptData.isEmpty()){ + try { + // 解密前使用Base64做解码处理 + byte[] decryptData = Base64.getDecoder().decode(encryptData); + try { + byte[] decryptResult = KmsUtil.decrypt(decryptData, Mode.CBC); + String result=new String(decryptResult); + log.info("解密后={}",result); + return result; + } catch (KmsSdkException e) { + log.error("解密失败={}",e.getMessage()); + return ""; + } + }catch (Exception e){ + log.error("Base64转换失败={}",e.getMessage()); + return encryptData; + } + }else { + log.info("待解密数据为空串"); + return ""; + } + } + + /** + * 数据校验 + * originData 加密前的数据原文 + */ + public static String kmsSign(String originData) { + log.info("校验原始数据={}",originData); + if(originData!=null && !originData.isEmpty()){ + try{ + // 解密前使用Base64做解码处理 + byte[] originByte = Base64.getDecoder().decode(originData); + try { + byte[] signByteData = KmsUtil.hmacSM3Encrypt(originByte); + // String sginData=new String(signByteData); + String sginData=Base64.getEncoder().encodeToString(signByteData); + log.info("校验后返回签名={}",sginData); + return sginData; + } catch (KmsSdkException e) { + log.error("校验失败={}",e.getMessage()); + return ""; + } + }catch (Exception e){ + log.error("Base64转换失败={}",e.getMessage()); + return originData; + } + }else{ + log.info("校验原始数据为空串"); + return ""; + } + + + } +} diff --git a/server/src/main/java/com/aisino/iles/common/util/Md5Utils.java b/server/src/main/java/com/aisino/iles/common/util/Md5Utils.java new file mode 100644 index 0000000..887559b --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/util/Md5Utils.java @@ -0,0 +1,67 @@ +package com.aisino.iles.common.util; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * **************************************************** + * + * @author shishaobo + * @ClassName: Md5Utils + * @Description: TODO(MD5加密 工具类) + * @date 2018年9月12日 下午3:04:01 + *

+ * **************************************************** + */ + +public class Md5Utils { + + /** + * @param string 待加密字符串 + * @return String MD5加密后的字符串 + * @throws + * @Title: getMd5Str + * @Description: TODO(对字符串进行MD5加密) + */ + public static String getMd5Str(String string) { + try { + //拿到一个MD5转换器(如果想要SHA1加密参数换成"SHA1") + MessageDigest messageDigest = MessageDigest.getInstance("MD5"); + //输入的字符串转换成字节数组 + byte[] inputByteArray = string.getBytes(); + //inputByteArray是输入字符串转换得到的字节数组 + messageDigest.update(inputByteArray); + //转换并返回结果,也是字节数组,包含16个元素 + byte[] resultByteArray = messageDigest.digest(); + //字符数组转换成字符串返回 + return byteArrayToHex(resultByteArray); + } catch (NoSuchAlgorithmException e) { + return null; + } + } + + /** + * @param byteArray 需要转换的字符数组 + * @return String 转换完成的字符串 + * @throws + * @Title: byteArrayToHex + * @Description: TODO(字符数组转换成字符串) + */ + public static String byteArrayToHex(byte[] byteArray) { + //首先初始化一个字符数组,用来存放每个16进制字符 + char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + //new一个字符数组,这个就是用来组成结果字符串的(解释一下:一个byte是八位二进制,也就是2位十六进制字符) + char[] resultCharArray = new char[byteArray.length * 2]; + //遍历字节数组,通过位运算(位运算效率高),转换成字符放到字符数组中去 + int index = 0; + for (byte b : byteArray) { + resultCharArray[index++] = hexDigits[b >>> 4 & 0xf]; + resultCharArray[index++] = hexDigits[b & 0xf]; + } + + //字符数组组合成字符串返回 + return new String(resultCharArray); + } + + +} diff --git a/server/src/main/java/com/aisino/iles/common/util/MigrationUtil.java b/server/src/main/java/com/aisino/iles/common/util/MigrationUtil.java new file mode 100644 index 0000000..953616b --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/util/MigrationUtil.java @@ -0,0 +1,57 @@ +package com.aisino.iles.common.util; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.ContentType; +import cn.hutool.http.Header; +import cn.hutool.http.HttpUtil; +import cn.hutool.http.ssl.TrustAnyHostnameVerifier; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class MigrationUtil { + + private static String base; + + private MigrationUtil() { + + } + + + private static String gab_url() { + return base + "/migration/qg_gabczrk"; + } + + + @SuppressWarnings("unchecked") + public static JsonNode getGabrkxx(String zjhm) { + String body; + JsonNode ret; + try { + body = HttpUtil.createGet(gab_url() + "?sfzh=" + zjhm) + .setHostnameVerifier(new TrustAnyHostnameVerifier()) + .header(Header.ACCEPT, ContentType.JSON.getValue()) + .contentType(ContentType.JSON.getValue()) + .disableCache() + .execute() + .body(); + if (StrUtil.isBlank(body)) { + throw new RuntimeException("公安部人口库接口无返回"); + } + ret = new ObjectMapper().readTree(body); + return ret; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Autowired + public void initBase(@Value("http://151.50.3.163:9095") String base) { + MigrationUtil.base = base; + } +} diff --git a/server/src/main/java/com/aisino/iles/common/util/PageBean.java b/server/src/main/java/com/aisino/iles/common/util/PageBean.java new file mode 100644 index 0000000..5b7ef57 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/util/PageBean.java @@ -0,0 +1,101 @@ +package com.aisino.iles.common.util; + +/** + * 分页 + * + * @Author: yj + * @Date: 2019/10/21 0021 9:50 + */ +public class PageBean { + + private Integer page; + private Integer pageSize; + + private Integer startWith; + private Integer offset; + private Integer totalPage; + private Integer count; + + + public static PageBean buildPageBean(Integer page, Integer pageSize, Integer count) { + return new PageBean(page, pageSize, count); + } + + public PageBean() { + } + + public PageBean(Integer page, Integer pageSize, Integer count) { + this.page = page; + this.pageSize = pageSize; + this.count = count; + this.totalPage = count % pageSize == 0 ? count / pageSize : count / pageSize + 1; + } + + /** + * 设置页码 + */ + public void setPage(Integer page) { + if (page > totalPage) { + this.page = 0; + } else { + this.page = page; + } + } + + + public void setOffset(Integer offset) { + this.offset = offset; + } + + /** + * 设置偏移量 + */ + public void setOffset() { + Integer offsets = this.page * this.pageSize; + if (offsets > count) { + this.offset = count - offsets; + } else { + this.offset = this.pageSize; + } + } + + public void setStartWith() { + this.startWith = this.page * this.pageSize; + } + + public Integer getPage() { + return page; + } + + public Integer getPageSize() { + return pageSize; + } + + public void setPageSize(Integer pageSize) { + this.pageSize = pageSize; + } + + public Integer getStartWith() { + return startWith; + } + + public Integer getOffset() { + return offset; + } + + public Integer getTotalPage() { + return totalPage; + } + + public void setTotalPage(Integer totalPage) { + this.totalPage = totalPage; + } + + public Integer getCount() { + return count; + } + + public void setCount(Integer count) { + this.count = count; + } +} diff --git a/server/src/main/java/com/aisino/iles/common/util/PageableHelper.java b/server/src/main/java/com/aisino/iles/common/util/PageableHelper.java new file mode 100644 index 0000000..7c5fd3a --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/util/PageableHelper.java @@ -0,0 +1,49 @@ +package com.aisino.iles.common.util; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +import java.util.Optional; + +/** + * 用于生成分页数据集的小工具。 + * + * @author huxin + */ +public class PageableHelper { + /** + * 生成分页数据 + * + * @param page 页码 (1开始) + * @param limit 每页显示数(默认20) + * @param sort 排序列 + * @param dir 排序方式 升降序 + * @return 分页数据 + */ + public static Pageable buildPageRequest(Integer page, Integer limit, String sort, String dir) { + return PageRequest.of( + Optional.ofNullable(page).filter(p -> p > 0).map(p -> p - 1).orElse(0), + Optional.ofNullable(limit).filter(l -> l > 0).orElse(20), + Optional.ofNullable(sort).filter(StringUtils::isNotEmpty) + .map(s -> Sort.by( + Optional.ofNullable(dir) + .filter(StringUtils::isNotEmpty) + .flatMap(Sort.Direction::fromOptionalString) + .orElse(Sort.Direction.ASC), + s.split(",")) + ).orElse(Sort.unsorted()) + ); + } + + /** + * 生成分页数据 + * + * @param page 页码 (1开始) + * @param limit 每页显示数(默认20) + * @return 分页数据 + */ + public static Pageable buildPageRequest(Integer page, Integer limit) { + return buildPageRequest(page, limit, null, null); + } +} diff --git a/server/src/main/java/com/aisino/iles/common/util/PdfMerger.java b/server/src/main/java/com/aisino/iles/common/util/PdfMerger.java new file mode 100644 index 0000000..39376e3 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/util/PdfMerger.java @@ -0,0 +1,365 @@ +package com.aisino.iles.common.util; + +import com.aisino.iles.lawenforcement.model.Document; +import org.apache.pdfbox.multipdf.PDFMergerUtility; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.font.PDFont; +import org.apache.pdfbox.pdmodel.font.PDType0Font; +import org.apache.pdfbox.pdmodel.font.PDType1Font; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.awt.*; +import java.io.*; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +public class PdfMerger { + private static final Logger log = LoggerFactory.getLogger(PdfMerger.class); + + // 定义输入文件结构 + public static class PdfInput { + private final String fileName; + private final Document document; + private final byte[] data; // 存储字节数据而非流 + + public PdfInput(String fileName,Document document, byte[] data) throws IOException { + this.fileName = fileName; + this.document = document; + this.data = data; + } + + public InputStream getStream() { + return new ByteArrayInputStream(data); + } + } + + public static void mergePDFsWithPageNumbers(List inputs, String outputFile) throws IOException { + PDDocument mergedDocument = new PDDocument(); + List fileInfos = new ArrayList<>(); + int totalPages = 1; // 包含目录页 + + // 第一阶段:收集文档信息 + for (PdfInput input : inputs) {// 获取新流 + PDDocument srcDoc = PDDocument.load(input.data); + int pageCount = srcDoc.getNumberOfPages(); + int endPage = 0; + if (pageCount>1) + endPage = totalPages + pageCount; + fileInfos.add(new FileInfo(input.fileName,input.document, pageCount, totalPages + 1,endPage)); + totalPages += pageCount; + } + // 添加目录页 + // addTableOfContents(mergedDocument, fileInfos); + addTableOfContentsWithPage(mergedDocument, fileInfos); + // 使用PDFMergerUtility来正确合并文档内容 + PDFMergerUtility merger = new PDFMergerUtility(); + for (PdfInput input : inputs ) { + try (PDDocument doc = PDDocument.load(input.data)) { + merger.appendDocument(mergedDocument, doc); + } + } + + // 添加页码到合并后的文档 + addPageNumbersToDocument(mergedDocument); + + // 保存合并后的文档 + mergedDocument.save(outputFile); + mergedDocument.close(); + } + + + // 添加表格目录页实现 + private static void addTableOfContentsWithPage(PDDocument doc, List fileInfos) throws IOException { + List tocPages = new ArrayList<>(); + PDPage currentTocPage = new PDPage(PDRectangle.A4); + tocPages.add(currentTocPage); + doc.addPage(currentTocPage); + + float yPosition = 750; + + PDFont font = PDType0Font.load(doc, PdfMerger.class.getResourceAsStream("/fonts/simsun.ttf")); + try (PDPageContentStream headerStream = new PDPageContentStream(doc, currentTocPage)) { + headerStream.beginText(); + headerStream.setFont(font, 24); + headerStream.newLineAtOffset(230, yPosition); + headerStream.showText("卷 内 目 录"); + headerStream.endText(); + } + yPosition -= 30; // 下移标题位置 + + List data = new ArrayList<>(); + data.add(new String[]{"序号", "文件名称及编号", "日期", "页号", "备注"}); + DateTimeFormatter formatter= DateTimeFormatter.ofPattern("yyyy年MM月dd日"); + for (FileInfo info : fileInfos) { + int startPage = info.startPage; + int endPage = info.endPage; + String pageStr = endPage>0? startPage +"-"+ endPage : String.valueOf(startPage); + + String title; + if(info.document.getDocumentCode()!=null&&!"".equals(info.document.getDocumentCode())) + title = "《"+info.document.getDocumentName()+"》"+info.document.getDocumentCode(); + else + title = "《"+info.document.getDocumentName()+"》" ; + data.add(new String[]{info.document.getDocumentNo().toString(), title, info.document.getDocumentDate().format(formatter), pageStr,""}); + } + try (PDPageContentStream entryStream = new PDPageContentStream(doc, currentTocPage, PDPageContentStream.AppendMode.APPEND, true)) { + //drawTable(entryStream,50,yPosition,data,font); + drawTableWithWrappedText(entryStream, 30, yPosition, data, + new float[]{30f, 350f, 80f, 40f,30f}, 20f,font); + } + + } + + + /** + * 绘制带自动换行的表格 + * @param contentStream 内容流 + * @param startX 起始X坐标 + * @param startY 起始Y坐标 + * @param data 表格数据 + * @param colWidths 每列宽度 + * @param rowHeight 行高 + * @param rowHeight 行高 + */ + private static void drawTableWithWrappedText( PDPageContentStream contentStream, + float startX, float startY, List data, + float[] colWidths, float rowHeight,PDFont font) throws IOException { + + final int rows = data.size(); + final int cols = data.get(0).length; + float tableWidth = 0; + for (float width : colWidths) { + tableWidth += width; + } + + // 绘制表格边框 + contentStream.setLineWidth(1f); + drawTableBorders(contentStream, startX, startY, tableWidth, rows, colWidths, rowHeight); + + // 设置字体 + contentStream.setFont(font, 10); + float textStartX = startX + 5; + float textStartY = startY - 15; + + // 处理每行数据 + for (int i = 0; i < rows; i++) { + String[] rowData = data.get(i); + float maxHeightInRow = rowHeight; // 记录当前行最大高度 + + // 处理每列数据 + for (int j = 0; j < cols; j++) { + String cellText = rowData[j]; + float colWidth = colWidths[j] - 10; // 留出边距 + + // 获取自动换行后的文本行 + List lines = wrapText(cellText, colWidth, font, 16); + + // 计算当前单元格需要的高度 + float cellHeight = lines.size() * 12; // 每行文本高度12 + if (cellHeight > maxHeightInRow) { + maxHeightInRow = cellHeight; + } + + // 绘制文本 + float currentY = textStartY; + for (String line : lines) { + contentStream.beginText(); + contentStream.newLineAtOffset(textStartX, currentY); + if(i==0){ + // 设置描边颜色(与填充色相同) + contentStream.setStrokingColor(Color.BLACK); + } + contentStream.showText(line); + contentStream.endText(); + currentY -= 12; // 移动到下一行 + } + + textStartX += colWidths[j]; + } + + // 调整下一行的起始位置 + textStartY -= maxHeightInRow; + textStartX = startX + 5; + } + } + + /** + * 绘制表格边框 + */ + private static void drawTableBorders(PDPageContentStream contentStream, float startX, float startY, + float tableWidth, int rows, float[] colWidths, float rowHeight) + throws IOException { + // 绘制水平线 + float nextY = startY; + for (int i = 0; i <= rows; i++) { + contentStream.moveTo(startX, nextY); + contentStream.lineTo(startX + tableWidth, nextY); + contentStream.stroke(); + nextY -= rowHeight; + } + + // 绘制垂直线 + float nextX = startX; + contentStream.moveTo(nextX, startY); + contentStream.lineTo(nextX, startY - rows * rowHeight); + contentStream.stroke(); + + for (float width : colWidths) { + nextX += width; + contentStream.moveTo(nextX, startY); + contentStream.lineTo(nextX, startY - rows * rowHeight); + contentStream.stroke(); + } + } + + /** + * 文本自动换行处理 + * @param text 原始文本 + * @param maxWidth 最大宽度 + * @param font 字体 + * @param fontSize 字体大小 + * @return 换行后的文本列表 + */ + private static List wrapText(String text, float maxWidth, PDFont font, int fontSize) throws IOException { + List lines = new ArrayList<>(); + String[] words = text.split(" "); + StringBuilder currentLine = new StringBuilder(); + + for (String word : words) { + // 测试添加单词后是否超出宽度 + String testLine = currentLine.length() > 0 ? + currentLine.toString() + " " + word : word; + float testWidth = font.getStringWidth(testLine) / 1000 * fontSize; + + if (testWidth <= maxWidth) { + currentLine.append(currentLine.length() > 0 ? " " + word : word); + } else { + // 当前行已满,添加到结果 + if (currentLine.length() > 0) { + lines.add(currentLine.toString()); + currentLine = new StringBuilder(word); + } else { + // 单个单词就超过宽度,强制分割 + lines.add(word); + currentLine = new StringBuilder(); + } + } + } + + // 添加最后一行 + if (currentLine.length() > 0) { + lines.add(currentLine.toString()); + } + + return lines; + } + + + // 添加目录页实现 + private static void addTableOfContents(PDDocument doc, List fileInfos) throws IOException { + List tocPages = new ArrayList<>(); + PDPage currentTocPage = new PDPage(PDRectangle.A4); + tocPages.add(currentTocPage); + doc.addPage(currentTocPage); + + float yPosition = 750; + boolean isFirstPage = true; + + PDFont font = PDType0Font.load(doc, PdfMerger.class.getResourceAsStream("/fonts/simsun.ttf")); + for (FileInfo info : fileInfos) { + // 处理新页面的标题(仅第一页) + if (isFirstPage) { + try (PDPageContentStream headerStream = new PDPageContentStream(doc, currentTocPage)) { + headerStream.beginText(); + headerStream.setFont(font, 24); + headerStream.newLineAtOffset(230, yPosition); + headerStream.showText("卷 内 目 录"); + headerStream.endText(); + } + yPosition -= 50; // 下移标题位置 + isFirstPage = false; + } + + // 添加目录条目 + try (PDPageContentStream entryStream = new PDPageContentStream(doc, currentTocPage, PDPageContentStream.AppendMode.APPEND, true)) { + entryStream.beginText(); + entryStream.setFont(font, 14); + entryStream.newLineAtOffset(50, yPosition); + entryStream.showText(info.fileName.replace("pdf","").replace("PDF","") +"..................................."+ info.startPage); + entryStream.endText(); + } + + yPosition -= 30; + + // 分页逻辑 + if (yPosition < 50 && fileInfos.indexOf(info) < fileInfos.size()-1) { + currentTocPage = new PDPage(PDRectangle.A4); + doc.addPage(currentTocPage); + tocPages.add(currentTocPage); + yPosition = 750; + isFirstPage = true; // 新页面需要重新添加标题 + } + } + } + + // 文件信息记录类 + private static class FileInfo { + final String fileName; + final Document document; + final int pageCount; + final int startPage; + final int endPage; + + FileInfo(String fileName,Document document, int pageCount, int startPage, int endPage) { + this.fileName = fileName; + this.document = document; + this.pageCount = pageCount; + this.startPage = startPage; + this.endPage = endPage; + } + } + + + + private static void addPageNumbersToDocument(PDDocument document) throws IOException { + int totalPages = document.getNumberOfPages(); + + for (int i = 0; i < totalPages; i++) { + PDPage page = document.getPage(i); + + // 获取页面尺寸 + PDRectangle pageSize = page.getMediaBox(); + float pageWidth = pageSize.getWidth(); + PDFont font = PDType0Font.load(document, PdfMerger.class.getResourceAsStream("/fonts/simsun.ttf")); + // 添加页码 + try (PDPageContentStream contentStream = new PDPageContentStream( + document, + page, + PDPageContentStream.AppendMode.APPEND, + true, + true)) { + + // 设置字体和字号 + contentStream.setFont(font, 10); + + // 页码文本 + String pageText = String.format("%d / %d", i + 1, totalPages); + + // 计算文本宽度以居中显示 + float textWidth = font.getStringWidth(pageText) / 1000 * 10; + float startX = (pageWidth - textWidth) / 2; + + // 在页面底部添加页码 + contentStream.beginText(); + contentStream.newLineAtOffset(startX, 20); // 20是距底部的距离 + contentStream.showText(pageText); + contentStream.endText(); + } + } + } +} \ No newline at end of file diff --git a/server/src/main/java/com/aisino/iles/common/util/PinYinUtils.java b/server/src/main/java/com/aisino/iles/common/util/PinYinUtils.java new file mode 100644 index 0000000..7f4275e --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/util/PinYinUtils.java @@ -0,0 +1,48 @@ +package com.aisino.iles.common.util; + + +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.pinyin.PinyinUtil; +import lombok.extern.slf4j.Slf4j; + +import java.util.Optional; + +@Slf4j +public class PinYinUtils { + + /** + * 汉字转为拼音 + * + * @param chinese 中文 + * @return 拼音 + */ + public static String toPinyin(String chinese) { + return Optional.ofNullable(chinese) + .map(StrUtil::trimToNull) + .map(s -> PinyinUtil.getPinyin(s, "")) + .orElse(null); + } + + /** + * 汉字转换简拼 + * + * @param chinese 中文 + * @return 简拼 + */ + public static String toSimplePinyin(String chinese) { + return Optional.ofNullable(chinese) + .map(StrUtil::trimToNull) + .map(s -> PinyinUtil.getFirstLetter(s, "")) + .orElse(null); + } + + /** + * 判断是否为中文 + * + * @param chineseWord 中文 + * @return true为中文, false 不是中文 + */ + public static boolean isChinese(char chineseWord) { + return PinyinUtil.isChinese(chineseWord); + } +} diff --git a/server/src/main/java/com/aisino/iles/common/util/RSAUtil.java b/server/src/main/java/com/aisino/iles/common/util/RSAUtil.java new file mode 100644 index 0000000..5ec4032 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/util/RSAUtil.java @@ -0,0 +1,64 @@ +package com.aisino.iles.common.util; + +import org.apache.commons.codec.binary.Base64; + +import javax.crypto.Cipher; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; + +/** + * + * @author yunfei + * @ClassName: RSAUtil + * @Description: RSA加密 工具类 + * @date 2021年03月26日 下午3:04:01 + *

+ */ +public class RSAUtil { + + /** + * + * @param input 待加密的字符串 + * @param rsaPublicKey 密钥 + * @return String RSA加密后的字符串 + * @Description: 对字符串进行RSA加密 + */ + public static String encrypt(String input, String rsaPublicKey) { + String result = ""; + try { + // 将Base64编码后的公钥转换成PublicKey对象 + byte[] buffer = Base64.decodeBase64(rsaPublicKey); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer); + PublicKey publicKey = keyFactory.generatePublic(keySpec); + // 加密 + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + byte[] inputArray = input.getBytes(); + int inputLength = inputArray.length; + // 最大加密字节数,超出最大字节数需要分组加密 + int MAX_ENCRYPT_BLOCK = 117; + // 标识 + int offSet = 0; + byte[] resultBytes = {}; + byte[] cache = {}; + while (inputLength - offSet > 0) { + if (inputLength - offSet > MAX_ENCRYPT_BLOCK) { + cache = cipher.doFinal(inputArray, offSet, MAX_ENCRYPT_BLOCK); + offSet += MAX_ENCRYPT_BLOCK; + } else { + cache = cipher.doFinal(inputArray, offSet, inputLength - offSet); + offSet = inputLength; + } + resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length); + System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length); + } + result = Base64.encodeBase64String(resultBytes); + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } +} diff --git a/server/src/main/java/com/aisino/iles/common/util/ReconUtil.java b/server/src/main/java/com/aisino/iles/common/util/ReconUtil.java new file mode 100644 index 0000000..9469b7f --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/util/ReconUtil.java @@ -0,0 +1,208 @@ +package com.aisino.iles.common.util; + +import cn.hutool.http.ContentType; +import cn.hutool.http.Header; +import cn.hutool.http.HttpUtil; +import cn.hutool.http.ssl.TrustAnyHostnameVerifier; +import cn.hutool.json.JSONObject; +import com.aisino.iles.common.model.FaceValid; +import com.aisino.iles.common.model.OcrValid; +import com.aisino.iles.common.model.Ok; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.Optional; + +@Slf4j +@Component +public class ReconUtil { + + private static String base; + + private ReconUtil() { + + } + + private static String recon_url() { + return base + "/recon-api/ocr"; + } + + private static String face_url() { + return base + "/recon-api/face"; + } + + private static Ok failure(Integer code, String msg) { + Ok ok = Ok.of(); + ok.setMsg(msg); + ok.setCode(code); + ok.setSuccess(false); + return ok; + } + + private static String trimBase64(final String base64) { + if (StringUtils.isNotBlank(base64)) { + String s = StringUtils.trimToEmpty(base64); + return s.substring(s.indexOf(",") + 1); + } + return StringUtils.EMPTY; + } + + /** + * OCR识别 证件 护照等 + * + * @param base64Img 待识别的图片内容 + * @param imgType 图片类型 jpg,png,bmp + * @param reconType 识别类型 + * 二代身份证正面 2
+ * 二代身份证证背面 3
+ * 银行卡 17
+ * 车牌 19
+ * 名片 20
+ * @return 比对结果 Ok.code==0 成功 ok + */ + public static Ok reconCards(String base64Img, String imgType, Integer reconType) { + String body; + try { + body = HttpUtil.createPost(recon_url()) + .setHostnameVerifier(new TrustAnyHostnameVerifier()) + .disableCache() + .header(Header.ACCEPT, ContentType.JSON.getValue()) + .contentType(ContentType.JSON.getValue()) + .body(new JSONObject() {{ + set("img", trimBase64(base64Img)); + set("imgType", imgType); + set("reconType", reconType); + }}.toStringPretty()) + .execute() + .body(); + } catch (Exception e) { + throw new RuntimeException(e); + } + return resolveOcrResult(body); + } + + @SuppressWarnings("unchecked") + private static Ok resolveOcrResult(String body) { + if (StringUtils.isBlank(body)) { + return failure(1, "OCR识别接口无数据返回"); + } + JsonNode ret; + try { + ret = new ObjectMapper().readTree(body); + } catch (Exception ex) { + return failure(1, "OCR识别接口数据格式错误" + ex.getMessage()); + } + return (Ok) Optional.of(ret) + .map(j -> { + if (j.path("code").asInt() == 0) { + try { + return Ok.of(new ObjectMapper().readValue(j.path("data").toString(), OcrValid.class), j.path("msg").asText()); + } catch (Exception e) { + return failure(1, "OCR识别接口数据格式错误" + e.getMessage()); + } + } else { + return failure(j.path("code").asInt(), j.path("msg").asText()); + } + }).get(); + } + + /** + * OCR识别 营业执照 不动产登记证 等 + * + * @param base64Img 待识别的图片内容 + * @param LGId 识别类型 + * 1:机动车合格证;2:机动车登记证;3:不动产登记证;5:营业执照;6:常住人口登记卡 + * @return 比对结果 Ok.code==0 成功 ok + */ + public static Ok reconLG(String base64Img, String LGId) { + String body; + try { + body = HttpUtil.createPost(recon_url() + "/lg-recon") + .setHostnameVerifier(new TrustAnyHostnameVerifier()) + .header(Header.ACCEPT, ContentType.JSON.getValue()) + .contentType(ContentType.JSON.getValue()) + .disableCache() + .body(new JSONObject() { + { + set("img", trimBase64(base64Img)); + set("LGId", LGId); + } + }.toStringPretty()) + .execute() + .body(); + } catch (Exception e) { + throw new RuntimeException(e); + } + return resolveOcrResult(body); + } + + /** + * 人脸比对 + * + * @param base64Img1 第1张图片内容的base64格式字符串 + * @param base64Img2 第2张图片内容的base64格式字符串 + * @param threshold 比对阈值 结果小于该值 code将不为0 + * @return 比对结果 + */ + @SuppressWarnings("unchecked") + public static Ok faceCompare(String base64Img1, String base64Img2, Integer threshold) { + String body; + try { + body = HttpUtil.createPost(face_url()) + .setHostnameVerifier(new TrustAnyHostnameVerifier()) + .header(Header.ACCEPT, ContentType.JSON.getValue()) + .contentType(ContentType.JSON.getValue()) + .disableCache() + .body(new JSONObject() { + { + set("img1", trimBase64(base64Img1)); + set("img2", trimBase64(base64Img2)); + set("threshold", threshold); + } + }.toStringPretty()) + .execute() + .body(); + } catch (Exception e) { + return failure(1, "请求人脸比对接口出错" + e.getMessage()); + } + if (StringUtils.isBlank(body)) { + return failure(1, "人脸比对接口无数据返回"); + } + JsonNode ret; + try { + ret = new ObjectMapper().readTree(body); + } catch (Exception e) { + return failure(1, "人脸比对接口数据格式错误" + e.getMessage()); + } + return (Ok) Optional.of(ret) + .map(j -> { + FaceValid valid = null; + if (j.has("data")) { + try { + valid = new ObjectMapper().readValue(j.path("data").toString(), FaceValid.class); + } catch (JsonProcessingException e) { + return failure(1, "人脸比对接口数据格式错误" + e.getMessage()); + } + } + if (j.path("code").asInt() == 0) { + return Ok.of(valid, j.path("msg").asText()); + } else { + Ok r = failure(j.path("code").asInt(), j.path("msg").asText()); + r.setData(valid); + return r; + } + }).get(); + } + + @Autowired + public void initBase(@Value("${recon.base:http://localhost:9980}") String base) { + ReconUtil.base = base; + } +} diff --git a/server/src/main/java/com/aisino/iles/common/util/SM4Util.java b/server/src/main/java/com/aisino/iles/common/util/SM4Util.java new file mode 100644 index 0000000..1d349ea --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/util/SM4Util.java @@ -0,0 +1,283 @@ +package com.aisino.iles.common.util; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.crypto.Mode; +import cn.hutool.crypto.Padding; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.symmetric.SM4; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.beanutils.PropertyUtils; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.KeyGenerator; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.lang.reflect.InvocationTargetException; +import java.security.Key; +import java.security.SecureRandom; +import java.security.Security; +import java.util.Arrays; +import java.util.Optional; + +/** + * 国密SM4编码工具 + */ +@Slf4j +public class SM4Util { + static { + Security.addProvider(new BouncyCastleProvider()); + } + + private static final String ENCODING = "UTF-8"; + public static final String ALGORITHM_NAME = "SM4"; + // 加密算法/分组加密模式/分组填充方式 + // PKCS5Padding-以8个字节为一组进行分组加密 + // 定义分组加密模式使用:PKCS5Padding + public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding"; + public static final String ALGORITHM_NAME_CBC_PADDING = "SM4/CBC/PKCS5Padding"; + // 128-32位16进制;256-64位16进制 + public static final int DEFAULT_KEY_SIZE = 128; + + private static final String PROVIDER_NAME = BouncyCastleProvider.PROVIDER_NAME; +// private static final String PROVIDER_NAME = "temp"; + + /** + * 自动生成密钥 + * + * @return + * @explain + */ + public static String generateKey() throws Exception { + return new String(Hex.encodeHex(generateKey(DEFAULT_KEY_SIZE), false)); + } + + /** + * @param keySize + * @return + * @throws Exception + * @explain + */ + public static byte[] generateKey(int keySize) throws Exception { + KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, PROVIDER_NAME); + kg.init(keySize, new SecureRandom()); + return kg.generateKey().getEncoded(); + } + + /** + * 生成ECB暗号 + * + * @param algorithmName 算法名称 + * @param mode 模式 + * @param key + * @return + * @throws Exception + * @explain ECB模式(电子密码本模式:Electronic codebook) + */ + private static Cipher generateEcbCipher(String algorithmName, int mode, byte[] key) throws Exception { + Cipher cipher = Cipher.getInstance(algorithmName, PROVIDER_NAME); + Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME); + cipher.init(mode, sm4Key); + return cipher; + } + + /** + * sm4加密 + * + * @param hexKey 16进制密钥(忽略大小写) + * @param paramStr 待加密字符串 + * @return 返回16进制的加密字符串 + * @explain 加密模式:ECB + * 密文长度不固定,会随着被加密字符串长度的变化而变化 + */ + public static String encryptEcb(String hexKey, String paramStr) { + try { + String cipherText = ""; + // 16进制字符串-->byte[] + byte[] keyData = ByteUtils.fromHexString(hexKey); + // String-->byte[] + byte[] srcData = paramStr.getBytes(ENCODING); + // 加密后的数组 + byte[] cipherArray = encrypt_Ecb_Padding(keyData, srcData); + // byte[]-->hexString + cipherText = ByteUtils.toHexString(cipherArray); + return cipherText; + } catch (Exception e) { + return paramStr; + } + } + + /** + * 加密模式之Ecb + * + * @param key + * @param data + * @return + * @throws Exception + * @explain + */ + public static byte[] encrypt_Ecb_Padding(byte[] key, byte[] data) throws Exception { + Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key); + return cipher.doFinal(data); + } + + /** + * sm4解密 + * + * @param hexKey 16进制密钥 + * @param cipherText 16进制的加密字符串(忽略大小写) + * @return 解密后的字符串 + * @throws Exception + * @explain 解密模式:采用ECB + */ + public static String decryptEcb(String hexKey, String cipherText) throws IllegalBlockSizeException { + // 用于接收解密后的字符串 + String decryptStr = ""; + // hexString-->byte[] + byte[] keyData = ByteUtils.fromHexString(hexKey); + // hexString-->byte[] + byte[] cipherData = ByteUtils.fromHexString(cipherText); + // 解密 + byte[] srcData = new byte[0]; + try { + srcData = decrypt_Ecb_Padding(keyData, cipherData); + // byte[]-->String + decryptStr = new String(srcData, ENCODING); + } catch (IllegalBlockSizeException e) { + throw e; + } catch (Exception e) { + e.printStackTrace(); + } + return decryptStr; + } + + /** + * 解密 + * + * @param key + * @param cipherText + * @return + * @throws Exception + * @explain + */ + public static byte[] decrypt_Ecb_Padding(byte[] key, byte[] cipherText) throws Exception { + Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key); + return cipher.doFinal(cipherText); + } + public static byte[] decrypt_Cbc_Padding(byte[] key, byte[] cipherText) throws Exception { + Cipher cipher = generateEcbCipher(ALGORITHM_NAME_CBC_PADDING, Cipher.DECRYPT_MODE, key); + return cipher.doFinal(cipherText); + } + + /** + * 校验加密前后的字符串是否为同一数据 + * + * @param hexKey 16进制密钥(忽略大小写) + * @param cipherText 16进制加密后的字符串 + * @param paramStr 加密前的字符串 + * @return 是否为同一数据 + * @throws Exception + * @explain + */ + public static boolean verifyEcb(String hexKey, String cipherText, String paramStr) throws Exception { + // 用于接收校验结果 + boolean flag = false; + // hexString-->byte[] + byte[] keyData = ByteUtils.fromHexString(hexKey); + // 将16进制字符串转换成数组 + byte[] cipherData = ByteUtils.fromHexString(cipherText); + // 解密 + byte[] decryptData = decrypt_Ecb_Padding(keyData, cipherData); + // 将原字符串转换成byte[] + byte[] srcData = paramStr.getBytes(ENCODING); + // 判断2个数组是否一致 + flag = Arrays.equals(decryptData, srcData); + return flag; + } + /**警快办照片数据解谜*/ + public static String decryptCbc(String encryptedHex){ + byte[]encryptedBytes = SecureUtil.decode(encryptedHex); + byte[] key = SecureUtil.decode("fc20c5aed0a942bcaafc0778b05cc21a"); + IvParameterSpec ivParameterSpec = new IvParameterSpec(encryptedBytes, 0, 16); + SM4 sm4 = new SM4(Mode.CBC, Padding.PKCS5Padding, key, ivParameterSpec.getIV()); + return sm4.decryptStr(ArrayUtil.sub(encryptedBytes, 16, encryptedBytes.length)); + } + + public static void main(String[] args) { + try { + String json = "BF7B6BD7C1204BC4F3C87D235692DE9DBF7B6BD7C1204BC4F3C87D235692DE9DBF7B6BD7C1204BC4F3C87D235692DE9D"; + System.out.println("加密前源数据————" + json); + // 生成32位16进制密钥 + String key = SM4Util.generateKey(); + System.out.println(key + "-----生成key"); + String cipher = SM4Util.encryptEcb(key, json); + System.out.println("加密串---" + cipher); + System.out.println(SM4Util.verifyEcb(key, cipher, json)); + json = SM4Util.decryptEcb(key, cipher); + System.out.println("解密后数据---" + json); + + String zjhm = "13452511037"; + System.out.println("SM4加密前字符串" + zjhm); + String res = SM4Util.sm4Encrypt(zjhm); + System.out.println("SM4加密后的字符串" + res); + String result = SM4Util.sm4Decrypt(res); + System.out.println("SM4解密后的证件号码" + result); + byte[] bytes = ByteUtils.fromHexString("123123"); + System.out.println(bytes.length); + String s = sm4Encrypt("".toString()); + System.out.println("ss:" + s); + String decrypt = sm4Decrypt(s); + System.out.println("dd:" + decrypt); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * @return SM4加密后字符串 + * @Description SM4公用加密方法 + * @Author Jnno + * @Version 1.0 + * @param: paramStr 待加密字符串 + * @Date 2020-11-9 14:47 + **/ + public static String sm4Encrypt(String paramStr) { + return Optional.ofNullable(paramStr).filter(StringUtils::isNotEmpty).map(f -> SM4Util.encryptEcb(Constants.SM4_ENCRYPT_KEY, f)).orElse(""); + } + + public static String sm4EncryptByKey(String key, String paramStr) { + return Optional.ofNullable(paramStr).filter(StringUtils::isNotEmpty).map(f -> + SM4Util.encryptEcb(StringUtils.isNotBlank(key)?key:Constants.SM4_ENCRYPT_KEY_JKSQGL, f)).orElse(""); + } + + /** + * @return SM4解密后的字符串 + * @Description + * @Author Jnno + * @Version 1.0 + * @param: cipherText 16进制加密后的字符串 + * @Date 2020-11-9 14:52 + **/ + public static String sm4Decrypt(String cipherText) throws IllegalBlockSizeException { + if (cipherText == null) { + return null; + } + return SM4Util.decryptEcb(Constants.SM4_ENCRYPT_KEY, cipherText); + } + + public static class ByteUtils { + + public static String toHexString(byte[] bytes) { + return org.bouncycastle.pqc.math.linearalgebra.ByteUtils.toHexString(bytes); +// return null; + } + + public static byte[] fromHexString(String string) { + return org.bouncycastle.pqc.math.linearalgebra.ByteUtils.fromHexString(string); +// return null; + } + } +} diff --git a/server/src/main/java/com/aisino/iles/common/util/StringUtils.java b/server/src/main/java/com/aisino/iles/common/util/StringUtils.java new file mode 100644 index 0000000..11bc951 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/util/StringUtils.java @@ -0,0 +1,321 @@ +package com.aisino.iles.common.util; + +import cn.hutool.core.util.StrUtil; + +import java.sql.Timestamp; +import java.text.NumberFormat; +import java.util.Date; +import java.util.Map; +import java.util.UUID; + +/** + * 字符工具 + */ +public class StringUtils extends org.apache.commons.lang3.StringUtils { + /** + * 去掉尾部指定字符串 + * + * @param str 字符串 + * @param trailingString 匹配字符串 + * @return 去掉以后的字符串 + */ + public static String trimTrailingString(String str, String trailingString) { + if (!isNotEmpty(str)) { + return str; + } else { + StringBuilder sb = new StringBuilder(str); + int length = trailingString.length(); + while (sb.length() > 0 && sb.substring(sb.length() - length).equals(trailingString)) { + sb.delete(sb.length() - length, sb.length()); + } + + return sb.toString(); + } + } + +// /** +// * 不为空 +// * +// * @param str 字符串 +// * @return 是否不为空 +// */ +// public static Boolean isNotEmpty(String str) { +// return !isEmpty(str); +// } + + /** + * 根据西安机构代码规则调整字符串,移除尾部无效的零。 + *

+ * 处理规则: + * 从机构代码字符串的开头开始,每两个字符作为一对进行检查。 + * 如果一对字符不是 "00",则这对字符及其之前的所有字符被视为有效部分。 + * 该方法会确定最后一个这样的有效两字符对的位置。 + * 最终返回的字符串是从原始字符串开始到这最后一个有效对结束的部分。 + * 所有在此之后的字符(包括后续的 "00" 对、不完整的对或仅由零组成的尾部)都将被移除。 + *

+ *

+ * 示例: + *

    + *
  • {@code trimEven0("01610112000000000")} 返回 {@code "01610112"}
  • + *
  • {@code trimEven0("010010")} 返回 {@code "010010"} (因为 "10" 是最后一个非 "00" 对)
  • + *
  • {@code trimEven0("01610000")} 返回 {@code "0161"} (因为 "61" 后的 "00" 对被移除)
  • + *
  • {@code trimEven0("000100")} 返回 {@code "0001"} (因为 "01" 是最后一个非 "00" 对)
  • + *
  • {@code trimEven0("000000")} 返回 {@code ""} (所有对都是 "00")
  • + *
  • {@code trimEven0("01")} 返回 {@code "01"}
  • + *
  • {@code trimEven0("0")} 返回 {@code ""} (不足一对有效字符)
  • + *
  • {@code trimEven0("010")} 返回 {@code "01"} (最后的 "0" 不构成有效对的一部分,且它跟随在有效对 "01" 之后,所以被移除)
  • + *
  • {@code trimEven0(null)} 返回 {@code null}
  • + *
  • {@code trimEven0("")} 返回 {@code ""}
  • + *
+ * + * @param agencyCode 原始机构代码字符串。 + * @return 处理后的机构代码字符串。如果输入为 {@code null} 或空字符串,则原样返回。 + */ + public static String trimEven0(String agencyCode) { + if (agencyCode == null || agencyCode.isEmpty()) { + return agencyCode; + } + + int effectiveLength = -1; // 记录有效部分的长度,初始化为-1表示尚未找到有效部分 + + // 从字符串开头按两个字符一对进行遍历 + for (int i = 0; i + 1 < agencyCode.length(); i += 2) { + String pair = agencyCode.substring(i, i + 2); + if (!"00".equals(pair)) { + effectiveLength = i + 2; // 更新有效长度到当前这对字符的末尾 + } + } + + if (effectiveLength == -1) { + // 没有找到任何非 "00" 的字符对,或者字符串长度小于2 (例如 "0", "1") + // 或者所有字符对都是 "00" (例如 "00", "0000") + // 按照规则,这些情况应返回空字符串 + return ""; + } + + return agencyCode.substring(0, effectiveLength); + } + + public static String removeStr(String resouce, String remove) { + String result = resouce.replace(remove, ""); + return result; + } + + public static String getPercentage(int fenzi, int fenmu) { + // 创建一个数值格式化对象 + + NumberFormat numberFormat = NumberFormat.getInstance(); + + // 设置精确到小数点后2位 + + numberFormat.setMaximumFractionDigits(2); + String result = ""; + if (fenmu == 0) { + result = "0"; + } else { + result = numberFormat.format((float) fenzi / (float) fenmu * 100); + } + return result + "%"; + } + + /** + * 去除字符串尾部所有0(trim0("3206000")=="3206") + * 注意:此方法会把尾部所有0去掉,无论是奇数还是偶数个。 + * + * @param str + * @return + */ + public static String trim0(String str) { + String returnStr = str; + for (int i = str.length(); i > 0; i--) { + if (str.substring(i - 1, i).equals("0")) + returnStr = str.substring(0, i - 1); + else + break; + } + return returnStr; + } + + /** + * str不足len位补0 + * + * @param str 原字符串 + * int 字符串需要达到的位数 + * @return + */ + public static String add0(String str, int len) { + String returnStr = ""; + returnStr = str; + if (str != null) { + if (str.length() < len) { + for (int i = 0; i < (len - str.length()); i++) { + returnStr += "0"; + } + } + } + return returnStr; + } + + /** + * 15位身份证号码到18位身份证号码的转换 + * + * @param IDCardNO15 15位身份证号码 + * @return 18位身份证号码 + */ + public static String IDCardNO15To18(String IDCardNO15) { + String IDCardNO18 = ""; + if (IDCardNO15.length() == 15) { + String IDCardNO17 = ""; + if (Integer.parseInt(IDCardNO15.substring(6, 8)) > 20) + IDCardNO17 = IDCardNO15.substring(0, 6) + "19" + IDCardNO15.substring(6, 15); + else + IDCardNO17 = IDCardNO15.substring(0, 6) + "20" + IDCardNO15.substring(6, 15); + IDCardNO18 = IDCardNO17 + getVerifyBit(IDCardNO17); + } else + IDCardNO18 = IDCardNO15; + return IDCardNO18; + } + + /** + * 获得18位身份证号码的最后一位 + * IDCardNO18 17位身份证号码 + * + * @return 18位身份证号码的最后一位 + */ + public static String getVerifyBit(String IDCardNO17) { + String lastBit = ""; + int tempNo = 0; + tempNo = (Integer.parseInt(IDCardNO17.substring(0, 1)) * 7 + Integer.parseInt(IDCardNO17.substring(1, 2)) * 9 + + Integer.parseInt(IDCardNO17.substring(2, 3)) * 10 + Integer.parseInt(IDCardNO17.substring(3, 4)) * 5 + + Integer.parseInt(IDCardNO17.substring(4, 5)) * 8 + Integer.parseInt(IDCardNO17.substring(5, 6)) * 4 + + Integer.parseInt(IDCardNO17.substring(6, 7)) * 2 + Integer.parseInt(IDCardNO17.substring(7, 8)) * 1 + + Integer.parseInt(IDCardNO17.substring(8, 9)) * 6 + + Integer.parseInt(IDCardNO17.substring(9, 10)) * 3 + Integer.parseInt(IDCardNO17.substring(10, 11)) * 7 + + Integer.parseInt(IDCardNO17.substring(11, 12)) * 9 + Integer.parseInt(IDCardNO17.substring(12, 13)) * 10 + + Integer.parseInt(IDCardNO17.substring(13, 14)) * 5 + + Integer.parseInt(IDCardNO17.substring(14, 15)) * 8 + Integer.parseInt(IDCardNO17.substring(15, 16)) * 4 + + Integer.parseInt(IDCardNO17.substring(16, 17)) * 2) % 11; + + switch (tempNo) { + case 0: + lastBit = "1"; + break; + case 1: + lastBit = "0"; + break; + case 2: + lastBit = "X"; + break; + case 3: + lastBit = "9"; + break; + case 4: + lastBit = "8"; + break; + case 5: + lastBit = "7"; + break; + case 6: + lastBit = "6"; + break; + case 7: + lastBit = "5"; + break; + case 8: + lastBit = "4"; + break; + case 9: + lastBit = "3"; + break; + case 10: + lastBit = "2"; + break; + } + return lastBit; + } + + /** + * 从身份证号码中获取性别代码 + * + * @param idNum 身份证号码 + * @return 性别代码 (1 男 2 女) + */ + public static String getGenderFromIdNum(String idNum) { + // @todo 校验身份证号码 + String genderNumber = null; + if (idNum.length() == 15) { + genderNumber = idNum.substring(13, 14); + } else if (idNum.length() == 18) { + genderNumber = idNum.substring(16, 17); + } + return Integer.parseInt(genderNumber) % 2 > 0 ? "1" : "2"; + } + + /** + * 获得非null字符串。若str不等于null,则返回str,否则返回空串 + * + * @param str + * @return + */ + public static String getNotnullStr(String str) { + String newStr = ""; + if (str != null) + newStr = str; + return newStr; + } + + /** + * 获取n位随机数 + * + * @param num 位数 + * @return + */ + public static String getRandom(int num) { + StringBuilder sb = new StringBuilder(); + int[] random = new int[num]; + for (int i = 0; i < random.length; i++) { + sb.append(random[i]); + } + return sb.toString(); + } + + public static String parseMsgTemplate(String template, Map params) { + if (template == null || template.length() == 0) + throw new RuntimeException("解析字符串消息模板需要解析的模板为空"); + if (params == null) + throw new RuntimeException("解析字符串消息模板需要解析的参数为空"); + String msg = template; + for (String key : params.keySet()) { + String value = ""; + Object valueOri = params.get(key); + if (valueOri != null && valueOri instanceof Date) + try { + value = DateUtil.shortFmt(new Timestamp(((Date) valueOri).getTime())); + } catch (Exception e) { + value = DateUtil.shortFmt(((Date) valueOri)); + } + else + value = String.valueOf(valueOri); + + msg = msg.replaceAll("\\$\\{" + key + "\\}", value); + } + return msg; + } + + /** + * 生成32位uuid + * + * @return 32位数uuid(去掉-分隔符) + */ + public static String getUUID32() { + return UUID.randomUUID().toString().replace("-", "").toLowerCase(); + } + + public static Boolean isNumber(String source) { + if(StrUtil.isEmpty(source)){ + return false; + }else{ + return source.matches("^[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$"); + } + } +} diff --git a/server/src/main/java/com/aisino/iles/common/util/ValidateIdCardUtil.java b/server/src/main/java/com/aisino/iles/common/util/ValidateIdCardUtil.java new file mode 100644 index 0000000..db69c45 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/common/util/ValidateIdCardUtil.java @@ -0,0 +1,131 @@ +package com.aisino.iles.common.util; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +public class ValidateIdCardUtil { + + + final static Map zoneNum = new HashMap(); + + static { + zoneNum.put(11, "北京"); + zoneNum.put(12, "天津"); + zoneNum.put(13, "河北"); + zoneNum.put(14, "山西"); + zoneNum.put(15, "内蒙古"); + zoneNum.put(21, "辽宁"); + zoneNum.put(22, "吉林"); + zoneNum.put(23, "黑龙江"); + zoneNum.put(31, "上海"); + zoneNum.put(32, "江苏"); + zoneNum.put(33, "浙江"); + zoneNum.put(34, "安徽"); + zoneNum.put(35, "福建"); + zoneNum.put(36, "江西"); + zoneNum.put(37, "山东"); + zoneNum.put(41, "河南"); + zoneNum.put(42, "湖北"); + zoneNum.put(43, "湖南"); + zoneNum.put(44, "广东"); + zoneNum.put(45, "广西"); + zoneNum.put(46, "海南"); + zoneNum.put(50, "重庆"); + zoneNum.put(51, "四川"); + zoneNum.put(52, "贵州"); + zoneNum.put(53, "云南"); + zoneNum.put(54, "西藏"); + zoneNum.put(61, "陕西"); + zoneNum.put(62, "甘肃"); + zoneNum.put(63, "青海"); + zoneNum.put(64, "宁夏"); + zoneNum.put(65, "新疆"); + zoneNum.put(71, "台湾"); + zoneNum.put(81, "香港"); + zoneNum.put(82, "澳门"); + zoneNum.put(91, "外国"); + } + + final static int[] PARITYBIT = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'}; + final static int[] POWER_LIST = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, + 5, 8, 4, 2}; + + /** + * 身份证验证 + * + * @param certNo 号码内容 + * @return 是否有效 null和"" 都是false + */ + public static boolean isIDCard(String certNo) { + if (certNo == null || (certNo.length() != 15 && certNo.length() != 18)) + return false; + final char[] cs = certNo.toUpperCase().toCharArray(); + //校验位数 + int power = 0; + for (int i = 0; i < cs.length; i++) { + if (i == cs.length - 1 && cs[i] == 'X') + break;//最后一位可以 是X或x + if (cs[i] < '0' || cs[i] > '9') + return false; + if (i < cs.length - 1) { + power += (cs[i] - '0') * POWER_LIST[i]; + } + } + + //校验区位码 + if (!zoneNum.containsKey(Integer.valueOf(certNo.substring(0, 2)))) { + return false; + } + + //校验年份 + String year = null; + year = certNo.length() == 15 ? getIdcardCalendar(certNo) : certNo.substring(6, 10); + + + final int iyear = Integer.parseInt(year); + if (iyear < 1900 || iyear > Calendar.getInstance().get(Calendar.YEAR)) + return false;//1900年的PASS,超过今年的PASS + + //校验月份 + String month = certNo.length() == 15 ? certNo.substring(8, 10) : certNo.substring(10, 12); + final int imonth = Integer.parseInt(month); + if (imonth < 1 || imonth > 12) { + return false; + } + + //校验天数 + String day = certNo.length() == 15 ? certNo.substring(10, 12) : certNo.substring(12, 14); + final int iday = Integer.parseInt(day); + if (iday < 1 || iday > 31) + return false; + + //校验"校验码" + if (certNo.length() == 15) + return true; + return cs[cs.length - 1] == PARITYBIT[power % 11]; + } + + private static String getIdcardCalendar(String certNo) { + // 获取出生年月日 + String birthday = certNo.substring(6, 12); + SimpleDateFormat ft = new SimpleDateFormat("yyMMdd"); + Date birthdate = null; + try { + birthdate = ft.parse(birthday); + } catch (java.text.ParseException e) { + e.printStackTrace(); + } + Calendar cday = Calendar.getInstance(); + cday.setTime(birthdate); + String year = String.valueOf(cday.get(Calendar.YEAR)); + return year; + } + +// public static void main(String[] args) { +// boolean istrue = isIDCard("330622810725323"); +// System.out.println(istrue); +// } +} diff --git a/server/src/main/java/com/smartlx/sso/client/util/RestTemplateUtil.java b/server/src/main/java/com/smartlx/sso/client/util/RestTemplateUtil.java new file mode 100644 index 0000000..ba5c340 --- /dev/null +++ b/server/src/main/java/com/smartlx/sso/client/util/RestTemplateUtil.java @@ -0,0 +1,54 @@ +package com.smartlx.sso.client.util; + +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.util.CollectionUtils; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import java.nio.charset.Charset; +import java.util.Map; + +public class RestTemplateUtil { + public RestTemplateUtil() { + } + + public static T get(String url, MultiValueMap params, Class responseType) { + RestTemplate restTemplate = getRestTemplate("UTF-8"); + ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.GET, new HttpEntity(params), responseType, new Object[0]); + return (T)(responseEntity.getStatusCode() == HttpStatus.OK ? responseEntity.getBody() : null); + } + + public static T post(String url, Object body, Map headers, Class responseType) { + RestTemplate restTemplate = getRestTemplate("UTF-8"); + HttpHeaders httpHeaders = null; + if (!CollectionUtils.isEmpty(headers)) { + httpHeaders = new HttpHeaders(); + + for(String name : headers.keySet()) { + httpHeaders.set(name, (String)headers.get(name)); + } + } + + ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.POST, new HttpEntity(body, httpHeaders), responseType, new Object[0]); + return (T)(responseEntity.getStatusCode() == HttpStatus.OK ? responseEntity.getBody() : null); + } + + private static RestTemplate getRestTemplate(String charset) { + RestTemplate restTemplate = new RestTemplate(); + + for(HttpMessageConverter httpMessageConverter : restTemplate.getMessageConverters()) { + if (httpMessageConverter instanceof StringHttpMessageConverter) { + ((StringHttpMessageConverter)httpMessageConverter).setDefaultCharset(Charset.forName(charset)); + break; + } + } + + return restTemplate; + } +}