• CRUD生成器DBuilder设计与实现


    源码位于github:https://github.com/lvyahui8/dbuilder.git 。文中图片如果太小看不清楚,请右键点击“在新标签页中打开”即可看到原图

    有兴趣还可以加QQ群交流:146103720  DBuilder交流群

    第一章           引言

    1.1 研究背景及意义

    计算机软件技术发展至今,数据库已成为最广泛使用的存储格式化数据的媒介,数据库程序开发技术也日趋完善,大型的ORM框架使得数据库程序开发变得简单,并已成为操作关系型数据库的主流方式。数据库程序主要代码为CRUD(create, retrieve, update, delete)代码,随着ORM框架功能的完善,编写CRUD代码也衍生其固定的流程,对不同数据库表进行操作的CRUD代码也存在高度可重用性。当前编写重复性的CRUD代码成为开发人员的常态,不仅严重降低其积极性,而且损失其开发效率,所以迫切需要一种能够快速生成CRUD代码的产品,以期减少这方面的工作,提高开发效率。

    1.2 研究现状

    目前国外已经诞生一些解决上述需求的、具有很高可用性的CRUD生成器产品:CrudKit,CRUD-Admin-Generator,Dadabik,GroceryCrud,SximoBuilder。这些产品各有其特点,但也有一共同点:都是基于PHP进行开发(这在一定程度上决定于PHP语法的灵活性及其解析性)。SximoBuilder是其中的典型代表,尽管SximoBuilder设计独特、可用性高、流行度高,但也存在如下不足之处:

    • 不支持自定义表单控件;
    • 不支持多数据库;
    • 验证规则不完善,不支持异步验证;
    • 代码冗余度极大。

    然而对于当今日益复杂的web程序,上述几点是开发过程必须考虑的问题,因此,开发一款既具有SximoBuilder现有功能、又完善其不足之处的CRUD生成器产品,势在必行。

    1.3 研究内容

    基于国内外CRUD生成器研究现状,笔者开发一款名为DBuilder(D为DataAdministrator的简写)的CRUD 生成器。

    DBuilder借鉴SximoBuilder的模块为代码单元、由模板生成代码的思想,但拥有与SximoBuilder完全不同的代码生成器逻辑。它在实现SximoBuilder核心的代码生成、通用CRUD两种功能的基础上,通过重写代码逻辑完善其不足之处:代码冗余度大、缺少前端验证。

    第二章           DBuilder系统分析

    DBuilder面向的主要用户人群为web后台管理员以及开发人员,因此其系统分析过程,将更多的站在web后台管理员及开发人员的角度考虑问题。

    2.1 需求分析

    项目需要实现如下几点核心功能。

    1)        数据源管理

    用户可以在界面为项目配置多个数据源。配置的数据源信息包括数据库类型、地址、数据库名、端口、用户名、密码等信息。支持对数据源的增删改查,保证对数据源的增删改查不轻易造成系统问题。

    2)        代码生成

    此功能是DBuilder的核心要实现的功能,用户在选择数据源和数据表之后,能够对数据库表的字段做简单配置,配置包括Form表单配置、List(Table)列表配置、关系配置、全局属性配置。配置完成后DBuilder要能生成对数据库表的CRUD的MVC代码,即需要实现CRUD可用功能,但不用编写代码。

    3)        数据库表CRUD

    生成的代码必须支持数据表记录的新建、删除、更新、查询操作。

    4)        菜单管理

    DBuilder要能将生成的代码跟一个菜单项绑定,让用户点击菜单项之后,就可以使用DBuilder生成的CRUD功能。此菜单必须包括后台菜单,前台菜单不是必须的。

    5)        用户管理

    用户要实现多种角色。必须能够以邮箱为用户唯一标识,并作为登录参数。未来还要实现支持QQ、微信、新浪微博基于OAuth2.0的互联登录。

    6)        权限管理

    DBuilder要能实现不同用户角色对不同CRUD代码的执行、访问权限做到三维的可配置。譬如,现有一个文章管理的CRUD功能模块,用户角色分为系统管理员(SuperAdmin),管理员(Admin),访客(Guest),那么DBuilder要能实现如下的三维权限配置,且将之作为所有Module的默认权限。

    表2-1 Module权限配置表

    用户组与权限

    查看

    编辑

    删除

    导出

    SuperAdmin

    Admin

     

    Guest

     

     

     

    7)        站点参数配置

    DBuilder作为一个网站的web后台程序,对站点的全局参数配置也是必须的,这些参数包括网站名字、关键词、联系地址、友情链接等等。

    8)        操作日志

    DBuilder要记录用户的操作信息,包括访问的页面、执行的CRUD类型、时间等等信息。日志的记录形式支持数据库和文件两种方式。

    9)        多语言支持

    DBuilder要支持多国语言的切换。至少应该支持中文和英语两种语言,且以中文为默认。

    10)    多数据库类型支持

    DBuilder要支持多种类型数据库,暂时主要支持关系型数据库,包括mysql,MS SqlServer,oracle,PostGreSQL等等。

    2.2 数据原型分析

    按照需求提取可得实体有:用户、用户组、数据源、代码模块、菜单,关系有:权限、日志。实体与关系的含义如下:

    • 用户:表示使用DBuilder的用户;
    • 用户组:表示用户的类型分组,用户类型应该至少包括访客、管理员、超级管理员三种;
    • 数据源:表示DBuilder包含的数据库配置,一个数据源的配置包含连接一个数据库所需的基本参数;
    • 代码模块:表示DBuilder生成的代码模块,描述了代码文件和配置;
    • 菜单:表示DBuilder的左侧菜单项;
    • 权限:表示用户组对每个代码模块的各种操作权限;
    • 日志:表示用户对每个代码模块的CRUD访问日志。

    实体与关系的ER图如下:

     

    图2-1 ER图

    2.3 原则性要求

    DBuilder应该要做成一套高性能、高可用的CRUD生成器,为此DBuilder设计中应该符合下面几项原则:

    • DBuilder要精确到每个数据库字段可配置;
    • 应具备一个WEB后台应用的雏形,使用户可在此基础上快速建立完整的WEB后台应用;
    • DBuilder要尽可能减少SQL操作,必要时可借助缓存、异步等技术,减少请求的处理逻辑,提高页面效率,减少用户等待时间;
    • DBuilder要有美观、简洁、直观的用户界面;
    • DBuilder要留有大量的扩展接口,能够让用户通过二次开发快速实现较为复杂的功能。

    第三章           DBuilder系统设计

    3.1 系统架构

    DBuilder有下面2个核心的构件Core CRUD 模块和GModule,GModule对Core CRUD 模块有继承依赖的关系,GModule由MVC Code和CRUD Config组成;Core CRUD模块是手工编写的代码,而GModule是DBuilder生成的代码;Core CRUD 模块实现CRUD操作,GModule实现扩展功能。下图表示了这两个构件的组成和关系

     

    图3-1概念与构件

    下面对图中设计的概念、构件、模块关系以及Build与CRUD流程做详细阐述。

    3.1.1 Core CRUD 模块

    Core CRUD 模块实现核心CRUD操作,一切对GModule MVC中Controller的CRUD请求,最终转交至Core CRUD 模块进行处理。Core CRUD 模块会开放一些预处理和后处理接口交由GModule实现,这些接口会在Model,Controller,View上都有体现。

    Core CRUD 模块主要包括如下文件

    • app/controllers/admin/AdminController.php
    • app/models/BaseModel.php
    • app/config/crud/admin.php
    • app/views/admin/core/list.blade.php
    • app/views/admin/core/form.blade.php

    Core CRUD 模块读取GModule Configuration实现真正的CRUD操作。

    3.1.2 GModule

    GModule(Generated Module)不但实现了Core CRUD Module接口(MVC代码),而且具有自己配置文件(CRUD Configuration)。每一GModule表示以一张数据库表为主表,具备CRUD功能的代码文件合集(包括对应的MVC + Configuration代码)。譬如,DBuilder生成的一个GModule, 主表为core数据源user表,名字为User,那么User GModule应包含下面代码文件:

    • controllers/UserController.php
    • models/User.php
    • views/user/_list.blade.php
    • views/user/_form.blade.php
    • views/user/view.blade.php
    • config/crud/user.php

    代码文件命名取决于GModule的名字,故为保证生成的代码文件不冲突,取GModule的名字(GModule Key,GModule Name)作为GModule的唯一标识。每一个GModule的信息都被保存在数据库中。一次新建 GModule操作将会新建上述所有代码文件,更新相关文件,并插入一条GModule记录到数据库。一次更新 GModule操作将只会更新Configuration文件。

    GModule 由MVC代码和CRUD Configuration代码组成,下面分别进行阐述:

    • MVC代码:用来实现扩展接口。CRUD请求应最先路由到GModule MVC的中的Controller(控制器)。并且GModule MVC 应与Core CRUD Module的MVC代码有继承关系。
    • CRUD Configuration代码:实现对GModule主表增删改查参数的配置。该文件放置在app/config/crud/目录下,以php array的格式定义。它包含对所有字段的表单,列表,视图,关系等参数的配置,以及全局的参数配置。

    GModule并不表示具体某一个模块,而是代指一类模块,这种模块可以由DBuilder生成,或者由开发人员手工建立。它主要用来实现Core CRUD Module的接口,主要包括下述几部分

    1)        Controller接口

    假设GModule模块的 Controller为A,Core CRUD Module 的Controller为B,则A应继承自B。CRUD请求会先路由到A,而实际的处理者是B。A会实现B开放的下列接口。

    • beforeListExcuteQuery(&querier):该接口在List查询器执行查询之前调用,传递的参数为查询器引用。用来在查询之前,绑定特殊的查询参数。
    • beforeList(&data):该接口在List查询器执行之后,渲染List视图之前调用。传递的参数为视图参数引用,其中包括查询出的model集合。用来对查询的model 集合做后处理,或者对list视图绑定一些Module专有的参数。
    • beforeEditExcuteQuery(&querier):该接口在Edit请求中Model查询器执行查询之前调用,传递的是查询器引用。用来绑定查询model需要的特殊参数。
    • beforeEdit(&data):该接口在Edit中Model查询器执行之后,渲染视图之前调用,传递的是视图参数引用,其中包括查询器查询出的model。用来做渲染前的预处理。
    • afterSave(&model):该接口在Edit中,保存编辑的之后调用,传递的是保存在数据库中,最新的数据库记录持久化的model。用来对model做一些复杂的后级联处理。
    • beforeView(data): 该接口在View请求中,View 查询器查询之后调用,传递的是视图参数的引用。用来对视图显示做预处理。

    2)        Model 接口

    GModule MVC代码中的Model也继承自BaseModel,实现 BaseModel类开放的一些接口可以完成扩展。

    formatXXXAttribute():该接口用来格式化某个字段。本产品基于Laravel,其已经具备类似的接口,就是getXXXXAttribute()。但这样的接口的优先级比字段优先级高,这在特殊的情况下为开发带来了不便,所以再设计一个类似的接口,该接口的优先级低于字段本身。

    3)        View 接口

    视图的扩展接口与前两者不同,主要体现在子视图与视图块上,也就是在Core CURD模块的视图基础上,扩展视图组件。默认Core CRUD MVC视图生成的是一个表格或者一个表单,占满页面。而View接口将提供在该表格上下左右扩展页面组件的能力。

    4)        Configuration

    每一个GModule对应一个Configuration文件,其中包含GModule对主表各个字段的配置参数,以及布局参数。

    3.1.3 模块关系

    CRUD请求路由到GModule的Controller,GModule代码实现Core CRUD MVC开放的接口,而由Core CRUD Module去真正实现对数据库的CRUD操作。每一个GModule的信息应该被记录在数据库表中,以便给GModule关联菜单,控制权限,记录操作日志等等。一些主要模块之间的关系如下图所示。

     

    图3-2模块关系

    从图2-2中可以看到,由GModule管理模块根据用户配置来生成一个GModule A,当用户的CRUD请求到达GModule A时,GModule 会讲请求转交Core CRUD进行处理,Core CRUD 模块再以SQL对数据库进行CRUD操作。

    3.1.4 Build 与 CRUD流程

    DBuilder项目的方案,将真正的CRUD操作交给了Core CRUD Module去执行,CRUD参数由GET或者POST请求参数与GModule Configuration构成,而GModule的MVC代码只是去实现Core CRUD MVC开放的一些预处理或者后处理接口。

    图2-3是DBuilder最核心的流程图,包含Module的生成和处理CRUD请求的过程,图2-4是SximoBuilder 中Module的生成和处理CRUD请求的流程图。

     

    图3-3 DBuilder 代码生成和处理CRUD的流程

     

    图3-4 SximoBuilder 代码生成和处理CRUD的流程

    对比两者,可以看到两者的最大区别,是DBuilder复用一份CRUD代码,而不是像Sximo那样为每一个Module生成一套可以当独执行的CRUD代码。这样做的好处是提高了复用性,并通过Module CRUD MVC实现预处理/后处理接口达到扩展性的目的。

    3.2 Core数据源

    Core数据源是DBuilder的默认数据源,其类型为mysql,数据库名为dbuilder,本节按照《数据原型分析》一节进行详细的数据库设计。为提高程序性能,数据源信息保存在代码文件app/config/datasource.php中,文件内容如下:

    <?php return array (

        'core' =>

            array (

                'driver' => 'mysql',

                'host' => 'localhost',

                'database' => 'dbuilder',

                'username' => 'root',

                'password' => 'root',

                'charset' => 'utf8',

                'collation' => 'utf8_unicode_ci',

                'prefix' => '',

                'edit' => false,

                'port' => 3306,

            ),

            // more data source

     );

    其中Core数据源有下述数据表:

    1)        d_menu 表:表示后台左侧树形菜单,每一个可点击跳转的菜单项必须与一个Module进行关联。

    表3-1 web后台左侧菜单表

    field

    type

    default

    info

    id

    int

    auto_increment

    PRI

    module_id

    int

    module_name

    varchar(12)

    parent_id

    null

    父菜单项

    title

    varchar(12)

    module_title

    显示名称

    _order

    int

    0

    排序字段

    2)        d_module 表:记录了module信息,每一条d_module表的记录代表了DBuilder生成的一个Module。

    表3-2 module信息描述

    field

    type

    default

    info

    id

    int

    auto_increment

    PRI

    name

    varchar(32)

    UIN

    title

    varchar(32)

    module标题

    note

    varchar(32)

    module 说明

    db_source

    varchar(16)

    core

    数据源名称

    db_table

    varchar(16)

    module主表

    db_table_key

    varchar(16)

    主表PRI

    3)        d_user 表:保存着使用后台程序的用户。

    表3-3 web后台用户

    field

    type

    default

    info

    id

    int

    auto_increment

    PRI

    username

    varhcar(64)

    用户名

    email

    varchar(64)

    邮箱

    password

    varchar(64)

    HASH

    salt

    varchar(64)

    last_login

    timestamp

    最后登录时间

    remember_token

    记住密码口令

    group_id

    int

    组ID

    4)        d_group表:表示对后台用户的分组信息。

    表3-4 用户分组表

    field

    type

    default

    info

    id

    int

    auto_increment

    PRI

    name

    varhcar(64)

    组名

    note

    varchar(128)

    组说明

    level

    int

    组级别

    5)        d_group_access表:记录了每个GModule、不同后台用户组与各种操作权限的三维权限信息。

    表3-5 用户组对Module权限表

    field

    type

    default

    info

    id

    int

    auto_increment

    PRI

    group_id

    int

    组id

    module_id

    int

    Module模块ID

    edit

    int

    1

    可编辑

    view

    int

    1

    可查看

    delete

    int

    1

    可删除

    export

    int

    1

    可导出

    6)        d_log表:记录了每个用户的操作日志。

    表3-6 用户操作日志

    field

    type

    default

    info

    id

    int

    auto_increment

    PRI

    user_id

    int

    用户id

    ip_addr

    varchar(15)

    客户端IP

    module_id

    int

    访问的moduleid

    module_title

    varchar(16)

    task

    varchar(16)

    操作

    created_at

    timestamp

    可导出

    3.3 数据源管理模块

    DBuilder需要支持多数据源,多种类型数据库。数据源信息保存在d_database表中。考虑到数据库操作是频繁操作,如果将数据源信息保存在数据库中,则每次数据库操作将多一次数据源查询操作,这样做浪费性能。那么DBuilder不应该把数据源信息保存在数据库中,而应该保存在代码文件中。数据源管理的信息包括数据源名称(数据源的唯一标识,DBuilder默认的数据源名为core)、数据库类型、地址、端口、数据库名、用户名、密码等等信息。因为数据源管理模块并不对表进行增删改查操作,所以数据源管理模块并不是一个GModule模块。该模块的代码完全手工编写。

    3.4 GModule 管理模块

    DBuilder将以基于名字为“Module”的GModule作为生成GModule的用户接口,该模块称作GModule管理模块,换言之GModule管理模块本身就是一个GModule,该GModule的主表即是core数据源中保存GModule信息的数据库表,改GModule的名字为“Module”。GModule 管理模块包含创建,更新和删除GModule 的所有代码文件以及数据库记录。GModule的新建和删除需要更新全局的GModule路由。

    1)        GModule 路由

    GModule路由定义在一个独立的代码文件中,为一个以GModule名字进行减号分词并全部小写的字符串为键(譬如:GModule名字为OrderItem,则键值为order-item)、以Module中Controller类的类名为值的map字典,GModule路由是全局的。

    2)        GModule 新建&更新

    新建GModule将在数据库中生成一条记录、生成所有的module文件、并更新路由。更新操作只修改配置文件。新建与更新都使用相同的编辑视图,此编辑视图是对GModule Configuration的图形化配置界面。

    3)        GModule 删除

    GModule删除将删除所有的GModule MVC代码,删除GModule Configuration代码,删除数据库表记录,并更新GModule路由。

    3.5 Core CRUD 模块

    Core CRUD 模块是DBuilder处理CRUD请求的实际处理者,它由下述几部分组成:

    1)        参数解析初始化

    初始化Model,实例化一个Module的Model对象作为初始化查询器。加载Module Configuration,对未设置的值进行设置默认值,对参数进行汇聚。

    2)        表单Form

    主要包括新建和更新功能。根据GModule主表主键primaryKey是否设置判断是新建还是更新操作。下图是Form模块的流程

     

    图3-5 Form执行流程

    Form 分两部分,第一部分渲染Form页面给用户填写。第二部分为Form保存。

    渲染Form页面需要考虑的有Form控件和有外键关系的字段要怎么处理。Form控件需要支持类型包括text、text_date、text_datetime、textarea、select、radio、checkbox、file、hidden、address以及custom,自定义控件应该继承FormControl类,自定义控件的渲染由控件的render方法完成。Form渲染需要判断有关系的字段做辅助加载。比如对post(文章)表进行编辑,post表有一个字段为category_id,表示文章的栏目ID,对应category(栏目)表的id字段。这时需要对category_id使用select,radio,checkbox控件进行加载,方便用户输入。比如使用select控件,那么应该将category.id作为option的value,将category.name作为option中的text。这样做也是为了方便用户输入。此步骤与List中搜索时有共性,因此代码可复用。

    Form 保存需要考虑一些自定义控件的保存,自定义控件的数保存由自定义控件类的onSave方法完成。Form 保存还需要考虑关系的保存,默认应该级联更新附属表。Form 表单在用户输入完成点击保存之后,要分下面几步:

    • 根据字段配置的验证规则进行验证;
    • 应判断Module Configuration 中的relation进行分析,进行必要的级联操作;
    • 并要调用自定义控件的onSave方法;
    • 最后才应更新或新建主表数据;
    • 跳转:更新或新建成功跳转至List,失败跳转至Form。

    Form 还需要开放对应的预处理和后处理接口。

    3)        列表List(Table)

    List是一个分页Table,按照Module Configuration 中的字段配置显示分页数据。支持列表搜索,排序,勾选删除,导出等功能;

    • 分页展现数据以InitQuerier模块得到的Model作为查询器,结合分页,查询出基本的数据列表。分页类型为全页刷新类型(非异步分页);
    • List搜索:支持在Module Configuration中定义了search不等于false的字段作为搜索条件。搜索关系为逻辑与的关系。并反映在GET参数上。搜索输入控件根据字段的form type来定。在Form 中定义为select,radio,checkbox控件的字段,在List中都将使用select控件作为输入控件;
    • List 排序:以在Module Configuration中定义了form.sort 不等于 false的字段作为可排序字段。排序只支持按单一字段排序,降序方式含升序和降序;
    • List 多选操作主要支持多选删除,多选复制操作,任何删除操作都需确认;
    • List 数据每行记录的支持的操作按Module Configuration中的配置给出,默认支持编辑,删除,查看三项操作;
    • List 也要开放预处理/后处理接口给Module CRUD MVC。

    4)        查看View

    View 暂时以Form为基础,提供预处理后处理接口,但不允许编辑。

    第四章           DBuilder系统实现

    4.1 目录结构

    代码按照前段资源、MVC、Configuration、Library等概念进行了分目录存放。下面表格中给出了主要目录的说明:

    表4-2 代码主要目录

    目录

    作用

    assets

    此目录存放着各种各样的前端资源。包括bootstrap,以及自定义的css和js文件。

    plugins

    存放特殊前端插件的目录,比如富文本编辑器,视音频插件等等。

    app/controllers/admin

    存放着MVC中控制器的目录。其中,DBuilder的核心在admin目录下。

    app/models

    存放着MVC中模型(Model)的目录。用来做数据库查询用。

    app/views

    存放着MVC中视图的目录。文件名以*.blade.php的格式命名。

    app/library

    存放PHP辅助类,PHP库的目录。

    app/config/crud

    存放Module Configuration的目录。

    4.2 GModule 配置文件

    GModule配置文件定义了GModule的参数,该文件保存在app/config/crud/下,是以GModule Name进行蛇形分词得到的字符串命名的php文件(譬如:一GModule的名字为OrderItem,则GModule配置文件为order_item.php)。配置参数以数组格式返回。

    考虑到PHP数组在表格中呈现的美观性,对参数以配置中的Key=>Value形式,以点分形式Key.Value表示。

    表4-3 root配置

    Configuration Key

    类型

    默认值

    含义

    fields

    array

    array()

    字段列表

    fields.field_name

    array

    array()

    对field_name字段的配置

    fields.field_name.label

    string

    UP(field_name)

    显示在列表表格的表头的内容,和form控件旁边的内容

    fields.field_name.form

    array

    array()

    field_name字段的表单配置,具体参考

    fields.field_name.form配置

    fields.field_name.list

    array

    array()

    field_name 字段的列表配置,具体参考

    fields.field_name.list配置

    fields.field_name. relation

    array

    array()

    field_name 字段的关系

    表4-3中每个字段的表单配置说明如下表所示:

    表4-4 fields.field_name.form配置

    Configuration Key

    值类型

    默认

    含义

    type

    string

    text

    Form控件类型

    show

    bool

    true

    是否出现在表单

    hidden

    bool

    false

    是否以隐藏的空间在表单中

    rule

    string

    required

    验证规则

    ajax_validate

    bool

    false

    是否异步验证

    placeholder

    string

    控件中的提示

    表4-3中每个字段的列表配置说明如下表所示:

    表4-5 fields.field_name.list配置

    Configuration Key

    值类型

    默认

    含义

    show

    bool

    true

    是否出现在表单

    sort

    bool

    true

    字段是否可以排序,默认可排序

    search

    bool,array

    array()

    是否可搜索以及搜索规则

    string

    控件中的提示

    表4-3中每个字段的关系配置说明如下表所示:

    表4-6 fields.field_name.relation配置

    Configuration Key

    值类型

    默认

    含义

    table

    string

    关联表

    foreign_key

    string

    id

    对应关联表里的字段

    show

    string

    关联表里的一个字段,当需要转义时,将用该字段代替field_nae字段显示

    as

    string

    table_show

    转义查询出的值用哪个字段表示,主要为了防止主表和关联表有重复字段

     下面是一个名为post的GModule的Configuration文件

      1 <?php return array (
      2   'data_source' => 'core',
      3   'table' => 'post',
      4   'fields' => 
      5   array (
      6     'id' => 
      7     array (
      8       'label' => 'ID',
      9       'form' => 
     10       array (
     11         'show' => true,
     12         'hidden' => true,
     13         'type' => 'text',
     14         'rule' => 'required',
     15         'ajax_validate' => false,
     16         'placeholder' => '',
     17       ),
     18       'list' => 
     19       array (
     20         'show' => true,
     21         'sort' => true,
     22         'search' => '=',
     23         'lookup' => false,
     24       ),
     25       'relation' => 
     26       array (
     27         'type' => '',
     28         'table' => '',
     29         'foreign_key' => '',
     30         'show' => '',
     31         'as' => '',
     32       ),
     33     ),
     34     'title' => 
     35     array (
     36       'label' => '标题',
     37       'form' => 
     38       array (
     39         'show' => true,
     40         'hidden' => false,
     41         'type' => 'text',
     42         'rule' => 'required',
     43         'ajax_validate' => false,
     44         'placeholder' => '',
     45       ),
     46       'list' => 
     47       array (
     48         'show' => true,
     49         'sort' => true,
     50         'search' => '=',
     51         'lookup' => false,
     52       ),
     53       'relation' => 
     54       array (
     55         'type' => '',
     56         'table' => '',
     57         'foreign_key' => '',
     58         'show' => '',
     59         'as' => '',
     60       ),
     61     ),
     62     'short' => 
     63     array (
     64       'label' => '摘要',
     65       'form' => 
     66       array (
     67         'show' => true,
     68         'hidden' => false,
     69         'type' => 'textarea',
     70         'rule' => 'required',
     71         'ajax_validate' => false,
     72         'placeholder' => '',
     73       ),
     74       'list' => 
     75       array (
     76         'show' => true,
     77         'sort' => true,
     78         'search' => '=',
     79         'lookup' => false,
     80       ),
     81       'relation' => 
     82       array (
     83         'type' => '',
     84         'table' => 'category',
     85         'foreign_key' => 'id',
     86         'show' => 'id',
     87         'as' => '',
     88       ),
     89     ),
     90     'content' => 
     91     array (
     92       'label' => '正文',
     93       'form' => 
     94       array (
     95         'show' => true,
     96         'hidden' => false,
     97         'type' => 'wysiwyg',
     98         'rule' => 'required',
     99         'ajax_validate' => false,
    100         'placeholder' => '',
    101       ),
    102       'list' => 
    103       array (
    104         'show' => false,
    105         'sort' => true,
    106         'search' => '=',
    107         'lookup' => false,
    108       ),
    109       'relation' => 
    110       array (
    111         'type' => '',
    112         'table' => 'category',
    113         'foreign_key' => 'id',
    114         'show' => 'id',
    115         'as' => '',
    116       ),
    117     ),
    118     'view_ct' => 
    119     array (
    120       'label' => '查看次数',
    121       'form' => 
    122       array (
    123         'show' => false,
    124         'hidden' => false,
    125         'type' => 'text',
    126         'rule' => 'required',
    127         'ajax_validate' => false,
    128         'placeholder' => '',
    129       ),
    130       'list' => 
    131       array (
    132         'show' => true,
    133         'sort' => true,
    134         'search' => '=',
    135         'lookup' => false,
    136       ),
    137       'relation' => 
    138       array (
    139         'type' => '',
    140         'table' => '',
    141         'foreign_key' => '',
    142         'show' => '',
    143         'as' => '',
    144       ),
    145     ),
    146     'created_at' => 
    147     array (
    148       'label' => '创建时间',
    149       'form' => 
    150       array (
    151         'show' => false,
    152         'hidden' => false,
    153         'type' => 'text',
    154         'rule' => 'required',
    155         'ajax_validate' => false,
    156         'placeholder' => '',
    157       ),
    158       'list' => 
    159       array (
    160         'show' => false,
    161         'sort' => true,
    162         'search' => '=',
    163         'lookup' => false,
    164       ),
    165       'relation' => 
    166       array (
    167         'type' => '',
    168         'table' => '',
    169         'foreign_key' => '',
    170         'show' => '',
    171         'as' => '',
    172       ),
    173     ),
    174     'updated_at' => 
    175     array (
    176       'label' => '更新时间',
    177       'form' => 
    178       array (
    179         'show' => false,
    180         'hidden' => false,
    181         'type' => 'text',
    182         'rule' => 'required',
    183         'ajax_validate' => false,
    184         'placeholder' => '',
    185       ),
    186       'list' => 
    187       array (
    188         'show' => true,
    189         'sort' => true,
    190         'search' => '=',
    191         'lookup' => false,
    192       ),
    193       'relation' => 
    194       array (
    195         'type' => '',
    196         'table' => '',
    197         'foreign_key' => '',
    198         'show' => '',
    199         'as' => '',
    200       ),
    201     ),
    202     'category_id' => 
    203     array (
    204       'label' => '栏目',
    205       'form' => 
    206       array (
    207         'show' => true,
    208         'hidden' => false,
    209         'type' => 'select',
    210         'rule' => 'required',
    211         'ajax_validate' => false,
    212         'placeholder' => '',
    213       ),
    214       'list' => 
    215       array (
    216         'show' => true,
    217         'sort' => true,
    218         'search' => '=',
    219         'lookup' => false,
    220       ),
    221       'relation' => 
    222       array (
    223         'type' => 'belongsTo',
    224         'table' => 'category',
    225         'foreign_key' => 'id',
    226         'show' => 'title',
    227         'as' => 'category_title',
    228       ),
    229     ),
    230   ),
    231   'list_options' => 
    232   array (
    233     'page' => 10,
    234     'create' => true,
    235     'update' => true,
    236     'delete' => true,
    237   ),
    238   'form_options' => 
    239   array (
    240     'layout' => 
    241     array (
    242       'cols' => 12,
    243       'label_cols' => 1,
    244       'input_cols' => 11,
    245     ),
    246   ),
    247   'relations' => 
    248   array (
    249     'id' => 
    250     array (
    251       'type' => '',
    252       'table' => '',
    253       'foreign_key' => '',
    254       'show' => '',
    255       'as' => '',
    256     ),
    257     'title' => 
    258     array (
    259       'type' => '',
    260       'table' => '',
    261       'foreign_key' => '',
    262       'show' => '',
    263       'as' => '',
    264     ),
    265     'short' => 
    266     array (
    267       'type' => '',
    268       'table' => '',
    269       'foreign_key' => '',
    270       'show' => '',
    271       'as' => '',
    272     ),
    273     'content' => 
    274     array (
    275       'type' => '',
    276       'table' => '',
    277       'foreign_key' => '',
    278       'show' => '',
    279       'as' => '',
    280     ),
    281     'view_ct' => 
    282     array (
    283       'type' => '',
    284       'table' => '',
    285       'foreign_key' => '',
    286       'show' => '',
    287       'as' => '',
    288     ),
    289     'created_at' => 
    290     array (
    291       'type' => '',
    292       'table' => '',
    293       'foreign_key' => '',
    294       'show' => '',
    295       'as' => '',
    296     ),
    297     'updated_at' => 
    298     array (
    299       'type' => '',
    300       'table' => '',
    301       'foreign_key' => '',
    302       'show' => '',
    303       'as' => '',
    304     ),
    305     'category_id' => 
    306     array (
    307       'type' => '',
    308       'table' => '',
    309       'foreign_key' => '',
    310       'show' => '',
    311       'as' => '',
    312     ),
    313   ),
    314 );
    GModule Configuration

    4.3 数据源管理模块实现

    数据源管理模块完成基于网页界面对app/config/datasource.php文件的配置。包含数据源列表页,数据源新建与编辑页。

    • 数据源列表页:对应请求路径为admin/data-source/list,此页面读取DataSource文件渲染List(table)页面,密码不显示。
    • 数据源编辑页面:对应请求路径为 admin/data-source/edit,GET请求读取DataSource问价内容渲染表单,POST请求将数据源参数输出值DataSource文件。此页面提供测试连接功能,实现方法为基于表单的数据源参数连接数据库,执行一条SQL语句,连接是否成功取决于SQL是否执行成功。

    实现数据源管理的核心控制器代码放在DataSourceController.php文件中。

      1 <?php
      2 /**
      3  * Created by PhpStorm.
      4  * User: lvyahui
      5  * Date: 2016/5/12
      6  * Time: 15:35
      7  */
      8 
      9 namespace admin;
     10 
     11 use BaseModel;
     12 use IlluminateSupportFacadesConfig;
     13 use IlluminateSupportFacadesRedirect;
     14 use SiteHelpers;
     15 use IlluminateSupportFacadesResponse;
     16 use IlluminateSupportFacadesInput;
     17 
     18 use PDOException;
     19 use PDO;
     20 class DataSourceController extends AdminController
     21 {
     22     /**
     23      * 呈现数据源列表
     24      */
     25     public function getList()
     26     {
     27         $datasources = SiteHelpers::loadDataSources();
     28         $this->makeView(array(
     29             'datasources' => $datasources,
     30         ));
     31     }
     32 
     33     /**
     34      * 异步加载某数据源的所有数据表
     35      * @return IlluminateHttpJsonResponse
     36      */
     37     public function getTables()
     38     {
     39         $dataSourceName = Input::get("data_source");
     40         $dataSources = SiteHelpers::loadDataSources();
     41 
     42         $dataSource = $dataSources[$dataSourceName];
     43         $tables = BaseModel::getTableList($dataSource['database'], $dataSourceName);
     44         return Response::json(array(
     45             'success' => true,
     46             'data'    => array(
     47                 'tables'   => $tables,
     48                 'selected' => Input::get('table'),
     49             ),
     50         ));
     51     }
     52 
     53     /**
     54      * 呈现数据源编辑或者新建FORM
     55      * @param null $slug
     56      */
     57     public function getEdit($slug = null)
     58     {
     59         $dataSource = null;
     60         if ($slug) {
     61             // 更新
     62             $dataSource = $slug === 'core' ? Config::get('database.connections.core')
     63                 : Config::get('datasource.' . $slug);
     64             $dataSource['name'] = $slug;
     65         } else {
     66             // 新建
     67             $dataSource = array(
     68                 'name'     => '',
     69                 'driver'   => 'mysql',
     70                 'host'     => 'localhost',
     71                 'port'     => 3306,
     72                 'database' => '',
     73                 'username' => 'root',
     74                 'password' => '',
     75                 'charset'  =>   'utf8',
     76                 'collation' =>  'utf8_unicode_ci'
     77             );
     78         }
     79 
     80         $this->makeView(array(
     81             'dataSource'    =>  $dataSource
     82         ));
     83     }
     84 
     85     /**
     86      * 测试数据源连接是否可靠
     87      * @return IlluminateHttpJsonResponse
     88      */
     89     public function postTest(){
     90         $success = true;
     91         try{
     92             $dsn = Input::get('driver').':'.Input::get('host').':'.Input::get('port').';dbname='.Input::get('database');
     93             $dbh = new PDO($dsn,Input::get('username'),Input::get('password'));
     94 //            $connection = new Connection($dbh,Input::get('database'));
     95 //            $key = md5(date("Y-m-d H:i:s"));
     96 //            DB::addConnection($key,$connection);
     97 //            if(!DB::connection($key)->getDatabaseName()){
     98 //                $success = false;
     99 //            }
    100             $dbh = null;
    101         }catch(PDOException  $e){
    102             $success = false;
    103         }
    104         return Response::json(array(
    105             'success'   =>  $success,
    106         ));
    107     }
    108 
    109     /**
    110      * 保存编辑好的数据源信息
    111      * @param null $primaryKeyValue
    112      * @return mixed
    113      */
    114     public function postEdit($primaryKeyValue = null)
    115     {
    116         $dataSources = SiteHelpers::loadDataSources();
    117         $name = Input::get('name');
    118         $dataSources[$name] = Input::all();
    119         SiteHelpers::saveDataSources($dataSources);
    120 
    121         return Redirect::action(get_class($this).'@getList');
    122     }
    123 
    124 
    125     public function getTableFields(){
    126         $connection = Input::get('connection');
    127         $table = Input::get('table');
    128 
    129         $rawFields = BaseModel::getTableColumns($table,$connection);
    130 
    131         $fields = array();
    132         $pri = null;
    133         foreach($rawFields as $field){
    134             if($field->Key === 'PRI'){
    135                 $pri = $field->Field;
    136             }
    137             $fields [] = $field->Field;
    138         }
    139 
    140         return Response::json(array(
    141             'success'   =>  true,
    142             'data'  =>  array(
    143                 'fields'    =>  $fields,
    144                 'pri'       =>  $pri,
    145             ),
    146         ));
    147     }
    148 }
    DataSourceController

    4.4 CoreCRUD 模块实现

    CoreCRUD模块涉及的代码文件极其作用如下说明。

    • app/controllers/admin/AdminController.php:CoreCRUD模块的控制器,是CRUD操作核心的逻辑代码。主要分析请求参数和Module参数,调用Model层,渲染视图层,实现List呈现、List搜索排序、Form呈现、Form保存等功能;
    • app/models/BaseModel.php:CoreCRUD模块中的模型,是ModuleCRUD模块中模型的基类。未与表格关联。定义了一些公共的Model默认属性,以及一些静态的数据库操作方法,比如拉取数据库表字段列表;
    • app/config/crud/admin.php:CoreCRUD模块中的默认crud参数配置文件,但ModuleCRUD模块中的配置文件未定义某些参数时,将使用admin.php中的默认参数;
    • app/views/admin/core/list.blade.php:CoreCRUD模块中的列表视图文件,用来呈现数据列表;
    • app/views/admin/core/form.blade.php:CoreCRUD模块中的数据记录编辑视图文件,用来呈现数据编辑的表单。

    代码文件如下

      1 <?php
      2 
      3 namespace admin;
      4 
      5 use IlluminateSupportFacadesRequest;
      6 use IlluminateSupportFacadesResponse;
      7 use IlluminateSupportFacadesRedirect;
      8 use IlluminateSupportFacadesInput;
      9 use IlluminateSupportFacadesConfig;
     10 use IlluminateSupportFacadesView;
     11 use IlluminateSupportFacadesCache;
     12 use IlluminateSupportFacadesURL;
     13 use SiteHelpers;
     14 use Module;
     15 
     16 class AdminController extends BaseController
     17 {
     18     protected $layout = 'layouts.admin.main';
     19 
     20     /**
     21      * AdminController constructor.
     22      */
     23     public function __construct()
     24     {
     25         parent::__construct();
     26         View::share('stdName',$this->getStdName());
     27         View::share('reducName',SiteHelpers::reducCase($this->getStdName()));
     28         View::share('routeParams',$this->getRouteParams());
     29         if($this->model){
     30             $this->assignModel($this->model);
     31         }
     32         View::share('config',$this->savedConfig);
     33         if(!Cache::has('modules')){
     34             Cache::forever('modules',Module::all());
     35         }
     36     }
     37 
     38     public function getList(){
     39         $models = $this->paginateModels();
     40         $view = $this->getRouteParam('c').'._list';
     41         if(!View::exists($view)){
     42             $view = 'admin.core.list';
     43         }
     44         if(Request::ajax() || Input::has('isAjax')){
     45             return Response::json(array(
     46                'success'    =>  true,
     47                 'data'      =>  array(
     48                     'models'    =>  $models->toArray()
     49                 )
     50             ));
     51         }else{
     52             $this->makeView(array(
     53                 'models'  =>  $models,
     54                 $this->getStdName().'s' =>  $models,
     55             ),$view);
     56         }
     57     }
     58 
     59     public function getEdit($id = null)
     60     {
     61         if($id){
     62             $this->model = $this->model->find($id);
     63         }
     64         $data = array(
     65             $this->model->getKeyName() => $id,
     66             'model'   =>  $this->model,
     67             $this->modelName=>$this->model,
     68         );
     69         $this->beforeEdit($data);
     70 
     71         $view = $this->getRouteParam('c').'._form';
     72         if(!View::exists($view)){
     73             $view = 'admin.core.form';
     74         }
     75         $this->makeView($data,$view);
     76     }
     77 
     78     protected function config(){
     79         $config = 'crud/'.$this->getStdName();
     80         if(!file_exists(app_path('config/').$config.'.php')){
     81             $config = 'crud/admin';
     82         }
     83         return Config::get($config);
     84     }
     85 
     86     protected function assignModel($model)
     87     {
     88         $this->model = $model;
     89 
     90         $config = $this->config();
     91         $defaultConfig = Config::get('crud/admin');
     92         $relations = array();
     93         /* 将默认参数传递给module config */
     94         foreach($config['fields']   as $field => &$fieldConfig){
     95 //            if(isset($fieldConfig['value'])){
     96 //                $this->model->$field = $fieldConfig['value'];
     97 //            }
     98             $fieldConfig['form'] = array_merge(
     99                 $defaultConfig['fields']['field_name']['form'],
    100                 isset($fieldConfig['form']) ? $fieldConfig['form'] : array()
    101             );
    102             $fieldConfig['list'] = array_merge(
    103                 $defaultConfig['fields']['field_name']['list'],
    104                 isset($fieldConfig['list']) ? $fieldConfig['list'] : array()
    105             );
    106             if(isset($fieldConfig['relation']) &&
    107                 isset($fieldConfig['relation']['type']) && $fieldConfig['relation']['type'] !== '' ){
    108                 $relations[$field] = $fieldConfig['relation'];
    109             }
    110         }
    111 
    112         $config['list_options'] = array_merge(
    113             $defaultConfig['list_options'],
    114             isset($config['list_options']) ? $config['list_options'] : array()
    115         );
    116 
    117         $config['form_options'] = array_merge(
    118             $defaultConfig['form_options'],
    119             isset($config['form_options']) ? $config['form_options'] : array()
    120         );
    121 
    122         /* 将字段的relation汇聚出来,是为了后面的代码方便,同时减少循环 */
    123         $config['relations'] = $relations;
    124 
    125         $this->savedConfig = $config;
    126     }
    127 
    128     protected function paginateModels()
    129     {
    130         $models = array();
    131         if($this->model){
    132             $query = $this->model->newQuery();
    133             $this->handleListQuery($query);
    134             $selects = array($this->model->getTable().'.*');
    135 
    136             foreach($this->savedConfig['relations'] as $field=>&$params){
    137                 $query->join($params['table'],$params['table'].'.'.$params['foreign_key'],'=',$this->model->getTable().'.'.$field);
    138                 if(!isset($params['as'])){
    139                     $params['as'] = $params['table'].'_'.$params['show'];
    140                 }
    141                 $selects[] = $params['table'].'.'.$params['show'] . ' as '.$params['as'];
    142             }
    143             $query->select($selects);
    144             $orderBy = Input::get('list_order_by');
    145             if( $orderBy){
    146                 $query->orderBy($this->model->getTable().'.'.$orderBy,Input::get('list_sort_asc') ? 'asc' : 'desc');
    147             }else{
    148                 $query->orderBy($this->model->getTable().'.'.$this->model->getKeyName(),'desc');
    149             }
    150             $page = Input::has('_page') ? Input::get('_page') : 10;
    151             $models = $query->paginate($page);
    152         }
    153         return $models;
    154     }
    155 
    156 
    157     public function postEdit($primaryKeyValue = null){
    158 
    159         $primaryKeyName = $this->model->getKeyName();
    160         if($primaryKeyValue == null){
    161             $primaryKeyValue = Input::get($primaryKeyName);
    162         }
    163 
    164         $fields = $this->savedConfig['fields'];
    165         $datas = array();
    166         foreach($fields as $field => $fieldConfig){
    167             if(Input::has($field)){
    168                 $datas[$field] = Input::get($field);
    169             }
    170         }
    171 
    172         if($primaryKeyValue){
    173             $this->model->where($primaryKeyName,$primaryKeyValue)->update($datas);
    174         }else{
    175             $this->model->fill($datas);
    176             $this->model->save();
    177         }
    178         $this->afterSave($this->model);
    179         $resp = Redirect::action(get_class($this).'@getList')->withMessage('save success!');
    180 
    181         return $resp;
    182     }
    183 
    184     public function getIndex()
    185     {
    186         $this->makeView(null,'admin.index');
    187     }
    188 
    189     public function postDelete(){
    190         $ids = explode(',',Input::get('ids'));
    191         $data = array();
    192         $success   =  true;
    193         $data['ids'] = $ids;
    194         $ids = array_filter($ids,function($id){
    195             return $id;
    196         });
    197         $this->model->whereIn($this->model->getKeyName(),$ids)->delete();
    198         $data['redirect_url'] = URL::to(action(get_class($this).'@getList'));
    199         return Response::json(array(
    200             'success'   =>  $success,
    201             'data'      =>  $data,
    202         ));
    203     }
    204 
    205     public function getDelete($id){
    206         $this->beforeDelete($id);
    207         $this->model->where($this->model->getKeyName(),$id)->delete();
    208         return Redirect::action(get_class($this).'@getList');
    209     }
    210 
    211     public function missingMethod($parameters = array())
    212     {
    213         //
    214         $this->makeView(null,'site.404');
    215     }
    216 
    217     protected function handleListQuery(&$query)
    218     {
    219         $searchFields = array_intersect_key($this->savedConfig['fields'],Input::all());
    220         foreach($searchFields as $field=> $fieldConfig){
    221             if(isset($fieldConfig['list']['search'])){
    222                 $value = Input::get($field);
    223                 $operator = $fieldConfig['list']['search'];
    224                 if($value !== ''){
    225                     if($operator){
    226                         if($operator === 'like'){
    227                             $value = '%'.$value.'%';
    228                         }
    229                         $query = $query->where($this->model->getTable().'.'.$field,$operator,$value);
    230                     }else{
    231                         $query = $query->where($this->model->getTable().'.'.$field,$value);
    232                     }
    233                 }
    234             }
    235         }
    236 
    237     }
    238 
    239     protected function beforeDelete($id)
    240     {
    241 
    242     }
    243 
    244     protected function beforeEdit(&$data)
    245     {
    246 
    247     }
    248 
    249     protected function afterSave($model)
    250     {
    251 
    252     }
    253 
    254     public function getHelp(){
    255         $this->makeView(null,'admin.help');
    256     }
    257 
    258 }
    AdminController
      1 <?php
      2 
      3 /**
      4  * Created by PhpStorm.
      5  * User: Administrator
      6  * Date: 2015/10/10 0010
      7  * Time: 17:58
      8  */
      9 class BaseModel extends Eloquent
     10 {
     11     protected $table = '';
     12     protected $guarded = array('id');
     13     public $timestamps = false;
     14     public static function getTranslates($translate){
     15         $rows = DB::table($translate['table'])->select(array($translate['foreign_key'],$translate['show']))->get();
     16         return $rows;
     17     }
     18 
     19 
     20     static function getTableList( $db ,$connection = null)
     21     {
     22         $t = array();
     23         $dbname = 'Tables_in_'.$db ;
     24         $tables = $connection ? DB::connection($connection)->select("SHOW TABLES FROM {$db}") : DB::select("SHOW TABLES FROM {$db}");
     25         foreach($tables as $table)
     26         {
     27             $t[$table->$dbname] = $table->$dbname;
     28         }
     29         return $t;
     30     }
     31 
     32     static function getTableColumns( $table,$connection  = false)
     33     {
     34 //        $columns = array();
     35         $sql  = "SHOW COLUMNS FROM $table";
     36         $rawColumns = $connection ? DB::connection($connection)->select($sql)
     37             : DB::select($sql);
     38 //        foreach($rawColumns as $column)
     39 //            $columns[$column->Field] = $column->Field;
     40         return $rawColumns;
     41     }
     42 
     43     function getColoumnInfo( $result )
     44     {
     45         $pdo = DB::getPdo();
     46         $res = $pdo->query($result);
     47         $i =0;    $coll=array();
     48         while ($i < $res->columnCount())
     49         {
     50             $info = $res->getColumnMeta($i);
     51             $coll[] = $info;
     52             $i++;
     53         }
     54         return $coll;
     55 
     56     }
     57 
     58     function builColumnInfo( $statement )
     59     {
     60         $driver         = Config::get('database.default');
     61         $database         = Config::get('database.connections');
     62         $db         = $database[$driver]['database'];
     63         $dbuser     = $database[$driver]['username'];
     64         $dbpass     = $database[$driver]['password'];
     65         $dbhost     = $database[$driver]['host'];
     66 
     67         $data = array();
     68         $mysqli = new mysqli($dbhost,$dbuser,$dbpass,$db);
     69         if ($result = $mysqli->query($statement)) {
     70 
     71             /* Get field information for all columns */
     72             while ($finfo = $result->fetch_field()) {
     73                 $data[] = (object) array(
     74                     'Field'    => $finfo->name,
     75                     'Table'    => $finfo->table,
     76                     'Type'    => $finfo->type
     77                 );
     78             }
     79             $result->close();
     80         }
     81 
     82         $mysqli->close();
     83         return $data;
     84 
     85     }
     86 
     87     static function findPrimarykey( $table, $db = null)
     88     {
     89         $query = "SHOW KEYS FROM `{$table}` WHERE Key_name = 'PRIMARY'";
     90         $primaryKey = '';
     91         $keys = $db ? DB::connection($db)->select($query) : DB::select($query);
     92 
     93         foreach($keys as $key)
     94         {
     95             $primaryKey = $key->Column_name;
     96         }
     97 
     98         return $primaryKey;
     99     }
    100 }
    BaseModel
      1 <?php
      2 $formOption = $config['form_options'];
      3 $layout = $formOption['layout'];
      4 $labelCols = $layout['label_cols'];
      5 $inputCols = $layout['input_cols'];
      6 $labelCss = "col-sm-$labelCols";
      7 $inputCss = "col-sm-$inputCols";
      8 //  插件是否加载
      9 $loadUE = false;
     10 $loadSBox = false;
     11 $loadDatePicker = false;
     12 ?>
     13 <div class="panel panel-primary">
     14     <div class="panel-heading">
     15         <h3 class="panel-title">@if($model->id)  编辑<code>#{{$model->id}}</code>@else 新建  @endif</h3>
     16     </div>
     17     <div class="panel-body">
     18         <div class="row">
     19             <div class="col-sm-{{$layout['cols']}} col-sm-offset-{{(12-$layout['cols'])/2}}">
     20                 <form class="form-horizontal validate" action="{{URL::to('admin/'.$stdName.'/edit')}}" method="post">
     21                     <input type="hidden" name="{{$model->getKeyName()}}" value="{{$model->getKey()}}">
     22                     <?php foreach($config['fields'] as $field => $settings):?>
     23                     <?php
     24                     if ($field === $model->getKeyName() || !$settings['form']['show']) continue;
     25                     $type = $settings['form']['type'];
     26                     $rule = $settings['form']['rule'];
     27                     ?>
     28                     <?php if($settings['form']['type'] === 'hidden'):?>
     29                     <input type="hidden" name="{{$field}}" value="{{$model->$field}}">
     30                     <?php continue;?>
     31                     <?php endif; ?>
     32                     <div class="form-group">
     33                         <label for="{{$field}}"
     34                                class="{{$labelCss}} control-label">{{isset($settings['label']) ? $settings['label'] : strtoupper($field)}}</label>
     35                         <div class="{{$inputCss}}">
     36                             <?php if($type === 'textarea'):?>
     37                             <textarea name="{{$field}}" id="{{$field}}" rows="10"
     38                                       {{SiteHelpers::inputValidate($rule)}}
     39                                       class="form-control">{{$model->$field}}</textarea>
     40                             <?php elseif($type === 'select'):?>
     41                             <?php $loadSBox = true;?>
     42                             <select name="{{$field}}" id="{{$field}}" class="selectboxit" {{SiteHelpers::inputValidate($rule)}}>
     43                                 @if (isset($settings['form']['options']))
     44                                     @if(is_array($settings['form']['options']))
     45                                         @foreach($settings['form']['options'] as $value => $text)
     46                                             <option value="{{$value}}"
     47                                                     @if($value == $model->$field) selected @endif>{{$text}}</option>
     48                                         @endforeach
     49                                     @elseif(is_string($settings['form']['options']))
     50                                         @foreach($$settings['form']['options'] as $value => $text)
     51                                             <option value="{{$value}}">{{$value}}</option>
     52                                         @endforeach
     53                                     @endif
     54                                 @elseif(isset($settings['relation']['type']) && $settings['relation']['type'] )
     55                                     <?php
     56                                     $fieldTranslate = $config['relations'][$field];
     57                                     $options = BaseModel::getTranslates($fieldTranslate);
     58                                     foreach($options as $option):
     59                                     ?>
     60                                     <option value="<?=$option->$fieldTranslate['foreign_key']?>"
     61                                             @if($option->$fieldTranslate['foreign_key'] == $model->$field) selected @endif
     62                                     ><?=$option->$fieldTranslate['show']?>
     63                                     </option>
     64                                     <?php endforeach;?>
     65                                 @endif
     66                             </select>
     67                             <?php elseif($type === 'wysiwyg'):?>
     68                             <?php
     69                             $loadUE = true;
     70                             ?>
     71                             <script type="text/plain" name="{{$field}}" id="wysiwyg-edit"
     72                                     style="100%;height:240px;">{{$model->$field}}</script>
     73                             <?php elseif ($type === 'radio' || $type === 'checkbox'): ?>
     74 
     75                             @if(isset($settings['form']['options']))
     76                                 @foreach($settings['form']['options'] as $option => $text)
     77                                     <div class="{{$type}} {{$type}}-replace">
     78                                         <input type="{{$type}}" value="{{$option}}" name="{{$field}}"
     79                                                id="{{$field}}" @if($model->field === $option) checked @endif>
     80                                         <label>{{$text}}</label>
     81                                     </div>
     82                                 @endforeach
     83                             @endif
     84                             <?php elseif($type === 'date'):?>
     85                             <?php $loadDatePicker = true;?>
     86                             <div class="input-group">
     87                                 <input type="text" name="{{$field}}" id="{{$field}}" class="form-control datepicker" data-format="yyyy-MM-dd" {{SiteHelpers::inputValidate($rule)}}>
     88                                 <div class="input-group-addon">
     89                                     <a href="#"><i class="entypo-calendar"></i></a>
     90                                 </div>
     91                             </div>
     92                             <?php elseif($type === 'password'):?>
     93                             <input type="password" class="form-control" name="{{$field}}" id="{{$field}}"
     94                                    value="{{$model->$field}}"
     95                                     {{SiteHelpers::inputValidate($rule)}}
     96                             >
     97                             <?php elseif($type === 'file'):?>
     98                             <input type="file"
     99                                    class="form-control file2 inline btn btn-primary"
    100                                    data-label="<i class='glyphicon glyphicon-file'></i> 选择文件" >
    101                             <?php else:?>
    102                             <input type="text" class="form-control" name="{{$field}}" id="{{$field}}"
    103                                    {{SiteHelpers::inputMask($rule)}}
    104                                    {{SiteHelpers::inputValidate($rule)}}
    105                                    value="{{$model->$field}}">
    106                             <?php endif;?>
    107                         </div>
    108                     </div>
    109                     <?php endforeach;?>
    110                     <div class="form-group">
    111                         <div class="{{$inputCss}} col-sm-offset-{{$labelCols}}">
    112                             <button type="submit" class="btn btn-primary">保存</button>
    113                         </div>
    114                     </div>
    115                 </form>
    116             </div>
    117         </div>
    118     </div>
    119 </div>
    120 @yield('form.bottom','')
    121 @section('styles')
    122     @if($loadSBox)
    123         {{HTML::style('assets/js/selectboxit/jquery.selectBoxIt.css')}}
    124     @endif
    125 @append
    126 @section('scripts')
    127     <?php if($loadUE):?>
    128     {{HTML::script('plugins/ue-utf8-php/ueditor.config.js')}}
    129     {{HTML::script('plugins/ue-utf8-php/ueditor.all.min.js')}}
    130     {{HTML::script('plugins/ue-utf8-php/lang/zh-cn/zh-cn.js')}}
    131     <?php endif;?>
    132     @if($loadSBox)
    133         {{HTML::script('assets/js/selectboxit/jquery.selectBoxIt.min.js')}}
    134     @endif
    135     @if($loadDatePicker)
    136         {{HTML::script('assets/js/bootstrap-datepicker.js')}}
    137     @endif
    138     {{HTML::script('assets/js/jquery.inputmask.bundle.min.js')}}
    139     {{HTML::script('assets/js/jquery.validate.min.js')}}
    140 @append
    141 
    142 @section('footScript')
    143     <script>
    144         var ue = null,
    145                 ueId = 'wysiwyg-edit';
    146         if (document.getElementById(ueId)) {
    147             ue = UE.getEditor(ueId);
    148         }
    149     </script>
    150 @append
    app/views/admin/core/form.blade.php
      1 @section('headStyle')
      2     <style>
      3         .sort.sort-active {
      4             color: #000;
      5             font-weight: bold;
      6         }
      7     </style>
      8 @append
      9 <?php
     10 $list_options = $config['list_options'];
     11 $loadSBox = false;
     12 $loadDatePicker = false;
     13 ?>
     14 <div class="panel panel-default">
     15     <div class="panel-heading">
     16         <h3 class="panel-title"><?=isset($navMap[$stdName]['text']) ? $navMap[$stdName]['text'] : strtoupper($stdName)?>
     17             列表</h3>
     18     </div>
     19     <div class="panel-body">
     20         <div class="row">
     21             <div class="col-sm-12">
     22                 <div class="btn-group btn-group-sm" role="group">
     23                     @if($list_options['create'])
     24                         <a href="{{URL::to('admin/'.$stdName.'/edit')}}" class="btn btn-primary">新建</a>
     25                     @endif
     26                     <a class="btn btn-danger delete-selected">删除</a>
     27                     <a class="btn btn-default">导出</a>
     28                 </div>
     29             </div>
     30         </div>
     31         <br>
     32         <form class="list-form" action="" method="get">
     33             <input type="hidden" name="list_sort_asc" value="{{Input::get('list_sort_asc') !== null ? Input::get('list_sort_asc') : 1}}">
     34             <input type="hidden" name="list_order_by" value="">
     35             <table class="table table-bordered responsive table-hover table-striped">
     36                 <thead>
     37                 <tr>
     38                     <th>
     39                         <div class="checkbox checkbox-replace">
     40                             <input type="checkbox" class="item-all">
     41                         </div>
     42                     </th>
     43                     <?php foreach($config['fields'] as $field=>$settings):?>
     44                     <?php if($settings['list']['show']):?>
     45                     <th @if($settings['list']['sort'])
     46                         class="sort @if($field === Input::get('list_order_by')) sort-active @endif"
     47                         data-field="{{$field}}" @endif
     48                     ><?=is_array($settings) && isset($settings['label']) ? $settings['label'] : strtoupper($field)?>
     49                         <span class="pull-right">
     50                             @if($field === Input::get('list_order_by'))  @if(Input::get('list_sort_asc') == 1) <i class="fa fa-sort-asc"></i> @else <i class="fa fa-sort-desc"></i>  @endif @endif
     51                         </span>
     52                     </th>
     53                     <?php endif;?>
     54                     <?php endforeach;?>
     55                     <th>操作</th>
     56                 </tr>
     57                 </thead>
     58                 <tbody>
     59                 <tr>
     60                     <td></td>
     61                     @foreach($config['fields'] as $field=>$fieldConfig)
     62                         @if($fieldConfig['list']['show'])
     63                             @if(isset($fieldConfig['list']['search']) && $fieldConfig['list']['search'] !== false)
     64                                 <td>
     65                                     @if($fieldConfig['form']['type'] == 'select' || ($fieldConfig['form']['type'] === 'radio' || $fieldConfig['form']['type'] == 'checkbox'))
     66                                         <?php $loadSBox = true;?>
     67                                         @if(isset($fieldConfig['form']['options']) && $fieldConfig['form']['options'])
     68                                             <select name="{{$field}}" id="{{$field}}" class="selectboxit">
     69                                                 <option value="" class="default-value">请选择</option>
     70                                                 @foreach($fieldConfig['form']['options'] as $option => $text)
     71                                                     <option value="{{$option}}" @if(Input::get($field) && Input::get($field) === $option) selected @endif>{{$text}}</option>
     72                                                 @endforeach
     73                                             </select>
     74                                         @elseif(isset($fieldConfig['relation']['type']) && $fieldConfig['relation']['type'] )
     75                                             {{View::make('components.relation_select',array(
     76                                             'fieldConfig'=>$fieldConfig,'field' =>  $field, ))}}
     77                                         @endif
     78                                     @elseif($fieldConfig['form']['type'] === 'date')
     79                                         <?php $loadDatePicker = true;?>
     80                                         <input type="text" name="{{$field}}" id="{{$field}}" class="form-control datepicker" data-format="yyyy-MM-dd" value="{{Input::get($field)}}">
     81                                     @else
     82                                         <input type="text" name="{{$field}}" id="{{$field}}"
     83                                                value="{{Input::get($field)}}" class="form-control input-sm">
     84                                     @endif
     85                                 </td>
     86                             @else
     87                                 <td></td>
     88                             @endif
     89                         @endif
     90                     @endforeach
     91                     <td>
     92                         <div class="btn-group btn-group-sm" role="group">
     93                             <button type="submit" class="btn btn-primary">搜索</button>
     94                             <button type="reset" onclick="resetForm(this)" class="btn btn-warning hidden">重置</button>
     95                         </div>
     96                     </td>
     97                 </tr>
     98                 <?php foreach($models as  $model):?>
     99                 <tr>
    100                     <td width="18px">
    101                         <div class="checkbox checkbox-replace">
    102                             <input type="checkbox" name="d_delete_select" class="item" value="{{$model->id}}">
    103                         </div>
    104                     </td>
    105                     <?php foreach($config['fields'] as $filed=>$settings):?>
    106                     <?php if($settings['list']['show']):?>
    107                     <?php
    108                     $value = $model->$filed;
    109                     /* 字段在列表中需要翻译 */
    110                     if (array_key_exists($filed, $config['relations'])) {
    111                         $value = $model->$config['relations'][$filed]['as'];
    112                     }
    113                     ?>
    114                     <td>{{$value}}</td>
    115                     <?php endif;?>
    116                     <?php endforeach;?>
    117                     <td>
    118                         <div class="btn-group btn-group-sm" role="group">
    119                             @if($list_options['update'])
    120                                 <a href="{{URL::to('admin/'.$stdName.'/edit/'.$model->id)}}"
    121                                    class="btn btn-primary">编辑</a>
    122                             @endif
    123                             @if($list_options['delete'])
    124                                 <a href="{{URL::to('admin/'.$stdName.'/delete/'.$model->id)}}"
    125                                    class="btn btn-danger">删除</a>
    126                             @endif
    127                             @if(View::exists('admin.'.snake_case($stdName).'.list_item_links'))
    128                                 @include('admin.'.snake_case($stdName).'.list_item_links',array('model'=>$model))
    129                             @endif
    130                         </div>
    131                     </td>
    132                 </tr>
    133                 <?php endforeach;?>
    134                 </tbody>
    135             </table>
    136         </form>
    137         <div class="pull-right">
    138             {{$models->appends(Input::all())->links()}}
    139         </div>
    140     </div>
    141 </div>
    142 
    143 @section('styles')
    144     {{HTML::style('assets/js/datatables/responsive/css/datatables.responsive.css')}}
    145 
    146     @if($loadSBox)
    147         {{HTML::style('assets/js/selectboxit/jquery.selectBoxIt.css')}}
    148     @endif
    149 @append
    150 
    151 @section('scripts')
    152     {{HTML::script('assets/js/jquery.dataTables.min.js')}}
    153     {{HTML::script('assets/js/datatables/jquery.dataTables.columnFilter.js')}}
    154     @if($loadSBox)
    155         {{HTML::script('assets/js/selectboxit/jquery.selectBoxIt.min.js')}}
    156     @endif
    157     @if($loadDatePicker)
    158         {{HTML::script('assets/js/bootstrap-datepicker.js')}}
    159     @endif
    160 @append
    161 
    162 @section('footScript')
    163     <script>
    164         $(document).ready(function(){
    165             $('th.sort').click(function(){
    166                 var $th = $(this);
    167                 $('input[name="list_order_by"]').val($th.data('field'));
    168                 $('input[name="list_sort_asc"]').val($th.find('i').hasClass('fa-sort-asc') ? 0 : 1);
    169                 $('form.list-form').submit();
    170             });
    171 
    172             $('input.item-all').change(function(){
    173                 var $this = $(this),
    174                         $items = $('input.item');
    175                 if($this.is(':checked')){
    176                     $items.prop('checked','checked');
    177                 }else{
    178                     $items.removeProp('checked');
    179                 }
    180                 $items.trigger('change');
    181             });
    182 
    183 
    184             $('a.delete-selected').click(function(){
    185                 var ids = [],
    186                         $items = $('input.item:checked');
    187                 $items.each(function(i){
    188                     ids.push($(this).val());
    189                 });
    190                 var idsStr = ids.join(',');
    191                 confirmModal({
    192                     message  :   '确认删除:'+idsStr,
    193                     onOk:   function(){
    194                         $.post('{{URL::to('admin/'.snake_case($stdName).'/delete')}}',{"ids":idsStr},function(resp){
    195                             if(resp.success){
    196                                 window.location.href = resp.data.redirect_url;
    197                             }
    198                         },'json');
    199                     }
    200                 });
    201                 return false;
    202             });
    203         });
    204     </script>
    205 @append
    206 
    207 @section('modals')
    208     <div class="modal fade" id="confirm-modal" data-backdrop="static">
    209         <div class="modal-dialog">
    210             <div class="modal-content">
    211                 <div class="modal-header">
    212                     <h4 class="modal-title">操作确认</h4>
    213                 </div>
    214                 <div class="modal-body">
    215 
    216                 </div>
    217                 <div class="modal-footer">
    218                     <button type="button" class="btn btn-default cancel" data-dismiss="modal">取消</button>
    219                     <button type="button" class="btn btn-info ok">确认</button>
    220                 </div>
    221             </div>
    222         </div>
    223     </div>
    224 @stop
    app/views/admin/core/list.blade.php
     1 <?php
     2 /**
     3  * 说明:
     4  * 1. 以下配置项,不设置便是默认
     5  * Created by PhpStorm.
     6  * User: lvyahui
     7  * Date: 2016/5/2
     8  * Time: 12:33
     9  */
    10 
    11 return array(
    12 
    13     /**
    14      * 所有字段配置
    15      */
    16     'fields'    =>  array(
    17         'field_name' =>  array(
    18             /* 显示在列表表格的表头的内容,和form控件旁边的内容*/
    19             'label' =>  '字段中文名',
    20             /* 字段缺省值 */
    21             'value' =>  false,
    22             /* 针对表单的设置 */
    23             'form'  =>  array(
    24                 'show'  =>  true,
    25                 'hidden'    =>  false,
    26                 /*
    27                  * 字段对应表单的控件类型,默认text,
    28                  * 还支持常用的控件类型
    29                  * textarea
    30                  * radio
    31                  * checkbox
    32                  * number
    33                  * ipaddr
    34                  * wyswyg
    35                  * select
    36                  * date
    37                  * file
    38                  * 以及自定义类型
    39                  * */
    40                 'type'  =>  'text',
    41                 /*
    42                 'type'  =>  array(
    43                     'select'    =>  array(
    44                         'options'   =>  function(){
    45                             return array();
    46                         }
    47                     ),
    48                 ),
    49                 'type'  =>  array(
    50                     'radio'     =>  array(),
    51                 ),
    52                 */
    53                 /* 提交表单后的验证规则 */
    54                 'rule'  =>  'required',
    55                 'ajax_validate' =>  false,
    56                 'placeholder'   =>  'xx',
    57 
    58             ),
    59             // 针对列表的设置
    60             'list'  =>  array(
    61                 /* 字段在列表是否显示,默认为显示 */
    62                 'show'  =>  true,
    63                 /* 字段是否可以排序,默认不能排序 */
    64                 'sort'  =>  true,
    65                 /* 是否能够按这个字段搜索 */
    66                 'search'    =>  true,
    67                 /* 字段进行翻译,比如栏目Id字段,一般要转成栏目名称显示 */
    68                 'lookup'    =>  false,
    69             ),
    70 
    71         ),
    72         // more fields
    73     ),
    74 
    75     /**
    76      * 全局form配置,优先级小于字段配置
    77      */
    78     'form_options'  =>  array(
    79         'layout'    =>  array(
    80             'cols'  =>  12,
    81             'label_cols' =>  1,
    82             'input_cols' =>  11,
    83         ),
    84     ),
    85 
    86     /**
    87      * 全局list配置,优先级小于字段配置
    88      */
    89     'list_options'  =>  array(
    90         'page'      =>  10,
    91         'create'    =>  true,
    92         'update'    =>  true,
    93         'delete'    =>  true,
    94     ),
    95 
    96 );
    app/config/crud/admin.php

    4.5 GModule 管理模块实现

    GModule是一类由DBuilder生成的模块,它有一组模板定义在app/template目录下:

    • app/template/_form.tpl
    • app/template/_list.tpl
    • app/template/controller.tpl
    • app/template/model.tpl

    前面设计中指出,GModule管理模块本身是一个名为“Module”,主表为d_module,且手工建立的GModule,故其代码组成也是符合GModule规范的,笔者编写的代码主要为扩展代码。GModule管理模块对应了下述代码文件:

    • app/controllers/admin/ModuleController.php:控制器(Controller)代码,其实现CoreCRUD模块的接口,以及扩展的url接口;
    • app/models/Module.php:GModule管理模块的模型;
    • app/views/admin/module/_form.blade.php: FORM视图代码,其在原有的CoreCRUD 模块的FORM表单下部,扩展了一组Tab,其中第一个Tab中显示了所有字段的详细配置,通过以上扩展就能实现在CoreCRUD生成的Form表单页面中对GModule进行配置;
    • app/views/admin/module/_list.blade.php: LIST视图代码;
    • app/views/admin/module/fields_config.blade.php:字段配置表格视图代码;
    • app/views/admin/module/list_item_links.blade.php:扩展链接视图代码;
    • app/config/crud/module.php:GModule Configuration文件。

    下面贴上主要的代码文件ModuleController.php

      1 <?php
      2 /**
      3  * Created by PhpStorm.
      4  * User: lvyahui
      5  * Date: 2016/5/12
      6  * Time: 15:28
      7  */
      8 
      9 namespace admin;
     10 
     11 define('MODULE_ROUTES', json_encode(include(app_path() . '/module_routes.php')));
     12 
     13 use IlluminateSupportFacadesRedirect;
     14 use SiteHelpers;
     15 use BaseModel;
     16 use Module;
     17 use ConfigUtils;
     18 use IlluminateSupportFacadesInput;
     19 use IlluminateSupportFacadesConfig;
     20 use IlluminateSupportFacadesResponse;
     21 
     22 class ModuleController extends AdminController
     23 {
     24 
     25     protected function beforeEdit(&$data)
     26     {
     27         $data ['dataSources'] = SiteHelpers::loadDataSources();
     28         if ($data['model']->id) {
     29             $data ['moduleConf'] = ConfigUtils::get($data['model']->name);
     30         }
     31     }
     32 
     33     protected function afterSave($module)
     34     {
     35         /* 生成代码文件 */
     36         $codes = array(
     37             'moduleName'      => $module->name,
     38             'moduleTitle'     => $module->title,
     39             'tablePrimaryKey' => BaseModel::findPrimarykey($module->db_table, $module->db_source),
     40             'moduleNote'      => $module->note,
     41             'date'            => date('Y-m-d'),
     42             'dbSource'        => $module->db_source,
     43             'dbTable'         => $module->db_table,
     44         );
     45         $this->removeFiles($codes['moduleName']);
     46         /* 生成默认module Configuration*/
     47         $moduleConfs = $this->buildConfiguration($module->db_table, $module->db_source);
     48         SiteHelpers::saveArrayToFile(app_path('config/crud/') . snake_case($codes['moduleName']) . '.php', $moduleConfs);
     49 
     50         $controller = file_get_contents(app_path('template') . '/controller.tpl');
     51         $model = file_get_contents(app_path('template') . '/model.tpl');
     52         $formView = file_get_contents(app_path('template') . '/_form.tpl');
     53         $listView = file_get_contents(app_path('template') . '/_list.tpl');
     54         $codes['timestamps'] = isset($moduleConfs['fields']['created_at']) && isset($moduleConfs['fields']['updated_at'])
     55                                 ? 'true' : 'false';
     56         $buildController = SiteHelpers::blend($controller, $codes);
     57         $buildModel = SiteHelpers::blend($model, $codes);
     58         /* 生成 MVC 文件*/
     59         file_put_contents(app_path() . "/controllers/admin/{$codes['moduleName']}Controller.php", $buildController);
     60         file_put_contents(app_path() . "/models/{$codes['moduleName']}.php", $buildModel);
     61         $viewPath = app_path('/views/admin/') . snake_case($codes['moduleName']);
     62         if (!file_exists($viewPath)) mkdir($viewPath);
     63         file_put_contents($viewPath . "/_form.blade.php", $formView);
     64         file_put_contents($viewPath . "/_list.blade.php", $listView);
     65 
     66 
     67         /* 更新路由 */
     68         $moduleRoutes = json_decode(MODULE_ROUTES, true); //require(app_path().'/module_routes.php');
     69         if (is_array($moduleRoutes)) {
     70             $moduleRoutes[SiteHelpers::reducCase($codes['moduleName'])] = 'admin\' . "{$codes['moduleName']}Controller";
     71             SiteHelpers::saveArrayToFile(app_path() . '/module_routes.php', $moduleRoutes);
     72         }
     73     }
     74 
     75     protected function beforeDelete($id)
     76     {
     77         $module = Module::find($id);
     78         $moduleName = $module->name;
     79         $this->removeFiles($moduleName);
     80     }
     81 
     82     /**
     83      * 删除GModule相关文件文件
     84      * @param $moduleName
     85      */
     86     public function removeFiles($moduleName)
     87     {
     88         $controller = app_path('admin/controllers') . "/{$moduleName}Controller.php";
     89         if (file_exists($controller)) {
     90             unlink($controller);
     91         }
     92         $model = app_path('models') . "/{$moduleName}.php";
     93         if (file_exists($model)) {
     94             unlink($model);
     95         }
     96         $moduleConf = app_path('config/crud/') . snake_case($moduleName) . '.php';
     97         if (file_exists($moduleConf)) {
     98             unlink($moduleConf);
     99         }
    100 
    101         $viewPath = app_path('/views/admin/') . snake_case($moduleName);
    102         $formFile = $viewPath . '/_form.blade.php';
    103         $listFile = $viewPath . '/_list.blade.php';
    104         if (file_exists($formFile)) unlink($formFile);
    105         if (file_exists($listFile)) unlink($listFile);
    106     }
    107 
    108     private function buildConfiguration($table, $connection)
    109     {
    110         $rawColumns = BaseModel::getTableColumns($table, $connection);
    111         $fields = ConfigUtils::build($rawColumns);
    112         return array(
    113             'data_source' => $connection,
    114             'table'       => $table,
    115             'fields'      => $fields,
    116         );
    117     }
    118 
    119     /**
    120      * 获取字段配置列表
    121      * @return bool
    122      */
    123     public function getFieldsConfig()
    124     {
    125         $filedsConfig = null;
    126         if (Input::has('module_name')) {
    127             $filedsConfig = ConfigUtils::get(Input::get('module_name'))['fields'];
    128         } else {
    129             $table = Input::get('table');
    130             $connection = Input::get('connection');
    131 
    132             $filedsConfig = $this->buildConfiguration($table, $connection)['fields'];
    133         }
    134 
    135         $resp = $this->makeView(array(
    136             'fieldsConfig' => $filedsConfig,
    137         ));
    138         if ($resp) {
    139             return $resp;
    140         }
    141     }
    142 
    143 
    144     /**
    145      * 保存字段列表配置
    146      * @return mixed
    147      */
    148     public function postSaveFieldsConf()
    149     {
    150         $resp = Redirect::action(get_class($this) . '@getEdit', Input::get('id'));
    151         $postFields = Input::get('fields');
    152         $moduleName = Input::get('module_key');
    153         $confKey = SiteHelpers::reducCase($moduleName);
    154         $savedConfig = ConfigUtils::get($confKey);
    155         foreach ($savedConfig['fields'] as $fieldName => &$savefield) {
    156             $postField = $postFields[$fieldName];
    157             $savefield['label'] = $postField['label'];
    158             $savefield['form']['show'] = isset($postField['form']['show']);
    159             $savefield['list']['show'] = isset($postField['list']['show']);
    160         }
    161         ConfigUtils::saveGModuleConf($confKey, $savedConfig);
    162         return $resp;
    163     }
    164 
    165     /**
    166      * 呈现某一字段的配置参数FORM
    167      * @return bool
    168      */
    169     public function getFieldConfig()
    170     {
    171         $moduleKey = Input::get('module_key');
    172         $field = Input::get('field');
    173 
    174         $moduleConfig = Config::get('crud/' . snake_case($moduleKey));
    175         $fieldConf = &$moduleConfig['fields'][$field];
    176         $dbSource = SiteHelpers::loadDataSources()[$moduleConfig['data_source']];
    177         $tables = BaseModel::getTableList($dbSource['database'],$moduleConfig['data_source']);
    178 
    179         $resp = $this->makeView(array(
    180             'field'       => $field,
    181             'fieldConfig' => $fieldConf,
    182             'moduleKey'   => $moduleKey,
    183             'tables'    =>  $tables,
    184             'connection'    =>  $moduleConfig['data_source'],
    185         ));
    186 
    187         if ($resp) return $resp;
    188     }
    189 
    190     /**
    191      * 保存某一字段的配置参数
    192      * @return IlluminateHttpJsonResponse
    193      */
    194     public function postFieldConfig()
    195     {
    196         $data = array(
    197             'success' => true,
    198         );
    199         $moduleKey = Input::get('module_key');
    200         $field = Input::get('field');
    201         $moduleConfig = Config::get('crud/' . snake_case($moduleKey));
    202         $fieldConf = &$moduleConfig['fields'][$field];
    203 
    204         $postFormConf = Input::get('form');
    205         $postListConf = Input::get('list');
    206         $postRelationConf = Input::get('relation');
    207         $fieldConf['form']['type'] = $postFormConf['type'];
    208         if((
    209                 $postFormConf['type'] === 'select'
    210                 || $postFormConf['type'] === 'radio'
    211                 || $postFormConf['type'] === 'checkbox'
    212             )
    213             && isset($postFormConf['options'])
    214             && $postFormConf['options']
    215         ){
    216             $rawOptions = explode(',',$postFormConf['options']);
    217             $options = array();
    218             foreach($rawOptions as $option){
    219                 $options[$option]   = $option;
    220             }
    221             $fieldConf['form']['options'] = $options;
    222         }
    223         $fieldConf['form']['placeholder'] = $postFormConf['placeholder'];
    224         $fieldConf['form']['rule']  =  $postFormConf['rule'];
    225         $fieldConf['list']['sort'] = isset($postListConf['sort']);
    226         $fieldConf['list']['search'] = $postListConf['search'];
    227 
    228         $fieldConf['relation']  =   $postRelationConf;
    229 
    230         SiteHelpers::saveArrayToFile(app_path('config/crud/' . snake_case($moduleKey) . '.php'), $moduleConfig);
    231 
    232         return Response::json($data);
    233     }
    234 }
    ModuleController

    4.6 部署

    DBuilder部署运行的操作系统可以是Windows或Linux,本文将基于LNMP(Linux+Nginx+MySQL+PHP)环境进行部署,详细部署环境要求:

    • PHP Version > 5.4
    • MCrypt PHP 必须安装
    • OpenSSL 必须安装
    • MySQL Version > 5.4
    • Nginx、Apache等服务器

    首先,需要将DBuilder放置到Nginx的Default Server或者Vhost中,这里以Default Server为例。本文中DBuilder的根目录为

    /home/wwwroot/dbuilder/

    编辑nginx.conf文件,修改server节点:

    server

    {

       listen 80 default_server;

       #listen [::]:80 default_server ipv6only=on;

       #server_name www.lnmp.org;

       index index.html index.htm index.php;

       root  /home/wwwroot/dbuilder;

       #error_page   404   /404.html;

       include enable-php.conf;

       location / {

           try_files $uri $uri/ /index.php?$query_string;

       }

     

       location ~ [^/].php(/|$)

       {

              # comment try_files $uri =404; to enable pathinfo

              try_files $uri =404;

              fastcgi_pass  unix:/tmp/php-cgi.sock;

              #fastcgi_pass 127.0.0.1:9000;

              fastcgi_index index.php;

              include fastcgi.conf;

              #include pathinfo.conf;

       }

       location /nginx_status

       {

           stub_status on;

           access_log   off;

       }

       location ~ .*.(gif|jpg|jpeg|png|bmp|swf)$

       {

           expires      30d;

       }

       location ~ .*.(js|css)?$

       {

           expires      12h;

       }

       location ~ /.

       {

           deny all;

       }

       access_log  /home/wwwlogs/access.log  access;

    }

    修改DBuilder项目文件所属用户,保证nginx http进程对文件有读权限,本文部署环境中,nginx http进程为www用户进程;同时需要给部分DBuilder目录完全的写入权限,执行下列命令:

    cd /home/wwwroot

    chown –R www dbuilder

    chgrp –R www dbuilder

    cd dbuilder

    chmod –R 777 app/storage

    chmod -R 665 app/controllers/admin app/config/crud app/models/ app/views

    建立数据库,在mysql中创建名为dbuilder的数据库,并source Dbuilder根目录下的dbuilder.sql,具体执行如下命令

    # 首先进入msyql

    mysql –uroot –pyour_root_password

    # 进入mysql之后

    create database dbuilder default char set utf8;

    use dbuilder;

    source /home/wwwroot/dbuilder/dbuilder.sql;

    至此DBuilder部署完成,通过浏览器访问http://hostname/admin (hostname为主机域名或ip地址)即可以访问到DBuilder。

    4.7 案例

    设定:在不编写代码的基础上,以DBuilder生成一个简单可用的博客后台,博客后台有post表和category表,位于core数据源。

    CREATE TABLE post

    (

        id INT(11) PRIMARY KEY NOT NULL,

        category_id INT(11) NOT NULL,

        title VARCHAR(64) NOT NULL,

        short VARCHAR(256) NOT NULL,

        content TEXT NOT NULL,

        view_ct INT(11) DEFAULT '0' NOT NULL,

        created_at TIMESTAMP DEFAULT 'CURRENT_TIMESTAMP' NOT NULL,

        updated_at TIMESTAMP DEFAULT '0000-00-00 00:00:00' NOT NULL

    ) DEFAULT CHAR SET utf8;

    CREATE TABLE category

    (

        id INT(11) PRIMARY KEY NOT NULL,

        title VARCHAR(32) NOT NULL,

        level INT(11),

        weight INT(11) DEFAULT '0' NOT NULL COMMENT '排序字段',

        parent_id INT(11),

        post_ct INT(11) DEFAULT '0' NOT NULL,

    ) DEFAULT CHAR SET utf8;

    4.7.1 新建GModule

    准备好数据库表即可新建GModule,下面新建名为“Post”的GModule。进入GModule管理->新建界面,按图填写保存。

     

    图4-1 新建GModule页面

    编辑新建的Post GModule,可以看到在下部多出一个含有表格的tab。

     

    图4-2 GModule Configuration字段配置页面

    现在对于post表的所有字段都是默认配置,分别查看List和Form,可以看到List和Form都能正常读取数据库数据。

     

    图4-3 GMoudle 列表页面

     

    图4-4 GModule表单页面

    上面两图呈现的List和Form并不具有可用性,因此需要对字段做配置。

    4.7.2 GModule配置

    首先修改字段的中文名、是否包含在form、是否包含在List等属性。

     

    图4-5 GModule Configuration字段配置页面

    保存之后,再次刷新Post列表和Form。对比图4-3、图4-4发现内容发生了变化

     

    图4-6 GModule列表页面

     

    图4-7 GModule表单页面

    下面对每个字段做更详细的配置以得到更符合我们需求的页面,修改控件类型:short(摘要)字段为textarea(多行文本)类型,content(正文)字段为wysiwyg(富文本)类型,category_id字段为select(下拉列表)类型,updated_at(修改时间)为date(日期)类型。修改category_id(栏目外键)的关系为所属关系,并填写如下:

     

    图4-8 GModule 字段详细配置表单

    修改short(摘要)字段、title(标题)字段为不可排序与like模糊搜索,修改updated_at搜索方式为“>=”搜索

    4.7.3 List&Form效果

    刷新Post列表,可看到如下两个控件:date和select控件。

     

    图4-9 GModule 列表搜索日期与下拉列表控件

    输入搜索条件为修改日期:2016-03-03、栏目:C++、摘要:收到。结果按阅读次数排序。得到下面的列表结果。

     

    图4-10 GModule 列表搜索与排序

    点击其中一条记录进行编辑,测试Form功能。

     

    图4-11 GModule编辑表单

    修改之后点击保存也是正常可用的。

    整个配置过程,只需几分钟,但却实现了上述较为复杂的功能。而如果换成开发人员手工编写类似功能模块,至少需要两三个小时的时间,相比之下,DBuilder极大的提高了开发效率。

    第五章           总结与展望

    本文基于WEB技术基本实现了一款可用的CRUD生成器,其内核实现比SximoBuilder更精简,在代码高度复用的前提下,提供更强的扩展性,并支持多数据库、前端验证、自定义表单控件等等。

    由于时间原因,DBuilder尚未实现诸如用户管理、权限控制、操作日志记录、站点配置、多语言化等功能。另外,随着技术进步和网络普及,如何做到高并发、高性能以及支持数据集群的web系统是当前web项目开发需要着重考虑的问题。笔者将在DBuilder的后续改进中实现上述功能,并对高并发提供支持。同时,为了更好的推广和发展DBuilder,笔者已将DBuilder开源至Github:https://github.com/lvyahui8/dbuilder.git 。

  • 相关阅读:
    vue-cli(vue脚手架)简单流程
    windows环境之node.js安装与环境配置
    fiddler的下载和简单使用
    Linux 配置nginx 代理tomcat,配置ssl
    来聊一聊导出数据问题
    作为一个开发人员应该具备怎么样技术栈和职业素养
    NODEJS的误打误撞
    聊一下程序员的日常
    openstack安装部署私有云详细图文
    openstack:OpenStack架构详解,
  • 原文地址:https://www.cnblogs.com/lvyahui/p/5626466.html
Copyright © 2020-2023  润新知