{"id":20144139,"url":"https://github.com/quansitech/qscmf-utils","last_synced_at":"2025-04-09T18:53:54.804Z","repository":{"id":41992727,"uuid":"356208379","full_name":"quansitech/qscmf-utils","owner":"quansitech","description":"qscmf辅助开发库","archived":false,"fork":false,"pushed_at":"2024-09-04T02:15:31.000Z","size":62,"stargazers_count":0,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-23T20:51:20.657Z","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":null,"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":null,"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":"2021-04-09T09:10:06.000Z","updated_at":"2024-09-04T02:14:25.000Z","dependencies_parsed_at":"2024-05-28T06:19:07.995Z","dependency_job_id":null,"html_url":"https://github.com/quansitech/qscmf-utils","commit_stats":{"total_commits":29,"total_committers":3,"mean_commits":9.666666666666666,"dds":"0.27586206896551724","last_synced_commit":"530835b70d7eb22b49047d5259fef032b641d197"},"previous_names":[],"tags_count":26,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quansitech%2Fqscmf-utils","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quansitech%2Fqscmf-utils/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quansitech%2Fqscmf-utils/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quansitech%2Fqscmf-utils/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/quansitech","download_url":"https://codeload.github.com/quansitech/qscmf-utils/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247596530,"owners_count":20964118,"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:08:58.745Z","updated_at":"2025-04-09T18:53:54.778Z","avatar_url":"https://github.com/quansitech.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# qscmf辅助开发库\n\n+ 安装\n  \n  ```php\n  composer require quansitech/qscmf-utils\n  ```\n\n## \n\n## CmmProcess\n\n迁移中调用tp的脚本\n\n\u003e 用法：\n\u003e \n\u003e ```php\n\u003e $process = new \\Qscmf\\Utils\\MigrationHelper\\CmmProcess();\n\u003e //timeout为程序的超时退出时间，默认60秒\n\u003e $process-\u003esetTimeOut(100)-\u003ecallTp('/var/www/move/www/index.php', '/home/index/test');\n\u003e ```\n\n## \n\n## ConfigGenerator\n\n迁移中处理系统配置的工具类\n\n+ addGroup($name) //添加配置分组 \n\n+ deleteGroup($name) //删除配置分组\n\n+ updateGroup($config_name, $group_name)  //将配置转移到指定分组\n\n+ getGroupId($group_name) //根据分组名获取分组id\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+ updateSort($name, $sort) //修改配置的排序\n\n+ delete($name) //删除配置\n\n## \n\n## MenuGenerate\n\n生成菜单和节点列表\n自动处理menu和node的关系\n\n#### 用法\n\n+ 生成top_menu为平台的菜单和节点列表\n  \n  ```php\n  $this-\u003enodeData = [\n   '新闻中心'=\u003e [\n             [\n                 'name'      =\u003e 'index',\n                 'title'     =\u003e '新闻分类',\n                 'controller'=\u003e 'NewsCate',\n             ],\n             [\n                 'name'      =\u003e 'index',\n                 'title'     =\u003e '内容管理',\n                 'controller'=\u003e 'News',\n                 'sort'      =\u003e 1,\n             ],\n   ],\n  ];\n  \n  $menuGenerate = new Qscmf\\Utils\\MigrationHelper\\MenuGenerate();\n  $menuGenerate-\u003einsertAll($this-\u003enodeData);\n\n  // 撤销\n  $menuGenerate-\u003einsertAllRollback($this-\u003enodeData);\n  ```\n\n\n```\n+ 生成自定义top_menu的菜单和节点列表\n```php\n$data = [\n            [\n                'title'      =\u003e '平台2', //标题              (必填)\n                'module'     =\u003e 'newsAdmin', //模块英文名        (必填)\n                'module_name'=\u003e '后台管理', //模块中文名   (必填)\n                'url'        =\u003e '', //url                  (必填)\n                'type'       =\u003e '', //类型                (选填）\n                'sort'       =\u003e 0, //排序                (选填）\n                'icon'       =\u003e '', //icon                (选填）\n                'status'     =\u003e 1, //状态              (选填）\n                'top_menu'   =\u003e [\n                    '新闻中心'=\u003e [\n                        [\n                            'name'      =\u003e 'index',       //（必填）\n                            'title'     =\u003e '测试新闻中心',    //（必填）'\n                            'controller'=\u003e 'NewsController', //（必填）\n                            'sort'      =\u003e 1, //排序       //（选填）\n                            'icon'      =\u003e '', //图标        //（选填）\n                            'remark'    =\u003e '', //备注      //（选填）\n                            'status'    =\u003e 1, //状态        //（选填）\n                        ],\n                    ],\n                ],\n            ],\n        ];\n\n$menuGenerate = new Qscmf\\Utils\\MigrationHelper\\MenuGenerate();\n$menuGenerate-\u003einsertNavigationAll($data);\n\n// 撤销\n$menuGenerate-\u003einsertNavigationAllRollback($data);\n```\n\n+ 通过controller_title字段可自定义控制器title，默认为controller\n  \n  ```text\n  controller为英文，对用户来说不太好理解，使用自定义中文说明更友好。\n  ```\n\n```php\n$this-\u003enodeData = [\n    '新闻中心'=\u003e [\n              [\n                  'name'      =\u003e 'index',\n                  'title'     =\u003e '新闻分类',\n                  'controller'=\u003e 'NewsCate',\n                  'controller_title'=\u003e '新闻分类管理',\n              ],\n              [\n                  'name'      =\u003e 'index',\n                  'title'     =\u003e '内容管理',\n                  'controller'=\u003e 'News',\n                  'controller_title'=\u003e '新闻管理',\n                  'sort'      =\u003e 1,\n              ],\n    ],\n];\n\n$menuGenerate = new Qscmf\\Utils\\MigrationHelper\\MenuGenerate();\n$menuGenerate-\u003einsertAll($this-\u003enodeData);\n\n// 撤销\n$menuGenerate-\u003einsertAllRollback($this-\u003enodeData);\n```\n\n## \n\n## RefModel\n\n从关联表预提取关联数组（解决N+1循环取数导致数据库频繁访问的问题）\n\n#### API\n\n1. fill($data_ents, $key, $extra_where = null)\n   \n   \u003e 用处：给关联对象填充关联值\n   \u003e \n   \u003e data_ents 关联数据源\n   \u003e \n   \u003e key 从关联数据源提取关联表数据的键值\n   \u003e \n   \u003e extra_where 附加查询条件\n\n2. pick($value, $field = null, $callback = null)\n   \n   \u003e 用处：从关联对象中提取值\n   \u003e \n   \u003e value 关联数据源的对应数据，与fill方法的key对应\n   \u003e \n   \u003e field 指定提取的字段，默认null，表示提取所有字段\n   \u003e \n   \u003e callback 回调函数，接收一个参数，为关联数据中，field指定的数据， return 作为最终提取数据\n\n3. pickAll()\n   \n   \u003e 用处： 从关联对象中提取全部数据\n\n#### 用法\n\n一般用法\n\n```php\n$reader_ents = D(\"Reader\")-\u003ewhere(['status' =\u003e 1])-\u003eselect();\n$school_ref = new RefModel(D('School'), 'id'); //设置目标表的model类  设置目标表的关联id\n$school_ref-\u003efill($reader_ents, 'school_id'); // 通过$reader_ents的school_id预提取关联表的关联数据\n\nforeach($reader_ents as \u0026$v){\n    $v['school_name'] = $school_ref-\u003epick($v['school_id'], 'school_name'); //从预提取到的关联数据拿目标值, 当第二个参数传递null，则会返回包含表全部字段的数组\n}\n```\n\n高级用法\n\n```php\n//通过传递闭包函数来获取更复杂的关联数据\n//如用一般用法只能获取到读者头像id对应的本地图片路径，如果还需要进一步获取imageproxy的代理地址，则可传递一个闭包函数实现\n$reader_ents = D(\"Reader\")-\u003ewhere(['status' =\u003e 1])-\u003eselect();\n$pic_ref = new RefModel(D('FilePic'));\n$pic_ref-\u003efill($reader_ents, 'avatar'); \n\nforeach($reader_ents as \u0026$v){\n    $v['avatar_url'] = $pic_ref-\u003epick($v['avatar'], null, function($file_ent){\n        return \\Qscmf\\Utils\\Libs\\Common::imageproxy('100x100', $file_ent);\n    }); //闭包函数接收由第一二个参数决定的提取值，这里的imageproxy可以接收一条file_pic的数据库记录来拼接出图片的代理地址，因此我们可以第二个参数传递null来简化数据库的查询次数。\n}\n```\n\n```php\n//跨两张表查询数据\n//apply是读者申请表, return_reason表是申请退回原因定义表, 要查对着被退回的原因\n//status = 2是退回\n$reader_ents = D(\"Reader\")-\u003ewhere(['status' =\u003e 2])-\u003eselect();\n\n$apply_ref = new RefModel(D('Apply'), 'reader_id');\n$apply_ref-\u003efill($reader_ents, 'id'); \n\n$return_ref = new RefModel(D('ReturnReason'));\n$return_ref-\u003efill($apply_ref-\u003epickAll(), 'reason_id');\n\nforeach($reader_ents as \u0026$v){\n    $v['return_reason_text'] = $apply_ref-\u003epick($v['id'], 'reason_id', function($reason_id) use ($return_ref){\n        return $return_ref-\u003epick($reason_id, 'desc');\n    }); \n}\n```\n\n## \n\n## RedisLock\n\n基于Redis改造的悲观锁\n\n+ 先获取锁再执行业务逻辑，执行结束释放锁。\n+ 保证同一个方法的并发重复操作请求只有一个请求可以获取锁，在不进行高延迟事务处理的场景下可以使用。\n+ 若只要一次获取锁成功，其它等待的请求可以通过callback处理，提前退出\n  + ```text\n    情景举例\n    接口返回的数据使用了缓存，当发生缓存雪崩时，大量的请求就会直接发送到MySql，会导致MySql压力过大，响应缓慢。\n    解决方案是，在发生缓存雪崩时，使用悲观锁，只有一个请求能从MySql中获取数据，设置好缓存值后，其它请求不需要获取锁，直接返回缓存值即可。\n    ```\n\n##### lock\n\n```blade\n该方法可以获取锁\n\n参数 \n$key 名称\n$expire 过期时间 单位为秒\n$timeout  循环取锁时间 单位为秒，默认为0\n$interval 取锁失败后重试间隔时间 单位为微秒，默认为100000\ncallback  若回调返回有效值，则提前退出取锁流程\n          回调返回数据类型为数组，[$flag,$result]，若$flag为true，则返回$res，否则继续执行取锁流程\n\n返回值\n锁成功返回true 锁失败返回false\n```\n\n##### unlock\n\n```blade\n该方法可以释放锁\n\n参数 \n$key 名称\n\n返回值\n释放锁的个数\n```\n\n##### 代码示例\n\n```php\npublic function execShell(){\n    $redis_lock = \\Qscmf\\Utils\\Libs\\RedisLock::getInstance();\n    $is_lock = $redis_lock-\u003elock('exec_shell_lock_key', 60);\n    $is_lock === false \u0026\u0026 $this-\u003eerror('请一分钟后再操作');\n\n    shell_exec('ll \u003e/dev/null');\n\n    $redis_lock-\u003eunlock('exec_shell_lock_key');\n}\n```\n\n##### lockWithCallback\n```blade\n回调值无效则取锁\n\n参数 \nkey       名称\nexpire    过期时间 单位为秒\ncallback  若回调返回有效值，则提前退出取锁流程\n           回调返回数据类型为数组，[$flag,$result]，若$flag为true，则返回$result，否则继续执行取锁流程\ntimeout   循环取锁时间 单位为秒\ninterval  取锁失败后重试间隔时间 单位为微秒\n\n返回值为数组\n第一个值为锁情况，锁成功返回true 锁失败返回false，若不存在则为null\n第二个值为回调返回值，若不存在则为null\n两个值只会存在其中一种\n```\n##### 代码示例\n\n```php\npublic function getRes(){\n    $cache_data = S(\"api_cache_data\");\n    if(!$cache_data){\n        $redis_lock_cls = new RedisLock();\n        list($is_lock, $cache_data) = $redis_lock_cls-\u003elockWithCallback($this-\u003egenLockKey(),30, [$this,\"fetchCacheData\"],30, 100000);\n        if ($is_lock === false){\n            $res = ['info' =\u003e \"系统繁忙，请稍后再试\", 'status' =\u003e 0];\n        }elseif($is_lock === true){\n            // 业务逻辑           \n            $data = []; // 获取数据库数据\n            $res = ['info' =\u003e \"成功\", 'status' =\u003e 1, 'data' =\u003e $data];\n            S(\"api_cache_data\", json_encode($res));\n            $redis_lock_cls-\u003eunlock($this-\u003egenLockKey());\n        }else{\n            $res = $cache_data;\n        }\n    }else{\n        $res = $cache_data;\n    }\n\n    return $res;\n}\n\nprotected function genLockKey():string{\n    return 'api_redis_lock';\n}\n\npublic function fetchCacheData(){\n    $data = S(\"api_cache_data\");\n    $flag = is_array($data)\n    return [$flag, $data];\n}\n```\n\n## \n\n## 共享排他锁\n\n排他锁一般是基于某个key，只有一个进程可以持有。与其他的key的锁毫不相关。但有些业务场景，如基金配捐业务，基金有可配捐总额，为了避免并发问题，产生超出上总额的配捐数据，会对基金id上排他锁。平台也有个日配捐上限的设置，所有配捐共享这个上限值。如果此时管理员去修改日配捐上限，安全的做法应该要上一个配捐的总锁，避免在修改的过程中刚好有配捐业务导致数据错乱。此时这个总锁就要求和各个基金锁存在排他关系才能满足需求。共享排他锁就是为了满足这种需求而产生的工具。\n\n![流程图](https://github.com/quansitech/files/blob/master/share_exclusive_lock.png)\n\n#### API\n\n```php\n//排他锁\n$lock = new ExclusiveLock(string $key, in $expire = 5, int $timeout = 0)\n\n//上锁\n$lock-\u003elock();\n\n//解锁\n$lock-\u003eunlock();\n\n//注册共享锁\n$lock-\u003eregister(SharedLock $lock);\n\n\n//共享锁\n$share_lock = new SharedLock(string $key, int $type = self::TYPE_SHARED);\n```\n\n\n\n#### 用法\n\n```php\n//创建排他锁\n$all_lock = new ExclusiveLock('all_lock', 3600);\n//注册共享锁，并且该锁是独占类型。意思只要该排他锁生成，其余用了相同key的共享锁则不能产生\n$all_lock-\u003eregister(new SharedLock('single_lock', SharedLock::TYPE_EXCLUSIVE));\n$all_lock-\u003elock();\nsleep(10);\n$all_lock-\u003eunlock();\n\n\n//创建排他锁2\n$fund_lock = new ExclusiveLock('single_lock_1', 3600);\n//注册共享类型的共享锁，意思是相同key的共享类型共享锁可以存在多个，但和独占类型的共享锁互斥\n$fund_lock-\u003eregister(new SharedLock('single_lock', SharedLock::TYPE_SHARED));\n$fund_lock-\u003elock();\nsleep(10);\n$fund_lock-\u003eunlock();\n\n//创建排他锁3\n$fund_lock = new ExclusiveLock('single_lock_2', 3600);\n$fund_lock-\u003eregister(new SharedLock('single_lock', SharedLock::TYPE_SHARED));\n$fund_lock-\u003elock();\nsleep(10);\n$fund_lock-\u003eunlock();\n```\n\n简单说明下上面的代码，当all_lock类型的锁产生后，由于该锁同时持有独占类型的single_lock。那么single_lock_1和single_lock_2创建时将会发生堵塞，直到all_lock释放为止。反过来，如果single_lock_1先生成了，那么all_lock创建时也会发生堵塞。\n\nsingle_lock_1和single_lock_2由于持有single_lock的共享类型锁，所以它们之间不会发生堵塞。\n\n\n\n\n\n## imageproxy\n\n[imageproxy](https://github.com/willnorris/imageproxy) 是个图片裁剪、压缩、旋转的图片代理服务。框架集成了imageproxy全局函数来处理图片地址的格式化，通过.env来配置地址格式来处理不同环境下imageproxy的不同配置参数\n\n+ env的地址格式配置\n  \n  ```blade\n  IMAGEPROXY_URL={schema}://{domain}/ip/{options}/{remote_uri}\n  ```\n\n+ 占位符替换规则\n  \n  ```\n  占位符用{}包裹\n  schema 当前地址的协议类型 http 或者 https\n  domain 当前网站使用的域名\n  options 图片处理规则 https://godoc.org/willnorris.com/go/imageproxy#ParseOptions\n  remote_uri 代理的图片uri，如果外网图片，该占位符会替换成该地址，否则是网站图片的uri\n  path 网站图片的相对地址，如 http://localhost/Uploads/image/20190826/5d634f5f6570f.jpeg，path则为Uploads/image/20190826/5d634f5f6570f.jpeg\n  ```\n\n+ imageproxy全局函数\n  \n  ```php\n  // imageproxy图片格式处理\n  // options 图片处理规则\n  // file_id 图片id，若为ulr，则返回该url, 也可以是file_pic的数据库行记录（省略数据库查询操作）\n  // cache 默认为空，不开启缓存，否则可设置缓存时间，单位秒\n  // return 返回与.env配置格式对应的图片地址\n  \\Qscmf\\Utils\\Libs\\Common::imageproxy($options, $file_id, $cache)\n  ```\n\n如 IMAGEPROXY_URL={schema}://{domain}/ip/{options}/{remote_uri}\n\\Qscmf\\Utils\\Libs\\Common::imageproxy('100x150', 1)\n返回地址 http://localhost/ip/100x150/http://localhost/Uploads/image/20190826/5d634f5f6570f.jpeg\n\n如 IMAGEPROXY_URL={schema}://{domain}/ip/{options}/{path} (这种格式通常配合imageproxy -baseURL使用)\n返回地址 http://localhost/ip/100x150/Uploads/image/20190826/5d634f5f6570f.jpeg\n\n```\n+ 远程imageproxy代理\n\n有些项目，需要采用远程的一台服务器作为图片代理服务，此时可通过在.env设置IMAGEPROXY_REMOTE来设置远程服务器的域名\n```php\n//.env文件\nIMAGEPROXY_URL={schema}://{domain}/{options}/{remote_uri}\nIMAGEPROXY_REMOTE=http://www.test.com\n\n//imageporxy生成的地址\n$url = \\Qscmf\\Utils\\Libs\\Common::imageproxy('1920x540',$banner_id);\necho $url;\n//http://www.test.com/1920x540/http://localhost/Uploads/images/xxxx.jpg\n```\n\n## Common 公用函数\n\n+ imageproxy\n见imageproxy部分\n\n+ cached\n开箱即用的缓存工具，内部实现防缓存雪崩机制\n```php\n//参数说明\n//第一个参数为匿名函数，实现获取数据的业务逻辑\n//第二个参数为缓存过期时间，单位秒\n//第三个参数为缓存key，若为空则使用匿名函数的参数作为key\n//第四个参数为分组标识，若不为空，则产生的key将会归入该分组，使用clearCachedGroup方法可以清除该分组的缓存\n\n//用法举例\n//以下方法要从数据库读取数据，如果该页面是热点页，则无法承载太多的并发请求，需要针对其进行缓存\n$ent = D('Project')-\u003egetOneProject($map);\n\n//使用Common::cached方法快速实现该工作\n//以下为改造后效果\n//生成缓存函数便于重复使用\n$project_cached = Common::cached(function($map){\n    $ent = D('Project')-\u003egetOneProject($map);\n    return $ent;\n}, 60);\n\n//使用生成的缓存函数完成数据获取和缓存的工作\n$ent = $project_cached($map);\n\n//指定缓存key举例\n$project_cached = Common::cached(function($map){\n    $donate_amount = D('ProjectDonate')-\u003edonateAmount($map);\n    return $donate_amount;\n}, 3600, 'project_donate_amount_' . $project_id, 'project_donate_amount');\n\n//指定缓存key后，可实现对缓存值不落盘更新\n$redis = Cache::getInstance('redis');\n//incrByFloat 方法必须升级到think-core v13.3.0以上版本才能使用\n$redis-\u003eincrByFloat(\"project_donate_amount_{$project_id}\", $donate_amount);\n\n//清除分组缓存\nCommon::clearCachedGroup('project_donate_amount');\n```\n\n\n```\n\n\n## AuthNodeGenerate\n\n```text\n生成权限点\n\n使用权限点来限制字段、按钮的展示时，一般格式为：\n模块.控制器.方法名，如 admin.user.add\n```\n\n#### 用法\n\n+ 新增权限点\n\n若模块、控制器不存在则自动新增，它们的标题默认为名称，可以根据需要自定义标题。\n\n```php\n// 参数说明\n// $module_name 模块名\n// $controller_name 控制器名\n// $action_name 权限点名\n// $title 权限点标题\n// $pid 父节点，若为空则根据模块名、控制器名查找\n\nQscmf\\Utils\\MigrationHelper\\AuthNodeGenerate::addAuthNode('admin', 'user', 'add', '新增');\nQscmf\\Utils\\MigrationHelper\\AuthNodeGenerate::addAuthNode('admin', 'user', 'edit', '编辑');\n```\n\n```php\n// 修改模块、控制器标题\nQscmf\\Utils\\MigrationHelper\\AuthNodeGenerate::addAuthNode(['UserAdmin','用户'], ['user', '用户管理'], 'add', '新增');\n```\n\n+ 删除权限点\n\n```php\n// 参数说明\n// $module_name 模块名\n// $controller_name 控制器名\n// $action_name 权限点名，若为空则删除该控制器下的所有权限点\n\n// 只删除一个权限点\nQscmf\\Utils\\MigrationHelper\\AuthNodeGenerate::deleteAuthNode('admin', 'user', 'add');\nQscmf\\Utils\\MigrationHelper\\AuthNodeGenerate::deleteAuthNode('admin', 'user', 'edit');\n```\n\n```php\n// 删除控制器下所有权限点\nQscmf\\Utils\\MigrationHelper\\AuthNodeGenerate::deleteAuthNode('admin', 'user', '');\n```\n\n## AccessGenerate\n\nAccessGenerate 类是一个迁移助手工具，用于向数据库中插入或删除指定角色的权限点。\n\n### 方法列表\n\n#### `add(int $role_id, string $module, string $controller, string $action) : void`\n\n功能：向数据库中插入指定角色的权限点。\n\n- `$role_id`（整数类型）：角色ID，表示需要插入权限点的角色。\n- `$module`类型）：模块名称，表示权限点所属的模块。\n- `$controller`（字符串类型）：控器名称，表示权限点所的控制器。\n- `$action`（字符串类型）权限点名称，表示具权限点。\n\n返回值：无。\n\n#### `del(int $role_id, string $module, string $controller, string $action) : void`\n\n功能：从数据库中删除指定角色的权限点。\n\n参数：\n\n- `$role_id`（整数类型）：角色ID，表示需要删除权限点的角色。\n- `$module`字符串类型）：模块名称，表示需要删除权限点的模块。\n- `$controller`（字符串类型）控制名称，表示需要删除权限点的控制器。\n- `$action`（字符串类型）：权限点名称，表示具要删除的权限点。\n\n返回值：无。\n\n## DBComment\n```text\n给数据表及其字段添加/修改注释\n```\n\n#### 用法\n\n##### buildChangeSql\n```text\n根据注释映射数组生成一个更改数据表及其字段注释的DDL\n```\n```php\n\\Qscmf\\Utils\\Libs\\DBComment::buildChangeSql($comment_mapping);\n\n// 参数说明\n// $comment_mapping结构为\n// ['数据表名称'=\u003e['name'=\u003e'数据表名称','comment'=\u003e'数据表注释', 'column' =\u003e['字段名1'=\u003e'字段1注释','字段名2'=\u003e'字段2注释']]]\n\n```\n\n```php\n$comment_mapping = [\n        'migrations' =\u003e [\n            'name' =\u003e 'migrations',\n            'comment' =\u003e '数据迁移表',\n            'column' =\u003e [\n                'id' =\u003e '流水号，主键',\n                'migration' =\u003e '文件名',\n                'before' =\u003e '运行前执行情况',\n                'run' =\u003e '脚本执行情况',\n                'after' =\u003e '运行前执行情况',\n                'batch' =\u003e '批次',\n            ]\n        ],\n        'qs_access' =\u003e\n            [\n                'name' =\u003e 'qs_access',\n                'comment'=\u003e '用户组关联权限点表',\n                'column'=\u003e  [\n                    'role_id' =\u003e '用户组id,qs_role主键',\n                    'node_id' =\u003e '权限点id,qs_node主键',\n                    'level' =\u003e '权限点类型',\n                    'module' =\u003e '权限点名称',\n                ],\n            ],\n];\n\n\\Qscmf\\Utils\\MigrationHelper\\DBComment::buildChangeSql($comment_mapping);\n\n// 输出结果为一下内容，可使用information_schema.columns数据表核对字段定义部分\n/**\nALTER TABLE\n    `migrations` COMMENT = '数据迁移表',\n    CHANGE COLUMN `id` `id` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '流水号，主键',\n    CHANGE COLUMN `migration` `migration` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文件名',\n    CHANGE COLUMN `before` `before` TINYINT(1) NOT NULL COMMENT '运行前执行情况',\n    CHANGE COLUMN `run` `run` TINYINT(1) NOT NULL COMMENT '脚本执行情况',\n    CHANGE COLUMN `after` `after` TINYINT(1) NOT NULL COMMENT '运行前执行情况',\n    CHANGE COLUMN `batch` `batch` INT NOT NULL COMMENT '批次';\nALTER TABLE\n    `qs_access` COMMENT = '用户组关联权限点表',\n    CHANGE COLUMN `role_id` `role_id` SMALLINT UNSIGNED NOT NULL COMMENT '用户组id,qs_role主键',\n    CHANGE COLUMN `node_id` `node_id` SMALLINT UNSIGNED NOT NULL COMMENT '权限点id,qs_node主键',\n    CHANGE COLUMN `level` `level` TINYINT NOT NULL COMMENT '权限点类型',\n    CHANGE COLUMN `module` `module` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '权限点名称';\n**/\n ```\n\n## Scroller 滚动分页工具类\n\n滚动分页是基于每次获取的最后一条数据，去获取下一批数据，只要条件里包含里唯一键，就可以保证数据不重复，适用于数据量大，数据不断增加的场景。\n\n通过page的分页方式，当搜索到比较大的页码时，会导致查询时间过长，甚至超时，滚动分页可以避免这种情况。\n\n当数据并发量大时，滚动分页可以避免数据重复，保证用户体验。\n\n#### 用法\n\n```php\npublic function gets(){\n    $get_data = I('get.');\n    $count = $get_data['count'] ?: C(\"HOME_PER_PAGE_NUM\",null, 20);\n    \n    $order = 'sort asc,id desc';\n    $scroller = new Scroller($order);\n    \n    $map = [];\n    $map['status'] = \\Gy_Library\\DBCont::NORMAL_STATUS;\n\n    //检查参数里是否包含下一次的查询条件，有则调用applyLastCondition方法构造查询条件\n    if(isset($get_data['last_condition']) \u0026\u0026 !qsEmpty($get_data['last_condition'])){\n        $scroller-\u003eapplyLastCondition($map, $get_data['last_condition']);\n    }\n\n    $list = D(\"Gift\")-\u003egetGiftList($map, 1, $row_count, $order);\n    $res = [\n        'list' =\u003e $list,\n        'last_condition' =\u003e qsEmpty($list) ? \"\" : $scroller-\u003etoLastCondition($list[count($list) - 1]), //toLastCondition从最后一条数据生成下一次查询的条件\n    ];\n    \n    return new Response(\"获取成功\", 1, $res);\n}\n\n```\n\n## AnalysisCUD\n\n经常遇到一些场景，需要对一堆数据进行批量操作。前端操作完，提交到后端是一堆处理后的数据。里面混杂着要新增，更新，还可能隐含了要删除的数据。\n\n通常我们会根据提交上来的数据id，与数据库的数据进行比较，来判断是新增还是更新，还是删除。这个类就是为了简化这个操作。\n\n#### 用法\n\n```php\n$db_data = [\n    ['id' =\u003e 1, 'name' =\u003e 'item1'],\n    ['id' =\u003e 2, 'name' =\u003e 'item2'],\n    ['id' =\u003e 3, 'name' =\u003e 'item3']\n];\n\n$new_data = [\n    ['name' =\u003e 'item4'],       // 新增\n    ['id' =\u003e 2, 'name' =\u003e 'item2_updated'], // 更新\n    ['id' =\u003e 4, 'name' =\u003e 'item4'], // 新增\n];\n\n$cud = new AnalysisCUD($db_data, $new_data);\n$result = $cud-\u003eanalysis();\n\nprint_r($result);\n\n/*\nArray\n(\n    [to_insert] =\u003e Array\n        (\n            [0] =\u003e Array\n                (\n                    [name] =\u003e item4\n                )\n\n            [1] =\u003e Array\n                (\n                    [id] =\u003e 4\n                    [name] =\u003e item4\n                )\n\n        )\n\n    [to_update] =\u003e Array\n        (\n            [0] =\u003e Array\n                (\n                    [id] =\u003e 2\n                    [name] =\u003e item2_updated\n                )\n\n        )\n\n    [to_delete] =\u003e Array\n        (\n            [0] =\u003e Array\n                (\n                    [id] =\u003e 1\n                    [name] =\u003e item1\n                )\n\n            [1] =\u003e Array\n                (\n                    [id] =\u003e 3\n                    [name] =\u003e item3\n                )\n\n        )\n\n)\n*/\n```\n\n\n## 推送消息到钉钉群\n```shell\n当程序运行异常，需要开发者及时干预时，可以便捷发送到钉钉警报群通知相关人员，便于第一时间处理。\n\n如：Mysql 与 ES 数据同步差异较大。\n```\n\n#### 用法\n```php\n\\Qscmf\\Utils\\Libs\\DingTalkRobot::send(\"【系统标识】Mysql 与 ES 数据同步差异较大。\");\n```\n\n+ 配置 access_token\n  + 全局配置\n    ```env\n    DING_TALK_ACCESS_TOKEN=your access_token\n    ```\n  + 使用时替换，优先级高\n    ```php\n    \\Qscmf\\Utils\\Libs\\DingTalkRobot::send(\"【系统标识】Mysql 与 ES 数据同步差异较大。\", \"your access_token\");\n    ```\n    ","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fquansitech%2Fqscmf-utils","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fquansitech%2Fqscmf-utils","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fquansitech%2Fqscmf-utils/lists"}