{"id":20144170,"url":"https://github.com/quansitech/qs_cmf","last_synced_at":"2025-04-09T18:54:01.228Z","repository":{"id":34248485,"uuid":"123370614","full_name":"quansitech/qs_cmf","owner":"quansitech","description":"基于TP3.2，用于快速搭建信息系统框架","archived":false,"fork":false,"pushed_at":"2025-01-27T07:06:59.000Z","size":69284,"stargazers_count":9,"open_issues_count":2,"forks_count":10,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-23T20:51:20.649Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/quansitech.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-03-01T02:27:04.000Z","updated_at":"2025-03-17T05:01:52.000Z","dependencies_parsed_at":"2023-02-17T01:46:07.026Z","dependency_job_id":"d4bebbeb-f3cb-4df7-8798-acb12ae743e4","html_url":"https://github.com/quansitech/qs_cmf","commit_stats":{"total_commits":572,"total_committers":11,"mean_commits":52.0,"dds":0.5786713286713286,"last_synced_commit":"8b1e0125e3c452c2ce0508825726b20098b302da"},"previous_names":[],"tags_count":102,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quansitech%2Fqs_cmf","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quansitech%2Fqs_cmf/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quansitech%2Fqs_cmf/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quansitech%2Fqs_cmf/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/quansitech","download_url":"https://codeload.github.com/quansitech/qs_cmf/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248093800,"owners_count":21046749,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-13T22:09:03.889Z","updated_at":"2025-04-09T18:54:01.188Z","avatar_url":"https://github.com/quansitech.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# qscmf\n\n![lincense](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)\n[![LICENSE](https://img.shields.io/badge/license-Anti%20996-blue.svg)](https://github.com/996icu/996.ICU/blob/master/LICENSE)\n![Pull request welcome](https://img.shields.io/badge/pr-welcome-green.svg?style=flat-square)\n\n## 介绍\n\n快速搭建信息管理类系统的框架。基于tp3.2开发,在其基础上添加了许多功能特性。tp3.2已经停止更新，该框架源码也对核心源码做了部分改动。\n\n## 特性\n\n+ 支持composer依赖管理\n+ 支持phpunit及laravel dusk自动化测试\n+ 集成laravel数据库管理工具及依赖注入容器\n+ 支持ListBuilder、FormBuilder后台管理界面模块化开发\n+ 插件系统\n+ 简单易用，可自定义的配置管理\n+ 消息队列系统\n+ 集成Elasticsearch、可自定义索引重建自制，实现数据库记录与搜索引擎索引同步变动\n\n## 截图\n\n\u003cimg src=\"https://user-images.githubusercontent.com/1665649/55472251-36458e80-563e-11e9-87c0-10c386d5bd78.png\" /\u003e\n\n## 安装\n\n第一种安装方法\n\n```\ngit clone https://github.com/tiderjian/qs_cmf.git\n```\n\n代码拉取完成后，执行composer安装\n\n```\ncomposer install\n```\n\n第二种安装方法，composer create project\n\n```php\ncomposer create-project tiderjian/qscmf qscmf\n```\n\n完成第一种或者第二种安装后\n\n1. 复制.env.example并重命名为.env，配置数据库参数\n2. 执行migrate数据库迁移命令。\n\n```\nphp artisan migrate\n```\n\n将web服务器搭起来后，后台登录地址  协议://域名:端口/admin， 账号:admin 密码:Qs123!@#\n\n## 维护模式\n\n在.env将 APP_MAINTENANCE 设成true，系统进入维护状态，所有请求都只会提示系统维护中。如需要在维护模式下执行升级脚本，可传递\"maintenance\"给第三个参数\n\n```php\nphp index.php Qscmf/UpgradeFix/v300FixSchedule/queue/default maintenance\n```\n\n## Elasticsearch\n\n框架为集成Elasticsearch提供了方便的方法, 假设使用者已经具备elasticsearch使用的相关知识。\n\n1. 添加 \"elasticsearch/elasticsearch\": \"~6.0\" 到composer.json文件，执行composer update 命令安装扩展包。\n\n2. 安装elasticsearch, 具体安装方法自行查找，推荐使用laradock作为开发环境，直接集成了elasticsearch的docker安装环境。\n\n3. 安装ik插件，安装查找elasticsearch官方文档。\n\n4. 在.env下添加 ELASTICSEARCH_HOSTS值，设置为elasticsearch的启动ip和端口，如laradock的默认设置为10.0.75.1:9200，需要配置一组地址，可用“,”隔开。\n\n5. 设置需要使用elastic的model和字段\n   \n    以ChapterModel添加title和summary到全文索引为例\n   \n   ```php\n   // ChapterModel类必须继承接口\n   class ChapterModel  extends \\Gy_Library\\GyListModel implements \\Qscmf\\Lib\\Elasticsearch\\ElasticsearchModelContract{\n   \n      // ElasticsearchHelper已经实现了一些帮助函数\n      use \\Qscmf\\Lib\\Elasticsearch\\ElasticsearchHelper;\n   \n      // 初始化全文索引时需要指定该Model要添加的索引记录\n      public function elasticsearchIndexList()\n      {\n           // 如这里chapter表与course表关联，只有当course及chapter状态都为可用，且非描述类chapter(pid = 0为描述类chapter)才会添加全文索引\n          return $this-\u003ealias('ch')-\u003ejoin('__COURSE__ c ON c.id=ch.course_id and c.status = ' . DBCont::NORMAL_STATUS)-\u003ewhere(['ch.status' =\u003e DBCont::NORMAL_STATUS, 'ch.pid' =\u003e ['neq', 0]])-\u003efield('ch.*')-\u003eselect();\n      }\n   \n      // chapter进行增删查改时同时会更新索引内容，该方法是指定什么状态的记录才会进行索引更改\n      // 返回false为无需索引的记录，true则会进行索引更新\n      public function isElasticsearchIndex($ent){\n          if($ent['status'] != DBCont::NORMAL_STATUS || $ent['pid'] == 0){\n              return false;\n          }\n   \n          $course_ent = D('Course')-\u003efind($ent['course_id']);\n          if($course_ent['status'] == DBCont::NORMAL_STATUS){\n              return true;\n          }\n          else{\n              return false;\n          }\n      }\n   \n      // 程序会自动生成索引的配置参数，此处是定义生成参数的规则\n      // 以:开头的字母表示该处会自动替换成相应字段的实际值\n      // {}表示里面的字符会与替换后的:字段值进行连接，如:id{_chapter}, id实际值为 12，则该处会替换成 12_chapter\n      // | 表示可以将字段的实际值传递给指定的函数进行处理，转换成想要的值。如，description字段是富文本内容，我们将html标签进行索引，可以在model方法里自定义一个叫deleteHtmlTag的方法进行处理，当然也可以定义为全局函数，程序会先查找全局是否存在该函数，如果没有再去对象里查找有无该方法\n     // index和type的值在建立初始化全文索引时指定，具体查看全文索引初始化说明\n      public function elasticsearchAddDataParams()\n      {\n          return [\n              'index' =\u003e 'global_search',\n              'type' =\u003e 'content',\n              'id' =\u003e ':id{_chapter}',\n              'body' =\u003e [\n                  'title' =\u003e ':title',\n                  'desc' =\u003e ':description|html_entity_decode'\n              ]\n          ];\n      }\n   }\n   ```\n\n6. 初始化全文索引\n   \n    打开Home/Controller/ElasticController.class.php文件, 修改index方法里的$params变量，根据你的需要来设置\n\n7. 执行索引初始化，程序会自动检索数据库全部数据表，为需要添加索引的表和字段进行索引添加操作。\n   \n   ```\n   //进入app目录，下面有个makeIndex.php文件\n   php makeIndex.php\n   ```\n\n8. 可通过在config文件设置 \"ELASTIC_ALLOW_EXCEPTION\" 来禁止抛出异常，即使搜索引擎关闭，也不会影响原来的业务操作。\n\n9. 更新操作的索引重建仅会在索引字段发生变化时才会触发。\n\n## Controller\n\n[传送门](https://github.com/quansitech/qs_cmf/blob/master/docs/Controller.md)\n\n## Model\n\n[传送门](https://github.com/quansitech/qs_cmf/blob/master/docs/Model.md)\n\n## 数据库迁移\n\n扩展了laravel的迁移功能, 可在执行迁移前后插入一些操作。\n\n```php\nclass CreateTestTable extends Migration\n{\n\n    public function beforeCmmUp()\n    {\n        echo \"执行前置命令\" . PHP_EOL;\n    }\n\n    public function beforeCmmDown()\n    {\n        echo \"执行前置回滚命令\" . PHP_EOL;\n    }\n\n    /**\n     * Run the migrations.\n     *\n     * @return void\n     */\n    public function up()\n    {\n        Schema::create('test', function (Blueprint $table) {\n            $table-\u003ebigIncrements('id');\n            $table-\u003etimestamps();\n        });\n    }\n\n    /**\n     * Reverse the migrations.\n     *\n     * @return void\n     */\n    public function down()\n    {\n        Schema::dropIfExists('test');\n    }\n\n    public function afterCmmUp()\n    {\n        echo \"执行后置命令\" . PHP_EOL;\n    }\n\n    public function afterCmmDown()\n    {\n        echo \"执行后置回滚命令\" . PHP_EOL;\n    }\n}\n```\n\n运用场景：\n\n迁移文件是为了方便我们对数据库结构进行变更管理。那么是所有的数据库变更都会放到迁移文件处理吗？当然不是，像一些跟业务逻辑有关的数据处理就不应该放到迁移文件，否则这部分代码跟\n业务数据捆绑，很容易导致执行迁移时出错。而只有那些跟业务数据无关，用于构造系统数据存储结构的变更操作才应该放到迁移中。但一个业务系统维护久了，难免必须处理一些数据后才能正常\n执行数据库结构变更，例如唯一索引的创建往往就需要我们清理掉一些重复的业务数据。这时就有了不能放在迁移里的业务数据维护脚本的管理需求。\n\n依托上面的场景，就有了在迁移文件中加入了前后置的操作点的构思。默认情况下，只要设置了前后置操作点，执行迁移时就会自动执行一遍，回滚类同。\n\n那么如果只是想执行迁移而不执行前后置操作怎么办呢（例如执行自动测试脚本前，我们会自动构建系统的数据库）。只需在命令后面加入 --no-cmd即可\n\n下面是支持加入--no-cmd操作的命令\n\n```php\nphp artisan migrate --no-cmd\n\nphp artisan migrate:rollback --no-cmd\n\nphp artisan migrate:fresh --no-cmd\n\nphp artisan migrate:refresh --no-cmd\n\nphp artisan migrate:reset --no-cmd\n```\n\n+ CmmProcess\n  该类是为了方便在迁移中调用tp的脚本\n  \n  \u003e 用法：\n  \u003e \n  \u003e ```php\n  \u003e $process = new \\Larafortp\\CmmMigrate\\CmmProcess();\n  \u003e //timeout为程序的超时退出时间，默认60秒\n  \u003e $process-\u003esetTimeOut(100)-\u003ecallTp('/var/www/move/www/index.php', '/home/index/test');\n  \u003e ```\n\n+ ConfigGenerator\n   迁移中处理系统配置的工具类\n  \n   addGroup($name) //添加配置分组\n  \n   deleteGroup($name) //删除配置分组\n  \n   updateGroup($config_name, $group_name)  //将配置转移到指定分组\n  \n   以下为新增配置项的操作函数\n  \n  \u003e $name 配置名\n  \u003e \n  \u003e $title 配置标题\n  \u003e \n  \u003e $value 配置值\n  \u003e \n  \u003e $remark 配置说明\n  \u003e \n  \u003e $group 配置分组\n  \u003e \n  \u003e $sort 排序\n  \n   addNum($name, $title, $value, $remark = '', $group = 1, $sort = 0) //新增数字类型配置值\n  \n   addText($name, $title, $value, $remark = '', $group = 1, $sort = 0) //新增字符类型配置值\n  \n   addArray($name, $title, $value, $remark = '', $group = 1, $sort = 0) //新增数组类型配置值\n  \n   addPicture($name, $title, $value, $remark = '', $group = 1, $sort = 0) //新增图片类型配置值\n  \n   addUeditor($name, $title, $value, $remark = '', $group = 1, $sort = 0) //新增富文本类型配置值\n  \n   addSelect($name, $title, $value, $options, $remark = '', $group = 1, $sort = 0) //新增下拉选择配置值 $options 是下拉配置数组\n  \n   add($name, $type, $title, $group, $extra, $remark, $value, $sort) //新增配置方法，未预设的第三方组件可使用该函数\n  \n   delete($name) //删除配置\n\n## 后台JS\n\n[传送门](https://github.com/quansitech/qs_cmf/blob/master/docs/BackendJs.md)\n\n## ListBuilder\n\n[传送门](https://github.com/quansitech/qs_cmf/blob/master/docs/ListBuilder.md)\n\n## FormBuilder\n\n[传送门](https://github.com/quansitech/qs_cmf/blob/master/docs/FormBuilder.md)\n\n## CompareBuilder\n\n[传送门](https://github.com/quansitech/qs_cmf/blob/master/docs/CompareBuilder.md)\n\n## Builder\n\n[传送门](https://github.com/quansitech/qs_cmf/blob/master/docs/Builder.md)\n\n## Cache\n[传送门](https://github.com/quansitech/qs_cmf/blob/master/docs/Cache.md)\n\n## upload Api\n[传送门](https://github.com/quansitech/qs_cmf/blob/master/docs/Upload.md)\n\n## 前台js错误收集\n\n#### 用法\n\n在前端head中引入log.js后调用frontLog方法\n\n```php\n    \u003cscript src=\"__PUBLIC__/libs/log.js\"\u003e\u003c/script\u003e\n    \u003cscript\u003e\n      frontLog({\n        url:'/api/jsLog/index'\n      });\n    \u003c/script\u003e\n```\n\n## 权限功能\n\n[传送门](https://github.com/quansitech/qs_cmf/blob/master/docs/Auth.md)\n\n## 微信登录\n\n为解决第三方平台网站应用的PC扫码后openid不可操作问题，统一对PC端微信扫码以及微信端登录进行封装。\n\n* 从[微信公众平台](https://mp.weixin.qq.com/)中获取公众号的app_id和app_secret，并进行相关配置，放入.env文件\n  \n  ```dotenv\n  # 微信公众号\n  WX_APPID=\n  WX_APPSECRET=\n  ```\n\n* PC扫码页面，在需要显示二维码的地方加入iframe\n  \n  ```html\n  \u003ciframe src=\"{:U('qscmf/weixinLogin/scan')}\"\u003e\u003c/iframe\u003e\n  ```\n  \n  PS:\n1. 构造iframe的src时，可通过goto_url参数来指定PC端扫码后跳转的地址，默认为首页\n\n2. 构造iframe的src时，可通过mobile_goto_url参数来指定微信端扫码后跳转的地址，默认为首页\n* 微信端获取登录信息\n  \n  ```php\n    $wx_info=Qscmf\\Lib\\WeixinLogin::getInstance()-\u003egetInfoForMobile();\n  ```\n\n* 运行/扫码后可用``` session('wx_info') ```获取微信登录信息\n\n* 若'wx_info'的session值已设置，可通过设置config.php中的'WX_INFO_SESSION_KEY'来改变\n\n### 场景模拟\n\n一、 PC端实现扫码登录/注册\n\n* 扫码页面（扫码后需要跳转到'/home/index/wxLogin'）\n  \n  ```html\n  \u003c!-- 其它代码 --\u003e\n    \u003c!-- 此处是放入二维码的位置 --\u003e\n    \u003ciframe id=\"scan\" src=\"{:U('qscmf/weixinLogin/scan',['goto_url'=\u003eurlencode('/home/index/wxLogin')])}\"\u003e\u003c/iframe\u003e\n  \u003c!-- 其它代码 --\u003e\n  ```\n\n* 登录/注册业务处理（对应上一步的\"/home/index/wxLogin\"）\n  \n  ```php\n  $wx_info=json_decode(session('wx_info'),true);\n  // 若用户表为member表\n  $member=D('Member')-\u003ewhere(['openid'=\u003e$wx_info['id']])-\u003efind();\n  if ($member){\n    //登录\n    session('mid',$member['id']);\n  }else{\n    //注册\n    $ent=[\n        'openid'=\u003e$wx_info['id'],\n        'nickname'=\u003e$wx_info['nickname']\n    ];\n    $r=D('Member')-\u003ecreateAdd($ent);\n    if ($r===false){\n        E(D('Member')-\u003egetError());    \n    }\n    session('mid',$r);\n  }\n  redirect(U('home/user/index'));\n  ```\n\n二、 微信端实现授权登录/注册\n\n1. 授权页面 （授权后需要跳转到'/home/index/wxLogin'）\n   \n   ```php\n    $wx_info=Qscmf\\Lib\\WeixinLogin::getInstance()-\u003egetInfoForMobile();\n    if ($wx_info){\n        redirect(U('/home/index/wxLogin'));    \n    }   \n   ```\n\n2. 登录/注册业务处理（对应上一步的\"/home/index/wxLogin\"）\n   \n   ```php\n   // 与PC端扫码后登录/注册业务处理一致\n   ```\n\n## 全局函数\n\n[传送门](https://github.com/quansitech/qs_cmf/blob/master/docs/Helper.md)\n\n## js组件\n\n### selectAddr\n\n```blade\nselect框的地址选择器\n\n参数 \naddressLevel: array 省/市/县的select框默认值，默认为：['选择省','选择市','选择区']\nlevel: int 1|2|3 地址的等级：省/市/区，默认为：3\nurl: array 分别获取地址的接口url，默认为：['/api/area/getProvince.html','/api/area/getCityByProvince.html','/api/area/getDistrictByCity.html']\nonSelected: function (val,changeEle){}  每个select框选择地址后执行自定义function，val： 隐藏域的值 changeEle： 触发事件的select\n```\n\n代码示例\n\n```php\n\u003cinput type=\"hidden\" id=\"hidden_position\" name=\"city_id\" value=\"{$city_id}\"\u003e\n\n\u003cblock name=\"script\"\u003e\n\u003cscript type=\"text/javascript\" src=\"__PUBLIC__/libs/addrSelect/selectAddr.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n    jQuery(document).ready(function() {\n        $('#hidden_position').selectAddr({\n            addressLevel: ['省','市','区'],\n            level: 3,\n            onSelected: function (val,changeEle){\n                log(val);\n            }\n        });\n\n        function log(str) {\n            console.log(str+\"-111\");\n        }    \n    });\n\u003c/script\u003e\n\u003c/block\u003e\n```\n\n## 常量\n\nDOMAIN  域名，可通过env去改写，默认采用$_SERVER[\"HTTP_HOST\"]\n\nROOT 指定子目录，默认为空, 可通过env改写，如子路径 ROOT=/move\n\nSITE_URL 包含子目录的网站根地址\n\nHTTP_PROTOCOL  返回http或者https协议字符串, 可通过env指定\n\nREQUEST_URI 获取方向代理前的REQUEST_URI值\n\n## 扩展\n\n[传送门](https://github.com/quansitech/qs_cmf/blob/master/docs/Extends.md)\n\n```text\n部分扩展包会将组件的js/css注入 dashboard_layout 头部\n\n默认只注入路径 T('Admin@default/common/dashboard_layout')\n如果项目使用自定义的 layout ，可以通过 QS_INJECT_LAYOUT_PATH 配置需要注入的 layout 路径\n```\n```php\n// config.php文件加入以下配置\n// 只需要配置自定义的 layout 路径\n'QS_INJECT_LAYOUT_PATH' =\u003e [\n    T('Admin@default/common/letter/layout')\n]\n```\n\n## 消息队列\n\n[传送门](https://github.com/quansitech/qs_cmf/blob/master/docs/Resque.md)\n\n## 测试\n[传送门](/docs/Testing.md)\n\n#### 压缩前端js代码\n\n压缩办法很多，这里提供一种配置简单的方式，[传送门](https://gist.github.com/gaearon/42a2ffa41b8319948f9be4076286e1f3)\n\n### HEIC格式图片转JPG格式\n```\n为解决部分组件暂不支持展示heic格式图片，将其转换为jpg\n\n上传到阿里云oss的图片已处理\n```\n+ 使用\n    + 复制数据迁移文件*2022_07_18_014941_alter_file_pic_add_mime_type.php*，qs_file_pic添加字段 mime_type\n\n+ 扩展包需自行注册并实现heic_to_jpg行为\n    + 定义行为\n      ```php\n        class HeicToJpgBehavior{\n  \n        public function run(\u0026$params)\n        {\n            // $params为qs_file_pic的一条数据\n            // 具体逻辑\n            $params['url'] = 'your new url';           \n        }\n    \n        }\n      ```\n    + 注册行为\n      ```php\n      \\Think\\Hook::add('heic_to_jpg', 'xxx\\\\HeicToJpgBehavior');\n      ```\n\n### TRACE_ERROR\nenv增加了TRACE_ERROR配置，如果希望在debug关闭的模式下能收集到错误的报错位置，可以设置为true。这样就无需开启debug模式，也能收集到错误的报错位置。减少日志负担。\n\n### 后台使用react构建页面\n[传送门](https://github.com/quansitech/qs_cmf/blob/master/react-admin/README.md)\n\n### 静态资源配置CDN\n[传送门](./doc/InjectCdn.md)\n\n## 文档\n\n由于工作量大，文档会逐步补全。\n\n## lincense\n\n[MIT License](https://github.com/tiderjian/lara-for-tp/blob/master/LICENSE.MIT) AND [996ICU License](https://github.com/tiderjian/lara-for-tp/blob/master/LICENSE.996ICU)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fquansitech%2Fqs_cmf","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fquansitech%2Fqs_cmf","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fquansitech%2Fqs_cmf/lists"}