我们在使用Hypercmf框架(基于hyperf)时,APP、小程序调用接口时都没问题,但是网站调用接口确总是出现跨域。
首先,我们需要明白,网站请求为什么会跨域?!
预检请求(option):
在 CORS 中,可以使用 OPTIONS 方法发起一个预检请求 (一般都是浏览检测到请求跨域时,会自动发起),以检测实际请求是否可以被服务器所接受。预检请求报文中的 Access-Control-Request-Method 首部字段告知服务器实际请求所使用的 HTTP 方法;Access-Control-Request-Headers 首部字段告知服务器实际请求所携带的自定义首部字段。服务器基于从预检请求获得的信息来判断,是否接受接下来的实际请求。
从上面的概念我们知道,网站请求接口时,并不是直接请求,而是先有一个预检的过程,这个过程一单判断失败,将直接提示跨域,不会实际发生get、post等请求。
那么,在Hypercmf中我们如何解决呢?
首先,我们尝试Hyperf官方文档提供的解决办法(无效、无效、无效!),在项目中添加一个CorsMiddleware:
<?php
declare(strict_types=1);
namespace App\Middleware;
use Hyperf\Utils\Context;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class CorsMiddleware implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$response = Context::get(ResponseInterface::class);
$response = $response->withHeader('Access-Control-Allow-Origin', '*')
->withHeader('Access-Control-Allow-Credentials', 'true')
// Headers 可以根据实际情况进行改写。
->withHeader('Access-Control-Allow-Headers', 'DNT,Keep-Alive,User-Agent,Cache-Control,Content-Type,Authorization');
Context::set(ResponseInterface::class, $response);
if ($request->getMethod() == 'OPTIONS') {
return $response;
}
return $handler->handle($request);
}
}
然后在接口的类上或者方法上添加中间件:
/** * Class MediaController * @package App\Controller\Api * @Middlewares({ * @Middleware(CorsMiddleware::class) * }) * @Controller(prefix="api/media") */class MediaController{}
经测试,无效!
那Hypercmf如何优化的呢?
其实大家在开发中,使用注解GetMapping 、RequestMapping、PostMapping时,
会严格控制允许的方法,当请求接口方法不正确时,会提示Allow:xxxx,
根据这个提示,我们可以知道,在Hyperf中有一个中间件在拦截请求,它就是
\Hyperf\HttpServer\CoreMiddleware
请把“\Hyperf\HttpServer\CoreMiddleware”打在评论区!!!!
我们读一下它的源码,就知道它在干哪些脏活累活了,
于是我们就给他减负,有了第一个解决方案:自定义一个CoreMiddleware,并继承\Hyperf\HttpServer\CoreMiddleware 然后冲洗process方法
第一种解决方案
<?php/** * 创建人 : 微信:hypercmf * 创建时间 : 2025/7/14 16:21 * 个人主页 : http://hypercmf.com */namespace App\Middleware;use FastRoute\Dispatcher;use Hyperf\HttpServer\Router\Dispatched;use Hyperf\Server\Exception\ServerException;use Hyperf\Utils\Context;use Psr\Http\Message\ResponseInterface;use Psr\Http\Message\ServerRequestInterface;use Psr\Http\Server\RequestHandlerInterface;class CoreMiddleware extends \Hyperf\HttpServer\CoreMiddleware{ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { $request = Context::set(ServerRequestInterface::class, $request); /** @var Dispatched $dispatched */ $dispatched = $request->getAttribute(Dispatched::class); if (! $dispatched instanceof Dispatched) { throw new ServerException(sprintf('The dispatched object is not a %s object.', Dispatched::class)); } if ($request->getMethod() == 'OPTIONS') { $response = \Hyperf\Context\Context::get(ResponseInterface::class); $response = $response->withStatus(204) ->withHeader('Access-Control-Allow-Origin', '*') ->withHeader('Access-Control-Allow-Credentials', 'true') ->withHeader('Access-Control-Allow-Headers', 'DNT,Keep-Alive,User-Agent,Cache-Control,Content-Type,Authorization,Client-Type'); return $response; } $response = null; switch ($dispatched->status) { case Dispatcher::NOT_FOUND: $response = $this->handleNotFound($request); break; case Dispatcher::METHOD_NOT_ALLOWED: $response = $this->handleMethodNotAllowed($dispatched->params, $request); break; case Dispatcher::FOUND: $response = $this->handleFound($dispatched, $request); break; } if (! $response instanceof ResponseInterface) { $response = $this->transferToResponse($response, $request); } return $response->withAddedHeader('Server', 'HyperCMF'); }}
其实我们只需要加入来判断当请求时OPTIONS时,就给他拦截返回一下
if ($request->getMethod() == 'OPTIONS') { $response = \Hyperf\Context\Context::get(ResponseInterface::class); $response = $response->withStatus(204) ->withHeader('Access-Control-Allow-Origin', '*') ->withHeader('Access-Control-Allow-Credentials', 'true') ->withHeader('Access-Control-Allow-Headers', 'DNT,Keep-Alive,User-Agent,Cache-Control,Content-Type,Authorization,Client-Type'); return $response; }
然后在/config/autoload/dependencies.php里加一下,问题解决!
\Hyperf\HttpServer\CoreMiddleware::class => \App\Middleware\CoreMiddleware::class,
方法二:解决的办法很二
在使用中间件解决之前我为了查看网站的请求方式,我在注解上使用RequestMapping,
并把method设置为POST / OPTIONS 像下面这样:
/** * 首页数据 * @RequestMapping(path="index",methods={"POST","GET","OPTIONS"}) */
但是确实能解决跨域问题,只是写起来麻烦,不能一劳永逸!
然后我顺利开发了一整套短剧出海的项目,适用于APP、小程序、H5 。一套打通!