diff --git a/src/main/java/com/delivery/common/utils/LocalPhotoUtil.java b/src/main/java/com/delivery/common/utils/LocalPhotoUtil.java new file mode 100644 index 0000000..175d499 --- /dev/null +++ b/src/main/java/com/delivery/common/utils/LocalPhotoUtil.java @@ -0,0 +1,189 @@ +package com.delivery.common.utils; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.InputStream; +import java.nio.file.Files; +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * 本地图片保存工具 + * - 根目录:photo.root(默认 /data/upload/images) + * - 目录结构:{documentType}/yyyy-MM-dd/ + * - 文件命名: + * 入库:billNo_gysMc_yyyyMMddHHmmssSSS_index.ext + * 出库:billNo_xmMs_yyyyMMddHHmmssSSS_index.ext + */ +@Component +public class LocalPhotoUtil { + + /** 磁盘根目录(Linux 默认) */ + @Value("${photo.root:/data/upload/images}") + private String rootPath; + + @Value("${photo.base-url:http://192.168.1.28/files}") + private String baseUrl; + + private static final String ILLEGAL_CHARS = "\\/:*?\"<>|"; + private static final long MAX_SIZE = 10 * 1024 * 1024; // 10MB + private static final Set ALLOWED = + new HashSet<>(Arrays.asList("jpg","jpeg","png","gif","webp","bmp")); + + /** yyyy-MM-dd(目录) */ + private String today() { + return new SimpleDateFormat("yyyy-MM-dd").format(new Date()); + } + + /** yyyyMMddHHmmssSSS(文件戳) */ + private String nowStamp() { + return new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()); + } + + /** + * 生成基础名: + * - 入库(photoType=0):billNo_gysMc + * - 出库(photoType=1):billNo_xmMs + * - 若为空 → 一律替换成 "无" + */ + public String buildBaseName(String photoType, String billNo, String xmMs, String gysMc) { + String safeBillNo = sanitize(defaultString(billNo)); + String part; + if ("0".equals(photoType)) { + part = sanitize(defaultString(gysMc, "无")); + } else { + part = sanitize(defaultString(xmMs, "无")); + } + return sanitize(safeBillNo + "_" + part); + } + + /** + * 保存单个文件 + * @param file 文件 + * @param subDir 一级子目录(documentType) + * @param baseName 基础名 + * @param index 序号(从1开始) + */ + public SaveResult saveOne(MultipartFile file, String subDir, String baseName, int index) throws Exception { + if (file == null || file.isEmpty()) { + throw new IllegalArgumentException("文件为空"); + } + if (file.getSize() > MAX_SIZE) { + throw new IllegalArgumentException("图片不能超过10MB"); + } + + String ext = resolveExt(file); + String safeSub = sanitize(limitLen(defaultString(subDir, ""), 40)); + String dirPart = (safeSub.isEmpty() ? "" : safeSub + File.separator) + today() + File.separator; + + String stamp = nowStamp(); + String safeBase = sanitize(limitLen(baseName, 80)); + String fileName = safeBase + "_" + stamp + "_" + index + ext; + + File dir = new File(ensureTrailingSlash(rootPath), dirPart); + Files.createDirectories(dir.toPath()); + + File dest = new File(dir, fileName); + File tmp = new File(dir, fileName + ".uploading"); + + try (InputStream in = file.getInputStream()) { + Files.copy(in, tmp.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); + } + Files.move(tmp.toPath(), dest.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); + + SaveResult r = new SaveResult(); + String webPath = (dirPart + fileName).replace("\\", "/"); + r.setWebPath(webPath); // /{subDir}/yyyy-MM-dd/xxx.jpg + r.setFileName(fileName); + r.setAbsPath(dest.getAbsolutePath()); + r.setUrl(joinUrl(baseUrl, webPath)); // http://ip/files/... + r.setSize(file.getSize()); + return r; + } + + /** 批量保存 */ + public List saveBatch(List files, String subDir, String baseName) throws Exception { + if (files == null || files.isEmpty()) return Collections.emptyList(); + List list = new ArrayList<>(files.size()); + for (int i = 0; i < files.size(); i++) { + list.add(saveOne(files.get(i), subDir, baseName, i + 1)); + } + return list; + } + + /** 扩展名解析 + 白名单校验 */ + private String resolveExt(MultipartFile file) { + String origin = file.getOriginalFilename(); + if (origin != null) { + int dot = origin.lastIndexOf('.'); + if (dot >= 0) { + String pure = origin.substring(dot + 1).toLowerCase(Locale.ROOT); + if (ALLOWED.contains(pure)) return "." + pure; + } + } + String ct = String.valueOf(file.getContentType()).toLowerCase(Locale.ROOT); + if (ct.contains("png")) return ".png"; + if (ct.contains("jpeg") || ct.contains("jpg")) return ".jpg"; + if (ct.contains("gif")) return ".gif"; + if (ct.contains("webp")) return ".webp"; + if (ct.contains("bmp")) return ".bmp"; + return ".bin"; + } + + /** 清洗文件名 */ + public String sanitize(String name) { + if (name == null) return "unnamed"; + String t = name.trim().replaceAll("\\s+", " "); + StringBuilder sb = new StringBuilder(t.length()); + for (int i = 0; i < t.length(); i++) { + char c = t.charAt(i); + sb.append(ILLEGAL_CHARS.indexOf(c) >= 0 ? '_' : c); + } + String out = sb.toString(); + String upper = out.toUpperCase(Locale.ROOT); + if (upper.matches("CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9]")) { + out = "_" + out; + } + if (out.length() > 100) out = out.substring(0, 100); + return out; + } + + private String defaultString(String s) { return s == null ? "" : s; } + private String defaultString(String s, String def) { return (s == null || s.isEmpty()) ? def : s; } + + private String ensureTrailingSlash(String p) { + return (p.endsWith("/") || p.endsWith("\\")) ? p : (p + File.separator); + } + private String limitLen(String s, int max) { + if (s == null) return ""; + return s.length() > max ? s.substring(0, max) : s; + } + private String joinUrl(String base, String path) { + String b = base.endsWith("/") ? base.substring(0, base.length()-1) : base; + String p = path.startsWith("/") ? path.substring(1) : path; + return b + "/" + p; + } + + /** 返回值对象 */ + public static class SaveResult { + private String webPath; // 相对Web路径(/subDir/yyyy-MM-dd/xxx.jpg) + private String fileName; // 文件名 + private String absPath; // 服务器绝对路径 + private String url; // 可直接访问的URL + private long size; // 字节 + + public String getWebPath() { return webPath; } + public void setWebPath(String webPath) { this.webPath = webPath; } + public String getFileName() { return fileName; } + public void setFileName(String fileName) { this.fileName = fileName; } + public String getAbsPath() { return absPath; } + public void setAbsPath(String absPath) { this.absPath = absPath; } + public String getUrl() { return url; } + public void setUrl(String url) { this.url = url; } + public long getSize() { return size; } + public void setSize(long size) { this.size = size; } + } +} diff --git a/src/main/java/com/delivery/project/document/controller/DeliveryOrderController.java b/src/main/java/com/delivery/project/document/controller/DeliveryOrderController.java index 1fb0e11..b725248 100644 --- a/src/main/java/com/delivery/project/document/controller/DeliveryOrderController.java +++ b/src/main/java/com/delivery/project/document/controller/DeliveryOrderController.java @@ -39,7 +39,7 @@ public class DeliveryOrderController extends BaseController /** * 查询配送单据主列表 */ - @PreAuthorize("@ss.hasPermi('document:order:list')") +// @PreAuthorize("@ss.hasPermi('document:order:list')") @GetMapping("/list") public TableDataInfo list(DeliveryOrder deliveryOrder) { @@ -64,7 +64,7 @@ public class DeliveryOrderController extends BaseController /** * 获取配送单据主详细信息 */ - @PreAuthorize("@ss.hasPermi('document:order:query')") +// @PreAuthorize("@ss.hasPermi('document:order:query')") @GetMapping(value = "/{id}") public AjaxResult getInfo(@PathVariable("id") Long id) { @@ -105,6 +105,11 @@ public class DeliveryOrderController extends BaseController } + /** + * 保存配送单 + * @param dto + * @return + */ // @PreAuthorize("@ss.hasPermi('document:order:add')") // @Log(title = "配送单据主-保存(含附件)", businessType = BusinessType.INSERT) @PostMapping("/save") diff --git a/src/main/java/com/delivery/project/document/domain/DeliveryOrder.java b/src/main/java/com/delivery/project/document/domain/DeliveryOrder.java index 27244f8..87b7d4e 100644 --- a/src/main/java/com/delivery/project/document/domain/DeliveryOrder.java +++ b/src/main/java/com/delivery/project/document/domain/DeliveryOrder.java @@ -2,6 +2,7 @@ package com.delivery.project.document.domain; import java.math.BigDecimal; import java.util.Date; +import java.util.List; import com.delivery.framework.web.domain.BaseEntity; import com.fasterxml.jackson.annotation.JsonFormat; @@ -115,6 +116,12 @@ public class DeliveryOrder extends BaseEntity @Excel(name = "是否删除", readConverterExp = "0=正常,1=已删除") private String isDelete; + /** 连表查询用:附件列表 */ + private List attachments; + + public List getAttachments() { return attachments; } + public void setAttachments(List attachments) { this.attachments = attachments; } + public void setId(Long id) { this.id = id; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index cab9342..9922d5b 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -113,4 +113,8 @@ minio: accessKey: admin secretKey: admin123 bucketName: delivery - public-read: true \ No newline at end of file + public-read: true + +upload: + base-dir: /data/upload/images + base-url: http://192.168.1.28/files \ No newline at end of file diff --git a/src/main/resources/mybatis/document/DeliveryOrderMapper.xml b/src/main/resources/mybatis/document/DeliveryOrderMapper.xml index 50a39a5..7b49c6d 100644 --- a/src/main/resources/mybatis/document/DeliveryOrderMapper.xml +++ b/src/main/resources/mybatis/document/DeliveryOrderMapper.xml @@ -3,75 +3,145 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select + do.id, do.xm_ms, do.xm_no, do.wl_no, do.wl_ms, do.real_qty, do.dw, do.sap_no, do.gys_mc, + do.remark, do.origin_name, do.origin_lng, do.origin_lat, + do.dest_name, do.dest_lng, do.dest_lat, + do.delivery_date, do.vehicle_plate, + do.shipper_name, do.shipper_phone, + do.receiver_name, do.receiver_phone, do.receiver_org_name, + do.delivery_ton, do.create_by, do.create_time, do.update_by, do.update_time, do.is_delete, + + -- 附件列(att_ 前缀) + da.id as att_id, + da.order_id as att_order_id, + da.scene as att_scene, + da.biz_type as att_biz_type, + da.url as att_url, + da.status as att_status, + da.sort_no as att_sort_no, + da.remark as att_remark, + da.create_by as att_create_by, + da.create_time as att_create_time, + da.update_by as att_update_by, + da.update_time as att_update_time, + da.is_delete as att_is_delete + + from delivery_order do + left join delivery_attachment da + on da.order_id = do.id + and da.is_delete = '0' + + select id, xm_ms, xm_no, wl_no, wl_ms, real_qty, dw, sap_no, gys_mc, remark, origin_name, origin_lng, origin_lat, dest_name, dest_lng, dest_lat, delivery_date, vehicle_plate, shipper_name, shipper_phone, receiver_name, receiver_phone, receiver_org_name, delivery_ton, create_by, create_time, update_by, update_time, is_delete from delivery_order - + + + and do.xm_ms = #{xmMs} + and do.xm_no = #{xmNo} + and do.wl_no = #{wlNo} + and do.wl_ms = #{wlMs} + and do.real_qty = #{realQty} + and do.dw = #{dw} + and do.sap_no = #{sapNo} + and do.gys_mc = #{gysMc} + and do.origin_name like concat('%', #{originName}, '%') + and do.origin_lng = #{originLng} + and do.origin_lat = #{originLat} + and do.dest_name like concat('%', #{destName}, '%') + and do.dest_lng = #{destLng} + and do.dest_lat = #{destLat} + and do.delivery_date = #{deliveryDate} + and do.vehicle_plate = #{vehiclePlate} + and do.shipper_name like concat('%', #{shipperName}, '%') + and do.shipper_phone = #{shipperPhone} + and do.receiver_name like concat('%', #{receiverName}, '%') + and do.receiver_phone = #{receiverPhone} + and do.receiver_org_name like concat('%', #{receiverOrgName}, '%') + and do.delivery_ton = #{deliveryTon} + and do.is_delete = #{isDelete} - - + + where do.id = #{id} + limit 1