// +---------------------------------------------------------------------- namespace ywxapp\library; use BcMath\Number; use think\exception\HttpResponseException; use think\facade\Request; use think\Response; class Result { /** * Summary of StatusCode * @var int */ protected $StatusCode = 200; public $Message = null; /** * Summary of Headers * @var array */ protected $Headers = []; protected $AccessToken; protected $AccessExpired; protected $RefreshToken; protected $RefreshExpired; protected $Total = 0; /** * Summary of Type * @var string */ protected $Type = 'json'; protected $isCout = false; public function __construct() { if (! Request::isAjax()) { $this->Type = 'html'; } } public function success($data = null, $message = 'success', int $code = 0) { $result = ['code' => 0, 'message' => $message, 'timestamp' => time()]; if ($data) { $result['data'] = $data; } if ($this->Total > 0) { $result['count'] = $this->Total; } if ($this->AccessToken != null) { $result['access_token'] = $this->AccessToken; $result['access_expired'] = $this->AccessExpired; } if ($this->RefreshToken != null) { $result['refresh_token'] = $this->RefreshToken; $result['refresh_expired'] = $this->RefreshExpired; } $response = ''; if ('html' == strtolower($this->Type)) { \think\facade\View::config(['view_path' => root_path() . 'ywxapp/view/']); \think\facade\View::layout(false); $result = \think\facade\View::fetch('success.html', $result); } $response = Response::create($result, strtolower($this->Type), $this->StatusCode)->header($this->Headers); $this->applyTokenCookies($response); $response->send(); app()->http->end($response); exit; // throw new HttpResponseException($response); } public function error($message = 'Error', int $code = 1, $data = null) { $result = ['code' => $code, 'message' => $message, 'timestamp' => time()]; if ($data) { $result['data'] = $data; } if ($this->AccessToken != null) { $result['access_token'] = $this->AccessToken; $result['access_expired'] = $this->AccessExpired; } if ($this->RefreshToken != null) { $result['refresh_token'] = $this->RefreshToken; $result['refresh_expired'] = $this->RefreshExpired; } $response = ''; // 若已被强制为 JSON(如插件 API 在 MultiApp 中间件里 setType('json')), // 或请求本身是 ajax,则统一返回 JSON;否则按 HTML 渲染。 $type = ($this->Type === 'json') ? 'json' : (Request::isAjax() ? 'json' : 'html'); if ($this->StatusCode == 401) { $result['url'] = url('login/index'); $result['code'] = $this->StatusCode; if ('html' == strtolower($type)) { \think\facade\View::config(['view_path' => root_path() . 'ywxapp' . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR]); $result = \think\facade\View::fetch('401.html', $result); } $response = Response::create($result, strtolower($type), 401); $this->applyTokenCookies($response); } else { if ('html' == strtolower($type)) { \think\facade\View::config(['view_path' => root_path() . 'ywxapp' . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR]); \think\facade\View::layout(false); $result = \think\facade\View::fetch('error.html', $result); } $response = Response::create($result, strtolower($type), $this->StatusCode)->header($this->Headers); $this->applyTokenCookies($response); } $response->send(); app()->http->end($response); exit; throw new HttpResponseException($response); } /** * 返回封装后的 API 数据到客户端. * * @param mixed $data 要返回的数据 * @param int $code 返回的 code * @param mixed $msg 提示信息 * @param string $type 返回数据格式 * @param array $header 发送的 Header 信息 */ protected function result($data, $code = 0, $msg = '', $type = '', array $header = []) { $result = [ 'code' => $code, 'msg' => $msg, 'time' => Request::server('REQUEST_TIME'), 'data' => $data, ]; $type = $type ?: $this->getResponseType(); $response = Response::create($result, $type)->header($header); throw new HttpResponseException($response); } /** * URL 重定向. * * @param string $url 跳转的 URL 表达式 * @param array|int $params 其它 URL 参数 * @param int $code http code * @param array $with 隐式传参 */ public function redirect($url, $code = 302, $params = [], $with = []): self { if (is_int($params)) { $code = $params; } $response = \redirect($url); $response->code($code)->with($with); throw new HttpResponseException($response); } /** * 设置Token * @param string $token * @return void */ public function setAccessToken($token, $expired = null): self { $this->AccessToken = $token; $this->AccessExpired = $expired; return app()->result; } /** * 设置Token * @param string $token * @return void */ public function setRefreshToken($token, $expired = null): self { $this->RefreshToken = $token; $this->RefreshExpired = $expired; return $this; } public function setStatusCode(int $code = 200): self { $this->StatusCode = $code; return $this; } public function setMessage(string $message = ''): self { $this->Message = $message; return $this; } public function setHeaders(array $header = []): self { $this->Headers = $header; return $this; } /** * 强制设置响应类型(json/html) * 插件 API 由小程序/APP 等客户端调用,请求不带 X-Requested-With, * 在 MultiApp 中间件里统一 setType('json') 以确保返回 JSON。 * @param string $type * @return self */ public function setType(string $type = 'json'): self { $this->Type = strtolower($type); return app()->result; } /** * 把当前持有的 access/refresh token 写入响应 Cookie(path=/,非 httponly)。 * * 背景:登录/注册的 token 经 JwtService 存入本 Result 单例(setAccessToken/setRefreshToken), * 但 persistTokens() 用的全局 Cookie 门面与 Response 持有的 Cookie 实例并非同一个, * 仅靠门面 set 的 token 不会被 Response::send() 输出,导致整页跳转(菜单点击、iframe) * 不带 token → 后端读不到 → 跳登录页。此处直接写到 Response 实例上,确保 100% 随响应输出。 * * @param Response $response * @return void */ protected function applyTokenCookies(Response $response): void { if ($this->AccessToken !== null && $this->AccessToken !== '') { $expire = $this->AccessExpired ? ((int) $this->AccessExpired - time()) : 0; $response->cookie('access_token', (string) $this->AccessToken, [ 'expire' => $expire, 'path' => '/', 'httponly' => false, ]); } if ($this->RefreshToken !== null && $this->RefreshToken !== '') { $expire = $this->RefreshExpired ? ((int) $this->RefreshExpired - time()) : 0; $response->cookie('refresh_token', (string) $this->RefreshToken, [ 'expire' => $expire, 'path' => '/', 'httponly' => false, ]); } } public function setCount(int $total): self { $this->Total = $total; return app()->result; } public static function instance($options = []): Result { return app()->result; } }