├── .gitattributes ├── .gitignore ├── .htaccess ├── .user.ini ├── 404.html ├── LICENSE ├── README.md ├── app.php ├── demo ├── demo.php ├── resetTryNum.php ├── setNewCapImg.php └── test.php ├── file └── captchaImage │ └── 20211003 │ ├── 0148be359c004d060f419401c4668bef.png │ ├── 02a1b2198725450cc11131e8eae2c003.png │ ├── 09bfde7dde58b096d0ca1541994e34d2.png │ ├── 0b003516e950465923e2f0901acc5d9b.png │ ├── 11f50f25c426d32fe1f2beb9313622ce.png │ ├── 148b2b7870dd180787c6b53017cb2a55.png │ ├── 17f9bfbd74af113bff521d6be2f9da5d.png │ ├── 1d0784844615315c337ccf3206af56bd.png │ ├── 20f7b7adea197e1597811105e183b047.png │ ├── 227ed09e198f53b9d02d1c2f621ab7d3.png │ ├── 2333f18436b2b585c5cf80ab36a3e240.png │ ├── 2d3992e30e09650eaf50c0c901596a2d.png │ ├── 2e34f38b8cfe4d202d1ed53128a8bf63.png │ ├── 3ac5c70d4649a5f4f96a340941bcf547.png │ ├── 3aceadede089a9e92070eb3063aec9bf.png │ ├── 3cb02627ffa206ce897f1af18b5ec48a.png │ ├── 414a71862e8403887e71be0f70fe0518.png │ ├── 458f530114f0abc57a2310ef4fbfebfc.png │ ├── 4d35d755184df102a6d0c2ab528891e6.png │ ├── 4f8c308128ad7e6965553729c3f5a968.png │ ├── 55e93d9466f863b051b0ec708e082b07.png │ ├── 5656ceb5c6b5ec8220517620bf1947dd.png │ ├── 57e76cec2767246fa2b3bdc825825b96.png │ ├── 5e71ea9c3fb89f9b090c7aacbd5d20d6.png │ ├── 67e57d1f700f6289616f05e0d07927a1.png │ ├── 67f6b8207992f77ea729fc974af667c3.png │ ├── 680f252da719aebba99c680de3f2cf28.png │ ├── 6bfdde247a1bbf734bce313fb2119ad5.png │ ├── 6c9deb6f96bc468ec91ca61747425661.png │ ├── 6cb904c4410a696d48bfbeab7f7c6faa.png │ ├── 6d7dff98dfde16036cc51022aa56d46a.png │ ├── 70ef0dbea60f3bc320ecbd561e98f2e1.png │ ├── 73b54f28c525a8104f47c603c769b0b6.png │ ├── 7b211c54b26d84d09302d2ce76e34892.png │ ├── 7dd0d723de233ef18b25edebeb417c4e.png │ ├── 7e2ac751213696c6976aedd96860931c.png │ ├── 801a11c0013a9b436aea30ab92af2ccd.png │ ├── 80381788c90fc02a6d571cbd8b37a1e4.png │ ├── 83f5302252c47992a025b44ede951844.png │ ├── 8432eefd8e0f35bf24fac6b23276e7b7.png │ ├── 84a5cb8bf0f3cbe3a171d02f0905d692.png │ ├── 86ac166dea1b976b6f534453d3e85632.png │ ├── 8dd428e558d3ae1618ac4ca465121f62.png │ ├── 8f1b52e26aff8ca8de8b1e5db6572847.png │ ├── 936d272f592acb666c9112b8d2ca874c.png │ ├── 94004e28ec9e56cdb02cf5c4c21f6008.png │ ├── a4cacce73bc915aa191f58ee4c38d757.png │ ├── a64d978a7d8aa9429c227b484abb8b71.png │ ├── a6eec2c69d8d8c44ea1da2c76c36e9d8.png │ ├── a98f3c24834f2eb88b5b19324bc437eb.png │ ├── a9bf678fc83a46f76b5a1cfad1a81fdc.png │ ├── aa2f3415f5d0aacd16d9c9576c8cbe94.png │ ├── ab0107ad0dd0e3f669b18b630d49c0c0.png │ ├── adee477f003389c20c99a7c7ae85f8c8.png │ ├── af16bb321cf0dca471dc250e16fd8fb6.png │ ├── b32471ce1b069ccdd6d7fd6b8f19543c.png │ ├── b37767dae7eac51739e00efb711e05c2.png │ ├── b6b3936205bab90debcdcbba4913dd52.png │ ├── b6b4459d78674b60491ee93a3d7cb6bb.png │ ├── be20c0824120fd3c572dbd8e79e85e56.png │ ├── c132c6f0c2f594791132ada83c8c87ed.png │ ├── c5f74ea8a275479a591535769a7a14da.png │ ├── cfc316a94cb96a89dc489d8c910ede67.png │ ├── d39fb589d068165c802c387eb939b08c.png │ ├── d510a7eb22b0e73a446826e4cf650d50.png │ ├── d63383b7e0e1bad7e2cf2c410fa5cfe1.png │ ├── d79581b26e9fc8404c7b1367d30031de.png │ ├── dcb937860b94bf5456d61560b974d0cb.png │ ├── e196dd7d691a0f109f09d98aa6f27fa2.png │ ├── e5915ac42ef687e0c56765fbc0f5b2d5.png │ ├── e8aa52425aaae1ab0516a4b9507de5b2.png │ ├── eb19012e01ade6fc7aca76abc6d68b07.png │ ├── f3199eb84f452671601bbb14ca785faa.png │ ├── f4a0ca97ee8361b647a0d1e57d1b89a1.png │ ├── f65fc258fe04d7d935734e9dd53b9561.png │ ├── f8c59dd9ba830fe28534c85c821cfc3a.png │ └── fcfd1db2390d51c750d8c2359de683f8.png ├── index.php ├── lib ├── config.php ├── endCode.php ├── fun │ ├── json.php │ ├── other.php │ └── sign.php ├── mysqlCon.php ├── redisCon.php └── session.php ├── nodejs └── captcha │ ├── index.js │ ├── package.json │ └── temp │ └── none.txt ├── preview ├── css │ ├── app.f68f6c8e.css │ ├── chunk-13a41c84.bcd4fb12.css │ ├── chunk-aef2b596.bd6a6533.css │ └── chunk-vendors.a16c4353.css ├── fonts │ ├── element-icons.535877f5.woff │ └── element-icons.732389de.ttf ├── icon │ └── png │ │ ├── drag_black.png │ │ └── drag_white.png ├── js │ ├── app.537514ce.js │ ├── app.fdc052e7.js │ ├── chunk-13a41c84.f4d94071.js │ ├── chunk-aef2b596.e736b097.js │ └── chunk-vendors.2326356f.js ├── static │ ├── css │ │ ├── animation.css │ │ ├── colorUiIcon.eot │ │ ├── colorUiIcon.svg │ │ ├── colorUiIcon.ttf │ │ ├── icon.css │ │ └── main.css │ ├── icon │ │ ├── drag_black.png │ │ └── drag_white.png │ ├── index.5e7e3b56.css │ ├── js │ │ ├── chunk-vendors.3800e8e6.js │ │ ├── index.742f3939.js │ │ └── pages-index.831d8ba7.js │ └── static │ │ ├── css │ │ ├── animation.css │ │ ├── colorUiIcon.eot │ │ ├── colorUiIcon.svg │ │ ├── colorUiIcon.ttf │ │ ├── icon.css │ │ └── main.css │ │ ├── icon │ │ ├── drag_black.png │ │ └── drag_white.png │ │ ├── index.5e7e3b56.css │ │ └── js │ │ ├── chunk-vendors.3800e8e6.js │ │ ├── index.742f3939.js │ │ └── pages-index.831d8ba7.js ├── uniapp.html └── vuecli.html ├── public ├── captcha │ ├── checkCaptcha.php │ ├── function.php │ └── getCaptcha.php └── getToken.php └── rotateCaptcha.sql /.gitattributes: -------------------------------------------------------------------------------- 1 | *.css linguist-language=php -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | nodejs/captcha/node_modules/ -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine on 2 | RewriteRule ^(.*?).json$ $1.php 3 | RewriteRule ^nodejs /404.html 4 | 5 | ## nginx: 6 | ## rewrite ^(.*).json$ /$1.php; 7 | ## rewrite ^\/nodejs /404.html; -------------------------------------------------------------------------------- /.user.ini: -------------------------------------------------------------------------------- 1 | open_basedir=/www/wwwroot/127.0.0.8/:/tmp/ -------------------------------------------------------------------------------- /404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 404 8 | 21 | 22 | 23 | 24 |

404,您请求的文件不存在!

25 | 26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021 DengHongJie 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 旋转验证码 - PHP接口v0.1 2 | ![](https://s3.bmp.ovh/imgs/2021/10/5782d8b3cbde6179.png) 3 | ### 在线示例:[点击查看](http://rotatecaptcha.demo.api0.cn/) 4 | **** 5 | ### 国内访问加速: 6 | Gitee码云:[点击跳转](https://gitee.com/t1zf/rotateCaptcha/) 7 | **** 8 | ### 旋转验证码优点 9 | - 兼容pc和手机操作,拖动滑块即可完成验证,不像传统验证码需要输入数字或字母 10 | - 验证码底图可以无限多,录入底图仅需保证图片为正角即可,且每张底图生成后有随机干扰噪点,无法暴力缓存底图通过对比破解 11 | - 滑块拖动时会保存轨迹信息,可以分析轨迹数据识别真人或人机 12 | ### 缺点 13 | - 目前前端依赖组件库太多,接入其他项目较繁琐(已加入TODO优化待办事项) 14 | - 无法对抗打码平台(对人机效验要求高的话建议加入结合上下文的二次效验,例如旋转验证码通过后再弹出输入框让用户数据自己账号的结尾两位字符) 15 | **** 16 | ### 运行环境要求: 17 | - web服务器:Nginx或Apache 18 | - php脚本环境:PHP8(需要安装扩展:redis,取消禁用函数: shell_exec) 19 | - 数据库:MySQL 5.6 20 | - 缓存及Session:Redis 21 | - 验证码生成:NodeJs 22 | - (建议使用宝塔搭建运行环境) 23 | ### 安装教程: 24 | 1. 克隆项目到本地; 25 | 2. 使用宝塔创建网站,设置伪静态: 26 | - Apache 27 | ``` 28 | RewriteEngine on 29 | RewriteRule ^(.*?).json$ $1.php 30 | RewriteRule ^nodejs /404.html 31 | ``` 32 | - Nginx 33 | ``` 34 | rewrite ^(.*).json$ /$1.php; 35 | rewrite ^\/nodejs /404.html; 36 | ``` 37 | 3. 创建数据库,数据库名称以你项目为准 38 | 4. 导入`./rotateCaptcha.sql`文件到数据库 39 | 5. 修改配置文件`./lib/config.php`,注意:配置node路径时默认填写`node`即可,若php无nodejs执行权限,则需要填写nodejs的安装路径,并修改nodejs安装目录权限为`755`用户组`www` 40 | 6. 配置nodejs验证码生成模块,命令行进入web目录`./nodejs/captcha/`执行安装扩展命令: 41 | ``` 42 | npm i 43 | ``` 44 | 7. 通过浏览器访问刚配置好的网站,如果看到验证码测试页面,即代表安装成功! 45 | **** 46 | ### vue前端接入: 47 | - VueCli示例源码:[查看开源地址](https://github.com/1615958039/rotateCaptcha_vuecli) 48 | - UNIAPP示例源码:[查看开源地址](https://github.com/1615958039/rotateCaptcha_uniapp) 49 | **** 50 | 文档还在继续完善中,敬请期待... 51 | **** 52 | ## 开源不易,点个Star支持一下呗! 53 | **** 54 | 如安装过程或对源码有疑义的地方可提issue或联系作者私人QQ `1615958039` 55 | 56 | 57 | -------------------------------------------------------------------------------- /app.php: -------------------------------------------------------------------------------- 1 | getIp(), 12 | ]); 13 | $session["captchaCheckTime"] = 0; 14 | code(1); 15 | -------------------------------------------------------------------------------- /demo/setNewCapImg.php: -------------------------------------------------------------------------------- 1 | $imgMd5])){ 29 | 30 | easySqlInsert("captchaImage",[ 31 | "file" => $imgRelativePath, 32 | "addTime" => time(), 33 | "imgMd5" => $imgMd5, 34 | ]); 35 | copy($imagePath,$imgSavePath); 36 | unlink($imagePath); 37 | $saveSuccess++; 38 | 39 | }else{ 40 | 41 | $saveErrorImgs[] = "图片已存在数据库!md5=" . $imgMd5 . "图片路径:" . $imagePath; 42 | 43 | } 44 | 45 | } 46 | 47 | 48 | 49 | var_dump($saveErrorImgs); 50 | die("脚本运行结束! _ 插入成功".$saveSuccess."张图片!"); 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | /** 61 | * 获取自动目录的文件夹和文件列表 62 | */ 63 | function getDirList(string $path):array{ 64 | $temp = scandir($path); 65 | $fileList = []; 66 | $dirList = []; 67 | foreach ($temp as $v) { 68 | $a = $path . "/" . $v; 69 | if (is_dir($a)) { 70 | if ($v == '.' || $v == '..')continue; 71 | $dirList[] = $a; 72 | $rt = getDirList($a); 73 | $fileList = array_merge($fileList,$rt['file']); 74 | $dirList = array_merge($dirList,$rt['dir']); 75 | } else { 76 | $fileList[] = $a; 77 | } 78 | } 79 | return [ 80 | "file" => $fileList, 81 | "dir" => $dirList 82 | ]; 83 | } -------------------------------------------------------------------------------- /demo/test.php: -------------------------------------------------------------------------------- 1 | 67 | 68 | 72 | -------------------------------------------------------------------------------- /lib/config.php: -------------------------------------------------------------------------------- 1 | "127.0.0.1", //数据库地址 9 | "dbname" => "", //库名称 10 | "user" => "", //账号 11 | "pass" => "", //密码 12 | "charset" => "utf8mb4", //编码 13 | ]; 14 | 15 | $appKey = "rotateCaptcha_By_QQ_16159580639"; //项目key,请求签名时使用,用于请求md5效验,防中间人攻击篡改数据 16 | 17 | $tokenStart = 'rotateCaptcha_'; //token存在redis的开头字符串 18 | $tokenExpire = 86400 * 31; //token无操作后过期时间(秒) 19 | $tokenOneIpNum = 100; //单个ip 在token有效期内允许生成多少个token 20 | $tokenSaveDBIndex = 10; //token保存在redis哪个数据库 21 | 22 | $nodePath = "node"; //node命令地址,默认node,权限不足时需要填写node具体安装路径 23 | 24 | 25 | /** 26 | * 初始化变量 27 | */ 28 | $type = $_REQUEST['type']??null; //请求的type类型 29 | 30 | $pdo = null; //初始化pdo对象 31 | $redis = null; //redis初始化 32 | $session = null; //初始化session 33 | 34 | $date = date("Y-m-d H:i:s"); //当前日期 35 | 36 | $root = $_SERVER['DOCUMENT_ROOT']; //网站运行目录 "/www/wwwroot/php" 37 | 38 | 39 | 40 | 41 | /** 42 | * 旋转验证码数据配置 43 | */ 44 | $captchaConfig = [ 45 | "canvasSize" => 480, //默认正方形验证码像素大小 46 | "checkTimeOut" => 600, //验证码有效期 47 | 48 | "dragInterval" => 200, //鼠标轨迹间隔时间(ms) 49 | 50 | "dragTimeMin" => 500, //拖拽至少用时(ms) 51 | "dragTimeMax" => 10*1000,//拖拽最多用时(ms) 52 | 53 | "errorAccuracy" => 10, //左右误差度数允许值 54 | "oneCapErrNum" => 3, //每个验证码最多错误几次失效 55 | 56 | "ipDayAll" => 300, //单IP一天允许生成多少次验证码 57 | "ipDayError" => 100, //单IP一天允许验证失败次数 58 | 59 | "ipHourAll" => 100, //单IP一小时内允许生成多少张验证码 60 | "ipHourError" => 30, //单IP一小时内允许出错多少次 61 | 62 | "randomPoint" => 200, //初始 随机干扰点数量 63 | "randomLine" => 50, //初始 随机干扰线数量 64 | "randomBlock" => 3, //初始 随机干扰矩块数量 65 | 66 | /** 67 | * 动态干扰点线面 的生成规则,以IP统计 68 | * 请设置一个匿名函数并接收参数 $options 69 | * $options = [ 70 | * "dayAll" => 0, // 今天生成的验证码总数 71 | * "dayError" => 0, // 今天验证失败总数 72 | * "hourAll" => 0, // 一小时内验证码生成数量 73 | * "hourError" => 0, // 一小时内验证失败次数 74 | * ]; 75 | * 函数返回要生成的点线面数量,该值若小于初始数量则选用初始数量 76 | */ 77 | // 动态添加随机颜色的像素点 78 | "addPoint" => function(array $options):int{ 79 | return 0; 80 | }, 81 | // 动态添加随机颜色随机长度的线段 82 | "addLine" => function(array $options):int{ 83 | return 0; 84 | }, 85 | // 动态添加随机颜色随机大小的矩形 86 | "addBlock" => function(array $options):int{ 87 | return 0; 88 | }, 89 | 90 | 91 | 92 | /** 93 | * 验证码效验完,允许使用的最大次数 94 | */ 95 | "captchaUseMaxNum" => 3, 96 | /** 97 | * 验证码效验完,有效期(秒) 98 | */ 99 | "captchaUseMaxTime" => 60*60, 100 | 101 | ]; 102 | -------------------------------------------------------------------------------- /lib/endCode.php: -------------------------------------------------------------------------------- 1 | 请求失败 message 返回原因 8 | * 1 => 请求成功 9 | * -1 => 无token需要先获取token 10 | * -2 => 会话已失效,请重新登录 11 | */ 12 | function code(int|string|array $arr = 0, string|array $msg = ""):void{ 13 | if (is_array($arr)) { 14 | if (!$arr['code']) $arr['code'] = 0; 15 | if (!$arr['message']) $arr['message'] = 'error'; 16 | endCode($arr); 17 | } else if (!is_array($msg) && $msg != "") { 18 | endCode(['code' => $arr, "message" => $msg]); 19 | } else if (in_array($arr, [-2, -1, 0, 1]) && !is_string($arr)) { 20 | if (!is_array($msg)) $msg = []; 21 | if (isset($msg['message'])) { 22 | $msg['code'] = $arr; 23 | endCode($msg); 24 | } else { 25 | $msg['code'] = $arr; 26 | $msg['message'] = $arr == 0 ? 'error' : 'success'; 27 | endCode($msg); 28 | } 29 | } else if ($msg == "") { 30 | endCode(["code" => 0, "message" => $arr]); 31 | } 32 | } 33 | 34 | 35 | 36 | 37 | /** 38 | * json_encode 二次封装 39 | */ 40 | function setJson( 41 | array $arr=[], 42 | ):string{ 43 | if (!is_array($arr)) return ""; 44 | return json_encode($arr, JSON_UNESCAPED_UNICODE); 45 | } 46 | 47 | /** 48 | * json_decode 二次封装 49 | */ 50 | function getJson( 51 | string|array $json, 52 | ){ 53 | if(is_array($json))return $json; 54 | if (!$json) return false; 55 | $rt = false; 56 | try { 57 | $rt = json_decode($json, true); 58 | } catch (\Throwable $th) { 59 | var_dump($th); 60 | } 61 | if (!is_array($rt)) return false; 62 | return $rt; 63 | } -------------------------------------------------------------------------------- /lib/fun/other.php: -------------------------------------------------------------------------------- 1 | $appKey 11 | ]); 12 | ksort($param); 13 | $param = md5(objToUrlCode($param)); 14 | if($sign!==$param)code("签名错误!"); 15 | } 16 | 17 | 18 | /** 19 | * 对象转成url编码格式 20 | */ 21 | function objToUrlCode($arr) { 22 | $str = ''; 23 | foreach($arr as $key=>$value)$str = $str."&".$key."=".$value; 24 | return $str; 25 | } -------------------------------------------------------------------------------- /lib/mysqlCon.php: -------------------------------------------------------------------------------- 1 | PDO::ERRMODE_WARNING, 12 | PDO::ATTR_PERSISTENT => true, 13 | ]); 14 | $pdo->exec('set names '.$DBconfig['charset']); 15 | $pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false); 16 | } catch (\Throwable $th) { 17 | code("数据库连接失败!"); 18 | } 19 | 20 | 21 | /** 22 | * sql语句封装 23 | * 获取sql错误: $pdo->errorInfo() 24 | */ 25 | function runSql( 26 | string $sqly, 27 | $arr = [], 28 | $type = "", // type=list返回多列数据 29 | ){ 30 | global $pdo; 31 | if(!$pdo){ 32 | echo "数据库未连接"; 33 | return false; 34 | } 35 | if ($arr) { 36 | if (!is_array($arr)) { 37 | echo "SQL预处理出错 -> \$arr格式非array -> var_dump(\$arr) ->"; 38 | var_dump($arr); 39 | exit; 40 | } 41 | } 42 | $res = $pdo->prepare($sqly); 43 | if ($arr) $res->execute($arr); 44 | else $res->execute(); 45 | if (strpos($sqly, "COUNT(*)") > 0) { //统计 46 | return $res->fetchColumn(); 47 | } else if (strpos($sqly, "SELECT") === 0) { //SELECT 查询 48 | if ($type == "list") { //查询多条数据 49 | $rt = $res->fetchAll(\PDO::FETCH_ASSOC); 50 | }else{ //查询一条数据 51 | $rt = $res->fetch(\PDO::FETCH_ASSOC); 52 | } 53 | if(!$rt)$rt = []; 54 | return $rt; 55 | } else if (strpos($sqly, "UPDATE") === 0) { //更新 修改数据 56 | return $res->rowCount(); //返回受影响的行数 57 | } else if (strpos($sqly, "INSERT") === 0) { //插入 增加数据 58 | return $pdo->lastinsertid(); //返回新插入的ID 59 | } else if (strpos($sqly, "DELETE") === 0) { //删除数据 60 | return $res->rowCount(); //返回受影响的行数 61 | } else { 62 | echo "无法解析预处理语句 => " . $sqly; 63 | exit; 64 | } 65 | } 66 | 67 | 68 | 69 | /** 70 | * 替换sql预处理语句 为 正常sql语句 71 | */ 72 | function getSqlCode(string $sql,array $arr):string{ 73 | foreach($arr as $item){ 74 | $qStart = strpos($sql,"?"); 75 | $sqls = mb_substr($sql, 0, $qStart, 'utf-8'); 76 | $sqle = mb_substr($sql,$qStart+1,mb_strlen($sql,"utf-8"),"utf-8"); 77 | $sql = $sqls.'"'.$item.'"'.$sqle; 78 | } 79 | return $sql; 80 | } 81 | 82 | 83 | /** 84 | * 解析where参数 85 | * 支持写法: 86 | * 1. [ "字段名"=>("值"||[a,b,c...]) ] 87 | * 2. [ ["字段名","判断符",("值"||[a,b,c...])] ] 88 | * 3. [ "带?的sql语句"=>[a,b,c...] ] 89 | */ 90 | function easySqlAnalysisWhere(array $where=[]){ 91 | $sql = ""; //拼接后的sql语句 92 | $data = []; //需要预处理的参数值 93 | $i = 0; 94 | foreach($where as $key=>$value){ 95 | if(strpos($key, "?") > 0){ // "sql语句带?"=>[传参的值数组] : "id<>?"=>["1"] 96 | $sql = $sql . ($i==0?(" WHERE ".$key):(" AND ".$key)); 97 | foreach($value as $item)$data[] = $item; 98 | }else if(is_numeric($key) && is_array($value)){ // ["字段名","判断条件","值"] 99 | $value[1] = strtoupper($value[1]); 100 | 101 | if(in_array($value[1],[ 102 | "=","<>",">","<",">=","<=","LIKE" 103 | ])){ //正常符号 104 | 105 | $sql = $sql . ($i==0?" WHERE ":" AND ") . $value[0] . " " . $value[1] . " " . " ? "; 106 | $data[] = $value[2]; 107 | 108 | }else if(in_array($value[1],["NOT IN","IN"])){ // ["字段名","in",[a,b,c...]] 109 | $sql = $sql . ($i==0?" WHERE ":" AND ") . " $value[0] $value[1] ("; 110 | $_i = 0; 111 | foreach($value[2] as $item){ 112 | $sql = $sql . ($_i==0?"?":",?"); 113 | $data[] = $item; 114 | $_i++; 115 | } 116 | $sql = $sql . ") "; 117 | }else if($value[1]=="BETWEEN"){ // ["字段名","BETWEEN",[a,b]] 118 | $sql = $sql.($i==0?" WHERE ":" AND ")." $value[0] $value[1] ? AND ? "; 119 | $data[] = $value[2][0]; 120 | $data[] = $value[2][1]; 121 | } 122 | 123 | 124 | }else{ // "字段名"=>"值" || "字段名称" => [a,b,c] 125 | if(is_array($value)){ 126 | 127 | $sql = $sql . ($i==0?" WHERE ":" AND ") . " $key IN ("; 128 | $_i = 0; 129 | foreach($value as $item){ 130 | $sql = $sql . ($_i==0?"?":",?"); 131 | $data[] = $item; 132 | $_i++; 133 | } 134 | $sql = $sql . ") "; 135 | 136 | }else{ 137 | 138 | $sql = $sql . (($i==0)?(" WHERE $key=? "):(" AND $key=? ")); 139 | $data[] = $value; 140 | 141 | } 142 | } 143 | $i++; 144 | } 145 | return [$sql,$data]; 146 | } 147 | 148 | 149 | 150 | 151 | /** 152 | * 简易查询语句封装 153 | * @param array $orderBy [["字段","DESC||ASC"]] 154 | */ 155 | function easySqlSelect( 156 | string|array $values, //搜索类型 157 | string $tableName, //表名 158 | string|array $where=[], // 查询条件 [["字段名","判断条件","值"],"字段名"=>"值","sql语句带?"=>[传参的值数组]] 159 | string|array $orderBy=[], // 排序条件 ["字段名称"=>"排序方式","id"=>"DESC"], 160 | string|array $limit=["one"] //查询条数,不填或传"[1]"则只查询一条,["页码","每页行数"]则查询多行,页码从0开始 161 | ):array{ 162 | $data = []; 163 | if(is_array($values)){ //获取指定字段 164 | $temp = ""; 165 | foreach($values as $item){ 166 | $temp = $temp . ($temp==""?"":",") . $item; 167 | } 168 | $values = $temp; 169 | } 170 | $sql = "SELECT $values FROM $tableName "; 171 | if($where){ 172 | $aw = easySqlAnalysisWhere($where); 173 | $sql = $sql . $aw[0]; 174 | $data = array_merge($data,$aw[1]); 175 | } 176 | if($orderBy){ 177 | if(is_array($orderBy)){ 178 | $i = 0; 179 | foreach($orderBy as $key=>$value){ 180 | $value = strtoupper($value=="DESC"?"DESC":"ASC"); 181 | $sql = $sql . ($i==0?(" ORDER BY $key $value "):(" , $key $value ")); 182 | $i++; 183 | } 184 | }else{ 185 | $sql = $sql . " ORDER BY ".$orderBy." "; 186 | } 187 | } 188 | 189 | $isList = false; 190 | if($limit && isset($limit[1])){ 191 | $sql = $sql . " LIMIT ?,?"; 192 | $data[] = $limit[0] * $limit[1]; 193 | $data[] = $limit[1]; 194 | $isList = true; 195 | }else if(!$limit || $limit[0]=="one"){ 196 | $sql = $sql . " LIMIT ?"; 197 | $data[] = 1; 198 | }else if($limit[0]=="all"){ //返回全部 199 | $isList = true; 200 | }else if(isset($limit[0])){ 201 | $sql = $sql . " LIMIT ?"; 202 | $data[] = $limit[0]; 203 | $isList = true; 204 | }else{ 205 | $sql = $sql . " LIMIT 1"; 206 | } 207 | // var_dump($sql); 208 | // var_dump($data); 209 | return runSql($sql,$data,$isList?"list":""); 210 | } 211 | 212 | 213 | /** 214 | * 快速插入数据 215 | * @return 插入后的自增id 216 | */ 217 | function easySqlInsert( 218 | string $tableName, 219 | array $values=[], // ["字段名"=>"值"] 220 | bool $debug=false 221 | ):int{ 222 | $sql = "INSERT INTO `$tableName` "; 223 | $data = []; 224 | $keys = "("; 225 | $datas = "("; 226 | $i = 0; 227 | foreach($values as $key=>$value){ 228 | $keys = $keys . ($i==0?"":",") . "`$key`"; 229 | $datas = $datas . ($i==0?"":",") . "?"; 230 | $data[] = $value; 231 | $i++; 232 | } 233 | $sql = "$sql $keys) VALUES $datas)"; 234 | if($debug){ 235 | var_dump($sql); 236 | var_dump($data); 237 | var_dump(getSqlCode($sql,$data)); 238 | } 239 | return runSql($sql,$data); 240 | } 241 | 242 | /** 243 | * 快捷更新表数据 244 | * @return 返回影响的行数 245 | */ 246 | function easySqlUpdate( 247 | string $tableName, //表名 248 | array $where=[], //where参数 249 | /** 250 | * 需要更新的值,支持写法 251 | * 1. [字段名=>值] -> ["name"=>"我是ID1111"] 252 | * 2. ["SQL预处理语句"=>[参数1,参数2]] -> ["jf=jf+?",[1]] 253 | */ 254 | array $setValues=[], //需要更新的值 255 | bool $debug=false 256 | ):int{ 257 | $sql = "UPDATE `$tableName` "; 258 | $data = []; 259 | $i = 0; 260 | foreach($setValues as $key=>$value){ 261 | $sql = $sql . ($i==0?" SET ":" , "); 262 | 263 | if(is_array($value)){ // 传入sql语句 ["id=id+?"=>[1]] 264 | 265 | $sql = $sql . $key; 266 | $data = array_merge($data,$value); 267 | 268 | }else{ // 键值格式 ["字段名"=>"值"] 269 | 270 | $sql = $sql." $key=? "; 271 | $data[] = $value; 272 | 273 | } 274 | $i++; 275 | } 276 | if($where){ 277 | $aw = easySqlAnalysisWhere($where); 278 | $sql = $sql . $aw[0]; 279 | $data = array_merge($data,$aw[1]); 280 | } 281 | if($debug){ 282 | var_dump($sql); 283 | var_dump($data); 284 | } 285 | return runSql($sql,$data); 286 | } 287 | 288 | /** 289 | * 快速删除指定数据 290 | * @return 返回影响的行数 291 | */ 292 | function easySqlDelete( 293 | string $tableName, //表名 294 | array $where=[] 295 | ):int{ 296 | $aw = easySqlAnalysisWhere($where); 297 | $sql = "DELETE FROM $tableName " . $aw[0]; 298 | $data = $aw[1]; 299 | return runSql($sql,$data); 300 | } 301 | 302 | 303 | /** 304 | * 统计指定数量 305 | */ 306 | function easySqlCount(string $tableName,array $where=[]):int{ 307 | return (int)(easySqlSelect("count(*)",$tableName,$where)["count(*)"]); 308 | } 309 | 310 | 311 | 312 | /** 313 | * 执行事务 314 | * @param function $function 事务内执行的sql相关函数 315 | */ 316 | function easySqlTransaction($function,bool $dumpError=false):bool{ 317 | global $pdo; 318 | try { 319 | $pdo->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION); 320 | $pdo->beginTransaction(); 321 | if($function()===false)throw new Exception("function return false"); 322 | $pdo->commit(); 323 | return true; 324 | } catch (\Throwable $th) { 325 | $pdo->rollBack(); 326 | if($dumpError)var_dump($th); 327 | return false; 328 | } 329 | } 330 | 331 | /** 332 | * 拼接数组成sql需要返回的字段 333 | * @param array $fieldList 字段设置列表 334 | */ 335 | function analysisField(array $fieldList):string{ 336 | $param = []; 337 | foreach ($fieldList as $fieldName => $options){ 338 | 339 | if(is_callable($options))continue; 340 | 341 | if(is_numeric($fieldName) && is_string($options)){ //仅字段名称,无设置参数 342 | 343 | $param[] = $options; 344 | 345 | }else if(is_array($options) && is_string($fieldName)){ 346 | if(isset($options["from"]) && $options["from"]){ 347 | 348 | $param[] = $options["from"]. " AS ".$fieldName; 349 | 350 | }else{ 351 | 352 | $param[] = $fieldName; 353 | 354 | } 355 | } 356 | } 357 | $sql = ""; 358 | foreach($param as $item){ 359 | $sql .= ($sql==""?"":" , ") . " $item "; 360 | } 361 | return $sql; 362 | } 363 | 364 | 365 | /** 366 | * 解析可排序的字段 367 | * @param array $fieldList 字段设置列表 368 | * @param array $needReplace 需要替换为空的无用字符串数组 369 | */ 370 | function analysisCanSortField(array $fieldList,array $needReplace=[]):array{ 371 | $canSort = []; 372 | foreach ($fieldList as $fieldName => $options){ 373 | if( 374 | !is_array($options) 375 | || !is_string($fieldName) 376 | || !isset($options["sort"]) 377 | || $options["sort"]!==true 378 | || is_callable($options) 379 | )continue; 380 | $cleanFieldName = str_replace($needReplace,"",$fieldName); 381 | $canSort[$cleanFieldName] = $fieldName; 382 | } 383 | return $canSort; 384 | } 385 | 386 | 387 | 388 | /** 389 | * 解析搜索规则 390 | * [ 391 | * "search" => "搜索类型", // ["string","number","dateTime"] 392 | * "search" => [ 393 | * "type" => "搜索类型", // ["select","string"...] 394 | * "select" => [ 395 | * ["label" => "下拉框展示的名称","value" => "选中的值"] 396 | * ], 397 | * "select" => [ 398 | * "list" => ["label" => "下拉框展示的名称","value" => "选中的值"], 399 | * "multiple" => true, //是否允许多选 400 | * ] 401 | * ] 402 | * ] 403 | */ 404 | function getSearchOptions( 405 | array $fieldList, 406 | bool $checkHaving=false, //返回字段是否需要加入having语句中 407 | ):array{ 408 | $canSearch = []; 409 | foreach ($fieldList as $fieldName => $options){ 410 | 411 | if( 412 | !is_string($fieldName) 413 | || !is_array($options) 414 | || !isset($options["search"]) 415 | || is_callable($options) 416 | )continue; 417 | 418 | $item = [ 419 | "prop" => $fieldName, 420 | "label" => $options["label"]??$fieldName, 421 | ]; 422 | 423 | if($checkHaving){ 424 | $item["having"] = (isset($item["from"]) && $item["from"]!=""); 425 | } 426 | 427 | if(is_string($options["search"])){ 428 | $item["type"] = $options["search"]; 429 | }else if(is_array($options["search"])){ 430 | if(!isset($options["search"]["type"]))continue; 431 | switch ($options["search"]["type"]){ 432 | case 'select': 433 | if(!isset($options["search"]["select"]))continue 2; 434 | $item["type"] = "select"; 435 | if( 436 | is_array($options["search"]["select"]) 437 | && !isset($options["search"]["select"]["list"]) 438 | ){ //简写 "select" => [["label","value"]] 439 | $item["select"] = [ 440 | "list" => $options["search"]["select"] 441 | ]; 442 | }else{ 443 | $item["select"] = $options["search"]["select"]; 444 | } 445 | break; 446 | default: 447 | continue 2; 448 | break; 449 | } 450 | } 451 | $canSearch[] = $item; 452 | } 453 | return $canSearch; 454 | } 455 | 456 | 457 | /** 458 | * 获取字段类型对应的搜索规则列表 459 | * @param string $propType "string"||"number"||"select"||"dateTime" 460 | */ 461 | function getSearchRuleJson(string $propType){ 462 | return [ 463 | /** 464 | * 普通字符串 465 | */ 466 | "string" => ["等于","不等于","包含","不包含"], 467 | /** 468 | * 数字 469 | */ 470 | "number" => ["等于","不等于","大于","大于等于","小于","小于等于"], 471 | /** 472 | * 筛选框 473 | */ 474 | "select" => ["等于","不等于"], 475 | /** 476 | * 日期选择器 477 | */ 478 | "dateTime" => ["等于","不等于","大于","大于等于","小于","小于等于"], 479 | ][$propType]; 480 | } 481 | 482 | 483 | /** 484 | * 解析和效验精确搜索参数 485 | * 表单规则: 486 | * [ 487 | * { 488 | * prop: "字段名称", 489 | * rule: "符号", 490 | * value: "传入的值", 491 | * } 492 | * ] 493 | * @param array $searchOptions 搜索规则 494 | * @param array $searchData 搜索提交的表单,注意:需要获取having 495 | * @return array [ 496 | * "where" => [["条件",[预处理参数]]] 497 | * "having" => [["条件",[预处理参数]]] 498 | * ] 499 | */ 500 | function checkAndAnalysisSearchData( 501 | array $searchOptions, // ["prop" => "字段名称","having"=>"是否加入having语句中"] 502 | array $searchData, 503 | ):array{ 504 | 505 | $where = []; 506 | 507 | $having = []; 508 | 509 | foreach ($searchData as $index => $item){ 510 | if( 511 | !is_array($item) 512 | || !isset($item["prop"]) 513 | || !isset($item["rule"]) 514 | || !isset($item["value"]) 515 | || $item["prop"]=="" 516 | || $item["rule"]=="" 517 | || $item["value"]=="" 518 | )code("精确搜索的表单数据有误!(第".($index+1)."项)"); 519 | $propIndex = findPropIndex($searchOptions,$item["prop"]); 520 | if($propIndex==-1)code("字段“".$item["prop"]."”不可搜索"); 521 | if(!isset($searchOptions[$propIndex]["type"]))code("字段(index=>".$propIndex.")配置缺少类型参数!"); 522 | if(!in_array($item["rule"],getSearchRuleJson($searchOptions[$propIndex]["type"])))code("第".($index+1)."项,搜索规则错误!"); 523 | 524 | $sqlString = ""; 525 | $sqlData = []; 526 | 527 | if(in_array($searchOptions[$propIndex]["type"],["string","number","dateTime"])){ 528 | 529 | if(in_array($item["rule"],["包含","不包含"])){ 530 | 531 | $sqlString = " ".$item["prop"].($item["rule"]=="包含"?" LIKE ":" NOT LIKE ")."?"; 532 | $sqlData[] = "%".$item["value"]."%"; 533 | 534 | }else{ 535 | $symbolList = [ 536 | "等于" => "=", 537 | "不等于" => "<>", 538 | "大于" => ">", 539 | "大于等于" => ">=", 540 | "小于" => "<", 541 | "小于等于" => "<=", 542 | ]; 543 | $sqlString = " ".$item["prop"].$symbolList[$item["rule"]]."?"; 544 | $sqlData[] = $item["value"]; 545 | } 546 | 547 | }else if($searchOptions[$propIndex]["type"]=="select"){ 548 | 549 | $item["value"] = getJson($item["value"]); 550 | if(!$item["value"] || !is_array($item["value"]))code("请选择具体选项,第".($index+1)."项"); 551 | if(!isset($searchOptions[$propIndex]["select"]))code("字段(index=>".$propIndex.".select)配置缺少参数!"); 552 | if( 553 | isset($searchOptions[$propIndex]["select"]["multiple"]) 554 | && $searchOptions[$propIndex]["select"]["multiple"]==false 555 | ){ 556 | // 仅限单选 557 | if(count($item["value"])>1)code("第 ".($index+1)." 项仅限单选"); 558 | } 559 | $sqlString = " ".$item["prop"].($item["rule"]=="等于"?" IN ":" NOT IN "); 560 | $sqlString = $sqlString." ("; 561 | foreach ($item["value"] as $_index => $_item){ 562 | if( 563 | !is_string($_item) 564 | && !is_numeric($_item) 565 | && !in_array($_item,array_map(function($__item){ 566 | return $__item["value"]; 567 | },$searchOptions[$propIndex]["select"]["list"])) 568 | )code("第 ".($index+1)." 项的 第 $_index 选项类型有误!"); 569 | $sqlString = $sqlString.($_index==0?"":",")."?"; 570 | $sqlData[] = $_item; 571 | } 572 | $sqlString = $sqlString.") "; 573 | } 574 | 575 | if( 576 | isset($searchOptions[$propIndex]["having"]) 577 | && $searchOptions[$propIndex]["having"]===true 578 | ){ //字段使用了 AS ,需要将语句拼接在having内 579 | 580 | $where[] = [$sqlString,$sqlData]; 581 | 582 | }else{ //默认使用WHERE语句 583 | 584 | $having[] = [$sqlString,$sqlData]; 585 | 586 | } 587 | 588 | 589 | } 590 | return [ 591 | "where" => $where, 592 | "having" => $having 593 | ]; 594 | } 595 | 596 | /** 597 | * 查找搜索数据的索引 598 | * @return int 没找到则返回“-1” 599 | */ 600 | function findPropIndex(array $searchOptions,string $fieldName):int{ 601 | $rt = -1; 602 | foreach ($searchOptions as $index => $item){ 603 | if($item["prop"]==$fieldName){ 604 | $rt = $index; 605 | } 606 | } 607 | return $rt; 608 | } 609 | 610 | 611 | 612 | /** 613 | * 解析还未拼接的where数组 614 | * $where = [ 615 | * ["条件",[预处理参数]] 616 | * ] 617 | * @return array ["总的WHERE语句,不包含“WHERE字符串”",[预处理参数]] 618 | */ 619 | function analysisWhereCondition(array $where){ 620 | $whereSql = ""; 621 | $whereData = []; 622 | foreach ($where as $index => [$sqlString,$sqlData]){ 623 | if(!$sqlString)continue; 624 | $whereSql = $whereSql.($whereSql==""?"":" AND ").$sqlString; 625 | if(!$sqlData || !is_array($sqlData))continue; 626 | $whereData = array_merge($whereData,$sqlData); 627 | } 628 | return [$whereSql,$whereData]; 629 | } 630 | 631 | 632 | /** 633 | * 创建一个字段列表;传入的数据将原样返回,该函数仅做提示和效验使用 634 | * @param array $fieldList [ 635 | * "字段名称" => [ 636 | * "from" => "来自哪张表的哪个字段,不填则不设置AS语句", 637 | * "sort" => "字段是否可排序", 638 | * "search" => "精确搜索的字段类型", 639 | * "label" => "字段中文名称", 640 | * "filter" => function($item,$index){return "value"}, //过滤器,返回结果的新内容 641 | * ], 642 | * "字段名称" => function,//同上filter过滤器,字段等于过滤器函数时不参与sql语句拼接 643 | * 644 | * ] 645 | */ 646 | function newFieldList(array $fieldList = [ 647 | "sqlTableField" => [ 648 | "from" => "",//来自哪张表的哪个字段,不填则不设置AS语句 649 | "sort" => false,//字段是否可排序 650 | "search" => ""||[],//[string,number,dateTime,select]精确搜索的字段类型 651 | "label" => "",//字段中文名称 652 | "filter" => "function"// function($item,$index){return "value";}, //过滤器,返回结果的新内容 653 | ], 654 | "notSqlField" => "function", // function($item,$index){return "value";}, 655 | 656 | ]):array{ 657 | return $fieldList; 658 | } -------------------------------------------------------------------------------- /lib/redisCon.php: -------------------------------------------------------------------------------- 1 | connect('127.0.0.1', 6379); 13 | if (!$redis->ping()) die("redis连接失败!_01"); 14 | $redis->select(0); //默认选择 db0 15 | } catch (Exception $e) { 16 | die("redis连接失败!_02"); 17 | } 18 | 19 | 20 | 21 | /** 22 | * 获取 redis 23 | * $dbIndex 选择数据库 0-15 24 | */ 25 | function getRedis(string $key,int $dbIndex = 0){ 26 | global $redis; 27 | if(!$redis)code("未知错误!_redis_01_1"); 28 | if ($dbIndex < 0 || $dbIndex > 15) return false; 29 | $redis->select($dbIndex); 30 | $data = $redis->get($key); 31 | $isJson = getJson($data); 32 | if ($isJson) return $isJson; 33 | return $data; 34 | } 35 | 36 | 37 | /** 38 | * 写入 redis 39 | */ 40 | function setRedis(string $key,$value, int $expire = 0, int $dbIndex = 0):bool{ 41 | global $redis; 42 | if ($dbIndex < 0 || $dbIndex > 15) return false; 43 | if (!$key || !isset($value)) return false; 44 | $redis->select($dbIndex); 45 | if (is_array($value)) $value = setJson($value); 46 | if ($expire == 0) { 47 | global $tokenExpire; //默认到期时间 48 | $redis->setex($key, $tokenExpire, $value); 49 | } else{ 50 | $redis->setex($key, $expire, $value); 51 | } 52 | return true; 53 | } 54 | 55 | 56 | 57 | /** 58 | * 查询指定数据库的指定key数量 59 | */ 60 | function countRedis(string $key,int $dbIndex = 0):int{ 61 | global $redis; 62 | $redis->select($dbIndex); 63 | $temp = []; 64 | $temp = $redis->scan($temp, $key, 99999999); 65 | return count($temp)??0; 66 | } 67 | 68 | 69 | 70 | /** 71 | * 统计数量 72 | */ 73 | function setCacheCount( 74 | string $keyName, 75 | string|int $addNum=1,//需要增加的数量 76 | string|int $timeOut=86400, //缓存超时时间 77 | string|int $initNum=1, //进入时的数量 78 | ):bool{ 79 | $addNum = (int)$addNum; 80 | $timeOut = (int)$timeOut; 81 | $initNum = (int)$initNum; 82 | 83 | $countNum = getRedis($keyName,2); 84 | if(!$countNum){ //初次写入 85 | return setRedis($keyName,$initNum,$timeOut,2); 86 | } 87 | 88 | return setRedis($keyName,$countNum + $addNum,$timeOut,2); 89 | } 90 | 91 | 92 | 93 | /** 94 | * 获取统计数量 95 | */ 96 | function getCacheCount(string $keyName):int{ 97 | return (int)getRedis($keyName,2); 98 | } 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /lib/session.php: -------------------------------------------------------------------------------- 1 | -1, 'message' => 'token失效~', "token" => $token]; 15 | $key = $tokenStart . "_" . $token; 16 | if (!preg_match("/^[0-9a-f]{16}$/", $token)) code($notoken); 17 | $session = getRedis($key, 15); 18 | if (!$session) code($notoken); 19 | return $session; 20 | } 21 | 22 | /** 23 | * 向Session内写入数据 24 | */ 25 | function setSession(string $key, string $value):bool{ 26 | global $token; 27 | global $session; 28 | global $tokenStart; 29 | $session[$key . ''] = $value; 30 | if (setRedis($tokenStart . "_" . $token, $session, 0, 15)) { 31 | return true; 32 | } 33 | return false; 34 | } 35 | 36 | 37 | 38 | /** 39 | * 获取token 40 | * 允许get post cookie或请求头内传递 token 41 | */ 42 | function getToken():string{ 43 | $token = null; 44 | if(isset($_SERVER['HTTP_TOKEN'])){ 45 | $token = $_SERVER['HTTP_TOKEN']; //请求头内的token优先级最高 46 | }else if(isset($_REQUEST['token'])){ 47 | $token = $_REQUEST['token']; 48 | } 49 | if(!$token)$token = ""; 50 | return $token; 51 | } 52 | 53 | 54 | /** 55 | * 随机生成 token 56 | */ 57 | function randToken():void{ 58 | global $tokenExpire; 59 | global $tokenStart; 60 | global $tokenOneIpNum; 61 | $ip = getIp(); 62 | 63 | 64 | $sumKey = "tokenNum_".$ip."_".date("d"); 65 | $sumKeyDB = 10; 66 | 67 | $tokenCount = (int)getRedis($sumKey,$sumKeyDB); 68 | if($tokenCount > $tokenOneIpNum){ 69 | code("访问频繁了呢!"); 70 | } 71 | 72 | $ua = $_SERVER['HTTP_USER_AGENT']; 73 | $token = md5_16($ua . "_" . $ip . "_" . rand(0, 999999) . "_" . time() . "_" . rand(0, 999999) . "_" . rand(0, 999999)); 74 | $key = $tokenStart . "_" . $token; 75 | $value = [ 76 | "initTime" => time() 77 | ]; 78 | if ( 79 | setRedis($key, $value, $tokenExpire, 15) 80 | && setRedis($sumKey,$tokenCount+1,86400,$sumKeyDB) 81 | ) { 82 | code([ 83 | "code" => 1, 84 | "message" => "success", 85 | "token" => $token, 86 | "num" => $tokenCount, 87 | ]); 88 | } 89 | code("生成token失败!请联系管理员处理_"); 90 | } 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /nodejs/captcha/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Node点坐标合成脚本 3 | * node index.js [验证码原图] [验证码输出地址] [验证码图旋转角度] [画布大小] [随机点数量] [随机线条数量] [随机大矩形数量] 4 | */ 5 | const { createCanvas, loadImage } = require('canvas'); 6 | let fs = require('fs'); 7 | 8 | 9 | 10 | 11 | let initImg = process.argv[2]; 12 | if (!initImg) { 13 | console.log("请传入需要生成验证码的原图") 14 | process.exit(); 15 | } 16 | try { 17 | fs.accessSync(initImg, fs.constants.R_OK); 18 | } catch (error) { 19 | console.log("无法访问验证码原图") 20 | process.exit(); 21 | } 22 | 23 | let outImg = process.argv[3]; 24 | if (!outImg) { 25 | console.log("请传入验证码的输出的地址") 26 | process.exit(); 27 | } 28 | 29 | let rotationAngle = Number(process.argv[4]); 30 | if (rotationAngle < 0 || rotationAngle > 360) { 31 | console.log("请传入正确的验证码旋转角度") 32 | process.exit(); 33 | } 34 | 35 | let canvasSize = process.argv[5] == undefined ? 480 : Number(process.argv[5]); 36 | let randomPoint = process.argv[6] == undefined ? 200 : Number(process.argv[6]); 37 | let randomLine = process.argv[7] == undefined ? 50 : Number(process.argv[7]); 38 | let randomBlock = process.argv[8] == undefined ? 3 : Number(process.argv[8]); 39 | 40 | drawCapImage(initImg, outImg, rotationAngle, canvasSize, randomPoint, randomLine, randomBlock).then(() => { 41 | 42 | console.log("success") 43 | 44 | }).catch(err => { 45 | console.log("验证码图片生成失败") 46 | process.exit(); 47 | }) 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | /** 58 | * 画线条 59 | * rotate 围绕线条中心旋转 60 | */ 61 | function drawLine(ctx, x = 0, y = 0, width = 10, height = 1, bgColor = "#fff", rotate = 0) { 62 | 63 | ctx.translate(x + width / 2, y + height / 2); 64 | ctx.rotate((rotate) * Math.PI / 180); 65 | 66 | ctx.fillStyle = bgColor; 67 | ctx.fillRect(width / 2 * -1, height / 2 * -1, width, height); 68 | 69 | ctx.rotate((360 - rotate) * Math.PI / 180); 70 | ctx.translate((x + width / 2) * -1, (y + height / 2) * -1); 71 | 72 | } 73 | 74 | 75 | /** 76 | * 生成从minNum到maxNum的随机数 77 | */ 78 | function randomNum(minNum, maxNum) { 79 | switch (arguments.length) { 80 | case 1: 81 | return parseInt(Math.random() * minNum + 1, 10); 82 | break; 83 | case 2: 84 | return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10); 85 | break; 86 | default: 87 | return 0; 88 | break; 89 | } 90 | } 91 | 92 | 93 | /** 94 | * 验证码图片绘制 95 | */ 96 | async function drawCapImage(initImg, outImg, rotationAngle, canvasSize = 480, randomPoint = 200, randomLine = 50, randomBlock = 3) { 97 | return new Promise((yes, err) => { 98 | try { 99 | loadImage(initImg).then((image) => { 100 | const canvas = createCanvas(canvasSize, canvasSize); //创建画板 101 | const ctx = canvas.getContext('2d'); 102 | let option = { //图片写入参数 103 | h: canvasSize, //高度 104 | w: canvasSize //宽度 105 | } 106 | if (image.height > image.width) { 107 | //高大于宽,竖屏照片 108 | //设置高等于300,宽自动变化 109 | let c = Math.ceil(canvasSize / image.width * 100) / 100; 110 | option.h = Math.ceil(c * image.height); 111 | option.w = canvasSize; 112 | } else if (image.height < image.width) { 113 | let c = Math.ceil(canvasSize / image.height * 100) / 100; 114 | option.h = canvasSize; 115 | option.w = Math.ceil(c * image.width); 116 | } 117 | ctx.translate((canvasSize / 2), (canvasSize / 2)); 118 | ctx.rotate(rotationAngle * Math.PI / 180); 119 | ctx.arc(0, 0, (canvasSize / 2), 0, 2 * Math.PI); //限制在直径为150的圆内画图 120 | ctx.clip(); 121 | ctx.translate(-(canvasSize / 2), -(canvasSize / 2)); 122 | ctx.drawImage(image, 0, 0, option.w, option.h); 123 | ctx.translate((canvasSize / 2), (canvasSize / 2)); 124 | ctx.rotate((360 - rotationAngle) * Math.PI / 180); 125 | ctx.translate(-(canvasSize / 2), -(canvasSize / 2)); 126 | /** 127 | * 生成随机颜色点 128 | */ 129 | for (let index = 0; index < randomPoint; index++) { 130 | drawLine(ctx, 131 | randomNum(0, canvasSize), 132 | randomNum(0, canvasSize), 133 | randomNum(0, 4), 134 | randomNum(0, 4), 135 | "#" + (Array(6).join(0) + randomNum(0, 999999)).slice(-6), 136 | randomNum(0, 360) 137 | ) 138 | } 139 | 140 | /** 141 | * 随机生成线条 142 | */ 143 | for (let index = 0; index < randomLine; index++) { 144 | drawLine(ctx, 145 | randomNum(0, canvasSize), 146 | randomNum(0, canvasSize), 147 | randomNum(0, 200), 148 | randomNum(0, 2), 149 | "#" + (Array(6).join(0) + randomNum(0, 999999)).slice(-6), 150 | randomNum(0, 360) 151 | ) 152 | } 153 | 154 | /** 155 | * 随机生成大矩形 156 | */ 157 | for (let index = 0; index < randomBlock; index++) { 158 | drawLine(ctx, 159 | randomNum(0, canvasSize), 160 | randomNum(0, canvasSize), 161 | randomNum(0, 300), 162 | randomNum(0, 40), 163 | "#" + (Array(6).join(0) + randomNum(0, 999999)).slice(-6), 164 | randomNum(0, 360) 165 | ) 166 | } 167 | canvas.createPNGStream().pipe(fs.createWriteStream(outImg)) 168 | yes() 169 | }) 170 | } catch (error) { 171 | err(error) 172 | } 173 | }) 174 | } -------------------------------------------------------------------------------- /nodejs/captcha/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gd", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "run": "node index.js D:\\node\\gd\\ph.jpg D:\\node\\gd\\aaa.png" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "canvas": "^2.6.1" 13 | }, 14 | "devDependencies": { 15 | "@babel/core": "^7.10.3", 16 | "@babel/preset-env": "^7.10.3", 17 | "@babel/register": "^7.10.3", 18 | "ts-node": "^8.10.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /nodejs/captcha/temp/none.txt: -------------------------------------------------------------------------------- 1 | 空 -------------------------------------------------------------------------------- /preview/css/app.f68f6c8e.css: -------------------------------------------------------------------------------- 1 | .zIndex99999{z-index:99999} -------------------------------------------------------------------------------- /preview/css/chunk-13a41c84.bcd4fb12.css: -------------------------------------------------------------------------------- 1 | .dialog[data-v-42672cf8]{height:100vh;width:100vw;background:rgba(0,0,0,.4);display:flex;flex-direction:column;align-items:center;justify-content:center;position:absolute;top:0;left:0}.dialog .captche[data-v-42672cf8]{position:relative;width:320px;border-radius:20px;background-color:#fff;display:flex;flex-direction:column;align-items:center}.dialog .captche .subhead[data-v-42672cf8]{color:#8799a3;font-size:14px;margin-top:20px}.dialog .captche .tips[data-v-42672cf8]{font-size:17px;color:#333;margin-top:15px}.dialog .captche .image[data-v-42672cf8]{position:relative;height:170px;width:170px;margin-top:15px}.dialog .captche .image .src[data-v-42672cf8]{background-position:50%;background-size:cover;background-repeat:no-repeat;height:170px;width:170px;border-radius:50%}.dialog .captche .image .icon[data-v-42672cf8]{position:absolute;top:0;left:0;height:100%;width:100%;display:flex;align-items:center;justify-content:center}.dialog .captche .image .icon .loading[data-v-42672cf8]{color:#409eff;font-size:50px}.dialog .captche .image .icon .error[data-v-42672cf8]{color:#fff;font-size:80px}.dialog .captche .image .icon .verticalArrow[data-v-42672cf8]{position:absolute;height:100%;border-left:1px dashed #fff;margin-left:1px;width:1px}.dialog .captche .image .icon .transverseArrow[data-v-42672cf8]{position:absolute;height:1px;width:100%;border-top:1px dashed #fff;margin-top:1px;overflow:visible}.dialog .captche .capFoot[data-v-42672cf8]{height:50px;width:auto;display:flex;flex-direction:row;justify-content:center;margin-top:15px}.dialog .captche .capFoot .slider[data-v-42672cf8]{width:280px;height:50px;background-color:#f5f5f5;border-radius:500px;overflow:hidden;position:relative;border:1px solid rgba(0,0,0,.1)}.dialog .captche .capFoot .slider .sliderItem[data-v-42672cf8]{height:46px;width:60px;border-radius:500px;background-color:#fff;position:absolute;left:0;top:2px;cursor:pointer;display:flex;justify-content:center;align-items:center;box-shadow:0 21px 52px 0 rgba(82,82,82,.2)}.dialog .captche .capFoot .slider .sliderItem .i-loading i[data-v-42672cf8]{color:#8799a3;font-size:25px}.dialog .captche .capFoot .slider .sliderItem .i-drag[data-v-42672cf8]{background-position:50%;background-size:cover;background-repeat:no-repeat;height:28px;width:28px}.dialog .captche .copyright[data-v-42672cf8]{margin:15px;font-size:12px;color:#aaa}.dialog .captche .copyright a[data-v-42672cf8]{color:#409eff;margin-left:5px;margin-right:5px}.dialog .captche .close[data-v-42672cf8]{color:#fff;position:absolute;bottom:-60px;width:100%;text-align:center}.dialog .captche .close i[data-v-42672cf8]{cursor:pointer;font-size:26px}.shake[data-v-42672cf8]{-webkit-animation:shake-data-v-42672cf8 .8s ease-in-out;animation:shake-data-v-42672cf8 .8s ease-in-out}@-webkit-keyframes shake-data-v-42672cf8{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,70%{transform:translate3d(-3px,0,0)}40%,60%{transform:translate3d(3px,0,0)}50%{transform:translate3d(-3px,0,0)}}@keyframes shake-data-v-42672cf8{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,70%{transform:translate3d(-3px,0,0)}40%,60%{transform:translate3d(3px,0,0)}50%{transform:translate3d(-3px,0,0)}} -------------------------------------------------------------------------------- /preview/css/chunk-aef2b596.bd6a6533.css: -------------------------------------------------------------------------------- 1 | .dialog[data-v-17397f74]{height:100vh;width:100vw;background:rgba(0,0,0,.4);display:flex;flex-direction:column;align-items:center;justify-content:center;position:absolute;top:0;left:0}.dialog .captche[data-v-17397f74]{position:relative;width:320px;border-radius:20px;background-color:#fff;display:flex;flex-direction:column;align-items:center}.dialog .captche .subhead[data-v-17397f74]{color:#8799a3;font-size:14px;margin-top:20px}.dialog .captche .tips[data-v-17397f74]{font-size:17px;color:#333;margin-top:15px}.dialog .captche .image[data-v-17397f74]{position:relative;height:170px;width:170px;margin-top:15px}.dialog .captche .image .src[data-v-17397f74]{background-position:50%;background-size:cover;background-repeat:no-repeat;height:170px;width:170px;border-radius:50%}.dialog .captche .image .icon[data-v-17397f74]{position:absolute;top:0;left:0;height:100%;width:100%;display:flex;align-items:center;justify-content:center}.dialog .captche .image .icon .loading[data-v-17397f74]{color:#409eff;font-size:50px}.dialog .captche .image .icon .error[data-v-17397f74]{color:#fff;font-size:80px}.dialog .captche .image .icon .verticalArrow[data-v-17397f74]{position:absolute;height:100%;border-left:1px dashed #fff;margin-left:1px;width:1px}.dialog .captche .image .icon .transverseArrow[data-v-17397f74]{position:absolute;height:1px;width:100%;border-top:1px dashed #fff;margin-top:1px;overflow:visible}.dialog .captche .capFoot[data-v-17397f74]{height:50px;width:auto;display:flex;flex-direction:row;justify-content:center;margin-top:15px}.dialog .captche .capFoot .slider[data-v-17397f74]{width:280px;height:50px;background-color:#f5f5f5;border-radius:500px;overflow:hidden;position:relative;border:1px solid rgba(0,0,0,.1)}.dialog .captche .capFoot .slider .sliderItem[data-v-17397f74]{height:46px;width:60px;border-radius:500px;background-color:#fff;position:absolute;left:0;top:2px;cursor:pointer;display:flex;justify-content:center;align-items:center;box-shadow:0 21px 52px 0 rgba(82,82,82,.2)}.dialog .captche .capFoot .slider .sliderItem .i-loading i[data-v-17397f74]{color:#8799a3;font-size:25px}.dialog .captche .capFoot .slider .sliderItem .i-drag[data-v-17397f74]{background-position:50%;background-size:cover;background-repeat:no-repeat;height:28px;width:28px}.dialog .captche .copyright[data-v-17397f74]{margin:15px;font-size:12px;color:#aaa}.dialog .captche .copyright a[data-v-17397f74]{color:#409eff;margin-left:5px;margin-right:5px}.dialog .captche .close[data-v-17397f74]{color:#fff;position:absolute;bottom:-60px;width:100%;text-align:center}.dialog .captche .close i[data-v-17397f74]{cursor:pointer;font-size:26px}.shake[data-v-17397f74]{-webkit-animation:shake-data-v-17397f74 .8s ease-in-out;animation:shake-data-v-17397f74 .8s ease-in-out}@-webkit-keyframes shake-data-v-17397f74{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,70%{transform:translate3d(-3px,0,0)}40%,60%{transform:translate3d(3px,0,0)}50%{transform:translate3d(-3px,0,0)}}@keyframes shake-data-v-17397f74{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,70%{transform:translate3d(-3px,0,0)}40%,60%{transform:translate3d(3px,0,0)}50%{transform:translate3d(-3px,0,0)}} -------------------------------------------------------------------------------- /preview/fonts/element-icons.535877f5.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1615958039/rotateCaptcha/9b320d7195dea8b1a0ab062cf74aa5d12fc03e25/preview/fonts/element-icons.535877f5.woff -------------------------------------------------------------------------------- /preview/fonts/element-icons.732389de.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1615958039/rotateCaptcha/9b320d7195dea8b1a0ab062cf74aa5d12fc03e25/preview/fonts/element-icons.732389de.ttf -------------------------------------------------------------------------------- /preview/icon/png/drag_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1615958039/rotateCaptcha/9b320d7195dea8b1a0ab062cf74aa5d12fc03e25/preview/icon/png/drag_black.png -------------------------------------------------------------------------------- /preview/icon/png/drag_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1615958039/rotateCaptcha/9b320d7195dea8b1a0ab062cf74aa5d12fc03e25/preview/icon/png/drag_white.png -------------------------------------------------------------------------------- /preview/js/app.537514ce.js: -------------------------------------------------------------------------------- 1 | (function(e){function t(t){for(var r,o,i=t[0],s=t[1],u=t[2],l=0,d=[];l2&&void 0!==arguments[2]?arguments[2]:"error";e.$message({showClose:!0,message:t,type:n,customClass:"zIndex99999"})}var f=s.a.create(l);f.interceptors.request.use(function(){var e=Object(c["a"])(regeneratorRuntime.mark((function e(t){var n,r,i,s,l,d,f,p,g;return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:if(g=function(e){var t="";return Object.keys(e).map((function(n){t+="&".concat(n,"=").concat(e[n])})),t},p=function(e){var t=e.split("?");if("string"==typeof t[1]){t=t[1].split("&");var n={};for(var r in t){var o=t[r].split("=");n[o[0]]=o[1]}return n}return{}},f=function(e){var t={};return Object.keys(e).sort().map((function(n){t[n]=e[n]})),t},n=window.$vue,!t.configCheckSuccess){e.next=6;break}return e.abrupt("return",t);case 6:if(localStorage.getItem("token")||1==t.getToken){e.next=11;break}return localStorage.setItem("token",""),r=function(){var e=Object(c["a"])(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return console.log("获取Token"),e.next=3,n.$axios({url:"/public/getToken.json",datas:{type:"b"},getToken:!0}).then((function(e){localStorage.setItem("token",e.token)})).catch((function(e){console.log("getToken -> ",e)}));case 3:case"end":return e.stop()}}),e)})));return function(){return e.apply(this,arguments)}}(),e.next=11,r();case 11:if(localStorage.getItem("token")?t.headers["Token"]=localStorage.getItem("token"):console.log("未携带token"),!t["doReturn"]){e.next=14;break}return e.abrupt("return",t);case 14:try{if(t.url.indexOf("?")+1==t.url.length&&void 0!=t.datas&&""!=t.datas){for(s in i=0,t.datas)t.url+=(0==i++?"":"&")+s+"="+t.datas[s];t.datas=void 0}}catch(h){}try{try{void 0==t.method?void 0==t.data&&void 0==t.datas?t.method="GET":t.method="POST":"get"==t.method&&void 0!=t.datas&&(t.method="POST")}catch(h){console.log("请求前拦截器 -> 自动判断method出错:",h)}try{if(void 0!=t.datas&&void 0==t.data){for(l in d=new URLSearchParams,t.datas)d.append(l,t.datas[l]);t.data=d}}catch(h){console.log("拦截器请求前 -> datas参数传入 报错:",h)}t.headers["Content-Type"]="application/x-www-form-urlencoded"}catch(h){console.log("axios请求前 报错 -> "+h)}try{void 0!=t.jsonDatas&&(t.method="POST",t.headers["Content-Type"]="application/json",t.data=t.jsonDatas,t.withCredentials=!0,t.dataType="json")}catch(h){console.log("axios请求前 报错 -> "+h)}return t.showLoading&&(t.$loading=n.$loading({lock:!0,text:1==t.showLoading?"加载中,请稍等...":t.showLoading,spinner:"el-icon-loading",background:"rgba(0, 0, 0, 0.1)"})),t.headers.Sign=n.$md5(g(f(Object(a["a"])(Object(a["a"])(Object(a["a"])({},"object"==Object(o["a"])(t.datas)?t.datas:{}),p(t.url)),{},{appKey:u.appKey})))),t.configCheckSuccess=!0,e.abrupt("return",t);case 21:case"end":return e.stop()}}),e)})));return function(t){return e.apply(this,arguments)}}(),(function(e){return Promise.reject(e)})),f.interceptors.response.use((function(e){return new Promise((function(t,n){var r=window.$vue;try{e.config.$loading&&(e.config.$loading.close(),delete e.config.$loading)}catch(a){}try{if("object"==Object(o["a"])(e.data))switch(e.data.code=Number(e.data.code),e.data.code){case-2:break;case-1:localStorage.setItem("token",""),setTimeout((function(){r.$axios({url:"/public/getToken.json",datas:{type:"a"},getToken:!0}).then((function(o){localStorage.setItem("token",o.token),e.config.headers.Token=o.token,r.$axios(e.config).then(t).catch(n)})).catch((function(e){console.log("axios响应拦截器 - 获取token报错",e),n("获取token失败!")}))}),500);break;case 0:e.config.noTips||d(r,e.data.message),e.config.returnErrorData?n(e.data):n(e.data.message);break;case 1:t(e.data);break;default:break}else{try{e.config.getToken}catch(a){console.log(e),d(r,"服务器响应失败!请稍后重试")}n("拦截器 报错 |--\x3e code "+e.data.code)}}catch(a){d(r,"当前页面出现错误 -> "+a),console.log("(axios) 拦截器出错 -> "+a),n("拦截器 报错 |--\x3e error "+a)}}))}),(function(e){return Promise.reject(e)})),Plugin.install=function(e,t){e.axios=f,window.axios=f,Object.defineProperties(e.prototype,{axios:{get:function(){return f}},$axios:{get:function(){return f}}})},r["default"].use(Plugin);Plugin;var p=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{attrs:{id:"app"}},[n("router-view")],1)},g=[],h={},m=h,b=(n("5c0b"),n("2877")),v=Object(b["a"])(m,p,g,!1,null,null,null),y=v.exports,k=n("8c4f");r["default"].use(k["a"]);var w=[{path:"/",name:"index",component:function(){return n.e("chunk-aef2b596").then(n.bind(null,"09a0"))}}],j=new k["a"]({routes:w}),x=j,O=n("2f62");r["default"].use(O["a"]);var T=new O["a"].Store({state:{},mutations:{},actions:{},modules:{}}),S=n("5c96"),P=n.n(S);n("0fae");r["default"].use(P.a);var $=n("8237"),C=n.n($),_=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:2e3,r=window.$vue;if("object"==Object(o["a"])(e)&&(e=JSON.stringify(e)),""==t)return r.$message({showClose:!0,message:e,duration:n,type:"success"}),!0;switch(t){case"1":case"yes":case 1:t="success";break;case"0":case"error":case 0:t="error";default:break}return r.$message({showClose:!0,message:e,type:t,duration:n}),!0};r["default"].prototype.$md5=C.a,r["default"].config.productionTip=!1,r["default"].prototype.tw=_,window.$vue=new r["default"]({router:x,store:T,render:function(e){return e(y)}}).$mount("#app")},"5c0b":function(e,t,n){"use strict";n("9c0c")},"9c0c":function(e,t,n){}}); -------------------------------------------------------------------------------- /preview/js/app.fdc052e7.js: -------------------------------------------------------------------------------- 1 | (function(e){function t(t){for(var r,o,i=t[0],s=t[1],u=t[2],l=0,d=[];l2&&void 0!==arguments[2]?arguments[2]:"error";e.$message({showClose:!0,message:t,type:n,customClass:"zIndex99999"})}var f=s.a.create(l);f.interceptors.request.use(function(){var e=Object(c["a"])(regeneratorRuntime.mark((function e(t){var n,r,i,s,l,d,f,p,g;return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:if(g=function(e){var t="";return Object.keys(e).map((function(n){t+="&".concat(n,"=").concat(e[n])})),t},p=function(e){var t=e.split("?");if("string"==typeof t[1]){t=t[1].split("&");var n={};for(var r in t){var o=t[r].split("=");n[o[0]]=o[1]}return n}return{}},f=function(e){var t={};return Object.keys(e).sort().map((function(n){t[n]=e[n]})),t},n=window.$vue,!t.configCheckSuccess){e.next=6;break}return e.abrupt("return",t);case 6:if(localStorage.getItem("token")||1==t.getToken){e.next=11;break}return localStorage.setItem("token",""),r=function(){var e=Object(c["a"])(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return console.log("获取Token"),e.next=3,n.$axios({url:"/public/getToken.json",datas:{type:"b"},getToken:!0}).then((function(e){localStorage.setItem("token",e.token)})).catch((function(e){console.log("getToken -> ",e)}));case 3:case"end":return e.stop()}}),e)})));return function(){return e.apply(this,arguments)}}(),e.next=11,r();case 11:if(localStorage.getItem("token")?t.headers["Token"]=localStorage.getItem("token"):console.log("未携带token"),!t["doReturn"]){e.next=14;break}return e.abrupt("return",t);case 14:try{if(t.url.indexOf("?")+1==t.url.length&&void 0!=t.datas&&""!=t.datas){for(s in i=0,t.datas)t.url+=(0==i++?"":"&")+s+"="+t.datas[s];t.datas=void 0}}catch(h){}try{try{void 0==t.method?void 0==t.data&&void 0==t.datas?t.method="GET":t.method="POST":"get"==t.method&&void 0!=t.datas&&(t.method="POST")}catch(h){console.log("请求前拦截器 -> 自动判断method出错:",h)}try{if(void 0!=t.datas&&void 0==t.data){for(l in d=new URLSearchParams,t.datas)d.append(l,t.datas[l]);t.data=d}}catch(h){console.log("拦截器请求前 -> datas参数传入 报错:",h)}t.headers["Content-Type"]="application/x-www-form-urlencoded"}catch(h){console.log("axios请求前 报错 -> "+h)}try{void 0!=t.jsonDatas&&(t.method="POST",t.headers["Content-Type"]="application/json",t.data=t.jsonDatas,t.withCredentials=!0,t.dataType="json")}catch(h){console.log("axios请求前 报错 -> "+h)}return t.showLoading&&(t.$loading=n.$loading({lock:!0,text:1==t.showLoading?"加载中,请稍等...":t.showLoading,spinner:"el-icon-loading",background:"rgba(0, 0, 0, 0.1)"})),t.headers.Sign=n.$md5(g(f(Object(a["a"])(Object(a["a"])(Object(a["a"])({},"object"==Object(o["a"])(t.datas)?t.datas:{}),p(t.url)),{},{appKey:u.appKey})))),t.configCheckSuccess=!0,e.abrupt("return",t);case 21:case"end":return e.stop()}}),e)})));return function(t){return e.apply(this,arguments)}}(),(function(e){return Promise.reject(e)})),f.interceptors.response.use((function(e){return new Promise((function(t,n){var r=window.$vue;try{e.config.$loading&&(e.config.$loading.close(),delete e.config.$loading)}catch(a){}try{if("object"==Object(o["a"])(e.data))switch(e.data.code=Number(e.data.code),e.data.code){case-2:break;case-1:localStorage.setItem("token",""),setTimeout((function(){r.$axios({url:"/public/getToken.json",datas:{type:"a"},getToken:!0}).then((function(o){localStorage.setItem("token",o.token),e.config.headers.Token=o.token,r.$axios(e.config).then(t).catch(n)})).catch((function(e){console.log("axios响应拦截器 - 获取token报错",e),n("获取token失败!")}))}),500);break;case 0:e.config.noTips||d(r,e.data.message),e.config.returnErrorData?n(e.data):n(e.data.message);break;case 1:t(e.data);break;default:break}else{try{e.config.getToken}catch(a){console.log(e),d(r,"服务器响应失败!请稍后重试")}n("拦截器 报错 |--\x3e code "+e.data.code)}}catch(a){d(r,"当前页面出现错误 -> "+a),console.log("(axios) 拦截器出错 -> "+a),n("拦截器 报错 |--\x3e error "+a)}}))}),(function(e){return Promise.reject(e)})),Plugin.install=function(e,t){e.axios=f,window.axios=f,Object.defineProperties(e.prototype,{axios:{get:function(){return f}},$axios:{get:function(){return f}}})},r["default"].use(Plugin);Plugin;var p=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{attrs:{id:"app"}},[n("router-view")],1)},g=[],h={},m=h,v=(n("5c0b"),n("2877")),b=Object(v["a"])(m,p,g,!1,null,null,null),y=b.exports,k=n("8c4f");r["default"].use(k["a"]);var w=[{path:"/",name:"index",component:function(){return n.e("chunk-13a41c84").then(n.bind(null,"09a0"))}}],j=new k["a"]({routes:w}),x=j,O=n("2f62");r["default"].use(O["a"]);var T=new O["a"].Store({state:{},mutations:{},actions:{},modules:{}}),S=n("5c96"),P=n.n(S);n("0fae");r["default"].use(P.a);var $=n("8237"),C=n.n($),_=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:2e3,r=window.$vue;if("object"==Object(o["a"])(e)&&(e=JSON.stringify(e)),""==t)return r.$message({showClose:!0,message:e,duration:n,type:"success"}),!0;switch(t){case"1":case"yes":case 1:t="success";break;case"0":case"error":case 0:t="error";default:break}return r.$message({showClose:!0,message:e,type:t,duration:n}),!0};r["default"].prototype.$md5=C.a,r["default"].config.productionTip=!1,r["default"].prototype.tw=_,window.$vue=new r["default"]({router:x,store:T,render:function(e){return e(y)}}).$mount("#app")},"5c0b":function(e,t,n){"use strict";n("9c0c")},"9c0c":function(e,t,n){}}); -------------------------------------------------------------------------------- /preview/js/chunk-13a41c84.f4d94071.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-13a41c84"],{"04c9":function(a,t,e){"use strict";e("7390")},"09a0":function(a,t,e){"use strict";e.r(t);var r=function(){var a=this,t=a.$createElement,e=a._self._c||t;return e("div",[e("h2",[a._v("旋转验证码:")]),e("p",[a._v("(vue-cli版本)")]),e("el-button",{attrs:{icon:"el-icon-picture-outline-round",type:"primary"},on:{click:a.showCaptcha}},[a._v("打开验证码")]),e("el-button",{attrs:{icon:"el-icon-delete"},on:{click:a.cleanLog}},[a._v("清空验证记录")]),e("hr"),a._v(" 手机端测试页UNIAPP: "),e("a",{attrs:{href:"./uniapp.html"}},[a._v("./uniapp.html")]),e("hr"),a._v(" 本项目总开源地址:"),e("a",{attrs:{href:"https://github.com/1615958039/rotateCaptcha"}},[a._v("https://github.com/1615958039/rotateCaptcha")]),e("hr"),e("captchaDialog",{ref:"captchaDialog"})],1)},i=[],c=function(){var a=this,t=a.$createElement,e=a._self._c||t;return a.show?e("div",{staticClass:"dialog",style:{zIndex:a.zIndex}},[e("div",{staticClass:"captche"},[e("div",{staticClass:"subhead"},[a._v("人机验证")]),e("div",{staticClass:"tips"},[a._v("滑动滑块,使图片角度为正")]),e("div",{staticClass:"image"},[""!=a.captchaSrc?e("div",{staticClass:"src",style:{backgroundImage:"url("+a.captchaSrc+")",transform:"rotate("+a.imageTransform+"deg)"}}):a._e(),a.pageLoading||a.captchaSrcLoading?e("div",{staticClass:"icon"},[e("i",{staticClass:"loading el-icon-loading"})]):a._e(),a.captchaError?e("div",{staticClass:"icon"},[e("i",{staticClass:"error el-icon-close"})]):a._e(),a.isDrag?e("div",{staticClass:"icon"},[e("div",{staticClass:"verticalArrow"}),e("div",{staticClass:"transverseArrow"})]):a._e()]),e("div",{staticClass:"capFoot"},[e("div",{ref:"slider",staticClass:"slider"},[e("div",{ref:"sliderItem",staticClass:"sliderItem clickActive-gray",class:a.captchaError?"shake":"",style:{left:a.sliderItemLeft+"px",backgroundColor:a.captchaError?"red":a.isDrag?"#409EFF":"#fff"},on:{touchstart:a.dragStart,touchmove:function(t){return t.preventDefault(),a.dragMove.apply(null,arguments)},touchend:a.dragEnd,mousedown:a.dragStart,mousemove:function(t){return t.preventDefault(),a.dragMove.apply(null,arguments)},mouseup:a.dragEnd,dragstart:a.dragStart,dragend:a.dragEnd,dragover:function(a){a.stopPropagation()},drop:a.dragMove}},[a.dragLoading||a.pageLoading||a.captchaSrcLoading?e("div",{staticClass:"i-loading"},[e("i",{staticClass:"el-icon-loading"})]):e("div",{staticClass:"i-drag",style:{backgroundImage:"url("+a.dragIcon+")"}})])])]),a._m(0),e("div",{staticClass:"close",on:{click:function(t){return t.stopPropagation(),a.close.apply(null,arguments)}}},[e("i",{staticClass:"el-icon-circle-close"})])])]):a._e()},n=[function(){var a=this,t=a.$createElement,e=a._self._c||t;return e("div",{staticClass:"copyright"},[a._v(" 由"),e("a",[a._v("犭申")]),a._v("提供技术支持 ")])}],s=(e("a9e3"),e("d3b7"),null),o=null,d={props:{zIndex:{type:Number,default:999999999999}},data:function(){return{show:!1,captchaSrc:"",isDrag:!1,dragStartTime:0,dragUseTime:0,mouseTrackList:[],sliderItemInitX:0,sliderItemLeftMax:0,sliderItemLeft:0,captchaSrcLoading:!1,pageLoading:!1,dragLoading:!1,captchaError:!1}},computed:{imageTransform:function(){var a=this;return 0==a.sliderItemLeft?0:Math.ceil(360/a.sliderItemLeftMax*a.sliderItemLeft*100)/100},dragIcon:function(){var a=this,t="/preview/icon/png/drag_white.png",e="/preview/icon/png/drag_black.png";return a.captchaError||a.isDrag?t:e}},methods:{init:function(){var a=this;return new Promise((function(t,e){s=t,o=e,a.captchaSrc=!1,a.captchaError=!1,a.isDrag=!1,a.captchaSrcLoading=!1,a.pageLoading=!1,a.dragLoading=!1,a.show=!0,0==a.sliderItemLeftMax?(a.pageLoading=!0,setTimeout((function(){a.$nextTick((function(){var t=a.$refs.slider.getBoundingClientRect(),e=t.width,r=a.$refs.sliderItem.getBoundingClientRect();a.sliderItemInitX=r.left,a.sliderItemLeftMax=e-r.width,a.pageLoading=!1,a.getCaptchaSrc()}))}),500)):a.getCaptchaSrc()}))},dragStart:function(a){var t=this;return!t.dragLoading&&(!t.pageLoading&&(!t.captchaSrcLoading&&(t.mouseTrackList=[],t.dragStartTime=(new Date).getTime(),t.isDrag=!0,void t.addMouseTrack(a))))},dragMove:function(a){var t=this;if(t.dragLoading)return!1;if(t.pageLoading)return!1;if(t.captchaSrcLoading)return!1;if(!t.isDrag)return!1;var e=(a.changedTouches?a.changedTouches[0].clientX:a.clientX)-t.sliderItemInitX-30;e<0&&(e=0),e>t.sliderItemLeftMax&&(e=t.sliderItemLeftMax),t.sliderItemLeft=e,t.addMouseTrack(a)},dragEnd:function(a){var t=this;return!t.dragLoading&&(!t.pageLoading&&(!t.captchaSrcLoading&&(!!t.isDrag&&(t.addMouseTrack(a),t.isDrag=!1,t.dragLoading=!0,t.dragUseTime=(new Date).getTime()-t.dragStartTime,void t.checkCaptche()))))},getCaptchaSrc:function(){var a=this;a.captchaSrcLoading=!0,a.sliderItemLeft=0,a.captchaSrc="",a.$axios({url:"/public/captcha/getCaptcha.json"}).then((function(t){t.src&&(a.dragLoading=!1,a.captchaSrc="data:image/png;base64,".concat(t.src)),a.captchaSrcLoading=!1})).catch((function(t){a.captchaSrcLoading=!1,a.tw(t.message),setTimeout((function(){a.close()}),1e3)}))},addMouseTrack:function(){var a=this,t=""==a.mouseTrackList?a.dragStartTime:a.mouseTrackList[a.mouseTrackList.length-1].t,e=(new Date).getTime();t+200<=e&&a.mouseTrackList.push({r:Math.ceil(a.imageTransform/360*1e4)/100,t:e})},checkCaptche:function(){var a=this;a.$axios({url:"/public/captcha/checkCaptcha.json",datas:{rotationAngle:a.imageTransform,mouseTrackList:JSON.stringify(a.mouseTrackList),dragUseTime:a.dragUseTime,dragStartTime:a.dragStartTime,key_1:(new Date).getTime()*Math.random(),key_2:(new Date).getTime()*Math.random(),key_3:(new Date).getTime()*Math.random(),hash:(new Date).getTime()*Math.random(),md5Key:(new Date).getTime()*Math.random()},noTips:!0,returnErrorData:!0}).then((function(t){a.show=!1,s()})).catch((function(t){a.dragLoading=!1,a.captchaError=!0,setTimeout((function(){a.captchaError=!1,t.getNewCaptcha?a.getCaptchaSrc():(a.captchaSrcLoading=!1,a.sliderItemLeft=0)}),1e3)}))},close:function(){this.show=!1,o()}}},g=d,l=(e("04c9"),e("2877")),u=Object(l["a"])(g,c,n,!1,null,"42672cf8",null),h=u.exports,p={components:{captchaDialog:h},methods:{showCaptcha:function(){var a=this;this.$refs.captchaDialog.init().then((function(){a.$message({message:"验证成功!",type:"success"})})).catch((function(){a.$message({message:"验证码弹窗关闭",type:"warning"})}))},cleanLog:function(){var a=this;a.$axios({url:"/demo/resetTryNum.json",showLoading:"清空中..."}).then((function(t){a.tw("清空成功!您可重新获取验证码")}))}}},m=p,f=Object(l["a"])(m,r,i,!1,null,"704b4d2c",null);t["default"]=f.exports},7390:function(a,t,e){}}]); -------------------------------------------------------------------------------- /preview/js/chunk-aef2b596.e736b097.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-aef2b596"],{"08b9":function(a,t,e){},"09a0":function(a,t,e){"use strict";e.r(t);var r=function(){var a=this,t=a.$createElement,e=a._self._c||t;return e("div",[e("h2",[a._v("旋转验证码:")]),e("p",[a._v("(vue-cli版本)")]),e("el-button",{attrs:{icon:"el-icon-picture-outline-round",type:"primary"},on:{click:a.showCaptcha}},[a._v("打开验证码")]),e("el-button",{attrs:{icon:"el-icon-delete"},on:{click:a.cleanLog}},[a._v("清空验证记录")]),e("hr"),a._v(" 手机端测试页UNIAPP: "),e("a",{attrs:{href:"./uniapp.html"}},[a._v("./uniapp.html")]),e("hr"),a._v(" 本项目总开源地址:"),e("a",{attrs:{href:"https://github.com/1615958039/rotateCaptcha"}},[a._v("https://github.com/1615958039/rotateCaptcha")]),e("hr"),e("captchaDialog",{ref:"captchaDialog"})],1)},i=[],c=function(){var a=this,t=a.$createElement,e=a._self._c||t;return a.show?e("div",{staticClass:"dialog",style:{zIndex:a.zIndex}},[e("div",{staticClass:"captche"},[e("div",{staticClass:"subhead"},[a._v("人机验证")]),e("div",{staticClass:"tips"},[a._v("滑动滑块,使图片角度为正")]),e("div",{staticClass:"image"},[""!=a.captchaSrc?e("div",{staticClass:"src",style:{backgroundImage:"url("+a.captchaSrc+")",transform:"rotate("+a.imageTransform+"deg)"}}):a._e(),a.pageLoading||a.captchaSrcLoading?e("div",{staticClass:"icon"},[e("i",{staticClass:"loading el-icon-loading"})]):a._e(),a.captchaError?e("div",{staticClass:"icon"},[e("i",{staticClass:"error el-icon-close"})]):a._e(),a.isDrag?e("div",{staticClass:"icon"},[e("div",{staticClass:"verticalArrow"}),e("div",{staticClass:"transverseArrow"})]):a._e()]),e("div",{staticClass:"capFoot"},[e("div",{ref:"slider",staticClass:"slider"},[e("div",{ref:"sliderItem",staticClass:"sliderItem clickActive-gray",class:a.captchaError?"shake":"",style:{left:a.sliderItemLeft+"px",backgroundColor:a.captchaError?"red":a.isDrag?"#409EFF":"#fff"},on:{touchstart:a.dragStart,touchmove:function(t){return t.preventDefault(),a.dragMove.apply(null,arguments)},touchend:a.dragEnd,mousedown:a.dragStart,mousemove:function(t){return t.preventDefault(),a.dragMove.apply(null,arguments)},mouseup:a.dragEnd,dragstart:a.dragStart,dragend:a.dragEnd,dragover:function(a){a.stopPropagation()},drop:a.dragMove}},[a.dragLoading||a.pageLoading||a.captchaSrcLoading?e("div",{staticClass:"i-loading"},[e("i",{staticClass:"el-icon-loading"})]):e("div",{staticClass:"i-drag",style:{backgroundImage:"url("+a.dragIcon+")"}})])])]),a._m(0),e("div",{staticClass:"close",on:{click:function(t){return t.stopPropagation(),a.close.apply(null,arguments)}}},[e("i",{staticClass:"el-icon-circle-close"})])])]):a._e()},n=[function(){var a=this,t=a.$createElement,e=a._self._c||t;return e("div",{staticClass:"copyright"},[a._v(" 由"),e("a",[a._v("犭申")]),a._v("提供技术支持 ")])}],s=(e("a9e3"),e("d3b7"),null),o=null,d={props:{zIndex:{type:Number,default:999999999999}},data:function(){return{show:!1,captchaSrc:"",isDrag:!1,dragStartTime:0,dragUseTime:0,mouseTrackList:[],sliderItemInitX:0,sliderItemLeftMax:0,sliderItemLeft:0,captchaSrcLoading:!1,pageLoading:!1,dragLoading:!1,captchaError:!1}},computed:{imageTransform:function(){var a=this;return 0==a.sliderItemLeft?0:Math.ceil(360/a.sliderItemLeftMax*a.sliderItemLeft*100)/100},dragIcon:function(){var a=this,t="/icon/png/drag_white.png",e="/icon/png/drag_black.png";return a.captchaError||a.isDrag?t:e}},methods:{init:function(){var a=this;return new Promise((function(t,e){s=t,o=e,a.captchaSrc=!1,a.captchaError=!1,a.isDrag=!1,a.captchaSrcLoading=!1,a.pageLoading=!1,a.dragLoading=!1,a.show=!0,0==a.sliderItemLeftMax?(a.pageLoading=!0,setTimeout((function(){a.$nextTick((function(){var t=a.$refs.slider.getBoundingClientRect(),e=t.width,r=a.$refs.sliderItem.getBoundingClientRect();a.sliderItemInitX=r.left,a.sliderItemLeftMax=e-r.width,a.pageLoading=!1,a.getCaptchaSrc()}))}),500)):a.getCaptchaSrc()}))},dragStart:function(a){var t=this;return!t.dragLoading&&(!t.pageLoading&&(!t.captchaSrcLoading&&(t.mouseTrackList=[],t.dragStartTime=(new Date).getTime(),t.isDrag=!0,void t.addMouseTrack(a))))},dragMove:function(a){var t=this;if(t.dragLoading)return!1;if(t.pageLoading)return!1;if(t.captchaSrcLoading)return!1;if(!t.isDrag)return!1;var e=(a.changedTouches?a.changedTouches[0].clientX:a.clientX)-t.sliderItemInitX-30;e<0&&(e=0),e>t.sliderItemLeftMax&&(e=t.sliderItemLeftMax),t.sliderItemLeft=e,t.addMouseTrack(a)},dragEnd:function(a){var t=this;return!t.dragLoading&&(!t.pageLoading&&(!t.captchaSrcLoading&&(!!t.isDrag&&(t.addMouseTrack(a),t.isDrag=!1,t.dragLoading=!0,t.dragUseTime=(new Date).getTime()-t.dragStartTime,void t.checkCaptche()))))},getCaptchaSrc:function(){var a=this;a.captchaSrcLoading=!0,a.sliderItemLeft=0,a.captchaSrc="",a.$axios({url:"/public/captcha/getCaptcha.json"}).then((function(t){t.src&&(a.dragLoading=!1,a.captchaSrc="data:image/png;base64,".concat(t.src)),a.captchaSrcLoading=!1})).catch((function(t){a.captchaSrcLoading=!1,a.tw(t.message),setTimeout((function(){a.close()}),1e3)}))},addMouseTrack:function(){var a=this,t=""==a.mouseTrackList?a.dragStartTime:a.mouseTrackList[a.mouseTrackList.length-1].t,e=(new Date).getTime();t+200<=e&&a.mouseTrackList.push({r:Math.ceil(a.imageTransform/360*1e4)/100,t:e})},checkCaptche:function(){var a=this;a.$axios({url:"/public/captcha/checkCaptcha.json",datas:{rotationAngle:a.imageTransform,mouseTrackList:JSON.stringify(a.mouseTrackList),dragUseTime:a.dragUseTime,dragStartTime:a.dragStartTime,key_1:(new Date).getTime()*Math.random(),key_2:(new Date).getTime()*Math.random(),key_3:(new Date).getTime()*Math.random(),hash:(new Date).getTime()*Math.random(),md5Key:(new Date).getTime()*Math.random()},noTips:!0,returnErrorData:!0}).then((function(t){a.show=!1,s()})).catch((function(t){a.dragLoading=!1,a.captchaError=!0,setTimeout((function(){a.captchaError=!1,t.getNewCaptcha?a.getCaptchaSrc():(a.captchaSrcLoading=!1,a.sliderItemLeft=0)}),1e3)}))},close:function(){this.show=!1,o()}}},g=d,l=(e("36ac"),e("2877")),u=Object(l["a"])(g,c,n,!1,null,"17397f74",null),h=u.exports,p={components:{captchaDialog:h},methods:{showCaptcha:function(){var a=this;this.$refs.captchaDialog.init().then((function(){a.$message({message:"验证成功!",type:"success"})})).catch((function(){a.$message({message:"验证码弹窗关闭",type:"warning"})}))},cleanLog:function(){var a=this;a.$axios({url:"/demo/resetTryNum.json",showLoading:"清空中..."}).then((function(t){a.tw("清空成功!您可重新获取验证码")}))}}},m=p,f=Object(l["a"])(m,r,i,!1,null,"704b4d2c",null);t["default"]=f.exports},"36ac":function(a,t,e){"use strict";e("08b9")}}]); -------------------------------------------------------------------------------- /preview/static/css/animation.css: -------------------------------------------------------------------------------- 1 | /* 2 | Animation 微动画 3 | 基于ColorUI组建库的动画模块 by 文晓港 2019年3月26日19:52:28 4 | */ 5 | 6 | /* css 滤镜 控制黑白底色gif的 */ 7 | .gif-black{ 8 | mix-blend-mode: screen; 9 | } 10 | .gif-white{ 11 | mix-blend-mode: multiply; 12 | } 13 | 14 | 15 | /* Animation css */ 16 | [class*=animation-] { 17 | animation-duration: .5s; 18 | animation-timing-function: ease-out; 19 | animation-fill-mode: both 20 | } 21 | 22 | .animation-fade { 23 | animation-name: fade; 24 | animation-duration: .8s; 25 | animation-timing-function: linear 26 | } 27 | 28 | .animation-scale-up { 29 | animation-name: scale-up 30 | } 31 | 32 | .animation-scale-down { 33 | animation-name: scale-down 34 | } 35 | 36 | .animation-slide-top { 37 | animation-name: slide-top 38 | } 39 | 40 | .animation-slide-bottom { 41 | animation-name: slide-bottom 42 | } 43 | 44 | .animation-slide-left { 45 | animation-name: slide-left 46 | } 47 | 48 | .animation-slide-right { 49 | animation-name: slide-right 50 | } 51 | 52 | .animation-shake { 53 | animation-name: shake 54 | } 55 | 56 | .animation-reverse { 57 | animation-direction: reverse 58 | } 59 | 60 | @keyframes fade { 61 | 0% { 62 | opacity: 0 63 | } 64 | 65 | 100% { 66 | opacity: 1 67 | } 68 | } 69 | 70 | @keyframes scale-up { 71 | 0% { 72 | opacity: 0; 73 | transform: scale(.2) 74 | } 75 | 76 | 100% { 77 | opacity: 1; 78 | transform: scale(1) 79 | } 80 | } 81 | 82 | @keyframes scale-down { 83 | 0% { 84 | opacity: 0; 85 | transform: scale(1.8) 86 | } 87 | 88 | 100% { 89 | opacity: 1; 90 | transform: scale(1) 91 | } 92 | } 93 | 94 | @keyframes slide-top { 95 | 0% { 96 | opacity: 0; 97 | transform: translateY(-100%) 98 | } 99 | 100 | 100% { 101 | opacity: 1; 102 | transform: translateY(0) 103 | } 104 | } 105 | 106 | @keyframes slide-bottom { 107 | 0% { 108 | opacity: 0; 109 | transform: translateY(100%) 110 | } 111 | 112 | 100% { 113 | opacity: 1; 114 | transform: translateY(0) 115 | } 116 | } 117 | 118 | @keyframes shake { 119 | 120 | 0%, 121 | 100% { 122 | transform: translateX(0) 123 | } 124 | 125 | 10% { 126 | transform: translateX(-9px) 127 | } 128 | 129 | 20% { 130 | transform: translateX(8px) 131 | } 132 | 133 | 30% { 134 | transform: translateX(-7px) 135 | } 136 | 137 | 40% { 138 | transform: translateX(6px) 139 | } 140 | 141 | 50% { 142 | transform: translateX(-5px) 143 | } 144 | 145 | 60% { 146 | transform: translateX(4px) 147 | } 148 | 149 | 70% { 150 | transform: translateX(-3px) 151 | } 152 | 153 | 80% { 154 | transform: translateX(2px) 155 | } 156 | 157 | 90% { 158 | transform: translateX(-1px) 159 | } 160 | } 161 | 162 | @keyframes slide-left { 163 | 0% { 164 | opacity: 0; 165 | transform: translateX(-100%) 166 | } 167 | 168 | 100% { 169 | opacity: 1; 170 | transform: translateX(0) 171 | } 172 | } 173 | 174 | @keyframes slide-right { 175 | 0% { 176 | opacity: 0; 177 | transform: translateX(100%) 178 | } 179 | 180 | 100% { 181 | opacity: 1; 182 | transform: translateX(0) 183 | } 184 | } -------------------------------------------------------------------------------- /preview/static/css/colorUiIcon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1615958039/rotateCaptcha/9b320d7195dea8b1a0ab062cf74aa5d12fc03e25/preview/static/css/colorUiIcon.eot -------------------------------------------------------------------------------- /preview/static/css/colorUiIcon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1615958039/rotateCaptcha/9b320d7195dea8b1a0ab062cf74aa5d12fc03e25/preview/static/css/colorUiIcon.ttf -------------------------------------------------------------------------------- /preview/static/icon/drag_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1615958039/rotateCaptcha/9b320d7195dea8b1a0ab062cf74aa5d12fc03e25/preview/static/icon/drag_black.png -------------------------------------------------------------------------------- /preview/static/icon/drag_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1615958039/rotateCaptcha/9b320d7195dea8b1a0ab062cf74aa5d12fc03e25/preview/static/icon/drag_white.png -------------------------------------------------------------------------------- /preview/static/static/css/animation.css: -------------------------------------------------------------------------------- 1 | /* 2 | Animation 微动画 3 | 基于ColorUI组建库的动画模块 by 文晓港 2019年3月26日19:52:28 4 | */ 5 | 6 | /* css 滤镜 控制黑白底色gif的 */ 7 | .gif-black{ 8 | mix-blend-mode: screen; 9 | } 10 | .gif-white{ 11 | mix-blend-mode: multiply; 12 | } 13 | 14 | 15 | /* Animation css */ 16 | [class*=animation-] { 17 | animation-duration: .5s; 18 | animation-timing-function: ease-out; 19 | animation-fill-mode: both 20 | } 21 | 22 | .animation-fade { 23 | animation-name: fade; 24 | animation-duration: .8s; 25 | animation-timing-function: linear 26 | } 27 | 28 | .animation-scale-up { 29 | animation-name: scale-up 30 | } 31 | 32 | .animation-scale-down { 33 | animation-name: scale-down 34 | } 35 | 36 | .animation-slide-top { 37 | animation-name: slide-top 38 | } 39 | 40 | .animation-slide-bottom { 41 | animation-name: slide-bottom 42 | } 43 | 44 | .animation-slide-left { 45 | animation-name: slide-left 46 | } 47 | 48 | .animation-slide-right { 49 | animation-name: slide-right 50 | } 51 | 52 | .animation-shake { 53 | animation-name: shake 54 | } 55 | 56 | .animation-reverse { 57 | animation-direction: reverse 58 | } 59 | 60 | @keyframes fade { 61 | 0% { 62 | opacity: 0 63 | } 64 | 65 | 100% { 66 | opacity: 1 67 | } 68 | } 69 | 70 | @keyframes scale-up { 71 | 0% { 72 | opacity: 0; 73 | transform: scale(.2) 74 | } 75 | 76 | 100% { 77 | opacity: 1; 78 | transform: scale(1) 79 | } 80 | } 81 | 82 | @keyframes scale-down { 83 | 0% { 84 | opacity: 0; 85 | transform: scale(1.8) 86 | } 87 | 88 | 100% { 89 | opacity: 1; 90 | transform: scale(1) 91 | } 92 | } 93 | 94 | @keyframes slide-top { 95 | 0% { 96 | opacity: 0; 97 | transform: translateY(-100%) 98 | } 99 | 100 | 100% { 101 | opacity: 1; 102 | transform: translateY(0) 103 | } 104 | } 105 | 106 | @keyframes slide-bottom { 107 | 0% { 108 | opacity: 0; 109 | transform: translateY(100%) 110 | } 111 | 112 | 100% { 113 | opacity: 1; 114 | transform: translateY(0) 115 | } 116 | } 117 | 118 | @keyframes shake { 119 | 120 | 0%, 121 | 100% { 122 | transform: translateX(0) 123 | } 124 | 125 | 10% { 126 | transform: translateX(-9px) 127 | } 128 | 129 | 20% { 130 | transform: translateX(8px) 131 | } 132 | 133 | 30% { 134 | transform: translateX(-7px) 135 | } 136 | 137 | 40% { 138 | transform: translateX(6px) 139 | } 140 | 141 | 50% { 142 | transform: translateX(-5px) 143 | } 144 | 145 | 60% { 146 | transform: translateX(4px) 147 | } 148 | 149 | 70% { 150 | transform: translateX(-3px) 151 | } 152 | 153 | 80% { 154 | transform: translateX(2px) 155 | } 156 | 157 | 90% { 158 | transform: translateX(-1px) 159 | } 160 | } 161 | 162 | @keyframes slide-left { 163 | 0% { 164 | opacity: 0; 165 | transform: translateX(-100%) 166 | } 167 | 168 | 100% { 169 | opacity: 1; 170 | transform: translateX(0) 171 | } 172 | } 173 | 174 | @keyframes slide-right { 175 | 0% { 176 | opacity: 0; 177 | transform: translateX(100%) 178 | } 179 | 180 | 100% { 181 | opacity: 1; 182 | transform: translateX(0) 183 | } 184 | } -------------------------------------------------------------------------------- /preview/static/static/css/colorUiIcon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1615958039/rotateCaptcha/9b320d7195dea8b1a0ab062cf74aa5d12fc03e25/preview/static/static/css/colorUiIcon.eot -------------------------------------------------------------------------------- /preview/static/static/css/colorUiIcon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1615958039/rotateCaptcha/9b320d7195dea8b1a0ab062cf74aa5d12fc03e25/preview/static/static/css/colorUiIcon.ttf -------------------------------------------------------------------------------- /preview/static/static/icon/drag_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1615958039/rotateCaptcha/9b320d7195dea8b1a0ab062cf74aa5d12fc03e25/preview/static/static/icon/drag_black.png -------------------------------------------------------------------------------- /preview/static/static/icon/drag_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1615958039/rotateCaptcha/9b320d7195dea8b1a0ab062cf74aa5d12fc03e25/preview/static/static/icon/drag_white.png -------------------------------------------------------------------------------- /preview/uniapp.html: -------------------------------------------------------------------------------- 1 | 旋转验证码
-------------------------------------------------------------------------------- /preview/vuecli.html: -------------------------------------------------------------------------------- 1 | rotatecaptcha
-------------------------------------------------------------------------------- /public/captcha/checkCaptcha.php: -------------------------------------------------------------------------------- 1 | 360)code("旋转角度获取失败!请重试"); 16 | $rotationAngle = (int)round(360 - $_REQUEST['rotationAngle']); //旋转角度,四舍五入 17 | 18 | $captchaLog = easySqlSelect("*","captchaLog",[ 19 | "id" => $session['captchaLogId'], 20 | ]); 21 | if(!$captchaLog)checkError("验证失败!_code_1"); 22 | if($captchaLog['tryNum'] >= $captchaConfig['oneCapErrNum'])checkError("验证次数超出限制!请刷新重试"); //验证次数超出限制 23 | if($captchaLog['addTime']+$captchaConfig['checkTimeOut'] < time())checkError("效验超时!请刷新重试"); //验证码超时时间效验 24 | 25 | $captchaCheckOutTime = $captchaLog['addTime'] + $captchaConfig['checkTimeOut']; //验证码超时时间 26 | 27 | $dragUseTime = (int)$_REQUEST['dragUseTime']; // 拖拽用时 28 | if($dragUseTime > $captchaConfig['dragTimeMax'] || $dragUseTime<$captchaConfig['dragTimeMin'])checkError("验证失败!_code_5"); 29 | $dragUseTime = $dragUseTime / 1000; 30 | 31 | $dragStartTime = (int)$_REQUEST['dragStartTime'] / 1000; 32 | if($dragStartTime < $captchaLog['addTime'] || $dragStartTime + $dragUseTime > $captchaCheckOutTime)checkError("效验超时!请重试"); 33 | 34 | /** 35 | * 鼠标轨迹解析 36 | */ 37 | $mouseTrackList = getJson((string)$_REQUEST['mouseTrackList']); 38 | if(!$mouseTrackList || count($mouseTrackList) < 2)checkError("鼠标轨迹获取失败!请重试"); 39 | foreach($mouseTrackList as $index=>$item){ 40 | if(!isset($item['r']) || !isset($item['t']))checkError("效验失败_v_1"); 41 | $item['t'] = $item['t'] / 1000; //转为秒单位 42 | if($item['t'] < $dragStartTime)checkError("效验失败_v_2"); 43 | if($item['t'] > ($dragUseTime + $dragStartTime))checkError("效验失败_v_2_2"); 44 | $lastTime = $index==0? $dragStartTime : $mouseTrackList[$index-1]['t'] / 1000; 45 | if($item['t'] < $lastTime + ($captchaConfig['dragInterval'] / 1000))checkError("效验失败_v_3"); 46 | $item['r'] = (int)round($item['r']); 47 | if($item['r']<0 || $item['r']>100)checkError("效验失败_v_4"); 48 | } 49 | 50 | 51 | 52 | if(!in_array($rotationAngle,getSuccessRotationAngle($captchaLog['rotationAngle']))){ //角度效验失败 53 | 54 | if(!easySqlUpdate("captchaLog",[ 55 | "id" => $captchaLog['id'], 56 | ],[ 57 | "tryNum=tryNum+1" => [] 58 | ]))code("意外错误!_code_2"); 59 | if($captchaLog['tryNum']+1>=$captchaConfig['oneCapErrNum'])checkError("错误次数超过限制"); 60 | code([ 61 | "code" => 0, 62 | "message" => "角度错误!请重试" 63 | ]); 64 | 65 | }else{ //角度效验成功 66 | 67 | if(!easySqlUpdate("captchaLog",[ 68 | "id" => $captchaLog['id'], 69 | ],[ 70 | "yesTime" => time(), 71 | "yesUseTime" => $dragUseTime, 72 | ]))code("意外错误!_code_3"); 73 | unset($session['captchaLogId']); // 删除session里储存的验证码id 74 | 75 | $session['captchaUse'] = 0; //记录验证码使用次数 76 | $session['captchaCheckTime'] = time(); //记录效验时间 77 | 78 | code(1); //验证成功 79 | } 80 | 81 | 82 | /** 83 | * 结束代码并返回需要刷新验证码 84 | */ 85 | function checkError($msg="请先完成人机效验!"):void{ 86 | code([ 87 | "code" => 0, 88 | "getNewCaptcha" => true, 89 | "message" => $msg 90 | ]); 91 | } -------------------------------------------------------------------------------- /public/captcha/function.php: -------------------------------------------------------------------------------- 1 | = $captchaConfig["captchaUseMaxNum"] 15 | || $session["captchaCheckTime"] + $captchaConfig["captchaUseMaxTime"] < time() 16 | ){ 17 | if($endCode)code(0,[ 18 | "message" => "请先完成人机效验!", 19 | "needCaptcha" => true, 20 | ]); 21 | return false; 22 | } 23 | 24 | return true; 25 | } 26 | 27 | 28 | /** 29 | * 使用一次验证码有效数量 30 | */ 31 | function useCaptchaSuccessNum($userNum=1):bool{ 32 | global $session; 33 | if(!isCheckCaptchaSuccess())return false; 34 | $session["captchaUse"] = $session["captchaUse"] + $userNum; 35 | return true; 36 | } 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | /** 46 | * 获取可通过的角度列表 47 | */ 48 | function getSuccessRotationAngle(int $rotationAngle):array{ 49 | global $captchaConfig; 50 | $yesArray = [$rotationAngle]; 51 | for ($i=0; $i < $captchaConfig['errorAccuracy']; $i++) { 52 | $yesArray[] = $rotationAngle - ($i+1); 53 | $yesArray[] = $rotationAngle + ($i+1); 54 | } 55 | foreach($yesArray as $index => $value){ 56 | if($value<0){ 57 | $yesArray[$index] = 360 + $value; 58 | }else if($value > 360){ 59 | $yesArray[$index] = $value - 360; 60 | } 61 | } 62 | if(in_array(0,$yesArray) && !in_array(360,$yesArray)){ 63 | $yesArray[] = 360; 64 | } 65 | if(!in_array(0,$yesArray) && in_array(360,$yesArray)){ 66 | $yesArray[] = 0; 67 | } 68 | return $yesArray; 69 | } -------------------------------------------------------------------------------- /public/captcha/getCaptcha.php: -------------------------------------------------------------------------------- 1 | =?) AS dayAll, 13 | (SELECT count(*) FROM captchaLog WHERE addIp=? AND addTime>=? AND yesTime=0) AS dayError, 14 | (SELECT count(*) FROM captchaLog WHERE addIp=? AND addTime>=?) AS hourAll, 15 | (SELECT count(*) FROM captchaLog WHERE addIp=? AND addTime>=? AND yesTime=0) AS hourError 16 | ",[ 17 | $ip,$todayStart, 18 | $ip,$todayStart, 19 | $ip,$anHourAgo, 20 | $ip,$anHourAgo, 21 | ]); 22 | if(!$capCount || !isset($capCount["dayAll"]))code("验证码生成失败!_code_2"); 23 | 24 | if($capCount["hourError"] > $captchaConfig['ipHourError'])code("频繁验证,请一小时后再试!"); 25 | if($capCount["hourAll"] > $captchaConfig['ipHourAll'])code("频繁验证,请一小时后再试!"); 26 | if($capCount["dayError"] > $captchaConfig['ipDayError'])code("频繁验证,请明天后再试!"); 27 | if($capCount["dayAll"] > $captchaConfig['ipDayAll'])code("频繁验证,请明天再试!"); 28 | 29 | $capPointNum = max($captchaConfig["randomPoint"],$captchaConfig["addPoint"]($capCount)); //取配置参数的最大值 30 | $capLineNum = max($captchaConfig["randomLine"],$captchaConfig["addLine"]($capCount)); //取配置参数的最大值 31 | $capBlockNum = max($captchaConfig["randomBlock"],$captchaConfig["addBlock"]($capCount)); //取配置参数的最大值 32 | 33 | 34 | $captchaImage = easySqlSelect("*","captchaImage",[],[ 35 | "useNum" => "ASC", 36 | "id" => "DESC", 37 | ]); 38 | if(!$captchaImage)code("验证码生成失败!_code_3"); 39 | 40 | $capImgFile = $root."/".$captchaImage['file']; // 拼接验证码原图路径 41 | $outImg = $root."/nodejs/captcha/temp/".md5(time()."|".rand(0,999)."|".rand(0,999).$capImgFile).".png"; //验证码生成后的临时路径 42 | $rotationAngle = (int)rand(0,360); //验证码随机旋转角度 43 | 44 | /** 45 | * 拼接命令行参数 46 | */ 47 | $cmd = "{$nodePath} {$root}/nodejs/captcha/index.js {$capImgFile} {$outImg} {$rotationAngle} {$captchaConfig['canvasSize']} {$capPointNum} {$capLineNum} {$capBlockNum}"; 48 | $cmdEnd = str_replace(PHP_EOL, '', shell_exec($cmd)); 49 | if($cmdEnd!="success")code("生成失败!_code_1"); 50 | 51 | $imgBase64 = base64_encode(file_get_contents($outImg)); //验证码图片转base64 52 | unlink($outImg); //删除临时验证码 53 | 54 | $captchaLogId = easySqlInsert("captchaLog",[ 55 | "captchaId" => $captchaImage["id"], 56 | "rotationAngle" => $rotationAngle, 57 | "addIp" => $ip, 58 | "addTime" => time(), 59 | ]); 60 | if(!$captchaLogId)code("验证码生成失败!_code_4"); 61 | 62 | if(!easySqlUpdate("captchaImage",[ 63 | "id" => $captchaImage["id"] 64 | ],[ 65 | "useNum=useNum+1" => [] 66 | ]))code("意外错误!_code_1"); 67 | 68 | $session['captchaLogId'] = $captchaLogId; //验证码logID写入session 69 | 70 | code(1,[ 71 | "src" => $imgBase64, //输出验证码 72 | ]); -------------------------------------------------------------------------------- /public/getToken.php: -------------------------------------------------------------------------------- 1 |