Merge branch 'dev' of https://gitea.f2b211.com/jsasg/orico-official-website into dev
This commit is contained in:
@@ -69,7 +69,7 @@
|
|||||||
<ul class="love2">
|
<ul class="love2">
|
||||||
{volist name="recommends" id="vo"}
|
{volist name="recommends" id="vo"}
|
||||||
<li>
|
<li>
|
||||||
<a>
|
<a href="{:url('article/detail', ['id' => $vo.id])}">
|
||||||
<div class="lvimg"><img src="{$vo.image}"></div>
|
<div class="lvimg"><img src="{$vo.image}"></div>
|
||||||
<p class="lvtit">{$vo.title}</p>
|
<p class="lvtit">{$vo.title}</p>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -19,8 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- 赛博云 -->
|
<!-- 赛博云 -->
|
||||||
{notempty name="data.cyber"}
|
{notempty name="data.cyber"}
|
||||||
<!-- narssben-us 这个样式如果是中文就去掉 如果是因为就加在narssbshow后面-->
|
<div class="nDtopCtMian narssbshow {eq name='Request.cookie.think_lang' value='en-us'}narssben-us{/eq}">
|
||||||
<div class="nDtopCtMian narssbshow narssben-us">
|
|
||||||
{notempty name="data.cyber.focus_image"}
|
{notempty name="data.cyber.focus_image"}
|
||||||
<div class="nDtopIt">
|
<div class="nDtopIt">
|
||||||
<img src="{$data.cyber.focus_image.image}" class="tpimg" />
|
<img src="{$data.cyber.focus_image.image}" class="tpimg" />
|
||||||
|
|||||||
70
app/openapi/common.php
Normal file
70
app/openapi/common.php
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
// 这是系统自动生成的公共文件
|
||||||
|
|
||||||
|
if (!function_exists('image_domain_concat')) {
|
||||||
|
/**
|
||||||
|
* 图片域名拼接
|
||||||
|
* @param $path
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function image_domain_concat($path)
|
||||||
|
{
|
||||||
|
if (empty($path)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$domain = env('OPENAPI.RESOURCE_IMAGES_DOMAIN');
|
||||||
|
if (empty($domain)) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rtrim($domain, '/') . '/' . ltrim($path, '/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('video_domain_concat')) {
|
||||||
|
/**
|
||||||
|
* 视频域名拼接
|
||||||
|
* @param $path
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function video_domain_concat($path)
|
||||||
|
{
|
||||||
|
if (empty($path)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$domain = env('OPENAPI.RESOURCE_VIDEOS_DOMAIN');
|
||||||
|
if (empty($domain)) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rtrim($domain, '/') . '/' . ltrim($path, '/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('html_image_replace')) {
|
||||||
|
/**
|
||||||
|
* 替换html中的图片路径
|
||||||
|
* @param $html
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function html_image_replace($html)
|
||||||
|
{
|
||||||
|
if (empty($html)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return preg_replace_callback('/<img[^>]+src\s*=\s*([\'"])((?:(?!\1).)*)\1[^>]*>/i',
|
||||||
|
function($matches) {
|
||||||
|
$src = $matches[2];
|
||||||
|
if (!empty($src) && !str_starts_with($src, 'http')) {
|
||||||
|
// 保留原始标签,只替换src属性
|
||||||
|
return str_replace($src, image_domain_concat($src), $matches[0]);
|
||||||
|
}
|
||||||
|
return $matches[0];
|
||||||
|
},
|
||||||
|
$html
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
96
app/openapi/controller/v1/Article.php
Normal file
96
app/openapi/controller/v1/Article.php
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\openapi\controller\v1;
|
||||||
|
|
||||||
|
use app\openapi\model\ArticleModel;
|
||||||
|
|
||||||
|
class Article
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 文章列表
|
||||||
|
*/
|
||||||
|
public function list()
|
||||||
|
{
|
||||||
|
$params = request()->get([
|
||||||
|
'category_id',
|
||||||
|
'language' => 'zh-cn',
|
||||||
|
'page/d' => 1,
|
||||||
|
'size/d' => 50
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($params['size'] > 200) {
|
||||||
|
// 每页不超过200条
|
||||||
|
$params['size'] = 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
$articles = ArticleModel::with([
|
||||||
|
'category' => fn($query) => $query->field('id, name')
|
||||||
|
])
|
||||||
|
->field([
|
||||||
|
'id',
|
||||||
|
'language_id',
|
||||||
|
'category_id',
|
||||||
|
'title',
|
||||||
|
'desc',
|
||||||
|
'image',
|
||||||
|
'deleted_at'
|
||||||
|
])
|
||||||
|
->categoryId($params['category_id']??null)
|
||||||
|
->language($params['language']??'zh-cn')
|
||||||
|
->hidden(['language_id', 'category_id'])
|
||||||
|
->paginate([
|
||||||
|
'list_rows' => $params['size'],
|
||||||
|
'page' => $params['page']
|
||||||
|
])
|
||||||
|
->each(function($it) {
|
||||||
|
if (!empty($it['image']) && !str_starts_with($it['image'], 'http')) {
|
||||||
|
$it['image'] = image_domain_concat($it['image']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $it;
|
||||||
|
});
|
||||||
|
|
||||||
|
return success('success', $articles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文章详情
|
||||||
|
*/
|
||||||
|
public function detail()
|
||||||
|
{
|
||||||
|
$id = request()->param('id');
|
||||||
|
|
||||||
|
$article = ArticleModel::with([
|
||||||
|
'category' => fn($query) => $query->field('id, name')
|
||||||
|
])
|
||||||
|
->withoutField([
|
||||||
|
'language_id',
|
||||||
|
'seo_title',
|
||||||
|
'seo_keywords',
|
||||||
|
'seo_desc',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
'deleted_at'
|
||||||
|
])
|
||||||
|
->bypk($id)
|
||||||
|
->hidden(['category_id'])
|
||||||
|
->find();
|
||||||
|
|
||||||
|
if (is_null($article)) {
|
||||||
|
return error('the article does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片处理
|
||||||
|
if (!empty($article['image']) && !str_starts_with($article['image'], 'http')) {
|
||||||
|
$article['image'] = image_domain_concat($article['image']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 详情中图片处理
|
||||||
|
if (!empty($article['content'])) {
|
||||||
|
$article['content'] = html_image_replace($article['content']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return success('success', $article);
|
||||||
|
}
|
||||||
|
}
|
||||||
50
app/openapi/controller/v1/ArticleCategory.php
Normal file
50
app/openapi/controller/v1/ArticleCategory.php
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\openapi\controller\v1;
|
||||||
|
|
||||||
|
use app\openapi\model\ArticleCategoryModel;
|
||||||
|
|
||||||
|
class ArticleCategory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 文章分类列表
|
||||||
|
*/
|
||||||
|
public function list()
|
||||||
|
{
|
||||||
|
$params = request()->get([
|
||||||
|
'parent_id',
|
||||||
|
'language' => 'zh-cn',
|
||||||
|
'page/d' => 1,
|
||||||
|
'size/d' => 50
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($params['size'] > 200) {
|
||||||
|
// 每页不超过200条
|
||||||
|
$params['size'] = 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
$categories = ArticleCategoryModel::withoutField([
|
||||||
|
'language_id',
|
||||||
|
'unique_label',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])
|
||||||
|
->language($params['language']??'zh-cn')
|
||||||
|
->parent($params['parent_id']??null)
|
||||||
|
->order(['sort' => 'asc', 'id' => 'desc'])
|
||||||
|
->paginate([
|
||||||
|
'list_rows' => $params['size'],
|
||||||
|
'page' => $params['page'],
|
||||||
|
])
|
||||||
|
->each(function($item) {
|
||||||
|
if (!empty($item['icon']) && !str_starts_with($item['icon'], 'http')){
|
||||||
|
$item['icon'] = image_domain_concat($item['icon']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $item;
|
||||||
|
});
|
||||||
|
|
||||||
|
return success('success', $categories);
|
||||||
|
}
|
||||||
|
}
|
||||||
37
app/openapi/controller/v1/Authorize.php
Normal file
37
app/openapi/controller/v1/Authorize.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\openapi\controller\v1;
|
||||||
|
|
||||||
|
use OAuth2\OAuth2;
|
||||||
|
use OAuth2\OAuth2ServerException;
|
||||||
|
use oauth\OAuthStorage;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
class Authorize
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 获取/刷新token
|
||||||
|
*/
|
||||||
|
public function token()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$post = request()->post([
|
||||||
|
'client_id',
|
||||||
|
'client_secret',
|
||||||
|
'grant_type',
|
||||||
|
'refresh_token',
|
||||||
|
]);
|
||||||
|
$server = request()->server();
|
||||||
|
$request = new Request([], $post, [], [], [], $server);
|
||||||
|
$storage = new OAuthStorage;
|
||||||
|
$oauth = new OAuth2($storage);
|
||||||
|
$token = $oauth->grantAccessToken($request);
|
||||||
|
return success('success', json_decode($token->getContent(), true));
|
||||||
|
} catch (OAuth2ServerException $e) {
|
||||||
|
return error($e->getMessage() . ' - ' . $e->getDescription());
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
return error($th->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
174
app/openapi/controller/v1/Product.php
Normal file
174
app/openapi/controller/v1/Product.php
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\openapi\controller\v1;
|
||||||
|
|
||||||
|
use app\openapi\model\ProductModel;
|
||||||
|
|
||||||
|
class Product
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 产品列表
|
||||||
|
*/
|
||||||
|
public function list()
|
||||||
|
{
|
||||||
|
$params = request()->get([
|
||||||
|
'category_id',
|
||||||
|
'language' => 'zh-cn',
|
||||||
|
'page/d' => 1,
|
||||||
|
'size/d' => 50
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($params['size'] > 200) {
|
||||||
|
// 每页不超过200条
|
||||||
|
$params['size'] = 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
$list = ProductModel::with([
|
||||||
|
'category' => fn($query) => $query->field(['id', 'name']),
|
||||||
|
])
|
||||||
|
->field([
|
||||||
|
'id',
|
||||||
|
'category_id',
|
||||||
|
'spu',
|
||||||
|
'name',
|
||||||
|
'short_name',
|
||||||
|
'cover_image',
|
||||||
|
'desc',
|
||||||
|
'deleted_at'
|
||||||
|
])
|
||||||
|
->where(function($query) use($params) {
|
||||||
|
$model = $query->getModel();
|
||||||
|
if (!empty($params['category_id'])) {
|
||||||
|
$model->scopeCategoryId($query, $params['category_id']);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
->language($params['language']??'zh-cn')
|
||||||
|
->order(['sort' => 'asc', 'id' => 'desc'])
|
||||||
|
->hidden(['category_id'])
|
||||||
|
->paginate([
|
||||||
|
'list_row' => $params['size'],
|
||||||
|
'page' => $params['page']
|
||||||
|
])
|
||||||
|
->each(function($item) {
|
||||||
|
if (!empty($item['cover_image']) && !str_starts_with($item['cover_image'], 'http')) {
|
||||||
|
$item['cover_image'] = image_domain_concat($item['cover_image']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $item;
|
||||||
|
});
|
||||||
|
|
||||||
|
return success('success', $list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品详情
|
||||||
|
*/
|
||||||
|
public function detail()
|
||||||
|
{
|
||||||
|
$id = request()->param('id');
|
||||||
|
|
||||||
|
$product = ProductModel::with([
|
||||||
|
// 关联分类
|
||||||
|
'category' => fn($query) => $query->field(['id', 'name']),
|
||||||
|
// 关联属性
|
||||||
|
'params' => fn($query) => $query->field(['product_id', 'name', 'value'])
|
||||||
|
->hidden(['product_id']),
|
||||||
|
// 关联sku
|
||||||
|
'skus' => fn($query) => $query->withoutField(['created_at', 'updated_at'])
|
||||||
|
->with([
|
||||||
|
'sku_attr' => fn($query) => $query->with('attr')->hidden(['sku_id', 'attr_id'])
|
||||||
|
])
|
||||||
|
->hidden(['id', 'product_id']),
|
||||||
|
// 关联购买链接
|
||||||
|
'links' => fn($query) => $query->field(['product_id', 'platform_id', 'link'])
|
||||||
|
->with(['platform' => fn($query) => $query->field(['id', 'platform'])])
|
||||||
|
->hidden(['product_id', 'platform_id']),
|
||||||
|
// 关联相关产品
|
||||||
|
'related' => fn($query) => $query->field(['product_id', 'related_product_id'])
|
||||||
|
->with([
|
||||||
|
'product' => fn($query) => $query->field(['id', 'name', 'spu', 'cover_image'])->withBind([
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'spu',
|
||||||
|
'cover_image'
|
||||||
|
])
|
||||||
|
])
|
||||||
|
->hidden(['product_id', 'related_product_id'])
|
||||||
|
])
|
||||||
|
->withoutField([
|
||||||
|
'language_id',
|
||||||
|
'stock_qty',
|
||||||
|
'seo_title',
|
||||||
|
'seo_keywords',
|
||||||
|
'seo_desc',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
'deleted_at'
|
||||||
|
])
|
||||||
|
->bypk($id)
|
||||||
|
->hidden(['category_id'])
|
||||||
|
->find();
|
||||||
|
|
||||||
|
// 处理封面图
|
||||||
|
if (!empty($product['cover_image']) && !str_starts_with($product['cover_image'], 'http')) {
|
||||||
|
$product['cover_image'] = image_domain_concat($product['cover_image']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理视频图片
|
||||||
|
if (!empty($product['video_img']) && !str_starts_with($product['video_img'], 'http')) {
|
||||||
|
$product['video_img'] = image_domain_concat($product['video_img']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理视频
|
||||||
|
if (!empty($product['video_url']) && !str_starts_with($product['video_url'], 'http')) {
|
||||||
|
$product['video_url'] = video_domain_concat($product['video_url']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理详情内容中图片
|
||||||
|
if (!empty($product['detail'])) {
|
||||||
|
$product['detail'] = html_image_replace($product['detail']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理sku中图片
|
||||||
|
if (!empty($product['skus'])) {
|
||||||
|
$skus = $product['skus']->toArray();
|
||||||
|
foreach ($skus as $key => $sku) {
|
||||||
|
// sku二级列表图
|
||||||
|
if (!empty($sku['main_image']) && !str_starts_with($sku['main_image'], 'http')) {
|
||||||
|
$skus[$key]['main_image'] = image_domain_concat($sku['main_image']);
|
||||||
|
}
|
||||||
|
// sku相册图
|
||||||
|
if (!empty($sku['photo_album'])) {
|
||||||
|
$photo_album = json_decode($sku['photo_album'], true);
|
||||||
|
foreach ($photo_album as $idx => $photo) {
|
||||||
|
if (!empty($photo) && !str_starts_with($photo, 'http')) {
|
||||||
|
$photo_album[$idx] = image_domain_concat($photo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$skus[$key]['photo_album'] = $photo_album;
|
||||||
|
}
|
||||||
|
// sku属性图片
|
||||||
|
foreach ($sku['sku_attr'] as $idx => $attr) {
|
||||||
|
if (!empty($attr['attr_value']) && !str_starts_with($attr['attr_value'], 'http')) {
|
||||||
|
$skus[$key]['sku_attr'][$idx]['attr_value'] = image_domain_concat($attr['attr_value']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$product['skus'] = $skus;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理相关产品中图片
|
||||||
|
if (!empty($product['related'])) {
|
||||||
|
$related = $product['related']->toArray();
|
||||||
|
foreach ($related as $key => $item) {
|
||||||
|
if (!empty($item['cover_image']) && !str_starts_with($item['cover_image'], 'http')) {
|
||||||
|
$related[$key]['cover_image'] = image_domain_concat($item['cover_image']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$product['related'] = $related;
|
||||||
|
}
|
||||||
|
|
||||||
|
return success('success', $product);
|
||||||
|
}
|
||||||
|
}
|
||||||
50
app/openapi/controller/v1/ProductCategory.php
Normal file
50
app/openapi/controller/v1/ProductCategory.php
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\openapi\controller\v1;
|
||||||
|
|
||||||
|
use app\openapi\model\ProductCategoryModel;
|
||||||
|
|
||||||
|
class ProductCategory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 产品分类列表
|
||||||
|
*/
|
||||||
|
public function list()
|
||||||
|
{
|
||||||
|
$params = request()->get([
|
||||||
|
'parent_id',
|
||||||
|
'language' => 'zh-cn',
|
||||||
|
'page' => 1,
|
||||||
|
'size' => 50
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($params['size'] > 200) {
|
||||||
|
// 每页不超过200条
|
||||||
|
$params['size'] = 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
$categories = ProductCategoryModel::withoutField([
|
||||||
|
'language_id',
|
||||||
|
'unique_id',
|
||||||
|
'related_tco_category',
|
||||||
|
'created_at',
|
||||||
|
'updated_at'
|
||||||
|
])
|
||||||
|
->language($params['language']??'zh-cn')
|
||||||
|
->parent($params['parent_id']??null)
|
||||||
|
->paginate([
|
||||||
|
'list_rows' => $params['size'],
|
||||||
|
'page' => $params['page']
|
||||||
|
])
|
||||||
|
->each(function($item) {
|
||||||
|
if (!empty($item['icon']) && !str_starts_with($item['icon'], 'http')) {
|
||||||
|
$item['icon'] = image_domain_concat($item['icon']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $item;
|
||||||
|
});
|
||||||
|
|
||||||
|
return success('success', $categories);
|
||||||
|
}
|
||||||
|
}
|
||||||
5
app/openapi/event.php
Normal file
5
app/openapi/event.php
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
// 这是系统自动生成的event定义文件
|
||||||
|
return [
|
||||||
|
|
||||||
|
];
|
||||||
5
app/openapi/middleware.php
Normal file
5
app/openapi/middleware.php
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
// 这是系统自动生成的middleware定义文件
|
||||||
|
return [
|
||||||
|
|
||||||
|
];
|
||||||
31
app/openapi/middleware/Auth.php
Normal file
31
app/openapi/middleware/Auth.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\openapi\middleware;
|
||||||
|
|
||||||
|
use OAuth2\OAuth2;
|
||||||
|
use OAuth2\OAuth2ServerException;
|
||||||
|
use oauth\OAuthStorage;
|
||||||
|
|
||||||
|
class Auth
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 处理请求
|
||||||
|
*
|
||||||
|
* @param \think\Request $request
|
||||||
|
* @param \Closure $next
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
public function handle($request, \Closure $next)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$oauth = new OAuth2(new OAuthStorage);
|
||||||
|
$token = $oauth->getBearerToken();
|
||||||
|
$oauth->verifyAccessToken($token);
|
||||||
|
} catch (OAuth2ServerException $e) {
|
||||||
|
return error('Unauthorized', $e->sendHttpResponse(), 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
34
app/openapi/model/ArticleCategoryModel.php
Normal file
34
app/openapi/model/ArticleCategoryModel.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\openapi\model;
|
||||||
|
|
||||||
|
use app\common\model\ArticleCategoryBaseModel;
|
||||||
|
use think\facade\Db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文章分类模型
|
||||||
|
* @mixin \think\Model
|
||||||
|
*/
|
||||||
|
class ArticleCategoryModel extends ArticleCategoryBaseModel
|
||||||
|
{
|
||||||
|
// 所属语言范围查询
|
||||||
|
public function scopeLanguage($query, $language = 'zh-cn')
|
||||||
|
{
|
||||||
|
$query->whereExists(function($subquery) use($language) {
|
||||||
|
$lang_model = new LanguageModel;
|
||||||
|
$subquery->model($lang_model)
|
||||||
|
->name($lang_model->getName())
|
||||||
|
->field(['id'])
|
||||||
|
->where('id', '=', Db::raw($this->getTable() . '.language_id'))
|
||||||
|
->where('code', '=', $language);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所属上级分类范围查询
|
||||||
|
public function scopeParent($query, $parent_id)
|
||||||
|
{
|
||||||
|
if (is_null($parent_id)) return;
|
||||||
|
$query->where('pid', '=', $parent_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
40
app/openapi/model/ArticleModel.php
Normal file
40
app/openapi/model/ArticleModel.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\openapi\model;
|
||||||
|
|
||||||
|
use app\common\model\ArticleBaseModel;
|
||||||
|
use think\facade\Db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文章模型
|
||||||
|
* @mixin \think\Model
|
||||||
|
*/
|
||||||
|
class ArticleModel extends ArticleBaseModel
|
||||||
|
{
|
||||||
|
// 关联分类
|
||||||
|
public function category()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(ArticleCategoryModel::class, 'category_id', 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所属语言范围查询
|
||||||
|
public function scopeLanguage($query, $language = 'zh-cn')
|
||||||
|
{
|
||||||
|
$query->whereExists(function($subquery) use($language) {
|
||||||
|
$lang_model = new LanguageModel;
|
||||||
|
$subquery->model($lang_model)
|
||||||
|
->name($lang_model->getName())
|
||||||
|
->field(['id'])
|
||||||
|
->where('id', '=', Db::raw($this->getTable() . '.language_id'))
|
||||||
|
->where('code', '=', $language);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所属分类范围查询
|
||||||
|
public function scopeCategoryId($query, $category_id)
|
||||||
|
{
|
||||||
|
if (is_null($category_id)) return;
|
||||||
|
$query->where('category_id', '=', $category_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
app/openapi/model/LanguageModel.php
Normal file
19
app/openapi/model/LanguageModel.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\openapi\model;
|
||||||
|
|
||||||
|
use app\common\model\LanguageBaseModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语言模型
|
||||||
|
* @mixin \think\Model
|
||||||
|
*/
|
||||||
|
class LanguageModel extends LanguageBaseModel
|
||||||
|
{
|
||||||
|
// 所属code范围查询
|
||||||
|
public function scopeCode($query, $code)
|
||||||
|
{
|
||||||
|
$query->where('code', '=', $code);
|
||||||
|
}
|
||||||
|
}
|
||||||
32
app/openapi/model/OAuthAccessTokenModel.php
Normal file
32
app/openapi/model/OAuthAccessTokenModel.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\openapi\model;
|
||||||
|
|
||||||
|
use think\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @mixin \think\Model
|
||||||
|
*/
|
||||||
|
class OAuthAccessTokenModel extends Model
|
||||||
|
{
|
||||||
|
// 表名
|
||||||
|
protected $name = 'oauth_access_token';
|
||||||
|
|
||||||
|
// 字段信息
|
||||||
|
protected $schema = [
|
||||||
|
'id' => 'int',
|
||||||
|
'client_id' => 'string',
|
||||||
|
'user_id' => 'int',
|
||||||
|
'access_token' => 'string',
|
||||||
|
'expires' => 'int',
|
||||||
|
'scope' => 'string',
|
||||||
|
'created_at' => 'datetime',
|
||||||
|
];
|
||||||
|
|
||||||
|
// access_token范围查询
|
||||||
|
public function scopeAccessToken($query, $access_token)
|
||||||
|
{
|
||||||
|
$query->where('access_token', '=', $access_token);
|
||||||
|
}
|
||||||
|
}
|
||||||
33
app/openapi/model/OAuthAuthCodeModel.php
Normal file
33
app/openapi/model/OAuthAuthCodeModel.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\openapi\model;
|
||||||
|
|
||||||
|
use think\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @mixin \think\Model
|
||||||
|
*/
|
||||||
|
class OAuthAuthCodeModel extends Model
|
||||||
|
{
|
||||||
|
// 表名
|
||||||
|
protected $name = 'oauth_auth_code';
|
||||||
|
|
||||||
|
// 字段信息
|
||||||
|
protected $schema = [
|
||||||
|
'id' => 'int',
|
||||||
|
'code' => 'string',
|
||||||
|
'client_id' => 'string',
|
||||||
|
'user_id' => 'int',
|
||||||
|
'expires' => 'int',
|
||||||
|
'redirect_uri' => 'string',
|
||||||
|
'scope' => 'string',
|
||||||
|
'created_at' => 'datetime',
|
||||||
|
];
|
||||||
|
|
||||||
|
// code范围查询
|
||||||
|
public function scopeCode($query, $code)
|
||||||
|
{
|
||||||
|
$query->where('code', '=', $code);
|
||||||
|
}
|
||||||
|
}
|
||||||
33
app/openapi/model/OAuthClientModel.php
Normal file
33
app/openapi/model/OAuthClientModel.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\openapi\model;
|
||||||
|
|
||||||
|
use think\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @mixin \think\Model
|
||||||
|
*/
|
||||||
|
class OAuthClientModel extends Model
|
||||||
|
{
|
||||||
|
// 表名
|
||||||
|
protected $name = 'oauth_client';
|
||||||
|
|
||||||
|
// 字段信息
|
||||||
|
protected $schema = [
|
||||||
|
'id' => 'int',
|
||||||
|
'client_id' => 'string',
|
||||||
|
'client_secret' => 'string',
|
||||||
|
'redirect_uri' => 'string',
|
||||||
|
'enabled' => 'int',
|
||||||
|
'created_at' => 'datetime',
|
||||||
|
'updated_at' => 'datetime',
|
||||||
|
'deleted_at' => 'datetime'
|
||||||
|
];
|
||||||
|
|
||||||
|
// client_id范围查询
|
||||||
|
public function scopeClientId($query, $client_id)
|
||||||
|
{
|
||||||
|
$query->where('client_id', '=', $client_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
32
app/openapi/model/OAuthRefreshTokenModel.php
Normal file
32
app/openapi/model/OAuthRefreshTokenModel.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\openapi\model;
|
||||||
|
|
||||||
|
use think\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @mixin \think\Model
|
||||||
|
*/
|
||||||
|
class OAuthRefreshTokenModel extends Model
|
||||||
|
{
|
||||||
|
// 表名
|
||||||
|
protected $name = 'oauth_refresh_token';
|
||||||
|
|
||||||
|
// 字段信息
|
||||||
|
protected $schema = [
|
||||||
|
'id' => 'int',
|
||||||
|
'client_id' => 'string',
|
||||||
|
'user_id' => 'int',
|
||||||
|
'refresh_token' => 'string',
|
||||||
|
'expires' => 'int',
|
||||||
|
'scope' => 'string',
|
||||||
|
'created_at' => 'datetime'
|
||||||
|
];
|
||||||
|
|
||||||
|
// refresh_token范围查询
|
||||||
|
public function scopeRefreshToken($query, $refresh_token)
|
||||||
|
{
|
||||||
|
$query->where('refresh_token', '=', $refresh_token);
|
||||||
|
}
|
||||||
|
}
|
||||||
33
app/openapi/model/ProductCategoryModel.php
Normal file
33
app/openapi/model/ProductCategoryModel.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\openapi\model;
|
||||||
|
|
||||||
|
use app\common\model\ProductCategoryBaseModel;
|
||||||
|
use think\facade\Db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品分类模型
|
||||||
|
* @mixin \think\Model
|
||||||
|
*/
|
||||||
|
class ProductCategoryModel extends ProductCategoryBaseModel
|
||||||
|
{
|
||||||
|
// 所属语言
|
||||||
|
public function scopeLanguage($query, $language)
|
||||||
|
{
|
||||||
|
$query->whereExists(function($subquery) use($language) {
|
||||||
|
$lang_model = new LanguageModel;
|
||||||
|
$subquery->model($lang_model)
|
||||||
|
->name($lang_model->getName())
|
||||||
|
->where('id', '=', Db::Raw($this->getTable() . '.language_id'))
|
||||||
|
->where('code', '=', $language);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所属上级范围查询
|
||||||
|
public function scopeParent($query, $parent_id)
|
||||||
|
{
|
||||||
|
if (is_null($parent_id)) return;
|
||||||
|
$query->where('pid', '=', $parent_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
62
app/openapi/model/ProductModel.php
Normal file
62
app/openapi/model/ProductModel.php
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\openapi\model;
|
||||||
|
|
||||||
|
use app\common\model\ProductBaseModel;
|
||||||
|
use think\facade\Db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品模型
|
||||||
|
* @mixin \think\Model
|
||||||
|
*/
|
||||||
|
class ProductModel extends ProductBaseModel
|
||||||
|
{
|
||||||
|
// 关联分类
|
||||||
|
public function category()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(ProductCategoryModel::class, 'category_id', 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关联产品参数
|
||||||
|
public function params()
|
||||||
|
{
|
||||||
|
return $this->hasMany(ProductParamsModel::class, 'product_id', 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关联sku
|
||||||
|
public function skus()
|
||||||
|
{
|
||||||
|
return $this->hasMany(ProductSkuModel::class, 'product_id', 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关联购买链接
|
||||||
|
public function links()
|
||||||
|
{
|
||||||
|
return $this->hasMany(ProductPurchaseLinkModel::class, 'product_id', 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关联相关产品
|
||||||
|
public function related()
|
||||||
|
{
|
||||||
|
return $this->hasMany(ProductRelatedModel::class, 'product_id', 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据所属语言范围查询
|
||||||
|
public function scopeLanguage($query, $language)
|
||||||
|
{
|
||||||
|
$query->whereExists(function($subquery) use($language) {
|
||||||
|
$lang_model = new LanguageModel;
|
||||||
|
$subquery->model($lang_model)
|
||||||
|
->name($lang_model->getName())
|
||||||
|
->where('id', '=', Db::Raw($this->getTable() . '.language_id'))
|
||||||
|
->where('code', '=', $language);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据category_id范围查询
|
||||||
|
public function scopeCategoryId($query, $category_id)
|
||||||
|
{
|
||||||
|
$query->where('category_id', '=', $category_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
app/openapi/model/ProductParamsModel.php
Normal file
15
app/openapi/model/ProductParamsModel.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\openapi\model;
|
||||||
|
|
||||||
|
use app\common\model\ProductParamsBaseModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品参数模型
|
||||||
|
* @mixin \think\Model
|
||||||
|
*/
|
||||||
|
class ProductParamsModel extends ProductParamsBaseModel
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
19
app/openapi/model/ProductPurchaseLinkModel.php
Normal file
19
app/openapi/model/ProductPurchaseLinkModel.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\openapi\model;
|
||||||
|
|
||||||
|
use app\common\model\ProductPurchaseLinkBaseModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品购买链接模型
|
||||||
|
* @mixin \think\Model
|
||||||
|
*/
|
||||||
|
class ProductPurchaseLinkModel extends ProductPurchaseLinkBaseModel
|
||||||
|
{
|
||||||
|
// 关联购买平台
|
||||||
|
public function platform()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(ProductPurchasePlatformModel::class, 'platform_id', 'id')->bind(['platform']);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
app/openapi/model/ProductPurchasePlatformModel.php
Normal file
15
app/openapi/model/ProductPurchasePlatformModel.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\openapi\model;
|
||||||
|
|
||||||
|
use app\common\model\ProductPurchasePlatformBaseModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品购买链接平台模型
|
||||||
|
* @mixin \think\Model
|
||||||
|
*/
|
||||||
|
class ProductPurchasePlatformModel extends ProductPurchasePlatformBaseModel
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
19
app/openapi/model/ProductRelatedModel.php
Normal file
19
app/openapi/model/ProductRelatedModel.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\openapi\model;
|
||||||
|
|
||||||
|
use app\common\model\ProductRelatedBaseModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品 - 相关产品模型
|
||||||
|
* @mixin \think\Model
|
||||||
|
*/
|
||||||
|
class ProductRelatedModel extends ProductRelatedBaseModel
|
||||||
|
{
|
||||||
|
// 关联产品
|
||||||
|
public function product()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(ProductModel::class, 'related_product_id', 'id');
|
||||||
|
}
|
||||||
|
}
|
||||||
19
app/openapi/model/ProductSkuAttrModel.php
Normal file
19
app/openapi/model/ProductSkuAttrModel.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\openapi\model;
|
||||||
|
|
||||||
|
use app\common\model\ProductSkuAttrBaseModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品sku属性模型
|
||||||
|
* @mixin \think\Model
|
||||||
|
*/
|
||||||
|
class ProductSkuAttrModel extends ProductSkuAttrBaseModel
|
||||||
|
{
|
||||||
|
// 关联属性
|
||||||
|
public function attr()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(\app\common\model\ProductAttrBaseModel::class, 'attr_id', 'id')->bind(['attr_name']);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
app/openapi/model/ProductSkuModel.php
Normal file
19
app/openapi/model/ProductSkuModel.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\openapi\model;
|
||||||
|
|
||||||
|
use app\common\model\ProductSkuBaseModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品sku模型
|
||||||
|
* @mixin \think\Model
|
||||||
|
*/
|
||||||
|
class ProductSkuModel extends ProductSkuBaseModel
|
||||||
|
{
|
||||||
|
// 关联属性
|
||||||
|
public function skuAttr()
|
||||||
|
{
|
||||||
|
return $this->hasMany(ProductSkuAttrModel::class, 'sku_id', 'id');
|
||||||
|
}
|
||||||
|
}
|
||||||
39
app/openapi/route/v1.php
Normal file
39
app/openapi/route/v1.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use think\facade\Route;
|
||||||
|
|
||||||
|
Route::group('v1', function() {
|
||||||
|
// 接口授权
|
||||||
|
Route::post('authorize', 'v1.Authorize/token');
|
||||||
|
|
||||||
|
// 接口授权后才能访问
|
||||||
|
Route::group(function(){
|
||||||
|
// 获取产品列表
|
||||||
|
Route::get('products', 'v1.Product/list');
|
||||||
|
Route::group('product', function() {
|
||||||
|
// 获取产品信息
|
||||||
|
Route::get(':id', 'v1.Product/detail')->when('id', 'number');
|
||||||
|
|
||||||
|
// 获取产品分类
|
||||||
|
Route::get('categories', 'v1.ProductCategory/list');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取文章动态
|
||||||
|
Route::get('articles', 'v1.Article/list');
|
||||||
|
Route::group('article', function() {
|
||||||
|
// 获取文章详情
|
||||||
|
Route::get(':id', 'v1.Article/detail')->when('id', 'number');
|
||||||
|
|
||||||
|
// 获取文章分类
|
||||||
|
Route::get('categories', 'v1.ArticleCategory/list');
|
||||||
|
});
|
||||||
|
})
|
||||||
|
->middleware(\app\openapi\middleware\Auth::class);
|
||||||
|
})
|
||||||
|
->middleware(\think\middleware\Throttle::class, [
|
||||||
|
'visit_rate' => '5/m',
|
||||||
|
'visit_fail_response' => function (\think\middleware\Throttle $throttle, \think\Request $request, int $wait_seconds) {
|
||||||
|
return \think\Response::create('您的操作过于频繁, 请在 ' . $wait_seconds . ' 秒后再试。')->code(429);
|
||||||
|
},
|
||||||
|
])
|
||||||
|
->completeMatch(true);
|
||||||
@@ -32,7 +32,8 @@
|
|||||||
"topthink/think-throttle": "^2.0",
|
"topthink/think-throttle": "^2.0",
|
||||||
"intervention/image": "^3.10",
|
"intervention/image": "^3.10",
|
||||||
"topthink/think-cors": "^1.0",
|
"topthink/think-cors": "^1.0",
|
||||||
"phpoffice/phpspreadsheet": "^3.8"
|
"phpoffice/phpspreadsheet": "^3.8",
|
||||||
|
"friendsofsymfony/oauth2-php": "^1.3"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"symfony/var-dumper": ">=4.2",
|
"symfony/var-dumper": ">=4.2",
|
||||||
|
|||||||
42
database/migrations/20250519083010_create_oauth_client.php
Normal file
42
database/migrations/20250519083010_create_oauth_client.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use think\migration\Migrator;
|
||||||
|
use think\migration\db\Column;
|
||||||
|
|
||||||
|
class CreateOauthClient extends Migrator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Change Method.
|
||||||
|
*
|
||||||
|
* Write your reversible migrations using this method.
|
||||||
|
*
|
||||||
|
* More information on writing migrations is available here:
|
||||||
|
* http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
|
||||||
|
*
|
||||||
|
* The following commands can be used in this method and Phinx will
|
||||||
|
* automatically reverse them when rolling back:
|
||||||
|
*
|
||||||
|
* createTable
|
||||||
|
* renameTable
|
||||||
|
* addColumn
|
||||||
|
* renameColumn
|
||||||
|
* addIndex
|
||||||
|
* addForeignKey
|
||||||
|
*
|
||||||
|
* Remember to call "create()" or "update()" and NOT "save()" when working
|
||||||
|
* with the Table class.
|
||||||
|
*/
|
||||||
|
public function change()
|
||||||
|
{
|
||||||
|
$table = $this->table('oauth_client', ['engine' => 'MyISAM', 'collation' => 'utf8mb4_general_ci', 'comment' => 'OAuth 客户端表']);
|
||||||
|
$table->addColumn('agent_id', 'integer', ['limit' => 10, 'null' => true, 'comment' => '代理ID'])
|
||||||
|
->addColumn('client_id', 'string', ['limit' => 32, 'comment' => '客户端id'])
|
||||||
|
->addColumn('client_secret', 'string', ['limit' => 128, 'comment' => '客户端密钥'])
|
||||||
|
->addColumn('redirect_uri', 'string', ['limit' => 255, 'comment' => '回调地址'])
|
||||||
|
->addColumn('enabled', 'boolean', ['default' => 1, 'comment' => '是否启用'])
|
||||||
|
->addColumn('created_at', 'timestamp', ['null' => false, 'default' => 'CURRENT_TIMESTAMP', 'comment' => '创建时间'])
|
||||||
|
->addColumn('updated_at', 'timestamp', ['null' => false, 'default' => 'CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP', 'comment' => '更新时间'])
|
||||||
|
->addColumn('deleted_at', 'timestamp', ['null' => true, 'default' => null, 'comment' => '删除时间'])
|
||||||
|
->create();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use think\migration\Migrator;
|
||||||
|
use think\migration\db\Column;
|
||||||
|
|
||||||
|
class CreateOauthAuthCode extends Migrator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Change Method.
|
||||||
|
*
|
||||||
|
* Write your reversible migrations using this method.
|
||||||
|
*
|
||||||
|
* More information on writing migrations is available here:
|
||||||
|
* http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
|
||||||
|
*
|
||||||
|
* The following commands can be used in this method and Phinx will
|
||||||
|
* automatically reverse them when rolling back:
|
||||||
|
*
|
||||||
|
* createTable
|
||||||
|
* renameTable
|
||||||
|
* addColumn
|
||||||
|
* renameColumn
|
||||||
|
* addIndex
|
||||||
|
* addForeignKey
|
||||||
|
*
|
||||||
|
* Remember to call "create()" or "update()" and NOT "save()" when working
|
||||||
|
* with the Table class.
|
||||||
|
*/
|
||||||
|
public function change()
|
||||||
|
{
|
||||||
|
$table = $this->table('oauth_auth_code', ['engine' => 'MyISAM', 'collation' => 'utf8mb4_general_ci', 'comment' => '授权码表']);
|
||||||
|
$table->addColumn('code', 'string', ['limit' => 64, 'null' => false, 'default' => '', 'comment' => '授权码'])
|
||||||
|
->addColumn('client_id', 'integer', ['limit' => 11, 'null' => false, 'default' => 0, 'comment' => '客户端ID'])
|
||||||
|
->addColumn('user_id', 'integer', ['limit' => 11, 'null' => false, 'default' => 0, 'comment' => '用户ID'])
|
||||||
|
->addColumn('expires', 'integer', ['null' => true, 'comment' => '过期时间'])
|
||||||
|
->addColumn('redirect_uri', 'string', ['limit' => 255, 'null' => false, 'default' => '', 'comment' => '回调地址'])
|
||||||
|
->addColumn('scope', 'string', ['limit' => 255, 'null' => false, 'default' => '', 'comment' => '授权范围'])
|
||||||
|
->addColumn('created_at', 'timestamp', ['null' => false, 'default' => 'CURRENT_TIMESTAMP', 'comment' => '创建时间'])
|
||||||
|
->create();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use think\migration\Migrator;
|
||||||
|
use think\migration\db\Column;
|
||||||
|
|
||||||
|
class CreateOauthAccessToken extends Migrator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Change Method.
|
||||||
|
*
|
||||||
|
* Write your reversible migrations using this method.
|
||||||
|
*
|
||||||
|
* More information on writing migrations is available here:
|
||||||
|
* http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
|
||||||
|
*
|
||||||
|
* The following commands can be used in this method and Phinx will
|
||||||
|
* automatically reverse them when rolling back:
|
||||||
|
*
|
||||||
|
* createTable
|
||||||
|
* renameTable
|
||||||
|
* addColumn
|
||||||
|
* renameColumn
|
||||||
|
* addIndex
|
||||||
|
* addForeignKey
|
||||||
|
*
|
||||||
|
* Remember to call "create()" or "update()" and NOT "save()" when working
|
||||||
|
* with the Table class.
|
||||||
|
*/
|
||||||
|
public function change()
|
||||||
|
{
|
||||||
|
$table = $this->table('oauth_access_token', ['engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => 'OAuth2访问令牌表']);
|
||||||
|
$table->addColumn('client_id', 'string', ['limit' => 32, 'default' => '', 'comment' => '客户端ID'])
|
||||||
|
->addColumn('user_id', 'integer', ['limit' => 10, 'default' => 0, 'comment' => '用户ID'])
|
||||||
|
->addColumn('access_token', 'string', ['limit' => 64, 'default' => '', 'comment' => '访问令牌'])
|
||||||
|
->addColumn('expires', 'integer', ['limit' => 11, 'default' => 0, 'comment' => '过期时间'])
|
||||||
|
->addColumn('scope', 'string', ['limit' => 2000, 'default' => '', 'comment' => '授权范围'])
|
||||||
|
->addColumn('created_at', 'timestamp', ['default' => 'CURRENT_TIMESTAMP', 'comment' => '创建时间'])
|
||||||
|
->create();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use think\migration\Migrator;
|
||||||
|
use think\migration\db\Column;
|
||||||
|
|
||||||
|
class CreateOauthRefreshToken extends Migrator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Change Method.
|
||||||
|
*
|
||||||
|
* Write your reversible migrations using this method.
|
||||||
|
*
|
||||||
|
* More information on writing migrations is available here:
|
||||||
|
* http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
|
||||||
|
*
|
||||||
|
* The following commands can be used in this method and Phinx will
|
||||||
|
* automatically reverse them when rolling back:
|
||||||
|
*
|
||||||
|
* createTable
|
||||||
|
* renameTable
|
||||||
|
* addColumn
|
||||||
|
* renameColumn
|
||||||
|
* addIndex
|
||||||
|
* addForeignKey
|
||||||
|
*
|
||||||
|
* Remember to call "create()" or "update()" and NOT "save()" when working
|
||||||
|
* with the Table class.
|
||||||
|
*/
|
||||||
|
public function change()
|
||||||
|
{
|
||||||
|
$table = $this->table('oauth_refresh_token', ['engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => 'OAuth2刷新令牌表']);
|
||||||
|
$table->addColumn('client_id', 'string', ['limit' => 32, 'default' => '', 'comment' => '客户端ID'])
|
||||||
|
->addColumn('user_id', 'integer', ['limit' => 10, 'default' => 0, 'comment' => '用户ID'])
|
||||||
|
->addColumn('refresh_token', 'string', ['limit' => 64, 'default' => '', 'comment' => '刷新令牌'])
|
||||||
|
->addColumn('expires', 'integer', ['limit' => 11, 'default' => 0, 'comment' => '过期时间'])
|
||||||
|
->addColumn('scope', 'string', ['limit' => 2000, 'default' => '', 'comment' => '授权范围'])
|
||||||
|
->addColumn('created_at', 'timestamp', ['default' => 'CURRENT_TIMESTAMP', 'comment' => '创建时间'])
|
||||||
|
->create();
|
||||||
|
}
|
||||||
|
}
|
||||||
167
extend/oauth/OAuthStorage.php
Normal file
167
extend/oauth/OAuthStorage.php
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
<?php
|
||||||
|
namespace oauth;
|
||||||
|
|
||||||
|
use app\openapi\model\OAuthAccessTokenModel;
|
||||||
|
use app\openapi\model\OAuthAuthCodeModel;
|
||||||
|
use app\openapi\model\OAuthClientModel;
|
||||||
|
use app\openapi\model\OAuthRefreshTokenModel;
|
||||||
|
use OAuth2\IOAuth2GrantClient;
|
||||||
|
use OAuth2\IOAuth2GrantCode;
|
||||||
|
use OAuth2\IOAuth2RefreshTokens;
|
||||||
|
use OAuth2\Model\IOAuth2AccessToken;
|
||||||
|
use OAuth2\Model\IOAuth2AuthCode;
|
||||||
|
use OAuth2\Model\IOAuth2Client;
|
||||||
|
use OAuth2\Model\IOAuth2Token;
|
||||||
|
use OAuth2\Model\OAuth2AccessToken;
|
||||||
|
use OAuth2\Model\OAuth2AuthCode;
|
||||||
|
use OAuth2\Model\OAuth2Client;
|
||||||
|
use OAuth2\Model\OAuth2Token;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OAuth2存储实现
|
||||||
|
*/
|
||||||
|
class OAuthStorage implements IOAuth2GrantCode, IOAuth2RefreshTokens, IOAuth2GrantClient
|
||||||
|
{
|
||||||
|
private $salt = '';
|
||||||
|
|
||||||
|
public function __construct($salt = null)
|
||||||
|
{
|
||||||
|
if (!is_null($salt)) {
|
||||||
|
$this->salt = $salt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addClient($client_id, $client_secret, $redirect_uri)
|
||||||
|
{
|
||||||
|
// 实现添加客户端的逻辑
|
||||||
|
$client = OAuthClientModel::create([
|
||||||
|
'client_id' => $client_id,
|
||||||
|
'client_secret' => hash('sha1', $client_id . $client_secret . $this->salt),
|
||||||
|
'redirect_uri' => $redirect_uri,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return !$client->isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAuthCode($code): IOAuth2AuthCode
|
||||||
|
{
|
||||||
|
// 实现获取授权码的逻辑
|
||||||
|
$ret = OAuthAuthCodeModel::code($code)->find();
|
||||||
|
if (is_null($ret)) {
|
||||||
|
throw new \Exception('授权码不存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OAuth2AuthCode($ret->client_id, '', $ret->expires, $ret->scope, null, $ret->redirect_uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createAuthCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null)
|
||||||
|
{
|
||||||
|
// 实现创建授权码的逻辑
|
||||||
|
$auth = OAuthAuthCodeModel::create([
|
||||||
|
'code' => $code,
|
||||||
|
'client_id' => $client_id,
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'redirect_uri' => $redirect_uri,
|
||||||
|
'expires' => $expires,
|
||||||
|
'scope' => $scope,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return !$auth->isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function markAuthCodeAsUsed($code)
|
||||||
|
{
|
||||||
|
// 实现标记授权码为已使用的逻辑
|
||||||
|
OAuthAuthCodeModel::code($code)->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClient($client_id): IOAuth2Client
|
||||||
|
{
|
||||||
|
// 实现获取客户端的逻辑
|
||||||
|
$ret = OAuthClientModel::clientId($client_id)->find();
|
||||||
|
if (is_null($ret)) {
|
||||||
|
throw new \Exception('客户端不存在');
|
||||||
|
}
|
||||||
|
if ($ret->enabled != 1) {
|
||||||
|
throw new \Exception('客户端已禁用');
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OAuth2Client($ret->client_id, $ret->client_secret, [$ret->redirect_uri]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkClientCredentials(IOAuth2Client $client, $client_secret = null): bool
|
||||||
|
{
|
||||||
|
// 实现检查客户端凭证的逻辑
|
||||||
|
$client = OAuthClientModel::clientId($client->getPublicId())->find();
|
||||||
|
if (is_null($client)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $client->client_secret == hash('sha1', $client->client_id . $client_secret . $this->salt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAccessToken($access_token): IOAuth2AccessToken
|
||||||
|
{
|
||||||
|
// 实现获取访问令牌的逻辑
|
||||||
|
$ret = OAuthAccessTokenModel::accessToken($access_token)->find();
|
||||||
|
if (is_null($ret)) {
|
||||||
|
throw new \Exception('访问令牌不存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OAuth2AccessToken($ret->client_id, $ret->access_token, $ret->expires, $ret->scope, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createAccessToken($access_token, IOAuth2Client $client, $user_id, $expires, $scope = null)
|
||||||
|
{
|
||||||
|
// 实现创建访问令牌的逻辑
|
||||||
|
OAuthAccessTokenModel::create([
|
||||||
|
'access_token' => $access_token,
|
||||||
|
'client_id' => $client->getPublicId(),
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'expires' => $expires,
|
||||||
|
'scope' => $scope,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkClientCredentialsGrant(IOAuth2Client $client, $client_secret): array
|
||||||
|
{
|
||||||
|
// 实现检查受限授权类型的逻辑
|
||||||
|
return ['issue_refresh_token' => true];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkRestrictedGrantType(IOAuth2Client $client, $grant_type): bool
|
||||||
|
{
|
||||||
|
// 实现检查受限授权类型的逻辑
|
||||||
|
return $grant_type == 'client_credentials' || $grant_type == 'refresh_token';
|
||||||
|
}
|
||||||
|
|
||||||
|
// IOAuth2RefreshTokens 接口方法实现
|
||||||
|
public function getRefreshToken($refresh_token): IOAuth2Token
|
||||||
|
{
|
||||||
|
// 实现获取刷新令牌的逻辑
|
||||||
|
$ret = OAuthRefreshTokenModel::refreshToken($refresh_token)->find();
|
||||||
|
if (is_null($ret)) {
|
||||||
|
throw new \Exception('刷新令牌不存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OAuth2Token($ret->client_id, $ret->refresh_token, $ret->expires, $ret->scope, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createRefreshToken($refresh_token, IOAuth2Client $client, $user_id, $expires, $scope = null)
|
||||||
|
{
|
||||||
|
// 实现创建刷新令牌的逻辑
|
||||||
|
OAuthRefreshTokenModel::create([
|
||||||
|
'refresh_token' => $refresh_token,
|
||||||
|
'client_id' => $client->getPublicId(),
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'expires' => $expires,
|
||||||
|
'scope' => $scope,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function unsetRefreshToken($refresh_token)
|
||||||
|
{
|
||||||
|
// 实现注销刷新令牌的逻辑
|
||||||
|
OAuthRefreshTokenModel::refreshToken($refresh_token)->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user