动手做一个通用代码生成器

1,311 阅读3分钟

思路分析

我们可以认为代码生成器是收集一些信息后讲信息渲染到固定的代码模板中生成最终代码

准备知识

通过上面的分析,我们知道要写一个代码生成器要有以下一些预备知识:

  1. 可以获取用户输入
  2. 可以获取数据库信息
  3. 可以将内存信息写入到本地文件

上面的这几点几乎所有流行的编程语言都可以做得到。我们这里选择使用nodeJs express

ejs mysql

关键步骤代码

  1. 安装Express 应用程序生成器

    # 如果安装缓慢请使用 cnpm  https://www.oschina.net/p/cnpm?hmsr=aladdin1e1
    npm install -g express-generator
    
    # 高版本node(包含在 Node.js 8.2.0 及更高版本中)可以用下面命令安装
    npx express-generator
    
  2. 生成Express 基本框架

    # 生成项目文件夹
    mkdir gen
    cd gen
    
    # 生成Express框架
    express -e --git
    
  3. 安装依赖包

    # mysql 用来操作数据库  lodash 是一个JavaScript常用的工具包
    npm install --save lodash mysql
    
  4. 创建基本信息收集表单

    打开前面通过命令生成的Express项目文件夹,打开项目根目录下的app.js 文件

    // 代码的12 - 14左右 
    // view engine setup
    app.set('views', path.join(__dirname, 'views'));
    app.set('view engine', 'ejs');
    

    可以看到ejs模板目录是项目根目录下的views文件夹,在index.ejs中添加一个表单,如下代码

    <!DOCTYPE html>
    <html>
    
    <head>
        <title>速聚代码生成器</title>
        <link rel='stylesheet' href='/lib/bootstrap-4.6.0/css/bootstrap.min.css'/>
        <link rel='stylesheet' href='/stylesheets/style.css'/>
    </head>
    
    <body>
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <a class="navbar-brand" href="#">速聚代码生成器</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav"
                aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarNav">
            <ul class="navbar-nav">
                <li class="nav-item active">
                    <a class="nav-link" href="#">生成代码 <span class="sr-only">(current)</span></a>
                </li>
            </ul>
        </div>
    </nav>
    
        <div class="row" style="margin:30px auto;width:600px ">
            <form action="/tables" method="post">
                <div class="form-group">
                    <label for="exampleInputEmail1">项目名称</label>
                    <input type="name" class="form-control">
                </div>
                <div class="form-group">
                    <label for="exampleInputPassword1">作者</label>
                    <input class="form-control" name="author">
                </div>
                <div class="form-group">
                    <label for="exampleInputPassword1">表前缀移除</label>
                    <input class="form-control" name="pre">
                </div>
                <div class="form-group">
                    <label for="exampleInputPassword1">生成文件</label>
                    <br/>
                    <span>后端: </span>
                    <label><input type="checkbox" name="files[]" value="mappper">mappper</label>
                    <label><input type="checkbox" name="files[]" value="mappperXML">mappperXML</label>
                    <label><input type="checkbox" name="files[]" value="entity">entity</label>
                    <label><input type="checkbox" name="files[]" value="param">param</label>
                    <label><input type="checkbox" name="files[]" value="result">result</label>
                    <label><input type="checkbox" name="files[]" value="controller">controller</label>
                    <label><input type="checkbox" name="files[]" value="service">service</label>
                    <label><input type="checkbox" name="files[]" value="serviceimpl">serviceimpl</label>
                    <label><input type="checkbox" name="files[]" value="enum">enum</label>
                    <br/>
                    <span>前端: </span>
                    <label><input type="checkbox" name="files[]" value="indexJsx">indexJsx</label>
                    <label><input type="checkbox" name="files[]" value="addJsx">addJsx</label>
                    <label><input type="checkbox" name="files[]" value="editJsx">editJsx</label>
                    <label><input type="checkbox" name="files[]" value="apiJs">apiJs</label>
                </div>
                <div class="form-group">
                    <label for="exampleInputPassword1">是否生成api文档</label>
                    <br/>
                    <label><input type="radio" name="api"></label>
                    <label><input type="radio" name="api"></label>
                </div>
                <button type="submit" class="btn btn-primary">下一步</button>
            </form>
        </div>
    </body>
    
    </html>
    
    
  5. 创建接收表单信息路由

    Express 的路由文件写在项目根目录下的routes文件夹下,我们在routes/index.js添加一个新的路由用来接收表单信息

    Express基本路由

    router.post('/tables', function (req, res) {
       // req 就数据请求对象
    });
    
    
  6. 创建数据库连接池

    在获取到用户提交的信息之后我们还需要拿到数据库中的信息。

    # 在项目根目录下创建一个配置文件夹 用来存放配置文件
    mkdir config
    

    在config文件夹中创建一个db.js的文件用来配置数据库信息和创建数据库连接池

    var mysql = require('mysql')
    var pool = mysql.createPool({
        connectionLimit: 10,
        host: '127.0.0.1',
        user: 'root',
        password: '',
        database: 'database'
    });
    
    function  query(sql, values, callback) {
        console.log("db pool");
        pool.getConnection(function (err, connection) {
            if(err) throw err;
            console.log("sql ",sql,values);
            //Use the connection
            connection.query(sql, values,function (err, results, fields) {
                console.log(JSON.stringify(results),err);
                //每次查询都会 回调
                callback(err, results);
                //只是释放链接,在缓冲池了,没有被销毁
                connection.release();
                if(err) throw error;
            });
    
        });
    }
    
    exports.query = query;
    
  7. 获取指定库中所有的表信息

    通过上述创建的数据库连接池就能从数据库中读信息啦,我们回到routes/index.js文件

    // 引入数据库
    const db = require("../config/db")
    
    ....
    
    /**
     *  获取数据库中所有的数据表信息
     */
    router.post('/tables', function (req, res) {
        /**
         * 简单思路
         * 1. 通过 req.body 得到上一步中提交过来的信息
         * 2. 通过查询数据库得到目前连接的数据库的表信息
         */
        db.query("show tables", [], function (err, rows) {
            res.render('index', {
                postData: {...req.body},
                tables
            });
        });
    });
    
    
  8. 获取指定表中所有的字段信息

    同样的方法我们可以新建一个路由用来获取指定表的所有字段信息

    /**
     * 通过表名返回表结构信息
     */
    router.get('/table_info', function (req, res) {
        db.query('show full fields from' + req.query.name, [], function (err, rows) {
            res.send(rows);
        });
    });
    
  9. 汇总数据库信息和表单信息渲染到模板

    router.post('/gen', function (req, res) {
    
        // let dir = tempRootPath + new Date().getTime() + "/"
        let dir = tempRootPath
        const {tableColumn, postData} = req.body
    
        // 循环所有表
        _.each(tableColumn, function (item, key) {
            // 帕斯卡表名
            let pskTableName = _.capitalize(key)
    
            let frontPath = dir + key + "/front/" + pskTableName + "/"
            // 生成前端部分  按照表名称生成组件用帕斯卡方式
            mkdirsSync(frontPath)
    
            // 用到的值直接结构以免出现bug
            const {author, pre} = postData
            /*
             *  根据文件结构配置模板,生成对应的数据
             */
            let allColumns = tableColumn[key] ? tableColumn[key] : []
    
            // 生成代码
            generate(frontTemplateFiles, frontPath, {
                updateColumns: _.filter(allColumns, item => item.showUpdate),
                updateMustColumns: _.filter(allColumns, item => item.mustUpdate),
                addColumns: _.filter(allColumns, item => item.showAdd),
                addMustColumns: _.filter(allColumns, item => item.mustAdd),
                listColumns: _.filter(allColumns, item => item.showList),
                searchColumns: _.filter(allColumns, item => item.isSearch),
                allColumns,
                author,
                pre,
                pskTableName
            })
        })
    
    
        res.send("ok")
    })
    

代码地址

github地址