新增打印据编号字段

新增保存图片到本地磁盘D盘接口
This commit is contained in:
2025-09-28 08:22:53 +08:00
parent d390060597
commit 7f0d4b6b6f
6 changed files with 472 additions and 3 deletions

View File

@@ -123,6 +123,7 @@ public class SecurityConfig
"/ws/**",
"/photo/**",
"/wisdom/stock/**",
"/system/media/**",
"/wisdom/**",
"/mock/**",
"/information/device/**",

View File

@@ -0,0 +1,464 @@
//package com.zg.project.wisdom.controller;
//
//import com.zg.framework.web.domain.AjaxResult;
//import org.apache.commons.lang3.StringUtils;
//import org.springframework.http.MediaType;
//import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
//import org.springframework.util.unit.DataSize;
//import org.springframework.web.bind.annotation.*;
//import org.springframework.web.multipart.MultipartFile;
//
//import javax.annotation.Resource;
//import java.io.IOException;
//import java.io.InputStream;
//import java.io.OutputStream;
//import java.nio.file.*;
//import java.util.*;
//import java.util.concurrent.CompletableFuture;
//import java.util.concurrent.Semaphore;
//
///**
// * 多媒体上传控制器(支持图片与视频并行落盘)
// */
//@RestController
//@RequestMapping("/system/media")
//public class MediaUploadController {
//
// /** 根目录(如需配置化,可改为从 application.yml 读取) */
// private static final Path ROOT_DIR = Paths.get("D:/uploads");
//
// /** 本次上传总大小限制100MB */
// private static final long MAX_TOTAL_BYTES = DataSize.ofMegabytes(100).toBytes();
//
// /** 图片/视频数量限制 */
// private static final int MAX_IMAGE_COUNT = 100;
// private static final int MAX_VIDEO_COUNT = 5;
//
// /** 单次请求内的并发上限(建议 2~4默认 4 */
// private static final int PER_REQUEST_CONCURRENCY = 4;
//
// /** 注入线程池 BeanthreadPoolTaskExecutor */
// @Resource(name = "threadPoolTaskExecutor")
// private ThreadPoolTaskExecutor executor;
//
// /**
// * 上传接口
// */
// @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
// public AjaxResult upload(
// @RequestParam("username") String username,
// @RequestParam("nameBase") String nameBase,
// @RequestParam(value = "imageNames", required = false) String imageNames,
// @RequestParam(value = "videoNames", required = false) String videoNames,
// @RequestPart(value = "images", required = false) MultipartFile[] images,
// @RequestPart(value = "videos", required = false) MultipartFile[] videos
// ) {
// try {
// // 1. 基础校验
// if (StringUtils.isBlank(username)) {
// return AjaxResult.error("username 不能为空");
// }
// if (StringUtils.isBlank(nameBase)) {
// return AjaxResult.error("nameBase命名信息不能为空");
// }
//
// int imageCount = images == null ? 0 : images.length;
// int videoCount = videos == null ? 0 : videos.length;
//
// if (imageCount == 0 && videoCount == 0) {
// return AjaxResult.error("请至少上传一张图片或一段视频");
// }
// if (imageCount > MAX_IMAGE_COUNT) {
// return AjaxResult.error("图片最多支持上传 " + MAX_IMAGE_COUNT + " 张");
// }
// if (videoCount > MAX_VIDEO_COUNT) {
// return AjaxResult.error("视频最多支持上传 " + MAX_VIDEO_COUNT + " 段");
// }
//
// // 2. 总体积校验
// long totalBytes = 0L;
// if (images != null) for (MultipartFile f : images) totalBytes += safeSize(f);
// if (videos != null) for (MultipartFile f : videos) totalBytes += safeSize(f);
// if (totalBytes > MAX_TOTAL_BYTES) {
// return AjaxResult.error("本次上传总大小超过 100 MB约 " + (totalBytes / 1024 / 1024) + " MB");
// }
//
// // 3. 目标目录
// String base = sanitize(nameBase);
// String user = sanitize(username);
// String folderName = base + "_" + user;
// Path targetDir = ROOT_DIR.resolve(folderName).normalize();
// if (!targetDir.startsWith(ROOT_DIR)) {
// return AjaxResult.error("非法路径");
// }
// Files.createDirectories(targetDir);
//
// // 4. 解析前端传递的文件名
// List<String> imageNameList = new ArrayList<>();
// List<String> videoNameList = new ArrayList<>();
//
// if (StringUtils.isNotBlank(imageNames)) {
// try {
// imageNameList = Arrays.asList(imageNames.split(","));
// } catch (Exception e) {
// // 如果解析失败,使用空列表
// imageNameList = new ArrayList<>();
// }
// }
// if (StringUtils.isNotBlank(videoNames)) {
// try {
// videoNameList = Arrays.asList(videoNames.split(","));
// } catch (Exception e) {
// // 如果解析失败,使用空列表
// videoNameList = new ArrayList<>();
// }
// }
//
// // 5. 并发落盘任务
// int seq = 1;
// List<CompletableFuture<Map<String, Object>>> futures = new ArrayList<>();
// Semaphore semaphore = new Semaphore(PER_REQUEST_CONCURRENCY);
//
// // 5.1 图片处理
// if (images != null) {
// for (int i = 0; i < images.length; i++) {
// MultipartFile img = images[i];
// if (img == null || img.isEmpty()) continue;
//
// // 使用前端传递的文件名,如果没有则从原始文件名获取扩展名
// String originalName = i < imageNameList.size() ? imageNameList.get(i) : img.getOriginalFilename();
// String ext = getExtension(originalName).toLowerCase(Locale.ROOT);
// if (StringUtils.isBlank(ext)) {
// ext = "jpg"; // 图片默认扩展名
// }
//
// final String filename = String.format("%s%s%03d.%s", base, user, seq++, ext);
// final Path dest = targetDir.resolve(filename).normalize();
//
// futures.add(CompletableFuture.supplyAsync(() -> {
// acquire(semaphore);
// try { return saveOne(img, dest, "image"); }
// finally { semaphore.release(); }
// }, executor));
// }
// }
//
// // 5.2 视频处理
// if (videos != null) {
// for (int i = 0; i < videos.length; i++) {
// MultipartFile vid = videos[i];
// if (vid == null || vid.isEmpty()) continue;
//
// // 使用前端传递的文件名,如果没有则从原始文件名获取扩展名
// String originalName = i < videoNameList.size() ? videoNameList.get(i) : vid.getOriginalFilename();
// String ext = getExtension(originalName).toLowerCase(Locale.ROOT);
// if (StringUtils.isBlank(ext)) {
// ext = "mp4"; // 视频默认扩展名
// }
//
// final String filename = String.format("%s%s%03d.%s", base, user, seq++, ext);
// final Path dest = targetDir.resolve(filename).normalize();
//
// futures.add(CompletableFuture.supplyAsync(() -> {
// acquire(semaphore);
// try { return saveOne(vid, dest, "video"); }
// finally { semaphore.release(); }
// }, executor));
// }
// }
//
// // 6. 等待完成并返回
// List<Map<String, Object>> files = new ArrayList<>(futures.size());
// for (CompletableFuture<Map<String, Object>> f : futures) {
// files.add(f.join());
// }
// files.sort(Comparator.comparing(m -> String.valueOf(m.get("path"))));
//
// Map<String, Object> data = new LinkedHashMap<>();
// data.put("folder", targetDir.toString());
// data.put("username", username);
// data.put("nameBase", nameBase);
// data.put("totalBytes", totalBytes);
// data.put("count", files.size());
// data.put("files", files);
//
// return AjaxResult.success("上传成功", data);
//
// } catch (Exception e) {
// return AjaxResult.error("保存文件失败:" + e.getMessage());
// }
// }
//
// // ========================= 辅助方法 =========================
//
// /** 安全获取文件大小,避免 NPE */
// private static long safeSize(MultipartFile f) {
// try { return (f == null) ? 0L : f.getSize(); }
// catch (Exception ignored) { return 0L; }
// }
//
// /** 获取扩展名(无则返回空字符串) */
// private static String getExtension(String originalName) {
// if (StringUtils.isBlank(originalName)) return "";
// int i = originalName.lastIndexOf('.');
// return (i >= 0 && i < originalName.length() - 1) ? originalName.substring(i + 1) : "";
// }
//
// /** 单文件保存 */
// private Map<String, Object> saveOne(MultipartFile src, Path dest, String type) {
// try {
// Path parent = dest.getParent();
// if (parent == null) throw new IOException("父目录为空");
// if (!dest.normalize().startsWith(parent.normalize())) throw new IOException("非法路径");
//
// try (InputStream in = src.getInputStream();
// OutputStream out = Files.newOutputStream(dest,
// StandardOpenOption.CREATE,
// StandardOpenOption.TRUNCATE_EXISTING,
// StandardOpenOption.WRITE)) {
// in.transferTo(out);
// }
//
// Map<String, Object> m = new LinkedHashMap<>();
// m.put("type", type);
// m.put("path", dest.toString());
// m.put("sizeBytes", src.getSize());
// m.put("sizeMB", String.format(Locale.ROOT, "%.2f", src.getSize() / 1024.0 / 1024.0));
// return m;
//
// } catch (Exception ex) {
// throw new RuntimeException("保存失败:" + dest.getFileName() + ",原因:" + ex.getMessage(), ex);
// }
// }
//
// /** 获取信号量许可证(受控并发) */
// private static void acquire(Semaphore sem) {
// try { sem.acquire(); }
// catch (InterruptedException e) {
// Thread.currentThread().interrupt();
// throw new RuntimeException("线程被中断", e);
// }
// }
//
// /** 文件名/目录名清洗 */
// private static String sanitize(String s) {
// String cleaned = s.replaceAll("[^\\p{L}\\p{N}_-]", "_");
// return cleaned.length() > 64 ? cleaned.substring(0, 64) : cleaned;
// }
//}
package com.zg.project.wisdom.controller;
import com.zg.framework.web.domain.AjaxResult;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.unit.DataSize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Semaphore;
/**
* 多媒体上传控制器(支持图片与视频并行落盘)
*/
@RestController
@RequestMapping("/system/media")
public class MediaUploadController {
/** 根目录(如需配置化,可改为从 application.yml 读取) */
private static final Path ROOT_DIR = Paths.get("D:/uploads");
/** 本次上传总大小限制100MB */
private static final long MAX_TOTAL_BYTES = DataSize.ofMegabytes(100).toBytes();
/** 图片/视频数量限制 */
private static final int MAX_IMAGE_COUNT = 100;
private static final int MAX_VIDEO_COUNT = 5;
/** 单次请求内的并发上限(建议 2~4默认 4 */
private static final int PER_REQUEST_CONCURRENCY = 4;
/** 注入线程池 BeanthreadPoolTaskExecutor */
@Resource(name = "threadPoolTaskExecutor")
private ThreadPoolTaskExecutor executor;
/**
* 上传接口
*/
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public AjaxResult upload(
@RequestParam("username") String username,
@RequestParam("nameBase") String nameBase,
@RequestPart(value = "images", required = false) MultipartFile[] images,
@RequestPart(value = "videos", required = false) MultipartFile[] videos
) {
try {
// 1. 基础校验
if (StringUtils.isBlank(username)) {
return AjaxResult.error("username 不能为空");
}
if (StringUtils.isBlank(nameBase)) {
return AjaxResult.error("nameBase命名信息不能为空");
}
int imageCount = images == null ? 0 : images.length;
int videoCount = videos == null ? 0 : videos.length;
if (imageCount == 0 && videoCount == 0) {
return AjaxResult.error("请至少上传一张图片或一段视频");
}
if (imageCount > MAX_IMAGE_COUNT) {
return AjaxResult.error("图片最多支持上传 " + MAX_IMAGE_COUNT + "");
}
if (videoCount > MAX_VIDEO_COUNT) {
return AjaxResult.error("视频最多支持上传 " + MAX_VIDEO_COUNT + "");
}
// 2. 总体积校验
long totalBytes = 0L;
if (images != null) for (MultipartFile f : images) totalBytes += safeSize(f);
if (videos != null) for (MultipartFile f : videos) totalBytes += safeSize(f);
if (totalBytes > MAX_TOTAL_BYTES) {
return AjaxResult.error("本次上传总大小超过 100 MB" + (totalBytes / 1024 / 1024) + " MB");
}
// 3. 目标目录
String base = sanitize(nameBase);
String user = sanitize(username);
String folderName = base + "_" + user;
Path targetDir = ROOT_DIR.resolve(folderName).normalize();
if (!targetDir.startsWith(ROOT_DIR)) {
return AjaxResult.error("非法路径");
}
Files.createDirectories(targetDir);
// 4. 并发落盘任务
int seq = 1;
List<CompletableFuture<Map<String, Object>>> futures = new ArrayList<>();
Semaphore semaphore = new Semaphore(PER_REQUEST_CONCURRENCY);
// 4.1 图片(不做格式校验)
if (images != null) {
for (MultipartFile img : images) {
if (img == null || img.isEmpty()) continue;
String ext = getExtension(img.getOriginalFilename()).toLowerCase(Locale.ROOT);
final String filename = String.format(
"%s%s%03d%s", base, user, seq++, StringUtils.isBlank(ext) ? "" : "." + ext
);
final Path dest = targetDir.resolve(filename).normalize();
futures.add(CompletableFuture.supplyAsync(() -> {
acquire(semaphore);
try { return saveOne(img, dest, "image"); }
finally { semaphore.release(); }
}, executor));
}
}
// 4.2 视频(不做格式校验)
if (videos != null) {
for (MultipartFile vid : videos) {
if (vid == null || vid.isEmpty()) continue;
String ext = getExtension(vid.getOriginalFilename()).toLowerCase(Locale.ROOT);
final String filename = String.format(
"%s%s%03d%s", base, user, seq++, StringUtils.isBlank(ext) ? "" : "." + ext
);
final Path dest = targetDir.resolve(filename).normalize();
futures.add(CompletableFuture.supplyAsync(() -> {
acquire(semaphore);
try { return saveOne(vid, dest, "video"); }
finally { semaphore.release(); }
}, executor));
}
}
// 5. 等待完成并返回
List<Map<String, Object>> files = new ArrayList<>(futures.size());
for (CompletableFuture<Map<String, Object>> f : futures) {
files.add(f.join());
}
files.sort(Comparator.comparing(m -> String.valueOf(m.get("path"))));
Map<String, Object> data = new LinkedHashMap<>();
data.put("folder", targetDir.toString());
data.put("username", username);
data.put("nameBase", nameBase);
data.put("totalBytes", totalBytes);
data.put("count", files.size());
data.put("files", files);
return AjaxResult.success("上传成功", data);
} catch (Exception e) {
return AjaxResult.error("保存文件失败:" + e.getMessage());
}
}
// ========================= 辅助方法 =========================
/** 安全获取文件大小,避免 NPE */
private static long safeSize(MultipartFile f) {
try { return (f == null) ? 0L : f.getSize(); }
catch (Exception ignored) { return 0L; }
}
/** 获取扩展名(无则返回空字符串) */
private static String getExtension(String originalName) {
if (StringUtils.isBlank(originalName)) return "";
int i = originalName.lastIndexOf('.');
return (i >= 0 && i < originalName.length() - 1) ? originalName.substring(i + 1) : "";
}
/** 单文件保存 */
private Map<String, Object> saveOne(MultipartFile src, Path dest, String type) {
try {
Path parent = dest.getParent();
if (parent == null) throw new IOException("父目录为空");
if (!dest.normalize().startsWith(parent.normalize())) throw new IOException("非法路径");
try (InputStream in = src.getInputStream();
OutputStream out = Files.newOutputStream(dest,
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING,
StandardOpenOption.WRITE)) {
in.transferTo(out);
}
Map<String, Object> m = new LinkedHashMap<>();
m.put("type", type);
m.put("path", dest.toString());
m.put("sizeBytes", src.getSize());
m.put("sizeMB", String.format(Locale.ROOT, "%.2f", src.getSize() / 1024.0 / 1024.0));
return m;
} catch (Exception ex) {
throw new RuntimeException("保存失败:" + dest.getFileName() + ",原因:" + ex.getMessage(), ex);
}
}
/** 获取信号量许可证(受控并发) */
private static void acquire(Semaphore sem) {
try { sem.acquire(); }
catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("线程被中断", e);
}
}
/** 文件名/目录名清洗 */
private static String sanitize(String s) {
String cleaned = s.replaceAll("[^\\p{L}\\p{N}_-]", "_");
return cleaned.length() > 64 ? cleaned.substring(0, 64) : cleaned;
}
}

View File

@@ -23,4 +23,6 @@ public class PkDatDTO {
private String xmMs;
/** 库位 */
private String pcode;
/** 打印机编号 */
private Integer printer;
}

View File

@@ -6,6 +6,7 @@ spring:
druid:
# 主库数据源
master:
# url: jdbc:mysql://101.132.133.142:3306/wisdom?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
url: jdbc:mysql://192.168.1.20:3306/wisdom?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
# url: jdbc:mysql://192.168.1.251:3306/wisdom?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
# url: jdbc:mysql://localhost:3306/wisdom?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8

View File

@@ -57,9 +57,9 @@ spring:
servlet:
multipart:
# 单个文件大小
max-file-size: 10MB
max-file-size: 100MB
# 设置总上传的文件大小
max-request-size: 20MB
max-request-size: 100MB
# 服务模块
devtools:
restart:
@@ -68,6 +68,7 @@ spring:
# redis 配置
redis:
# 地址
# host: 101.132.133.142
host: 192.168.1.20
# host: 192.168.1.251
# host: localhost

View File

@@ -11,7 +11,7 @@
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.wlNo}, #{item.realQty}, 1, 1, #{item.sapNo},
#{item.gysMc}, #{item.remark}, #{item.xmMs}, #{item.pcode}, 1)
#{item.gysMc}, #{item.remark}, #{item.xmMs}, #{item.pcode}, #{item.printer})
</foreach>
</insert>