出库图片模块逻辑修改
新增打印标签功能,向pk数据库插入数据
This commit is contained in:
@@ -13,18 +13,17 @@ import java.util.*;
|
||||
/**
|
||||
* 本地图片保存工具
|
||||
* - 根目录:photo.root(默认 D:\photo)
|
||||
* - 目录结构:{subDir}/yyyy-MM-dd/
|
||||
* - 文件命名:{billNo}_{sapNo}_{xmMs}_{yyyyMMddHHmmssSSS}_{index}.{ext}
|
||||
* - 返回:相对web路径(用于拼接 /photo/ 前缀生成可访问URL)与文件名
|
||||
* - 目录结构:{documentType}/yyyy-MM-dd/
|
||||
* - 文件命名:
|
||||
* 入库:billNo_gysMc_yyyyMMddHHmmssSSS_index.ext
|
||||
* 出库:billNo_xmMs_yyyyMMddHHmmssSSS_index.ext
|
||||
*/
|
||||
@Component
|
||||
public class LocalPhotoUtil {
|
||||
|
||||
/** 本地根目录,可在 yml 配置:photo.root: D:\photo */
|
||||
@Value("${photo.root:D:\\\\photo}")
|
||||
private String rootPath;
|
||||
|
||||
/** 非法文件名字符(Windows) */
|
||||
private static final String ILLEGAL_CHARS = "\\/:*?\"<>|";
|
||||
|
||||
/** yyyy-MM-dd(目录) */
|
||||
@@ -38,22 +37,28 @@ public class LocalPhotoUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装基础文件名:billNo_sapNo_xmMs(不含后缀、不含时间戳/序号)
|
||||
* 生成基础名:
|
||||
* - 入库(photoType=0):billNo_gysMc
|
||||
* - 出库(photoType=1):billNo_xmMs
|
||||
* - 若为空 → 一律替换成 "无"
|
||||
*/
|
||||
public String buildBaseName(String billNo, String xmMs) {
|
||||
String b = defaultString(billNo);
|
||||
String x = defaultString(xmMs);
|
||||
String joined = String.join("_", b, x);
|
||||
return sanitize(joined);
|
||||
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 一级子目录(建议 "photo" 固定,也可按业务传入)
|
||||
* @param baseName 基础名(不含扩展名;可用 buildBaseName 生成)
|
||||
* @param index 序号(从1开始),用于同批次区分
|
||||
* @return SaveResult(webPath, fileName)
|
||||
* @param subDir 一级子目录(documentType)
|
||||
* @param baseName 基础名
|
||||
* @param index 序号
|
||||
*/
|
||||
public SaveResult saveOne(MultipartFile file, String subDir, String baseName, int index) throws Exception {
|
||||
String ext = resolveExt(file);
|
||||
@@ -62,7 +67,6 @@ public class LocalPhotoUtil {
|
||||
|
||||
String stamp = nowStamp();
|
||||
String safeBase = sanitize(baseName);
|
||||
// 最终文件名:base_时间戳_序号.ext
|
||||
String fileName = safeBase + "_" + stamp + "_" + index + ext;
|
||||
|
||||
File dir = new File(rootPath, dirPart);
|
||||
@@ -76,16 +80,13 @@ public class LocalPhotoUtil {
|
||||
}
|
||||
|
||||
SaveResult r = new SaveResult();
|
||||
r.setWebPath((dirPart + fileName).replace("\\", "/")); // e.g. photo/2025-09-01/xxx.jpg
|
||||
r.setWebPath((dirPart + fileName).replace("\\", "/"));
|
||||
r.setFileName(fileName);
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量保存(顺序与入参 files 一致)
|
||||
* @param files 文件列表
|
||||
* @param subDir 一级子目录(建议传 "photo")
|
||||
* @param baseName 基础名(不含扩展名)
|
||||
* 批量保存
|
||||
*/
|
||||
public List<SaveResult> saveBatch(List<MultipartFile> files, String subDir, String baseName) throws Exception {
|
||||
if (files == null || files.isEmpty()) {
|
||||
@@ -98,27 +99,14 @@ public class LocalPhotoUtil {
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 便捷方法:直接传业务字段(内部会调用 buildBaseName)
|
||||
* @param files 多文件
|
||||
* @param subDir 一级子目录(建议 "photo")
|
||||
* @param billNo 单据号
|
||||
* @param xmMs 项目描述
|
||||
*/
|
||||
public List<SaveResult> saveBatchByBiz(List<MultipartFile> files, String subDir,
|
||||
String billNo, String xmMs) throws Exception {
|
||||
String base = buildBaseName(billNo, xmMs);
|
||||
return saveBatch(files, subDir, base);
|
||||
}
|
||||
|
||||
/** 解析扩展名:优先取原文件名,其次按 contentType 兜底 */
|
||||
/** 解析扩展名 */
|
||||
private String resolveExt(MultipartFile file) {
|
||||
String origin = file.getOriginalFilename();
|
||||
if (origin != null) {
|
||||
int dot = origin.lastIndexOf('.');
|
||||
if (dot >= 0) {
|
||||
String ext = origin.substring(dot);
|
||||
if (ext.length() <= 10) { // 简单防御:避免异常超长扩展名
|
||||
if (ext.length() <= 10) {
|
||||
return ext;
|
||||
}
|
||||
}
|
||||
@@ -129,7 +117,7 @@ public class LocalPhotoUtil {
|
||||
return ".bin";
|
||||
}
|
||||
|
||||
/** 清洗文件名:去空白、替换非法字符、规避Windows保留名、限长 */
|
||||
/** 清洗文件名 */
|
||||
public String sanitize(String name) {
|
||||
if (name == null) return "unnamed";
|
||||
String t = name.trim().replaceAll("\\s+", " ");
|
||||
@@ -143,14 +131,12 @@ public class LocalPhotoUtil {
|
||||
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;
|
||||
}
|
||||
|
||||
/** null 转空串;带默认值重载 */
|
||||
private String defaultString(String s) {
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
@@ -160,11 +146,8 @@ public class LocalPhotoUtil {
|
||||
|
||||
/** 返回值对象 */
|
||||
public static class SaveResult {
|
||||
/** 相对web路径,例如 photo/2025-09-01/xxx.jpg(用于拼接 /photo/ 前缀生成URL) */
|
||||
private String webPath;
|
||||
/** 最终文件名(含扩展名) */
|
||||
private String fileName;
|
||||
|
||||
public String getWebPath() { return webPath; }
|
||||
public void setWebPath(String webPath) { this.webPath = webPath; }
|
||||
public String getFileName() { return fileName; }
|
||||
|
||||
@@ -33,15 +33,19 @@ public class PhotoController {
|
||||
* - photoType:0(入库相关) / 1(出库相关)
|
||||
* - billNo:单据号
|
||||
* - xmMs:项目描述
|
||||
* - sapNo:订单号
|
||||
* - documentType:单据类型(建文件夹用,不入库)
|
||||
* - gysMc:供应商名称(入库时命名用)
|
||||
* 返回:每张图片的可访问 URL 列表
|
||||
*/
|
||||
@PostMapping(value = "/upload/batch", consumes = "multipart/form-data")
|
||||
public AjaxResult uploadBatch(@RequestPart("files") List<MultipartFile> files,
|
||||
@RequestParam("photoType") String photoType,
|
||||
@RequestParam("billNo") String billNo,
|
||||
@RequestParam("xmMs") String xmMs) {
|
||||
List<String> urls = photoService.uploadAndSave(files, photoType, billNo, xmMs);
|
||||
@RequestParam(value = "xmMs", required = false, defaultValue = "") String xmMs,
|
||||
@RequestParam(value = "documentType", required = false, defaultValue = "default") String documentType,
|
||||
@RequestParam(value = "gysMc", required = false, defaultValue = "") String gysMc) {
|
||||
// 有新参数就走新方法;没有则兼容旧逻辑
|
||||
List<String> urls = photoService.uploadAndSave(files, photoType, billNo, xmMs, documentType, gysMc);
|
||||
return AjaxResult.success("上传成功", urls);
|
||||
}
|
||||
|
||||
@@ -50,14 +54,14 @@ public class PhotoController {
|
||||
* 与多图参数一致,只是文件字段为 file
|
||||
* 返回:单张图片的可访问 URL
|
||||
*/
|
||||
@PostMapping(value = "/upload", consumes = "multipart/form-data")
|
||||
public AjaxResult upload(@RequestPart("file") MultipartFile file,
|
||||
@RequestParam("photoType") String photoType,
|
||||
@RequestParam("billNo") String billNo,
|
||||
@RequestParam("xmMs") String xmMs) {
|
||||
List<String> urls = photoService.uploadAndSave(Collections.singletonList(file), photoType, billNo, xmMs);
|
||||
return AjaxResult.success("上传成功", urls.isEmpty() ? null : urls.get(0));
|
||||
}
|
||||
// @PostMapping(value = "/upload", consumes = "multipart/form-data")
|
||||
// public AjaxResult upload(@RequestPart("file") MultipartFile file,
|
||||
// @RequestParam("photoType") String photoType,
|
||||
// @RequestParam("billNo") String billNo,
|
||||
// @RequestParam("xmMs") String xmMs) {
|
||||
// List<String> urls = photoService.uploadAndSave(Collections.singletonList(file), photoType, billNo, xmMs);
|
||||
// return AjaxResult.success("上传成功", urls.isEmpty() ? null : urls.get(0));
|
||||
// }
|
||||
|
||||
/**
|
||||
* 【查询:按 billNo(可选photoType)取照片URL列表】
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.zg.project.wisdom.controller;
|
||||
|
||||
import com.zg.framework.web.domain.AjaxResult;
|
||||
import com.zg.project.wisdom.domain.dto.PkDatDTO;
|
||||
import com.zg.project.wisdom.service.IPkDatService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/pk/dat")
|
||||
public class PkDatController {
|
||||
|
||||
@Resource
|
||||
private IPkDatService pkDatService;
|
||||
|
||||
/**
|
||||
* 批量新增 pk.dat 数据
|
||||
*/
|
||||
@PostMapping("/batchAdd")
|
||||
public AjaxResult batchAdd(@RequestBody List<PkDatDTO> list) {
|
||||
int rows = pkDatService.batchInsertPkDat(list);
|
||||
return rows > 0 ? AjaxResult.success("成功插入 " + rows + " 条记录")
|
||||
: AjaxResult.error("插入失败");
|
||||
}
|
||||
}
|
||||
26
src/main/java/com/zg/project/wisdom/domain/dto/PkDatDTO.java
Normal file
26
src/main/java/com/zg/project/wisdom/domain/dto/PkDatDTO.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package com.zg.project.wisdom.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 前端传参 DTO
|
||||
*/
|
||||
@Data
|
||||
public class PkDatDTO {
|
||||
/** 物料号 */
|
||||
private String wlNo;
|
||||
/** 数量 */
|
||||
private BigDecimal realQty;
|
||||
/** 物料凭证(SAP单号) */
|
||||
private String sapNo;
|
||||
/** 供应商 */
|
||||
private String gysMc;
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
/** 项目描述 */
|
||||
private String xmMs;
|
||||
/** 库位 */
|
||||
private String pcode;
|
||||
}
|
||||
11
src/main/java/com/zg/project/wisdom/mapper/PkDatMapper.java
Normal file
11
src/main/java/com/zg/project/wisdom/mapper/PkDatMapper.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package com.zg.project.wisdom.mapper;
|
||||
|
||||
import com.zg.project.wisdom.domain.dto.PkDatDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface PkDatMapper {
|
||||
|
||||
int batchInsertPkDat(List<PkDatDTO> list);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.zg.project.wisdom.service;
|
||||
|
||||
import com.zg.project.wisdom.domain.dto.PkDatDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface IPkDatService {
|
||||
/**
|
||||
* 批量插入 pk.dat 数据
|
||||
* @param list DTO 列表
|
||||
* @return 插入行数
|
||||
*/
|
||||
int batchInsertPkDat(List<PkDatDTO> list);
|
||||
}
|
||||
@@ -14,16 +14,20 @@ public interface PhotoService {
|
||||
/**
|
||||
* 多图上传并落库,返回每张图片的可访问URL
|
||||
*
|
||||
* @param files 图片文件列表(必传)
|
||||
* @param photoType 照片业务类型:0=入库相关,1=出库相关(必填)
|
||||
* @param billNo 单据号(必填,用于关联)
|
||||
* @param xmMs 项目描述(用于文件命名,可为空)
|
||||
* @param files 图片文件列表(必传)
|
||||
* @param photoType 照片业务类型:0=入库相关,1=出库相关(必填)
|
||||
* @param billNo 单据号(必填,用于关联)
|
||||
* @param xmMs 项目描述(出库时命名用,可为空)
|
||||
* @param documentType 单据类型(用于目录结构,不入库表)
|
||||
* @param gysMc 供应商名称(入库时命名用)
|
||||
* @return URLs 列表(每个元素都是可直接 <img src> 的地址)
|
||||
*/
|
||||
List<String> uploadAndSave(List<MultipartFile> files,
|
||||
String photoType, String billNo,
|
||||
String xmMs);
|
||||
|
||||
String photoType,
|
||||
String billNo,
|
||||
String xmMs,
|
||||
String documentType,
|
||||
String gysMc);
|
||||
|
||||
/**
|
||||
* 按 billNo(可选 photoType)查询照片 URL 列表
|
||||
|
||||
@@ -38,10 +38,13 @@ public class PhotoServiceImpl implements PhotoService {
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<String> uploadAndSave(List<MultipartFile> files,
|
||||
String photoType, String billNo,
|
||||
String xmMs) {
|
||||
String photoType,
|
||||
String billNo,
|
||||
String xmMs,
|
||||
String documentType,
|
||||
String gysMc) {
|
||||
|
||||
// ========= 1. 入参校验 =========
|
||||
// 1. 基础校验
|
||||
if (files == null || files.isEmpty()) {
|
||||
throw new IllegalArgumentException("请选择要上传的图片");
|
||||
}
|
||||
@@ -51,48 +54,46 @@ public class PhotoServiceImpl implements PhotoService {
|
||||
if (StringUtils.isBlank(billNo)) {
|
||||
throw new IllegalArgumentException("billNo 不能为空");
|
||||
}
|
||||
if (StringUtils.isBlank(documentType)) {
|
||||
documentType = "default"; // 硬编码兜底
|
||||
}
|
||||
|
||||
// 组装基础文件名:billNo_sapNo_xmMs(内部会做非法字符清洗)
|
||||
final String baseName = localPhotoUtil.buildBaseName(
|
||||
StringUtils.defaultString(billNo),
|
||||
StringUtils.defaultString(xmMs));
|
||||
// 2. xmMs / gysMc 兜底处理
|
||||
String safeXmMs = StringUtils.isBlank(xmMs) ? "无" : xmMs;
|
||||
String safeGysMc = StringUtils.isBlank(gysMc) ? "无" : gysMc;
|
||||
|
||||
// 3. 基础名(入库用供应商,出库用项目)
|
||||
final String baseName = localPhotoUtil.buildBaseName(photoType, billNo, safeXmMs, safeGysMc);
|
||||
|
||||
try {
|
||||
// ========= 2. 本地落盘 =========
|
||||
// 目录:D:\photo\photo\yyyy-MM-dd\
|
||||
List<SaveResult> saved = localPhotoUtil.saveBatch(files, "", baseName);
|
||||
// 4. 保存文件
|
||||
List<LocalPhotoUtil.SaveResult> saved = localPhotoUtil.saveBatch(files, documentType, baseName);
|
||||
|
||||
// ========= 3. 生成URL + 组装入库对象 =========
|
||||
List<String> urls = new ArrayList<String>(saved.size());
|
||||
List<StockPhoto> rows = new ArrayList<StockPhoto>(saved.size());
|
||||
// 5. 拼 URL + 入库对象
|
||||
List<String> urls = new ArrayList<>(saved.size());
|
||||
List<StockPhoto> rows = new ArrayList<>(saved.size());
|
||||
|
||||
for (SaveResult s : saved) {
|
||||
// 根据当前上下文拼 URL,例如:http://host:port/photo/photo/2025-09-01/xxx.jpg
|
||||
for (LocalPhotoUtil.SaveResult s : saved) {
|
||||
String url = ServletUriComponentsBuilder.fromCurrentContextPath()
|
||||
.path("/photo/") // 与 StaticResourceConfig.addResourceHandlers 保持一致
|
||||
.path(s.getWebPath()) // 形如:photo/2025-09-01/xxx.jpg
|
||||
.path("/photo/")
|
||||
.path(s.getWebPath())
|
||||
.toUriString();
|
||||
urls.add(url);
|
||||
|
||||
// 只存一个URL到表中(其余信息按你最简诉求不存)
|
||||
StockPhoto p = new StockPhoto();
|
||||
p.setBillNo(billNo);
|
||||
p.setPhotoType(photoType);
|
||||
p.setFileName(s.getFileName());
|
||||
p.setUrl(url);
|
||||
// 如需记录创建人:p.setCreateBy(SecurityUtils.getUserId().toString());
|
||||
rows.add(p);
|
||||
}
|
||||
|
||||
// ========= 4. 批量入库 =========
|
||||
if (!rows.isEmpty()) {
|
||||
stockPhotoMapper.batchInsert(rows);
|
||||
}
|
||||
|
||||
return urls;
|
||||
} catch (Exception e) {
|
||||
// Service 层抛出 RuntimeException,触发 @Transactional 回滚数据库写入
|
||||
// (注意:文件已落盘无法自动回滚,必要时可加失败清理逻辑)
|
||||
throw new RuntimeException("上传/保存失败:" + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.zg.project.wisdom.service.impl;
|
||||
|
||||
import com.zg.project.wisdom.domain.dto.PkDatDTO;
|
||||
import com.zg.project.wisdom.mapper.PkDatMapper;
|
||||
import com.zg.project.wisdom.service.IPkDatService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class PkDatServiceImpl implements IPkDatService {
|
||||
|
||||
@Resource
|
||||
private PkDatMapper pkDatMapper;
|
||||
|
||||
@Override
|
||||
public int batchInsertPkDat(List<PkDatDTO> list) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
return pkDatMapper.batchInsertPkDat(list);
|
||||
}
|
||||
}
|
||||
18
src/main/resources/mybatis/wisdom/PkDatMapper.xml
Normal file
18
src/main/resources/mybatis/wisdom/PkDatMapper.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.zg.project.wisdom.mapper.PkDatMapper">
|
||||
|
||||
<insert id="batchInsertPkDat" parameterType="java.util.List">
|
||||
INSERT INTO pk.dat
|
||||
(wlh, num, ste, lnum, prf, gys, mrk, des_pro, inf, printer)
|
||||
VALUES
|
||||
<foreach collection="list" item="item" separator=",">
|
||||
(#{item.wlNo}, #{item.realQty}, 1, 1, #{item.sapNo},
|
||||
#{item.gysMc}, #{item.remark}, #{item.xmMs}, #{item.pcode}, 1)
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
</mapper>
|
||||
@@ -340,6 +340,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
a.xm_ms,
|
||||
a.xm_no_ck,
|
||||
a.xm_ms_ck,
|
||||
a.gys_mc,
|
||||
a.wl_no,
|
||||
a.wl_ms,
|
||||
a.gys_no,
|
||||
@@ -365,6 +366,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
MIN(t.xm_ms) AS xm_ms,
|
||||
MIN(t.xm_no_ck) AS xm_no_ck,
|
||||
MIN(t.xm_ms_ck) AS xm_ms_ck,
|
||||
MIN(t.gys_mc) AS gys_mc,
|
||||
MIN(t.wl_no) AS wl_no,
|
||||
MIN(t.wl_ms) AS wl_ms,
|
||||
MIN(t.gys_no) AS gys_no,
|
||||
|
||||
Reference in New Issue
Block a user