前言:始于一个随手的需求

最近在零点博客整理旧文档,发现直接复制的SQL语句简直像“原始代码”,缩进混乱、关键字粘连,看着就头疼。作为一名深耕C#开发多年的工程师,我不禁想:能不能写个轻量级的工具自己用?顺便也帮大家避避坑。

这其实是一个非常好的后端开发练手项目。今天这篇内容,我不整那些虚头巴脑的概念,直接带大家从零开发一个支持MySQL语法的在线格式化器。既然是实战,咱们就来聊聊具体的实现逻辑、前端交互以及怎么用C#的正则表达式处理复杂的SQL语句。

一、技术选型与整体架构

为了保持轻量,我们选择.NET Core作为后端框架。前端嘛,配合简单的HTML/CSS/JS即可,毕竟咱们目的是展示后端逻辑。

  • 后端: C# WebAPI,处理核心的字符串处理逻辑。
  • 核心逻辑: 正则表达式匹配,栈结构管理缩进。
  • 数据库交互: 虽然是工具,但我还是把防注入的思路融入了正则校验。

二、核心逻辑拆解:C# 如何“读懂” SQL?

很多新手觉得处理SQL字符串很难,其实无非就是切割、识别、缩进这三步。在C#中,我们要用到Regex类来大展身手。

1. 拆分语句(Split & Replace)

首先,我们要把一条长SQL拆成多行。我们可以利用Regex.Replace方法,把换行符或者多个空格替换成标准的换行符。

// 简单的按符号拆分示例
string sqlInput = "SELECT * FROM User WHERE Id = 1 AND Name = 'ZhangSan'";
// 这里的正则是一个简化版,实际生产中需要处理更多边缘情况
string[] lines = Regex.Split(sqlInput, @"(\s+(?:AND|OR|WHERE|FROM|SELECT)\s+)", RegexOptions.IgnoreCase);

2. 缩进管理(关键点!)

怎么控制缩进?这就得用到栈(Stack)了。当遇到SELECTFROMLEFT JOIN等关键字入栈,遇到分号出栈。每个层级增加一个Tab或2个空格。

using System.Text.RegularExpressions;
using System.Collections.Generic;

public string FormatSql(string rawSql)
{
    var stack = new Stack<int>();
    stack.Push(0); // 初始层级0

    // 使用一个更强大的正则来匹配关键字,包括括号匹配
    // 这里只展示逻辑思路:当匹配到开始括号,层级+1,匹配到结束括号,层级-1
    var pattern = @"(\b(SELECT|FROM|WHERE|LEFT JOIN|INNER JOIN|RIGHT JOIN|ON|AND|OR|ORDER BY|GROUP BY|)\b)";

    return Regex.Replace(rawSql, pattern, match =>
    {
        string keyword = match.Value.ToUpper();
        if (IsStartKeyword(keyword)) stack.Push(stack.Peek() + 1);
        if (IsEndKeyword(keyword)) stack.Pop();

        // 返回格式化后的内容,包括缩进
        return Environment.NewLine + new string('\t', stack.Peek() - 1) + keyword;
    }, RegexOptions.IgnoreCase);
}

三、避坑指南:实战中的那些坑

在用C#写这段逻辑时,我踩了不少坑,这里给大家提炼一下:

  1. 正则写死的痛苦: SQL语法千变万化,千万不要试图用一个正则写死所有规则。对于复杂的存储过程或嵌套查询,最好采用递归或状态机模式。我现在的方案只针对常用MySQL语句做了优化,因为它足够满足博客引用的需求。
  2. 空值处理: 用户可能复制的是空行或者乱码。在后端接收参数时,务必判断IsNullOrEmpty,否则正则库会直接报错。
  3. 防注入意识: 虽然咱们是做格式化工具,但这道题给了极好的机会科普SQL注入防护。在把用户输入的SQL送入工具前,如果是用于查询,一定要先用预处理语句(Parameterized Query)处理,或者在展示层面做HTML实体转义。

四、前端交互与数据传输(JSON解析)

后端C#处理完字符串后,我们需要把它吐给前端。JSON是C#的最佳拍档。

[HttpGet("Format")]
public IActionResult Format(string sql)
{
    if (string.IsNullOrWhiteSpace(sql)) return BadRequest("SQL内容不能为空");

    var formatted = FormatSql(sql);

    return Ok(new 
    { 
        original = sql, 
        formatted = formatted, 
        timestamp = DateTime.Now 
    });
}

前端通过JS的fetch获取JSON数据,通过简单的CSS给格式化后的SQL加上背景色,一个看起来很专业的在线工具就完成了。

五、总结

从需求提出到代码落地,这个基于C#的SQL格式化器开发周期其实非常短。它涵盖了从后端开发的逻辑处理,到前端开发的展示,甚至包括正则表达式的高级用法。

写这类源码案例,最重要的不是把代码贴出来,而是要把底层拆解的逻辑讲清楚。比如为什么用栈?为什么正则要这样写?只有理解了这些,当你面对复杂的EMLOG插件开发或项目维护时,才能游刃有余。

如果你也有在零点博客写代码的困惑,欢迎留言讨论。咱们下期见!