若依自定义sql查询console

2 阅读2分钟

通过在java中添加自定义的sql查询的jdbctemplate,可以实现前端传入任意sql然后在后端查询的效果

package com.ruoyi.web.controller.tool;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;

/**
 * SQL控制台
 */
@RestController
@RequestMapping("/tool/sqlConsole")
public class SqlConsoleController extends BaseController
{
    @Autowired
    private DataSource dataSource;

    /**
     * 获取SQL查询的列名
     */
    @PreAuthorize("@ss.hasRole('admin')")
    @PostMapping("/columns")
    public AjaxResult columns(@RequestBody Map<String, String> params)
    {
        String sql = params.get("sql");
        if (sql == null || sql.trim().isEmpty())
        {
            return AjaxResult.error("SQL语句不能为空");
        }
        sql = sql.trim();

        try
        {
            JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
            // 直接在原查询末尾追加 LIMIT 0,只获取列元数据不返回数据
            String columnSql = sql + " LIMIT 0";
            List<String> columns = jdbcTemplate.query(columnSql, rs -> {
                List<String> cols = new ArrayList<>();
                int count = rs.getMetaData().getColumnCount();
                for (int i = 1; i <= count; i++)
                {
                    cols.add(rs.getMetaData().getColumnLabel(i));
                }
                return cols;
            });
            return AjaxResult.success(columns);
        }
        catch (Exception e)
        {
            return AjaxResult.error("获取列名失败: " + e.getMessage());
        }
    }

    /**
     * 执行SQL语句
     */
    @PreAuthorize("@ss.hasRole('admin')")
    @Log(title = "SQL控制台", businessType = BusinessType.OTHER)
    @PostMapping("/execute")
    public AjaxResult execute(@RequestBody Map<String, String> params)
    {
        String sql = params.get("sql");
        if (sql == null || sql.trim().isEmpty())
        {
            return AjaxResult.error("SQL语句不能为空");
        }
        sql = sql.trim();

        try
        {
            JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
            String upperSql = sql.toUpperCase().replaceAll("^\s+", "");

            if (upperSql.startsWith("SELECT") || upperSql.startsWith("SHOW") || upperSql.startsWith("DESCRIBE") || upperSql.startsWith("DESC") || upperSql.startsWith("EXPLAIN"))
            {
                List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql);
                List<String> columns = new ArrayList<>();
                if (!rows.isEmpty())
                {
                    columns.addAll(rows.get(0).keySet());
                }
                Map<String, Object> data = new HashMap<>();
                data.put("type", "select");
                data.put("columns", columns);
                data.put("rows", rows);
                return AjaxResult.success(data);
            }
            else if (upperSql.startsWith("INSERT") || upperSql.startsWith("UPDATE") || upperSql.startsWith("DELETE"))
            {
                int affectedRows = jdbcTemplate.update(sql);
                Map<String, Object> data = new HashMap<>();
                data.put("type", "dml");
                data.put("affectedRows", affectedRows);
                return AjaxResult.success(data);
            }
            else
            {
                jdbcTemplate.execute(sql);
                Map<String, Object> data = new HashMap<>();
                data.put("type", "ddl");
                data.put("message", "执行成功");
                return AjaxResult.success(data);
            }
        }
        catch (Exception e)
        {
            return AjaxResult.error("执行失败: " + e.getMessage());
        }
    }
}

以下为前端代码,可以查询出对应的字段以及对应的sql结果

<template>
  <div class="app-container">
    <el-alert
      title="仅管理员可使用此功能,请谨慎操作,SQL语句将直接在数据库中执行。"
      type="warning"
      show-icon
      :closable="false"
      style="margin-bottom: 15px;"
    />

    <el-input
      v-model="sql"
      type="textarea"
      :rows="8"
      placeholder="请输入SQL语句,例如:select * from sys_user limit 10"
    />

    <div style="margin: 10px 0;">
      <el-button type="primary" :loading="loading" @click="handleExecute">执 行</el-button>
      <el-button :loading="loadingColumns" @click="handleShowColumns">查看字段</el-button>
      <el-button @click="handleClear">清 空</el-button>
    </div>

    <el-alert
      v-if="errorMsg"
      :title="errorMsg"
      type="error"
      show-icon
      style="margin-bottom: 15px;"
    />

    <el-alert
      v-if="successMsg"
      :title="successMsg"
      type="success"
      show-icon
      style="margin-bottom: 15px;"
    />

    <div v-if="columnList.length > 0" style="margin-bottom: 15px;">
      <el-tag v-for="col in columnList" :key="col" style="margin-right: 8px; margin-bottom: 5px;">{{ col }}</el-tag>
    </div>

    <el-table
      v-if="columns.length > 0"
      :data="rows"
      border
      style="width: 100%;"
      max-height="500"
    >
      <el-table-column
        v-for="col in columns"
        :key="col"
        :prop="col"
        :label="col"
        :show-overflow-tooltip="true"
      />
    </el-table>
  </div>
</template>

<script>
import { executeSql, getSqlColumns } from '@/api/tool/sqlConsole'

export default {
  name: 'SqlConsole',
  data() {
    return {
      sql: '',
      loading: false,
      loadingColumns: false,
      columns: [],
      rows: [],
      columnList: [],
      errorMsg: '',
      successMsg: ''
    }
  },
  methods: {
    handleExecute() {
      if (!this.sql.trim()) {
        this.errorMsg = '请输入SQL语句'
        return
      }
      this.loading = true
      this.errorMsg = ''
      this.successMsg = ''
      this.columns = []
      this.rows = []
      executeSql(this.sql).then(res => {
        const data = res.data
        if (data.type === 'select') {
          this.columns = data.columns
          this.rows = data.rows
          if (data.rows.length === 0) {
            this.successMsg = '查询成功,无数据'
          } else {
            this.successMsg = '查询成功,共 ' + data.rows.length + ' 条数据'
          }
        } else if (data.type === 'dml') {
          this.successMsg = '执行成功,影响 ' + data.affectedRows + ' 行'
        } else {
          this.successMsg = data.message || '执行成功'
        }
      }).catch(err => {
        this.errorMsg = err.msg || err.message || '执行失败'
      }).finally(() => {
        this.loading = false
      })
    },
    handleClear() {
      this.sql = ''
      this.columns = []
      this.rows = []
      this.columnList = []
      this.errorMsg = ''
      this.successMsg = ''
    },
    handleShowColumns() {
      if (!this.sql.trim()) {
        this.errorMsg = '请输入SQL语句'
        return
      }
      this.loadingColumns = true
      this.errorMsg = ''
      this.columnList = []
      getSqlColumns(this.sql).then(res => {
        this.columnList = res.data
        if (res.data.length === 0) {
          this.successMsg = '未获取到字段信息'
        } else {
          this.successMsg = '共 ' + res.data.length + ' 个字段'
        }
      }).catch(err => {
        this.errorMsg = err.msg || err.message || '获取字段失败'
      }).finally(() => {
        this.loadingColumns = false
      })
    }
  }
}
</script>