• php7扩展开发[11] MVC之路由解析和加载文件


    ```
    场景:想要用C实现PHP的一个MVC结构的路由解析和加载文件的功能,一共要解决几个问题
    1.由于MVC要加载多个C文件,所以要修正config.m4,修改config.m4内容第十行左右,去掉dnl,
    PHP_ARG_WITH(dora, for route support,
    dnl Make sure that the comment is aligned:
    [  --with-route             Include dora support])
    在下面追加到以下内容:
    if test -z "$PHP_DEBUG" ; then
        AC_ARG_ENABLE(debug, [--enable-debug compile with debugging system], [PHP_DEBUG=$enableval],[PHP_DEBUG=no] )
    fi

    最后一行,加载所需所有C文件,如下:
      PHP_NEW_EXTENSION(dora, dora.c common/utilts.c loader/loader.c route/route.c controller/controller.c model/model.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
      PHP_ADD_BUILD_DIR([$ext_builddir/common])
      PHP_ADD_BUILD_DIR([$ext_builddir/loader])
      PHP_ADD_BUILD_DIR([$ext_builddir/route])
      PHP_ADD_BUILD_DIR([$ext_builddir/controller])
      PHP_ADD_BUILD_DIR([$ext_builddir/model])
     
    ```
    ```c

    #include "utilts.h"
    #include "php_ini.h"
    #include "ext/standard/info.h"
    #include "Zend/zend_list.h"
    #include "Zend/zend_interfaces.h"

    //执行PHP文件函数
    int zend_execute_scripts_ext(char *filepath){

        zval retval;

        zend_file_handle zfd;
        zfd.type = ZEND_HANDLE_FILENAME;
        zfd.filename = filepath;
        zfd.free_filename = 0;
        zfd.opened_path = NULL;

        //zend_execute_scripts(int type, zval *retval, int file_count, ...);
        //FAILURE OR SUCCESS
        return  zend_execute_scripts(ZEND_INCLUDE TSRMLS_CC,&retval,1,&zfd);
        


    }


    //调用类中的方法
    int call_user_class_method(zval *retval, zend_class_entry *obj_ce,
                               zval *obj, zval func,  uint32_t params_count, zval params[]){


        HashTable *function_table;

        if(obj) {
                    function_table = &Z_OBJCE_P(obj)->function_table;
            }else{
                    function_table = (CG(function_table));
        }

        zend_fcall_info fci;  
        fci.size = sizeof(fci);  
        fci.function_table = function_table;  
        fci.object =  obj ? Z_OBJ_P(obj) : NULL;;
        fci.function_name = func;   
        fci.retval = retval;  
        fci.param_count = params_count;  
        fci.params = params;  
        fci.no_separation = 1;  
        fci.symbol_table = NULL;  


     
        //FAILURE OR SUCCESS
        return  zend_call_function(&fci, NULL TSRMLS_CC);         //函数调用结束。  

    }


    ```
    ```c
    3.修改php_route.h头文件内容

    在第五十行左右,加入以下内容

    //定义类
    extern zend_class_entry *route_ce;
    //定义loader类中的方法
    PHP_METHOD(route_ce,__construct);
    PHP_METHOD(route_ce,run);
    ```
    ```c
    4.修改route.c文件内容

    /**
     * 声明构造函数
     * @param
     * @return
     */
    ZEND_METHOD(route,__construct){

     
        zval *app_dir;

        if( zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &app_dir) == FAILURE )
        {
            RETURN_NULL();
        }
        //zend_update_static_property_stringl(zend_class_entry *scope, const char *name, size_t name_length, const char *value, size_t value_length);
        zend_update_static_property(route_ce, "app_dir", sizeof("app_dir")-1, app_dir TSRMLS_CC);

    }



    /**
     * 加载view
     * @param
     * @return
     */
    ZEND_METHOD(route,run){


          zend_string* controller_name = zend_string_init("Index",strlen("Index"),0);
          zend_string* action_name     = zend_string_init("Index",strlen("Index"),0);

          zval *c_result;
          zval *a_result;
          int flag;



          //设置站点目录
          zval *app_dir = zend_read_static_property(Z_OBJCE_P(getThis()), "app_dir", sizeof("app_dir")-1, 0 TSRMLS_DC);


          //获取GET请求参数hashtable
          zval *get_arr = &PG(http_globals)[TRACK_VARS_GET];
          HashTable *ht= HASH_OF(get_arr);
          //int array_count = zend_hash_num_elements(Z_ARRVAL_P(get_arr));
        

          //获取controller_name
          zend_string *c_key= zend_string_init("controller", sizeof("controller")-1, 0);

          if ((c_result = zend_hash_find(ht, c_key)) != NULL) {
          
                controller_name = zval_get_string(c_result);
            
          }else{

                zend_error_noreturn(E_CORE_ERROR,  "Couldn't find controller param in url.");

          }
          //释放key的变量
          zend_string_release(c_key);


          //获取action_name
          zend_string *a_key= zend_string_init("action", sizeof("action")-1, 0);

          if ((a_result = zend_hash_find(ht, a_key)) != NULL) {

                action_name = zval_get_string(a_result);
                //php_printf("%s ", Z_STRVAL_P(a_result));
                //php_printf("%s ", zval_get_string(a_result));
          }else{

                zend_error_noreturn(E_CORE_ERROR,"Couldn't find action param in url.");

          }

          //释放key的变量
          zend_string_release(a_key);


          //拼装controller文件路径
          char *path = Z_STRVAL_P(app_dir);
          char *c_2 = "controllers/";
          strcat(path,c_2);

          //zend_string->char *
          char *c_3 = ZSTR_VAL(controller_name);
          strcat(path,c_3);

          char *c_4 = ".php";
          strcat(path,c_4);

          //php_printf("%s ", c_1);
          // php_printf("%s ", controller_name);
          // php_printf("%s ", action_name);
          //PHPWRITE(Z_STRVAL_P(app_dir), Z_STRLEN_P(app_dir));



          //加载执行controller文件
          flag = zend_execute_scripts_ext(c_1);

          if(flag == FAILURE){

                zend_error_noreturn(E_CORE_ERROR,"Couldn't find file: %s.",c_1);

          }


          //查找controller对应的
          //zend_class_entry *zend_lookup_class(zend_string *name);
          zend_class_entry *controller_ce = zend_lookup_class(controller_name);

          if(controller_ce == NULL){

                zend_error_noreturn(E_CORE_ERROR,"Couldn't find file: %s.",c_1);
          }


          zval obj;
          object_init_ex(&obj, controller_ce);

          
          zval function_name;
          ZVAL_STRING(&function_name,ZSTR_VAL(action_name));

          
          flag = call_user_class_method(return_value, controller_ce, &obj, function_name, 0, NULL);

          if(flag == FAILURE){


                zend_error_noreturn(E_CORE_ERROR,
                                    "Couldn't find implementation for method %s%s%s",
                                    controller_ce ? ZSTR_VAL(controller_ce->name) : "",
                                    controller_ce ? "::" : "",
                                    function_name);
            
          }

          //RETURN_ZVAL(get_arr, 1, 0);

    }

    const zend_function_entry route_functions[] = {


        //注册route类中的方法
        ZEND_ME(route, __construct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
        ZEND_ME(route,run,NULL,ZEND_ACC_PUBLIC)


        PHP_FE_END    /* Must be the last line in route_functions[] */
    };

    PHP_MINIT_FUNCTION(route)
    {

        //注册route类
        zend_class_entry ce;

        //define INIT_NS_CLASS_ENTRY(class_container, ns, class_name, functions)
        INIT_NS_CLASS_ENTRY(ce,"Dora" ,"Route", route_functions);
        route_ce = zend_register_internal_class(&ce TSRMLS_CC);

        //声明一个静态数据成员app_dir
        //zend_declare_property_string(zend_class_entry *ce, const char *name, size_t name_length, const char *value, int access_type);
        zend_declare_property_string(route_ce, "app_dir", strlen("app_dir"), "",ZEND_ACC_PUBLIC|ZEND_ACC_STATIC TSRMLS_DC);
        

        return SUCCESS;
    }
    ```
    ```c
    5.编译安装
    phpize
    ./configure --with-php-config=/usr/bin/php-config
    make && make install

    ```
    ```c
    6.php7创建类所有到的知识点


    常见的变量操作宏

    CG    -> Complier Global      编译时信息,包括函数表等(zend_globals_macros.h:32)
    EG    -> Executor Global      执行时信息(zend_globals_macros.h:43)
    PG    -> PHP Core Global      主要存储php.ini中的信息
    SG    -> SAPI Global          SAPI信息

    //===============================================================================
    PHP7中的zval的类型:
    /* regular data types */
    define IS_UNDEF                    0
    define IS_NULL                     1
    define IS_FALSE                    2
    define IS_TRUE                     3
    define IS_LONG                     4
    define IS_DOUBLE                   5
    define IS_STRING                   6
    define IS_ARRAY                    7
    define IS_OBJECT                   8
    define IS_RESOURCE                 9
    define IS_REFERENCE                10

    define IS_CONSTANT                 11
    define IS_CONSTANT_AST             12

    define _IS_BOOL                    13
    define IS_CALLABLE                 14

    define IS_INDIRECT                 15
    define IS_PTR                      17

    //===============================================================================
    PHP7中获取的zval赋值:
    - ZVAL_STRING(zv, str, 1);
    + ZVAL_STRING(zv, str);
    - ZVAL_STRINGL(zv, str, len, 1);
    + ZVAL_STRINGL(zv, str, len);
    - ZVAL_STRING(zv, str, 0);
    + ZVAL_STRING(zv, str);
    + efree(str);
    - ZVAL_STRINGL(zv, str, len, 0);
    + ZVAL_STRINGL(zv, str, len);

    //===============================================================================

    PHP7中获取的zval的值和长度:
    define Z_LVAL(zval)                (zval).value.lval
    define Z_LVAL_P(zval_p)            Z_LVAL(*(zval_p))

    define Z_DVAL(zval)                (zval).value.dval
    define Z_DVAL_P(zval_p)            Z_DVAL(*(zval_p))

    define Z_STR(zval)                 (zval).value.str
    define Z_STR_P(zval_p)             Z_STR(*(zval_p))

    define Z_STRVAL(zval)              ZSTR_VAL(Z_STR(zval))
    define Z_STRVAL_P(zval_p)          Z_STRVAL(*(zval_p))

    define Z_STRLEN(zval)              ZSTR_LEN(Z_STR(zval))
    define Z_STRLEN_P(zval_p)          Z_STRLEN(*(zval_p))

    define Z_STRHASH(zval)             ZSTR_HASH(Z_STR(zval))
    define Z_STRHASH_P(zval_p)         Z_STRHASH(*(zval_p))

    define Z_ARR(zval)                 (zval).value.arr
    define Z_ARR_P(zval_p)             Z_ARR(*(zval_p))

    define Z_ARRVAL(zval)              Z_ARR(zval)
    define Z_ARRVAL_P(zval_p)          Z_ARRVAL(*(zval_p))

    define Z_OBJ(zval)                 (zval).value.obj
    define Z_OBJ_P(zval_p)             Z_OBJ(*(zval_p))

    define Z_OBJ_HT(zval)              Z_OBJ(zval)->handlers
    define Z_OBJ_HT_P(zval_p)          Z_OBJ_HT(*(zval_p))

    define Z_OBJ_HANDLER(zval, hf)     Z_OBJ_HT((zval))->hf
    define Z_OBJ_HANDLER_P(zv_p, hf)   Z_OBJ_HANDLER(*(zv_p), hf)

    define Z_OBJ_HANDLE(zval)          (Z_OBJ((zval)))->handle
    define Z_OBJ_HANDLE_P(zval_p)      Z_OBJ_HANDLE(*(zval_p))

    define Z_OBJCE(zval)               (Z_OBJ(zval)->ce)
    define Z_OBJCE_P(zval_p)           Z_OBJCE(*(zval_p))

    define Z_OBJPROP(zval)             Z_OBJ_HT((zval))->get_properties(&(zval))
    define Z_OBJPROP_P(zval_p)         Z_OBJPROP(*(zval_p))

    define Z_OBJDEBUG(zval,tmp)        (Z_OBJ_HANDLER((zval),get_debug_info)?Z_OBJ_HANDLER((zval),get_debug_info)(&(zval),&tmp):(tmp=0,Z_OBJ_HANDLER((zval),get_properties)?Z_OBJPROP(zval):NULL))
    define Z_OBJDEBUG_P(zval_p,tmp)    Z_OBJDEBUG(*(zval_p), tmp)

    define Z_RES(zval)                 (zval).value.res
    define Z_RES_P(zval_p)             Z_RES(*zval_p)

    define Z_RES_HANDLE(zval)          Z_RES(zval)->handle
    define Z_RES_HANDLE_P(zval_p)      Z_RES_HANDLE(*zval_p)

    define Z_RES_TYPE(zval)            Z_RES(zval)->type
    define Z_RES_TYPE_P(zval_p)        Z_RES_TYPE(*zval_p)

    define Z_RES_VAL(zval)             Z_RES(zval)->ptr
    define Z_RES_VAL_P(zval_p)         Z_RES_VAL(*zval_p)

    define Z_REF(zval)                 (zval).value.ref
    define Z_REF_P(zval_p)             Z_REF(*(zval_p))

    define Z_REFVAL(zval)              &Z_REF(zval)->val
    define Z_REFVAL_P(zval_p)          Z_REFVAL(*(zval_p))

    define Z_AST(zval)                 (zval).value.ast
    define Z_AST_P(zval_p)             Z_AST(*(zval_p))

    define Z_ASTVAL(zval)              (zval).value.ast->ast
    define Z_ASTVAL_P(zval_p)          Z_ASTVAL(*(zval_p))

    define Z_INDIRECT(zval)            (zval).value.zv
    define Z_INDIRECT_P(zval_p)        Z_INDIRECT(*(zval_p))

    define Z_CE(zval)                  (zval).value.ce
    define Z_CE_P(zval_p)              Z_CE(*(zval_p))

    define Z_FUNC(zval)                (zval).value.func
    define Z_FUNC_P(zval_p)            Z_FUNC(*(zval_p))

    define Z_PTR(zval)                 (zval).value.ptr
    define Z_PTR_P(zval_p)             Z_PTR(*(zval_p))

    //===============================================================================

    php7 用来判断类型和取值

    void display_value(zval zv,zval *zv_p,zval **zv_pp)
    {
        if( Z_TYPE(zv) == IS_NULL )
        {
            php_printf("类型是 IS_NULL! ");
        }
         
        if( Z_TYPE_P(zv_p) == IS_LONG )
        {
            php_printf("类型是 IS_LONG,值是:%ld" , Z_LVAL_P(zv_p));
        }
         
        if(Z_TYPE_PP(zv_pp) == IS_DOUBLE )
        {
            php_printf("类型是 IS_DOUBLE,值是:%f" , Z_DVAL_PP(zv_pp) );
        }
    }

    //================================================================================

    PHP7中的定义返回值的宏 Zend/zend_API.h
    define RETVAL_BOOL(b)                                  ZVAL_BOOL(return_value, b)
    define RETVAL_NULL()                                   ZVAL_NULL(return_value)
    define RETVAL_LONG(l)                                  ZVAL_LONG(return_value, l)
    define RETVAL_DOUBLE(d)                                ZVAL_DOUBLE(return_value, d)
    define RETVAL_STR(s)                                   ZVAL_STR(return_value, s)
    define RETVAL_INTERNED_STR(s)                  ZVAL_INTERNED_STR(return_value, s)
    define RETVAL_NEW_STR(s)                               ZVAL_NEW_STR(return_value, s)
    define RETVAL_STR_COPY(s)                              ZVAL_STR_COPY(return_value, s)
    define RETVAL_STRING(s)                                ZVAL_STRING(return_value, s)
    define RETVAL_STRINGL(s, l)                    ZVAL_STRINGL(return_value, s, l)
    define RETVAL_EMPTY_STRING()                   ZVAL_EMPTY_STRING(return_value)
    define RETVAL_RES(r)                                   ZVAL_RES(return_value, r)
    define RETVAL_ARR(r)                                   ZVAL_ARR(return_value, r)
    define RETVAL_OBJ(r)                                   ZVAL_OBJ(return_value, r)
    define RETVAL_ZVAL(zv, copy, dtor)             ZVAL_ZVAL(return_value, zv, copy, dtor)
    define RETVAL_FALSE                                    ZVAL_FALSE(return_value)
    define RETVAL_TRUE                                     ZVAL_TRUE(return_value)


    define RETURN_BOOL(b)                                  { RETVAL_BOOL(b); return; }
    define RETURN_NULL()                                   { RETVAL_NULL(); return;}
    define RETURN_LONG(l)                                  { RETVAL_LONG(l); return; }
    define RETURN_DOUBLE(d)                                { RETVAL_DOUBLE(d); return; }
    define RETURN_STR(s)                                   { RETVAL_STR(s); return; }
    define RETURN_INTERNED_STR(s)                  { RETVAL_INTERNED_STR(s); return; }
    define RETURN_NEW_STR(s)                               { RETVAL_NEW_STR(s); return; }
    define RETURN_STR_COPY(s)                              { RETVAL_STR_COPY(s); return; }
    define RETURN_STRING(s)                                { RETVAL_STRING(s); return; }
    define RETURN_STRINGL(s, l)                    { RETVAL_STRINGL(s, l); return; }
    define RETURN_EMPTY_STRING()                   { RETVAL_EMPTY_STRING(); return; }
    define RETURN_RES(r)                                   { RETVAL_RES(r); return; }
    define RETURN_ARR(r)                                   { RETVAL_ARR(r); return; }
    define RETURN_OBJ(r)                                   { RETVAL_OBJ(r); return; }
    define RETURN_ZVAL(zv, copy, dtor)             { RETVAL_ZVAL(zv, copy, dtor); return; }
    define RETURN_FALSE                                    { RETVAL_FALSE; return; }
    define RETURN_TRUE                                     { RETVAL_TRUE; return; }
    array_init(return_value);//初始化return_value成数组,此操作完后就可以返回一个空的数组
    object_init(return_value);//初始化return_value成Object,此操作完成后返回一个空的对像




    //===============================================================================
    grep "define ZEND_ACC"  Zend/*.h
    内核中提供了定义类以方法的修饰词 Zend/zend_compile.h声明定义

    define ZEND_ACC_STATIC         0x01
    define ZEND_ACC_ABSTRACT       0x02
    define ZEND_ACC_FINAL          0x04
    define ZEND_ACC_IMPLEMENTED_ABSTRACT       0x08
    define ZEND_ACC_IMPLICIT_ABSTRACT_CLASS    0x10
    define ZEND_ACC_EXPLICIT_ABSTRACT_CLASS    0x20
    define ZEND_ACC_INTERFACE                  0x40
    define ZEND_ACC_TRAIT                      0x80
    define ZEND_ACC_ANON_CLASS                 0x100
    define ZEND_ACC_ANON_BOUND                 0x200

    define ZEND_ACC_PUBLIC     0x100
    define ZEND_ACC_PROTECTED  0x200
    define ZEND_ACC_PRIVATE    0x400
    define ZEND_ACC_PPP_MASK  (ZEND_ACC_PUBLIC | ZEND_ACC_PROTECTED | ZEND_ACC_PRIVATE)
    define ZEND_ACC_CHANGED    0x800
    define ZEND_ACC_IMPLICIT_PUBLIC    0x1000
    define ZEND_ACC_CTOR       0x2000
    define ZEND_ACC_DTOR       0x4000
    define ZEND_ACC_CLONE      0x8000


    //===============================================================================
    1. grep ZEND_ACC  Zend/*.h
    内核中提供了定义类属性的宏  Zend/zend_API.h声明定义

    ZEND_API int zend_declare_property_ex(zend_class_entry *ce, zend_string *name, zval *property, int access_type, zend_string *doc_comment);
    ZEND_API int zend_declare_property(zend_class_entry *ce, const char *name, size_t name_length, zval *property, int access_type);
    ZEND_API int zend_declare_property_null(zend_class_entry *ce, const char *name, size_t name_length, int access_type);
    ZEND_API int zend_declare_property_bool(zend_class_entry *ce, const char *name, size_t name_length, zend_long value, int access_type);
    ZEND_API int zend_declare_property_long(zend_class_entry *ce, const char *name, size_t name_length, zend_long value, int access_type);
    ZEND_API int zend_declare_property_double(zend_class_entry *ce, const char *name, size_t name_length, double value, int access_type);
    ZEND_API int zend_declare_property_string(zend_class_entry *ce, const char *name, size_t name_length, const char *value, int access_type);
    ZEND_API int zend_declare_property_stringl(zend_class_entry *ce, const char *name, size_t name_length, const char *value, size_t value_len, int access_type);


    更新类中的数据成员 Zend/zend_API.h声明定义

    ZEND_API void zend_update_property_ex(zend_class_entry *scope, zval *object, zend_string *name, zval *value);
    ZEND_API void zend_update_property(zend_class_entry *scope, zval *object, const char *name, size_t name_length, zval *value);
    ZEND_API void zend_update_property_null(zend_class_entry *scope, zval *object, const char *name, size_t name_length);
    ZEND_API void zend_update_property_bool(zend_class_entry *scope, zval *object, const char *name, size_t name_length, zend_long value);
    ZEND_API void zend_update_property_long(zend_class_entry *scope, zval *object, const char *name, size_t name_length, zend_long value);
    ZEND_API void zend_update_property_double(zend_class_entry *scope, zval *object, const char *name, size_t name_length, double value);
    ZEND_API void zend_update_property_str(zend_class_entry *scope, zval *object, const char *name, size_t name_length, zend_string *value);
    ZEND_API void zend_update_property_string(zend_class_entry *scope, zval *object, const char *name, size_t name_length, const char *value);
    ZEND_API void zend_update_property_stringl(zend_class_entry *scope, zval *object, const char *name, size_t name_length, const char *value, size_t value_length);

    grep "zend_read_"  ../../Zend/*.h
    读取类中的数据成员 在Zend/zend_API.h声明定义

    ZEND_API zval *zend_read_property(zend_class_entry *scope, zval *object, const char *name, size_t name_length, zend_bool silent, zval *rv);



    //===============================================================================
    2. grep "zend_declare_class_constant"  Zend/*.h
    创建类中的常量的方法在Zend/zend_API.h声明定义

    ZEND_API int zend_declare_class_constant(zend_class_entry *ce, const char *name, size_t name_length, zval *value);
    ZEND_API int zend_declare_class_constant_null(zend_class_entry *ce, const char *name, size_t name_length);
    ZEND_API int zend_declare_class_constant_long(zend_class_entry *ce, const char *name, size_t name_length, zend_long value);
    ZEND_API int zend_declare_class_constant_bool(zend_class_entry *ce, const char *name, size_t name_length, zend_bool value);
    ZEND_API int zend_declare_class_constant_double(zend_class_entry *ce, const char *name, size_t name_length, double value);
    ZEND_API int zend_declare_class_constant_stringl(zend_class_entry *ce, const char *name, size_t name_length, const char *value, size_t value_length);
    ZEND_API int zend_declare_class_constant_string(zend_class_entry *ce, const char *name, size_t name_length, const char *value);

    更新类中的常量数据成员 Zend/zend_API.h声明定义
    ZEND_API int zend_update_class_constants(zend_class_entry *class_type);



    //=================================================================================
    3. grep "zend_update_static_"  ../../Zend/*.h
    更新类中的静态数据成员 在Zend/zend_API.

    ZEND_API int zend_update_static_property(zend_class_entry *scope, const char *name, size_t name_length, zval *value);
    ZEND_API int zend_update_static_property_null(zend_class_entry *scope, const char *name, size_t name_length);
    ZEND_API int zend_update_static_property_bool(zend_class_entry *scope, const char *name, size_t name_length, zend_long value);
    ZEND_API int zend_update_static_property_long(zend_class_entry *scope, const char *name, size_t name_length, zend_long value);
    ZEND_API int zend_update_static_property_double(zend_class_entry *scope, const char *name, size_t name_length, double value);
    ZEND_API int zend_update_static_property_string(zend_class_entry *scope, const char *name, size_t name_length, const char *value);
    ZEND_API int zend_update_static_property_stringl(zend_class_entry *scope, const char *name, size_t name_length, const char *value, size_t value_length);


    grep "zend_read_"  ../../Zend/*.h
    读取类中的数据成员 在Zend/zend_API.h声明定义

    ZEND_API zval *zend_read_static_property(zend_class_entry *scope, const char *name, size_t name_length, zend_bool silent);


    ```

    - 请尊重本人劳动成功,可以随意转载但保留以下信息
    - 作者:岁月经年
    - 时间:2016年04月


    ![avatar](http://images.cnblogs.com/cnblogs_com/djhull/965538/o_qrcode_for_gh_d8bbf6b9b984_430%20(1).jpg)

  • 相关阅读:
    两排滚动js
    弹性布局
    channelartlist添加栏目链接
    首页调取二级、三级栏目
    dede完美分页样式
    如何安装sass
    首页分页(自由列表)
    tag标签调取
    25.简单的路由
    24.简单的自定义服务
  • 原文地址:https://www.cnblogs.com/djhull/p/5406456.html
Copyright © 2020-2023  润新知