Hyperf框架开发网站接口跨域问题,以Hypercmf内容管理框架实战经验告诉你解决方案。

123 阅读2分钟

我们在使用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 。一套打通!