Files
YwxAppThink/ywxapp/library/Result.php
T
ywxapp 1d49e6f5ee feat: 公共表更名 common/member 前缀 + 插件命令自动加载 + 版本 1.1.1
- 18 张公共表更名(addon/attachment/configure/links/spider_log/spider_stat/sms/notice/ad/task/prop/medal/help/card -> common_*,member_wallets->member_wallet,score_rule/score_log -> member_*,addon_config->common_addonconf),模型全部对齐新表名,Db 直引用清零
- Attachment 模型补  表名绑定,修复富文本上传查 wxapp_attachment 1146 隐患
- install.sql + 迁移 SQL:backend_admin/backend_role delete_at 默认 0,修复软删除(NULL != 0)误过滤导致后台菜单为空
- AppService::boot() 支持插件 info.php 声明 commands 自动注册插件命令(psr-4 自动加载,坏类名自动跳过)
- 各插件(haonav/mqttbroker/wxchat/articles/blog/forum 等)字段与配置同步调整
- 框架版本 1.1.0 -> 1.1.1
2026-08-20 20:19:15 +08:00

359 lines
12 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// +----------------------------------------------------------------------
// | YwxApp [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: ywxapp<admin@ywxapp.cn>
// +----------------------------------------------------------------------
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 数据到客户端(已弃用,字段契约与 success/error 不一致).
*
* 请改用 apiSuccess() / apiError():统一 JSON、统一字段(code/msg/data/timestamp),
* 业务 code 与 HTTP 状态码解耦,不再受后台 HTML/401 跳转逻辑影响。
*
* @param mixed $data 要返回的数据
* @param int $code 返回的 code
* @param mixed $msg 提示信息
* @param string $type 返回数据格式
* @param array $header 发送的 Header 信息
*
* @deprecated 使用 apiSuccess() / apiError() 替代
*/
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);
}
/**
* API 成功响应(强制 JSON,契约稳定).
*
* 专供 App / 小程序 / 第三方客户端调用,与后台 HTML 响应完全解耦:
* - 无论请求是否 ajax,固定返回 JSON;
* - 统一字段:code(0) / msg / data / timestamp
* - 若本实例持有 token,自动附加 access_token / refresh_token 等字段;
* - 若设置了 count,自动附加 count(分页场景)。
*
* @param mixed $data 业务数据,可为数组 / 模型 / null
* @param string $message 成功提示语
* @param int $count 分页总数(可选,>0 时附加 count 字段)
*
* @return void 通过 Response 直接输出并终止
*/
public function apiSuccess($data = null, string $message = 'success', int $count = 0): void
{
$result = ['code' => 0, 'msg' => $message, 'timestamp' => time()];
if ($data !== null) {
$result['data'] = $data;
}
if ($count > 0) {
$result['count'] = $count;
}
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 = Response::create($result, 'json', $this->StatusCode)->header($this->Headers);
$this->applyTokenCookies($response);
$response->send();
app()->http->end($response);
exit;
}
/**
* API 失败响应(强制 JSON,业务 code 与 HTTP 状态解耦).
*
* 与 apiSuccess 配套,专供客户端调用:
* - 固定返回 JSON
* - 统一字段:code / msg / data(可选) / timestamp
* - 业务 code 原样透传(如 401 失败返回 code=401,而非被覆盖);
* - HTTP 状态码由 $httpStatus 控制(默认 200,鉴权失败传 401)。
*
* @param string $message 失败提示语
* @param int $code 业务状态码(默认 1,鉴权失败建议 401)
* @param mixed $data 附加数据(可选)
* @param int $httpStatus HTTP 状态码(默认 200
*
* @return void 通过 Response 直接输出并终止
*/
public function apiError(string $message = 'Error', int $code = 1, $data = null, int $httpStatus = 200): void
{
$result = ['code' => $code, 'msg' => $message, 'timestamp' => time()];
if ($data !== null) {
$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 = Response::create($result, 'json', $httpStatus)->header($this->Headers);
$this->applyTokenCookies($response);
$response->send();
app()->http->end($response);
exit;
}
/**
* 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 写入响应 Cookiepath=/,非 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;
}
}