【新增】ps本地判分

This commit is contained in:
huababa1
2025-07-04 18:43:54 +08:00
parent 5707432118
commit ef92d7ae97
11 changed files with 1326 additions and 1 deletions

693
checkPSD.jsx Normal file
View File

@@ -0,0 +1,693 @@
// 简单 JSON.stringify 兼容
if (typeof JSON === "undefined") {
JSON = {};
}
var inputPath1 = '${inputPath1}';
// 载入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);
//////////////////////////////////////////////
/////////////////////////////////////////////
// 辅助函数 - 颜色模式
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;
}

View File

@@ -136,7 +136,11 @@
<artifactId>poi-ooxml-lite</artifactId>
<version>5.2.3</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20231013</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>

View File

@@ -2,6 +2,7 @@ package com.example.exam.exam.controller.auto;
import com.example.exam.exam.controller.auto.vo.StuInfoVo;
import com.example.exam.exam.service.autoforbrower.AutoForBrowerService;
import com.example.exam.exam.service.autoforps.AutoForPsService;
import com.example.exam.exam.service.brower.JudgementBrowerService;
import com.example.exam.exam.service.autoforc.AutoForCService;
import com.example.exam.exam.service.autoforchoice.AutoForChoiceService;
@@ -47,6 +48,8 @@ public class AutoController {
JudgementForExcelService judgementForExcelService;
@Resource
StuPaperScoreService stuPaperScoreService;
@Resource
AutoForPsService autoForPsService;
// C语言
@@ -113,6 +116,12 @@ public class AutoController {
// win10虚拟界面
//PS
@PostMapping("/judgementForPS")
public Result<Double> judgementForPS(@RequestBody StuInfoVo stuInfoVo) throws SQLException, IOException {
return Result.success(autoForPsService.autoForPs(stuInfoVo));
}
// 单项选择
/**
* 将选择题写入到JSON文件中

View File

@@ -0,0 +1,46 @@
package com.example.exam.exam.dal;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ExamPsKeyword {
/**
* 主键id
*/
private String id;
/**
* 试题id
*/
private String quId;
/**
* 父id
*/
private String parentId;
/**
* 键
*/
private String key;
/**
* 值
*/
private String value;
/**
* 权值
*/
private String rate;
/**
* 类型(学生考点1结果考点2)
*/
private String type;
private String sort;
@TableField(exist = false)
private List<ExamPsKeyword> children;
}

View File

@@ -2,6 +2,7 @@ package com.example.exam.exam.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.exam.exam.dal.ExamQuestionAnswer;
import com.example.exam.exam.dal.ExamPsKeyword;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@@ -23,4 +24,7 @@ public interface ExamQuestionAnswerMapper extends BaseMapper<ExamQuestionAnswer
String selectCountPointByQuId(String quId);
String selectExamQuestionAnswerScoreByAnswerId(String answerId);
List<ExamPsKeyword> selectPsAnswerById(String quId);
}

View File

@@ -0,0 +1,13 @@
package com.example.exam.exam.service.autoforps;
import com.example.exam.exam.controller.auto.vo.StuInfoVo;
import java.io.IOException;
public interface AutoForPsService {
/**
* 自动判题 Ps
* @param stuInfoVo 学生信息
*/
Double autoForPs(StuInfoVo stuInfoVo) throws IOException;
}

View File

@@ -0,0 +1,111 @@
package com.example.exam.exam.service.autoforps;
import com.example.exam.exam.controller.auto.vo.StuInfoVo;
import com.example.exam.exam.dal.*;
import com.example.exam.exam.mapper.EducationPaperQuMapper;
import com.example.exam.exam.mapper.EducationPaperSchemeMapper;
import com.example.exam.exam.service.mysql.IMysqlLocalService;
import com.example.exam.exam.service.ps.PsService;
import com.example.exam.exam.service.question.IExamQuestionService;
import com.example.exam.exam.service.stupaperscore.StuPaperScoreService;
import com.example.exam.exam.service.tenant.SystemTenantService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
@Service
public class AutoForPsServiceImpl implements AutoForPsService{
@Resource
PsService psService;
@Resource
EducationPaperSchemeMapper educationPaperSchemeMapper;
@Resource
IExamQuestionService examQuestionService;
@Resource
EducationPaperQuMapper educationPaperQuMapper;
@Resource
StuPaperScoreService stuPaperScoreService;
@Resource
SystemTenantService systemTenantService;
@Override
public Double autoForPs(StuInfoVo stuInfoVo) throws IOException {
SystemTenant systemTenant = systemTenantService.getId(stuInfoVo.getSchoolName());
Double score = 0.0;
// 0、获取到试卷信息试卷详情
List<EducationPaperScheme> educationPaperSchemeList = educationPaperSchemeMapper.selectEducationPaperTaskByPaperId(stuInfoVo.getPaperId());
List<EducationPaperQu> educationPaperQus = educationPaperQuMapper.selectPaperQuListByPaperId(stuInfoVo.getPaperId());
// 1、获取到学生文件路径
String filePath = stuInfoVo.getFilePath();
File folder = new File(filePath);
// 2、获取到学生文件
File[] files = folder.listFiles();
for (File file : files) {
File csFiles = new File(file.getPath());
String quId = csFiles.getName();
File[] csFileList = csFiles.listFiles();
String name = csFileList[0].getName();
if ("图像处理".equals(name)) {
File[] cs_file_list = csFiles.listFiles();
String lastFilePath = cs_file_list[0].getPath();
File[] lastFiles = cs_file_list[0].listFiles();
// 3、根据题号进行查询试题的相关信息
Optional<EducationPaperScheme> result = educationPaperSchemeList.stream().filter(quLists -> quLists.getSpName().equals(name)).findFirst();
Optional<EducationPaperQu> results = educationPaperQus.stream().filter(quLists -> quLists.getQuId().equals(quId)).findFirst();
EducationPaperScheme educationPaperScheme = result.get();
EducationPaperQu educationPaperQu = results.get();
String quScore = educationPaperScheme.getQuScores();
ExamQuestion examQuestion = examQuestionService.selectExamQuestionByQuId(quId);
for (File lastFile : lastFiles) {
if (lastFile.getName().contains("原始")) {
String judgementStr = "<p>-----------------------------------------------------------</p>";
judgementStr += "<p>试题序号:" + educationPaperQu.getSort() + "</p>";
judgementStr += "<p>试题编号:" + examQuestion.getQuNum() + "</p>";
judgementStr += "<p>试题分数:" + Double.parseDouble(quScore) + "</p>";
judgementStr += "<p>试题名称:" + name + "</p>";
SourceAndText wordpojo = psService.Judgement(Double.parseDouble(quScore), lastFilePath, lastFile.getPath(), examQuestion, judgementStr);
score += wordpojo.getScore();
judgementStr = wordpojo.getText();
judgementStr += "<p>试题得分:" + wordpojo.getScore() + "</p>";
// 4、需要更新学生试题得分首先需要查询试题的数据库是否保存信息
// 通过 quIdstuIdpaperId 查询
StuPaperScoreDO stuPaperScoreDO = stuPaperScoreService.getStuScoreByPaperIdAndQuid(stuInfoVo.getStuId(), stuInfoVo.getPaperId(), quId);
if (stuPaperScoreDO != null) {
// 说明已经是做过该题,需要更新数据
stuPaperScoreDO.setScore(new BigDecimal(wordpojo.getScore()));
stuPaperScoreDO.setContent(judgementStr);
stuPaperScoreDO.setSort(educationPaperQu.getSort());
stuPaperScoreDO.setSubjectName(name);
stuPaperScoreDO.setIsTrue(wordpojo.getScore() == 0 ? 1 : wordpojo.getScore() == Double.parseDouble(quScore) ? 0 : 2);
stuPaperScoreDO.setTrueScore(new BigDecimal(quScore));
stuPaperScoreDO.setTenantId(systemTenant.getId());
stuPaperScoreService.updateStuPaperScore(stuPaperScoreDO);
} else {
StuPaperScoreDO insertInfo = new StuPaperScoreDO();
insertInfo.setStuId(stuInfoVo.getStuId());
insertInfo.setPaperId(stuInfoVo.getPaperId());
insertInfo.setQuId(quId);
insertInfo.setScore(new BigDecimal(wordpojo.getScore()));
insertInfo.setContent(judgementStr);
insertInfo.setSort(educationPaperQu.getSort());
insertInfo.setSubjectName(name);
insertInfo.setTrueScore(new BigDecimal(quScore));
insertInfo.setTenantId(systemTenant.getId());
insertInfo.setIsTrue(wordpojo.getScore() == 0 ? 1 : wordpojo.getScore() == Double.parseDouble(quScore) ? 0 : 2);
stuPaperScoreService.insertStuPaperScore(insertInfo);
}
break;
}
}
}
}
return score;
}
}

View File

@@ -0,0 +1,19 @@
package com.example.exam.exam.service.ps;
import com.example.exam.exam.dal.ExamQuestion;
import com.example.exam.exam.dal.SourceAndText;
import java.io.File;
import java.io.IOException;
public interface PsService {
/**
* 读取考生文件,与题型中要求进行判断
* @param path 文件路径
* @param examQuestion 试题参数
* @param sorce 试题分数
* @return 得分
* @throws Exception 异常
*/
SourceAndText Judgement(double sorce, String lastFilePath, String path, ExamQuestion examQuestion, String judgementStr) throws IOException;
}

View File

@@ -0,0 +1,287 @@
package com.example.exam.exam.service.ps;
import com.example.exam.exam.dal.ExamQuestion;
import com.example.exam.exam.dal.SourceAndText;
import com.example.exam.exam.mapper.ExamQuestionAnswerMapper;
import com.example.exam.exam.dal.ExamPsKeyword;
import com.example.exam.exam.utils.HtmlAppender;
import com.example.exam.exam.utils.c.LogFileUtils;
import com.example.exam.exam.utils.ps.PsUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.stereotype.Service;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class PsServiceImpl implements PsService{
static String answerLogPath ; // 文件路径
@Resource
private ExamQuestionAnswerMapper examQuestionAnswerMapper;
private static final DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final DateTimeFormatter DATE_FORMATTER =
DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss");
@Override
public SourceAndText Judgement(double score, String lastFilePath, String path, ExamQuestion examQuestion, String judgementStr) throws IOException {
// 创建log文件txt用于记录
answerLogPath = lastFilePath + File.separator + "log.txt";
SourceAndText sourceAndText = new SourceAndText();
// 构建树形结构
List<ExamPsKeyword> examPsKeywordList = examQuestionAnswerMapper.selectPsAnswerById(examQuestion.getQuId());
List<ExamPsKeyword> treeAnswerList = buildTree(examPsKeywordList);
String homeDir = System.getProperty("user.dir");
String jsxTemplatePath = homeDir + "\\checkPSD.jsx"; // 模板路径
String photoshopExe = PsUtil.findPhotoshopExe();
if (photoshopExe == null) {
appendToFile(answerLogPath, "未检测到本机的PhotoShop!");
judgementStr = HtmlAppender.appendHtmlLine(judgementStr, "未检测到本机的PhotoShop!");
sourceAndText.setText(judgementStr);
sourceAndText.setScore(0.0);
return sourceAndText;
}
String sthJsonPath = path.replaceAll("(?i)\\.psd$", ".json");
Path jsonFilePath = Paths.get(sthJsonPath);
Path jsxPath=null;
// 执行 Photoshop 脚本
try {
String jsxTargetPath= PsUtil.runTwoPsdsInOneScript(path, jsxTemplatePath, photoshopExe);
jsxPath = Paths.get(jsxTargetPath);
appendToFile(answerLogPath, "Photoshop脚本执行完毕");
} catch (Exception e) {
appendToFile(answerLogPath, "执行 Photoshop 脚本失败: " + e.getMessage());
throw new RuntimeException("执行 Photoshop 脚本失败: " + e.getMessage(), e);
}
// 读取学生的JSON文件
try {
String sthJsonStr = Files.readString(Paths.get(sthJsonPath), StandardCharsets.UTF_8);
JSONObject studentJson = new JSONObject(sthJsonStr);
double totalScore = 0.0;
// 遍历树形结构,对比每个节点
for (ExamPsKeyword item : treeAnswerList) {
SourceAndText studentScorePojo = compareItemWithStudentJson(item, studentJson, "", judgementStr);
totalScore += studentScorePojo.getScore();
judgementStr = studentScorePojo.getText();
}
// 计算最终得分
double maxPossibleScore = calculateMaxPossibleScore(treeAnswerList);
double finalScore = ((totalScore / maxPossibleScore) * score);
double finalScoreRatio = Math.round(finalScore * 100.0) / 100.0; // 四舍五入到2位小数
appendToFile(answerLogPath, "最终得分: " + finalScoreRatio);
judgementStr = HtmlAppender.appendHtmlLine(judgementStr, "最终得分: " +finalScoreRatio);
sourceAndText.setScore(finalScoreRatio);
sourceAndText.setText(judgementStr);
return sourceAndText;
} catch (IOException e) {
appendToFile(answerLogPath, "读取 JSON 文件失败");
judgementStr = HtmlAppender.appendHtmlLine(judgementStr, "读取 JSON 文件失败!");
sourceAndText.setText(judgementStr);
sourceAndText.setScore(0.0);
return sourceAndText;
}finally {
// 检查文件是否存在
if (Files.exists(jsonFilePath)) {
try {
// 如果存在则删除
Files.delete(jsonFilePath);
} catch (IOException e) {
}
}
if (Files.exists(jsxPath)) {
try {
// 如果存在则删除
Files.delete(jsxPath);
} catch (IOException e) {
}
}
}
}
private SourceAndText compareItemWithStudentJson(ExamPsKeyword item, JSONObject studentJson, String parentPath, String judgementStr) {
SourceAndText result = new SourceAndText();
// 当前节点路径(用中文【】包裹是为了兼容你已有的格式)
String currentPath = parentPath.isEmpty()
? "" + item.getKey() + ""
: parentPath + "" + item.getKey() + "";
// 如果是叶子节点,直接比较值
if (item.getChildren() == null || item.getChildren().isEmpty()) {
Object valueObj = getValueFromJson(studentJson, currentPath); // 用路径查找学生值
String studentValue = valueObj != null ? valueObj.toString() : null;
String correctValue = item.getValue();
boolean isCorrect = correctValue != null && correctValue.equals(studentValue);
if (isCorrect) {
judgementStr = HtmlAppender.appendHtmlLine(judgementStr, currentPath + "" + correctValue + "】【✅】");
appendToFile(answerLogPath, currentPath + "" + correctValue + "】【✅】");
result.setScore(result.getScore() + Double.parseDouble(item.getRate()));
} else {
judgementStr = HtmlAppender.appendHtmlLine(judgementStr, currentPath + "" + correctValue + "】【❌】");
appendToFile(answerLogPath, currentPath + "" + correctValue + "】【❌】");
}
result.setText(judgementStr);
} else {
// 有子节点,递归比较
for (ExamPsKeyword child : item.getChildren()) {
SourceAndText childResult = compareItemWithStudentJson(child, studentJson, currentPath, judgementStr);
judgementStr = childResult.getText(); // ✅ 更新外层judgementStr
result.setScore(result.getScore() + childResult.getScore());
}
result.setText(judgementStr);
}
return result;
}
// 计算最大可能得分
private double calculateMaxPossibleScore(List<ExamPsKeyword> treeAnswerList) {
double total = 0;
for (ExamPsKeyword item : treeAnswerList) {
if (item.getChildren() == null || item.getChildren().isEmpty()) {
if (item.getRate() != null) {
total += Double.parseDouble(item.getRate());
}
} else {
total += calculateMaxPossibleScore(item.getChildren());
}
}
return total;
}
private Object getValueFromJson(JSONObject json, String path) {
String[] keys = path.split("【|】");
Object current = json;
for (String rawKey : keys) {
if (rawKey == null || rawKey.isEmpty()) continue;
if (current instanceof JSONObject) {
current = ((JSONObject) current).opt(rawKey);
} else if (current instanceof JSONArray) {
JSONArray array = (JSONArray) current;
Object matched = null;
// 如果数组项是 JSONObject并且包含 "图层名",我们按图层名匹配
for (int i = 0; i < array.length(); i++) {
JSONObject obj = array.optJSONObject(i);
if (obj != null && rawKey.equals(obj.optString("图层名"))) {
matched = obj;
break;
}
}
// 若未按图层名匹配成功就尝试找任何包含该key的对象
if (matched == null) {
for (int i = 0; i < array.length(); i++) {
JSONObject obj = array.optJSONObject(i);
if (obj != null && obj.has(rawKey)) {
matched = obj.get(rawKey);
break;
}
}
}
current = matched;
} else {
return null;
}
}
return current;
}
/**
* 构建答案树形结构
*/
private List<ExamPsKeyword> buildTree(List<ExamPsKeyword> flatList) {
if (CollectionUtils.isEmpty(flatList)) {
return Collections.emptyList();
}
// 第一步:构建所有节点
Map<String, ExamPsKeyword> nodeMap = new LinkedHashMap<>();
Map<String, List<ExamPsKeyword>> parentChildMap = new HashMap<>();
flatList.forEach(item -> {
ExamPsKeyword node = new ExamPsKeyword();
node.setId(item.getId());
node.setKey(item.getKey());
node.setValue(item.getValue()); // 先保留原始值
node.setRate(item.getRate());
node.setType(item.getType());
node.setSort(item.getSort());
nodeMap.put(item.getId(), node);
String parentId = item.getParentId() != null ? item.getParentId() : "0";
parentChildMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(node);
});
// 第二步:建立父子关系并识别真正父节点
Set<ExamPsKeyword> realParents = new HashSet<>();
parentChildMap.forEach((parentId, children) -> {
if ("0".equals(parentId)) {
children.forEach(node -> {
// 标记没有子节点的独立节点
if (!parentChildMap.containsKey(node.getId())) {
node.setChildren(null);
}
});
} else {
ExamPsKeyword parent = nodeMap.get(parentId);
if (parent != null) {
parent.setChildren(children);
realParents.add(parent); // 记录真正有子节点的父节点
}
}
});
// 第三步对有子节点的父节点设置value=null
realParents.forEach(parent -> parent.setValue(null));
return parentChildMap.getOrDefault("0", Collections.emptyList());
}
public static void appendToFile(String filePath, String content) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath, true))) {
String timestamp = LocalDateTime.now().format(formatter);
String logLine = String.format("[%s] %s", timestamp, content);
writer.write(logLine);
writer.newLine(); // 可选:添加换行符
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,123 @@
package com.example.exam.exam.utils.ps;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
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; // 没找到
}
public static String runTwoPsdsInOneScript(String psdPath, String jsxTemplatePath, String photoshopExe)
throws IOException, InterruptedException {
File psdFile1 = new File(psdPath);
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 = psdPath.replace("\\", "\\\\").replace("'", "\\'");
String jsxContent = jsxTemplate
.replace("${inputPath1}", safePath1);
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 = psdPath.replaceAll("(?i)\\.psd$", ".json");
int maxWaitSeconds = 60; // 最多等待 60秒根据实际调整
int waited = 0;
while (!(Files.exists(Paths.get(jsonPath1)))) {
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 进程已关闭");
return jsxTargetPath;
}
}

View File

@@ -15,7 +15,17 @@
<result property="attribute" column="attribute" />
<result property="sort" column="sort" />
</resultMap>
<resultMap type="ExamPsKeyword" id="ExamPsKeywordResult">
<id property="id" column="id" jdbcType="VARCHAR"/>
<result property="quId" column="qu_id" jdbcType="VARCHAR"/>
<result property="parentId" column="parent_id" jdbcType="VARCHAR"/>
<result property="key" column="key_name" jdbcType="VARCHAR"/>
<result property="value" column="key_value" jdbcType="VARCHAR"/>
<result property="rate" column="rate" jdbcType="VARCHAR"/>
<result property="type" column="type" jdbcType="VARCHAR"/>
<result property="sort" column="sort" jdbcType="VARCHAR"/>
</resultMap>
<sql id="selectExamQuestionAnswerVo">
select answer_id, qu_id, is_right, image, content,contentIn, score_rate,attribute,sort from exam_question_answer
</sql>
@@ -44,4 +54,10 @@
<select id="selectExamQuestionAnswerScoreByAnswerId" resultType="java.lang.String">
select score_rate from exam_question_answer where answer_id =#{answerId}
</select>
<select id="selectPsAnswerById" resultMap="ExamPsKeywordResult">
select id,qu_id,parent_id,key_name,key_value,rate,type from exam_ps_keyword where qu_id=#{quId}
and type='1'
order by sort asc
</select>
</mapper>