From d8ad1a3dbf115b86cf851c420346d12d86846289 Mon Sep 17 00:00:00 2001 From: zheng <765324639@qq.com> Date: Fri, 13 Feb 2026 09:20:40 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6SQL=E6=9E=84=E5=BB=BA=EF=BC=8C=E9=98=B2?= =?UTF-8?q?=E6=AD=A2SQL=E6=B3=A8=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 添加SqlWithParams内部类,分离SQL和参数 2. 修改buildSql方法返回SqlWithParams对象 3. 支持IN操作符的参数化查询 4. 更新DataQuery.java中的调用方式 5. 更新QueryConditionTest.java中的测试用例 --- .gitignore | 3 ++ .../table/relation/service/DataQuery.java | 4 +- .../relation/service/dto/QueryCondition.java | 54 ++++++++++++------- .../relation/service/QueryConditionTest.java | 26 ++++++++- 4 files changed, 66 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index 549e00a..47ab411 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,6 @@ build/ ### VS Code ### .vscode/ + +logs +table_database.db \ No newline at end of file diff --git a/src/main/java/com/github/zavier/table/relation/service/DataQuery.java b/src/main/java/com/github/zavier/table/relation/service/DataQuery.java index 61362b5..6edeed0 100644 --- a/src/main/java/com/github/zavier/table/relation/service/DataQuery.java +++ b/src/main/java/com/github/zavier/table/relation/service/DataQuery.java @@ -131,7 +131,9 @@ private List> executeQuery(QueryCondition queryCondition) { } final DataSource dataSource = sourceOptional.get(); - return sqlExecutor.sqlQueryWithLimit(dataSource, queryCondition.buildSql()); + final QueryCondition.SqlWithParams sqlWithParams = queryCondition.buildSql(); + return sqlExecutor.sqlQueryWithLimit(dataSource, sqlWithParams.getSql(), + sqlWithParams.getParams().toArray()); } } diff --git a/src/main/java/com/github/zavier/table/relation/service/dto/QueryCondition.java b/src/main/java/com/github/zavier/table/relation/service/dto/QueryCondition.java index a4a0a15..4b16a83 100644 --- a/src/main/java/com/github/zavier/table/relation/service/dto/QueryCondition.java +++ b/src/main/java/com/github/zavier/table/relation/service/dto/QueryCondition.java @@ -14,12 +14,31 @@ public class QueryCondition { private List customizeConditionSqlList; private String logicNoDeleteCondition; - public String buildSql() { + public static class SqlWithParams { + private String sql; + private List params; + + public SqlWithParams(String sql, List params) { + this.sql = sql; + this.params = params; + } + + public String getSql() { + return sql; + } + + public List getParams() { + return params; + } + } + + public SqlWithParams buildSql() { final String schema = this.getSchema(); final String tableName = this.getTable(); final List conditionList = this.getConditions(); StringBuilder sql = new StringBuilder(); + List params = new ArrayList<>(); sql.append("select * from ").append(schema).append(".").append(tableName); if (conditionList != null && !conditionList.isEmpty()) { sql.append(" where "); @@ -28,8 +47,21 @@ public String buildSql() { final String column = condition.getField(); final String operator = condition.getOperator(); final Object value = condition.getValue(); - // TODO SQL注入 - sql.append(column).append(" ").append(operator).append(" ").append(convertSqlValue(value)); + + if ("IN".equalsIgnoreCase(operator) && value instanceof Collection) { + Collection collection = (Collection) value; + sql.append(column).append(" in ("); + for (Object obj : collection) { + sql.append("?,"); + params.add(obj); + } + sql.setLength(sql.length() - 1); + sql.append(")"); + } else { + sql.append(column).append(" ").append(operator).append(" ?"); + params.add(value); + } + if (i < conditionList.size() - 1) { sql.append(" and "); } @@ -43,21 +75,7 @@ public String buildSql() { if (StringUtils.isNotBlank(logicNoDeleteCondition)) { sql.append(" and ").append(logicNoDeleteCondition); } - return sql.toString(); - } - - private String convertSqlValue(Object value) { - if (value instanceof Collection) { - Collection collection = (Collection) value; - List values = new ArrayList<>(); - for (Object obj : collection) { - values.add(convertSqlValue(obj)); - } - return "(" + String.join(",", values) + ")"; - } else if (value instanceof Number) { - return value.toString(); - } - return "'" + value.toString() + "'"; + return new SqlWithParams(sql.toString(), params); } diff --git a/src/test/java/com/github/zavier/table/relation/service/QueryConditionTest.java b/src/test/java/com/github/zavier/table/relation/service/QueryConditionTest.java index 01f79be..adfc61e 100644 --- a/src/test/java/com/github/zavier/table/relation/service/QueryConditionTest.java +++ b/src/test/java/com/github/zavier/table/relation/service/QueryConditionTest.java @@ -4,6 +4,7 @@ import com.github.zavier.table.relation.service.dto.QueryCondition; import org.junit.jupiter.api.Test; +import java.util.Arrays; import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -22,7 +23,28 @@ void buildSql() { condition.setValue("110022"); queryCondition.setConditions(List.of(condition)); - final String sql = queryCondition.buildSql(); - assertEquals("select * from employees.dept_manager where emp_no = '110022'", sql); + final QueryCondition.SqlWithParams sqlWithParams = queryCondition.buildSql(); + assertEquals("select * from employees.dept_manager where emp_no = ?", sqlWithParams.getSql()); + assertEquals(1, sqlWithParams.getParams().size()); + assertEquals("110022", sqlWithParams.getParams().get(0)); + } + + @Test + void buildSqlWithInCondition() { + QueryCondition queryCondition = new QueryCondition(); + queryCondition.setSchema("employees"); + queryCondition.setTable("dept_manager"); + + final Condition condition = new Condition(); + condition.setField("emp_no"); + condition.setOperator("IN"); + condition.setValue(Arrays.asList(110022, 110039)); + queryCondition.setConditions(List.of(condition)); + + final QueryCondition.SqlWithParams sqlWithParams = queryCondition.buildSql(); + assertEquals("select * from employees.dept_manager where emp_no in (?,?)", sqlWithParams.getSql()); + assertEquals(2, sqlWithParams.getParams().size()); + assertEquals(110022, sqlWithParams.getParams().get(0)); + assertEquals(110039, sqlWithParams.getParams().get(1)); } } \ No newline at end of file From f98c27a5b6695ad699cc014f64d33b45213336b3 Mon Sep 17 00:00:00 2001 From: zheng <765324639@qq.com> Date: Fri, 13 Feb 2026 09:21:04 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20CLAUDE.md=20=E5=92=8C?= =?UTF-8?q?=20openspec=20=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/commands/opsx/apply.md | 152 +++++ .claude/commands/opsx/archive.md | 157 ++++++ .claude/commands/opsx/bulk-archive.md | 242 ++++++++ .claude/commands/opsx/continue.md | 114 ++++ .claude/commands/opsx/explore.md | 174 ++++++ .claude/commands/opsx/ff.md | 94 ++++ .claude/commands/opsx/new.md | 69 +++ .claude/commands/opsx/onboard.md | 510 +++++++++++++++++ .claude/commands/opsx/sync.md | 134 +++++ .claude/commands/opsx/verify.md | 164 ++++++ .claude/settings.local.json | 11 + .claude/skills/openspec-apply-change/SKILL.md | 156 ++++++ .../skills/openspec-archive-change/SKILL.md | 114 ++++ .../openspec-bulk-archive-change/SKILL.md | 246 ++++++++ .../skills/openspec-continue-change/SKILL.md | 118 ++++ .claude/skills/openspec-explore/SKILL.md | 290 ++++++++++ .claude/skills/openspec-ff-change/SKILL.md | 101 ++++ .claude/skills/openspec-new-change/SKILL.md | 74 +++ .claude/skills/openspec-onboard/SKILL.md | 529 ++++++++++++++++++ .claude/skills/openspec-sync-specs/SKILL.md | 138 +++++ .../skills/openspec-verify-change/SKILL.md | 168 ++++++ CLAUDE.md | 155 +++++ .../design.md | 151 +++++ .../proposal.md | 34 ++ .../specs/data-query/spec.md | 99 ++++ .../tasks.md | 25 + openspec/config.yaml | 7 + 27 files changed, 4226 insertions(+) create mode 100644 .claude/commands/opsx/apply.md create mode 100644 .claude/commands/opsx/archive.md create mode 100644 .claude/commands/opsx/bulk-archive.md create mode 100644 .claude/commands/opsx/continue.md create mode 100644 .claude/commands/opsx/explore.md create mode 100644 .claude/commands/opsx/ff.md create mode 100644 .claude/commands/opsx/new.md create mode 100644 .claude/commands/opsx/onboard.md create mode 100644 .claude/commands/opsx/sync.md create mode 100644 .claude/commands/opsx/verify.md create mode 100644 .claude/settings.local.json create mode 100644 .claude/skills/openspec-apply-change/SKILL.md create mode 100644 .claude/skills/openspec-archive-change/SKILL.md create mode 100644 .claude/skills/openspec-bulk-archive-change/SKILL.md create mode 100644 .claude/skills/openspec-continue-change/SKILL.md create mode 100644 .claude/skills/openspec-explore/SKILL.md create mode 100644 .claude/skills/openspec-ff-change/SKILL.md create mode 100644 .claude/skills/openspec-new-change/SKILL.md create mode 100644 .claude/skills/openspec-onboard/SKILL.md create mode 100644 .claude/skills/openspec-sync-specs/SKILL.md create mode 100644 .claude/skills/openspec-verify-change/SKILL.md create mode 100644 CLAUDE.md create mode 100644 openspec/changes/archive/2026-02-12-fix-sql-injection-vulnerability/design.md create mode 100644 openspec/changes/archive/2026-02-12-fix-sql-injection-vulnerability/proposal.md create mode 100644 openspec/changes/archive/2026-02-12-fix-sql-injection-vulnerability/specs/data-query/spec.md create mode 100644 openspec/changes/archive/2026-02-12-fix-sql-injection-vulnerability/tasks.md create mode 100644 openspec/config.yaml diff --git a/.claude/commands/opsx/apply.md b/.claude/commands/opsx/apply.md new file mode 100644 index 0000000..bf23721 --- /dev/null +++ b/.claude/commands/opsx/apply.md @@ -0,0 +1,152 @@ +--- +name: "OPSX: Apply" +description: Implement tasks from an OpenSpec change (Experimental) +category: Workflow +tags: [workflow, artifacts, experimental] +--- + +Implement tasks from an OpenSpec change. + +**Input**: Optionally specify a change name (e.g., `/opsx:apply add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. + +**Steps** + +1. **Select the change** + + If a name is provided, use it. Otherwise: + - Infer from conversation context if the user mentioned a change + - Auto-select if only one active change exists + - If ambiguous, run `openspec list --json` to get available changes and use the **AskUserQuestion tool** to let the user select + + Always announce: "Using change: " and how to override (e.g., `/opsx:apply `). + +2. **Check status to understand the schema** + ```bash + openspec status --change "" --json + ``` + Parse the JSON to understand: + - `schemaName`: The workflow being used (e.g., "spec-driven") + - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) + +3. **Get apply instructions** + + ```bash + openspec instructions apply --change "" --json + ``` + + This returns: + - Context file paths (varies by schema) + - Progress (total, complete, remaining) + - Task list with status + - Dynamic instruction based on current state + + **Handle states:** + - If `state: "blocked"` (missing artifacts): show message, suggest using `/opsx:continue` + - If `state: "all_done"`: congratulate, suggest archive + - Otherwise: proceed to implementation + +4. **Read context files** + + Read the files listed in `contextFiles` from the apply instructions output. + The files depend on the schema being used: + - **spec-driven**: proposal, specs, design, tasks + - Other schemas: follow the contextFiles from CLI output + +5. **Show current progress** + + Display: + - Schema being used + - Progress: "N/M tasks complete" + - Remaining tasks overview + - Dynamic instruction from CLI + +6. **Implement tasks (loop until done or blocked)** + + For each pending task: + - Show which task is being worked on + - Make the code changes required + - Keep changes minimal and focused + - Mark task complete in the tasks file: `- [ ]` → `- [x]` + - Continue to next task + + **Pause if:** + - Task is unclear → ask for clarification + - Implementation reveals a design issue → suggest updating artifacts + - Error or blocker encountered → report and wait for guidance + - User interrupts + +7. **On completion or pause, show status** + + Display: + - Tasks completed this session + - Overall progress: "N/M tasks complete" + - If all done: suggest archive + - If paused: explain why and wait for guidance + +**Output During Implementation** + +``` +## Implementing: (schema: ) + +Working on task 3/7: +[...implementation happening...] +✓ Task complete + +Working on task 4/7: +[...implementation happening...] +✓ Task complete +``` + +**Output On Completion** + +``` +## Implementation Complete + +**Change:** +**Schema:** +**Progress:** 7/7 tasks complete ✓ + +### Completed This Session +- [x] Task 1 +- [x] Task 2 +... + +All tasks complete! You can archive this change with `/opsx:archive`. +``` + +**Output On Pause (Issue Encountered)** + +``` +## Implementation Paused + +**Change:** +**Schema:** +**Progress:** 4/7 tasks complete + +### Issue Encountered + + +**Options:** +1.