diff --git a/exam-module-exam/exam-module-exam-biz/src/main/java/pc/exam/pp/module/exam/controller/admin/exception/PsException.java b/exam-module-exam/exam-module-exam-biz/src/main/java/pc/exam/pp/module/exam/controller/admin/exception/PsException.java new file mode 100644 index 00000000..a334ade8 --- /dev/null +++ b/exam-module-exam/exam-module-exam-biz/src/main/java/pc/exam/pp/module/exam/controller/admin/exception/PsException.java @@ -0,0 +1,9 @@ +package pc.exam.pp.module.exam.controller.admin.exception; + +public class PsException extends RuntimeException { + + public PsException(String message) { + super(message); + } + +} diff --git a/exam-module-exam/exam-module-exam-biz/src/main/java/pc/exam/pp/module/exam/dal/dataobject/ExamPsKeyword.java b/exam-module-exam/exam-module-exam-biz/src/main/java/pc/exam/pp/module/exam/dal/dataobject/ExamPsKeyword.java new file mode 100644 index 00000000..ff2d328d --- /dev/null +++ b/exam-module-exam/exam-module-exam-biz/src/main/java/pc/exam/pp/module/exam/dal/dataobject/ExamPsKeyword.java @@ -0,0 +1,43 @@ +package pc.exam.pp.module.exam.dal.dataobject; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +@TableName(value = "exam_ps_keyword", autoResultMap = true) +@Data +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +public class ExamPsKeyword { + /** + * 主键id + */ + private String id; + /** + * 试题id + */ + private String quId; + /** + * 父id + */ + private String parentId; + /** + * 键 + */ + private String keyName; + /** + * 值 + */ + private String keyValue; + /** + * 权值 + */ + private String rate; + /** + * 类型(学生考点:1,结果考点:2) + */ + private String type; +} diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/Ps/PsController.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/Ps/PsController.java new file mode 100644 index 00000000..d76ad1ba --- /dev/null +++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/Ps/PsController.java @@ -0,0 +1,17 @@ +package pc.exam.pp.module.judgement.controller.admin.Ps; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import pc.exam.pp.module.judgement.controller.service.mysql.IMysqlServerice; +import pc.exam.pp.module.judgement.controller.service.ps.IPsService; + +@RestController +@RequestMapping("/tool/Ps") +public class PsController { + + @Autowired + private IPsService psService; + + +} diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/getpoints/GetPointsController.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/getpoints/GetPointsController.java index 371d4ef1..c02ab1fd 100644 --- a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/getpoints/GetPointsController.java +++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/getpoints/GetPointsController.java @@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.*; import pc.exam.pp.framework.common.pojo.CommonResult; import pc.exam.pp.framework.tenant.core.aop.TenantIgnore; import pc.exam.pp.module.exam.dal.dataobject.ExamQuestionAnswer; +import pc.exam.pp.module.judgement.controller.admin.getpoints.dto.PsAnswerSubmitDTO; import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.Points; import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.PointsVo; import pc.exam.pp.module.judgement.controller.service.getpoints.ExamGetPointsService; @@ -76,4 +77,41 @@ public class GetPointsController { public CommonResult get_browser_point(@RequestBody Points points) { return CommonResult.success(examGetPointsService.get_browser_point(points)); } + + /** + * 文件得出PS操作考点 + * @return 得分 + */ + @Operation(summary = "文件得出PS操作考点") + @PostMapping("/get_ps_point") + @TenantIgnore + public CommonResult get_ps_point(@RequestBody PointsVo pointsVo) throws IOException { + try { + return CommonResult.success(examGetPointsService.get_ps_point(pointsVo)); + + } catch (RuntimeException e) { + return CommonResult.error(987312, e.getMessage()); + } catch (InterruptedException e) { + return CommonResult.error(987312, e.getMessage()); + } + + } + /** + * 设置PS操作考点 + * @return 得分 + */ + @Operation(summary = "文件设置PS操作考点") + @PostMapping("/set_ps_point") + @TenantIgnore + public void set_ps_point(@RequestBody PsAnswerSubmitDTO psAnswerSubmitDTO) { + examGetPointsService.set_ps_point(psAnswerSubmitDTO); + } + @Operation(summary = "获取PS操作考点") + @GetMapping("/getPsPointById/{quId}") + @TenantIgnore + public CommonResult getPsPointById(@PathVariable("quId") String quId) { + return CommonResult.success(examGetPointsService.getPsPointById(quId)); + } + + } diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/getpoints/dto/PsAnswerNode.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/getpoints/dto/PsAnswerNode.java new file mode 100644 index 00000000..457b30f9 --- /dev/null +++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/getpoints/dto/PsAnswerNode.java @@ -0,0 +1,20 @@ +package pc.exam.pp.module.judgement.controller.admin.getpoints.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import java.util.List; + + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class PsAnswerNode { + private String key; + + private String value; + + private Integer rate; + + private List children; +} diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/getpoints/dto/PsAnswerSubmitDTO.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/getpoints/dto/PsAnswerSubmitDTO.java new file mode 100644 index 00000000..5f0c053f --- /dev/null +++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/getpoints/dto/PsAnswerSubmitDTO.java @@ -0,0 +1,16 @@ +package pc.exam.pp.module.judgement.controller.admin.getpoints.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +@Data +@NoArgsConstructor +@AllArgsConstructor +public class PsAnswerSubmitDTO { + private String quId; + private List questionAnswerList; + private String type; +} + diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/getpoints/dto/PsViewDto.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/getpoints/dto/PsViewDto.java new file mode 100644 index 00000000..acb8b246 --- /dev/null +++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/getpoints/dto/PsViewDto.java @@ -0,0 +1,17 @@ +package pc.exam.pp.module.judgement.controller.admin.getpoints.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.PsVo; + +import java.util.List; +@Data +@NoArgsConstructor +@AllArgsConstructor +public class PsViewDto { + private List pointList; + + private List answerList; +} diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/getpoints/vo/PointsVo.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/getpoints/vo/PointsVo.java index f320c9ab..a62f1642 100644 --- a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/getpoints/vo/PointsVo.java +++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/getpoints/vo/PointsVo.java @@ -8,7 +8,9 @@ import lombok.NoArgsConstructor; @AllArgsConstructor @NoArgsConstructor public class PointsVo { + //原始文件路径 private String shucaiPath; + //答案文件路径 private String answerPath; } diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/getpoints/vo/PsVo.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/getpoints/vo/PsVo.java new file mode 100644 index 00000000..b41b448c --- /dev/null +++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/getpoints/vo/PsVo.java @@ -0,0 +1,18 @@ +package pc.exam.pp.module.judgement.controller.admin.getpoints.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +@Data +@AllArgsConstructor +@NoArgsConstructor +public class PsVo { + + private String key; + + private String value; + + private List children; +} diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/getpoints/ExamGetPointsService.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/getpoints/ExamGetPointsService.java index b3ff5861..5761d886 100644 --- a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/getpoints/ExamGetPointsService.java +++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/getpoints/ExamGetPointsService.java @@ -1,6 +1,9 @@ package pc.exam.pp.module.judgement.controller.service.getpoints; +import pc.exam.pp.module.exam.dal.dataobject.ExamPsKeyword; import pc.exam.pp.module.exam.dal.dataobject.ExamQuestionAnswer; +import pc.exam.pp.module.judgement.controller.admin.getpoints.dto.PsAnswerSubmitDTO; +import pc.exam.pp.module.judgement.controller.admin.getpoints.dto.PsViewDto; import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.FilePointsVo; import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.Points; import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.PointsVo; @@ -23,4 +26,10 @@ public interface ExamGetPointsService { boolean update_mysql_point(Points points); List getPointById(String quId); + + PsViewDto get_ps_point(PointsVo pointsVo) throws RuntimeException, IOException, InterruptedException; + + void set_ps_point(PsAnswerSubmitDTO psAnswerSubmitDTO); + + PsViewDto getPsPointById(String quId); } diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/getpoints/ExamGetPointsServiceImpl.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/getpoints/ExamGetPointsServiceImpl.java index ce1638f7..a60267d0 100644 --- a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/getpoints/ExamGetPointsServiceImpl.java +++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/getpoints/ExamGetPointsServiceImpl.java @@ -1,12 +1,17 @@ package pc.exam.pp.module.judgement.controller.service.getpoints; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.xingyuv.jushauth.utils.UuidUtils; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.ibatis.executor.BatchResult; import org.springframework.stereotype.Service; +import pc.exam.pp.module.exam.controller.admin.exception.PsException; import pc.exam.pp.module.exam.dal.dataobject.ExamMysqlKeyword; +import pc.exam.pp.module.exam.dal.dataobject.ExamPsKeyword; import pc.exam.pp.module.exam.dal.dataobject.ExamQuestionAnswer; import pc.exam.pp.module.exam.dal.mysql.mysqlkeyword.MysqlKeywordMapper; import pc.exam.pp.module.exam.dal.mysql.question.ExamQuestionAnswerMapper; @@ -15,17 +20,22 @@ import pc.exam.pp.module.exam.utils.file.GetDifferencesBetweenFolders; import pc.exam.pp.module.exam.utils.uuid.IdUtils; import pc.exam.pp.module.infra.dal.dataobject.config.ConfigDO; import pc.exam.pp.module.infra.service.config.ConfigService; -import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.FileNode; -import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.FilePointsVo; -import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.Points; -import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.PointsVo; +import pc.exam.pp.module.judgement.controller.admin.getpoints.dto.PsAnswerNode; +import pc.exam.pp.module.judgement.controller.admin.getpoints.dto.PsAnswerSubmitDTO; +import pc.exam.pp.module.judgement.controller.admin.getpoints.dto.PsViewDto; +import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.*; +import pc.exam.pp.module.judgement.controller.service.ps.IPsService; +import pc.exam.pp.module.judgement.controller.utils.ps.PsUtil; import pc.exam.pp.module.judgement.controller.utils.zip.ZipUtil; -import java.io.File; -import java.io.IOException; +import java.io.*; +import java.net.URL; +import java.net.URLConnection; import java.nio.charset.Charset; import java.nio.charset.MalformedInputException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; @@ -43,7 +53,8 @@ public class ExamGetPointsServiceImpl implements ExamGetPointsService{ MysqlKeywordMapper mysqlKeywordMapper; @Resource private ExamQuestionAnswerMapper examQuestionAnswerMapper; - + @Resource + private IPsService psService; @Override public FilePointsVo get_file_point(PointsVo pointsVo) throws IOException { @@ -375,5 +386,322 @@ public class ExamGetPointsServiceImpl implements ExamGetPointsService{ return examQuestionAnswers; } + @Override + public PsViewDto get_ps_point(PointsVo pointsVo) throws RuntimeException { + String timestamp = String.valueOf(System.currentTimeMillis()); + String baseDir = "ps\\" + timestamp; + // 下载学生文件和答案文件 + String sthPath = downloadStudentFile(pointsVo.getShucaiPath(), baseDir); + String answerPath = downloadStudentFile(pointsVo.getAnswerPath(), baseDir); + + System.out.println("学生PSD路径: " + sthPath); + System.out.println("答案PSD路径: " + answerPath); + + String sthJsonPath = sthPath.replaceAll("(?i)\\.psd$", ".json"); + String answerJsonPath = answerPath.replaceAll("(?i)\\.psd$", ".json"); + + String photoshopExe = PsUtil.findPhotoshopExe(); + if (photoshopExe == null) { + throw new PsException("请先在本机安装PS软件!"); + } + + String homeDir = System.getProperty("user.dir"); + String jsxTemplatePath = homeDir + "\\ps\\checkPSD.jsx"; // 模板路径 + + try { + // 执行学生和答案PSD文件 + runTwoPsdsInOneScript(sthPath, answerPath, jsxTemplatePath, photoshopExe); + System.out.println("Photoshop脚本执行完毕"); + } catch (Exception e) { + throw new RuntimeException("执行 Photoshop 脚本失败: " + e.getMessage(), e); + } + + // 读取两个 JSON 文件 + try { + String sthJsonStr = Files.readString(Paths.get(sthJsonPath), StandardCharsets.UTF_8); + String answerJsonStr = Files.readString(Paths.get(answerJsonPath), StandardCharsets.UTF_8); + + + ObjectMapper mapper = new ObjectMapper(); + Map jsonStuMap = mapper.readValue(sthJsonStr, new TypeReference>() {}); + List stulist = new ArrayList<>(); + for (Map.Entry entry : jsonStuMap.entrySet()) { + stulist.add(mapToPsVo(entry.getKey(), entry.getValue())); + } + Map jsonAnswerMap = mapper.readValue(answerJsonStr, new TypeReference>() {}); + List answerlist = new ArrayList<>(); + for (Map.Entry entry : jsonAnswerMap.entrySet()) { + answerlist.add(mapToPsVo(entry.getKey(), entry.getValue())); + } + + + System.out.println("✅ 学生PSD数据: " + stulist); + System.out.println("✅ 答案PSD数据: " + answerlist); + PsViewDto psViewDto=new PsViewDto(); + + List pointList = findExtraOrDifferentInAnswer(stulist, answerlist); + System.out.println("答案相对考生多出来的节点集合: " + pointList); + + psViewDto.setPointList(pointList); + psViewDto.setAnswerList(answerlist); + + return psViewDto; + } catch (IOException e) { + e.printStackTrace(); // 打印异常堆栈 + throw new PsException("读取 JSON 文件失败!"); + } + } + + @Override + public void set_ps_point(PsAnswerSubmitDTO psAnswerSubmitDTO) { + List stuList = new ArrayList<>(); + String quId = String.valueOf(psAnswerSubmitDTO.getQuId()); + + // 开始递归转换 学生考点 + for (PsAnswerNode node : psAnswerSubmitDTO.getQuestionAnswerList()) { + convertNodeToKeyword(node, "0", quId, stuList); + } + //给List统一设置type + String type = psAnswerSubmitDTO.getType(); + for (ExamPsKeyword keyword : stuList) { + keyword.setType(type); + } + stuList.forEach(System.out::println); + //先根据删除试题id删除 + psService.deletedKeywordById(quId); + //再更新 + psService.insertPsKeywordList(stuList); + } + + @Override + public PsViewDto getPsPointById(String quId) { + PsViewDto psViewDto=new PsViewDto(); + List pointList=psService.selectPsPointByType(quId,"1"); + List answerList=psService.selectPsPointByType(quId,"2"); + psViewDto.setAnswerList(answerList); + psViewDto.setPointList(pointList); + return psViewDto; + } + + + + + // 递归转换方法 + private void convertNodeToKeyword(PsAnswerNode node, String parentId, String quId, List resultList) { + // 生成唯一 ID(用 UUID 或其它方式) + String id = IdUtils.simpleUUID(); + + ExamPsKeyword keyword = new ExamPsKeyword(); + keyword.setId(id); + keyword.setQuId(quId); + keyword.setParentId(parentId); + keyword.setKeyName(node.getKey()); + keyword.setKeyValue(node.getValue()); + keyword.setRate(node.getRate() == null ? null : node.getRate().toString()); + resultList.add(keyword); + + // 递归子节点 + if (node.getChildren() != null) { + for (PsAnswerNode child : node.getChildren()) { + convertNodeToKeyword(child, id, quId, resultList); + } + } + } + + + + + public List findExtraOrDifferentInAnswer(List stuList, List answerList) { + if (answerList == null) return Collections.emptyList(); + if (stuList == null) stuList = Collections.emptyList(); + + // 把学生列表转为 key->PsVo 映射 + Map stuMap = stuList.stream() + .collect(Collectors.toMap(PsVo::getKey, p -> p, (a, b) -> a)); + + List extraList = new ArrayList<>(); + + for (PsVo ansNode : answerList) { + PsVo stuNode = stuMap.get(ansNode.getKey()); + + if (stuNode == null) { + // 学生没有该键,整项加入 + extraList.add(ansNode); + } else { + boolean valueDifferent = !Objects.equals(ansNode.getValue(), stuNode.getValue()); + + // 递归对比子节点 + List ansChildren = ansNode.getChildren() != null ? ansNode.getChildren() : Collections.emptyList(); + List stuChildren = stuNode.getChildren() != null ? stuNode.getChildren() : Collections.emptyList(); + List childDiffs = findExtraOrDifferentInAnswer(stuChildren, ansChildren); + + if (valueDifferent || !childDiffs.isEmpty()) { + PsVo diffNode = new PsVo(); + diffNode.setKey(ansNode.getKey()); + diffNode.setValue(valueDifferent ? ansNode.getValue() : null); + diffNode.setChildren(childDiffs.isEmpty() ? null : childDiffs); + extraList.add(diffNode); + } + } + } + + return extraList; + } + + + public PsVo mapToPsVo(String key, Object value) { + PsVo vo = new PsVo(); + vo.setKey(key); + + if (value instanceof Map) { + Map map = (Map) value; + List children = new ArrayList<>(); + for (Map.Entry entry : map.entrySet()) { + children.add(mapToPsVo(entry.getKey(), entry.getValue())); + } + vo.setChildren(children); + vo.setValue(null); + + } else if (value instanceof List) { + List list = (List) value; + List children = new ArrayList<>(); + + for (Object item : list) { + if (item instanceof Map) { + Map mapItem = (Map) item; + // 获取图层名作为 key + String layerName = (String) mapItem.get("图层名"); + if (layerName == null) { + // 如果没有图层名,直接用空字符串 + layerName = ""; + } + PsVo childVo = new PsVo(); + childVo.setKey(layerName); + + // 除了图层名外的字段作为 children + List grandChildren = new ArrayList<>(); + for (Map.Entry e : mapItem.entrySet()) { + if (!"图层名".equals(e.getKey())) { + grandChildren.add(mapToPsVo(e.getKey(), e.getValue())); + } + } + childVo.setChildren(grandChildren); + childVo.setValue(null); + + children.add(childVo); + } else { + // 如果不是 Map 类型,按原逻辑处理 + children.add(mapToPsVo(null, item)); + } + } + vo.setChildren(children); + vo.setValue(null); + + } else { + // 基础类型 + vo.setValue(value == null ? null : value.toString()); + vo.setChildren(null); + } + return vo; + } + + private void runTwoPsdsInOneScript(String psdPath1, String psdPath2, String jsxTemplatePath, String photoshopExe) + throws IOException, InterruptedException { + + File psdFile1 = new File(psdPath1); + String baseDir = psdFile1.getParent(); + String jsxTargetPath = baseDir + File.separator + "run_both_" + System.currentTimeMillis() + ".jsx"; + + String jsxTemplate = Files.readString(Paths.get(jsxTemplatePath), StandardCharsets.UTF_8); + + String safePath1 = psdPath1.replace("\\", "\\\\").replace("'", "\\'"); + String safePath2 = psdPath2.replace("\\", "\\\\").replace("'", "\\'"); + String jsxContent = jsxTemplate + .replace("${inputPath1}", safePath1) + .replace("${inputPath2}", safePath2); + + Files.writeString(Paths.get(jsxTargetPath), jsxContent, StandardCharsets.UTF_8); + + String command = String.format("\"%s\" -r \"%s\"", photoshopExe, jsxTargetPath); + System.out.println("运行 Photoshop 脚本: " + command); + + Process process = Runtime.getRuntime().exec(command); + + // 异步打印输出日志 + new Thread(() -> { + try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + String line; + while ((line = br.readLine()) != null) { + System.out.println("[PS OUT] " + line); + } + } catch (IOException ignored) {} + }).start(); + + new Thread(() -> { + try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream()))) { + String line; + while ((line = br.readLine()) != null) { + System.err.println("[PS ERR] " + line); + } + } catch (IOException ignored) {} + }).start(); + + // 不使用 waitFor 阻塞,改成轮询判断两个 JSON 文件是否生成 + String jsonPath1 = psdPath1.replaceAll("(?i)\\.psd$", ".json"); + String jsonPath2 = psdPath2.replaceAll("(?i)\\.psd$", ".json"); + + int maxWaitSeconds = 60; // 最多等待 60秒,根据实际调整 + int waited = 0; + while (!(Files.exists(Paths.get(jsonPath1)) && Files.exists(Paths.get(jsonPath2)))) { + Thread.sleep(1000); // 每秒检查一次 + waited++; + if (waited > maxWaitSeconds) { + // 超时,结束进程并抛异常 + process.destroyForcibly(); + throw new RuntimeException("等待 Photoshop 生成 JSON 文件超时"); + } + } + + System.out.println("检测到 JSON 文件生成,关闭 Photoshop 进程..."); + + // 手动杀死 Photoshop 进程,确保释放资源 + Process killProcess = Runtime.getRuntime().exec("taskkill /IM Photoshop.exe /F"); + killProcess.waitFor(); + + System.out.println("Photoshop 进程已关闭"); + } + + + + + + public String downloadStudentFile(String fileUrl, String filePath) { + try { + URL url = new URL(fileUrl); + URLConnection connection = url.openConnection(); + + String fileName = new File(url.getPath()).getName(); + File dir = new File(filePath); + if (!dir.exists()) dir.mkdirs(); + + File saveFile = new File(dir, fileName); + + try (InputStream in = connection.getInputStream(); + FileOutputStream out = new FileOutputStream(saveFile)) { + + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = in.read(buffer)) != -1) { + out.write(buffer, 0, bytesRead); + } + + System.out.println("✅ 下载成功: " + saveFile.getAbsolutePath()); + return saveFile.getAbsolutePath(); + } + } catch (IOException e) { + System.err.println("❌ 下载失败: " + e.getMessage()); + return null; + } + } } diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/ps/IPsService.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/ps/IPsService.java new file mode 100644 index 00000000..7eaac6f0 --- /dev/null +++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/ps/IPsService.java @@ -0,0 +1,18 @@ +package pc.exam.pp.module.judgement.controller.service.ps; + +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Service; +import pc.exam.pp.module.exam.dal.dataobject.ExamPsKeyword; +import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.PsVo; + +import java.util.List; + +public interface IPsService { + void deletedKeywordById(String quId); + + void insertPsKeywordList(List resultList); + + + List selectPsPointByType(String quId, String type); + +} diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/ps/PsServiceImpl.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/ps/PsServiceImpl.java new file mode 100644 index 00000000..fc038335 --- /dev/null +++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/ps/PsServiceImpl.java @@ -0,0 +1,29 @@ +package pc.exam.pp.module.judgement.controller.service.ps; + +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import pc.exam.pp.module.exam.dal.dataobject.ExamPsKeyword; +import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.PsVo; +import pc.exam.pp.module.judgement.dal.mysql.ps.PsMapper; + +import java.util.List; + +@Service +public class PsServiceImpl implements IPsService { + @Resource + PsMapper psMapper; + @Override + public void deletedKeywordById(String quId) { + psMapper.deleteByQuId(quId); + } + + @Override + public void insertPsKeywordList(List resultList) { + psMapper.insertBatch(resultList); + } + + @Override + public List selectPsPointByType(String quId, String type) { + return psMapper.selectPsPointByType(quId,type); + } +} diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/utils/ps/PsUtil.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/utils/ps/PsUtil.java new file mode 100644 index 00000000..c08b2023 --- /dev/null +++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/utils/ps/PsUtil.java @@ -0,0 +1,53 @@ +package pc.exam.pp.module.judgement.controller.utils.ps; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +public class PsUtil { + + + + // 查询注册表路径,返回command字符串(即exe路径+参数) + private static String queryReg(String regPath) throws IOException, InterruptedException { + Process process = Runtime.getRuntime().exec("reg query \"" + regPath + "\" /ve"); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "GBK")); // 注册表输出一般GBK编码 + String line; + while ((line = reader.readLine()) != null) { + line = line.trim(); + if (line.startsWith("(默认)")) { + String[] parts = line.split(" "); + if (parts.length >= 3) { + return parts[parts.length - 1].replace("\"", "").trim(); // 去掉双引号 + } + } + } + process.waitFor(); + return null; + } + + // 尝试读取多个注册表路径获取 Photoshop 路径 + public static String findPhotoshopExe() { + String[] regPaths = { + "HKLM\\SOFTWARE\\Classes\\Applications\\Photoshop.exe\\shell\\edit\\command", + "HKLM\\SOFTWARE\\WOW6432Node\\Classes\\Applications\\Photoshop.exe\\shell\\edit\\command" + }; + + for (String path : regPaths) { + try { + String exePath = queryReg(path); + if (exePath != null && !exePath.isEmpty()) { + // 通常 exe 路径后面会跟参数 %1,只取 exe 路径部分 + int idx = exePath.toLowerCase().indexOf("photoshop.exe"); + if (idx != -1) { + exePath = exePath.substring(0, idx + "photoshop.exe".length()); + } + return exePath; + } + } catch (Exception e) { + // 忽略异常,继续尝试其他路径 + } + } + return null; // 没找到 + } +} diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/dal/mysql/ps/PsMapper.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/dal/mysql/ps/PsMapper.java new file mode 100644 index 00000000..9a130a4d --- /dev/null +++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/dal/mysql/ps/PsMapper.java @@ -0,0 +1,20 @@ +package pc.exam.pp.module.judgement.dal.mysql.ps; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import pc.exam.pp.framework.mybatis.core.mapper.BaseMapperX; +import pc.exam.pp.module.exam.dal.dataobject.ExamPsKeyword; +import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.PsVo; + +import java.util.List; + + +@Mapper +public interface PsMapper extends BaseMapperX { + + void deleteByQuId(String quId); + + + List selectPsPointByType(@Param("quId") String quId + ,@Param("type") String type); +} \ No newline at end of file diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/resources/mapper/ps/PsMapper.xml b/exam-module-judgement/exam-module-judgement-biz/src/main/resources/mapper/ps/PsMapper.xml new file mode 100644 index 00000000..fe451e5f --- /dev/null +++ b/exam-module-judgement/exam-module-judgement-biz/src/main/resources/mapper/ps/PsMapper.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + delete from exam_ps_keyword where qu_id =#{quId} + + + + + \ No newline at end of file diff --git a/exam-server/src/main/resources/application.yaml b/exam-server/src/main/resources/application.yaml index 03b2f981..7b810298 100644 --- a/exam-server/src/main/resources/application.yaml +++ b/exam-server/src/main/resources/application.yaml @@ -352,6 +352,7 @@ exam: - education_paper_session - exam_mysql_keyword - education_question_examine + - exam_ps_keyword ignore-caches: - user_role_ids - permission_menu_ids diff --git a/ps/checkPSD.jsx b/ps/checkPSD.jsx new file mode 100644 index 00000000..2161c131 --- /dev/null +++ b/ps/checkPSD.jsx @@ -0,0 +1,693 @@ + + +// 简单 JSON.stringify 兼容 +if (typeof JSON === "undefined") { + JSON = {}; +} + +var inputPath1 = '${inputPath1}'; +var inputPath2 = '${inputPath2}'; +// 载入XMP扩展支持(全局调用一次即可) +if (!ExternalObject.AdobeXMPScript) { + ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript'); +} +JSON.stringify = function (obj, replacer, space) { + if (typeof space === "number") { + space = new Array(space + 1).join(" "); + } + function format(value, indent) { + var type = typeof value; + if (value === null) return "null"; + if (type === "string") return '"' + value + '"'; + if (type === "number" || type === "boolean") return String(value); + if (type === "object") { + var isArray = (value instanceof Array); + var items = []; + var pad = indent + (space || ""); + for (var key in value) { + if (value.hasOwnProperty(key)) { + var val = format(value[key], pad); + if (!isArray) { + val = '"' + key + '": ' + val; + } + items.push(val); + } + } + if (isArray) { + return "[\n" + pad + items.join(",\n" + pad) + "\n" + indent + "]"; + } else { + return "{\n" + pad + items.join(",\n" + pad) + "\n" + indent + "}"; + } + } + return "null"; + } + return format(obj, ""); +}; + + + + +// 主函数 +function processFile(path) { + var fileRef = new File(path); + if (!fileRef.exists) { + // alert("PSD文件不存在: " + path); + throw new Error("PSD文件不存在"); + } + + + var doc = app.open(fileRef); + + + + var result = {}; + + // 文档属性 + result["颜色模式"] = getColorModeName(doc.mode); + result["颜色深度"] = getBitsPerChannelLabel(doc.bitsPerChannel); + result["图像宽度"] = doc.width.as('px') + " 像素"; + result["图像高度"] = doc.height.as('px') + " 像素"; + result["画布宽度"]= doc.width.as('px') + " 像素"; + result["画布高度"]= doc.height.as('px') + " 像素"; + result["分辨率"] = doc.resolution + " dpi"; + + // 图层数组 + result["图层数"] = doc.artLayers.length; + result["智能对象图层数"] = countSmartObjects(doc); + + // 图层详细信息 + result["图层信息"] = []; + for (var i = 0; i < doc.artLayers.length; i++) { + var layer = doc.artLayers[i]; + doc.activeLayer = layer; // 激活当前图层 + + + var info = {}; + info["图层名"] = layer.name; + info["类型"] = getLayerKindName(layer.kind); + info["可见"] = layer.visible; + info["锁定"] = layer.allLocked; + + try { + info["图层蒙版密度"] = layer.layerMaskDensity !== undefined ? layer.layerMaskDensity : null; + } catch (e) {} + + + // 文字图层特殊信息 + if (layer.kind === LayerKind.TEXT) { + var t = layer.textItem; + try { + info["文字内容"] = t.contents; + } catch (e) { + + } + try { + info["字体"] = fontMap[t.font] || t.font;} // 如果找不到对应中文名,则保留英文名 + catch (e) { + + } + try { + info["字号"] = t.size.toString(); }// 修正这里 + catch (e) { + } + + try { + info["颜色"] = getSolidColorHex(t.color);} + catch (e) { + + } + try { + info["字距"] = t.tracking;} + catch (e) { + + } + try { + info["仿斜体"] = t.fauxItalic;} + catch (e) { + + } + try { + info["仿粗体"] = t.fauxBold; + } catch (e) { + + } + + var transformProps = getTextTransformProps(); + if (transformProps) { + info["变形"] = transformProps; + } + } + + + // 详细读取样式(如果你需要更详细数据的话) + var layerStyles = getLayerStyles(); + if (layerStyles) { + info["图层样式"] = layerStyles; + } + + // 检测滤镜信息,放到info里 + var filterInfo = detectFilters(layer.name); + if (filterInfo) { + info["滤镜信息"] = filterInfo; + } + + result["图层信息"].push(info); + } + + + + + var jsonFilePath = path.replace(/\.psd$/i, ".json"); + var jsonFile = new File(jsonFilePath); + + if (jsonFile.open("w")) { + jsonFile.encoding = "UTF8"; + jsonFile.write(JSON.stringify(result, null, 2)); + jsonFile.close(); + // alert("JSON 文件已生成: " + jsonFilePath); + } else { + // alert("无法打开文件进行写入: " + jsonFilePath); + throw new Error("无法打开文件进行写入"); + } + + + doc.close(SaveOptions.DONOTSAVECHANGES); + + +} +// 运行两个 PSD 的处理 +processFile(inputPath1); +processFile(inputPath2); + +////////////////////////////////////////////// + +///////////////////////////////////////////// + +// 辅助函数 - 颜色模式 +function getColorModeName(mode) { + switch (mode) { + case DocumentMode.RGB: return "RGB"; + case DocumentMode.CMYK: return "CMYK"; + case DocumentMode.GRAYSCALE: return "灰度"; + case DocumentMode.BITMAP: return "位图"; + case DocumentMode.INDEXEDCOLOR: return "索引颜色"; + default: return "未知"; + } +} +function getBitsPerChannelLabel(bits) { + switch (bits) { + case BitsPerChannelType.ONE: return "1 位/通道"; + case BitsPerChannelType.EIGHT: return "8 位/通道"; + case BitsPerChannelType.SIXTEEN: return "16 位/通道"; + case BitsPerChannelType.THIRTYTWO: return "32 位/通道"; + default: return "未知"; + } +} + +// 辅助函数 - 图层类型名 +function getLayerKindName(kind) { + switch (kind) { + case LayerKind.NORMAL: return "普通图层"; + case LayerKind.TEXT: return "文字图层"; + case LayerKind.SMARTOBJECT: return "智能对象图层"; + case LayerKind.SOLIDFILL: return "纯色填充图层"; + case LayerKind.GRADIENTFILL: return "渐变填充图层"; + case LayerKind.PATTERNFILL: return "图案填充图层"; + case LayerKind.BRIGHTNESSCONTRAST: return "亮度/对比度调整层"; + case LayerKind.LEVELS: return "色阶调整层"; + case LayerKind.CURVES: return "曲线调整层"; + case LayerKind.EXPOSURE: return "曝光度调整层"; + case LayerKind.VIBRANCE: return "自然饱和度调整层"; + case LayerKind.HUESATURATION: return "色相/饱和度调整层"; + case LayerKind.COLORBALANCE: return "色彩平衡调整层"; + case LayerKind.BLACKANDWHITE: return "黑白调整层"; + case LayerKind.PHOTOFILTER: return "照片滤镜调整层"; + case LayerKind.CHANNELMIXER: return "通道混合器调整层"; + case LayerKind.COLORLOOKUP: return "颜色查找调整层"; + case LayerKind.INVERSION: return "反相调整层"; + case LayerKind.POSTERIZE: return "色调分离调整层"; + case LayerKind.THRESHOLD: return "阈值调整层"; + case LayerKind.GRADIENTMAP: return "渐变映射调整层"; + case LayerKind.SELECTIVECOLOR: return "可选颜色调整层"; + default: return "未知类型"; + } +} + +// 读取颜色为HEX +function getSolidColorHex(color) { + if (!color) return null; + if (color.typename === "SolidColor") { + if (color.rgb) { + var r = Math.round(color.rgb.red); + var g = Math.round(color.rgb.green); + var b = Math.round(color.rgb.blue); + return "#" + toHex(r) + toHex(g) + toHex(b); + } + } + return null; +} +function toHex(c) { + var h = c.toString(16); + return h.length == 1 ? "0" + h : h; +} + +// 统计智能对象图层数量 +function countSmartObjects(doc) { + var count = 0; + for (var i = 0; i < doc.artLayers.length; i++) { + if (doc.artLayers[i].kind === LayerKind.SMARTOBJECT) { + count++; + } + } + return count; +} + +// 读取文字图层的变形属性 +function getTextTransformProps() { + try { + var ref = new ActionReference(); + ref.putEnumerated(stringIDToTypeID("layer"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt")); + var desc = executeActionGet(ref); + if (desc.hasKey(stringIDToTypeID("textKey"))) { + var textDesc = desc.getObjectValue(stringIDToTypeID("textKey")); + if (textDesc.hasKey(stringIDToTypeID("transform"))) { + var transformDesc = textDesc.getObjectValue(stringIDToTypeID("transform")); + var horizontal = transformDesc.getDouble(stringIDToTypeID("horizontalShear")); + var vertical = transformDesc.getDouble(stringIDToTypeID("verticalShear")); + var scaleX = transformDesc.getDouble(stringIDToTypeID("horizontalScale")); + var scaleY = transformDesc.getDouble(stringIDToTypeID("verticalScale")); + return { + "水平扭曲": horizontal.toFixed(2) + "%", + "垂直扭曲": vertical.toFixed(2) + "%", + "水平缩放": scaleX.toFixed(2) + "%", + "垂直缩放": scaleY.toFixed(2) + "%" + }; + } + } + } catch (e) { + return null; + } + return null; +} + +function getLayerStyles() { + try { + var ref = new ActionReference(); + ref.putEnumerated(stringIDToTypeID("layer"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt")); + var desc = executeActionGet(ref); + + if (!desc.hasKey(stringIDToTypeID("layerEffects"))) { + $.writeln("图层无layerEffects"); + return null; + } + + var effects = desc.getObjectValue(stringIDToTypeID("layerEffects")); + var styles = {}; + + // ============ 描边 =============== + if (effects.hasKey(stringIDToTypeID("frameFX"))) { + var frameFX = effects.getObjectValue(stringIDToTypeID("frameFX")); + styles["描边_启用"] = frameFX.getBoolean(stringIDToTypeID("enabled")); + if (styles["描边_启用"]) { + if (frameFX.hasKey(stringIDToTypeID("size"))) { + styles["描边_大小"] = frameFX.getUnitDoubleValue(stringIDToTypeID("size")) + " 像素"; + } + if (frameFX.hasKey(stringIDToTypeID("color"))) { + var colorDesc = frameFX.getObjectValue(stringIDToTypeID("color")); + styles["描边_颜色"] = colorDescToHex(colorDesc); + } + if (frameFX.hasKey(stringIDToTypeID("position"))) { + var pos = frameFX.getEnumerationValue(stringIDToTypeID("position")); + styles["描边_位置"] = getStrokePositionName(pos); + } + if (frameFX.hasKey(stringIDToTypeID("opacity"))) { + styles["描边_不透明度"] = Math.round(frameFX.getUnitDoubleValue(stringIDToTypeID("opacity"))) + "%"; + } + if (frameFX.hasKey(stringIDToTypeID("blendMode"))) { + var blendMode = frameFX.getEnumerationValue(stringIDToTypeID("blendMode")); + styles["描边_混合模式"] = getBlendModeName(blendMode); + } + } + } + + // ============ 内发光 =============== + if (effects.hasKey(stringIDToTypeID("innerGlow"))) { + var innerGlow = effects.getObjectValue(stringIDToTypeID("innerGlow")); + styles["内发光_启用"] = innerGlow.getBoolean(stringIDToTypeID("enabled")); + if (styles["内发光_启用"]) { + if (innerGlow.hasKey(stringIDToTypeID("opacity"))) { + styles["内发光_不透明度"] = Math.round(innerGlow.getUnitDoubleValue(stringIDToTypeID("opacity")) ) + "%"; + } + if (innerGlow.hasKey(stringIDToTypeID("color"))) { + styles["内发光_颜色"] = colorDescToHex(innerGlow.getObjectValue(stringIDToTypeID("color"))); + } + if (innerGlow.hasKey(stringIDToTypeID("glowTechnique"))) { + var tech = innerGlow.getEnumerationValue(stringIDToTypeID("glowTechnique")); + styles["内发光_技巧"] = getInnerGlowTechniqueName(tech); + } + if (innerGlow.hasKey(stringIDToTypeID("source"))) { + var source = innerGlow.getEnumerationValue(stringIDToTypeID("source")); + styles["内发光_来源"] = getInnerGlowSourceName(source); + } + if (innerGlow.hasKey(stringIDToTypeID("chokeMatte"))) { + styles["内发光_阻塞"] = innerGlow.getUnitDoubleValue(stringIDToTypeID("chokeMatte")) + " 像素"; + } + if (innerGlow.hasKey(stringIDToTypeID("blur"))) { + styles["内发光_模糊"] = innerGlow.getUnitDoubleValue(stringIDToTypeID("blur")) + " 像素"; + } + } + } + + // ============ 斜面和浮雕 =============== + if (effects.hasKey(stringIDToTypeID("bevelEmboss"))) { + var bevel = effects.getObjectValue(stringIDToTypeID("bevelEmboss")); + styles["斜面和浮雕_启用"] = bevel.getBoolean(stringIDToTypeID("enabled")); + if (styles["斜面和浮雕_启用"]) { + if (bevel.hasKey(stringIDToTypeID("style"))) { + styles["斜面和浮雕_样式"] = getBevelStyleName(bevel.getEnumerationValue(stringIDToTypeID("style"))); + } + if (bevel.hasKey(stringIDToTypeID("technique"))) { + styles["斜面和浮雕_方法"] = getBevelTechniqueName(bevel.getEnumerationValue(stringIDToTypeID("technique"))); + } + if (bevel.hasKey(stringIDToTypeID("depth"))) { + styles["斜面和浮雕_深度"] = bevel.getInteger(stringIDToTypeID("depth")) + "%"; + } + if (bevel.hasKey(stringIDToTypeID("direction"))) { + styles["斜面和浮雕_方向"] = getBevelDirectionName(bevel.getEnumerationValue(stringIDToTypeID("direction"))); + } + if (bevel.hasKey(stringIDToTypeID("size"))) { + styles["斜面和浮雕_大小"] = bevel.getUnitDoubleValue(stringIDToTypeID("size")) + " 像素"; + } + if (bevel.hasKey(stringIDToTypeID("soften"))) { + styles["斜面和浮雕_软化"] = bevel.getUnitDoubleValue(stringIDToTypeID("soften")) + " 像素"; + } + if (bevel.hasKey(stringIDToTypeID("useGlobalAngle"))) { + styles["斜面和浮雕_全局光"] = bevel.getBoolean(stringIDToTypeID("useGlobalAngle")); + } + if (bevel.hasKey(stringIDToTypeID("angle"))) { + styles["斜面和浮雕_角度"] = bevel.getDouble(stringIDToTypeID("angle")).toFixed(1) + "°"; + } + if (bevel.hasKey(stringIDToTypeID("highlightMode"))) { + var highlight = bevel.getObjectValue(stringIDToTypeID("highlightMode")); + if (highlight.hasKey(stringIDToTypeID("mode"))) { + styles["斜面和浮雕_高光模式"] = getBlendModeName(highlight.getEnumerationValue(stringIDToTypeID("mode"))); + } + if (highlight.hasKey(stringIDToTypeID("color"))) { + styles["斜面和浮雕_高光颜色"] = colorDescToHex(highlight.getObjectValue(stringIDToTypeID("color"))); + } + } + if (bevel.hasKey(stringIDToTypeID("shadowMode"))) { + var shadow = bevel.getObjectValue(stringIDToTypeID("shadowMode")); + if (shadow.hasKey(stringIDToTypeID("mode"))) { + styles["斜面和浮雕_阴影模式"] = getBlendModeName(shadow.getEnumerationValue(stringIDToTypeID("mode"))); + } + if (shadow.hasKey(stringIDToTypeID("color"))) { + styles["斜面和浮雕_阴影颜色"] = colorDescToHex(shadow.getObjectValue(stringIDToTypeID("color"))); + } + } + } + } + + // ============ 光泽 =============== + if (effects.hasKey(stringIDToTypeID("glossContour"))) { + styles["光泽_启用"] = true; // 简化处理 + + var gloss = effects.getObjectValue(stringIDToTypeID("glossContour")); + if (gloss.hasKey(stringIDToTypeID("range"))) { + styles["光泽_范围"] = (gloss.getDouble(stringIDToTypeID("range")) * 100).toFixed(1) + "%"; + } + if (gloss.hasKey(stringIDToTypeID("highlightSize"))) { + styles["光泽_高光大小"] = gloss.getUnitDoubleValue(stringIDToTypeID("highlightSize")) + " 像素"; + } + } + + // ============ 外发光 =============== + if (effects.hasKey(stringIDToTypeID("outerGlow"))) { + var outerGlow = effects.getObjectValue(stringIDToTypeID("outerGlow")); + styles["外发光_启用"] = outerGlow.getBoolean(stringIDToTypeID("enabled")); + if (styles["外发光_启用"]) { + if (outerGlow.hasKey(stringIDToTypeID("opacity"))) { + styles["外发光_不透明度"] = Math.round(outerGlow.getUnitDoubleValue(stringIDToTypeID("opacity")) ) + "%"; + } + if (outerGlow.hasKey(stringIDToTypeID("color"))) { + styles["外发光_颜色"] = colorDescToHex(outerGlow.getObjectValue(stringIDToTypeID("color"))); + } + if (outerGlow.hasKey(stringIDToTypeID("technique"))) { + styles["外发光_图素方法"] = getOuterGlowTechniqueName(outerGlow.getEnumerationValue(stringIDToTypeID("technique"))); + } + if (outerGlow.hasKey(stringIDToTypeID("size"))) { + styles["外发光_大小"] = outerGlow.getUnitDoubleValue(stringIDToTypeID("size")) + " 像素"; + } + if (outerGlow.hasKey(stringIDToTypeID("spread"))) { + styles["外发光_扩展"] = outerGlow.getUnitDoubleValue(stringIDToTypeID("spread")) + " 像素"; + } + if (outerGlow.hasKey(stringIDToTypeID("contour"))) { + var contour = outerGlow.getObjectValue(stringIDToTypeID("contour")); + styles["外发光_等高线"] = getContourName(contour.getEnumerationValue(stringIDToTypeID("name"))); + } + } + } + + return hasOwnProperties(styles) ? styles : null; + + } catch (e) { + $.writeln("读取图层样式异常: " + e.message); + return null; + } +} + + + + +// 辅助函数:描边位置 +function getStrokePositionName(value) { + switch (value) { + case stringIDToTypeID("inside"): return "内侧"; + case stringIDToTypeID("center"): return "居中"; + case stringIDToTypeID("outside"): return "外侧"; + default: return "未知"; + } +} + +// 斜面和浮雕 样式名称 +function getBevelStyleName(value) { + switch (value) { + case stringIDToTypeID("outerBevel"): return "外斜面"; + case stringIDToTypeID("innerBevel"): return "内斜面"; + case stringIDToTypeID("emboss"): return "浮雕"; + case stringIDToTypeID("pillowEmboss"): return "枕头状浮雕"; + default: return "未知"; + } +} +// 斜面和浮雕 方法 +function getBevelTechniqueName(value) { + switch (value) { + case stringIDToTypeID("smooth"): return "平滑"; + case stringIDToTypeID("chiselHard"): return "雕刻清晰"; + case stringIDToTypeID("chiselSoft"): return "雕刻柔和"; + default: return "未知"; + } +} +// 斜面和浮雕 方向 +function getBevelDirectionName(value) { + switch (value) { + case stringIDToTypeID("up"): return "上"; + case stringIDToTypeID("down"): return "下"; + default: return "未知"; + } +} +// 图层混合模式 +function getBlendModeName(value) { + switch (value) { + case stringIDToTypeID("normal"): return "正常"; + case stringIDToTypeID("multiply"): return "正片叠底"; + case stringIDToTypeID("screen"): return "滤色"; + case stringIDToTypeID("overlay"): return "叠加"; + case stringIDToTypeID("softLight"): return "柔光"; + case stringIDToTypeID("hardLight"): return "强光"; + case stringIDToTypeID("difference"): return "差值"; + case stringIDToTypeID("colorBurn"): return "颜色加深"; + case stringIDToTypeID("colorDodge"): return "颜色减淡"; + case stringIDToTypeID("lighten"): return "变亮"; + case stringIDToTypeID("darken"): return "变暗"; + default: return "其他"; + } +} +// 外发光 图素方法 +function getOuterGlowTechniqueName(value) { + switch (value) { + case stringIDToTypeID("precise"): return "精确"; + case stringIDToTypeID("softer"): return "柔和"; + default: return "未知"; + } +} +// 等高线名称(外发光等) +function getContourName(value) { + switch (value) { + case stringIDToTypeID("linear"): return "线性"; + case stringIDToTypeID("ring"): return "环形"; + case stringIDToTypeID("cone"): return "锥形"; + case stringIDToTypeID("cove"): return "凹形"; + case stringIDToTypeID("stairs"): return "阶梯"; + case stringIDToTypeID("custom"): return "自定义"; + case stringIDToTypeID("zigZag"): return "锯齿1"; + case stringIDToTypeID("roundedSteps"): return "圆角阶梯"; + default: return "未知"; + } +} +function colorDescToHex(colorDesc) { + try { + var red = Math.round(colorDesc.getDouble(stringIDToTypeID('red'))); + var green = Math.round(colorDesc.getDouble(stringIDToTypeID('green'))); + var blue = Math.round(colorDesc.getDouble(stringIDToTypeID('blue'))); + return rgbToHex(red, green, blue); + } catch (e) { + return null; + } +} +function getInnerGlowTechniqueName(value) { + switch(value) { + case stringIDToTypeID("precise"): return "精确"; + case stringIDToTypeID("softer"): return "柔和"; + default: return "未知"; + } +} +function getInnerGlowSourceName(value) { + switch(value) { + case stringIDToTypeID("center"): return "中心"; + case stringIDToTypeID("edge"): return "边缘"; + default: return "未知"; + } +} + +function rgbToHex(r, g, b) { + return "#" + [r, g, b].map(function(x) { + var hex = x.toString(16); + return hex.length === 1 ? "0" + hex : hex; + }).join(''); +} + +// 滤镜检测示例(只检测图层名包含的关键词) +// 修改后的detectFilters函数,优先用XMP元数据读取镜头光晕参数 +function detectFilters(layerName) { + var filters = {}; + if (!layerName || typeof layerName !== "string") { + return null; + } + var name = layerName.toLowerCase(); + + // 先尝试读取XMP中镜头光晕参数 + + var lensFlareMeta = readHistoryFromXMP(); + if (lensFlareMeta) { + return lensFlareMeta; // 直接返回读取的元数据 + } + // else { + // // 没读到元数据,返回默认提示 + // filters["滤镜类型"] = "镜头光晕"; + // filters["亮度"] = "⚠️ 无法读取,建议人工确认"; + // filters["镜头类型"] = "⚠️ 无法读取,建议人工确认"; + // return filters; + // } + + +} + + +// 结合你的读取函数用法举例 +function readHistoryFromXMP() { + try { + if (typeof ExternalObject.AdobeXMPScript === "undefined") { + ExternalObject.AdobeXMPScript = new ExternalObject("lib:AdobeXMPScript"); + } + + var xmp = new XMPMeta(app.activeDocument.xmpMetadata.rawData); + var nsMM = "http://ns.adobe.com/xap/1.0/mm/"; + var nsEvt = "http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"; + + XMPMeta.registerNamespace(nsMM, "xmpMM"); + XMPMeta.registerNamespace(nsEvt, "stEvt"); + + var count = xmp.countArrayItems(nsMM, "History"); + if (count === 0) { + $.writeln("没有找到历史记录"); + return null; + } + + + for (var i = 1; i <= count; i++) { + var itemXMP = xmp.getArrayItem(nsMM, "History", i); + var historyLine = itemXMP.toString(); + + $.writeln("历史记录第" + i + "条: " + historyLine); + + if (historyLine.indexOf("镜头光晕") !== -1 || historyLine.toLowerCase().indexOf("lens flare") !== -1) { + var brightnessMatch = historyLine.match(/亮度[::]?\s*(\d+%?)/); + var typeMatch = historyLine.match(/类型[::]?\s*([^\s]+)/); + + return { + "历史":historyLine, + "滤镜类型": "镜头光晕", + "亮度": brightnessMatch ? brightnessMatch[1] : "未知", + "镜头类型": typeMatch ? typeMatch[1] : "未知" + }; + } + } + + + + + } catch (e) { + return { + "读取XMP历史记录失败:": e.message + }; + + } +} + + + +var fontMap = { + "SimSun": "宋体", + "SimHei": "黑体", + "FangSong_GB2312": "仿宋", + "KaiTi_GB2312": "楷体", + "MicrosoftYaHei": "微软雅黑", + "MicrosoftJhengHei": "微软正黑体", + "NSimSun": "新宋体", + "PMingLiU": "新细明体", + "MingLiU": "细明体", + "DFKai-SB": "标楷体", + "STKaiti": "华文楷体", + "STSong": "华文宋体", + "STHeiti": "华文黑体", + "STFangsong": "华文仿宋", + "STXihei": "华文细黑", + "STZhongsong": "华文中宋", + "STXinwei": "华文新魏", + "STLiti": "华文隶书", + "STHupo": "华文琥珀", + "STCaiyun": "华文彩云", + "STXingkai": "华文行楷", + "STHupo": "华文琥珀", + "STFangsong": "华文仿宋", + "Arial": "Arial(英文字体)", + "TimesNewRomanPSMT": "Times New Roman", + "CourierNewPSMT": "Courier New", + "Georgia": "Georgia", + "Tahoma": "Tahoma", + "Verdana": "Verdana" +}; + +// 判断对象是否含有自有属性(兼容无Object.keys的环境) +function hasOwnProperties(obj) { + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + return true; + } + } + return false; +} + + +