出库图片模块逻辑修改

新增打印标签功能,向pk数据库插入数据
This commit is contained in:
2025-09-19 11:23:51 +08:00
parent 2e32af8fbe
commit d390060597
11 changed files with 196 additions and 82 deletions

View File

@@ -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; }

View File

@@ -33,15 +33,19 @@ public class PhotoController {
* - photoType0(入库相关) / 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列表】

View File

@@ -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("插入失败");
}
}

View 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;
}

View 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);
}

View File

@@ -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);
}

View File

@@ -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 列表

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View 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>

View File

@@ -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,