├── .gitignore ├── src ├── config │ └── plugin │ │ └── wen-gg │ │ └── webman-api-sign │ │ ├── middleware.php │ │ └── app.php ├── Driver │ ├── BaseDriver.php │ ├── DatabaseDriver.php │ └── ArrayDriver.php ├── ApiSignException.php ├── ApiSignMiddleware.php ├── app_sign.sql ├── Install.php └── ApiSignService.php ├── composer.json ├── LICENSE ├── README.md └── composer.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /test/ -------------------------------------------------------------------------------- /src/config/plugin/wen-gg/webman-api-sign/middleware.php: -------------------------------------------------------------------------------- 1 | [ 5 | \Wengg\WebmanApiSign\ApiSignMiddleware::class, 6 | ], 7 | ]; 8 | -------------------------------------------------------------------------------- /src/Driver/BaseDriver.php: -------------------------------------------------------------------------------- 1 | 2022-08-25 11 | */ 12 | public function getInfo(string $app_key); 13 | } 14 | -------------------------------------------------------------------------------- /src/ApiSignException.php: -------------------------------------------------------------------------------- 1 | 2022-08-25 8 | */ 9 | class DatabaseDriver implements BaseDriver 10 | { 11 | /** 12 | * @var array 13 | */ 14 | protected $config; 15 | 16 | public function __construct(array $config) 17 | { 18 | $this->config = $config; 19 | } 20 | 21 | public function getInfo(string $app_key) 22 | { 23 | $data = \support\Db::table($this->config['table'])->where('app_key', $app_key)->first(); 24 | return $data ? (array) $data : []; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Driver/ArrayDriver.php: -------------------------------------------------------------------------------- 1 | 2022-08-25 8 | */ 9 | class ArrayDriver implements BaseDriver 10 | { 11 | /** 12 | * @var array 13 | */ 14 | protected $config; 15 | 16 | public function __construct(array $config) 17 | { 18 | $this->config = $config; 19 | } 20 | 21 | public function getInfo(string $app_key) 22 | { 23 | $list = $this->config[$this->config['table']] ?? []; 24 | $list && $list = array_combine(array_column($list, 'app_key'), $list); 25 | return $list[$app_key] ?? []; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/config/plugin/wen-gg/webman-api-sign/app.php: -------------------------------------------------------------------------------- 1 | true, 5 | 6 | // 7 | 'driver' => \Wengg\WebmanApiSign\Driver\ArrayDriver::class, //如有需要可自行实现BaseDriver 8 | 'encrypt' => 'sha256', //加密方式 9 | 'timeout' => 60, //timestamp超时时间秒,0不限制 10 | 'table' => 'app_sign', //表名 11 | 12 | //字段对照,可从(header,get,post)获取的值 13 | 'fields' => [ 14 | 'app_key' => 'appKey', //app_key 15 | 'timestamp' => 'timestamp', //时间戳 16 | 'noncestr' => 'noncestr', //随机字符串 17 | 'signature' => 'signature', //签名字符串 18 | ], 19 | 20 | //driver为ArrayDriver时生效,对应table 21 | 'app_sign' => [ 22 | [ 23 | 'app_key' => '1661408635', //应用key 24 | 'app_secret' => 'D81668E7B3F24F4DAB32E5B88EAE27AC', //应用秘钥 25 | 'app_name' => '默认', //应用名称 26 | 'status' => 1, //状态:0=禁用,1=启用 27 | 'expired_at' => null, //过期时间,例如:2023-01-01 00:00:00,null不限制 28 | ], 29 | ], 30 | ]; 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Mosquito 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/ApiSignMiddleware.php: -------------------------------------------------------------------------------- 1 | getConfig(); 16 | if (!$config) { 17 | return $next($request); 18 | } 19 | $fields = $config['fields']; 20 | $data = array_merge($request->all(), [ 21 | $fields['app_key'] => $request->header($fields['app_key'], $request->input($fields['app_key'])), 22 | $fields['timestamp'] => $request->header($fields['timestamp'], $request->input($fields['timestamp'])), 23 | $fields['noncestr'] => $request->header($fields['noncestr'], $request->input($fields['noncestr'])), 24 | $fields['signature'] => $request->header($fields['signature'], $request->input($fields['signature'])), 25 | ]); 26 | $service->check($data); 27 | 28 | return $next($request); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app_sign.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Target Server Type : MySQL 3 | Target Server Version : 80018 4 | File Encoding : 65001 5 | */ 6 | 7 | SET NAMES utf8mb4; 8 | SET FOREIGN_KEY_CHECKS = 0; 9 | 10 | -- ---------------------------- 11 | -- Table structure for app_sign 12 | -- ---------------------------- 13 | DROP TABLE IF EXISTS `app_sign`; 14 | CREATE TABLE `app_sign` ( 15 | `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, 16 | `app_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '应用key', 17 | `app_secret` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '应用秘钥', 18 | `app_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '应用名称', 19 | `status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '状态:0=禁用,1=启用', 20 | `expired_at` datetime(0) NULL DEFAULT NULL COMMENT '过期时间', 21 | `created_at` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', 22 | `updated_at` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', 23 | PRIMARY KEY (`id`) USING BTREE, 24 | UNIQUE INDEX `app_key`(`app_key`) USING BTREE 25 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '应用签名表' ROW_FORMAT = Dynamic; 26 | 27 | SET FOREIGN_KEY_CHECKS = 1; 28 | -------------------------------------------------------------------------------- /src/Install.php: -------------------------------------------------------------------------------- 1 | 'config/plugin/wen-gg/webman-api-sign', 13 | ); 14 | 15 | /** 16 | * Install 17 | * @return void 18 | */ 19 | public static function install() 20 | { 21 | static::installByRelation(); 22 | } 23 | 24 | /** 25 | * Uninstall 26 | * @return void 27 | */ 28 | public static function uninstall() 29 | { 30 | self::uninstallByRelation(); 31 | } 32 | 33 | /** 34 | * installByRelation 35 | * @return void 36 | */ 37 | public static function installByRelation() 38 | { 39 | foreach (static::$pathRelation as $source => $dest) { 40 | if ($pos = strrpos($dest, '/')) { 41 | $parent_dir = base_path() . '/' . substr($dest, 0, $pos); 42 | if (!is_dir($parent_dir)) { 43 | mkdir($parent_dir, 0777, true); 44 | } 45 | } 46 | //symlink(__DIR__ . "/$source", base_path()."/$dest"); 47 | copy_dir(__DIR__ . "/$source", base_path() . "/$dest"); 48 | echo "Create $dest 49 | "; 50 | } 51 | } 52 | 53 | /** 54 | * uninstallByRelation 55 | * @return void 56 | */ 57 | public static function uninstallByRelation() 58 | { 59 | foreach (static::$pathRelation as $source => $dest) { 60 | $path = base_path() . "/$dest"; 61 | if (!is_dir($path) && !is_file($path)) { 62 | continue; 63 | } 64 | echo "Remove $dest 65 | "; 66 | if (is_file($path) || is_link($path)) { 67 | unlink($path); 68 | continue; 69 | } 70 | remove_dir($path); 71 | } 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # webman-api-sign 2 | 适用于webman的api签名 3 | 4 | # 安装 5 | composer require wen-gg/webman-api-sign 6 | 7 | # 配置 8 | ```php 9 | return [ 10 | 'enable' => true, 11 | 12 | // 13 | 'driver' => \Wengg\WebmanApiSign\Driver\ArrayDriver::class, //如有需要可自行实现BaseDriver 14 | 'encrypt' => 'sha256', //加密方式 15 | 'timeout' => 60, //timestamp超时时间秒,0不限制 16 | 'table' => 'app_sign', //表名 17 | 18 | //字段对照,可从(header,get,post)获取的值 19 | 'fields' => [ 20 | 'app_key' => 'appKey', //app_key 21 | 'timestamp' => 'timestamp', //时间戳 22 | 'noncestr' => 'noncestr', //随机字符串 23 | 'signature' => 'signature', //签名字符串 24 | ], 25 | 26 | //driver为ArrayDriver时生效,对应table 27 | 'app_sign' => [ 28 | [ 29 | 'app_key' => '1661408635', //应用key 30 | 'app_secret' => 'D81668E7B3F24F4DAB32E5B88EAE27AC', //应用秘钥 31 | 'app_name' => '默认', //应用名称 32 | 'status' => 1, //状态:0=禁用,1=启用 33 | 'expired_at' => null, //过期时间,例如:2023-01-01 00:00:00,null不限制 34 | ], 35 | ], 36 | ]; 37 | ``` 38 | 39 | # 签名计算 40 | 注意:签名数据除业务参数外需加上app_key,timestamp,noncestr对应的字段数据 41 | 1. 签名数据先按照键名升序排序 42 | 2. 使用 & 链接签名数组(参数不转义,空数据不参与加密),再在尾部加上app_secret 43 | 3. 再根据配置的加密方式 hash() 签名数据 44 | 45 | # 示例 46 | 47 | 排序前 48 | ```json 49 | { 50 | "a": "1", 51 | "b": [ 52 | "你好世界", 53 | "abc123" 54 | ], 55 | "c": { 56 | "e": [], 57 | "d": "hello" 58 | }, 59 | "appKey": "1661408635", 60 | "timestamp": "1662721474", 61 | "noncestr": "ewsqam" 62 | } 63 | ``` 64 | 排序后 65 | ```json 66 | { 67 | "a": "1", 68 | "appKey": "1661408635", 69 | "b": [ 70 | "你好世界", 71 | "abc123" 72 | ], 73 | "c": { 74 | "d": "hello", 75 | "e": [] 76 | }, 77 | "noncestr": "ewsqam", 78 | "timestamp": "1662721474" 79 | } 80 | ``` 81 | 链接后 82 | ``` 83 | a=1&appKey=1661408635&b[0]=你好世界&b[1]=abc123&c[d]=hello&noncestr=ewsqam×tamp=1662721474D81668E7B3F24F4DAB32E5B88EAE27AC 84 | ``` 85 | 加密 86 | ```php 87 | $signature = hash('sha256', 'a=1&appKey=1661408635&b[0]=你好世界&b[1]=abc123&c[d]=hello&noncestr=ewsqam×tamp=1662721474D81668E7B3F24F4DAB32E5B88EAE27AC'); 88 | ``` -------------------------------------------------------------------------------- /src/ApiSignService.php: -------------------------------------------------------------------------------- 1 | config = config('plugin.wen-gg.webman-api-sign.app') ?: []; 20 | if ($this->config) { 21 | $this->driver = new $this->config['driver']($this->config); 22 | } 23 | } 24 | 25 | /** 26 | * 获取配置 27 | * @author mosquito 2022-08-25 28 | */ 29 | public function getConfig() 30 | { 31 | return $this->config; 32 | } 33 | 34 | /** 35 | * 获取驱动 36 | * @author mosquito 2022-08-25 37 | */ 38 | public function getDriver() 39 | { 40 | return $this->driver; 41 | } 42 | 43 | /** 44 | * 数据排序 45 | * @param array $data 46 | * @author mosquito 2022-08-25 47 | */ 48 | public function sortData(array $data) 49 | { 50 | $sort = function (array &$data) use (&$sort) { 51 | ksort($data); 52 | foreach ($data as &$value) { 53 | if (is_array($value)) { 54 | $sort($value); 55 | } 56 | } 57 | }; 58 | $sort($data); 59 | return $data; 60 | } 61 | 62 | /** 63 | * 签名 64 | * @author mosquito 2022-08-25 65 | */ 66 | public function sign(array $data) 67 | { 68 | unset($data[$this->config['fields']['signature']]); 69 | if (!isset($data[$this->config['fields']['app_key']]) || !isset($data[$this->config['fields']['timestamp']]) || !isset($data[$this->config['fields']['noncestr']])) { 70 | throw new ApiSignException("签名参数错误", ApiSignException::PARAMS_ERROR); 71 | } 72 | 73 | //应用数据 74 | $app_sign = $this->driver->getInfo($data[$this->config['fields']['app_key']]); 75 | if (!$app_sign) { 76 | throw new ApiSignException("应用key未找到", ApiSignException::APPKEY_NOT_FOUND); 77 | } 78 | if ($app_sign['status'] != 1) { 79 | throw new ApiSignException("应用key已禁用", ApiSignException::APPKEY_DISABLE); 80 | } 81 | if ($app_sign['expired_at'] && $app_sign['expired_at'] < date('Y-m-d H:i:s')) { 82 | throw new ApiSignException("应用key已过期", ApiSignException::APPKEY_EXPIRED); 83 | } 84 | 85 | //计算签名 86 | $data = $this->sortData($data); 87 | $encrypt = $this->config['encrypt'] ?: 'sha256'; 88 | $str = urldecode(http_build_query($data)) . $app_sign['app_secret']; 89 | $signature = hash($encrypt, $str); 90 | return $signature; 91 | } 92 | 93 | /** 94 | * 验签 95 | * @author mosquito 2022-08-25 96 | */ 97 | public function check(array $data) 98 | { 99 | if (!$signature = $data[$this->config['fields']['signature']]) { 100 | throw new ApiSignException("签名参数错误", ApiSignException::PARAMS_ERROR); 101 | } 102 | if ($signature !== $this->sign($data)) { 103 | throw new ApiSignException("签名验证失败", ApiSignException::SIGN_VERIFY_FAIL); 104 | } 105 | $ts = time(); 106 | if ($this->config['timeout'] && $this->config['timeout'] > 0 && ($data[$this->config['fields']['timestamp']] + $this->config['timeout'] < $ts || $data[$this->config['fields']['timestamp']] - $this->config['timeout'] > $ts)) { 107 | throw new ApiSignException("签名超时", ApiSignException::SIGN_TIMEOUT); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "9aede846290251f55f14e7363ceea66e", 8 | "packages": [ 9 | { 10 | "name": "nikic/fast-route", 11 | "version": "v1.3.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/nikic/FastRoute.git", 15 | "reference": "181d480e08d9476e61381e04a71b34dc0432e812" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", 20 | "reference": "181d480e08d9476e61381e04a71b34dc0432e812", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": ">=5.4.0" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit": "^4.8.35|~5.7" 28 | }, 29 | "type": "library", 30 | "autoload": { 31 | "files": [ 32 | "src/functions.php" 33 | ], 34 | "psr-4": { 35 | "FastRoute\\": "src/" 36 | } 37 | }, 38 | "notification-url": "https://packagist.org/downloads/", 39 | "license": [ 40 | "BSD-3-Clause" 41 | ], 42 | "authors": [ 43 | { 44 | "name": "Nikita Popov", 45 | "email": "nikic@php.net" 46 | } 47 | ], 48 | "description": "Fast request router for PHP", 49 | "keywords": [ 50 | "router", 51 | "routing" 52 | ], 53 | "support": { 54 | "issues": "https://github.com/nikic/FastRoute/issues", 55 | "source": "https://github.com/nikic/FastRoute/tree/master" 56 | }, 57 | "time": "2018-02-13T20:26:39+00:00" 58 | }, 59 | { 60 | "name": "psr/container", 61 | "version": "2.0.2", 62 | "source": { 63 | "type": "git", 64 | "url": "https://github.com/php-fig/container.git", 65 | "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" 66 | }, 67 | "dist": { 68 | "type": "zip", 69 | "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", 70 | "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", 71 | "shasum": "" 72 | }, 73 | "require": { 74 | "php": ">=7.4.0" 75 | }, 76 | "type": "library", 77 | "extra": { 78 | "branch-alias": { 79 | "dev-master": "2.0.x-dev" 80 | } 81 | }, 82 | "autoload": { 83 | "psr-4": { 84 | "Psr\\Container\\": "src/" 85 | } 86 | }, 87 | "notification-url": "https://packagist.org/downloads/", 88 | "license": [ 89 | "MIT" 90 | ], 91 | "authors": [ 92 | { 93 | "name": "PHP-FIG", 94 | "homepage": "https://www.php-fig.org/" 95 | } 96 | ], 97 | "description": "Common Container Interface (PHP FIG PSR-11)", 98 | "homepage": "https://github.com/php-fig/container", 99 | "keywords": [ 100 | "PSR-11", 101 | "container", 102 | "container-interface", 103 | "container-interop", 104 | "psr" 105 | ], 106 | "support": { 107 | "issues": "https://github.com/php-fig/container/issues", 108 | "source": "https://github.com/php-fig/container/tree/2.0.2" 109 | }, 110 | "time": "2021-11-05T16:47:00+00:00" 111 | }, 112 | { 113 | "name": "workerman/webman-framework", 114 | "version": "v1.5.22", 115 | "source": { 116 | "type": "git", 117 | "url": "https://github.com/walkor/webman-framework.git", 118 | "reference": "f52d9739a264d99d49427081c8a85303c02a770e" 119 | }, 120 | "dist": { 121 | "type": "zip", 122 | "url": "https://api.github.com/repos/walkor/webman-framework/zipball/f52d9739a264d99d49427081c8a85303c02a770e", 123 | "reference": "f52d9739a264d99d49427081c8a85303c02a770e", 124 | "shasum": "" 125 | }, 126 | "require": { 127 | "ext-json": "*", 128 | "nikic/fast-route": "^1.3", 129 | "php": ">=7.2", 130 | "psr/container": ">=1.0", 131 | "workerman/workerman": "^4.0.4 || ^5.0.0" 132 | }, 133 | "suggest": { 134 | "ext-event": "For better performance. " 135 | }, 136 | "type": "library", 137 | "autoload": { 138 | "psr-4": { 139 | "Webman\\": "./src", 140 | "Support\\": "./src/support", 141 | "support\\": "./src/support", 142 | "Support\\View\\": "./src/support/view", 143 | "Support\\Bootstrap\\": "./src/support/bootstrap", 144 | "Support\\Exception\\": "./src/support/exception" 145 | } 146 | }, 147 | "notification-url": "https://packagist.org/downloads/", 148 | "license": [ 149 | "MIT" 150 | ], 151 | "authors": [ 152 | { 153 | "name": "walkor", 154 | "email": "walkor@workerman.net", 155 | "homepage": "https://www.workerman.net", 156 | "role": "Developer" 157 | } 158 | ], 159 | "description": "High performance HTTP Service Framework.", 160 | "homepage": "https://www.workerman.net", 161 | "keywords": [ 162 | "High Performance", 163 | "http service" 164 | ], 165 | "support": { 166 | "email": "walkor@workerman.net", 167 | "forum": "https://wenda.workerman.net/", 168 | "issues": "https://github.com/walkor/webman/issues", 169 | "source": "https://github.com/walkor/webman-framework", 170 | "wiki": "https://doc.workerman.net/" 171 | }, 172 | "time": "2024-08-04T01:40:07+00:00" 173 | }, 174 | { 175 | "name": "workerman/workerman", 176 | "version": "v4.1.16", 177 | "source": { 178 | "type": "git", 179 | "url": "https://github.com/walkor/workerman.git", 180 | "reference": "405d904d33026e19497dffc3d085bbc16e66534e" 181 | }, 182 | "dist": { 183 | "type": "zip", 184 | "url": "https://api.github.com/repos/walkor/workerman/zipball/405d904d33026e19497dffc3d085bbc16e66534e", 185 | "reference": "405d904d33026e19497dffc3d085bbc16e66534e", 186 | "shasum": "" 187 | }, 188 | "require": { 189 | "php": ">=7.0" 190 | }, 191 | "suggest": { 192 | "ext-event": "For better performance. " 193 | }, 194 | "type": "library", 195 | "autoload": { 196 | "psr-4": { 197 | "Workerman\\": "./" 198 | } 199 | }, 200 | "notification-url": "https://packagist.org/downloads/", 201 | "license": [ 202 | "MIT" 203 | ], 204 | "authors": [ 205 | { 206 | "name": "walkor", 207 | "email": "walkor@workerman.net", 208 | "homepage": "http://www.workerman.net", 209 | "role": "Developer" 210 | } 211 | ], 212 | "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", 213 | "homepage": "http://www.workerman.net", 214 | "keywords": [ 215 | "asynchronous", 216 | "event-loop" 217 | ], 218 | "support": { 219 | "email": "walkor@workerman.net", 220 | "forum": "http://wenda.workerman.net/", 221 | "issues": "https://github.com/walkor/workerman/issues", 222 | "source": "https://github.com/walkor/workerman", 223 | "wiki": "http://doc.workerman.net/" 224 | }, 225 | "funding": [ 226 | { 227 | "url": "https://opencollective.com/workerman", 228 | "type": "open_collective" 229 | }, 230 | { 231 | "url": "https://www.patreon.com/walkor", 232 | "type": "patreon" 233 | } 234 | ], 235 | "time": "2024-07-04T08:26:39+00:00" 236 | } 237 | ], 238 | "packages-dev": [], 239 | "aliases": [], 240 | "minimum-stability": "stable", 241 | "stability-flags": [], 242 | "prefer-stable": false, 243 | "prefer-lowest": false, 244 | "platform": [], 245 | "platform-dev": [], 246 | "plugin-api-version": "2.6.0" 247 | } 248 | --------------------------------------------------------------------------------