Compare commits

...

155 Commits
2.5.0 ... 2.7.4

Author SHA1 Message Date
crazywhalecc
5ddc514545 update docs 2022-03-27 01:27:50 +08:00
sunxyw
3cfcbaec16 fix mb_strpos empty delimiter 2022-03-27 01:16:44 +08:00
sunxyw
50f06e6a4f add middleware params docs (#78) 2022-03-26 22:46:24 +08:00
Github Build Bot
c7e2b15629 increment build number 2022-03-26 07:44:43 +00:00
Jerry Ma
8e81f72584 Merge pull request #76 from zhamao-robot/feature/cron
新增 Cron 注解及修复部分 Bug
2022-03-26 15:44:29 +08:00
crazywhalecc
0fb5ed00f6 update docs 2022-03-26 14:59:13 +08:00
crazywhalecc
bf7920cc15 Add Cron annotation event 2022-03-26 14:51:57 +08:00
crazywhalecc
971f03ae0f Fix type convert error 2022-03-26 14:51:25 +08:00
Jerry Ma
24b9d93920 Merge pull request #75 from zhamao-robot/add-auto-build-number-workflow
添加构建工作流以自增构建编号
2022-03-26 01:49:43 +08:00
sunxyw
8d81f4d5df add build workflow to increment build number 2022-03-25 23:40:41 +08:00
Jerry Ma
b6e135a642 Merge pull request #74 from zhamao-robot/fix-onsetup-attribute-exec
Fix OnSetup Attribute execution bug (release 2.7.3)
2022-03-25 19:21:07 +08:00
crazywhalecc
d3f4ade215 Add Attribute and property (build 453, release 2.7.3) 2022-03-25 19:15:58 +08:00
crazywhalecc
56d2a52706 Fix OnSetup Attribute execution bug (build 452, release 2.7.3) 2022-03-25 19:02:43 +08:00
Jerry Ma
b40dc1e05e Merge pull request #71 from zhamao-robot/add-help-generator-docs
增加命令帮助生成器文档
2022-03-24 16:54:50 +08:00
Jerry Ma
bed3337866 Merge pull request #73 from zhamao-robot/integration-test-ignore-docs
修改集成测试配置以忽略文档变更
2022-03-24 16:40:48 +08:00
sunxyw
28dc73d060 Update integration-test.yml 2022-03-24 15:36:36 +08:00
sunxyw
5ea1226ad0 add help generator docs 2022-03-23 20:03:47 +08:00
sunxyw
e43f2a4158 update readme about php8 attribute (#70)
* update readme

* add docs about php8 attribute
2022-03-23 19:45:07 +08:00
sunxyw
d73e771ef3 fix help generator not working with alias (#69) 2022-03-23 19:44:16 +08:00
crazywhalecc
c077e9418c revert changes 2022-03-22 12:01:21 +08:00
crazywhalecc
2ce888c84c add robots.txt 2022-03-22 12:00:37 +08:00
crazywhalecc
b4d175ff98 fix docs problem 2022-03-22 11:52:12 +08:00
crazywhalecc
35e18d0481 add some irrelevant docs to expand white border 2022-03-22 01:26:53 +08:00
Jerry Ma
f6cccb00e3 Merge pull request #68 from zhamao-robot/document-upgrade
将文档重新部署和重构为 VuePress
2022-03-22 01:00:43 +08:00
Jerry Ma
2a82c82039 update to build 451
重构全局函数,统一函数命名,并补全注释
2022-03-22 01:00:20 +08:00
Jerry Ma
a2e8a1b582 update to build 451 2022-03-22 00:55:34 +08:00
crazywhalecc
dbd78d4b86 change ALL docs from MkDocs to VuePress!! 2022-03-22 00:51:03 +08:00
sunxyw
8105892b6e fix review 2022-03-21 23:28:57 +08:00
sunxyw
bc7d5871e4 replace deprecated functions 2022-03-21 20:49:09 +08:00
sunxyw
680e6a8c5f refactor global functions 2022-03-21 20:09:28 +08:00
Jerry Ma
c364084cb2 add command help generator (build 450)
增加命令帮助生成器 by @sunxyw
2022-03-21 14:13:56 +08:00
Jerry Ma
63f8042746 update to build 450 2022-03-21 12:52:48 +08:00
Jerry Ma
48e6b2cea0 update README.md [skip ci] 2022-03-21 05:04:37 +08:00
sunxyw
228309c571 fix cs 2022-03-21 04:46:54 +08:00
sunxyw
ca3a3df3ba add command help generator 2022-03-21 04:43:27 +08:00
crazywhalecc
c7df37b17c update install-runtime.sh 2022-03-21 02:58:59 +08:00
crazywhalecc
41f03fbba4 update docs 2022-03-21 01:25:26 +08:00
crazywhalecc
b3089c1bba add composer module support (build 449, 2.7.2) 2022-03-21 01:24:07 +08:00
crazywhalecc
c5a6f1fea4 update docs 2022-03-20 23:28:20 +08:00
crazywhalecc
ba2777137b let build command faster (build 448, 2.7.1) 2022-03-20 23:26:42 +08:00
crazywhalecc
6d90be164a update docs 2022-03-20 22:16:34 +08:00
crazywhalecc
4da6f5859a update to 2.7.0 release (build 447) 2022-03-20 22:12:58 +08:00
crazywhalecc
15d4ea710a add --no-state-check option (build 446) 2022-03-20 21:04:07 +08:00
crazywhalecc
f0541c1f32 Merge remote-tracking branch 'origin/master' 2022-03-20 19:05:54 +08:00
crazywhalecc
2b8cab1824 add AnnotationReader ignore name config (build 445, 2.7.0-beta5) 2022-03-20 19:05:13 +08:00
Jerry Ma
61c7972915 Update README.md 2022-03-20 17:09:24 +08:00
crazywhalecc
44a0eec74c change to integration-test 2022-03-20 17:00:37 +08:00
crazywhalecc
e57753e44b change to integration-test 2022-03-20 16:58:53 +08:00
crazywhalecc
74e91a2950 fix unpack autoload not working, change exclude_annotate to another name (build 444) 2022-03-20 16:51:48 +08:00
crazywhalecc
7ce3ef41df fix comment spacing problem (build 443) 2022-03-20 16:23:07 +08:00
crazywhalecc
444a77933a change autoload to hotload for phar hotload mode (build 442) 2022-03-20 16:20:14 +08:00
crazywhalecc
78f78c607d add packer namespace adjust (build 441) 2022-03-20 16:18:33 +08:00
crazywhalecc
69155002dc add site to gitignore 2022-03-20 16:13:51 +08:00
crazywhalecc
475d14fab7 update composer.json 2022-03-20 16:13:33 +08:00
crazywhalecc
f4d7e63358 update php cs fixer 2022-03-20 15:48:14 +08:00
crazywhalecc
f222d2b45b update composer requirement version 2022-03-20 15:43:31 +08:00
Jerry Ma
d0155fe1da Create code-style-analysis.yml 2022-03-20 15:35:10 +08:00
Jerry Ma
4737d0b507 Update and rename main.yml to mkdocs-deploy.yml 2022-03-20 15:23:05 +08:00
Jerry Ma
09bd0197bb Merge pull request #64 from sunxyw/patch-1
docs: add weather bot example
2022-03-20 15:20:59 +08:00
crazywhalecc
f0f120bd32 add Macroable (build 440) 2022-03-20 01:54:14 +08:00
crazywhalecc
c897da29c6 add PHP8 Attribute compatibility (build 439, 2.7.0-beta4) 2022-03-20 01:53:36 +08:00
sunxyw
e347e254e8 update docs index 2022-03-20 00:11:45 +08:00
crazywhalecc
12363aebf0 Merge remote-tracking branch 'origin/master' 2022-03-19 23:23:54 +08:00
crazywhalecc
ff0b925313 update docs 2022-03-19 23:23:14 +08:00
sunxyw
a6b4bd9b80 update weather bot example 2022-03-19 21:35:48 +08:00
sunxyw
485fa5476c add weather bot example 2022-03-19 21:25:10 +08:00
Jerry Ma
689076d97c Update mkdocs.yml 2022-03-19 20:33:13 +08:00
Jerry Ma
cca6102e91 Update README.md 2022-03-18 16:57:27 +08:00
Jerry Ma
095855162b Update mkdocs.yml 2022-03-18 16:48:30 +08:00
Jerry Ma
326f934013 Update mkdocs.yml 2022-03-18 16:44:04 +08:00
Jerry Ma
35b0c258fe Merge pull request #63 from sunxyw/patch-2
docs: add qingyunke chatbot integration example
2022-03-18 16:01:15 +08:00
sunxyw
6650846b15 update integrate-qingyunke-chatbot.md 2022-03-18 15:19:19 +08:00
sunxyw
a33d320f4c update integrate-qingyunke-chatbot.md 2022-03-18 02:54:23 +08:00
sunxyw
0bcfea6aa4 add qingyunke chatbot integration example 2022-03-18 02:30:45 +08:00
crazywhalecc
db6e63e91c fix Response class null error (build 438) 2022-03-17 20:48:09 +08:00
crazywhalecc
a7f84fb53a Merge remote-tracking branch 'origin/master' 2022-03-17 19:48:38 +08:00
crazywhalecc
ce7f2b1765 change ctx return force to ContextInterface (build 437) 2022-03-17 19:42:59 +08:00
Jerry Ma
abbfb59eff Merge pull request #62 from sunxyw/patch-2
docs: add missing module version example
2022-03-17 18:19:02 +08:00
sunxyw
b57fef16f9 add missing module version example 2022-03-17 01:53:06 +08:00
crazywhalecc
1706afbcd0 add cs fixer and PHPStan and activate it (build 436) 2022-03-15 18:05:33 +08:00
crazywhalecc
d01bd69aa5 update to 2.7.0-beta1 (build 435) 2022-03-13 22:50:32 +08:00
crazywhalecc
e6b9ae3ee1 add --watch function for no-installed-inotify users 2022-03-13 22:50:01 +08:00
crazywhalecc
73b6b8045d enhancement for process state 2022-03-13 22:47:11 +08:00
crazywhalecc
3c87abc6e8 enhancement for process state 2022-03-13 22:46:22 +08:00
crazywhalecc
e95925c129 add compatibility for PHP 8.1 2022-03-13 22:16:02 +08:00
crazywhalecc
82c44d6c40 enhancement for process state 2022-03-13 22:15:27 +08:00
crazywhalecc
e0a268e05e add autoload-dev auto scanner 2022-03-13 22:11:30 +08:00
crazywhalecc
487892e1d9 add force kill framework command --force 2022-03-13 22:05:53 +08:00
crazywhalecc
7ab4e88359 add KILLER PROMPT function 2022-03-13 22:05:23 +08:00
crazywhalecc
4702b6ee75 separate ProcessManager and WorkerManager 2022-03-13 22:04:52 +08:00
crazywhalecc
20cd3aa66d update docs 2022-03-13 22:03:52 +08:00
crazywhalecc
391114bdef update docs 2022-03-13 21:57:41 +08:00
crazywhalecc
ffe1052ecc add gitignore items 2022-03-13 21:54:43 +08:00
crazywhalecc
b4159152a7 add watcher 2022-01-08 20:19:10 +08:00
crazywhalecc
8fc6e4b0f7 update docs 2022-01-08 16:26:47 +08:00
crazywhalecc
c8938b7a4b update docs 2022-01-08 16:23:10 +08:00
crazywhalecc
34db1626a5 update to 2.6.6 (build 434) 2022-01-08 16:19:43 +08:00
crazywhalecc
7f0c97c5b9 update to 2.6.5 (build 433) 2021-12-28 22:40:40 +08:00
crazywhalecc
74050c46e7 update to 2.6.4 (build 432) 2021-12-25 19:21:41 +08:00
3ed1cb665a update to build 431 2021-12-22 14:39:46 +08:00
crazywhalecc
a6f33ba69d update to build 430 2021-12-08 21:29:38 +08:00
Jerry Ma
176b690417 Update v2.md 2021-12-07 13:28:21 +08:00
Jerry Ma
e22b1b90ec Update build-update.md 2021-12-07 13:27:33 +08:00
Jerry Ma
a3c560790c Merge pull request #55 from zhamao-robot/fix/cqafter
update to build 429
2021-12-07 13:25:55 +08:00
570e2108dd - 新增配置项 onebot.message_command_policy
- 新增 CQCommand 阻断策略的自定义配置功能
- 修复 CQAfter 无法正常使用的 bug #53
2021-12-07 13:21:29 +08:00
Jerry Ma
59c0d95e5d Create 2_Feature_request.yaml 2021-12-07 12:34:18 +08:00
Jerry Ma
9ceaecdc02 Update README.md 2021-12-07 12:17:47 +08:00
Jerry Ma
19d50898ef Create 1_Bug_report.yaml 2021-12-07 12:15:49 +08:00
Jerry Ma
5b62ca62ae Update README.md 2021-11-17 00:28:54 +08:00
fb528d30ce update docs 2021-11-16 16:51:05 +08:00
5f2d5ed334 update to build 428 2021-11-16 16:49:32 +08:00
c20e459900 update docs 2021-11-16 15:44:34 +08:00
09220825cf update to build 427 2021-11-16 15:41:01 +08:00
Jerry Ma
3d4db23d27 Update v2.md 2021-11-10 14:10:08 +08:00
Jerry Ma
4496b67dcc Update build-update.md 2021-11-10 14:09:18 +08:00
Jerry Ma
2a13298384 update to 2.5.8 (build 426) 2021-11-10 14:07:21 +08:00
Jerry Ma
d6ec404d76 Merge pull request #52 from YuFengZe/master
Update CQ.php
2021-11-10 14:06:13 +08:00
YuFengZe
3235fd4dc1 Update CQ.php 2021-11-08 22:05:41 +08:00
crazywhalecc
293740fee2 update to 2.5.7 (build 425) 2021-11-03 23:26:43 +08:00
crazywhalecc
d3c420ec84 update to build 424 (2.6.0-alpha1) 2021-11-02 16:01:24 +08:00
crazywhalecc
85ef09d43c Merge remote-tracking branch 'origin/master' 2021-11-01 00:29:11 +08:00
Jerry Ma
e4561d69c4 Merge pull request #45 from YuFengZe/master
Bug Fixed
2021-10-31 22:50:49 +08:00
Jerry Ma
6b4d206099 Update Hello.php 2021-10-31 22:50:12 +08:00
YuFengZe
50843edf6a Bug Fixed
解决发送“我是谁”却返回机器人信息的奇怪问题。
2021-10-31 22:01:14 +08:00
crazywhalecc
3a1686f8da update docs 2021-10-18 22:24:28 +08:00
crazywhalecc
66dd91bb97 update to 2.5.6 (build 423) 2021-10-17 23:55:02 +08:00
crazywhalecc
e020e5d593 update docs 2021-10-17 17:00:33 +08:00
crazywhalecc
3d62663281 update docs 2021-10-17 16:56:52 +08:00
crazywhalecc
8d9485c02e update docs 2021-10-17 15:37:55 +08:00
Jerry Ma
9fb45dd683 Update README.md 2021-10-15 09:38:07 +08:00
Jerry Ma
8a4924dba9 Update light-cache.md 2021-10-08 11:41:40 +08:00
crazywhalecc
beaf7be606 update to version 2.5.5 (buid 422) 2021-10-06 18:01:40 +08:00
Jerry Ma
7dbd21bdf4 Merge pull request #44 from furleywolf/patch-1
更新message-util的文档
2021-09-15 12:46:48 +08:00
furleywolf
9ce3056203 更新message-util的文档
发现漏掉了一个方法,还是补上去吧
2021-09-13 17:35:39 +08:00
crazywhalecc
880b4e847c update to version 2.5.4 (buid 421) 2021-09-11 16:27:16 +08:00
crazywhalecc
71e83d5bc8 update docs 2021-09-11 12:06:22 +08:00
crazywhalecc
432fd92cca update docs 2021-09-11 12:00:48 +08:00
crazywhalecc
907a9cea25 update to build 420 2021-09-11 11:59:02 +08:00
07391810ff update to build 419 2021-09-11 11:07:23 +08:00
5063421364 update docs 2021-09-10 11:28:56 +08:00
2b4d308783 update to build 418 2021-09-10 11:24:32 +08:00
e2f49968b3 Merge remote-tracking branch 'origin/master' 2021-09-01 14:14:13 +08:00
229778ebf9 update to build 417 2021-09-01 14:14:00 +08:00
Jerry Ma
d2c0972c93 Add actions badge [skip ci] 2021-08-31 13:40:29 +08:00
Jerry Ma
d300b6e518 Update main.yml 2021-08-31 13:39:11 +08:00
Jerry Ma
56cb7b2223 Update main.yml 2021-08-30 16:42:47 +08:00
Jerry Ma
2fc42d5d60 Update README.md 2021-08-30 16:40:53 +08:00
Jerry Ma
999e90f709 Update index.md 2021-08-30 16:17:13 +08:00
Jerry Ma
45c6cd7d2a Update main.yml 2021-08-30 16:12:35 +08:00
Jerry Ma
5aa0858021 Update main.yml 2021-08-30 16:05:15 +08:00
Jerry Ma
e981da3932 Update main.yml 2021-08-30 16:00:36 +08:00
Jerry Ma
2685be7306 Create main.yml 2021-08-30 11:27:51 +08:00
Jerry Ma
a13c4628f5 Update README.md 2021-07-15 11:37:35 +08:00
Jerry Ma
b36417454c Update README.md 2021-07-10 15:55:15 +08:00
crazywhalecc
a6f5952dee update to 2.5.1 (build 416) 2021-07-09 12:59:07 +08:00
272 changed files with 11159 additions and 5147 deletions

View File

@@ -0,0 +1,38 @@
name: 🐛 漏洞BUG报告
description: ⚠️ 请不要直接在此提交安全漏洞
labels: bug
body:
- type: input
id: affected-versions
attributes:
label: 受影响版本
placeholder: x.y.z
validations:
required: true
- type: textarea
id: description
attributes:
label: 描述
description: 请详细地描述您的问题
validations:
required: true
- type: textarea
id: reproduce-steps
attributes:
label: 复现步骤
description: |
请尽可能地提供可以复现此步骤的漏洞。
如果步骤过长或难以描述,您可以自行建立一个用于复现漏洞的仓库。
validations:
required: true
- type: textarea
id: possible-solution
attributes:
label: 解决方案
description: 如果您对这个漏洞的成因或修复有任何意见的话,请在此提出
- type: textarea
id: additional-context
attributes:
label: 附加信息
description: 其他可能有帮助的信息,如日志、截图等

View File

@@ -0,0 +1,19 @@
name: 🚀 功能建议
description: 新功能、改进的意见、草案
labels: enhancement
body:
- type: textarea
id: description
attributes:
label: 描述
description: 请提供简洁清楚的描述
validations:
required: true
- type: textarea
id: example
attributes:
label: 例子
description: |
一个简单的例子,展示该功能将如何被使用(包括代码、配置文件等)
如果这是针对已有功能的改进,请展示改进前后使用方式(或效能)的对比

30
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: Build
on:
pull_request:
branches:
- master
types:
- closed
paths:
- 'src/**.php'
jobs:
incremental-build-number:
if: github.event.pull_request.merged == true
name: Incremental Build Number
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Increment build number
run: sed -i -r 's/(.*)(\VERSION_ID\s=\s)([0-9]+)(.*)/echo "\1\2$((\3+1))\4"/ge' src/ZM/ConsoleApplication.php
- name: Commit change
run: |
git config --global user.name 'Github Build Bot'
git config --global user.email 'noreply@github.com'
git add src/ZM/ConsoleApplication.php
git commit -m "increment build number"
git push

65
.github/workflows/integration-test.yml vendored Normal file
View File

@@ -0,0 +1,65 @@
name: Integration Test
on:
push:
branches:
- master
paths-ignore:
- '**.md'
- 'docs/**'
pull_request:
branches:
- master
types:
- opened
- synchronize
- reopened
- ready_for_review
- review_requested
paths-ignore:
- '**.md'
- 'docs/**'
jobs:
integration:
name: Integration Test (PHP ${{ matrix.php-versions }}) (OS ${{ matrix.operating-system }})
runs-on: ${{ matrix.operating-system }}
strategy:
matrix:
operating-system: [ "ubuntu-latest", "macos-latest" ]
php-versions: [ "7.2", "7.3", "7.4", "8.0", "8.1" ]
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup PHP
uses: "shivammathur/setup-php@v2"
with:
php-version: ${{ matrix.php-versions }}
extensions: swoole, posix, json
- name: Setup problem matchers for PHP
run: echo "::add-matcher::${{ runner.tool_cache }}/php.json"
- name: Validate composer.json
run: "composer validate --strict"
- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install Composer Dependencies
run: "composer install --prefer-dist --no-progress --optimize-autoloader"
- name: Run Static Analysis
run: "composer analyse"
- name: Run PHP CS Fixer Check
run: "./vendor/bin/php-cs-fixer fix --dry-run --diff"

32
.github/workflows/vuepress-deploy.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: VuePress Auto Deploy
on:
push:
branches:
- master
jobs:
build:
name: Deploy docs
runs-on: ubuntu-latest
steps:
- name: Checkout master
uses: actions/checkout@v2
- name: Deploy docs to GitHub Pages
uses: jenkey2011/vuepress-deploy@master
env:
ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_SCRIPT: yarn && yarn docs:build
BUILD_DIR: docs/.vuepress/dist/
- name: Copy deployment to current folder
run: |
cp -r "${GITHUB_WORKSPACE}/docs/.vuepress/dist" "./deploy"
- name: Deploy to Zhamao Server
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.ZHAMAO_XIN_PRIVATE_KEY }}
ARGS: "-rltgoDzvO --delete"
SOURCE: "deploy/"
REMOTE_HOST: ${{ secrets.ZHAMAO_XIN_HOST }}
REMOTE_USER: ${{ secrets.ZHAMAO_XIN_USER }}
TARGET: ${{ secrets.ZHAMAO_XIN_TARGET }}

14
.gitignore vendored
View File

@@ -12,9 +12,23 @@ composer.lock
.daemon_pid
/runtime/
/tmp/
/temp/
/site/
# go-cqhttp快速安装启动相关可能被废弃
/ext/go-cqhttp/data/
/ext/go-cqhttp/logs/
/ext/go-cqhttp/config.hjson
/ext/go-cqhttp/device.json
/ext/go-cqhttp/go-cqhttp
/ext/go-cqhttp/session.token
.zm_worker_*.pid
# Git Hook 的相关锁文件
cghooks.lock
# Vuepress 相关文件
/node_modules/
/docs/.vuepress/dist/
package-lock.json

75
.php-cs-fixer.php Normal file
View File

@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
/**
* @since 2.7.0
*/
return (new PhpCsFixer\Config())
->setRiskyAllowed(true)
->setRules([
'@PSR12' => true,
'@Symfony' => true,
'@PhpCsFixer' => true,
'array_syntax' => [
'syntax' => 'short',
],
'list_syntax' => [
'syntax' => 'short',
],
'concat_space' => [
'spacing' => 'one',
],
'blank_line_before_statement' => [
'statements' => [
'declare',
],
],
'ordered_imports' => [
'imports_order' => [
'class',
'function',
'const',
],
'sort_algorithm' => 'alpha',
],
'single_line_comment_style' => [
'comment_types' => [
],
],
'yoda_style' => [
'always_move_variable' => false,
'equal' => false,
'identical' => false,
],
'multiline_whitespace_before_semicolons' => [
'strategy' => 'no_multi_line',
],
'constant_case' => [
'case' => 'lower',
],
'class_attributes_separation' => true,
'combine_consecutive_unsets' => true,
'declare_strict_types' => true,
'linebreak_after_opening_tag' => true,
'lowercase_static_reference' => true,
'no_useless_else' => true,
'no_unused_imports' => true,
'not_operator_with_successor_space' => false,
'not_operator_with_space' => false,
'ordered_class_elements' => true,
'php_unit_strict' => false,
'phpdoc_separation' => false,
'single_quote' => true,
'standardize_not_equals' => true,
'multiline_comment_opening_closing' => true,
'phpdoc_summary' => false,
])
->setFinder(
PhpCsFixer\Finder::create()
->exclude('vendor')
->exclude('docs')
->in(__DIR__ . '/src')
)
->setUsingCache(false);

View File

@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Reload Zhamao Server" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="./zhamao server:reload &amp;&amp; exit" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
</component>

View File

@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run Zhamao Server" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="./zhamao server" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="./zhamao" />
<option name="SCRIPT_OPTIONS" value="server" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
</component>

17
.run/Run watcher.run.xml Normal file
View File

@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run watcher" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="bin/watcher" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/bin/zsh" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
</component>

View File

@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Stop Zhamao Server" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="./zhamao server:stop &amp;&amp; exit" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
</component>

View File

@@ -7,6 +7,7 @@
[![zhamao License](https://img.shields.io/hexpm/l/plug.svg?maxAge=2592000)](https://github.com/zhamao-robot/zhamao-framework/blob/master/LICENSE)
[![Latest Stable Version](http://img.shields.io/packagist/v/zhamao/framework.svg)](https://packagist.org/packages/zhamao/framework)
[![Banner](https://img.shields.io/badge/OneBot-v11-success)](https://github.com/howmanybots/onebot)
![Integration Test](https://github.com/zhamao-robot/zhamao-framework/actions/workflows/integration-test.yml/badge.svg)
[![注解数量](https://img.shields.io/github/search/zhamao-robot/zhamao-framework/AnnotationBase.svg)](https://github.com/zhamao-robot/zhamao-framework/search?q=AnnotationBase)
[![TODO 数量](https://img.shields.io/github/search/zhamao-robot/zhamao-framework/TODO.svg)](https://github.com/zhamao-robot/zhamao-framework/search?q=TODO)
@@ -15,12 +16,14 @@
开发者 QQ 群:**670821194** [点击加入群聊](https://jq.qq.com/?_wv=1027&k=YkNI3AIr)
**如果有愿意一起开发框架本身的开发者,请提出 PR 或 Issue 参与开发!如果对框架本身的核心设计有更好的想法,可与作者成立开发组(目前仅作者 1 人),参与 OneBot V12 生态和框架本身的开发。**
**如果有愿意一起开发框架本身的开发者,请提出 PR 或 Issue 参与开发!如果对框架本身的核心设计有更好的想法,可与作者成立开发组(目前仅 2 人),参与 OneBot V12 生态和框架本身的开发。**
**相关正在进行的版本任务见 Projects 一栏!**
## 简介
炸毛框架使用 PHP 编写,采用 Swoole 扩展为基础,主要面向 API 服务聊天机器人OneBot 兼容的 QQ 机器人对接),包含 Websocket、HTTP 等监听和请求库,用户代码采用模块化处理,使用注解可以方便地编写各类功能。
炸毛框架使用 PHP 编写,采用 Swoole 扩展为基础,主要面向 API 服务聊天机器人OneBot 兼容的 QQ 机器人对接),包含 Websocket、HTTP
等监听和请求库,用户代码采用模块化处理,使用注解可以方便地编写各类功能。
框架主要用途为 HTTP 服务器,机器人搭建框架。尤其对于 QQ 机器人消息处理较为方便和全面,提供了众多会话机制和内部调用机制,可以以各种方式设计你自己的模块。
@@ -39,48 +42,54 @@ public function index() {
}
```
> 从 2.7.0 版本开始,框架已支持同时使用 Annotation 和原生 Attribute 注解,供开发者根据需要自由选用。
## 开始
如果你是初学者,可以直接使用以下脚本部署 PHP 环境和安装框架的脚手架:
```bash
# 新建一个自己喜欢名字的文件夹,运行一键安装脚本
# 新建一个自己喜欢名字的文件夹,运行一键安装脚本 (仅限 x86-64(AMD64) 和 AArch64(ARM64) 平台)
mkdir zhamao-app/
cd zhamao-app/
# 默认安装的 PHP 版本为 7.4,如需使用其他版本,请设置环境变量 ZM_DOWN_PHP_VERSION 为对应的 PHP 版本,例如:
# export ZM_DOWN_PHP_VERSION=8.1
bash -c "$(curl -fsSL https://api.zhamao.xin/go.sh)"
# 启动
runtime/php vendor/bin/start server
./zhamao server:start
```
## 文档v2 版本)
查看文档(国内自建):<https://docs-v2.zhamao.xin/>
关于其他安装方式,请参阅[文档](https://framework.zhamao.xin/guide/installation.html) 。
备用链接(国外托管):<https://docs-v2.zhamao.me/>
## 文档
自行构建文档:`mkdocs build -d distribute`
查看文档(国内自建):<https://framework.zhamao.xin/>
备用链接(国外托管):<https://framework.zhamao.me/>
## 特点
- 原生为多账号设计,支持多个机器人负载均衡
- 原生支持多个机器人客户端同时连接
- 使用 Swoole 多工作进程机制和协程加持,尽可能简单的情况下提升了性能
- 灵活的注解事件绑定机制
- 灵活的注解事件绑定机制,可同时使用 Annotation 和原生 Attribute 注解
- 易用的上下文,模块内随处可用
- 采用模块化编写,可自由搭配其他 composer 组件
- 采用模块化编写,可自由搭配其他 Composer 组件,也可单文件面向过程编写
- 支持模块打包、热加载,分享模块更方便
- 常驻内存,全局缓存变量随处使用,提供多种缓存方案
- 自带 MySQL、Redis 等数据库连接池等数据库连接方案
- 本身为 HTTP 服务器、WebSocket 服务器,可以构建属于自己的 HTTP API 接口
- 静态文件服务器,可将前端合并到一起
- 自带 PHP + Swoole 环境无需手动编译安装by [crazywhalecc/static-php-cli](https://github.com/crazywhalecc/static-php-cli)
## 从 v1 升级
炸毛框架 v2 相对 v1 版本改动了不少内容,其中包括框架底层机制、注解事件分发、调试、命名空间等变化,详情可查看上方文档。
如果旧版框架使用过程中无问题且对新功能暂无需求,可以继续使用 v1 版本,后续也将维护安全类更新和修复致命 bug。
## 下载源码
框架源码可直接克隆本仓库进行编辑,如果你在国内,访问 GitHub 和 clone 仓库比较慢,可以将 `github.com` 替换为 `fgit.zhamao.me` 进行加速。
框架源码可直接克隆本仓库进行编辑,如果你在国内,访问 GitHub 和克隆仓库比较慢,可以将 `github.com` 替换为 `fgit.zhamao.me` 进行加速。
例如:`git clone https://fgit.zhamao.me/zhamao-robot/zhamao-framework.git --depth 1`
## 贡献和捐赠
如果你在使用过程中发现任何问题,可以提交 Issue 或自行 Fork 后修改并提交 Pull Request。
目前项目仅一人维护,耗费精力较大,所以非常欢迎对框架的贡献。
@@ -89,17 +98,20 @@ runtime/php vendor/bin/start server
我们会将捐赠的资金用于本项目驱动的炸毛机器人和框架文档的服务器开销上。[捐赠列表](https://github.com/zhamao-robot/thanks)
如果您不想直接参与框架的开发,也可以分享你编写的模块,帮助完善框架生态。
### 支付宝
![支付宝二维码](https://cdn.jsdelivr.net/gh/zhamao-robot/zhamao-framework/resources/images/alipay_img.jpg)
如果你对我们的周边感兴趣,我们还有炸毛机器人定制 logo 的雨伞,详情咨询作者 QQ我们会作为您捐助了本项目
## 关于
框架和 SDK 是 炸毛机器人 项目的核心框架开源部分。炸毛机器人是作者写的一个高性能机器人,曾获全国计算机设计大赛一等奖。
作者的炸毛机器人已从2018年初起稳定运行了**三年**,并且持续迭代。
作者的炸毛机器人已从2018年初起稳定运行了**四年半**,并且持续迭代。
欢迎随时在 HTTP-API 插件群里提问,当然更好的话可以加作者 QQ[627577391](http://wpa.qq.com/msgrd?v=3&uin=627577391&site=qq&menu=yes)或提交 Issue 进行疑难解答。
欢迎随时在 HTTP-API 插件群里提问,当然更好的话可以加作者 QQ[627577391](http://wpa.qq.com/msgrd?v=3&uin=627577391&site=qq&menu=yes)
或提交 [Issue](https://github.com/zhamao-robot/zhamao-framework/issues/new/choose) 进行疑难解答。
本项目在更新内容时,请及时关注 GitHub 动态,更新前请将自己的模块代码做好备份。
@@ -109,4 +121,10 @@ runtime/php vendor/bin/start server
在贡献代码时,请保管好自己的全局配置文件中的敏感信息,请勿将带有个人信息的配置文件上传 GitHub 等网站。
![star](https://starchart.cc/zhamao-robot/zhamao-framework.svg)
感谢 JetBrains 为此开源项目提供 PhpStorm 开发工具支持:
<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/PhpStorm.svg" width="300">
感谢 [php-libonebot](https://github.com/botuniverse/php-libonebot) 开发者 @sunxyw 中为项目开发规范化提出的一些建议。
<!-- ![star](https://starchart.cc/zhamao-robot/zhamao-framework.svg) -->

1
bin/phpunit-swoole Executable file → Normal file
View File

@@ -59,6 +59,7 @@ require PHPUNIT_COMPOSER_INSTALL;
$starttime = microtime(true);
go(function () {
try {
require_once __DIR__.'/../test/bootstrap.php';
PHPUnit\TextUI\Command::main(false);
} catch (Exception $e) {
echo $e->getMessage() . PHP_EOL;

118
bin/watcher Executable file
View File

@@ -0,0 +1,118 @@
#!/usr/bin/env bash
_red="\e[33m"
_green="\e[32m"
_reset="\e[0m"
unix_s=$(uname -s)
unix_release=$(
marked_release=""
if [ "$unix_s" = "Linux" ]; then
echo $HOME | grep com.termux >/dev/null
if [ $? == 0 ]; then
marked_release="termux"
elif [ -f "/etc/redhat-release" ]; then
if [ "$(cat /etc/redhat-release | awk '{print $1}' | grep -v '^$')" = "CentOS" ]; then
marked_release="CentOS"
else
marked_release="unknown"
fi
elif [ -f "/etc/os-release" ]; then
cat /etc/os-release | grep Alpine > /dev/null
if [ $? == 0 ]; then
marked_release="Alpine"
fi
fi
if [ "$marked_release" = "" ]; then
if [ -f "/etc/issue" ]; then
marked_release=$(cat /etc/issue | grep -v '^$' | awk '{print $1}')
else
marked_release="unknown"
fi
fi
elif [ "$unix_s" = "Darwin" ]; then
marked_release=$(sw_vers | grep ProductName | awk '{print $2" "$3" "$4}')
fi
echo $marked_release
)
unix_release=$(echo $unix_release | xargs)
function echo_error() {
echo -e "${_red}$1${_reset}"
}
function echo_info() {
echo -e "${_green}$1${_reset}"
}
function install_test() {
which fswatch >/dev/null
if [ $? != 0 ]; then
operate_confirm "fswatch还没有安装是否确认安装" && install_fswatch
fi
}
function install_fswatch() {
if [ "$unix_s" = "Linux" ]; then
case $unix_release in
"Kali" | "Ubuntu" | "Debian" | "Raspbian" | 'Pop!_OS')
sudo apt-get install fswatch -y ;;
#"termux") pkg install $1 -y ;;
"CentOS")
curl -o fswatch.tgz https://download.fastgit.org/emcrisostomo/fswatch/releases/download/1.16.0/fswatch-1.16.0.tar.gz && \
tar -xzf fswatch.tgz && \
cd fswatch-1.16.0 && \
./configure && \
make && \
sudo make install && \
cd .. && \
rm -rf fswatch.tgz fswatch-1.16.0
;;
#"Alpine") apk add $1 ;;
*)
echo_error "不支持的 Linux 发行版:$unix_release"
exit 1
;;
esac
elif [ "$unix_s" = "Darwin" ]; then
brew install fswatch
else
echo_error "不支持的操作系统:$unix_s"
exit 1
fi
}
function operate_confirm() {
echo -n $(echo_info "$1 [Y/n] ")
read operate
operate=$(echo $operate | tr A-Z a-z)
if [[ "$operate" = "y" || "$operate" = "" ]]; then
return 0
else
return 1
fi
}
echo_info "当前系统:$unix_release"
install_test
if [ $? -ne 0 ]; then
exit 1
else
echo_info "程序路径:$(which fswatch)"
fi
watch_dir="./src"
if [ ! -d "$watch_dir" ]; then
echo_error "src目录不存在"
exit 1
else
echo_info "监听目录:$watch_dir"
fi
_pid=$(cat .daemon_pid | awk -F"\"pid\": " '{print $2}' | grep -v ^$ | sed 's/,//g')
if [ "$_pid" = "" ]; then
echo_error "未检测到框架进程"
exit 1
fi
fswatch $watch_dir | while read file
do
echo "Detect file change: $file"
kill -USR1 $_pid
done

View File

@@ -4,9 +4,22 @@
"minimum-stability": "stable",
"license": "Apache-2.0",
"extra": {
"exclude_annotate": [
"src/ZM"
]
"zm": {
"exclude-annotation-path": [
"src/ZM"
]
},
"hooks": {
"post-merge": "composer install",
"pre-commit": [
"echo committing as $(git config user.name)",
"./vendor/bin/php-cs-fixer fix --dry-run --diff ./src"
],
"pre-push": [
"./vendor/bin/php-cs-fixer fix --dry-run --diff ./src",
"composer analyse"
]
}
},
"authors": [
{
@@ -17,25 +30,27 @@
"prefer-stable": true,
"bin": [
"bin/start",
"bin/phpunit-swoole"
"bin/phpunit-swoole",
"bin/watcher"
],
"require": {
"php": ">=7.2",
"php": "^7.2 || ^7.3 || ^7.4 || ^8.0 || ^8.1",
"ext-json": "*",
"ext-posix": "*",
"doctrine/annotations": "~1.12 || ~1.4.0",
"doctrine/dbal": "^2.13.1",
"dragonmantank/cron-expression": "^3.3",
"jelix/version": "^2.0",
"koriym/attributes": "^1.0",
"psy/psysh": "^0.11.2",
"symfony/console": "~5.0 || ~4.0 || ~3.0",
"symfony/polyfill-ctype": "^1.19",
"symfony/polyfill-mbstring": "^1.19",
"symfony/console": "~5.0 || ~4.0 || ~3.0",
"symfony/polyfill-php80": "^1.16",
"symfony/routing": "~5.0 || ~4.0 || ~3.0",
"zhamao/console": "^1.0",
"zhamao/config": "^1.0",
"zhamao/request": "^1.1",
"zhamao/connection-manager": "^1.0",
"jelix/version": "^2.0",
"league/climate": "^3.6",
"psy/psysh": "@stable",
"doctrine/orm": "^2.9"
"zhamao/console": "^1.0",
"zhamao/request": "^1.1"
},
"suggest": {
"ext-ctype": "Use C/C++ extension instead of polyfill will be more efficient",
@@ -52,8 +67,28 @@
"src/ZM/global_functions.php"
]
},
"autoload-dev": {
"psr-4": {
"Module\\": "src/Module",
"Custom\\": "src/Custom"
}
},
"config": {
"optimize-autoloader": true,
"sort-packages": true
},
"require-dev": {
"swoole/ide-helper": "@dev",
"phpunit/phpunit": "^8.5 || ^9.0"
"brainmaestro/composer-git-hooks": "^2.8",
"friendsofphp/php-cs-fixer": "^3.2 != 3.7.0",
"phpstan/phpstan": "^1.1",
"phpunit/phpunit": "^8.5 || ^9.0",
"swoole/ide-helper": "^4.5"
},
"scripts": {
"post-install-cmd": [
"[ $COMPOSER_DEV_MODE -eq 0 ] || vendor/bin/cghooks add"
],
"analyse": "phpstan analyse --memory-limit 300M -l 0 ./src",
"cs-fix": "php-cs-fixer fix $1"
}
}

View File

@@ -1,79 +1,76 @@
<?php
/** @noinspection PhpFullyQualifiedNameUsageInspection */
/** @noinspection PhpComposerExtensionStubsInspection */
/** bind host */
declare(strict_types=1);
/* bind host */
$config['host'] = '0.0.0.0';
/** bind port */
/* bind port */
$config['port'] = 20001;
/** 框架开到公网或外部的HTTP访问链接通过 DataProvider::getFrameworkLink() 获取 */
/* 框架开到公网或外部的HTTP访问链接通过 DataProvider::getFrameworkLink() 获取 */
$config['http_reverse_link'] = 'http://127.0.0.1:' . $config['port'];
/** 框架是否启动debug模式当debug模式为true时启用热更新需要安装inotify扩展 */
/* 框架是否启动debug模式当debug模式为true时启用热更新需要安装inotify扩展 */
$config['debug_mode'] = false;
/** 存放框架内文件数据的目录 */
/* 存放框架内文件数据的目录 */
$config['zm_data'] = realpath(WORKING_DIR) . '/zm_data/';
/** 存放各个模块配置文件的目录 */
/* 存放各个模块配置文件的目录 */
$config['config_dir'] = $config['zm_data'] . 'config/';
/** 存放崩溃和运行日志的目录 */
/* 存放崩溃和运行日志的目录 */
$config['crash_dir'] = $config['zm_data'] . 'crash/';
/** 对应swoole的server->set参数 */
/* 对应swoole的server->set参数 */
$config['swoole'] = [
'log_file' => $config['crash_dir'] . 'swoole_error.log',
//'worker_num' => swoole_cpu_num(), //如果你只有一个 OneBot 实例连接到框架并且代码没有复杂的CPU密集计算则可把这里改为1使用全局变量
'dispatch_mode' => 2, //包分配原则,见 https://wiki.swoole.com/#/server/setting?id=dispatch_mode
// 'worker_num' => swoole_cpu_num(), //如果你只有一个 OneBot 实例连接到框架并且代码没有复杂的CPU密集计算则可把这里改为1使用全局变量
'dispatch_mode' => 2, // 包分配原则,见 https://wiki.swoole.com/#/server/setting?id=dispatch_mode
'max_coroutine' => 300000,
'max_wait_time' => 5
//'task_worker_num' => 4,
//'task_enable_coroutine' => true
'max_wait_time' => 5,
// 'task_worker_num' => 4,
// 'task_enable_coroutine' => true
];
/** 一些框架与Swoole运行时设置的调整 */
/* 一些框架与框架运行时设置的调整 */
$config['runtime'] = [
'swoole_coroutine_hook_flags' => SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL),
'swoole_server_mode' => SWOOLE_PROCESS
'swoole_server_mode' => SWOOLE_PROCESS,
'middleware_error_policy' => 1,
'reload_delay_time' => 800,
'global_middleware_binding' => [],
'save_console_log_file' => false, // 改为目标路径,则将 Console 输出的日志保存到文件
'annotation_reader_ignore' => [ // 设置注解解析器忽略的注解名或命名空间,防止解析到不该解析的
'name' => [
'mixin',
],
'namespace' => [],
],
];
/** 轻量字符串缓存,默认开启 */
/* 轻量字符串缓存,默认开启 */
$config['light_cache'] = [
'size' => 512, //最多允许储存的条数需要2的倍数
'max_strlen' => 32768, //单行字符串最大长度需要2的倍数
'hash_conflict_proportion' => 0.6, //Hash冲突率越大越好但是需要的内存更多
'size' => 512, // 最多允许储存的条数需要2的倍数
'max_strlen' => 32768, // 单行字符串最大长度需要2的倍数
'hash_conflict_proportion' => 0.6, // Hash冲突率越大越好但是需要的内存更多
'persistence_path' => $config['zm_data'] . '_cache.json',
'auto_save_interval' => 900
'auto_save_interval' => 900,
];
/** 大容量跨进程变量存储2.2.0可用) */
/* 大容量跨进程变量存储2.2.0可用) */
$config['worker_cache'] = [
'worker' => 0,
'transaction_timeout' => 30000
'transaction_timeout' => 30000,
];
/** @deprecated 放弃使用,旧版数据库,请使用 mysql_config 和 doctrine/dbal 搭配使用 */
$config['sql_config'] = [
'sql_host' => '',
'sql_port' => 3306,
'sql_username' => 'name',
'sql_database' => 'db_name',
'sql_password' => '',
'sql_options' => [
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_EMULATE_PREPARES => false
],
'sql_no_exception' => false,
'sql_default_fetch_mode' => PDO::FETCH_ASSOC //added in 1.5.6
];
/** MySQL数据库连接信息host留空则启动时不创建sql连接池 */
/* MySQL数据库连接信息host留空则启动时不创建sql连接池 */
$config['mysql_config'] = [
'host' => '',
'port' => 33306,
'port' => 3306,
'unix_socket' => null,
'username' => 'root',
'password' => '123456',
@@ -82,72 +79,74 @@ $config['mysql_config'] = [
'pool_size' => 64,
'options' => [
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
],
];
/** Redis连接信息host留空则启动时不创建Redis连接池 */
/* Redis连接信息host留空则启动时不创建Redis连接池 */
$config['redis_config'] = [
'host' => '',
'port' => 6379,
'timeout' => 1,
'db_index' => 0,
'auth' => ''
'auth' => '',
];
/** onebot连接约定的token */
/* onebot连接约定的token */
$config['access_token'] = '';
/** HTTP服务器固定请求头的返回 */
/* HTTP服务器固定请求头的返回 */
$config['http_header'] = [
'Server' => 'zhamao-framework',
'Content-Type' => 'text/html; charset=utf-8'
'Content-Type' => 'text/html; charset=utf-8',
];
/** HTTP服务器在指定状态码下回复的页面默认 */
/* HTTP服务器在指定状态码下回复的页面默认 */
$config['http_default_code_page'] = [
'404' => '404.html'
'404' => '404.html',
];
/** zhamao-framework在框架启动时初始化的atomic们 */
/* zhamao-framework在框架启动时初始化的atomic们 */
$config['init_atomics'] = [
//'custom_atomic_name' => 0, //自定义添加的Atomic
// 'custom_atomic_name' => 0, //自定义添加的Atomic
];
/** 终端日志显示等级0-4 */
/* 终端日志显示等级0-4 */
$config['info_level'] = 2;
/** 上下文接口类 implemented from ContextInterface */
/* 上下文接口类 implemented from ContextInterface */
$config['context_class'] = \ZM\Context\Context::class;
/** 静态文件访问 */
/* 静态文件访问 */
$config['static_file_server'] = [
'status' => false,
'document_root' => realpath(__DIR__ . '/../') . '/resources/html',
'document_index' => [
'index.html'
]
'index.html',
],
];
/** 机器人解析模块关闭后无法使用如CQCommand等注解(上面的modules即将废弃) */
/* 机器人解析模块关闭后无法使用如CQCommand等注解(上面的modules即将废弃) */
$config['onebot'] = [
'status' => true,
'single_bot_mode' => false,
'message_level' => 99999
'message_level' => 99,
'message_convert_string' => true,
'message_command_policy' => 'interrupt',
];
/** 一个远程简易终端使用nc直接连接即可但是不建议开放host为0.0.0.0(远程连接) */
/* 一个远程简易终端使用nc直接连接即可但是不建议开放host为0.0.0.0(远程连接) */
$config['remote_terminal'] = [
'status' => false,
'host' => '127.0.0.1',
'port' => 20002,
'token' => ''
'token' => '',
];
/** 模块(插件)加载器的相关设置 */
/* 模块(插件)加载器的相关设置 */
$config['module_loader'] = [
'enable_hotload' => false,
'load_path' => $config['zm_data'] . 'modules'
'load_path' => $config['zm_data'] . 'modules',
];
return $config;

View File

@@ -0,0 +1,141 @@
<template>
<div class="doc-chat-container">
<div class="doc-chat-content">
<div v-for="i in chat" v-bind="i">
<div class="doc-chat-row" v-if="i.type === 0">
<div class="doc-chat-box">{{ i.content }}</div>
<img class="doc-chat-avatar" src="http://api.btstu.cn/sjtx/api.php" alt=""/>
</div>
<div class="doc-chat-row doc-chat-row-robot" v-else-if="i.type === 1">
<img class="doc-chat-avatar" src="https://docs-v1.zhamao.xin/logo.png" alt=""/>
<div class="doc-chat-box doc-chat-box-robot">
<span v-for="(p,index) in i.content.split('\n')">{{p}}<br v-if="index !== i.content.length - 1"></span>
</div>
</div>
<div class="doc-chat-row doc-chat-banner" v-else-if="i.type === 2">
{{ i.content }}
</div>
<div class="doc-chat-row doc-chat-row-robot" v-else-if="i.type === 3">
<img class="doc-chat-avatar" src="https://docs-v1.zhamao.xin/logo.png" alt=""/>
<div class="doc-chat-box doc-chat-box-robot">
<img :src="i.content" alt="" />
</div>
</div>
</div>
</div>
</div>
</template>
<!--
type:
0: 我方发送消息
1: 机器人回复消息
2: 显示一个横幅系统消息
3: 机器人回复一个图片
-->
<script>
export default {
name: "ChatBox",
props: ['myChats'],
data() {
return {
chat: this.myChats,
multiline: ''
}
}
}
</script>
<style scoped>
.doc-chat-content {
padding: 12px;
}
.doc-chat-container {
border-radius: 6px;
max-width: 550px;
min-height: 30px;
/*noinspection CssUnresolvedCustomProperty*/
background-color: #f2f4f5;
margin-bottom: 1em;
box-shadow: 0 3px 1px -2px rgba(0,0,0,.2), 0 2px 2px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12);
}
.doc-chat-row {
margin: 0;
display: flex;
flex-wrap: wrap;
flex: 1 1 auto;
justify-content: flex-end;
}
.doc-chat-banner {
justify-content: center;
background: rgba(0,0,0,0.1);
width: max-content;
margin: 8px auto;
padding: 4px 14px;
border-radius: 8px;
color: gray;
font-size: 14px;
}
.doc-chat-row-robot {
justify-content: flex-start !important;
}
.doc-chat-box {
color: #000000de;
position: relative;
width: fit-content;
max-width: 55%;
border-radius: .5rem;
padding: .4rem .6rem;
margin: .4rem .8rem;
background-color: #fff;
line-height: 1.5;
font-size: 16px;
outline: none;
overflow-wrap: break-word;
white-space: normal;
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
}
.doc-chat-box:after {
content: "";
position: absolute;
right: auto;
top: 0;
width: 8px;
height: 12px;
color: #fff;
border: 0 solid transparent;
border-bottom: 7px solid;
border-radius: 0 0 8px 0;
left: calc(100% - 4px);
box-sizing: inherit;
}
.doc-chat-box-robot:after {
content: "";
position: absolute;
right: calc(100% - 4px);
top: 0;
width: 8px;
height: 12px;
color: #fff;
border: 0 solid transparent;
border-bottom: 7px solid;
border-radius: 0 0 0 8px;
left: auto;
box-sizing: inherit;
}
.doc-chat-avatar {
background-color: aquamarine;
width: 36px !important;
height: 36px !important;
border-radius: 18px;
}
</style>

188
docs/.vuepress/config.js Normal file
View File

@@ -0,0 +1,188 @@
module.exports = {
title: '炸毛框架',
description: '一个聊天机器人 + Web 框架',
theme: 'antdocs',
markdown: {
lineNumbers: true
},
head: [
['link', { rel: 'icon', href: '/logo_trans.png' }],
['script', {}, `
var _hmt = _hmt || [];
(function () {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?f0f276cefa10aa31a20ae3815a50b795";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
`]
],
themeConfig: {
repo: 'zhamao-robot/zhamao-framework',
logo: '/logo_trans.png',
docsDir: 'docs',
editLinks: true,
lastUpdated: '上次更新',
activeHeaderLinks: false,
nav: [
{ text: '指南', link: '/guide/' },
{ text: '事件和注解', link: '/event/' },
{ text: '组件', link: '/component/' },
{ text: '进阶', link: '/advanced/' },
{ text: 'FAQ', link: '/faq/' },
{ text: '更新日志', link: '/update/v2/' },
{ text: '炸毛框架 v1', link: 'https://docs-v1.zhamao.xin/' }
],
sidebar: {
'/guide/': [
{
title: '指南',
collapsable: false,
sidebarDepth: 1,
children: [
'',
'installation',
'quickstart-robot',
'quickstart-http',
'onebot-choose',
'basic-config',
'write-module',
'register-event',
'upgrade',
'errcode'
]
}
],
'/event/': [
{
title: '事件和注解',
collapsable: false,
sidebarDepth: 1,
children: [
'',
'robot-annotations',
'route-annotations',
'framework-annotations',
'middleware',
'custom-annotations',
'event-dispatcher'
]
}
],
'/component/': [
'',
{
title: '聊天机器人组件',
collapsable: true,
sidebarDepth: 2,
children: [
'bot/robot-api-12',
'bot/robot-api',
'bot/cqcode',
'bot/message-util',
'bot/access-token',
'bot/turing-api',
'bot/help-generator.md',
]
},
{
title: '存储组件',
collapsable: true,
sidebarDepth: 2,
children: [
'store/light-cache',
'store/mysql',
'store/mysql-db',
'store/redis',
'store/atomics',
'store/spin-lock',
'store/data-provider'
]
},
{
title: '通用组件',
collapsable: true,
sidebarDepth: 2,
children: [
'common/context',
'common/coroutine-pool',
'common/singleton-trait',
'common/zmutil',
'common/global-functions',
'common/console',
'common/task-worker',
'common/remote-terminal',
'common/event-tracer'
]
},
{
title: 'HTTP 组件',
collapsable: true,
sidebarDepth: 2,
children: [
'http/zmrequest',
'http/route-manager'
]
},
{
title: '模块/插件管理',
collapsable: true,
sidebarDepth: 2,
children: [
'module/module-pack',
'module/module-unpack'
]
}
],
'/advanced/': [
'',
{
title: '框架高级开发',
collapsable: true,
sidebarDepth: 2,
children: [
'framework-structure',
'custom-start',
'manually-install',
'inside-class',
'multi-process',
'task-worker'
]
},
{
title: '开发实战教程',
collapsable: true,
sidebarDepth: 2,
children: [
'connect-ws-client',
'example/admin',
'example/integrate-qingyunke-chatbot',
'example/weather-bot'
]
},
],
'/faq/': [
'',
'to-v2',
'usual-question',
'address-already-in-use',
'display-deadlock',
'light-cache-wrong',
'wait-message-cqbefore'
],
'/update/': [
{
title: '更新日志',
collapsable: true,
sidebarDepth: 0,
children: [
'v2',
'v1',
'build-update'
]
},
'config'
]
}
}
}

View File

@@ -0,0 +1 @@
framework.zhamao.me

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -0,0 +1 @@
User-agent: *

View File

@@ -0,0 +1 @@
@accentColor: #3665aa;

40
docs/README.md Normal file
View File

@@ -0,0 +1,40 @@
---
home: true
heroImage: ./logo_trans.png
actionBtn:
text: 快速上手
link: /guide/
type: primary
size: large
features:
- title: 高性能
details: 基于 PHP 的 Swoole 高性能扩展,利用 WebSocket 进行与 OneBot 协议兼容的聊天机器人软件的通信,还有数据库连接池、内存缓存、多任务进程等特色,大幅增强性能。
- title: 易于开发
details: 所有功能采用模块化设计,除特殊情况外几乎所有功能都不需要修改框架内任意代码,框架采用灵活的注解进行各类事件绑定,同时支持下断点调试。
- title: 接口直观
details: 支持命令、普通文本、正则匹配、自然语言处理等多种对话解析方式,利用协程巧妙实现了直观的交互式会话模式,同时支持多种富文本的处理。
footer: |
Apache-2.0 Licensed &nbsp;|&nbsp; Copyright © 2019-2022 Zhamao Developer Team &nbsp;|&nbsp; <a href="http://beian.miit.gov.cn">沪ICP备2021010446号-1</a>
---
# 快速上手
## 安装框架和环境
此命令可一键以模板安装 `zhamao-framework``PHP` 独立环境到目录下,无需 root仅限 Linux
```bash
mkdir my-app
cd my-app
bash -c "$(curl -fsSL https://api.zhamao.xin/go.sh)"
```
## 运行框架
```bash
./zhamao server
```
## 效果图
![index_demo](/index_demo.png)

4
docs/advanced/README.md Normal file
View File

@@ -0,0 +1,4 @@
# 进阶开发
在本章,下面的部分将详细说明一些具体的案例和自定义框架的操作。
> 更多进阶教程敬请期待....(或者你可以选择提 Issue 到框架 GitHub有需求就写入文档

View File

@@ -11,9 +11,11 @@
以上两种方式,`Header` 方式比 `GET` 方式优先级要高,如果两者均没有指定,框架会将此连接当作 `default` 类型接入。
!!! note "提示"
::: tip 提示
对于对接 OneBot 标准的机器人客户端,只要符合 OneBot 标准,即 `X-Client-Role` 会自动带上 `universal``qq` 等字样,就会自动标记为 `qq` 类型。
对于对接 OneBot 标准的机器人客户端,只要符合 OneBot 标准,即 `X-Client-Role` 会自动带上 `universal``qq` 等字样,就会自动标记为 `qq` 类型。
:::
## 逻辑编写
@@ -140,4 +142,3 @@ public function onMessage() {
```php
server()->close($conn->getFd());
```

View File

@@ -5,8 +5,10 @@
从前面的几章中,我们了解到框架有多种下载到本地的方式。
- Composer 依赖模式
- Starter 从模板创建模式
- Starter 从模板创建模式(等同于 Composer 模式)
- 源码模式
- Phar Composer 依赖模式
- Phar 源码模式
### Composer 依赖模式

View File

@@ -87,14 +87,13 @@ class AdminMiddleware implements MiddlewareInterface
*/
```
<chat-box>
^ 假设我是管理员
) 禁言 1234567 600
( 禁言成功!
^ 假设我不在管理员名单里
) 禁言 1234567 900
^ 机器人没有回复,因为中间件返回了 false不继续执行
</chat-box>
<chat-box :my-chats="[
{type:0,content:'禁言 1234567 600'},
{type:1,content:'禁言成功!'},
{type:2,content:'假设我不在管理员名单里'},
{type:0,content:'禁言 1234567 900'},
{type:2,content:'机器人没有回复,因为中间件返回了 false不继续执行'},
]"></chat-box>
而这时候有朋友又要问了,如果我有一系列管理员命令,假设都在一个叫 `AdminFunc.php` 的模块类里,我是不是还得一个一个地给注解事件写 `@Middleware("admin")` 呢?当然不需要!如果你这个类所有的注解事件都是机器人的聊天事件(`@CQCommand``@CQMessage`)的话,可以直接给类注解这个中间件,效果等同于给每一个函数写一次中间件注解。
@@ -120,112 +119,111 @@ class AdminFunc
上面我们讲到了,中间件里面使用了 `LightCache` 轻量缓存来储存临时的管理员列表,那么我们将这部分的代码完善吧!
=== "src/Module/Example/AdminFunc.php"
**src/Module/Example/AdminFunc.php**
```php
<?php
```php
<?php
namespace Module\Example;
namespace Module\Example;
use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\Http\Middleware;
use ZM\API\CQ;
use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\Http\Middleware;
use ZM\API\CQ;
/**
* Class AdminFunc
* @package Module\Example
* @Middleware("admin")
*/
class AdminFunc
{
/**
* @CQCommand(match="禁言",message_type="group")
*/
public function banSomeone() {
$r1 = ctx()->getNextArg("请输入禁言的人或at他");
$r2 = ctx()->getFullArg("请输入禁言的时间(秒)");
$cq = CQ::getCQ($r1);
if ($cq !== null) {
if ($cq["type"] != "at") return "请at或者输入正确的QQ号";
$r1 = $cq["params"]["qq"];
}
// 群内禁言用户
ctx()->getRobot()->setGroupBan(ctx()->getGroupId(), $r1, $r2);
return "禁言成功!";
}
/**
* Class AdminFunc
* @package Module\Example
* @CQCommand(match="解除禁言",message_type="group")
*/
public function unbanSomeone() {
$r1 = ctx()->getNextArg("请输入禁言的人或at他");
$cq = CQ::getCQ($r1);
if ($cq !== null) {
if ($cq["type"] != "at") return "请at或者输入正确的QQ号";
$r1 = $cq["params"]["qq"];
}
// 群内禁言用户
ctx()->getRobot()->setGroupBan(ctx()->getGroupId(), $r1, 0);
return "解除禁言成功!";
}
}
```
**src/Module/Example/AdminManager.php**
```php
<?php
namespace Module\Example;
use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\Http\Middleware;
use ZM\Annotation\Swoole\OnStart;
use ZM\Store\LightCache;
use ZM\Store\Lock\SpinLock;
class AdminManager
{
/**
* @OnStart()
*/
public function onStart() {
if (!LightCache::isset("admin_list")) { //一次性代码首次执行才会执行if
LightCache::set("admin_list", [ // 框架启动时初始化管理员列表
"123456",
"234567"
], -2); // 这里用 -2 的原因是将这一列表持久化保存,避免关闭框架后丢失
}
}
/**
* @CQCommand(match="添加管理员")
* @Middleware("admin")
*/
class AdminFunc
{
/**
* @CQCommand(match="禁言",message_type="group")
*/
public function banSomeone() {
$r1 = ctx()->getNextArg("请输入禁言的人或at他");
$r2 = ctx()->getFullArg("请输入禁言的时间(秒)");
$cq = CQ::getCQ($r1);
if ($cq !== null) {
if ($cq["type"] != "at") return "请at或者输入正确的QQ号";
$r1 = $cq["params"]["qq"];
}
// 群内禁言用户
ctx()->getRobot()->setGroupBan(ctx()->getGroupId(), $r1, $r2);
return "禁言成功!";
}
/**
* @CQCommand(match="解除禁言",message_type="group")
*/
public function unbanSomeone() {
$r1 = ctx()->getNextArg("请输入禁言的人或at他");
$cq = CQ::getCQ($r1);
if ($cq !== null) {
if ($cq["type"] != "at") return "请at或者输入正确的QQ号";
$r1 = $cq["params"]["qq"];
}
// 群内禁言用户
ctx()->getRobot()->setGroupBan(ctx()->getGroupId(), $r1, 0);
return "解除禁言成功!";
}
public function addAdmin() { //只有管理员才能添加管理员
$qq = ctx()->getNextArg("请输入要添加管理员的QQ(qq号码不可at");
SpinLock::lock("admin_list"); //如果是多进程模式的话需要加锁
$ls = LightCache::get("admin_list");
if (!in_array($qq, $ls)) $ls[] = $qq;
LightCache::set("admin_list", $ls, -2);
SpinLock::unlock("admin_list"); //如果是多进程模式的话需要加锁
return "成功添加 $qq 到管理员列表!";
}
```
=== "src/Module/Example/AdminManager.php"
```php
<?php
namespace Module\Example;
use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\Http\Middleware;
use ZM\Annotation\Swoole\OnStart;
use ZM\Store\LightCache;
use ZM\Store\Lock\SpinLock;
class AdminManager
{
/**
* @OnStart()
*/
public function onStart() {
if (!LightCache::isset("admin_list")) { //一次性代码首次执行才会执行if
LightCache::set("admin_list", [ // 框架启动时初始化管理员列表
"123456",
"234567"
], -2); // 这里用 -2 的原因是将这一列表持久化保存,避免关闭框架后丢失
}
}
/**
* @CQCommand(match="添加管理员")
* @Middleware("admin")
*/
public function addAdmin() { //只有管理员才能添加管理员
$qq = ctx()->getNextArg("请输入要添加管理员的QQ(qq号码不可at");
SpinLock::lock("admin_list"); //如果是多进程模式的话需要加锁
$ls = LightCache::get("admin_list");
if (!in_array($qq, $ls)) $ls[] = $qq;
LightCache::set("admin_list", $ls, -2);
SpinLock::unlock("admin_list"); //如果是多进程模式的话需要加锁
return "成功添加 $qq 到管理员列表!";
}
}
```
<chat-box>
^ 现在我是 123456
) 禁言 13579 60
( 禁言成功!
) 解除禁言 13579
( 解除禁言成功!
) 添加管理员 98765
( 成功添加 98765 到管理员列表!
^ 现在我是98765
) 禁言 13579
( 请输入禁言的时间(秒)
) 120
( 禁言成功!
</chat-box>
}
```
<chat-box :my-chats="[
{type:2,content:'现在我是 123456'},
{type:0,content:'禁言 13579 60'},
{type:1,content:'禁言成功!'},
{type:0,content:'解除禁言 13579'},
{type:1,content:'解除禁言成功!'},
{type:0,content:'添加管理员 98765'},
{type:1,content:'成功添加 98765 到管理员列表!'},
{type:2,content:'现在我是98765'},
{type:0,content:'禁言 13579'},
{type:1,content:'请输入禁言的时间(秒)'},
{type:0,content:'120'},
{type:1,content:'禁言成功!'},
]"></chat-box>

View File

@@ -0,0 +1,85 @@
# 接入青云客智能聊天机器人API
作为一个群聊机器人,懂得聊天会让机器人增色不少,在大数据和 AI 热潮下,不少厂商都研发了自己的智能聊天 API例如图灵机器人、腾讯智能闲聊等大厂开发的 API 自然有着他人无可比拟的健壮性和可靠性,但是随之而来不菲的价格显然并不适合大众开发者。这时候一个免费、可用的智能聊天 API 便非常重要了,其中,青云客是少有的完全免费、无需注册的智能聊天 API提供了包括智能聊天、歌词、天气查询、笑话等多种有用功能且接入简单非常适合新手开发者尝试。
## 结果演示
![圖片](https://user-images.githubusercontent.com/31698606/158875192-108698a3-b54e-4fc0-889a-0829ca328b13.png)
## 阅读接入指南
不管接入何种服务,阅读接入指南永远都是最优先、最重要的一步,所幸青云客的接入指南十分简单,简单来说归纳为以下:
* 请求GET https://api.qingyunke.com/api.php
* 参数:
* * `key` 目前固定为 `free`
* * `appid` 目前固定为 `0`
* * `msg` 关键词,需要经过 `urlencode`
* 注意:返回结果中 `{br}` 代表换行
## 逻辑编写
阅读过后,我们便可以进行主要的编写工作了。
首先,为了机器人的性能考虑,也为了避免过分打扰群聊的聊天,我们希望机器人只有在主动触发(@AT 或者 关键词等)时才会进行智能聊天。
对于关键词匹配,我们可以使用 `@CQCommand`
```php
/**
* 智能聊天
*
* @CQCommand(start_with="机器人")
*/
public function chat()
{
// 替换掉机器人前缀,并获取消息内容
$msg = ctx()->getMessage();
$msg = str_replace('机器人', '', $msg);
if (empty(trim($msg))) {
$msg = ctx()->getFullArg('怎么了?');
}
Console::info('正在获取智能聊天回复:' . $msg);
// 请求 API 获取回复
$raw_data = file_get_contents('https://api.qingyunke.com/api.php?key=free&appid=0&msg=' . urlencode($msg));
try {
$data = json_decode($raw_data, true, 512, JSON_THROW_ON_ERROR);
} catch (\Exception $e) {
$data = ['content' => '机器人解析异常,请稍后再试'];
Console::warning('无法获取智能聊天回复:' . $e->getMessage());
}
if ($data['result'] !== 0) {
$data = ['content' => '机器人服务异常,请稍后再试'];
Console::warning('无法获取智能聊天回复:' . $raw_data);
}
Console::info('获取智能聊天回复完成:' . $data['content']);
// 将 {br} 替换为换行
$data['content'] = strtr($data['content'], ['{br}' => "\n"]);
return $data['content'];
}
```
这样我们的命令便只会在用户发送以`机器人`开头的消息时才会触发。
同时,我们也希望在 @AT 机器人时也进行回复,此时可以使用 `@CQBefore` 方法进行折中:
```php
/**
* 将 AT 机器人的消息交由智能聊天处理
*
* @CQBefore("message")
*/
public function changeAt(): bool
{
// 判断此条消息是否 AT 了机器人
if (MessageUtil::isAtMe(ctx()->getMessage(), ctx()->getRobotId())) {
// 将 AT 本身从消息中去掉
$msg = str_replace(CQ::at(ctx()->getRobotId()), '', ctx()->getMessage());
ctx()->setMessage('机器人' . trim($msg));
// 调用智能聊天
ctx()->reply($this->chat());
return false;
}
return true;
}
```

View File

@@ -0,0 +1,113 @@
# 基于词性分析和魅族天气的天气查询机器人
本文将基于 [`jieba-php`](https://github.com/fukuball/jieba-php) 中文分词库以及 [魅族天气 API](https://github.com/shichunlei/-Api/blob/master/MeizuWeather.md) 开发一个天气查询机器人。
## 结果演示
![圖片](https://user-images.githubusercontent.com/31698606/159122016-61ba9696-5786-4561-b371-827d9f1d01aa.png)
尾部的随机表情并非本教程的一部分。
## 逻辑编写
[jieba-php](https://github.com/fukuball/jieba-php) 是目前比较好用的中文分词库,虽然最近的维护并不活跃,但已足够我们的需求:
```shell
composer require fukuball/jieba-php:dev-master
```
以下代码使用了本文作者自行编写的天气查询库,需要进行引入:
```shell
composer require sunxyw/weather
```
您也可以将以下代码自行改写为直接调用魅族天气 API详情请参阅[魅族天气 API 文档](https://github.com/shichunlei/-Api/blob/master/MeizuWeather.md)。
```php
<?php
namespace Bot\Module\SmartChat;
use Fukuball\Jieba\Jieba;
use Fukuball\Jieba\Posseg;
use Sunxyw\Weather\Weather;
use ZM\Annotation\CQ\CQCommand;
use ZM\Console\Console;
class WeatherReport
{
/**
* 加载字典
*
* @OnStart(worker_id=-1)
*
* @return void
*/
public function initDictionary(): void
{
// 分词以及词性分析需要载入字典到内存
ini_set('memory_limit', '600M');
Jieba::init(['dict' => 'small']);
Posseg::init();
}
/**
* 查询天气
*
* @CQCommand(keyword="天气")
*
* @return string
*/
public function cmdQueryWeather(): string
{
// 分词并进行词性分析
$seg_list = Posseg::cut(ctx()->getMessage());
$tags = array_column($seg_list, 'tag');
// 找出词性为 ns地名的单词
$location_index = array_search('ns', $tags, true);
$location = $seg_list[$location_index]['word'];
// 此处引入了本文作者自己写的天气库
$w = new Weather();
try {
$report = $w->getWeather($location);
} catch (\InvalidArgumentException) {
return '城市输入错误';
} catch (\JsonException $e) {
Console::warning("天气查询失败:{$e->getMessage()}");
return '天气查询失败';
}
$template = <<<EOF
%s天气%s
温度:%s℃
湿度:%s%%
风向:%s %s
空气质量:%s
------------------------------
未来三天天气:
%s%s日间%s℃夜间%s℃吹%s %s
%s%s日间%s℃夜间%s℃吹%s %s
%s%s日间%s℃夜间%s℃吹%s %s
EOF;
$args = [
$report->getCity(),
$report->getRealtime()['weather'],
$report->getRealtime()['temperature'],
$report->getRealtime()['humidity'],
$report->getRealtime()['wind_direction'],
$report->getRealtime()['wind_speed'],
$report->getRealtime()['air_quality'],
];
foreach (array_slice($report->getForecastDaily(), 0, 3) as $forecast) {
$args[] = $forecast['date'];
$args[] = $forecast['weather'];
$args[] = $forecast['temperature']['day'];
$args[] = $forecast['temperature']['night'];
$args[] = $forecast['wind_direction'];
$args[] = $forecast['wind_speed'];
}
return vsprintf($template, ...$args);
}
}
```

View File

@@ -3,4 +3,3 @@
## 框架运行总结构图
![](https://static.zhamao.me/images/docs/a23d8a952cf9c88d395888d220605a4f.png)

View File

@@ -1,9 +0,0 @@
# 进阶开发
在本章,下面的部分将详细说明一些具体的案例和自定义框架的操作。
- 如何自定义修改框架本身?- [框架启动方式](/advanced/custom-start/)
- 如何接入一个自己的 WebSocket 客户端?- [接入 WebSocket 客户端](/advanced/connect-ws-client/)
- 框架到底是怎么工作的?- [框架结构剖析](/advanced/framework-structure/)
> 更多进阶教程敬请期待....(或者你可以选择提 Issue 到框架 GitHub有需求就写入文档

View File

@@ -65,6 +65,3 @@ $obj->match ==> [
]
*/
```

View File

@@ -63,8 +63,10 @@ PHP 也是如此,框架的多进程又是怎么一回事呢?为什么要采
| LightCache | 框架封装的跨进程的 key-value 存储模型 | 性能强悍,无 I/O 和网络通信 | 需要提前分配最大内存大小,最大单个值长度大小,不灵活 |
| WorkerCache | 框架封装的基于进程的 key-value 存储模型,类似 Redis | 无需提前分配最大内存大小,受限于 PHP memory_limit | 见 WorkerCache 的说明 |
!!! note "WorkerCache 的说明"
对于 WorkerCache 来说其实是比较特殊的进程间通信。具体来说就是WorkerCache 的原理就是将变量指定的存到一个进程中,如果是本进程读写的话直接相当于改一下全局变量,如果是其他进程读写的话,则依靠进程间通信。
::: tip WorkerCache 的说明
所以缺点也显而易见,如果使用过程中不是命中了 WorkerCache 存储所在的进程的话,则一直会使用进程间通信,影响一定的效率
对于 WorkerCache 来说其实是比较特殊的进程间通信。具体来说就是WorkerCache 的原理就是将变量指定的存到一个进程中,如果是本进程读写的话直接相当于改一下全局变量,如果是其他进程读写的话,则依靠进程间通信
所以缺点也显而易见,如果使用过程中不是命中了 WorkerCache 存储所在的进程的话,则一直会使用进程间通信,影响一定的效率。
:::

3
docs/advanced/php-env.md Normal file
View File

@@ -0,0 +1,3 @@
# PHP 环境高级部署方式
TODO: 留个坑。

View File

@@ -1,4 +1,3 @@
# 使用 TaskWorker 进程处理密集运算
> 新开个坑有时间补上。__填坑标记__
> TODO: 新开个坑有时间补上。__填坑标记__

View File

@@ -1,103 +0,0 @@
.md-header-nav__button.md-logo {
padding: .2rem;
margin: .2rem;
}
.md-header-nav__button.md-logo img, .md-header-nav__button.md-logo svg {
width: 1.6rem;
height: 1.6rem;
}
.doc-chat-container {
border-radius: 6px;
width: 100%;
min-height: 30px;
/*noinspection CssUnresolvedCustomProperty*/
background-color: var(--md-code-bg-color);
padding: 12px;
margin-right: auto;
margin-left: auto;
box-shadow: 0 3px 1px -2px rgba(0,0,0,.2), 0 2px 2px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12);
}
.doc-chat-row {
margin: 0;
display: flex;
flex-wrap: wrap;
flex: 1 1 auto;
justify-content: flex-end;
}
.doc-chat-banner {
justify-content: center;
background: rgba(0,0,0,0.1);
width: max-content;
margin: 8px auto;
padding: 4px 14px;
border-radius: 8px;
color: gray;
font-size: 14px;
}
.doc-chat-row-robot {
justify-content: flex-start !important;
}
.doc-chat-box {
color: #000000de;
position: relative;
width: fit-content;
max-width: 55%;
border-radius: .5rem;
padding: .4rem .6rem;
margin: .4rem .8rem;
background-color: #fff;
line-height: 1.5;
font-size: 16px;
outline: none;
overflow-wrap: break-word;
white-space: normal;
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
}
.doc-chat-box:after {
content: "";
position: absolute;
right: auto;
top: 0;
width: 8px;
height: 12px;
color: #fff;
border: 0 solid transparent;
border-bottom: 7px solid;
border-radius: 0 0 8px 0;
left: calc(100% - 4px);
box-sizing: inherit;
}
.doc-chat-box-robot:after {
content: "";
position: absolute;
right: calc(100% - 4px);
top: 0;
width: 8px;
height: 12px;
color: #fff;
border: 0 solid transparent;
border-bottom: 7px solid;
border-radius: 0 0 0 8px;
left: auto;
box-sizing: inherit;
}
.doc-chat-avatar {
background-color: aquamarine;
width: 36px !important;
height: 36px !important;
border-radius: 18px;
}
.md-typeset .admonition, .md-typeset details {
font-size: .72rem;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

3
docs/component/README.md Normal file
View File

@@ -0,0 +1,3 @@
# 框架组件
这里列举了框架内的你可能会用到的常用组件。

View File

@@ -1,4 +1,4 @@
# Token 验证
# 接入安全验证 - Token
为了保障安全,框架支持给接入的 WebSocket 连接验证 Token如果不设置 Token 同时又将框架的端口暴露在公网将会非常危险。
@@ -27,9 +27,9 @@
go-cqhttp 的配置段:
```hjson
// 访问密钥, 强烈推荐在公网的服务器设置
access_token: "emhhbWFvLXJvYm90"
```
// 访问密钥, 强烈推荐在公网的服务器设置
access_token: "emhhbWFvLXJvYm90"
```
框架的配置文件配置段:

View File

@@ -1,4 +1,4 @@
# CQ 码(多媒体消息)
# 多媒体消息 - CQ码
消息中的多媒体内容使用 CQ 码来表示,形如 `[CQ:face,id=178]`。其中,`[CQ:]` 是固定格式;`face` 是「功能名」,除了 `face` 还有许多不同的功能名;`id=178` 是「参数」,某些功能不需要参数,而另一些需要多个参数,当有多个参数时,参数间使用逗号分隔。
@@ -14,11 +14,13 @@
更多 CQ 码功能请参考 [消息段类型](https://github.com/howmanybots/onebot/blob/master/v11/specs/message/segment.md)。
!!! warning "注意"
::: warning 注意
CQ 码中不应有多余的空格,例如不应该使用 `[CQ:face, id=178]`
CQ 码中不应有多余的空格,例如不应该使用 `[CQ:face, id=178]`
CQ 码的参数值可以包含空格、换行、除 `[],&` 之外的特殊符号等。在解析时,应直接取 `[CQ:` 后、第一个 `,``]` 前的部分为功能名,第一个 `,` 之后到 `]` 之间的部分为参数,按 `,` 分割后,每个部分第一个 `=` 前的内容为参数名,之后的部分为参数值。例如 `[CQ:share,title=标题中有=等号,url=http://baidu.com]` 中,功能名为 `share``title` 参数值为 `标题中有=等号``url` 参数值为 `http://baidu.com`
CQ 码的参数值可以包含空格、换行、除 `[],&` 之外的特殊符号等。在解析时,应直接取 `[CQ:` 后、第一个 `,``]` 前的部分为功能名,第一个 `,` 之后到 `]` 之间的部分为参数,按 `,` 分割后,每个部分第一个 `=` 前的内容为参数名,之后的部分为参数值。例如 `[CQ:share,title=标题中有=等号,url=http://baidu.com]` 中,功能名为 `share``title` 参数值为 `标题中有=等号``url` 参数值为 `http://baidu.com`
:::
## 转义
@@ -69,12 +71,12 @@ class Hello {
}
```
效果
效果
<chat-box>
) 发送图片
[ https://zhamao.xin/file/hello.jpg
</chat-box>
<chat-box :my-chats="[
{type:0,content:'发送图片'},
{type:3,content:'https://zhamao.xin/file/hello.jpg'}
]"></chat-box>
## CQ 码操作
@@ -191,15 +193,17 @@ public function faceTest() {
}
```
<chat-box>
) 打盹
( 正在打盹...
[ https://docs-v1.zhamao.xin/face/8.gif
</chat-box>
<chat-box :my-chats="[
{type:0,content:'打盹'},
{type:1,content:'正在打盹...'},
{type:3,content:'https://docs-v1.zhamao.xin/face/8.gif'},
]"></chat-box>
!!! note "提示"
对于不断更新的 QQ 版本下,可能会持续扩充新的 QQ 表情,如果上表没有新的表情的话,也可以使用消息接收的方式,让机器人收到表情后解析出来对应的 id 然后再发送。
::: tip 提示
对于不断更新的 QQ 版本下,可能会持续扩充新的 QQ 表情,如果上表没有新的表情的话,也可以使用消息接收的方式,让机器人收到表情后解析出来对应的 id 然后再发送。
:::
### CQ::image() - 发送图片
@@ -221,7 +225,11 @@ public function faceTest() {
- 绝对路径,例如 `file:///root/imagetest/1.png`,格式使用 [`file` URI](https://tools.ietf.org/html/rfc8089)
- 网络 URL例如 `http://i1.piimg.com/567571/fdd6e7b6d93f1ef0.jpg`
- Base64 编码,例如 `base64://iVBORw0KGgoAAAANSUhEUgAAABQAAAAVCAIAAADJt1n/AAAAKElEQVQ4EWPk5+RmIBcwkasRpG9UM4mhNxpgowFGMARGEwnBIEJVAAAdBgBNAZf+QAAAAABJRU5ErkJggg==`
- Base64 编码,例如
```
base64://iVBORw0KGgoAAAANSUhEUgAAABQAAAAVCAIAAADJt1n/AAAAKElEQVQ4EWPk5+RmIBcwkasRpG9UM4mhNxpgowFGMARGEwnBIEJVAAAdBgBNAZf+QAAAAABJRU5ErkJggg==
```
### CQ::record() - 发送语音
@@ -250,10 +258,10 @@ public function say() {
}
```
<chat-box>
) 说你好
( [语音消息,点击收听] 2'' )))
</chat-box>
<chat-box :my-chats="[
{type:0,content:'说你好'},
{type:1,content:'[语音消息,点击收听] 2\'\' )))'},
]"></chat-box>
> 此 CQ 码只能用于单独一条文本消息中,如果混有其他字符串,则会吞掉其他字符串内容。
@@ -274,10 +282,10 @@ public function atTest() {
}
```
<chat-box>
) at测试
( @鲸鱼 你好啊!
</chat-box>
<chat-box :my-chats="[
{type:0,content:'at测试'},
{type:1,content:'@鲸鱼 你好啊!'},
]"></chat-box>
### CQ::video() - 发送短视频
@@ -346,9 +354,11 @@ public function atTest() {
匿名发消息。需要在允许匿名发消息的群里发。
!!! tip "提示"
::: tip 提示
当收到匿名消息时,需要通过 [消息事件的群消息](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/message.md#群消息) 的 `anonymous` 字段判断。
当收到匿名消息时,需要通过 [消息事件的群消息](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/message.md#群消息) 的 `anonymous` 字段判断。
:::
定义:`CQ::anonymous($ignore = 1)`
@@ -515,9 +525,11 @@ public function xmlTest() {
其中 `$resid` 是面向 go-cqhttp 扩展的参数,默认不填为 0走小程序通道填了走富文本通道发送。
!!! tip "提示"
::: tip 提示
因为某些众所周知的原因XML 和 JSON 的返回不提供实例,有兴趣的可以自行研究如何编写,文档不含任何相关教程。
因为某些众所周知的原因XML 和 JSON 的返回不提供实例,有兴趣的可以自行研究如何编写,文档不含任何相关教程。
:::
### CQ::_custom() - 扩展自定义 CQ 码
@@ -536,4 +548,3 @@ public function xmlTest() {
CQ::_custom("at",["qq" => "123456","qwe" => "asd"]);
// 返回:[CQ:at,qq=123456,qwe=asd]
```

View File

@@ -0,0 +1,35 @@
# 命令帮助生成器 - CommandHelpGenerator
类定义:`\ZM\Utils\MessageUtil`
目前包含在 `MessageUtil` 类中,日后可能会进行拆分。
> 2.7.3 版本起可用。
## 方法
### generateCommandHelp
自动扫描定义的所有命令,生成注解树,并以此生成命令列表及帮助。
第一次运行时,会遍历一遍注解树并进行生成,此后会从缓存中读取。
定义:`generateCommandHelp()`
返回值:`array` 每个元素对应一个命令的帮助信息,格式为:`命令名(其他触发条件):命令描述`
示例:`天气(温度、包含“天气”):查询指定城市的天气`
```php
/**
* 输出帮助信息
*
* @CQCommand("帮助")
*/
#[CQCommand('帮助')]
public function help(): string
{
$helps = MessageUtil::generateCommandHelp();
array_unshift($helps, '帮助:');
return implode("\n", $helps);
}
```

View File

@@ -1,4 +1,4 @@
# MessageUtil 消息处理工具类
# 消息处理工具类 - MessageUtil
类定义:`\ZM\Utils\MessageUtil`
@@ -42,6 +42,21 @@ MessageUtil::containsImage("[CQ:image,file=a.jpg,url=http://xxx]"); // true
MessageUtil::containsImage("[CQ:face,id=140] 咦,这是一条带表情的消息"); // false
```
### isAtMe()
检查消息中是否含有@bot的消息
定义:`isAtMe($msg, $me_id)`
参数 `$me_id` 为Bot的QQ号。
返回:`bool`true 就是有false 就没有。
```php
MessageUtil::isAtMe("[CQ:at,qq=123456]炸毛你好","123456"); // true
MessageUtil::isAtMe("[CQ:at,qq=123456789]另一个朋友你好","123456"); // false
```
### getImageCQFromLocal()
通过文件路径获取图片的发送 CQ 码。
@@ -66,9 +81,11 @@ MessageUtil::containsImage("[CQ:face,id=140] 咦,这是一条带表情的消
返回:数组,切分后的。
!!! tip "为什么不直接使用 explode 呢"
::: tip 为什么不直接使用 explode 呢
因为 `explode()` 只会简单粗暴的切割字符串,假设用户输入的消息中两个词中间有多个空格,则会有空的词出现。例如 `你好 我是一个长空格`。此函数会将多个空格当作一个空格来对待。
因为 `explode()` 只会简单粗暴的切割字符串,假设用户输入的消息中两个词中间有多个空格,则会有空的词出现。例如 `你好 我是一个长空格`。此函数会将多个空格当作一个空格来对待。
:::
```php
MessageUtil::splitCommand("你好 我是傻瓜\n我是傻瓜二号"); // ["你好","我是傻瓜","我是傻瓜二号"]
@@ -108,8 +125,57 @@ public function onStart() {
}
```
<chat-box>
) 炸毛不聪明
( 其实还是很聪明的!
</chat-box>
<chat-box :my-chats="[
{type:0,content:'炸毛不聪明'},
{type:1,content:'其实还是很聪明的!'},
]"></chat-box>
### strToArray()
`string` 类型的消息文本转换为 `array` 格式。
定义:`strToArray($msg, bool $ignore_space = true, bool $trim_text = false)`
参数 `$msg` 为带 OB/CQ 码的字符串消息,如 `你好啊,[CQ:at,qq=123]`
参数 `$ignore_space``false` 时,转换的数组内会包含空 `text` 段。
参数 `$trim_text``true` 时,会自动去除 `text` 段消息头尾的换行符和空格。
这个命令转换的数组格式符合 OneBot 11/12 标准,但细节上可能会与不同 OneBot 实现有所差异。
```php
$str = "你好啊,[CQ:at,qq=123]";
$arr = \ZM\Utils\MessageUtil::strToArray($str);
```
转换结果参考如下:
```json
[
{
"type": "text",
"data": {
"text": "你好啊,"
}
},
{
"type": "at",
"data": {
"qq": "123"
}
}
]
```
### arrayToStr()
`array` 格式的消息内容转换为字符串 + CQ 码的形式。
定义:`arrayToStr(array $array)`
```php
// 我们使用上边的 $arr 作为传入值。
$new_str = \ZM\Utils\MessageUtil::arrayToStr($arr);
// 结果:"你好啊,[CQ:at,qq=123]"
```

View File

@@ -0,0 +1,11 @@
# 机器人动作 - V12
::: tip 提示
目前由于 OneBot 12 标准还没有定稿,处于草案阶段,故框架暂不更新。
在未来升级到 OneBot 12 标准后,框架会提供转换及兼容措施以及 12 版本的 API 方法。
:::
见 [机器人动作OneBot 11](./robot-api.html)。

View File

@@ -1,26 +1,29 @@
# 机器人 APIZMRobot
# 机器人动作 - V11
ZMRobot 类是封装好的 OneBot 标准的 API 接口调用类,可以在机器人连接后通过连接或者机器人 QQ 号获取对象并调用接口(如发送群消息、获取群列表等操作)。
OneBotV11 类是封装好的 OneBot 标准的 API 接口调用类,可以在机器人连接后通过连接或者机器人 QQ 号获取对象并调用接口(如发送群消息、获取群列表等操作)。
| 属性项 | 属性值 | 备注 |
| -------- | ---------------- | ------------------------------ |
| 名称 | ZMRobot | |
| 类型 | 实例化类 | `$r = new ZMRobot($conn)` |
| 命名空间 | `ZM\API\ZMRobot` | 使用前先 `use ZM\API\ZMRobot;` |
| 属性项 | 属性值 | 备注 |
| -------- | ------------------ | -------------------------------- |
| 名称 | OneBotV11 | |
| 类型 | 实例化类 | `$r = new OneBotV11($conn)` |
| 命名空间 | `ZM\API\OneBotV11` | 使用前先 `use ZM\API\OneBotV11;` |
| 别名 | `ZM\API\ZMRobot` | 此类目前是 `extends OneBotV11` |
> 你也可以继续使用 2.5 版本之前的别名类 `ZMRobot`,但未来框架将会优先兼容 OneBot V12 版本的协议,可能会造成更新问题,建议切换为 OneBotV11 类。
## 属性
对象属性方法是对 API 的调整,例如是否以 `_async``_rate_limited` 后缀发送 API、设置协程返回还是异步返回结果等。
### ZMRobot::API_NORMAL
### OneBotV11::API_NORMAL
以默认(无后缀)方式请求 API。
### ZMRobot::API_ASYNC
### OneBotV11::API_ASYNC
以后缀 `_async` 方式异步请求 API。
### ZMRobot::API_RATE_LIMITED
### OneBotV11::API_RATE_LIMITED
以后缀 `_rate_limited` 方式请求 API。
@@ -30,7 +33,7 @@ ZMRobot 类是封装好的 OneBot 标准的 API 接口调用类,可以在机
设置后缀。目前支持 `_async``_rate_limited`
- **prefix**: `int` `默认:API_NORMAL`,可选 `ZMRobot::API_NORMAL``ZMRobot::API_ASYNC``ZMRobot::API_RATE_LIMITED`
- **prefix**: `int` `默认:API_NORMAL`,可选 `OneBotV11::API_NORMAL``OneBotV11::API_ASYNC``OneBotV11::API_RATE_LIMITED`
设置后缀后,请求的 API 会发生变化。例如发送私聊消息:`sendPrivateMsg()`,请求的 API 为 `send_private_msg_async`,详见 [OneBot 文档](https://github.com/howmanybots/onebot/blob/master/v11/specs/api/README.md)。
@@ -43,22 +46,22 @@ ZMRobot 类是封装好的 OneBot 标准的 API 接口调用类,可以在机
获取当前对象的机器人 QQ 或 OneBot 实例的 ID。
```php
$bot = ZMRobot::get(123456);
$bot = OneBotV11::get(123456);
echo $bot->getSelfId(); //123456
```
### ZMRobot::get()
### OneBotV11::get()
静态方法,用来通过机器人 QQ 或 OneBot 实例的 ID 获取 ZMRobot 对象。
静态方法,用来通过机器人 QQ 或 OneBot 实例的 ID 获取 OneBotV11对象。
参数:`$robot_id`,必填。
```php
$r = ZMRobot::get(123456);
$r = OneBotV11::get(123456);
$r->sendPrivateMsg(55555, "hello");
```
### ZMRobot::getRandom()
### OneBotV11::getRandom()
静态方法,随机获取一个连接到框架的机器人(多个机器人实例连接到框架时适用)。
@@ -66,21 +69,21 @@ $r->sendPrivateMsg(55555, "hello");
```php
try {
$bot = ZMRobot::getRandom();
$bot = OneBotV11::getRandom();
$bot->sendPrivateMsg(55555, "foo");
} catch (\ZM\Exception\RobotNotFoundException $e) {
echo "还没有机器人连接到框架!\n";
}
```
### ZMRobot::getAllRobot()
### OneBotV11::getAllRobot()
获取所有连接到框架的机器人的 ZMRobot 对象。
获取所有连接到框架的机器人的 OneBotV11 对象。
返回值:`ZMRobot[]`
返回值:`OneBotV11[]`
```php
$all = ZMRobot::getAllRobot();
$all = OneBotV11::getAllRobot();
foreach($all as $v) {
$v->sendPrivateMsg(55555, "机器人轮流给一个人发消息啦!");
}
@@ -95,7 +98,7 @@ foreach($all as $v) {
```php
//从上下文获取 Websocket 连接对象
$conn = ctx()->getConnection();
$bot = new ZMRobot($conn);
$bot = new OneBotV11($conn);
```
## 返回结果处理
@@ -103,13 +106,14 @@ $bot = new ZMRobot($conn);
因为框架的机器人是兼容 OneBot 标准的(原 CQHTTP所以每次接收发送 API 请求的结果都是大体一样的结构。我们以 `sendPrivateMsg()` 为例,因为发送出去的每一条消息都会在 OneBot 实例(如 CQHTTP 插件、go-cqhttp 等)中对应一个消息 ID以供我们核查消息和后续撤回等操作需要。
```php
$bot = ZMRobot::get("123456"); // 机器人QQ号
$bot = OneBotV11::get("123456"); // 机器人QQ号
$obj = $bot->sendGroupMsg("234567", "你好");
echo json_encode($obj, 128|256);
```
输出结果
```json
// 输出结果
{
"status": "ok",
"retcode": 0,
@@ -158,21 +162,18 @@ vardump($result["retcode"]); //如果成功撤回,输出 int(0)
| ------------ | -------------- | ------- |
| `message_id` | number (int32) | 消息 ID |
例子
代码
=== "代码"
```php
$bot = OneBotV11::get(123456); // 123456是你的机器人QQ
$bot->sendPrivateMsg("627577391", "你好啊!你好你好!");
```
```php
$bot = ZMRobot::get(123456); // 123456是你的机器人QQ
$bot->sendPrivateMsg("627577391", "你好啊!你好你好!");
```
=== "效果"
<chat-box>
( 你好啊!你好你好!
</chat-box>
效果
<chat-box :my-chats="[
{type:1,content:'你好啊!你好你好!'}
]"></chat-box>
### sendGroupMsg()
@@ -618,10 +619,11 @@ vardump($result["retcode"]); //如果成功撤回,输出 int(0)
获取 Cookies。
!!! warning "注意"
::: warning 注意
目前开源的 mirai 为底层的机器人客户端均不支持获取 Cookies 和 CSRF Token包括 go-cqhttp。
目前开源的 mirai 为底层的机器人客户端均不支持获取 Cookies 和 CSRF Token包括 go-cqhttp。
:::
参数
@@ -766,6 +768,23 @@ vardump($result["retcode"]); //如果成功撤回,输出 int(0)
响应数据:无
### getExtendedAPI()
用来调用 OneBot 标准之外扩展出来的自定义 API。与下方 `callExtendedAPI` 不同的是,为了方便用户使用,炸毛框架内置了热门使用并且相对稳定的机器人客户端的专有 API。
目前内置了 `go-cqhttp` 频道相关的扩充 API。
使用示例:`getExtendedAPI('go-cqhttp')->getGuildList()`
使用示例2`getExtendedAPI()->sendGuildChannelMsg($guild_id, $channel_id, '频道的消息')`
唯一一个参数做保留,用于选择不同客户端,目前仅支持 `go-cqhttp`,所以缺省也默认为 `go-cqhttp`
::: warning 注意
由于不同版本的扩展 API 变化可能会很大,改动较多,炸毛框架不会将对应扩展方法写入文档,具体调用情况可根据 IDE 自动补全中的文档或对应类的注释查看。
:::
### callExtendedAPI() (扩充 API
用来调用 OneBot 标准之外扩展出来的自定义 API。
@@ -787,4 +806,3 @@ $result = $bot->callExtendedAPI("get_group_root_files", ["group_id" => 123456]);
var_dump($result["data"]);
// 输出群文件列表
```

View File

@@ -1,4 +1,4 @@
# 图灵机器人 APITuringAPI
# 图灵聊天 - TuringAPI
类定义:`\ZM\API\TuringAPI`
@@ -60,27 +60,27 @@ class Hello {
如上述代码,我们将申请的 apikey 填入变量 `$api` 中,启动机器人即可使用,以下是实测消息(我用自己申请的 key 做测试回复的消息)。
<chat-box>
) 你咋了
( 我没事哦,谢谢您的关心。
) 上海天气
( 上海:周一 03月29日,小雨 东南风转东风,最低气温14度最高气温24度。
^ 切换为群内
) 机器人
( 我在!有什么事吗?
) 你叫啥
( 我的名字叫炸毛,认识你很高兴呢!
</chat-box>
<chat-box :my-chats="[
{type:0,content:'你咋了'},
{type:1,content:'我没事哦,谢谢您的关心。'},
{type:0,content:'上海天气'},
{type:1,content:'上海:周一 03月29日,小雨 东南风转东风,最低气温14度最高气温24度。'},
{type:2,content:'切换为群内'},
{type:0,content:'机器人'},
{type:1,content:'我在!有什么事吗?'},
{type:0,content:'你叫啥'},
{type:1,content:'我的名字叫炸毛,认识你很高兴呢!'},
]"></chat-box>
在默认示例模块中的例子是直接可以拿来用的,这段代码同时做了对 at 的处理、以及兼容用户自定义写的其他命令的方式,下面是默认模块填好 apikey 后可以用的各种方式提问:
<chat-box>
^ 切换为群内
) 我是一条普通消息,这条机器人不会回复我
) @机器人 你叫啥
( 我是聪明可爱的炸毛,认识你很高兴。
) 机器人
( 我在!有什么事吗?
) 一言
( 多少事,从来急,天地转,光阴迫,一万年太久,只争朝夕。
</chat-box>
<chat-box :my-chats="[
{type:2,content:'切换为群内'},
{type:0,content:'我是一条普通消息,这条机器人不会回复我'},
{type:0,content:'@机器人 你叫啥'},
{type:1,content:'我是聪明可爱的炸毛,认识你很高兴。'},
{type:0,content:'机器人'},
{type:1,content:'我在!有什么事吗?'},
{type:0,content:'一言'},
{type:1,content:'多少事,从来急,天地转,光阴迫,一万年太久,只争朝夕。'},
]"></chat-box>

View File

@@ -52,10 +52,11 @@ vendor/bin/start server --log-debug # 以 debug 等级启动框架
输出 warning 级别的 log。
!!! warning 注意
::: warning 注意
框架内出现的用户态异常,比如无法发送 API、无法连接数据库等错误都是 warning 错误,不会导致框架崩溃或功能错误的异常情况建议都使用 warning 输出而不是 error。
框架内出现的用户态异常,比如无法发送 API、无法连接数据库等错误都是 warning 错误,不会导致框架崩溃或功能错误的异常情况建议都使用 warning 输出而不是 error。
:::
### Console::info()
@@ -100,9 +101,11 @@ $str = Console::setColor("I am gold color.", "gold");
炸毛框架支持从终端输入命令来进行一些操作,例如重启框架、停止框架、执行函数等。
!!! warning 注意
::: warning 注意
在 Docker、systemd、daemon 状态下启动的框架会自动关闭终端等待输入,交互不可用。
在 Docker、systemd、daemon 状态下启动的框架会自动关闭终端等待输入,交互不可用。
:::
### reload
@@ -174,4 +177,3 @@ vendor/bin/start server --log-theme={主题名}
vendor/bin/start server --log-theme=white-term # 如果用的是白色终端,这个主题更友好
vendor/bin/start server --log-theme=no-color # 如果不想让 log 带有任何颜色,使用无色主题
```

View File

@@ -40,31 +40,33 @@ public function hello() {
可以使用的事件:所有 **getFrame()** 可以使用的,`@OnOpenEvent()``@OnCloseEvent()`
!!! tip "提示"
::: tip 提示
值得注意的是,由于机器人客户端和炸毛框架的连接是通过 WebSocket 进行的,而 WebSocket 是长连接,所以同一个机器人一次连接下收发消息所用的连接是同一个,所以 Fd 也是相同的。同理,炸毛框架的内部来区分多个机器人也是通过这一 Fd 进行判定的。
值得注意的是,由于机器人客户端和炸毛框架的连接是通过 WebSocket 进行的,而 WebSocket 是长连接,所以同一个机器人一次连接下收发消息所用的连接是同一个,所以 Fd 也是相同的。同理,炸毛框架的内部来区分多个机器人也是通过这一 Fd 进行判定的。
=== "代码"
:::
```php
/**
* @CQCommand("测试fd")
*/
public function testfd() {
ctx()->reply("当前机器人连接的fd是".ctx()->getFd()."机器人QQ是".ctx()->getRobotId());
}
```
代码
=== "效果"
```php
/**
* @CQCommand("测试fd")
*/
public function testfd() {
ctx()->reply("当前机器人连接的fd是".ctx()->getFd()."机器人QQ是".ctx()->getRobotId());
}
```
<chat-box>
^ 假设我们和连接55555的机器人的私聊
) 测试fd
( 当前机器人连接的fd是1机器人QQ是55555
^ 假设切到了另一个机器人66666的私聊
) 测试fd
( 当前机器人连接的fd是2机器人QQ是66666
</chat-box>
效果
<chat-box :my-chats="[
{type:2,content:'假设我们和连接55555的机器人的私聊'},
{type:0,content:'测试fd'},
{type:1,content:'当前机器人连接的fd是1机器人QQ是55555'},
{type:2,content:'假设切到了另一个机器人66666的私聊'},
{type:0,content:'测试fd'},
{type:1,content:'当前机器人连接的fd是2机器人QQ是66666'},
]"></chat-box>
## getData() - 获取事件完整数据
@@ -82,11 +84,11 @@ public function onMessage() {
}
```
<chat-box>
^ 假设我是QQ为123456的用户私聊发消息
) 哈咯!!
( 消息类型是private
</chat-box>
<chat-box :my-chats="[
{type:2,content:'假设我是QQ为123456的用户私聊发消息'},
{type:0,content:'哈咯!!'},
{type:1,content:'消息类型是private'},
]"></chat-box>
## getRequest() - HTTP 请求对象
@@ -124,7 +126,7 @@ public function ping() {
## getRobot() - 获取机器人 API 对象
返回当前上下文关联的机器人 API 调用对象 [ZMRobot](bot/robot-api.md)。
返回当前上下文关联的机器人 API 调用对象 [ZMRobot](../bot/robot-api.md)。
可以使用的事件:所有 HTTP API 发来的事件:`@CQCommand()``@CQMessage()` 等。
@@ -132,10 +134,10 @@ public function ping() {
ctx()->getRobot()->sendPrivateMsg(123456, "发送私聊消息");
```
<chat-box>
^ 正在和机器人聊天
( 发送私聊消息
</chat-box>
<chat-box :my-chats="[
{type:2,content:'正在和机器人聊天'},
{type:1,content:'发送私聊消息'},
]"></chat-box>
## getMessage() - 获取消息
@@ -143,24 +145,26 @@ ctx()->getRobot()->sendPrivateMsg(123456, "发送私聊消息");
可以使用的事件:`@CQCommand()``@CQMessage``@CQBefore("message")``@CQAfter("message")`
=== "代码"
```php
/**
* @CQMessage(group_id=33333)
*/
public function groupRepeat() {
ctx()->reply(ctx()->getMessage());
}
```
代码
=== "效果"
<chat-box>
^ 现在在群33333内机器人已经成了复读机
) 来世还做复读机!!!
( 来世还做复读机!!!
) 你不许复读!
( 你不许复读!
</chat-box>
```php
/**
* @CQMessage(group_id=33333)
*/
public function groupRepeat() {
ctx()->reply(ctx()->getMessage());
}
```
效果
<chat-box :my-chats="[
{type:2,content:'现在在群33333内机器人已经成了复读机'},
{type:0,content:'来世还做复读机!!!'},
{type:1,content:'来世还做复读机!!!'},
{type:0,content:'你不许复读!'},
{type:1,content:'你不许复读!'},
]"></chat-box>
## getUserId() - 获取用户 QQ 号
@@ -275,16 +279,16 @@ function yourName(){
}
```
<chat-box>
) 自我介绍
( 你叫啥名字呀?
) jerry
( 好的,可爱的机器人记住你叫 jerry 啦!以后多聊天哦!
) 自我介绍
( 你叫啥名字呀?
^ 10分钟没理机器人
( 你都10分钟不理我了嘤嘤嘤
</chat-box>
<chat-box :my-chats="[
{type:0,content:'自我介绍'},
{type:1,content:'你叫啥名字呀?'},
{type:0,content:'jerry'},
{type:1,content:'好的,可爱的机器人记住你叫 jerry 啦!以后多聊天哦!'},
{type:0,content:'自我介绍'},
{type:1,content:'你叫啥名字呀?'},
{type:2,content:'10分钟没理机器人'},
{type:1,content:'你都10分钟不理我了嘤嘤嘤'},
]"></chat-box>
## getArgs() - 自动获取参数
@@ -316,12 +320,12 @@ public function argTest1() {
}
```
<chat-box>
) test
( 请输入你要传入的参数内容
) test2
( 参数内容test2
</chat-box>
<chat-box :my-chats="[
{type:0,content:'test'},
{type:1,content:'请输入你要传入的参数内容'},
{type:0,content:'test2'},
{type:1,content:'参数内容test2'},
]"></chat-box>
`getArgs()` 也有三层封装,在使用过程中避免麻烦的话,推荐使用下面这几种 `get*Arg()` 方式。
@@ -341,14 +345,14 @@ public function argTest1() {
}
```
<chat-box>
) test abc def argtest
( 参数内容abc def argtest
) test
( 请输入你要传入的参数内容
) abc def
( 参数内容abc def
</chat-box>
<chat-box :my-chats="[
{type:0,content:'test abc def argtest'},
{type:1,content:'参数内容abc def argtest'},
{type:0,content:'test'},
{type:1,content:'请输入你要传入的参数内容'},
{type:0,content:'abc def'},
{type:1,content:'参数内容abc def'},
]"></chat-box>
## getNextArg()
@@ -364,14 +368,14 @@ public function argTest1() {
}
```
<chat-box>
) test abc def argtest
( 参数内容abc
) test
( 请输入你要传入的参数内容
) abc
( 参数内容abc
</chat-box>
<chat-box :my-chats="[
{type:0,content:'test abc def argtest'},
{type:1,content:'参数内容abc'},
{type:0,content:'test'},
{type:1,content:'请输入你要传入的参数内容'},
{type:0,content:'abc'},
{type:1,content:'参数内容abc'},
]"></chat-box>
## getNumArg()
@@ -389,14 +393,14 @@ public function argTest1() {
}
```
<chat-box>
) test abc 334 argtest
( 数字参数内容334
) test abc
( 请输入你要传入的数字内容
) 998
( 参数内容998
</chat-box>
<chat-box :my-chats="[
{type:0,content:'test abc 334 argtest'},
{type:1,content:'数字参数内容334'},
{type:0,content:'test abc'},
{type:1,content:'请输入你要传入的参数内容'},
{type:0,content:'998'},
{type:1,content:'参数内容998'},
]"></chat-box>
## copy()
@@ -418,8 +422,7 @@ public function argTest1() {
}
```
<chat-box>
) test abc 334 argtest
( 参数内容abc, 334, argtest
</chat-box>
<chat-box :my-chats="[
{type:0,content:'test abc 334 argtest'},
{type:1,content:'参数内容abc, 334, argtest'},
]"></chat-box>

View File

@@ -60,4 +60,3 @@ for($i = 0; $i < 1000; ++$i) {
`$name` 为字符串,是你要用的协程池的名称。
`$size` 为大小,最大不可超过 Swoole 配置文件中指定的最大协程数量。

View File

@@ -0,0 +1,104 @@
# 事件跟踪器及调试
众所周知炸毛框架中的事件由内置的事件分发器EventDispatcher负责分发但调试事件分发在之前的版本比较困难例如不能获取到事件如何被调用以及事件如何被捕获。
EventTracer 的作用是记录事件的调用顺序,以便于调试。
命名空间使用指南:`use ZM\Event\EventTracer;`
## EventTracer::getCurrentEvent() - 获取当前注解事件对象
```php
/**
* @OnStart()
*/
public function onStart() {
zm_dump(EventTracer::getCurrentEvent());
}
/*
^ ZM\Annotation\Swoole\OnStart^ {#192
+worker_id: 0
+method: "onStart"
+class: "Module\Example\Hello"
}
*/
```
这里这个方法必须在注解事件内执行,如果在注解事件外执行,将会返回 `null`
## EventTracer::getCurrentEventMiddlewares() - 获取当前注解事件的中间件们
```php
/**
* @OnStart()
* @Middleware("timer")
*/
public function onStart() {
zm_dump(EventTracer::getCurrentEventMiddlewares());
}
/*
^ array:1 [
0 => ZM\Annotation\Http\Middleware^ {#194
+middleware: "timer"
+params: []
+method: "onStart"
+class: "Module\Example\Hello"
}
]
*/
```
返回值为当前注解事件的中间件们,如果没有注解中间件,返回 `[]`
## EventTracer::getEventTraceList() - 获取注解事件的列表
此处返回的是 `getCurrentEvent()` 相同的对象,但是返回的是一个数组,数组中的元素是注解事件。
```php
/**
* 一个简单随机数的功能demo
* 问法1随机数 1 20
* 问法2从1到20的随机数
* @CQCommand("随机数")
* @Middleware("timer")
* @CQCommand(pattern="*从*到*的随机数")
* @return string
*/
public function randNum() {
// 此处为随机数代码
zm_dump(EventTracer::getEventTraceList());
return "随机数:" . rand(1, 20);
}
/*
^ array:2 [
0 => ZM\Annotation\CQ\CQCommand^ {#193
+match: ""
+pattern: "*从*到*的随机数"
+regex: ""
+start_with: ""
+end_with: ""
+keyword: ""
+alias: []
+message_type: ""
+user_id: 0
+group_id: 0
+discuss_id: 0
+level: 20
+method: "randNum"
+class: "Module\Example\Hello"
}
1 => ZM\Annotation\Swoole\OnMessageEvent^ {#165
+connect_type: "default"
+rule: "connectIsQQ()"
+level: 99
+method: "handleByEvent"
+class: "ZM\Module\QQBot"
}
]
*/
```
## EventDispatcher::enableEventTrace() - 启用事件跟踪器
还没写完,不着急。

View File

@@ -8,35 +8,35 @@
根据加载的用户编写的代码类名来获取类所在的文件路径。
=== "src/Module/Example/Hello.php"
**src/Module/Example/Hello.php**
```php
<?php
namespace Module\Example;
class Hello { ... }
```
```php
<?php
namespace Module\Example;
class Hello { ... }
```
=== "src/Module/Example/Start.php"
**src/Module/Example/Start.php**
```php
<?php
namespace Module\Example;
use ZM\Annotation\Swoole\OnStart;
class Start {
/**
* @OnStart()
*/
public function onStart() {
Console::info("Path: ".getClassPath(Hello::class));
}
}
```
```php
<?php
namespace Module\Example;
use ZM\Annotation\Swoole\OnStart;
class Start {
/**
* @OnStart()
*/
public function onStart() {
Console::info("Path: ".getClassPath(Hello::class));
}
}
```
=== "输出结果"
**输出结果**
```
[11:12:02] [I] [#0] Path: /mnt/d/project/zhamao-framework/src/Module/Example/Hello.ph
```
```
[11:12:02] [I] [#0] Path: /mnt/d/project/zhamao-framework/src/Module/Example/Hello.ph
```
## explodeMsg()
@@ -289,7 +289,7 @@ zm_dump($pass);
定义:`zm_config($name, $key = null)`
有关 ZMConfig 模块的说明,见 [指南 - 基本配置](/guide/basic-config/)。
有关 ZMConfig 模块的说明,见 [指南 - 基本配置](/guide/basic-config)。
```php
zm_config("global"); //等同于 ZMConfig::get("global");
@@ -321,4 +321,3 @@ zm_config("global", "swoole"); //等同于 ZMConfig::get("global", "swoole");
## zm_verbose()
`Console::verbose($msg)`

View File

@@ -1,37 +1,44 @@
# 远程终端
框架在 2.3 版本时删除了本地终端(就是框架启动后可以在终端输入一些参数),因为框架的多进程模式会导致终端输入错乱,所以暂时取消掉了。
而远程终端应运而生,为的是弥补这一功能。与之前不同的是,远程终端使用 nc 连接,无需任何其他组件和客户端,而且功能更丰富,支持自定义命令。
## 启用
有两种开启方式:
- 永久开启:全局配置文件中找到 `remote_terminal``status`,改为 true启动框架即可。
- 临时开启:启动框架时加上参数 `--remote-terminal`。例如:`vendor/bin/start server --remote-terminal`
## 配置
在一般情况下,框架为了安全,直接按照默认配置,会监听 `127.0.0.1:20002` 端口,不可以远程访问,只能使用本机的 nc 连接,效果如下:
本地主机:
![img.png](https://static.zhamao.me/images/docs/3432551c08b34ca10aaf19f3f82aedeb.png)
从别的主机:
![img.png](https://static.zhamao.me/images/docs/6f35f2745d66c7e186da75b6f09248c2.png)
如果将 `host` 改为 `0.0.0.0` 或对应监听地址,即可指向性访问。
但是,如果你又想远程连接,又想保证安全,那么可以设置一个 token 参数,来保证连接时需要输入 token 才能使用远程终端。
假设我们的 token 是 `iAMTokEn`
![img.png](https://static.zhamao.me/images/docs/e502af4c0fd9359615548303cacb70dd.png
)
![img.png](https://static.zhamao.me/images/docs/e502af4c0fd9359615548303cacb70dd.png)
## 使用
默认情况下,使用 `nc` 命令即可。
```bash
nc <your-host> <your-port> -vvv
# nc 127.0.0.1 20002 -vvv
```
输入 help 即可查看内置的常用指令:
![img.png](https://static.zhamao.me/images/docs/7b74aa2b487c86482097ec7692c66e08.png
)
![img.png](https://static.zhamao.me/images/docs/7b74aa2b487c86482097ec7692c66e08.png)

View File

@@ -1,4 +1,4 @@
# 单例类SingletonTrait
# 单例类 - SingletonTrait
单例类,顾名思义,就是让用户声明的类拥有单例的特性,而这一组件引入的方式也最直接。它是一个 PHP 的 `trait`

View File

@@ -2,7 +2,7 @@
此类管理的是 TaskWorker 相关工作。有关使用 TaskWorker 的教程,见 [进阶 - 使用 TaskWorker 进程处理密集运算](/advanced/task-worker)
类定义:`\ZM\Utils\TaskManager`
类定义:`\ZM\Utils\Manager\TaskManager`
使用 TaskWorker 需要先在 `global.php` 配置文件中开启!
@@ -23,4 +23,3 @@
```php
TaskManager::runTask("heavy_task", 100, "param1", "param2");
```

View File

@@ -64,4 +64,3 @@ array:31 [
```
> 为什么不能重载所有文件?因为框架是多进程模型,而重载相当于只重新启动了一次 Worker 进程Manager 和 Master 进程未重启,所以被 Manager、Master 进程已经加载的 PHP 文件无法使用 reload 命令重新加载。详见 [进阶 - 进程间隔离](/advanced/multi-process/#_5)。

View File

@@ -6,9 +6,11 @@ HTTP 路由管理器用作管理炸毛框架内 `@RequestMapping` 和静态目
> 2.3.0 版本起可用。
!!! warning "注意"
::: warning 注意
因为炸毛框架的路由实现是不基于跨进程的共享内存的,所以每次使用这里面的工具函数都需要单独在所有 Worker 进程中执行一次,最好的办法就是在启动框架时执行(`@OnStart(-1)` 即可,代表此注解事件将在每个工作进程中都被执行一次)。
因为炸毛框架的路由实现是不基于跨进程的共享内存的,所以每次使用这里面的工具函数都需要单独在所有 Worker 进程中执行一次,最好的办法就是在启动框架时执行(`@OnStart(-1)` 即可,代表此注解事件将在每个工作进程中都被执行一次)。
:::
## 方法

View File

@@ -4,10 +4,11 @@
命名空间:`use ZM\Requests\ZMRequest;`
!!! warning "注意"
::: warning 注意
在使用 Swoole 4.6.0 以下(不包含)的版本时,最好使用 Swoole 官方推荐的 Saber 或者 ZMRequest 这个轻量的 HTTP 请求客户端,不要使用 curl_exec因为在老版本的 Swoole 上对 curl 的协程 Hook 支持不是很完善。
在使用 Swoole 4.6.0 以下(不包含)的版本时,最好使用 Swoole 官方推荐的 Saber 或者 ZMRequest 这个轻量的 HTTP 请求客户端,不要使用 curl_exec因为在老版本的 Swoole 上对 curl 的协程 Hook 支持不是很完善。
:::
## ZMRequest::get()
@@ -262,11 +263,12 @@ $a->onClose(function($client){
返回值:`true|false`,当为 `true` 时代表握手成功,此时可以在回调里愉快地收发消息了。如果为 `false` 表明握手失败。
!!! warning "注意"
::: warning 注意
这里由于是协程转异步,所以不能确定 `upgrade()``onMessage()` 哪个先会被触发(一般情况下如果服务器不是立刻响应回包信息,总是会先返回 `upgrade()` 的结果。
这里由于是协程转异步,所以不能确定 `upgrade()``onMessage()` 哪个先会被触发(一般情况下如果服务器不是立刻响应回包信息,总是会先返回 `upgrade()` 的结果。
:::
## 设置参数
见:[Swoole - HTTP 客户端](http://wiki.swoole.com/#/coroutine_client/http_client?id=set)

View File

@@ -1,3 +0,0 @@
# 框架组件
这里列举了框架内的你可能会用到的常用组件。

View File

@@ -14,7 +14,6 @@ src/
   │   └── zm.json
   └── Middleware/
   └── TimerMiddleware.php
```
我们在 Example 目录下创建一个 `zm.json` 的文件,编写配置,即代表 `src/Module/Example/` 文件夹及里面的用户模块源码为一个模块包,也就可以被框架识别并打包处理。
@@ -37,14 +36,16 @@ src/
- 含义:模块的描述。
??? note "点我查看编写实例"
::: tip 编写实例
```json
{
"name": "my-first-module",
"description": "这个是一个示例模块打包教程"
}
```
```json
{
"name": "my-first-module",
"description": "这个是一个示例模块打包教程"
}
```
:::
#### - version
@@ -53,13 +54,17 @@ src/
版本处理方式和 Composer 基本一致,建议使用三段式,也就是 `大版本.小版本.补丁版本`。关于三段式版本的描述和规范,见 [到底三段式版本号是什么?](https://www.chrisyue.com/what-the-hell-are-semver-and-the-difference-between-composer-version-control-sign-tilde-and-caret.html)。
??? note "点我查看编写实例:"
```json
{
"name": "my-first-module",
"description": "这个是一个示例模块打包教程"
}
```
::: tip 编写实例
```json
{
"name": "my-first-module",
"description": "这个是一个示例模块打包教程",
"version": "1.0.0"
}
```
:::
#### - depends
@@ -68,17 +73,20 @@ src/
此处用作模块的依赖检测,假设模块 `foo` 依赖模块 `bar` 的 1.x 版本但是不兼容 `bar` 的 2.x 版本,可以像 Composer 的 `require` 一样编写版本依赖:`^1.0`。也可以使用 `~``>=``*` 这些与 Composer 包管理相同逻辑的版本依赖关系,详见 [Composer - 包版本](https://docs.phpcomposer.com/01-basic-usage.html#Package-Versions)。
??? note "点我查看编写实例:"
```json
{
"name": "foo",
"description": "这个是一个示例模块打包教程",
"depends": {
"bar": "^1.0",
"bsr": "*"
}
::: tip 编写实例
```json
{
"name": "foo",
"description": "这个是一个示例模块打包教程",
"depends": {
"bar": "^1.0",
"bsr": "*"
}
```
}
```
:::
#### - light-cache-store
@@ -89,25 +97,168 @@ src/
我们假设在项目模块中使用到了 `group-status` 这一个 LightCache那么只需要写 `light-cache-store` 配置项,在模块打包时就会将持久化的数据也打包到 phar 模块包内。
??? note "点我查看编写实例:"
```json
{
"name": "foo",
"description": "这个是一个示例模块打包教程",
"light-cache-store": [
"group-status"
]
}
```
::: tip 编写实例
```json
{
"name": "foo",
"description": "这个是一个示例模块打包教程",
"light-cache-store": [
"group-status"
]
}
```
:::
| 字段名 | 类型 | 含义 |
| ---------------------- | ---------------------------------- | ---------------------------------------- |
| description | string | 模块的描述 |
| version | string | 模块的版本(建议使用 x.y.z 三段式) |
| depends | array of string例如`{"a":"b"}` | 模块依赖关系声明 |
| light-cache-store | string[] | 打包模块时要储存的持久化 LightCache 键名 |
| global-config-override | string \| false | 是否需要手动编辑全局配置(`global.php` |
| | | |
#### - global-config-override
- 类型string | false。
- 含义:解包时是否需要手动编辑全局配置(`global.php`)。
这里如果写 string 类型的,那么就是相当于在解包时会提示此处的内容,内容推荐填写要求解包模块用户需要编辑的项目,比如 「请将 static_file_server 的 status 改为 true以便使用静态文本功能」。
如果是 false那么和不指定此参数效果是一样的无需用户修改 global.php。
::: tip 编写实例
```json
{
"name": "foo",
"description": "这个是一个示例模块打包教程",
"global-config-override": "请将 static_file_server 的 status 改为 true"
}
```
:::
#### - allow-hotload
- 类型bool。
- 含义是否允许用户无需解压直接加载模块包文件phar
当此项为 true 时,可以将模块包直接放入 `zm_data/modules` 文件夹下,然后将 `global.php` 中的 `module_loader` 项中的 `enable_hotload` 改为 true启动框架即可加载。
::: tip 编写实例
```json
{
"name": "foo",
"description": "这个是一个示例模块打包教程",
"allow-hotload": true
}
```
:::
::: warning 注意
如果使用允许热加载,那么模块包中的配置最好不要有 `global-config-override``light-cache-store`,以此来达到最正确的效果,一般热加载更适合 Library类型的模块。
:::
#### - zm-data-store
- 类型array of string例如 `["foo","bar"]`)。
- 含义:打包时要添加到模块包内的 `zm_data` 目录下的子目录或文件。
其中项目必须是相对路径,不能是绝对路径,且必须是在配置项 `zm_data` 指定的目录(默认会在框架项目的根目录下的 `zm_data/` 目录。
我们假设要打包一个 `{zm_data 目录}/config/` 目录及其目录下的文件,和一个 `main.png` 文件,下方是实例。
::: tip 编写实例
```json
{
"name": "foo",
"description": "这个是一个示例模块打包教程",
"zm-data-store": [
"config/",
"main.png"
]
}
```
:::
在打包时框架会自动添加这些文件到 phar 插件包内,到解包时,会自动将这些文件释放到对应框架的 `zm_data` 目录下。
## 打包模块命令
编写配置文件 `zm.json` 后,就可以被框架正常识别为模块形式,你也可以使用对无需打包的模块进行配置以进行分类管理。
### module:list
使用 list 命令可以列出炸毛框架检测到配置文件或打包好的模块。
```
$ ./zhamao module:list
[foo]
类型: source
版本: 1.0.0
描述: 示例模块打包文件
目录: src/Module/Example
没有发现已打包且装载的模块!
```
其中 `[ ]` 内为识别出来的模块名称,由上方用户编写的 `zm.json` 定义,类型为 `source` 是源码形式,也就是指定了 `zm.json` 形式的模块,目录为模块所在子目录。
我们假设打包上方定义的 `foo` 模块,使用下方命令 `module:pack` 即可。
### module:pack
使用 pack 命令可以将配置好的模块打包为 `xxx.phar` 文件并转移或发布给他人。
我们假设打包模块脚手架的默认模块 `src/Module/Example` 下面的模块源码和附带一个 `zm_data` 目录下的文件(我们就随便打包一下 Swoole 的输出日志吧)。`zm.json` 文件内容如下:
```json
{
"name": "foo",
"description": "示例模块打包文件",
"version": "1.0.0",
"allow-hotload": true,
"zm-data-store": [
"crash/swoole_error.log"
]
}
```
然后输入命令:
```
$ ./zhamao module:pack foo
[15:07:11] [I] 模块输出文件:/root/zhamao-framework/zm_data/output/foo_1.0.0.phar
[15:07:11] [S] 打包完成!
```
如果提示文件夹不存在,请先手动创建文件夹:`mkdir /path/to/your/zm_data/output`
在打包后,你将获得一个 `foo_1.0.0.phar` 的文件。
> 如果你没有在 `zm.json` 中指定 `version`,那么输出的 phar 文件是不会带版本号的。
打包后的 phar 内将包含:
- Hello.php
- zm.json
- crash/swoole_error.log
- 必要的框架热加载以及解包需要的配置信息
## 打包命令
```bash
# ./zhamao 和原先的 vendor/bin/start 是完全一致的
./zhamao module:pack <module-name>
```
例如,打包上面的名叫 foo 的模块:`./zhamao module:pack foo`
打包命令执行后,将会在 `zm_data` 下的 `output` 目录输出一个 phar 文件。如果你指定了 `version` 参数,那么文件名将会是 `${name}_${version}.phar`,如果没有指定版本,那么只会有 `${name}.phar`,同时如果文件已经存在,将覆盖写入。
## 查看模块信息命令
```bash
./zhamao module:list
```
通过此命令可以查看模块相关的信息,如未打包但已配置的模块信息等。

View File

@@ -0,0 +1,84 @@
# 模块解包
从 2.5 版本起,炸毛框架的模块源码支持了打包和分发,分发后必不可少的一步就是将其解包。
解包过程大致为:
1. 检查模块的配置文件是否正常。
2. 检查模块的依赖问题,如果有依赖但未安装,则抛出异常。
3. 检查 LightCache 轻量缓存是否需要写入。
4. 检查 `zm_data` 是否有需要存入的数据。
5. 合并 `composer.json` 文件。
6. 拷贝 `zm_data` 相关的文件。
7. 写入 LightCache 相关数据。
8. 提示用户手动合并 `global.php` 全局配置文件。
9. 拷贝模块 PHP 源文件。
## 解包命令
```bash
./zhamao module:unpack <module-name>
```
首先将待解包的 phar 文件放入 `zm_data` 目录下的 `modules` 文件夹(如果不存在需要手动创建),如果你手动修改过 `global.php` 下面的 `module_loader.load_path` 项,需要放入对应的目录。
放入后,结构如下:
```
zm_data/
zm_data/modules/
zm_data/modules/foo.phar
```
接下来需要知道模块的名称。当然一般情况下phar 的名称可以获取到模块的实际名称,如 `foo`,但最好用 `./zhamao module:list` 列出模块的信息来获取真实的模块名称。
```
./zhamao module:list
# 下面是输出
[foo]
类型: 模块包(phar)
位置: zm_data/modules/我是假的名字.phar
```
解包过程十分简单,只需要执行一次命令即可。
```
./zhamao module:unpack foo
# 下面是输出
[10:05:40] [I] Releasing source file: src/Module/Example/Hello.php
[10:05:40] [I] Releasing source file: src/Module/Example/zm.json
[10:05:40] [S] 解压完成!
```
### 命令参数
在解包时会遇到各种复杂的情况,如源码文件已存在、数据已存在、依赖问题等,通过增加参数可以控制解包时的行为。
#### --overwrite-light-cache
含义:覆盖现有的 LightCache 键值(如果存在的话)。
#### --overwrite-zm-data
含义:覆盖现有的 `zm_data` 下的文件(如果存在的话)。
#### --overwrite-source
含义:覆盖现有的 PHP 模块源文件(如果存在的话)。
#### --ignore-depends
含义:解包时忽略检查依赖。
### 常见问题
如果你解包的模块包要求修改 `global.php`,则会出现类似这样的提示:
```
# ./zhamao module:unpack foo
[14:47:39] [W] 模块作者要求用户手动修改 global.php 配置文件中的项目:
[14:47:39] [W] *请把全局配置文件的light_cache项目中max_strlen选项调整为至少65536
请输入修改模式y(使用vim修改)/e(自行使用其他编辑器修改后确认)/N(默认暂不修改)[y/e/N]
```
一般这种情况,根据第二条提示(第二条提示为打包时填入的 `global-config-override`)。如果输入 y则会自动执行命令 `vim config/global.php`,如果输入的是 e则会等待你手动修改完成文件最后按回车完成修改。默认情况直接回车的话会跳过此步骤如果模块要求了修改但跳过修改安装后可能会有功能缺失等问题。

View File

@@ -16,10 +16,11 @@ $config['init_atomics'] = [
这时我们就成功初始化两个原子计数器,名字分别为 `foo``bar`
!!! warning "注意"
::: warning 注意
初始化的值必须是不小于 0 的 int32 值!
初始化的值必须是不小于 0 的 int32 值!
:::
## 使用
@@ -59,7 +60,8 @@ class Hello {
设置计数的数字:`ZMAtomic::get("bar")->set(77);`
!!! note "提示"
::: tip 提示
还有一些不常用的方法,可以看 Swoole 官方的文档,这里就不一一列举了。
还有一些不常用的方法,可以看 Swoole 官方的文档,这里就不一一列举了。
:::

View File

@@ -8,6 +8,10 @@ DataProvider 是框架内提供的一个简易的文件管理类。
`working_dir()`
## DataProvider::getSourceRootDir()
获取用户的源码根目录,除 Phar 模式外与 `getWorkingDir()` 相同。
## DataProvider::getFrameworkLink()
`ZMConfig::get("global", "http_reverse_link")`,获取反向代理的链接。
@@ -46,6 +50,45 @@ DataProvider::getDataFolder("TestModule"); // 例如返回 /root/zhamao-framewor
文件名同上 `saveToJson()` 的定义,解析后的返回值为原先的内容或 `null`(如果文件不存在或 json 解析失败)。
## DataProvider::scanDirFiles()
递归或非递归扫描目录,返回相对目录的文件列表或绝对目录的文件列表。(非常好用)
定义:`scanDirFiles($dir, $recursive = true, $relative = false)`
`$dir` 为要扫描的目录,`$recursive` 为是否递归,`$relative` 为是否返回相对目录的文件列表。
从给定的目录下开始遍历整个目录,如果将 `$recursive` 设置为 `true`,则会递归扫描子目录,否则将返回包含目录的文件列表。
如果将 `$relative` 设置为 `true`,则会返回文件列表的相对路径,否则返回绝对路径。
例如:假设目录 `/home/test/` 下有两个文件:`test1.txt``testdir/test2.txt`:如果将 `$recursive` 设置为 `true``$relative` 设置为 `false`,则返回的文件列表为:
```json
[
"/home/test/test1.txt",
"home/test/testdir/test2.txt"
]
```
相同条件下,如果将 `$relative` 设置为 `true`
```json
[
"test1.txt",
"testdir/test2.txt"
]
```
如果再把 `$recursive` 设置为 `false`
```json
[
"test1.txt",
"testdir"
]
```
## 其他文件读取
框架比较贴近原生的 PHP所以推荐直接使用原生的方法来读写文件`file_get_contents``file_put_contents`)。但有一点要注意,框架内最好使用**工作目录或者绝对路径**。
@@ -58,7 +101,8 @@ $r = file_get_contents(working_dir() . "/composer.json");
file_put_contents("/tmp/test.txt", "hello world");
```
!!! warning "注意"
::: warning 注意
在默认的情况里,框架的根目录均为可写可读的,在读写文件时务必要注意目录的位置和权限。使用 `working_dir()` 获取目录后面需要加 `/` 再追加自己的文件名或子目录名。
在默认的情况里,框架的根目录均为可写可读的,在读写文件时务必要注意目录的位置和权限。使用 `working_dir()` 获取目录后面需要加 `/` 再追加自己的文件名或子目录名。
:::

View File

@@ -81,14 +81,14 @@ public function storeAfterRemove() {
}
```
<chat-box>
) store
( OK
) storeAfterRemove
( 内容存在!
^ 等待 30 秒
( 内容不存在!
</chat-box>
<chat-box :my-chats="[
{type:0,content:'store'},
{type:1,content:'OK'},
{type:0,content:'storeAfterRemove'},
{type:1,content:'内容存在!'},
{type:2,content:'等待 30 秒'},
{type:1,content:'内容不存在!'},
]"></chat-box>
### LightCache::update()
@@ -125,9 +125,9 @@ dump(LightCache::getExpire("test")); // 返回 10
```php
$s = LightCache::set("test", "hello", 20); //假设这条代码执行时时间戳是 1616838482
zm_sleep(10);
dump(LightCache::getExpire("test")); // 返回 1616838502
dump(LightCache::getExpireTS("test")); // 返回 1616838502
zm_sleep(10);
dump(LightCache::getExpire("test")); // 返回 null
dump(LightCache::getExpireTS("test")); // 返回 null
```
### LightCache::getMemoryUsage()
@@ -218,14 +218,14 @@ public function getStore() {
}
```
<chat-box>
^ 我在 2021-01-05 15:21:00 发送这条消息
) getStore
( 2021-01-05 15:20:00
^ 这时我用 Ctrl+C 停止框架,过一会儿再启动
) getStore
( 存储时间2021-01-05 15:20:00
</chat-box>
<chat-box :my-chats="[
{type:2,content:'我在 2021-01-05 15:21:00 发送这条消息'},
{type:0,content:'getStore'},
{type:1,content:'2021-01-05 15:20:00'},
{type:2,content:'这时我用 Ctrl+C 停止框架,过一会儿再启动'},
{type:0,content:'getStore'},
{type:1,content:'存储时间2021-01-05 15:20:00'},
]"></chat-box>
### 数据加锁
@@ -250,10 +250,11 @@ public function test() {
在运行完测试后,通过 `LightCache::get("web_count")`,获取到的数你会发现不是 200000。怎么回事呢请自行翻阅多进程开发相关的书籍哦或者简单理解为有一些情况下进程 1 执行到了 `if-else` 语句,另一个进程也执行到了这里,两次在代码层面加的数是相同的,则虽然请求了两次,但是后执行 set 的那个进程又覆盖了前一个进程执行的值,导致最终结果加了 1 而不是 2
!!! note "提示"
::: tip 提示
同样的场景,使用 ZMAtomic 就不需要使用锁了。Atomic 是一句话:`add(1)` 立即加值的。而 LightCache 需要加锁的情况一般都是 `get->改值->set` 这样的代码。
同样的场景,使用 ZMAtomic 就不需要使用锁了。Atomic 是一句话:`add(1)` 立即加值的。而 LightCache 需要加锁的情况一般都是 `get->改值->set` 这样的代码。
:::
解决这一问题,就需要用到锁。这种情况下,我们首先考虑的是自旋锁,框架也因此内置了一个方便使用的自旋锁组件。详见下一章:自旋锁。
@@ -271,13 +272,13 @@ public function test() {
### WorkerCache 跨进程大缓存
WorkerCache 和 LightCache 几乎完全不同WorkerCache 存储的方式说白了就是 PHP 的静态变量,不过框架支持使用封装好的进程间通信进行跨进程读取。但由于需要设置一个存储变量的进程,所以配置文件必须先指定要将数据存到哪个 Worker/TaskWorker 进程中。关于框架内多进程的说明,请见 [进阶 - 多进程 Hack](/advanced/multi-process/)。
WorkerCache 和 LightCache 几乎完全不同WorkerCache 存储的方式说白了就是 PHP 的静态变量,不过框架支持使用封装好的进程间通信进行跨进程读取。但由于需要设置一个存储变量的进程,所以配置文件必须先指定要将数据存到哪个 Worker/TaskWorker 进程中。关于框架内多进程的说明,请见 [进阶 - 多进程 Hack](/advanced/multi-process)。
定义:`ZM\Store\WorkerCache`
#### 配置
见 [基本配置](/guide/basic-config/)。
见 [基本配置](/guide/basic-config)。
#### WorkerCache::get()
@@ -342,12 +343,11 @@ class Hello {
}
```
<chat-box>
) set_store hello world
( 成功!
) get_store hello
( world
) get_store foo
( 内容不存在!
</chat-box>
<chat-box :my-chats="[
{type:0,content:'set_store hello world'},
{type:1,content:'成功!'},
{type:0,content:'get_store hello'},
{type:1,content:'world'},
{type:0,content:'get_store foo'},
{type:1,content:'内容不存在!'},
]"></chat-box>

View File

@@ -0,0 +1,101 @@
# MySQL 数据库(旧版组件)
::: warning 注意
此 MySQL 组件为旧版 MySQL 查询器组件,为了统一和提升对未来独立组件的兼容性,现转变为使用 `doctrine/dbal``doctrine/orm` 库来实现查询器,请转到 [MySQL 查询器]()。
:::
## 配置
炸毛框架的数据库组件支持原生 SQL、查询构造器去掉了复杂的对象模型关联同时默认为数据库连接池使开发变得简单。
数据库的配置位于 `config/global.php` 文件的 `sql_config` 段。
数据库操作的唯一核心工具类和功能类为 `\ZM\DB\DB`,使用前需要配置 host 和 use 此类。
## 查询构造器
在 炸毛框架 中,数据库查询构造器为创建和执行数据库查询提供了一个方便的接口,它可用于执行应用程序中大部分数据库操作。同时,查询构造器使用 `prepare` 预处理来保护程序免受 SQL 注入攻击,因此没有必要转义任何传入的字符串。
### 新增数据
```php
DB::table("admin")->insert(['admin_name', 'admin_password'])->save();
// INSERT INTO admin VALUES ('admin_name', 'admin_password')
```
其中 `insert` 的参数是插入条目的数据列表。假设 admin 表有 `name``password` 两列。
> 自增 ID 插入 0 即可。
### 删除数据
```php
DB::table("admin")->delete()->where("name", "admin_name")->save();
// DELETE FROM admin WHERE name = 'admin_name'
```
其中 `where` 语句的第一个参数为列名,当只有两个参数时,第二个参数为值,效果等同于 SQL 语句:`WHERE name = 'admin_name'`,当含有第三个参数且第二个参数为 `=``!=``LIKE` 的时候,效果就是 `WHERE 第一个参数 第二个参数的操作符 第三个参数`
### 更新数据
```php
DB::table("admin")->update(["name" => "fake_admin"])->where("name", "admin_name")->save();
// UPDATE admin SET name = 'fake_admin' WHERE name = 'admin_name'
```
`update()` 方法中是要 SET 的内容的键值对,例如上面把 `name` 列的值改为 `fake_admin`
### 查询数据
```php
$r = DB::table("admin")->select(["name"])->where("name", "fake_admin")->fetchFirst();
// SELECT name FROM admin WHERE name = 'fake_admin'
echo $r["name"];
echo DB::table("admin")->where("name", "fake_admin")->value("name");
// SELECT * FROM admin WHERE name = 'fake_admin'
```
`select()` 方法的参数是要查询的列,默认留空为 `["*"]`,也就是所有列都获取,也可以在 table 后直接 where 查询。
其中 `fetchFirst()` 获取第一行数据。
如果只需获取一行中的一个字段值,也可以通过上面所示的 `value()` 方法直接获取。
多列数据获取需要使用 `fetchAll()`
```php
$r = DB::table("admin")->select()->fetchAll();
// SELECT * FROM admin WHERE 1
foreach($r as $k => $v) {
echo $v["name"].PHP_EOL;
}
```
### 查询条数
```php
DB::table("admin")->where("name", "fake_admin")->count();
//SELECT count(*) FROM admin WHERE name = 'fake_admin'
```
## 直接执行 SQL
> 在查询器外执行的 SQL 语句都不会被缓存,都是一定会请求数据库的。
### DB::rawQuery()
- 用途:直接执行模板查询的裸 SQL 语句。
- 参数:`$line``$params`
- 返回:查到的行的数组
`$line` 为请求的 SQL 语句,`$params` 为模板参数。
```php
$r = DB::rawQuery("SELECT * FROM admin WHERE name = ?", ["fake_admin"]);
//SELECT * FROM admin WHERE name = 'fake_admin'
echo $r[0]["password"];
```
> 参数查询已经从根本上杜绝了 SQL 注入的问题。

View File

@@ -0,0 +1,3 @@
# MySQLStatement
你好啊,这里是 Statement。TODO

View File

@@ -1,97 +1,208 @@
# MySQL 数据库
## 简介
炸毛框架的数据库组件对接了 MySQL 连接池,在使用过程中无需配置即可实现 MySQL 查询,同时拥有高并发。
目前 2.5 版本后炸毛框架底层采用了 `doctrine/dbal` 组件,可以方便地构建 SQL 语句。
本章大体查询内容均以下表 `users` 为基础:
| id | username | gender | update_time |
| -- | -------- | ------ | ----------- |
| 1 | jack | man | 2021-10-12 |
| 2 | rose | woman | 2021-10-11 |
## 配置
炸毛框架的数据库组件支持原生 SQL、查询构造器去掉了复杂的对象模型关联同时默认为数据库连接池使开发变得简单。
数据库的配置位于 `config/global.php` 文件的 `sql_config` 段。
数据库的配置位于 `config/global.php` 文件的 `mysql_config`,见 [全局配置](../../../../guide/basic-config#mysql_config)
数据库操作的唯一核心工具类和功能类为 `\ZM\DB\DB`,使用前需要配置 host 和 use 此类
如果 `mysql_config.host` 字段为空,则不创建数据库连接池,填写后将创建,且默认保持长连接
## 查询构造器
## 执行 SQL 语句
炸毛框架 中,数据库查询构造器为创建和执行数据库查询提供了一个方便的接口,它可用于执行应用程序中大部分数据库操作。同时,查询构造器使用 `prepare` 预处理来保护程序免受 SQL 注入攻击,因此没有必要转义任何传入的字符串
### 新增数据
一开始,无论你做什么数据库操作,均需要获取一个 `\ZM\MySQL\MySQLWrapper` 作为你的操作对象
```php
DB::table("admin")->insert(['admin_name', 'admin_password'])->save();
// INSERT INTO admin VALUES ('admin_name', 'admin_password')
/** @var \ZM\MySQL\MySQLWrapper $wrapper */
$wrapper = \ZM\MySQL\MySQLManager::getWrapper();
```
其中 `insert` 的参数是插入条目的数据列表。假设 admin 表有 `name``password` 两列。
::: tip 提示
> 自增 ID 插入 0 即可
这部分内容部分直接取自 [DBAL - Data Retrieval And Manipulation](https://www.doctrine-project.org/projects/doctrine-dbal/en/2.13/reference/data-retrieval-and-manipulation.html) 原文并直接翻译,如有实际不同请提交 Issue 反馈
### 删除数据
:::
### 执行预处理 SQL 语句
预处理查询很巧妙地解决了 SQL 注入问题,并且可以方便地绑定参数进行查询。
预处理一般是指使用 `?` 占位符或 `:xxx` 命名标签进行参数留空,先处理 SQL 语句再填入数据。
一般 `?` 具有前后位置性,例如如下的查询:
```php
DB::table("admin")->delete()->where("name", "admin_name")->save();
// DELETE FROM admin WHERE name = 'admin_name'
$sql = "SELECT * FROM users WHERE id = ? AND username = ?";
$stmt = $wrapper->getConnection()->prepare($sql);
$stmt->bindValue(1, "1");
$stmt->bindValue(2, "jack");
$resultSet = $stmt->executeQuery();
```
其中 `where` 语句的第一个参数为列名,当只有两个参数时,第二个参数为值,效果等同于 SQL 语句:`WHERE name = 'admin_name'`,当含有第三个参数且第二个参数为 `=``!=``LIKE` 的时候,效果就是 `WHERE 第一个参数 第二个参数的操作符 第三个参数`
其中 `$resultSet``Statement` 方法相似,此处的对象可能是 [数据库语句对象](./mysql-statement) 或 数据库结果对象(结果对象与语句对象的 `fetchXXX()` 部分一致)
### 更新数据
这里也可以使用命名标签,使用标签可以给相同参数处使用同一个标签:
```php
DB::table("admin")->update(["name" => "fake_admin"])->where("name", "admin_name")->save();
// UPDATE admin SET name = 'fake_admin' WHERE name = 'admin_name'
$sql = "SELECT * FROM users WHERE gender = :name OR username = :name";
$stmt = $wrapper->getConnection()->prepare($sql);
$stmt->bindValue("name", "jack");
$resultSet = $stmt->executeQuery();
```
`update()` 方法中是要 SET 的内容的键值对,例如上面把 `name` 列的值改为 `fake_admin`
### 执行常规语句
### 查询数据
执行常规语句为 `statement` 方式执行,此方法执行后只返回影响的行数,而不返回结果,适用于 `UPDATE` 等语句。
```php
$r = DB::table("admin")->select(["name"])->where("name", "fake_admin")->fetchFirst();
// SELECT name FROM admin WHERE name = 'fake_admin'
echo $r["name"];
echo DB::table("admin")->where("name", "fake_admin")->value("name");
// SELECT * FROM admin WHERE name = 'fake_admin'
<?php
$count = $wrapper->executeStatement('UPDATE users SET username = ? WHERE id = ?', array('jwage', 1));
echo $count; // 1
```
`select()` 方法的参数是要查询的列,默认留空为 `["*"]`,也就是所有列都获取,也可以在 table 后直接 where 查询。
### 执行查询语句
其中 `fetchFirst()` 获取第一行数据
如果只需获取一行中的一个字段值,也可以通过上面所示的 `value()` 方法直接获取。
多列数据获取需要使用 `fetchAll()`
为给定的 SQL 创建一个准备好的语句并将参数传递给 executeQuery 方法,然后返回结果集。此方法为上述的「预处理查询语句」的简化版,可直接在第二个参数使用 array 插入绑定参数执行
```php
$r = DB::table("admin")->select()->fetchAll();
// SELECT * FROM admin WHERE 1
foreach($r as $k => $v) {
echo $v["name"].PHP_EOL;
}
$resultSet = $wrapper->executeQuery('SELECT * FROM user WHERE username = ?', array('jack'));
$user = $resultSet->fetchAssociative();
/* $user 值
array(
0 => array(
'id' => 1,
'username' => 'jack',
'gender' => 'man',
'update_time' => '2021-10-12'
)
)
*/
```
### 查询条数
#### fetchAllAssociative()
执行查询并将所有结果返回一个数组中。
因此,上面的查询语句还可以直接被简化为一次方法调用:
```php
DB::table("admin")->where("name", "fake_admin")->count();
//SELECT count(*) FROM admin WHERE name = 'fake_admin'
$resultSet = $wrapper->fetchAllAssociative('SELECT * FROM user WHERE username = ?', array('jack'));
// 结果同 executeQuery()->fetchAllAssociative() 中 $user 的值。
```
#### fetchAllKeyValue()
## 直接执行 SQL
> 在查询器外执行的 SQL 语句都不会被缓存,都是一定会请求数据库的。
### DB::rawQuery()
- 用途:直接执行模板查询的裸 SQL 语句。
- 参数:`$line``$params`
- 返回:查到的行的数组
`$line` 为请求的 SQL 语句,`$params` 为模板参数。
执行查询并将前两列分别作为键和值提取到关联数组中。
```php
$r = DB::rawQuery("SELECT * FROM admin WHERE name = ?", ["fake_admin"]);
//SELECT * FROM admin WHERE name = 'fake_admin'
echo $r[0]["password"];
$resultSet = $wrapper->fetchAllKeyValue('SELECT username, gender FROM user WHERE username = ?', array('jack'));
/* $resultSet 值
array(
'jack' => 'man'
)
*/
```
> 参数查询已经从根本上杜绝了 SQL 注入的问题。
#### fetchAllAssociativeIndexed()
执行查询并将数据作为关联数组获取,其中键代表第一列,值是其余列及其值的关联数组。
```php
$users = $wrapper->fetchAllAssociativeIndexed('SELECT id, username, gender FROM users');
/*
array(
1 => array(
'username' => 'jack',
'gender' => 'man',
'update_time' => '2021-10-12'
)
)
*/
```
#### fetchNumeric()
查询并返回第一行数据,形式以数字索引方式返回每一列。
```php
$user = $wrapper->fetchNumeric('SELECT * FROM users WHERE username = ?', array('jack'));
/*
array(
0 => 'jwage',
1 => 'man',
2 => '2021-10-12'
)
*/
```
#### fetchOne()
仅返回查询结果的第一行第一列的值。
```php
$username = $wrapper->fetchOne('SELECT username FROM users WHERE id = ?', array(1));
echo $username; // jack
```
#### fetchAssociative()
返回结果内第一行的关联数组形式的数据。
```php
$users = $wrapper->fetchAssociative('SELECT * FROM users');
/*
array(
'id' => 1,
'username' => 'jack',
'gender' => 'man',
'update_time' => '2021-10-12'
)
*/
```
#### delete()
删除查询操作,第一个参数为表名,第二个参数为 `['列名' => '列值']`
```php
<?php
$wrapper->delete('users', array('username' => 'jack'));
// 等同于执行DELETE FROM user WHERE username = ? ,参数列表为('jack')
```
#### insert()
插入数据库一行,第一个参数为表名,第二个参数为对应数据。
```php
$wrapper->insert('users', array('id' => 0, 'username' => 'jwage', 'gender' => 'woman', 'update_time' => '2021-10-17'));
// INSERT INTO user (id, username, gender, update_time) VALUES (?,?,?,?) (0,jwage,woman,2021-10-17)
```
#### update()
更新数据库,使用给定数据更新匹配键值标识符的所有行。
```php
<?php
$wrapper->update('user', array('username' => 'jwage'), array('id' => 1));
// UPDATE user (username) VALUES (?) WHERE id = ? (jwage, 1)
```

View File

@@ -2,6 +2,12 @@
前面讲到 LightCache 轻量缓存在特定的情况下为了保证数据不被多进程的因素导致丢失或覆盖,在高并发情况下修改数据需要加锁,所以炸毛框架内置了 SpinLock 自旋锁。
::: tip 提示
框架单进程运行的模式下不需要任何自旋锁。
:::
## 配置
自旋锁使用无需配置,和 LightCache 同源。
@@ -57,9 +63,11 @@ public function test() {
原理剖析:在 LightCache 获取前,先对此内容上锁,这时如果其他进程有同时也在执行这个代码的时候,就会在 `SpinLock::lock()` 这行代码处原地等待,防止继续执行。等前面的那个进程执行到 `SpinLock::unlock()` 释放锁时,其他进程才可继续执行,从而避免了多个进程并行执行这段代码导致的数据错乱。
!!! error "警告"
::: danger 警告
使用锁时务必谨慎,如果不按照下面的规则使用自旋锁可能导致 CPU 占用率上升。
使用锁时务必谨慎,如果不按照下面的规则使用自旋锁可能导致 CPU 占用率上升。
:::
自旋锁使用约定:

View File

@@ -328,7 +328,7 @@ class Hello {
## 示例3接收 WS 客户端发来的数据)
见 [接入 WebSocket 客户端](/advanced/connect-ws-client/)。
见 [接入 WebSocket 客户端](/advanced/connect-ws-client)。
## 示例4使用 OnStart 给所有 Worker 进程写入缓存提速)
@@ -359,8 +359,7 @@ class Hello {
},
"key": "C",
"answer_type": 0
},
.....
}
}
```
@@ -411,10 +410,10 @@ class Hello {
聊天效果:
<chat-box>
) 找题 1
( 题目名称:法律与其他社会规范的区别在于( )。\nA. 是调整人们行为的规范\nB. 有约束力\nC. 由国家强制力保证执行\nD. 规定制裁措施\n正确答案C
</chat-box>
<chat-box :my-chats="[
{type:0,content:'找题 1'},
{type:1,content:'题目名称:法律与其他社会规范的区别在于( )。\nA. 是调整人们行为的规范\nB. 有约束力\nC. 由国家强制力保证执行\nD. 规定制裁措施\n正确答案C'},
]"></chat-box>
## 示例5创建每分钟自动执行的爬虫
@@ -431,3 +430,4 @@ public function onCrawl() {
## 示例6创建一个远程终端命令并调试框架
> 开个坑以后填。__填坑标记__
>

View File

@@ -2,13 +2,17 @@
## 注解事件概念
我们知道事件,是一个底层的 event loop 收到消息后调用对应的各类方法的一个模型,比如给机器人发送消息后框架要做的就是指定到一个你定义的函数上,处理你的业务逻辑代码。比如在默认模块中,提供了 **你好** 的回复:**你好啊,我是由炸毛框架构建的机器人!**。这项简单回复的任务就是一个事件的触发到响应的全过程。
我们知道事件,是一个底层的 event loop 收到消息后调用对应的各类方法的一个模型,比如给机器人发送消息后框架要做的就是指定到一个你定义的函数上,处理你的业务逻辑代码。比如在默认模块中,提供了 **你好** 的回复:**
你好啊,我是由炸毛框架构建的机器人!**。这项简单回复的任务就是一个事件的触发到响应的全过程。
**注解**Annotation又称标注Java 最早在 2004 年的 JDK 5 中引入的一种注释机制。目前 PHP 官方版本并未提供内置元注解和注解概念,但我们通过 `ReflectionClass` 反射类解析 PHP 代码注释从而实现了自己的一套注解机制。如果你没有写过 Java并且不了解注解是什么你可以理解为对 function 或 class 的一个修饰,因为传统的 PHP 代码逻辑我们都知道,不能简单给原先存在的函数贴标签,就比如,你不能在原本的 PHP 代码中给函数贴上一个可以影响它一生并且改变它行为的标签,而有了注解,就相当于有了给函数贴标签的机会。
**注解**Annotation又称标注Java 最早在 2004 年的 JDK 5 中引入的一种注释机制。目前 PHP 官方版本并未提供内置元注解和注解概念,但我们通过 `ReflectionClass` 反射类解析 PHP
代码注释从而实现了自己的一套注解机制。如果你没有写过 Java并且不了解注解是什么你可以理解为对 function 或 class 的一个修饰,因为传统的 PHP
代码逻辑我们都知道,不能简单给原先存在的函数贴标签,就比如,你不能在原本的 PHP 代码中给函数贴上一个可以影响它一生并且改变它行为的标签,而有了注解,就相当于有了给函数贴标签的机会。
在常见框架如 SpringSwoft 等代码结构里面,注解更是其核心的存在。
在炸毛框架中,我们所有事件的绑定均采用这一方式进行调用模块内各个方法。包括 Swoole 自身的框架启动事件、WebSocket 连接握手事件、HTTP 请求事件等等,也包括 CQHTTP 发来的事件,如`message``notice``request` 等。
在炸毛框架中,我们所有事件的绑定均采用这一方式进行调用模块内各个方法。包括 Swoole 自身的框架启动事件、WebSocket 连接握手事件、HTTP 请求事件等等,也包括 CQHTTP 发来的事件,如`message``notice`
`request` 等。
## 如何使用注解
@@ -29,9 +33,11 @@ class Hello {
}
```
其中 `@CQCommand()` 就是一个基本的注解应用。注意需引入相关注解Annotation**且必须** 以 `/**` 开始并以 `*/` 结束,否则会导致无法解析!上方 `@return` 为 IDE 自动生成的 PHPDoc不需要管。
其中 `@CQCommand()` 就是一个基本的注解应用。注意需引入相关注解Annotation**且必须** 以 `/**` 开始并以 `*/` 结束,否则会导致无法解析!上方 `@return` 为 IDE 自动生成的
PHPDoc不需要管。
有什么用?大有妙用!这个例子内注解类的用途是收到 QQ 消息后如果消息第一个词匹配到 `你好` 的话,框架就会自动处理,最终执行调用此 `hello()` 方法。注意 `CQCommand` 和其他任何后面讲到的注解类一样,需先 `use ZM\Annotation\` 下的对应注解类,否则也不能正常使用。
有什么用?大有妙用!这个例子内注解类的用途是收到 QQ 消息后如果消息第一个词匹配到 `你好` 的话,框架就会自动处理,最终执行调用此 `hello()` 方法。注意 `CQCommand`
和其他任何后面讲到的注解类一样,需先 `use ZM\Annotation\` 下的对应注解类,否则也不能正常使用。
### 基本语法
@@ -47,11 +53,27 @@ class Hello {
对于没有参数的注解类,`@参数名()` 直接使用即可。
### PHP8 原生 Attribute 使用
在 2.7.0 版本更新后,框架已经完全支持使用原生 Attribute 替代此前的 Annotation。如果你使用 PHP8.0 或以上版本,即可使用 Attribute 以取得最佳的原生编程体验。两者的参数和效果完全一致。
`@CQCommand` 为例,在 PHP8 中,你可以使用以下代码达成相同的效果。
```php
#[CQCommand('帮助', alias=['帮助列表', '菜单'])]
public function help() {
```
由于两者几乎完全一致,文档将不会加以区分,统称为注解。
> 关于原生注解的更多信息,请查阅[官方文档](https://www.php.net/manual/zh/language.attributes.php)。
## 注解和事件的关系
在炸毛框架里,注解常常被当作事件分发的一个重要角色,但注解本身又不是事件,更恰当的说,是注解代表了事件。
机器人开发过程中常见的 `@CQCommand`,或者是 HTTP 服务器路由绑定 `@RequestMapping` 都是相当于由对应注解代表了事件,而 `@Middleware``@Closed` 等这类注解显然不代表任何事件,只能当作这个函数或类的修饰属性而已。代表了事件的注解,我们称之为**注解事件**,它会在某种事件达成条件后触发注解下方的函数本身。
机器人开发过程中常见的 `@CQCommand`,或者是 HTTP 服务器路由绑定 `@RequestMapping` 都是相当于由对应注解代表了事件,而 `@Middleware``@Closed`
等这类注解显然不代表任何事件,只能当作这个函数或类的修饰属性而已。代表了事件的注解,我们称之为**注解事件**,它会在某种事件达成条件后触发注解下方的函数本身。
值得注意的是,注解事件本身概念是我凭空捏造的,我不好解释所以只能创造这么一个词来代指这一抽象的概念,硬要解释的话,大致就好比一个社区里有一个卖牛奶的,有几家人订阅了每日上门送牛奶的服务,只要你打了“给我配送牛奶”的注解,他就会上门。而它送的不止一种奶,可以给你个性化定制,比如让卖牛奶的给你带包糖带瓶水,而描述这个的注解就只能做一个之前注解的修饰。假设你只写了带包糖的注解,没有写给我配送牛奶的注解,那他永远也不会给你送牛奶和糖过来。
@@ -65,6 +87,3 @@ class Hello {
EventDispatcher::interrupt();
EventDispatcher::interrupt($data); // 也可以带返回值,自定义注解事件时有用。
```

View File

@@ -4,7 +4,7 @@
在炸毛框架中,中间件最直白的意思就是注解事件执行前、执行后、执行过程中可进行插入代码但不破坏原有代码。
```伪代码
```
@中间件1
@带条件的注解1
function 我的方法() {
@@ -165,3 +165,56 @@ public function onThrowing(?Exception $e) {
`ctx()` 为获取当前协程空间绑定的 `request``response` 对象。
## 中间件参数
中间件也可以接收额外的参数。例如,你需要在执行对应操作前验证对方是否具有指定的权限,你可以建立一个 `EnsureUserHasPermission` 中间件,并接收权限名称作为参数。
中间件参数可以在中间件中使用 `$this->middleware->params` 获取:
```php
#[MiddlewareClass('has_permission')]
class EnsureUserHasPermission implement MiddlewareInterface
{
#[OnBefore]
public function handle()
{
return $user->hasPermission($this->middleware->params[0]);
}
}
```
你可以直接在中间件注解中向中间件传递参数:
```php
#[Middleware('has_permission', ['can_execute_command'])]
public function doSomething()
{
```
## 中间件加载错误处理策略
中间件在某些情况下可能会产生普通 PHP 异常以外的异常,不能被框架的正常错误流程捕获,所以这里额外说明了中间件异常处理的几种策略。
中间件异常处理策略可以在 2.5.2 版本之后通过 `global.php` 中的 `runtime``middleware_error_policy` 设置。
- **0**: 无论被运行事件中间件是否存在,都不抛出异常,继续执行事件。
- **1**: 在框架启动时如果某事件被注解了一个不存在的中间件,则不抛出异常,在执行期间才检测是否存在此中间件,并抛出异常。
- **2**: 严格的中间件检查,在框架启动时就检测所有被注解了中间件的注解事件。
假设我们有一个路由注解 `@RequestMapping("/test")`,同时注解了一个不存在的中间件,如下:
```php
/**
* @RequestMapping("/test")
* @Middleware("foo")
*/
public function testRoute() {
return "I am testing middleware";
}
```
配置项为 0此中间件类不存在的话则会报告 warning并直接执行此函数。
配置项为 1在访问此路由执行此函数时会抛出异常中断此次事件。
配置项为 2在框架启动时抛出致命异常。

View File

@@ -4,9 +4,11 @@ QQ 机器人事件是指 CQHTTP 插件发来的 Event 事件,被框架处理
为了便于开发,这里的注解类对应 CQHTTP 插件返回的 `post_type` 类型,对号入座即可。
!!! tip "提示"
::: tip 提示
在使用注解绑定事件过程中,如果无 **必需** 参数,可一个参数也不写,效果就是此事件任何情况下都会调用此方法。例如:`@CQMessage()`
在使用注解绑定事件过程中,如果无 **必需** 参数,可一个参数也不写,效果就是此事件任何情况下都会调用此方法。例如:`@CQMessage()`
:::
事件是用户需要从 OneBot 被动接收的数据,有以下几个大类:
@@ -61,39 +63,39 @@ QQ 收到消息后触发的事件对应注解。
- 在用户 QQ 为 `123456` 的用户私聊给机器人发消息后机器人回复内容。
- 用户发送文字为 `hello` 时返回 `你好啊xxx` 的消息。
=== "代码"
代码
```php
<?php
namespace Module\Example;
```php
<?php
namespace Module\Example;
use ZM\Annotation\CQ\CQMessage;
use ZM\Annotation\CQ\CQMessage;
class Hello {
/**
* @CQMessage(message_type="private",user_id=123456)
*/
public function test() {
return "你和机器人私聊发送了这些文本:".ctx()->getMessage();
}
/**
* @CQMessage(message="hello")
*/
public function hello() {
return "你好啊,".ctx()->getUserId();
}
class Hello {
/**
* @CQMessage(message_type="private",user_id=123456)
*/
public function test() {
return "你和机器人私聊发送了这些文本:".ctx()->getMessage();
}
```
/**
* @CQMessage(message="hello")
*/
public function hello() {
return "你好啊,".ctx()->getUserId();
}
}
```
=== "效果"
效果
<chat-box>
) 假设我是私聊机器人
( 你和机器人私聊发送了这些文本:假设我是私聊机器人
^ 假设我现在切到群里在群里发hello
) hello
( 你好啊123456
</chat-box>
<chat-box :my-chats="[
{type:0,content:'假设我是私聊机器人'},
{type:1,content:'你和机器人私聊发送了这些文本:假设我是私聊机器人'},
{type:2,content:'假设我现在切到群里在群里发hello'},
{type:0,content:'hello'},
{type:1,content:'你好啊123456'},
]"></chat-box>
## CQCommand()
@@ -125,11 +127,13 @@ QQ 收到消息后触发的事件对应注解。
| group_id | `int64``string` | 限定消息发送来源群 ID`@CQMessage` | 空 |
| level | `int` | 事件优先级(越大越靠前) | 20 |
!!! warning "注意"
::: warning 注意
在 `@CQCommand` 注解事件中,从 `match` 到 `keyword` 六个参数中,必须且只能定义一个,`alias` 目前只能和 `match` 参数同时使用;
`@CQCommand` 注解事件中,从 `match``keyword` 六个参数中,必须且只能定义一个,`alias` 目前只能和 `match` 参数同时使用;
框架内部对于同一条消息事件,优先处理 `@CQCommand` 注解事件,如果未匹配到任何注解事件,则才会继续执行 `@CQMessage` 注解事件。
框架内部对于同一条消息事件,优先处理 `@CQCommand` 注解事件,如果未匹配到任何注解事件,则才会继续执行 `@CQMessage` 注解事件。
:::
- 参数 `match` 匹配模式是:遇到空格、换行就会切分,比如 `点歌 xxx yyy` 会被分割为 `[点歌,xxx,yyy]`,然后抽取第一个词做为命令去匹配,剩下的为参数。
- 参数 `pattern` 匹配模式是:\* 号位置变成参数,比如 `从*到*的随机数`,我们输入 `从1到9的随机数`,成功匹配,参数列表:`[1,9]`
@@ -141,52 +145,54 @@ QQ 收到消息后触发的事件对应注解。
我们以参数 `match` 写一个简单的 demo
=== "代码"
```php
<?php
namespace Module\Example;
代码
use ZM\Annotation\CQ\CQCommand;
```php
<?php
namespace Module\Example;
class Hello {
/**
* @CQCommand(match="疫情",alias={"COVID"})
*/
public function virus(){
$city = ctx()->getNextArg("请输入城市名称");
return "城市 ".$city." 的疫情状况如下:"."{这里假装是疫情接口返回的数据}";
}
/**
* 如果选择使用 match 参数的话,可以省略 `match=`
* @CQCommand("掷硬币")
*/
public function randChoice() {
return "你看到的是:" . (mt_rand(0,1) ? "正面" : "反面");
}
/**
* @CQCommand(pattern="*把*翻译成*")
*/
public function translate() {
ctx()->getNextArg(); // 为什么需要单独调用一次呢?看下面例子就知道啦
$text = ctx()->getNextArg(); // 获取第二个星号匹配的内容
$target = ctx()->getNextArg(); // 获取第三个星号匹配的内容
// 这里 FakeTranslateAPI 是假设我们对接了一个翻译的 API开发时请替换为自己的接口。
return "翻译结果:" . FakeTranslateAPI::translate($text, $target);
}
use ZM\Annotation\CQ\CQCommand;
class Hello {
/**
* @CQCommand(match="疫情",alias={"COVID"})
*/
public function virus(){
$city = ctx()->getNextArg("请输入城市名称");
return "城市 ".$city." 的疫情状况如下:"."{这里假装是疫情接口返回的数据}";
}
```
=== "效果"
/**
* 如果选择使用 match 参数的话,可以省略 `match=`
* @CQCommand("掷硬币")
*/
public function randChoice() {
return "你看到的是:" . (mt_rand(0,1) ? "正面" : "反面");
}
/**
* @CQCommand(pattern="*把*翻译成*")
*/
public function translate() {
ctx()->getNextArg(); // 为什么需要单独调用一次呢?看下面例子就知道啦
$text = ctx()->getNextArg(); // 获取第二个星号匹配的内容
$target = ctx()->getNextArg(); // 获取第三个星号匹配的内容
// 这里 FakeTranslateAPI 是假设我们对接了一个翻译的 API开发时请替换为自己的接口。
return "翻译结果:" . FakeTranslateAPI::translate($text, $target);
}
}
```
<chat-box>
) 疫情 北京
( 城市 北京 的疫情状况如下blablablabla
) COVID 香港
( 城市 香港 的疫情状况如下blablablabla
) 掷硬币
( 你看到的是:正面
) 我想把我爱你翻译成英语
( 翻译结果I love you!
</chat-box>
效果
<chat-box :my-chats="[
{type:0,content:'疫情 北京'},
{type:1,content:'城市 北京 的疫情状况如下blablablabla'},
{type:0,content:'COVID 香港'},
{type:1,content:'城市 香港 的疫情状况如下blablablabla'},
{type:0,content:'掷硬币'},
{type:1,content:'你看到的是:正面'},
{type:0,content:'我想把我爱你翻译成英语'},
{type:1,content:'翻译结果I love you!'},
]"></chat-box>
## CQNotice()
@@ -292,49 +298,52 @@ TODO先放着有时间再更。
### 用法
=== "代码"
代码
```php
<?php
namespace Module\Example;
```php
<?php
namespace Module\Example;
use ZM\Annotation\CQ\CQBefore;
use ZM\Annotation\CQ\CQMessage;
class Test {
/**
* @CQBefore("message")
*/
public function filter(){
// 可用于敏感词,如政治相关的词语不响应其他模块
if(mb_strpos(ctx()->getMessage(), "谷歌") !== false) return false;
else return true;
}
/**
* @CQCommand("百科")
*/
public function wiki() {
$content = ctx()->getNextArg("请说你要查百科的内容");
// 这里假设你对接了一个查百科的接口
return "已搜到匹配 $content 的如下结果:".FakeAPI::searchWiki($content);
}
use ZM\Annotation\CQ\CQBefore;
use ZM\Annotation\CQ\CQMessage;
class Test {
/**
* @CQBefore("message")
*/
public function filter(){
// 可用于敏感词,如政治相关的词语不响应其他模块
if(mb_strpos(ctx()->getMessage(), "谷歌") !== false) return false;
else return true;
}
```
/**
* @CQCommand("百科")
*/
public function wiki() {
$content = ctx()->getNextArg("请说你要查百科的内容");
// 这里假设你对接了一个查百科的接口
return "已搜到匹配 $content 的如下结果:".FakeAPI::searchWiki($content);
}
}
```
=== "效果"
效果
<chat-box>
) 百科 北京
( 已搜到匹配 北京 的如下结果blablabla
) 百科 谷歌被封
^ 机器人没有任何回复
<chat-box :my-chats="[
{type:0,content:'百科 北京'},
{type:1,content:'已搜到匹配 北京 的如下结果blablabla'},
{type:0,content:'百科 谷歌被封'},
{type:2,content:'机器人没有任何回复'},
]"></chat-box>
!!! warning "注意"
::: warning 注意
在设置了 `level` 参数后,如果设置了多个 `@CQBefore` 监听事件函数,更高 `level` 的事件函数返回了 `false`,则低 `level` 的绑定函数不会执行,所有 `@CQMessage` 绑定的事件也不会执行。
在设置了 `level` 参数后,如果设置了多个 `@CQBefore` 监听事件函数,更高 `level` 的事件函数返回了 `false`,则低 `level` 的绑定函数不会执行,所有 `@CQMessage` 绑定的事件也不会执行。
你也可以使用 `@CQBefore` 做一些消息的转发和过滤。比如你想去除用户发来的文字中的 emoji、图片等 CQ 码,只保留文本。
你也可以使用 `@CQBefore` 做一些消息的转发和过滤。比如你想去除用户发来的文字中的 emoji、图片等 CQ 码,只保留文本。
使用 `ctx()->waitMessage()` 时等待用户输入下一条消息功能和 CQBefore 配合过滤消息时需注意,见 [FAQ - CQBefore 过滤不了 waitMessage](/FAQ/wait-message-cqbefore/)
使用 `ctx()->waitMessage()` 时等待用户输入下一条消息功能和 CQBefore 配合过滤消息时需注意,见 [FAQ - CQBefore 过滤不了 waitMessage](/FAQ/wait-message-cqbefore/)
:::
## CQAfter()

View File

@@ -2,9 +2,11 @@
炸毛框架提供了一个简易但是高效易用的 HTTP 路由注解,你可以使用路由功能来开发任何 Web 应用微服务、API 接口、中间件等。
!!! quote "开发提示"
::: tip 开发提示
本章节涉及的路由和控制器概念可能和其他传统框架有一些出入,而且炸毛框架非绝对根据 PSR 标准进行开发,目的是使用上一些常见的东西尽可能地灵活和不啰嗦。
本章节涉及的路由和控制器概念可能和其他传统框架有一些出入,而且炸毛框架非绝对根据 PSR 标准进行开发,目的是使用上一些常见的东西尽可能地灵活和不啰嗦。
:::
## 控制器和路由
@@ -62,50 +64,54 @@ public function testName($param) {
}
```
### 路由示例
=== "代码"
```php
<?php
namespace Module\Example;
代码
use ZM\Annotation\Http\Controller;
use ZM\Annotation\Http\RequestMapping;
```php
<?php
namespace Module\Example;
use ZM\Annotation\Http\Controller;
use ZM\Annotation\Http\RequestMapping;
/**
* @Controller("/api")
*/
class Hello {
/**
* @Controller("/api")
* @RequestMapping("/index")
*/
class Hello {
/**
* @RequestMapping("/index")
*/
public function index(){
ctx()->getResponse()->end("This is API index page"); // 使用上下文获取响应对象
}
/**
* @RequestMapping("/ping")
*/
public function ping(){
return "pong"; // 直接返回字符串
}
public function index(){
ctx()->getResponse()->end("This is API index page"); // 使用上下文获取响应对象
}
```
/**
* @RequestMapping("/ping")
*/
public function ping(){
return "pong"; // 直接返回字符串
}
}
```
=== "效果"
效果
!!! example "效果描述"
当访问浏览器的 `http://localhost:20001/api/index` 时,浏览器会返回 `This is API index page`,当访问 `/api/ping` 的 url 时,浏览器会返回 `pong`。
::: tip 效果描述
```
/ -> 无任何路由
/api/index -> Hello->index
/api/ping -> Hello->ping
```
当访问浏览器的 `http://localhost:20001/api/index` 时,浏览器会返回 `This is API index page`,当访问 `/api/ping` 的 url 时,浏览器会返回 `pong`
!!! tip "提示"
```
/ -> 无任何路由
/api/index -> Hello->index
/api/ping -> Hello->ping
```
当 `@Controller` 为 `/` 的时候,效果和不写是一样的,`@RequestMapping` 为 `/` 或 `/index/inside` 等多级路由也是可以的。
:::
::: tip 提示
`@Controller``/` 的时候,效果和不写是一样的,`@RequestMapping``/``/index/inside` 等多级路由也是可以的。
:::
### 绑定参数
@@ -126,46 +132,46 @@ public function index($arg) {
### 示例
=== "获取 GET"
#### 获取 GET
```php
/**
* @RequestMapping("/testUrl")
*/
public function testUrl() {
$get = ctx()->getRequest()->get;
if(isset($get["name"])) return "hello, ".$get["name"];
else return "Unknown name!!";
}
```
```php
/**
* @RequestMapping("/testUrl")
*/
public function testUrl() {
$get = ctx()->getRequest()->get;
if(isset($get["name"])) return "hello, ".$get["name"];
else return "Unknown name!!";
}
```
=== "获取 POSTx-www-form-urlencoded"
#### 获取 POSTx-www-form-urlencoded
```php
/**
* @RequestMapping("/testUrl")
*/
public function testUrl() {
$post = ctx()->getRequest()->post;
if(isset($post["name"])) return "hello, ".$post["name"];
else return "Unknown name!!";
}
```
```php
/**
* @RequestMapping("/testUrl")
*/
public function testUrl() {
$post = ctx()->getRequest()->post;
if(isset($post["name"])) return "hello, ".$post["name"];
else return "Unknown name!!";
}
```
=== "获取 JSON POST"
#### 获取 JSON POST
```php
/**
* @RequestMapping("/testUrl")
*/
public function testUrl() {
$post = ctx()->getRequest()->rawContent();
$json = json_decode($post, true);
if ($json === null) return "Invalid json data!";
if(isset($json["name"])) return "hello, ".$json["name"];
else return "Unknown name!!";
}
```
```php
/**
* @RequestMapping("/testUrl")
*/
public function testUrl() {
$post = ctx()->getRequest()->rawContent();
$json = json_decode($post, true);
if ($json === null) return "Invalid json data!";
if(isset($json["name"])) return "hello, ".$json["name"];
else return "Unknown name!!";
}
```
## 设置路由请求方式

View File

@@ -6,9 +6,20 @@
但是如果因为用户的误操作,导致炸毛框架其中的一个或多个进程阻塞,或者比如将框架挂在 screen 等守护但是守护服务进程被杀掉,总之就是无法使用 Ctrl+C 的方式正常关闭框架,这时就需要正确地杀掉所有框架进程(这固然可能会造成内存的缓存数据丢失)。
!!! warning "注意"
### v2.7.0 及以上版本教程
下方涉及 `ps` 命令后使用 `grep` 过滤的框架进程方式,如果你的服务器同时有其他使用 PHP 启动的服务,命令行刚好有 `server` 字样,可能会导致误杀,如果有影响的话,建议将 `grep server` 换成你启动时命令行的特殊参数或手动排除!
- 安全关框架指令:`./zhamao server:stop`
- 万能杀死所有框架进程指令:`./zhamao server:stop --force`
- 监视框架是否在运行:`./zhamao server:status`
- Worker 进程卡死:连续按 5 次 Ctrl+C 即可强行杀掉所有进程SIGKILL
### v2.6.6 及以下版本教程
::: warning 注意
下方涉及 `ps` 命令后使用 `grep` 过滤的框架进程方式,如果你的服务器同时有其他使用 PHP 启动的服务,命令行刚好有 `server` 字样,可能会导致误杀,如果有影响的话,建议将 `grep server` 换成你启动时命令行的特殊参数或手动排除!
:::
**一、**首先,使用 `ps``htop``netstat -nlp` 等命令确定框架的入口进程(也就是 Master 进程的 pid
@@ -52,6 +63,3 @@ Manager 进程下的子进程,连号部分为对应的 Worker 进程,比如
`htop` 使用方向键选择进程,选择到对应进程后可以使用 `F9` 来选择 kill 指令,比如让框架热重启,可以将光标移到 Master 进程上,使用 `SIGUSR1`
![image-20210708004921655](https://static.zhamao.me/images/docs/image-20210708004921655.png)

50
docs/guide/README.md Normal file
View File

@@ -0,0 +1,50 @@
# 介绍
::: tip 提示
编写文档需要较大精力,你也可以参与到本文档的建设中来,比如找错字,增加或更正内容,每页文档可直接点击右上方铅笔图标直接跳转至 GitHub 进行编辑,编辑后自动 Fork 并生成 Pull Request以此来贡献此文档
:::
炸毛框架使用 PHP 编写,采用 Swoole 扩展为基础,主要面向 API 服务聊天机器人OneBot 标准的机器人对接),包含 WebSocket、HTTP 等监听和请求库,用户代码采用模块化处理,使用注解可以方便地编写各类功能。
框架主要用途为 HTTP/WebSocket 服务器,机器人搭建框架。尤其对于聊天机器人消息处理较为方便和全面,提供了众多会话机制和内部调用机制,可以以各种方式设计你自己的模块。
此外QQ 机器人方面此框架基于 OneBot 标准的反向 WebSocket 连接,比传统 HTTP 通信更快。
```php
/**
* @CQCommand("你好")
*/
public function hello() {
ctx()->reply("你好,我是炸毛!");
}
/**
* @RequestMapping("/index")
*/
public function index() {
return "<h1>hello!</h1>";
}
```
## 开始前
首先,你需要了解你需要知道哪些事情才能开始着手使用框架:
1. Linux 命令行(会跑 Linux 程序)
2. php >=7.2 开发环境(项目会持续支持最新的 PHP 版本)
4. OneBot 机器人聊天接口标准
需要值得注意的是,本教程中所涉及的内容均为尽可能翻译为白话的方式进行描述,但对于框架的组件或事件等需要单独拆分说明文档的部分则需要足够详细,所以本教程提供一个快速上手的教程,并且会将最典型的安装方式写到快速教程篇。
## 框架特色
- 支持MySQL数据库连接池自带查询缓存提高多查询时的效率
- Websocket 服务器、HTTP 服务器兼容运行,一个框架多个用处
- 支持命令、自然语言处理等多种插件形式
- 支持多个机器人账号负载均衡
- 协程 + TaskWorker 进程重度任务处理机制,保证高效,单个请求响应时间为 0.1 ms 左右
- 模块分离和自由组合,可根据自身需求自己建立模块内的目录结构和代码结构
- 灵活的注释注解注册事件方式,弥补 PHP 语言缺少注解的遗憾

View File

@@ -2,9 +2,11 @@
到目前为止,炸毛框架的配置文件还没有任何变更,是默认的行为。在本章内容中,将列举出炸毛框架的配置文件的规则和使用。
!!! error "警告"
::: danger 警告
因为炸毛框架的全局配置中含有数据库名称和密码以及 access_token 等敏感字段,在使用版本控制软件过程中请不要将敏感信息写入配置文件并提交至开源仓库!
因为炸毛框架的全局配置中含有数据库名称和密码以及 access_token 等敏感字段,在使用版本控制软件过程中请不要将敏感信息写入配置文件并提交至开源仓库!
:::
## 全局配置文件 global.php
@@ -20,9 +22,10 @@
| `crash_dir` | 存放崩溃和运行日志的目录 | `zm_data` 下的 `crash/` |
| `config_dir` | 存放 saveToJson() 方法保存的数据的目录 | `zm_data` 下的 `config/` |
| `swoole` | 对应 Swoole server 中 set 的参数参考Swoole文档 | 见子表 `swoole` |
| `runtime` | 一些框架运行时调整的设置 | 见子表 `runtime` |
| `light_cache` | 轻量内置 key-value 缓存 | 见字表 `light_cache` |
| `worker_cache` | 跨进程变量级缓存 | 见子表 `worker_cache` |
| `sql_config` | MySQL 数据库连接信息 | 见子表 `sql_config` |
| `mysql_config` | MySQL 数据库连接信息 | 见子表 `mysql_config` |
| `redis_config` | Redis 连接信息 | 见子表 `redis_config` |
| `access_token` | OneBot 客户端连接约定的token留空则无相关设置见 [组件 - Access Token 验证](component/access-token) | 空 |
| `http_header` | HTTP 请求自定义返回的header | 见配置文件 |
@@ -34,6 +37,7 @@
| `server_event_handler_class` | 注册 Swoole Server 事件注解的类列表,在 Swoole 服务器启动前就被加载 | 空 |
| `onebot` | OneBot 协议相关配置 | 见子表 `onebot` |
| `remote_terminal` | 远程终端相关配置 | 见子表 `remote_terminal` |
| `module_loader` | 模块/插件 加载相关配置 | 见子表 `module_loader` |
### 子表 **swoole**
@@ -47,6 +51,17 @@
| `task_worker_num` | TaskWorker 工作进程数 | 默认不开启(此参数被注释) |
| `task_enable_coroutine` | TaskWorker 工作进程启用协程 | 默认不开启(此参数被注释)或 `bool` |
### 子表 runtime
| 配置名称 | 说明 | 默认值 |
| ----------------------------- | ------------------------------------------------------------ | --------------------------------------- |
| `swoole_coroutine_hook_flags` | Swoole 启动时一键协程化 Hook 的 Flag 值,详见 [一键协程化](http://wiki.swoole.com/#/runtime?id=%e5%87%bd%e6%95%b0%e5%8e%9f%e5%9e%8b) | `SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL)` |
| `swoole_server_mode` | Swoole Server 启动的进程模式,有 `SWOOLE_PROCESS``SWOOLE_BASE` 两种,见 [启动方式](http://wiki.swoole.com/#/learn?id=swoole_process) | `SWOOLE_PROCESS` |
| `middleware_error_policy` | 中间件错误处理策略,见 [中间件 - 错误处理策略](../../event/middleware/#_6) | 1 |
| `reload_delay_time` | 框架 reload 重载命令接收后延迟的时间毫秒0 为不等待) | 800 |
| `global_middleware_binding` | 给注解事件绑定全局中间件,见 [中间件 - 全局中间件](../../event/middleware/#_6) | `[]` |
| `save_console_log_file` | 当这里输入字符串路径时,所有 `Console::xxx()` 输出的日志都将保存到目标文件 | false |
### 子表 **light_cache**
| 配置名称 | 说明 | 默认值 |
@@ -63,17 +78,18 @@
| -------- | --------------------------- | ------ |
| `worker` | 跨进程缓存的存储工作进程 id | 0 |
### 子表 **sql_config**
### 子表 **mysql_config**
| 配置名称 | 说明 | 默认值 |
| ------------------------ | ------------------------------ | ------------------------------------------------------------ |
| `sql_host` | 数据库地址(留空则不使用数据库) | 空 |
| `sql_port` | 数据库端口 | 3306 |
| `sql_username` | 连接数据库的用户名 | |
| `sql_database` | 要连接的数据库名 | |
| `sql_password` | 数据库连接密码 | |
| `sql_options` | PDO 数据库的 options 参数 | `[PDO::ATTR_STRINGIFY_FETCHES => false,PDO::ATTR_EMULATE_PREPARES => false]` |
| `sql_default_fetch_mode` | PDO 的 fetch 模式 | `PDO::FETCH_ASSOC` |
| `host` | 数据库地址(留空则不使用数据库) | 空 |
| `port` | 数据库端口 | 3306 |
| `username` | 连接数据库的用户名 | |
| `dbname` | 要连接的数据库名 | |
| `password` | 数据库连接密码 | |
| `options` | PDO 数据库的 options 参数 | `[PDO::ATTR_STRINGIFY_FETCHES => false,PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]` |
| `pool_size` | MySQL 连接池大小 | 64 |
| `charset` | MySQL 字符集 | `utf8mb4` |
### 子表 **redis_config**
@@ -95,11 +111,13 @@
### 子表 onebot
| 配置名称 | 说明 | 默认值 |
| ----------------- | ------------------------------------------------------------ | ------ |
| `status` | 是否开启 OneBot 标准机器人解析功能 | true |
| `single_bot_mode` | 是否开启单机器人模式 | false |
| `message_level` | 机器人的 WebSocket 事件在 Swoole 原生事件 `@OnMessageEvent` 中的等级(越高说明越被优先处理) | 99999 |
| 配置名称 | 说明 | 默认值 |
| ------------------------ | ------------------------------------------------------------ | ------ |
| `status` | 是否开启 OneBot 标准机器人解析功能 | true |
| `single_bot_mode` | 是否开启单机器人模式 | false |
| `message_level` | 机器人的 WebSocket 事件在 Swoole 原生事件 `@OnMessageEvent` 中的等级(越高说明越被优先处理) | 99 |
| `message_convert_string` | 是否将数组格式的消息转换为字符串以保证与旧版本的兼容性 | true |
| `message_command_policy` | CQCommand命令匹配后执行流程`interrupt` 为不执行后续 CQMessage`continue` 为继续 | `interrupt` |
### 子表 remote_terminal
@@ -110,6 +128,13 @@
| `port` | 远程终端监听的 TCP 端口 | 20002 |
| `token` | 远程终端连接的令牌(如果为空("")则不验证) | "" |
### 子表 module_loader
| 配置名称 | 说明 | 默认值 |
| -------- | ------------------------------------------------------------ | ----------- |
| `enable_hotload` | 是否开启热加载模块包的功能 | false |
| `load_path` | 模块包加载的目录地址 | `zm_data` 下的 `modules` |
## 多环境下的配置文件
炸毛框架的配置文件模块支持不同环境下的配置文件,主要结构为 `global.{环境}.php`。在一般情况下,炸毛框架默认从教程引导方式根据指令 `vendor/bin/start server` 启动的框架是不带环境控制的。这章将讲述如何根据不同的环境development / staging / production来编写配置文件。

View File

@@ -1,3 +1,5 @@
# 错误码对照表
| 异常码 | 含义 | 解决方案 |
| ------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| E00001 | 炸毛框架未检测到 PHP 安装了 Swoole 扩展 | 根据文档安装扩展去! |
@@ -69,5 +71,11 @@
| E00067 | 模块解包合并 `composer.json` 时没有找到项目原文件 | 检查项目的工作目录下是否有 `composer.json` 文件存在。 |
| E00068 | 模块解包时无法正常拷贝文件 | 检查文件夹是否正常可以创建和写入。 |
| E00069 | 框架不能启动两个 ConsoleApplication 实例 | 不要重复使用 `new ConsoleApplication()`。 |
| E00070 | 框架找不到 `zm_data` 目录 | 检查配置中指定的 `zm_data` 目录是否存在。 |
| E00071 | 框架找不到对应类型的 API 调用类 | 检查 `getExtendedAPI($name)` 传入的 `$name` 是否正确 |
| E00072 | 上下文无法找到 | 检查上下文环境,如是否处于协程环境中 |
| E00073 | 在类中找不到方法 | 检查调用对象是否存在对应的方法method或检查是否插入了对应的macro宏方法 |
| E00074 | 参数非法 | 检查调用的参数是否正常(此处可能有多处问题,请看具体调用栈炸掉的地方) |
| E00075 | Cron表达式非法 | 检查 @Cron 中的表达式是否书写格式正确 |
| E00076 | Cron检查间隔非法 | 检查 @Cron 中的检查间隔毫秒是否在1000-60000之间 |
| E99999 | 未知错误 | |

View File

@@ -44,25 +44,26 @@ docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole vendor/bin/start
docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole vendor/bin/start server
```
启动后你会看到和下方类似的初始化内容,表明启动成功了
```verilog
$ vendor/bin/start server
host: 0.0.0.0 | port: 20001
log_level: 2 | version: 2.0.0
config: global.php | worker_num: 4
working_dir: /app/zhamao-framework
______
|__ / |__ __ _ _ __ ___ __ _ ___
/ /| '_ \ / _` | '_ ` _ \ / _` |/ _ \
/ /_| | | | (_| | | | | | | (_| | (_) |
/____|_| |_|\__,_|_| |_| |_|\__,_|\___/
=================================================================
working_dir: /app/zhamao-framework-starter
listen: 0.0.0.0:20001 | worker: 4 (auto)
environment: default | log_level: 2
version: 2.7.0 | master_pid: 28449
=================================================================
______
|__ / |__ __ _ _ __ ___ __ _ ___
/ /| '_ \ / _` | '_ ` _ \ / _` |/ _ \
/ /_| | | | (_| | | | | | | (_| | (_) |
/____|_| |_|\__,_|_| |_| |_|\__,_|\___/
[14:27:31] [S] [#3] Worker #3
[14:27:31] [S] [#0] Worker #0
[14:27:31] [S] [#2] Worker #2
[14:27:31] [S] [#1] Worker #1
[03-20 22:30:56] [S] [#1] Worker #1 started
[03-20 22:30:56] [S] [#2] Worker #2 started
[03-20 22:30:56] [S] [#3] Worker #3 started
[03-20 22:30:56] [S] [#0] Worker #0 started
```
单纯运行 炸毛框架 后,如果不部署或安装启动任何机器人客户端的话,仅仅相当于启动了一个 监听 20001 端口的WebSoket + HTTP 服务器。你可以通过浏览器访问http://127.0.0.1:20001 ,或者你部署到了服务器后需要输入服务器地址。

View File

@@ -2,7 +2,7 @@
## 什么是 OneBot
OneBot 是一个聊天机器人应用接口标准,详情戳[这里](https://github.com/howmanybots/onebot)。
OneBot 是一个聊天机器人应用接口标准,详情戳 [这里](https://github.com/howmanybots/onebot)。
## OneBot 实现选择
@@ -16,8 +16,10 @@ OneBot 是一个聊天机器人应用接口标准,详情戳[这里](https://gi
| [takayama-lily/onebot](https://github.com/takayama-lily/onebot) | [OICQ](https://github.com/takayama-lily/oicq) | takayama | |
| [ProtobufBot](https://github.com/ProtobufBot) | [Mirai](https://github.com/mamoe/mirai) | lz1998 | 事件和 API 数据内容和 OneBot 一致,通信方式不兼容 |
!!! warning "注意"
::: warning 注意
因为目前炸毛框架 2.0 只支持 WebSocket 方式的 OneBot 实现,所以目前上述项目的连接方式均只可选支持反向 WebSocket 通信的。后期会兼容 HTTP 和正向 WebSocket 通信方式。
因为目前炸毛框架 2.0 只支持 WebSocket 方式的 OneBot 实现,所以目前上述项目的连接方式均只可选支持反向 WebSocket 通信的。后期会兼容 HTTP 和正向 WebSocket 通信方式。
:::
如果你还没有自己的 QQ或者是其他原因导致的暂时无法使用上述 OneBot 实例,可以使用炸毛项目中的 OneBot 协议聊天模拟器。但目前还处在开发中,暂不可用。

View File

@@ -7,4 +7,3 @@ HTTP 服务器篇主要讲解如何通过炸毛框架来实现微服务、API
- [HTTP 服务器 - 存储 - Redis](/component/redis/)
- [HTTP 服务器 - 存储 - MySQL](/component/mysql/)
- [HTTP 客户端](/component/zmrequest/)

View File

@@ -1,7 +1,5 @@
# 快速上手 - 机器人篇
## 简介
看到这里,你已经完成了前面的环境部署,到了最关键的第一步了!
@@ -26,268 +24,107 @@ OneBot 机器人部分的选择详情见 [OneBot 实例](/guide/OneBot实例/)
2. 双击 exe 文件或者使用 `./go-cqhttp` 启动
3. 生成默认配置文件并修改默认配置
!!! warning "注意"
::: warning 注意
由于 go-cqhttp 项目还处于开发期,而且配置文件格式也发生了多次变化,但大体内容没有变(比如编写此文档时发布的版本中配置文件格式变成了 `hjson` 取代了原来的 `json`
由于 go-cqhttp 项目还处于开发期,而且配置文件格式也发生了多次变化,但大体内容没有变(比如编写此文档时发布的版本中配置文件格式变成了 `hjson` 取代了原来的 `json`
=== "config.yml最新格式"
:::
```yaml hl_lines="2 3 78 83"
account: # 账号相关
uin: 1233456 # QQ账号
password: '' # 密码为空时使用扫码登录
encrypt: false # 是否开启密码加密
status: 0 # 在线状态 请参考 https://github.com/Mrs4s/go-cqhttp/blob/dev/docs/config.md#在线状态
relogin: # 重连设置
delay: 3 # 首次重连延迟, 单位秒
interval: 3 # 重连间隔
max-times: 0 # 最大重连次数, 0为无限制
config.yml最新格式
# 是否使用服务器下发的新地址进行重连
# 注意, 此设置可能导致在海外服务器上连接情况更差
use-sso-address: true
```yaml {2,3,78,83}
account: # 账号相关
uin: 1233456 # QQ账号
password: '' # 密码为空时使用扫码登录
encrypt: false # 是否开启密码加密
status: 0 # 在线状态 请参考 https://github.com/Mrs4s/go-cqhttp/blob/dev/docs/config.md#在线状态
relogin: # 重连设置
delay: 3 # 首次重连延迟, 单位秒
interval: 3 # 重连间隔
max-times: 0 # 最大重连次数, 0为无限制
heartbeat:
# 心跳频率, 单位秒
# -1 为关闭心跳
interval: 5
# 是否使用服务器下发的新地址进行重连
# 注意, 此设置可能导致在海外服务器上连接情况更差
use-sso-address: true
message:
# 上报数据类型
# 可选: string,array
post-format: string
# 是否忽略无效的CQ码, 如果为假将原样发送
ignore-invalid-cqcode: false
# 是否强制分片发送消息
# 分片发送将会带来更快的速度
# 但是兼容性会有些问题
force-fragment: false
# 是否将url分片发送
fix-url: false
# 下载图片等请求网络代理
proxy-rewrite: ''
# 是否上报自身消息
report-self-message: false
# 移除服务端的Reply附带的At
remove-reply-at: false
# 为Reply附加更多信息
extra-reply-data: false
heartbeat:
# 心跳频率, 单位秒
# -1 为关闭心跳
interval: 5
output:
# 日志等级 trace,debug,info,warn,error
log-level: warn
# 是否启用 DEBUG
debug: false # 开启调试模式
message:
# 上报数据类型
# 可选: string,array
post-format: string
# 是否忽略无效的CQ码, 如果为假将原样发送
ignore-invalid-cqcode: false
# 是否强制分片发送消息
# 分片发送将会带来更快的速度
# 但是兼容性会有些问题
force-fragment: false
# 是否将url分片发送
fix-url: false
# 下载图片等请求网络代理
proxy-rewrite: ''
# 是否上报自身消息
report-self-message: false
# 移除服务端的Reply附带的At
remove-reply-at: false
# 为Reply附加更多信息
extra-reply-data: false
# 默认中间件锚点
default-middlewares: &default
# 访问密钥, 强烈推荐在公网的服务器设置
access-token: ''
# 事件过滤器文件目录
filter: ''
# API限速设置
# 该设置为全局生效
# 原 cqhttp 虽然启用了 rate_limit 后缀, 但是基本没插件适配
# 目前该限速设置为令牌桶算法, 请参考:
# https://baike.baidu.com/item/%E4%BB%A4%E7%89%8C%E6%A1%B6%E7%AE%97%E6%B3%95/6597000?fr=aladdin
rate-limit:
enabled: false # 是否启用限速
frequency: 1 # 令牌回复频率, 单位秒
bucket: 1 # 令牌桶大小
output:
# 日志等级 trace,debug,info,warn,error
log-level: warn
# 是否启用 DEBUG
debug: false # 开启调试模式
database: # 数据库相关设置
leveldb:
# 是否启用内置leveldb数据库
# 启用将会增加10-20MB的内存占用和一定的磁盘空间
# 关闭将无法使用 撤回 回复 get_msg 等上下文相关功能
enable: true
# 默认中间件锚点
default-middlewares: &default
# 访问密钥, 强烈推荐在公网的服务器设置
access-token: ''
# 事件过滤器文件目录
filter: ''
# API限速设置
# 该设置为全局生效
# 原 cqhttp 虽然启用了 rate_limit 后缀, 但是基本没插件适配
# 目前该限速设置为令牌桶算法, 请参考:
# https://baike.baidu.com/item/%E4%BB%A4%E7%89%8C%E6%A1%B6%E7%AE%97%E6%B3%95/6597000?fr=aladdin
rate-limit:
enabled: false # 是否启用限速
frequency: 1 # 令牌回复频率, 单位秒
bucket: 1 # 令牌桶大小
# 连接服务列表
servers:
# 添加方式,同一连接方式可添加多个,具体配置说明请查看文档
#- http: # http 通信
#- ws: # 正向 Websocket
#- ws-reverse: # 反向 Websocket
#- pprof: #性能分析服务器
# Zhamao Framework 所需要的服务器配置
- ws-reverse:
# 是否禁用当前反向WS服务
disabled: false
# 反向WS Universal 地址
# 注意 设置了此项地址后下面两项将会被忽略
universal: ws://127.0.0.1:20001/
# 反向WS API 地址
api: ws://your_websocket_api.server
# 反向WS Event 地址
event: ws://your_websocket_event.server
# 重连间隔 单位毫秒
reconnect-interval: 3000
middlewares:
<<: *default # 引用默认中间件
```
database: # 数据库相关设置
leveldb:
# 是否启用内置leveldb数据库
# 启用将会增加10-20MB的内存占用和一定的磁盘空间
# 关闭将无法使用 撤回 回复 get_msg 等上下文相关功能
enable: true
=== "config.hjsonv1.0.0-beta2或更早版本所用格式"
``` json hl_lines="3 5 81 84"
{
// QQ号
uin: 你的机器人QQ
// QQ密码
password: "你的QQ密码"
// 是否启用密码加密
encrypt_password: false
// 加密后的密码, 如未启用密码加密将为空, 请勿随意修改.
password_encrypted: ""
// 是否启用内置数据库
// 启用将会增加10-20MB的内存占用和一定的磁盘空间
// 关闭将无法使用 撤回 回复 get_msg 等上下文相关功能
enable_db: true
// 访问密钥, 强烈推荐在公网的服务器设置
access_token: ""
// 重连设置
relogin: {
// 是否启用自动重连
// 如不启用掉线后将不会自动重连
enabled: true
// 重连延迟, 单位秒
relogin_delay: 3
// 最大重连次数, 0为无限制
max_relogin_times: 0
}
// API限速设置
// 该设置为全局生效
// 原 cqhttp 虽然启用了 rate_limit 后缀, 但是基本没插件适配
// 目前该限速设置为令牌桶算法, 请参考:
//https://baike.baidu.com/item/%E4%BB%A4%E7%89%8C%E6%A1%B6%E7%AE%97%E6%B3%95/6597000?fr=aladdin
_rate_limit: {
// 是否启用限速
enabled: false
// 令牌回复频率, 单位秒
frequency: 1
// 令牌桶大小
bucket_size: 1
}
// 是否忽略无效的CQ码
// 如果为假将原样发送
ignore_invalid_cqcode: false
// 是否强制分片发送消息
// 分片发送将会带来更快的速度
// 但是兼容性会有些问题
force_fragmented: false
// 心跳频率, 单位秒
// -1 为关闭心跳
heartbeat_interval: 0
// HTTP设置
http_config: {
// 是否启用正向HTTP服务器
enabled: true
// 服务端监听地址
host: 0.0.0.0
// 服务端监听端口
port: 5700
// 反向HTTP超时时间, 单位秒
// 最小值为5小于5将会忽略本项设置
timeout: 0
// 反向HTTP POST地址列表
// 格式:
// {
// 地址: secret
// }
post_urls: {}
}
// 正向WS设置
ws_config: {
// 是否启用正向WS服务器
enabled: true
// 正向WS服务器监听地址
host: 0.0.0.0
// 正向WS服务器监听端口
port: 6700
}
// 反向WS设置
ws_reverse_servers: [
// 可以添加多个反向WS推送
{
// 是否启用该推送
enabled: true
// 反向WS Universal 地址
// 注意 设置了此项地址后下面两项将会被忽略
reverse_url: ws://127.0.0.1:20001/
// 反向WS API 地址
reverse_api_url: ""
// 反向WS Event 地址
reverse_event_url: ""
// 重连间隔 单位毫秒
reverse_reconnect_interval: 3000
}
]
// 上报数据类型
// 可选: string array
post_message_format: string
// 是否使用服务器下发的新地址进行重连
// 注意, 此设置可能导致在海外服务器上连接情况更差
use_sso_address: false
// 是否启用 DEBUG
debug: false
// 日志等级
// WebUi 设置
web_ui: {
// 是否启用 WebUi
enabled: true
// 监听地址
host: 127.0.0.1
// 监听端口
web_ui_port: 9999
// 是否接收来自web的输入
web_input: false
}
}
```
=== "config.json旧格式"
``` json hl_lines="2 3 30 31"
{
"uin": 你的QQ号,
"password": "你的密码",
"encrypt_password": false,
"password_encrypted": "",
"enable_db": true,
"access_token": "",
"relogin": {
"enabled": true,
"relogin_delay": 3,
"max_relogin_times": 0
},
"ignore_invalid_cqcode": false,
"force_fragmented": true,
"heartbeat_interval": 0,
"http_config": {
"enabled": false,
"host": "0.0.0.0",
"port": 5700,
"timeout": 0,
"post_urls": {}
},
"ws_config": {
"enabled": false,
"host": "0.0.0.0",
"port": 6700
},
"ws_reverse_servers": [
{
"enabled": true,
"reverse_url": "ws://127.0.0.1:20001/",
"reverse_api_url": "",
"reverse_event_url": "",
"reverse_reconnect_interval": 3000
}
],
"post_message_format": "string",
"debug": false,
"log_level": ""
}
```
# 连接服务列表
servers:
# 添加方式,同一连接方式可添加多个,具体配置说明请查看文档
#- http: # http 通信
#- ws: # 正向 Websocket
#- ws-reverse: # 反向 Websocket
#- pprof: #性能分析服务器
# Zhamao Framework 所需要的服务器配置
- ws-reverse:
# 是否禁用当前反向WS服务
disabled: false
# 反向WS Universal 地址
# 注意 设置了此项地址后下面两项将会被忽略
universal: ws://127.0.0.1:20001/
# 反向WS API 地址
api: ws://your_websocket_api.server
# 反向WS Event 地址
event: ws://your_websocket_event.server
# 重连间隔 单位毫秒
reconnect-interval: 3000
middlewares:
<<: *default # 引用默认中间件
```
其中 ws://127.0.0.1:20001/ 中的 127.0.0.1 和 20001 应分别对应炸毛框架配置的 HOST 和 PORT
@@ -320,19 +157,17 @@ public function repeat() {
这样,一个简易的复读机就做好了!回到 QQ 机器人聊天,向机器人发送 `echo 你好啊`,它会回复你 `你好啊`。
<chat-box>
) echo 你好啊
( 你好啊
) echo
( 请输入你要回复的内容
) 哦豁
( 哦豁
</chat-box>
<chat-box :my-chats="[
{type:0,content:'echo 你好啊'},
{type:1,content:'你好啊'},
{type:0,content:'echo'},
{type:1,content:'请输入你要回复的内容'},
{type:0,content:'哦豁'},
{type:1,content:'哦豁'},
]"></chat-box>
> 如果你只回复 `echo` 的话,它会先和你进入一个会话状态,并问你 `请输入你要回复的内容`,这时你再次说一些内容例如 `哦豁`,会回复你 `哦豁`。效果和直接输入 `echo 哦豁` 是一致的,这是炸毛框架内的一个封装好的命令参数对话询问功能。有关参数询问功能,请看后面的进阶模块。
## 使用机器人 API 和事件
如果你想不只是回复消息还要做其他复杂的动作Action使用 OneBot Action又名 OneBot API进行发送即可见 [机器人 API](/component/bot/robot-api)。

View File

@@ -39,8 +39,11 @@ class Weather {
}
```
!!! note "提示"
为了简单起见,我们在这里的例子中没有接入真实的天气数据,但要接入也非常简单,你可以使用中国天气网、和风天气等网站提供的 API本教程的进阶一栏后期会详细编写如何对接一个天气 API 接口。
::: tip 提示
为了简单起见,我们在这里的例子中没有接入真实的天气数据,但要接入也非常简单,你可以使用中国天气网、和风天气等网站提供的 API本教程的进阶一栏后期会详细编写如何对接一个天气 API 接口。
:::
在上方代码编写完毕后,运行框架(或运行过程中终端输入 `reload`),然后使用机器人客户端连接到炸毛框架,即可实现我们的第一个功能。我们在代码中编写了一个**注解事件**`@CQCommand`,此注解事件是用于接收用户的普通消息并切分成各类命令规则的一个注解事件绑定。代码中的注解事件省去了注解中的键名,也可以写作 `@CQCommand(match="天气")`
@@ -56,11 +59,11 @@ class Weather {
最后,函数直接返回了一个字符串,作为事件的响应,炸毛框架会自动处理并调用机器人客户端的接口,最后返回给用户消息。这里也可以使用上下文的 `ctx()->reply("xxx")` 方法替代,不返回字符串。注意两者只能选择一种方式,取决于开发者的开发习惯。
<chat-box>
) 天气 北京
( 北京 天气情况2020年12月22日-2~9℃ blablabla
) 天气
( 请告诉我你要查询的城市
) 北京
( 北京 天气情况2020年12月22日-2~9℃ blablabla
</chat-box>
<chat-box :my-chats="[
{type:0,content:'天气 北京'},
{type:1,content:'北京 天气情况2020年12月22日-2~9℃ blablabla'},
{type:0,content:'天气'},
{type:1,content:'请告诉我你要查询的城市'},
{type:0,content:'北京'},
{type:1,content:'北京 天气情况2020年12月22日-2~9℃ blablabla'},
]"></chat-box>

37
docs/guide/upgrade.md Normal file
View File

@@ -0,0 +1,37 @@
# 升级指南
因为框架在随着需求以及 Bug 在不断更新,所以在未来框架会发布新版本。为了方便从旧版本安装并使用框架的开发者无损更新到新版本,这里提供了升级版本上需要注意的事项。
## 版本约定
炸毛框架的版本号一般情况均按照 [Semantic Versioning 2.0.0](https://semver.org/) 标准进行滚动发行,规则简述如下:
假设版本号为 x.y.z
- `x` 为大版本,一般只有在发生完全无法兼容的更新时增加,需要开发者最重视。
- `y` 为小发行版本,默认情况下会新增组件功能,但会尽可能兼容旧版本,存在不兼容情况极少。
- `z` 为补丁版本,在不进行任何大功能更新情况下提供 Bug 的修复,完全兼容前版本。
例如,炸毛框架的 `2.4.2` 版本,在 `2.5.0` 发行后,框架提供了大量新组件,但是对旧版本的配置和组件完全兼容,无任何额外的说明,则可以直接升级。
## 升级方法
根据安装方法不同,升级的方法也不同。
框架安装方式有多种,但主要分为三类:
- Composer 加载库的方式
- 框架源码模式
- Phar 打包模式
在 Composer 加载库的方式下,一般是指使用命令 `composer require zhamao/framework``composer create-project zhamao/framework-starter` 的方式安装框架,框架的核心文件都在 `vendor` 目录下。
此方式安装的框架升级最方便,直接执行命令 `composer update` 即可。
框架源码模式安装一般为直接使用 `git clone` 框架本体的 GitHub 仓库或下载 master 分支安装,这种情况不可升级版本(或使用 `git pull` 拉取)。
Phar 打包模式更新则必须重新自行打包新版本,例如从 Composer 加载库方式打包的框架,则需在原目录使用 `composer update` 后再次打包一个新版本。
## 升级提示
如果在升级过程中遇到了提示,则可能是需要升级某些配置文件需要手动进行合并更新。如果提示了更新,建议到 `vendor/zhamao/framework/config/global.php` 框架的最新库内配置文件与 `config/global.php` 文件进行对比。

View File

@@ -6,7 +6,7 @@
框架默认使用脚手架构建好后,目录结构大致为下面这样:
```bash
```
zhamao-framework-starter/
├── config/ # 项目的配置文件文件夹,如 global.php
├── src/ # 项目的主要源码目录
@@ -28,20 +28,28 @@ namespace Module\<your-module-dir>;
class ModuleA {}
```
!!! fail "警告"
如果没有遵守上方的类和文件命名规则的话(文件名、文件夹名和命名空间的统一性),在加载框架时就会报错,无法找到对应的类。因为框架的注解解析依赖于 Composer 中 psr-4 规则的自动加载。
::: danger 警告
如果没有遵守上方的类和文件命名规则的话(文件名、文件夹名和命名空间的统一性),在加载框架时就会报错,无法找到对应的类。因为框架的注解解析依赖于 Composer 中 psr-4 规则的自动加载。
:::
## 创建模块
### 标准形式
我们这里以 `Entertain` 娱乐模块的创建为例,新建一个内有 `Dice.php` 掷骰子功能的模块,目录结构如下,在 `Module/` 下新建文件夹 `Entertain/`,再在此子目录下新建 `Dice.php` 文件。
```bash
```
zhamao-framework-starter/
└── src/
└── Module/
└── Entertain/
└── Dice.php
```
新建的 PHP 文件按照如下方式编写:
```php
<?php
namespace Module\Entertain;
@@ -55,7 +63,7 @@ class Dice {
如果你只开发很简单的一些功能,如一个 PHP 文件就可以实现的,可以少去创建模块文件夹的一步,直接将 `.php` 文件新建到 `Module/` 文件夹下,这时此文件的命名空间需要更正为 `namespace Module;` 即可,而文件夹结构也更加简单:
```bash
```
zhamao-framework-starter/
└── src/
└── Module/
@@ -64,5 +72,4 @@ zhamao-framework-starter/
### Composer 外部引入形式
(暂未支持,敬请期待)
(暂未支持,敬请期待TODO已经支持了但缺少文档编写

View File

@@ -1,135 +0,0 @@
# 介绍
> 本文档为炸毛框架 v2 版本,如需查看 v1 版本,[点我](https://docs-v1.zhamao.xin/)。
!!! tip "提示"
编写文档需要较大精力,你也可以参与到本文档的建设中来,比如找错字,增加或更正内容,每页文档可直接点击右上方铅笔图标直接跳转至 GitHub 进行编辑,编辑后自动 Fork 并生成 Pull Request以此来贡献此文档
炸毛框架使用 PHP 编写,采用 Swoole 扩展为基础,主要面向 API 服务聊天机器人OneBot 标准的机器人对接),包含 WebSocket、HTTP 等监听和请求库,用户代码采用模块化处理,使用注解可以方便地编写各类功能。
框架主要用途为 HTTP/WS 服务器,机器人搭建框架。尤其对于聊天机器人消息处理较为方便和全面,提供了众多会话机制和内部调用机制,可以以各种方式设计你自己的模块。
在 HTTP 和 WebSocket 服务器上PHP 的扩展 Swoole 提供了高性能的支持,使其效率可媲美 nginx 静态网页处理的效率。
此外QQ 机器人方面此框架基于 OneBot 标准的反向 WebSocket 连接,比传统 HTTP 通信更快。
```php
/**
* @CQCommand("你好")
*/
public function hello() {
ctx()->reply("你好,我是炸毛!");
}
/**
* @RequestMapping("/index")
*/
public function index() {
return "<h1>hello!</h1>";
}
```
## 开始前
首先,你需要了解你需要知道哪些事情才能开始着手使用框架:
1. Linux 命令行(会跑 Linux 程序)
2. php 7.2+ 开发环境(项目会持续支持最新的 PHP 版本)
4. OneBot 机器人聊天接口标准
需要值得注意的是,本教程中所涉及的内容均为尽可能翻译为白话的方式进行描述,但对于框架的组件或事件等需要单独拆分说明文档的部分则需要足够详细,所以本教程提供一个快速上手的教程,并且会将最典型的安装方式写到快速教程篇。
!!! bug "文档提示"
此文档采用 MkDocs 驱动,文档的搜索组件原生不支持中文搜索,且分词很难控制,所以搜索体验会大打折扣,敬请谅解!搜不到不是没这个东西,建议这种情况可以自行翻阅目录查看!
## 框架特色
- 支持MySQL数据库连接池自带查询缓存提高多查询时的效率
- Websocket 服务器、HTTP 服务器兼容运行,一个框架多个用处
- 支持命令、自然语言处理等多种插件形式
- 支持多个机器人账号负载均衡
- 协程 + TaskWorker 进程重度任务处理机制,保证高效,单个请求响应时间为 0.1 ms 左右
- 模块分离和自由组合,可根据自身需求自己建立模块内的目录结构和代码结构
- 灵活的注释注解注册事件方式,弥补 PHP 语言缺少注解的遗憾
## 文档主题
### 主题
<div class="tx-switch">
<button data-md-color-scheme="default"><code>默认模式</code></button>
<button data-md-color-scheme="slate"><code>暗黑模式</code></button>
</div>
<script>
var buttons = document.querySelectorAll("button[data-md-color-scheme]");
buttons.forEach(function(button) {
button.addEventListener("click", function() {
var attr = this.getAttribute("data-md-color-scheme");
setCookie("_theme", attr);
document.body.setAttribute("data-md-color-scheme", attr);
var name = document.querySelector("#__code_0 code span:nth-child(7)");
name.textContent = attr;
})
})
</script>
### 主色调
<div class="tx-switch">
<button data-md-color-primary="red"><code>red</code></button>
<button data-md-color-primary="pink"><code>pink</code></button>
<button data-md-color-primary="purple"><code>purple</code></button>
<button data-md-color-primary="deep-purple"><code>deep purple</code></button>
<button data-md-color-primary="indigo"><code>indigo</code></button>
<button data-md-color-primary="blue"><code>blue</code></button>
<button data-md-color-primary="light-blue"><code>light blue</code></button>
<button data-md-color-primary="cyan"><code>cyan</code></button>
<button data-md-color-primary="teal"><code>teal</code></button>
<button data-md-color-primary="green"><code>green</code></button>
<button data-md-color-primary="light-green"><code>light green</code></button>
<button data-md-color-primary="lime"><code>lime</code></button>
<button data-md-color-primary="yellow"><code>yellow</code></button>
<button data-md-color-primary="amber"><code>amber</code></button>
<button data-md-color-primary="orange"><code>orange</code></button>
<button data-md-color-primary="deep-orange"><code>deep orange</code></button>
<button data-md-color-primary="brown"><code>brown</code></button>
<button data-md-color-primary="grey"><code>grey</code></button>
<button data-md-color-primary="blue-grey"><code>blue grey</code></button>
<button data-md-color-primary="black"><code>black</code></button>
<button data-md-color-primary="white"><code>white</code></button>
</div>
### 辅色调
<div class="tx-switch"> <button data-md-color-accent="red"><code>red</code></button> <button data-md-color-accent="pink"><code>pink</code></button> <button data-md-color-accent="purple"><code>purple</code></button> <button data-md-color-accent="deep-purple"><code>deep purple</code></button> <button data-md-color-accent="indigo"><code>indigo</code></button> <button data-md-color-accent="blue"><code>blue</code></button> <button data-md-color-accent="light-blue"><code>light blue</code></button> <button data-md-color-accent="cyan"><code>cyan</code></button> <button data-md-color-accent="teal"><code>teal</code></button> <button data-md-color-accent="green"><code>green</code></button> <button data-md-color-accent="light-green"><code>light green</code></button> <button data-md-color-accent="lime"><code>lime</code></button> <button data-md-color-accent="yellow"><code>yellow</code></button> <button data-md-color-accent="amber"><code>amber</code></button> <button data-md-color-accent="orange"><code>orange</code></button> <button data-md-color-accent="deep-orange"><code>deep orange</code></button> </div>
<script>
var buttons = document.querySelectorAll("button[data-md-color-primary]")
buttons.forEach(function(button) {
button.addEventListener("click", function() {
var attr = this.getAttribute("data-md-color-primary")
setCookie("_primary_color", attr)
document.body.setAttribute("data-md-color-primary", attr)
var name = document.querySelector("#__code_2 code span:nth-child(7)")
name.textContent = attr.replace("-", " ")
})
})
</script>
<script>
var buttons2 = document.querySelectorAll("button[data-md-color-accent]")
buttons2.forEach(function(button) {
button.addEventListener("click", function() {
var attr = this.getAttribute("data-md-color-accent")
setCookie("_accent_color", attr)
document.body.setAttribute("data-md-color-accent", attr)
var name = document.querySelector("#__code_3 code span:nth-child(7)")
name.textContent = attr.replace("-", " ")
})
})
</script>

View File

@@ -1,97 +0,0 @@
hljs.initHighlighting()
var _hmt = _hmt || [];
(function () {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?f0f276cefa10aa31a20ae3815a50b795";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
function appendChatModule(id, chatDialogs) {
let insertDiv = document.getElementById(id);
let ss = '';
ss += '<div class="doc-chat-container">';
for(let i of chatDialogs) {
if (i.role === 0) {
ss += '<div class="doc-chat-row doc-chat-row-robot">\n' +
' <img class="doc-chat-avatar" src="https://docs-v1.zhamao.xin/logo.png" alt=""/>\n' +
' <div class="doc-chat-box doc-chat-box-robot">' + i.msg + '</div>\n' +
' </div>';
} else {
ss += '<div class="doc-chat-row">\n' +
' <div class="doc-chat-box">' + i.msg + '</div>\n' +
' <img class="doc-chat-avatar" src="http://api.btstu.cn/sjtx/api.php" alt=""/>\n' +
' </div>';
}
}
insertDiv.innerHTML = ss + '</div>';
}
function getCookie(name) {
var arr, reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)");
if (arr = document.cookie.match(reg))
return unescape(arr[2]);
else
return null;
}
function setCookie(name, value) {
var Days = 30;
var exp = new Date();
exp.setTime(exp.getTime() + Days * 24 * 60 * 60 * 1000);
document.cookie = name + "=" + escape(value) + ";expires=" + exp.toGMTString() + ";path=/";
}
s_theme=getCookie("_theme");
if(s_theme !== undefined) {
document.body.setAttribute("data-md-color-scheme", s_theme)
var name = document.querySelector("#__code_0 code span:nth-child(7)")
name.textContent = s_theme
}
s_primary=getCookie("_primary_color");
if(s_primary !== null) {
document.body.setAttribute("data-md-color-primary", s_primary);
var name2 = document.querySelector("#__code_2 code span:nth-child(7)");
if (s_primary !== null && name2 !== null) name2.textContent = s_primary.replace("-", " ");
}
s_accent=getCookie("_accent_color");
if(s_accent !== null) {
document.body.setAttribute("data-md-color-accent", s_accent);
var name3 = document.querySelector("#__code_3 code span:nth-child(7)");
if (s_accent !== null && name3 !== null) name3.textContent = s_accent.replace("-", " ");
}
setTimeout(() => {
let ls = document.querySelectorAll("chat-box");
for(let i of ls) {
let final = '<div class="doc-chat-container">';
let dialogs = i.innerHTML.split("\n");
for(let j of dialogs) {
if(j === '') continue;
if(j.substr(0, 2) === ') ') {
final += '<div class="doc-chat-row">\n' +
' <div class="doc-chat-box">' + j.substr(2).replaceAll("\\n", "<br>") + '</div>\n' +
' <img class="doc-chat-avatar" src="http://api.btstu.cn/sjtx/api.php" alt=""/>\n' +
' </div>';
} else if (j.substr(0, 2) === '( ') {
final += '<div class="doc-chat-row doc-chat-row-robot">\n' +
' <img class="doc-chat-avatar" src="https://docs-v1.zhamao.xin/logo.png" alt=""/>\n' +
' <div class="doc-chat-box doc-chat-box-robot">' + j.substr(2).replaceAll("\\n", "<br>") + '</div>\n' +
' </div>';
} else if (j.substr(0, 2) === '^ ') {
final += '<div class="doc-chat-row doc-chat-banner">' + j.substr(2) + '</div>';
} else if (j.substr(0, 2) === '[ ') {
final += '<div class="doc-chat-row doc-chat-row-robot">\n' +
' <img class="doc-chat-avatar" src="https://docs-v1.zhamao.xin/logo.png" alt=""/>\n' +
' <div class="doc-chat-box doc-chat-box-robot"><img src="' + j.substr(2) + '" alt=""/></div>\n' +
' </div>';
}
}
i.innerHTML = final;
}
}, 500);

221
docs/update/build-update.md Normal file
View File

@@ -0,0 +1,221 @@
# 更新日志master 分支 commit
此文档将显示非发布版的提交版本相关更新文档,可能与发布版的更新日志有重合,在此仅作更新记录。
同时此处将只使用 build 版本号进行区分。
## build 454 (2022-3-27)
- 修复部分命令下无法杀掉进程的 Bug
- 新增 `@Cron` 注解
- 修复全局函数 `match_pattern` 无法正常工作的 Bug
## build 453 (2022-3-25)
- 新增 property 注解用于 IDE 识别
## build 452 (2022-3-25)
- 修复 OnSetup 注解无法使用 Attribute 解析的 Bug
- 修复 HelpGenerator 的 Alias 不工作的 Bug
## build 451 (2022-3-21)
- 重构全局函数,统一函数命名,并补全注释
## build 450 (2022-3-21)
- 新增命令帮助生成器
## build 449 (2022-3-21)
- 新增 Composer 模块加载和分发模式
## build 448 (2022-3-20)
- 加快 build 命令的执行速度,取消进度条和提升性能
## build 447 (2022-3-20)
- 发布 2.7.0 正式版
## build 446 (2022-3-20)
- 新增 `./zhamao server` 下的 `--no-state-check` 参数,关闭“启动框架前的运行状态检查”功能
## build 445 (2022-3-20)
- 新增配置项 `runtime`.`annotation_reader_ignore`:支持注解解析器忽略注解的自定义
## build 444 (2022-3-20)
- 更改 `extra`.`exclude_annotate``zm`.`exclude-annotation-path`
## build 443 (2022-3-20)
- 修复注释空格的样式
## build 442 (2022-3-20)
- 修复打包模块后 `files` 的 autoload 项不能被解压和引入的 Bug
## build 441 (2022-3-20)
- 修复打包模块时命名空间与实际不一致的 Bug
## build 440 (2022-3-20)
- 新增方法宏Macroable
## build 439 (2022-3-19)
- 新增 PHP 8 Attribute 与注解同时支持的特性
## build 438 (2022-3-18)
- 修复 Response 类在 PHP 8.1 环境下的报错
## build 437 (2022-3-17)
- 修复 `ctx()` 可能会返回 null 的 Bug
## build 436 (2022-3-15)
- 新增 PHPStan 和 PHP CS Fixer 并优化全局代码
## build 435 (2022-3-13)
- 优化分离 WorkerManager 与 ProcessManager 的职责
- 新增 Ctrl+C 一次无法停止框架时多次 Ctrl+C 后可强行杀掉所有进程的功能
- `./zhamao server:stop` 新增参数 `--force`,使用 `SIGKILL` 强行杀掉所有进程
- 新增 AnnotationParser 对 `autoload-dev` 项中的 `psr-4` 默认检索条件
- 新增框架启动状态检测功能,如果已经启动了同样目录的框架,则会报错
- 新增“强制启用轮询模式启动热更新”功能(参数 `--polling-watch`
- 修复与 PHP 8.1 的兼容性
- 对 DaemonCommand 进行优化,与 ServerCommand 效果相同
- 修复 `autoload`.`psr-4` 不存在时报错的 Bug
- 新增框架停止时 Worker 退出回显状态码
- 新增 inotify 判断模式,如果使用 `--watch` 检测到没有安装 inotify则自动使用轮询模式
## build 434 (2022-1-8)
- 修复框架在 PHP 8.1 下运行时的一些问题
- 新增 Console 日志输出到文件的功能
## build 433 (2021-12-28)
- 修复 OneBotV11 因 IDE 自动优化导致 API 接口发生变化的问题
## build 432 (2021-12-25)
- 新增 GoCqhttpAPI 包,用于支持额外的 OneBot ActionAPI
- 修复 MySQL 查询器中 `fetchOne()` 方法无法正确返回值的 Bug
- 修复 Swoole Hook 因配置不当无法正确使用的 Bug
## build 431 (2021-12-22)
- 修复 Issue #50
- 新增 PhpStorm IDE 直接运行框架的脚本
## build 430 (2021-12-8)
- 删除调试信息
- 修复 #56 中关于数据库组件的 Bug
## build 429 (2021-12-7)
- 新增配置项 `onebot`.`message_command_policy`
- 新增 CQCommand 阻断策略的自定义配置功能
- 修复 CQAfter 无法正常使用的 bug #53
## build 428 (2021-11-16)
- 修复 `ctx()->waitMessage()` 在 array 消息模式下无法正确返回消息字符串的问题
- 新增 `ctx()->getArrayMessage()``ctx()->getStringMessage()` 两个方法
- 修复注解事件 `CQCommand``CQMessage` 在 array 消息模式下无法正确解析的 Bug
## build 427 (2021-11-16)
- 新增全局中间件,可在全局配置文件中设置
- 修复部分 Typo
- 新增指令 `server:status``server:reload``server:stop` 可用在新开终端中查看框架运行状态、重启和退出
- 新增支持 `array` 格式的消息
- 上下文 Context 对象新增 `getOriginMessage()` 用于获取原消息,`getMessage()` 如果在设置了转换后,将默认转换消息为字符串格式保持与旧模块兼容
- OneBot API 新增全局过滤器,可用作 Action 过滤重写等操作
- 配置文件新增 `runtime.reload_delay_time`,用于可配置重载 Worker 等待的时间(毫秒)
- 配置文件新增 `runtime.global_middleware_binding`,用于配置全局中间件
- 配置文件新增 `onebot.message_convert_string`,用于配置是否转换数组格式为字符串,保证与前版本的兼容性(默认为 true
- MessageUtil 消息工具类新增方法:`strToArray($msg, bool $ignore_space = true, bool $trim_text = false)`
- MessageUtil 消息工具类新增方法:`arrayToStr(array $array)`
- 新增框架启动多次监测功能,无法使用同一个框架项目同时启动两个框架
## build 426 (2021-11-10)
- 修复 CQ 码的解析函数 Bug#52
## build 425 (2021-11-3)
- 删除未实际应用功能的配置参数
- 修复 reload 时会断开 WebSocket 连接且导致进程崩溃的 Bug
## build 424 (2021-11-2)
- 新增 InstantModule 类、ZMServer 类、ModuleBase 类
- 配置文件新增 `runtime.reload_kill_connect``runtime.global_middleware_binding` 选项
- 修复部分情况下闭包事件分发时崩溃的 bug
- 新增内部方法 `_zm_env_check`
- 调整默认的 OneBot 模块对应的等级从 99999 调整为 99
- 新增导出框架运行参数的列表功能
## build 423 (2021-10-17)
- 修复 PHP 7.2 ~ 7.3 下无法使用新版 MySQL 组件的 bug
## build 422 (2021-10-6)
- 修复 `script_` 前缀无法被排除加载模块的 bug
- 修复 MySQL 组件的依赖问题
## build 421 (2021-9-11)
- 删除多余的调试信息
## build 420 (2021-9-11)
- 修复 OneBot 事件无法响应的 bug
- 新增部分 EventDispatcher 触发的事件 debug 日志
## build 419 (2021-9-11)
- 修复 DB 模块在未连接数据库的时候抛出未知异常
- 修复部分情况下打包模块出现的错误
## build 418 (2021-9-10)
- 修复 ZMAtomic 在 test 环境下的 bug
- 修复 MessageUtil 的报错
## build 417 (2021-8-29)
- 新增 AnnotationException统一框架内部的抛出异常的类型
- 新增 AnnotationParser 下的 `verifyMiddlewares()` 方法
- 私有化 CQAPI 类下的内部方法
- 将 WebSocket API 响应超时时间从 60 秒缩短为 30 秒
- 修复 DB 类不能使用旧查询器的 bug
- 统一 DB 类下抛出 Exception 的类型为 ZMException 的子类
- EventDispatcher 新增对 `middleware_error_policy` 的处理段
- 配置文件下 `runtime` 新增 `middleware_error_policy` 字段
- 将 LightCache 组件抛出的异常改为 LightCacheException
- ModuleManager 修复改配置的 `load_path` 不生效的 bug
- 修复打包时生成的 Phar Autoload 列表出错的 bug
- 将配置的 override 改为 overwrite
- 新增解包时忽略依赖的选项(`--ignore-depends`
- 删除众多调试日志,修改部分调试日志为 debug 级别的输出
- 修改 `ZM\MySQL\MySQLManager` 下的 `getConnection()``getWrapper()`
- MySQLPool 对象新增 `getCount()` 方法
- 新增 MySQLQueryBuilder 类(`doctrine/dbal` 的 wrapper 类)
- 修复 MySQLStatement 封装原 dbal 组件时与连接池不兼容的 bug
- 新增 MySQLStatementWrapper 类
- 完善 MySQLWrapper 类,用作主要的查询对象控制类
- 编写外部插件加载方式Phar 热加载功能)
- 修复 `ZMUtil::getClassesPsr4()` 方法在遇到空扩展名文件时的报错

View File

@@ -2,6 +2,23 @@
这里将会记录各个主版本的框架升级后,涉及 `global.php` 的更新日志,你可以根据这里描述的内容与你的旧配置文件进行合并。
## v2.7.0 (build 447)
- 新增 `$config['runtime']` 下的 `annotation_reader_ignore` 项。
## v2.6.6 (build 434)
- 新增 `$config['runtime']` 下的 `save_console_log_file` 项。
## v2.6.0 (build 427)
- 新增 `$config['runtime']` 下的 `reload_delay_time``global_middleware_binding` 项。
- 新增 `$config['onebot']` 下的 `message_convert_string` 项。
## v2.5.1 (build 417)
- 新增 `$config['runtime']` 下的 `middleware_error_policy` 选项。
## v2.5.0 (build 413)
- 新增 `$config['runtime']` 运行时设置。

View File

@@ -1,8 +1,203 @@
# 更新日志v2 版本)
## v2.5.0(未发布,测试中
## v2.7.4build 454
> 更新时间:未知
> 更新时间:2022.3.27
- 修复部分命令下无法杀掉进程的 Bug
- 新增 `@Cron` 注解
- 修复全局函数 `match_pattern` 无法正常工作的 Bug
## v2.7.3build 453
> 更新时间2022.3.25
- 新增命令帮助生成器
- 重构全局函数,统一函数命名,并补全注释
- 修复 OnSetup 注解无法使用 Attribute 解析的 Bug
- 修复 HelpGenerator 的 Alias 不工作的 Bug
- 新增 property 注解用于 IDE 识别
## v2.7.2build 449
> 更新时间2022.3.21
- 新增 Composer 模块加载和分发模式
## v2.7.1build 448
> 更新时间2022.3.20
- 加快 build 命令的执行速度,取消进度条和提升性能
## v2.7.0build 447
> 更新时间2022.3.20
- 优化分离 WorkerManager 与 ProcessManager 的职责
- 新增 Ctrl+C 一次无法停止框架时多次 Ctrl+C 后可强行杀掉所有进程的功能
- `./zhamao server:stop` 新增参数 `--force`,使用 `SIGKILL` 强行杀掉所有进程
- 新增 AnnotationParser 对 `autoload-dev` 项中的 `psr-4` 默认检索条件
- 新增框架启动状态检测功能,如果已经启动了同样目录的框架,则会报错
- 新增“强制启用轮询模式启动热更新”功能(参数 `--polling-watch`
- 修复与 PHP 8.1 的兼容性
- 对 DaemonCommand 进行优化,与 ServerCommand 效果相同
- 修复 `autoload`.`psr-4` 不存在时报错的 Bug
- 新增框架停止时 Worker 退出回显状态码
- 新增 inotify 判断模式,如果使用 `--watch` 检测到没有安装 inotify则自动使用轮询模式
- 新增 PHPStan 和 PHP CS Fixer 并优化全局代码
- 修复 `ctx()` 可能会返回 null 的 Bug
- 修复 Response 类在 PHP 8.1 环境下的报错
- 新增 PHP 8 Attribute 与注解同时支持的特性
- 新增方法宏Macroable
- 修复打包模块时命名空间与实际不一致的 Bug
- 修复打包模块后 `files` 的 autoload 项不能被解压和引入的 Bug
- 修复注释空格的样式
- 更改 `extra`.`exclude_annotate``zm`.`exclude-annotation-path`
- 新增配置项 `runtime`.`annotation_reader_ignore`:支持注解解析器忽略注解的自定义
- 新增 `./zhamao server` 下的 `--no-state-check` 参数,关闭“启动框架前的运行状态检查”功能
## v2.6.6build 434
> 更新时间2022.1.8
- 修复框架在 PHP 8.1 下运行时的一些问题
- 新增 Console 日志输出到文件的功能
## v2.6.5build 433
> 更新时间2021.12.28
- 修复 OneBotV11 因 IDE 自动优化导致 API 接口发生变化的问题
## v2.6.4build 432
> 更新时间2021.12.25
- 新增 GoCqhttpAPI 包,用于支持额外的 OneBot ActionAPI
- 修复 MySQL 查询器中 `fetchOne()` 方法无法正确返回值的 Bug
- 修复 Swoole Hook 因配置不当无法正确使用的 Bug
- 修复 Issue #50
- 新增 PhpStorm IDE 直接运行框架的脚本
## v2.6.3 (build 430)
> 更新时间2021.12.8
- 删除调试信息
- 修复 #56 中关于数据库组件的 Bug
## v2.6.2 (build 429)
> 更新时间2021.12.7
- 新增配置项 `onebot`.`message_command_policy`
- 新增 CQCommand 阻断策略的自定义配置功能
- 修复 CQAfter 无法正常使用的 bug #53
## v2.6.1 (build 428)
> 更新时间2021.11.16
- 修复 ctx()->waitMessage() 在 array 消息模式下无法正确返回消息字符串的问题
- 新增 ctx()->getArrayMessage() 和 ctx()->getStringMessage() 两个方法
- 修复注解事件 CQCommand 和 CQMessage 在 array 消息模式下无法正确解析的 Bug
## v2.6.0 (build 427)
> 更新时间2021.11.16
- 新增全局中间件,可在全局配置文件中设置
- 修复部分 Typo
- 新增指令 `server:status``server:reload``server:stop` 可用在新开终端中查看框架运行状态、重启和退出
- 新增支持 `array` 格式的消息
- 上下文 Context 对象新增 `getOriginMessage()` 用于获取原消息,`getMessage()` 如果在设置了转换后,将默认转换消息为字符串格式保持与旧模块兼容
- OneBot API 新增全局过滤器,可用作 Action 过滤重写等操作
- 配置文件新增 `runtime.reload_delay_time`,用于可配置重载 Worker 等待的时间(毫秒)
- 配置文件新增 `runtime.global_middleware_binding`,用于配置全局中间件
- 配置文件新增 `onebot.message_convert_string`,用于配置是否转换数组格式为字符串,保证与前版本的兼容性(默认为 true
- MessageUtil 消息工具类新增方法:`strToArray($msg, bool $ignore_space = true, bool $trim_text = false)`
- MessageUtil 消息工具类新增方法:`arrayToStr(array $array)`
- 新增框架启动多次监测功能,无法使用同一个框架项目同时启动两个框架
## v2.5.8 (build 426)
> 更新时间2021.11.10
- 修复 CQ 码的解析函数 Bug#52
## v2.5.7 (build 425)
> 更新时间2021.11.3
- 调低 OneBot 相关事件在 Swoole 的优先级
- 修复部分情况下闭包事件函数分发时引发的崩溃 bug
- 修复 reload 时会断开 WebSocket 连接且导致进程崩溃的 bug
## v2.5.6 (build 423)
> 更新时间2021.10.17
- 修复 PHP 7.2 ~ 7.3 下无法使用新版 MySQL 组件的 bug
## v2.5.5 (build 422)
> 更新时间2021.10.6
- 修复 `script_` 前缀无法被排除加载模块的 bug
- 修复 MySQL 组件的依赖问题
## v2.5.4 (buidl 421)
> 更新时间2021.9.11
- 删除多余的调试信息
## v2.5.3 (build 420)
> 更新时间2021.9.11
- 修复 DB 模块在未连接数据库的时候抛出未知异常
- 修复部分情况下打包模块出现的错误
- 修复 OneBot 事件无法响应的 bug
- 新增部分 EventDispatcher 触发的事件 debug 日志
## v2.5.2 (build 418)
> 更新时间2021.9.10
- 新增 AnnotationException统一框架内部的抛出异常的类型
- 新增 AnnotationParser 下的 `verifyMiddlewares()` 方法
- 私有化 CQAPI 类下的内部方法
- 将 WebSocket API 响应超时时间从 60 秒缩短为 30 秒
- 修复 DB 类不能使用旧查询器的 bug
- 统一 DB 类下抛出 Exception 的类型为 ZMException 的子类
- EventDispatcher 新增对 `middleware_error_policy` 的处理段
- 配置文件下 `runtime` 新增 `middleware_error_policy` 字段
- 将 LightCache 组件抛出的异常改为 LightCacheException
- ModuleManager 修复改配置的 `load_path` 不生效的 bug
- 修复打包时生成的 Phar Autoload 列表出错的 bug
- 将配置的 override 改为 overwrite
- 新增解包时忽略依赖的选项(`--ignore-depends`
- 删除众多调试日志,修改部分调试日志为 debug 级别的输出
- 修改 `ZM\MySQL\MySQLManager` 下的 `getConnection()``getWrapper()`
- MySQLPool 对象新增 `getCount()` 方法
- 新增 MySQLQueryBuilder 类(`doctrine/dbal` 的 wrapper 类)
- 修复 MySQLStatement 封装原 dbal 组件时与连接池不兼容的 bug
- 新增 MySQLStatementWrapper 类
- 完善 MySQLWrapper 类,用作主要的查询对象控制类
- 编写外部插件加载方式Phar 热加载功能)
- 修复 `ZMUtil::getClassesPsr4()` 方法在遇到空扩展名文件时的报错
## v2.5.1 (build 416)
> 更新时间2021.7.9
- 修复:脚手架无法正常使用 `init` 命令的 bug。
## v2.5.0build 415
> 更新时间2021.7.9
以下是版本**新增内容**

0
ext/go-cqhttp/go-cqhttp-down.sh Executable file → Normal file
View File

View File

@@ -33,9 +33,20 @@ function test_composer_and_php() {
return 0
}
if [ "$(uname -s)" != "Linux" ]; then
echo "Only support Linux!!!"
exit 1
fi
ZM_PHP_VERSION="7.4"
if [ "$ZM_DOWN_PHP_VERSION" != "" ]; then
ZM_PHP_VERSION="$ZM_DOWN_PHP_VERSION"
echo "Using custom PHP version: $ZM_PHP_VERSION"
fi
mkdir "$(pwd)/runtime" >/dev/null 2>&1
if [ ! -f "$(pwd)/runtime/php" ]; then
download_file "https://dl.zhamao.me/php-bin/down.php?php_ver=7.4&arch=$(uname -m)" "$(pwd)/runtime/php.tar.gz"
download_file "https://dl.zhamao.me/php-bin/down.php?php_ver=$ZM_PHP_VERSION&arch=$(uname -m)" "$(pwd)/runtime/php.tar.gz"
if [ $? -ne 0 ]; then
exit 1
fi

26
instant-demo.php Normal file
View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
require_once 'vendor/autoload.php';
use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\Swoole\OnOpenEvent;
use ZM\ConnectionManager\ConnectionObject;
use ZM\Console\Console;
use ZM\Module\InstantModule;
use ZM\ZMServer;
$weather = new InstantModule('weather');
$weather->onEvent(OnOpenEvent::class, ['connect_type' => 'qq'], function (ConnectionObject $conn) {
Console::info('机器人 ' . $conn->getOption('connect_id') . ' 已连接!');
});
$weather->onEvent(CQCommand::class, ['match' => '你好'], function () {
ctx()->reply('hello呀');
});
$app = new ZMServer('app-name');
$app->addModule($weather);
$app->run();

View File

@@ -14,12 +14,13 @@ theme:
accent: indigo
features:
- navigation.tabs
- navigation.sections
extra_javascript:
- https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js
- javascripts/library/highlight.min.js
- javascripts/config.js
extra_css:
- assets/css/extra.css
- https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/default.min.css
- assets/css/library/default.min.css
plugins:
- search:
lang: ja
@@ -38,7 +39,7 @@ extra:
version:
provider: mike
copyright: 'Copyright &copy; 2019 - 2021 CrazyBot Team&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="tx-switch">
copyright: 'Copyright &copy; 2019 - 2022 CrazyBot Team&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="tx-switch">
<button data-md-color-scheme="default"><code>默认模式</code></button>
<button data-md-color-scheme="slate"><code>暗黑模式</code></button>
</span>
@@ -66,6 +67,7 @@ nav:
- 基本配置: guide/basic-config.md
- 编写模块: guide/write-module.md
- 注册事件响应: guide/register-event.md
- 升级指南: guide/upgrade.md
- 错误码对照表: guide/errcode.md
- 事件和注解:
- 事件和注解: event/index.md
@@ -77,46 +79,57 @@ nav:
- 事件分发器: event/event-dispatcher.md
- 框架组件:
- 框架组件: component/index.md
- 上下文: component/context.md
- 聊天机器人组件:
- 机器人 API: component/bot/robot-api.md
- 机器人动作V12: component/bot/robot-api-12.md
- 机器人动作V11: component/bot/robot-api.md
- CQ 码(多媒体消息): component/bot/cqcode.md
- 机器人消息处理: component/bot/message-util.md
- Token 验证: component/bot/access-token.md
- 图灵机器人 API: component/bot/turing-api.md
- 存储:
- LightCache 轻量缓存: component/store/light-cache.md
- MySQL 数据库: component/store/mysql.md
- MySQL 查询器:
- 简介: component/store/mysql/mysql.md
- 配置: component/store/mysql/config.md
- 执行 SQL 语句: component/store/mysql/common-query.md
- MySQL 查询器(废弃): component/store/mysql-db.md
- Redis 数据库: component/store/redis.md
- ZMAtomic 原子计数器: component/store/atomics.md
- SpinLock 自旋锁: component/store/spin-lock.md
- 文件管理: component/store/data-provider.md
- 通用组件:
- 上下文: component/common/context.md
- 协程池: component/common/coroutine-pool.md
- 单例类: component/common/singleton-trait.md
- ZMUtil 杂项: component/common/zmutil.md
- 全局方法: component/common/global-functions.md
- Console 终端: component/common/console.md
- TaskWorker 管理: component/common/task-worker.md
- Terminal 终端: component/common/remote-terminal.md
- EventTracer 事件追踪器: component/common/event-tracer.md
- HTTP 服务器工具类:
- HTTP 和 WebSocket 客户端: component/zmrequest.md
- HTTP 路由管理: component/route-manager.md
- HTTP 和 WebSocket 客户端: component/http/zmrequest.md
- HTTP 路由管理: component/http/route-manager.md
- 模块/插件管理:
- 模块打包: component/module/module-pack.md
- 协程池: component/coroutine-pool.md
- 单例类: component/singleton-trait.md
- ZMUtil 杂项: component/zmutil.md
- 全局方法: component/global-functions.md
- Console 终端: component/console.md
- TaskWorker 管理: component/task-worker.md
- Terminal 终端: component/remote-terminal.md
- 模块解包: component/module/module-unpack.md
- 进阶开发:
- 进阶开发: advanced/index.md
- 框架剖析: advanced/framework-structure.md
- 框架启动模式: advanced/custom-start.md
- 手动安装环境: advanced/manually-install.md
- 从 v1 升级: advanced/to-v2.md
- 内部类文件手册: advanced/inside-class.md
- 接入 WebSocket 客户端: advanced/connect-ws-client.md
- 框架多进程: advanced/multi-process.md
- TaskWorker 提高并发: advanced/task-worker.md
- 框架高级开发:
- 框架剖析: advanced/framework-structure.md
- 框架启动模式: advanced/custom-start.md
- 手动安装环境: advanced/manually-install.md
- 内部类文件手册: advanced/inside-class.md
- 框架多进程: advanced/multi-process.md
- TaskWorker 提高并发: advanced/task-worker.md
- 开发实战教程:
- 接入 WebSocket 客户端: advanced/connect-ws-client.md
- 编写管理员才能触发的功能: advanced/example/admin.md
- 接入青云客聊天API: advanced/example/integrate-qingyunke-chatbot.md
- 开发天气机器人: advanced/example/weather-bot.md
- FAQ:
- FAQ: faq/FAQ.md
- 从 v1 升级: faq/to-v2.md
- 框架常见问题(持续更新): faq/usual-question.md
- 启动时报错 Address already in use: faq/address-already-in-use.md
- 出现 deadlock 字样: faq/display-deadlock.md
@@ -125,5 +138,6 @@ nav:
- 更新日志:
- 更新日志v2: update/v2.md
- 更新日志v1: update/v1.md
- 更新日志build update: update/build-update.md
- 配置文件更新日志: update/config.md
- <u>炸毛框架 v1</u>: https://docs-v1.zhamao.xin/

10
package.json Normal file
View File

@@ -0,0 +1,10 @@
{
"dependencies": {
"vuepress-theme-antdocs": "^1.3.5",
"vuepress": "^1.9.7"
},
"scripts": {
"docs:dev": "vuepress dev docs",
"docs:build": "vuepress build docs"
}
}

7
phpstan.neon Normal file
View File

@@ -0,0 +1,7 @@
parameters:
reportUnmatchedIgnoredErrors: false
level: 0
paths:
- ./src/
ignoreErrors:
- '#Used constant OS_TYPE_(LINUX|WINDOWS) not found#'

View File

@@ -1,8 +1,11 @@
<?php
declare(strict_types=1);
namespace Custom\Annotation;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\CustomAnnotation;
@@ -10,11 +13,17 @@ use ZM\Annotation\Interfaces\CustomAnnotation;
/**
* Class CustomAnnotation
* @Annotation
* @NamedArgumentConstructor()
* @Target("ALL")
* @package Custom\Annotation
*/
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_ALL)]
class Example extends AnnotationBase implements CustomAnnotation
{
/** @var string */
public $str = '';
public function __construct($str = '')
{
$this->str = $str;
}
}

View File

@@ -1,11 +1,10 @@
<?php /** @noinspection PhpFullyQualifiedNameUsageInspection */ #plain
<?php
//这里写你的全局函数
/**
* @param callable $func
* @param string $name
* @noinspection PhpUnused
*/
function pgo(callable $func, $name = "default") {
\ZM\Utils\CoroutinePool::go($func, $name);
declare(strict_types=1);
use ZM\Utils\CoroutinePool;
function pgo(callable $func, string $name = 'default')
{
CoroutinePool::go($func, $name);
}

View File

@@ -1,19 +1,22 @@
<?php /** @noinspection PhpMissingReturnTypeInspection */
<?php
declare(strict_types=1);
namespace Module\Example;
use ZM\Annotation\CQ\CQBefore;
use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\CQ\CQMessage;
use ZM\Annotation\Http\Middleware;
use ZM\Annotation\Http\RequestMapping;
use ZM\Annotation\Swoole\OnCloseEvent;
use ZM\Annotation\Swoole\OnOpenEvent;
use ZM\Annotation\Swoole\OnRequestEvent;
use ZM\API\CQ;
use ZM\API\OneBotV11;
use ZM\API\TuringAPI;
use ZM\ConnectionManager\ConnectionObject;
use ZM\Console\Console;
use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\Http\RequestMapping;
use ZM\Event\EventDispatcher;
use ZM\Exception\InterruptException;
use ZM\Module\QQBot;
@@ -23,7 +26,6 @@ use ZM\Utils\ZMUtil;
/**
* Class Hello
* @package Module\Example
* @since 2.0
*/
class Hello
@@ -32,45 +34,57 @@ class Hello
* 默认的图片监听路由对应目录,如需要使用可取消下面的注释,把上面的 /* 换成 /**
* @OnStart(-1)
*/
//public function onStart() {
// public function onStart() {
// \ZM\Http\RouteManager::addStaticFileRoute("/images/", \ZM\Utils\DataProvider::getWorkingDir()."/zm_data/images/");
//}
// }
/**
* 使用命令 .reload 发给机器人远程重载,注意将 user_id 换成你自己的 QQ
* @CQCommand(".reload",user_id=627577391)
*/
public function reload() {
ctx()->reply("重启中...");
public function reload()
{
ctx()->reply('重启中...');
ZMUtil::reload();
}
/**
* @CQCommand("我是谁")
*/
public function whoami() {
$user = ctx()->getRobot()->getLoginInfo();
return "你是" . $user["data"]["nickname"] . "QQ号是" . $user["data"]["user_id"];
public function whoami()
{
$bot = ctx()->getRobot()->getLoginInfo();
$bot_id = $bot['data']['user_id'];
$r = OneBotV11::get($bot_id);
$QQid = ctx()->getUserId();
$nick = $r->getStrangerInfo($QQid)['data']['nickname'];
return '你是' . $nick . 'QQ号是' . $QQid;
}
/**
* 向机器人发送"你好啊",也可回复这句话
* @CQCommand(match="你好",alias={"你好啊","你是谁"})
*/
public function hello() {
return "你好啊,我是由炸毛框架构建的机器人!";
public function hello()
{
return '你好啊,我是由炸毛框架构建的机器人!';
}
/**
* 一个最基本的第三方 API 接口使用示例
* @CQCommand("一言")
*/
public function hitokoto() {
$api_result = ZMRequest::get("https://v1.hitokoto.cn/");
if ($api_result === false) return "接口请求出错,请稍后再试!";
public function hitokoto()
{
$api_result = ZMRequest::get('https://v1.hitokoto.cn/');
if ($api_result === false) {
return '接口请求出错,请稍后再试!';
}
$obj = json_decode($api_result, true);
if ($obj === null) return "接口解析出错!可能返回了非法数据!";
return $obj["hitokoto"] . "\n----「" . $obj["from"] . "";
if ($obj === null) {
return '接口解析出错!可能返回了非法数据!';
}
return $obj['hitokoto'] . "\n----「" . $obj['from'] . '」';
}
/**
@@ -78,33 +92,36 @@ class Hello
* @CQCommand(start_with="机器人",end_with="机器人",message_type="group")
* @CQMessage(message_type="private",level=1)
*/
public function turingAPI() {
public function turingAPI()
{
$user_id = ctx()->getUserId();
$api = ""; // 请在这里填入你的图灵机器人的apikey
if ($api === "") return false; //如果没有填入apikey则此功能关闭
$api = ''; // 请在这里填入你的图灵机器人的apikey
if ($api === '') {
return false;
} // 如果没有填入apikey则此功能关闭
if (($this->_running_annotation ?? null) instanceof CQCommand) {
$msg = ctx()->getFullArg("我在!有什么事吗?");
$msg = ctx()->getFullArg('我在!有什么事吗?');
} else {
$msg = ctx()->getMessage();
}
ctx()->setMessage($msg);
if (MessageUtil::matchCommand($msg, ctx()->getData())->status === false) {
return TuringAPI::getTuringMsg($msg, $user_id, $api);
} else {
QQBot::getInstance()->handle(ctx()->getData(), ctx()->getCache("level") + 1);
//执行嵌套消息,递归层级+1
return true;
}
QQBot::getInstance()->handle(ctx()->getData(), ctx()->getCache('level') + 1);
// 执行嵌套消息,递归层级+1
return true;
}
/**
* 响应at机器人的消息
* @CQBefore("message")
*/
public function changeAt() {
public function changeAt()
{
if (MessageUtil::isAtMe(ctx()->getMessage(), ctx()->getRobotId())) {
$msg = str_replace(CQ::at(ctx()->getRobotId()), "", ctx()->getMessage());
ctx()->setMessage("机器人" . $msg);
$msg = str_replace(CQ::at(ctx()->getRobotId()), '', ctx()->getMessage());
ctx()->setMessage('机器人' . $msg);
Console::info(ctx()->getMessage());
}
return true;
@@ -118,15 +135,16 @@ class Hello
* @CQCommand(pattern="*从*到*的随机数")
* @return string
*/
public function randNum() {
public function randNum()
{
// 获取第一个数字类型的参数
$num1 = ctx()->getNumArg("请输入第一个数字");
$num1 = ctx()->getNumArg('请输入第一个数字');
// 获取第二个数字类型的参数
$num2 = ctx()->getNumArg("请输入第二个数字");
$num2 = ctx()->getNumArg('请输入第二个数字');
$a = min(intval($num1), intval($num2));
$b = max(intval($num1), intval($num2));
// 回复用户结果
return "随机数是:" . mt_rand($a, $b);
return '随机数是:' . mt_rand($a, $b);
}
/**
@@ -134,8 +152,9 @@ class Hello
* @RequestMapping("/httpTimer")
* @Middleware("timer")
*/
public function timer() {
return "This page is used as testing TimerMiddleware! Do not use it in production.";
public function timer()
{
return 'This page is used as testing TimerMiddleware! Do not use it in production.';
}
/**
@@ -143,8 +162,9 @@ class Hello
* @RequestMapping("/index")
* @RequestMapping("/")
*/
public function index() {
return "Hello Zhamao!";
public function index()
{
return 'Hello Zhamao!';
}
/**
@@ -153,8 +173,9 @@ class Hello
* @param $param
* @return string
*/
public function paramGet($param) {
return "Hello, " . $param["name"];
public function paramGet($param)
{
return 'Hello, ' . $param['name'];
}
/**
@@ -162,17 +183,18 @@ class Hello
* @OnOpenEvent("qq")
* @param $conn
*/
public function onConnect(ConnectionObject $conn) {
Console::info("机器人 " . $conn->getOption("connect_id") . " 已连接!");
public function onConnect(ConnectionObject $conn)
{
Console::info('机器人 ' . $conn->getOption('connect_id') . ' 已连接!');
}
/**
* 在机器人断开连接后向终端输出信息
* @OnCloseEvent("qq")
* @param ConnectionObject $conn
*/
public function onDisconnect(ConnectionObject $conn) {
Console::info("机器人 " . $conn->getOption("connect_id") . " 已断开连接!");
public function onDisconnect(ConnectionObject $conn)
{
Console::info('机器人 ' . $conn->getOption('connect_id') . ' 已断开连接!');
}
/**
@@ -180,7 +202,8 @@ class Hello
* @OnRequestEvent(rule="ctx()->getRequest()->server['request_uri'] == '/favicon.ico'",level=200)
* @throws InterruptException
*/
public function onRequest() {
public function onRequest()
{
EventDispatcher::interrupt();
}
@@ -188,8 +211,22 @@ class Hello
* 框架会默认关闭未知的WebSocket链接因为这个绑定的事件你可以根据你自己的需求进行修改
* @OnOpenEvent("default")
*/
public function closeUnknownConn() {
Console::info("Unknown connection , I will close it.");
public function closeUnknownConn()
{
Console::info('Unknown connection , I will close it.');
server()->disconnect(ctx()->getConnection()->getFd());
}
/**
* 输出帮助信息
*
* @CQCommand("帮助")
*/
#[CQCommand('帮助')]
public function help(): string
{
$helps = MessageUtil::generateCommandHelp();
array_unshift($helps, '帮助:');
return implode("\n", $helps);
}
}

Some files were not shown because too many files have changed in this diff Show More