{"record":{"id":"1beaf9574ab9a706","repo":"Tencent/APIJSON","slug":"order-value-item-value","errorCode":null,"errorMessage":"预编译模式下 @order:value 中 {item} 不合法! value 里面用 , 分割的每一项必须是 随机函数 rand() 或 column+ / column- 且其中 column 必须是 1 个单词！并且不要有多余的空格！","messagePattern":"预编译模式下 @order:value 中 (.+?) 不合法! value 里面用 , 分割的每一项必须是 随机函数 rand\\(\\) 或 column\\+ / column- 且其中 column 必须是 1 个单词！并且不要有多余的空格！","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":400,"severity":"error","filePath":"APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java","lineNumber":2181,"sourceCode":"\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tint index = item.endsWith(\"+\") ? item.length() - 1 : -1; //StringUtil.split返回数组中，子项不会有null\n\t\t\tString sort;\n\t\t\tif (index < 0) {\n\t\t\t\tindex = item.endsWith(\"-\") ? item.length() - 1 : -1;\n\t\t\t\tsort = index <= 0 ? \"\" : \" DESC \";\n\t\t\t}\n\t\t\telse {\n\t\t\t\tsort = \" ASC \";\n\t\t\t}\n\n\t\t\tString origin = index < 0 ? item : item.substring(0, index);\n\n\t\t\tif (isPrepared()) { //不能通过 ? 来代替，SELECT 'id','name' 返回的就是 id:\"id\", name:\"name\"，而不是数据库里的值！\n\t\t\t\t//这里既不对origin trim，也不对 ASC/DESC ignoreCase，希望前端严格传没有任何空格的字符串过来，减少传输数据量，节约服务器性能\n\t\t\t\tif (StringUtil.isName(origin) == false) {\n\t\t\t\t\tthrow new IllegalArgumentException(\"预编译模式下 @order:value 中 \" + item + \" 不合法! value 里面用 , 分割的\"\n\t\t\t\t\t\t\t+ \"每一项必须是 随机函数 rand() 或 column+ / column- 且其中 column 必须是 1 个单词！并且不要有多余的空格！\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tkeys[i] = gainKey(origin) + sort;\n\t\t}\n\n\t\treturn (hasPrefix ? \" ORDER BY \" : \"\") + StringUtil.concat(StringUtil.get(keys), joinOrder, \", \");\n\t}\n\n\t@Override\n\tpublic Map<String, String> getKeyMap() {\n\t\treturn keyMap;\n\t}\n\t@Override\n\tpublic AbstractSQLConfig<T, M, L> setKeyMap(Map<String, String> keyMap) {\n\t\tthis.keyMap = keyMap;\n\t\treturn this;","sourceCodeStart":2163,"sourceCodeEnd":2199,"githubUrl":"https://github.com/Tencent/APIJSON/blob/5284052872898eddc449a58f629e5c8d588b8e22/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java#L2163-L2199","documentation":"Prepared-mode validation of @order:value: after stripping the trailing '+'/'-' sort marker (rand() is handled earlier), the remaining column must be a single word (StringUtil.isName). ORDER BY items cannot be bound parameters, so anything else is rejected to prevent injection.","triggerScenarios":"\"@order\": \"user id+\" (space), \"@order\": \"name asc\" , \"@order\": \"LENGTH(name)-\" — the origin before the sort suffix fails isName.","commonSituations":"Writing 'asc'/'desc' (already encoded as +-) or SQL expressions into @order; joining a list with spaces; using function calls for custom ordering.","solutions":["Use the marker syntax with bare names: \"@order\": \"date-\" for date DESC","Use \"@order\": \"rand()\" for random ordering (special-cased)","For functional ordering, define it in RAW_MAP and reference via @raw"],"exampleFix":"// before\n{\"@order\": \"created_at desc\"}\n// after\n{\"@order\": \"created_at-\"}","handlingStrategy":"validation","validationCode":"for (const raw of String(obj['@order'] ?? '').split(',')) {\n  if (raw === 'rand()') continue;\n  const col = raw.endsWith('+') || raw.endsWith('-') ? raw.slice(0, -1) : raw;\n  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(col)) throw new Error(`@order item '${raw}' must be rand() or column+/-`);\n}","typeGuard":"const isOrderItemValid = s => s === 'rand()' || /^[A-Za-z_][A-Za-z0-9_]*[+-]?$/.test(s);","tryCatchPattern":"try { await api.get(req); } catch (e) { if (e.message.includes('@order')) replaceAscDescWithMarkers(req); else throw e; }","preventionTips":["Encode direction as +/- suffix, never asc/desc words","Reject free-text sort fields at the UI layer; use a column dropdown"],"tags":["apijson","order","prepared-statement","sql-injection"],"backgroundTag":null,"analyzedSha":"5284052872898eddc449a58f629e5c8d588b8e22","analyzedAt":"2026-08-14T15:15:29.577Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}