Commit 74fb33ac 74fb33ac3bf3d7f2944395999c7bae0a3b599bf6 by wei.feng

商用授权接口

1 parent 79ac923d
......@@ -22,8 +22,19 @@ class ErrorCode
const WITHDRAW_CONFIRM_BILLS_FAIL = 40053;
const WITHDRAW_APPLY_FAIL = 40054;
const WITHDRAW_FAIL = 40055;
const INVALID_CLIENT_ID = 40013;
const INVALID_ACCESS_TOKEN = 40014;
const INVALID_SIGNATURE = 40015;
const MISSING_ACCESS_TOKEN = 41001;
const MISSING_CLIENT_ID = 41002;
const MISSING_CLIENT_SECRET = 41003;
const MISSING_TIMESTAMP = 41004;
const MISSING_NONCE = 41005;
const API_NOT_AUTHORIZED = 48001;
const EMPTY_PARAMS = 63001;
const INVALID_TIMESTAMP = 40016;
const INVALID_NONCE = 40017;
const MISSING_PARAMS = 41000;
/**
......@@ -45,6 +56,19 @@ class ErrorCode
self::WITHDRAW_CONFIRM_BILLS_FAIL => '确认账单失败',
self::WITHDRAW_APPLY_FAIL => '提现申请失败',
self::WITHDRAW_FAIL => '提现失败',
self::INVALID_CLIENT_ID => '无效的client_id或client_secret',
self::INVALID_ACCESS_TOKEN => '无效的access_token; 或者填入的 client_id 与access_token代表的账号的 client_id 不一致',
self::INVALID_SIGNATURE => '无效的签名',
self::MISSING_ACCESS_TOKEN => '缺少access_token参数',
self::MISSING_CLIENT_ID => '缺少client_id参数',
self::MISSING_CLIENT_SECRET => '缺少client_secret参数',
self::MISSING_TIMESTAMP => '缺少timestamp参数',
self::MISSING_NONCE => '缺少nonce参数',
self::API_NOT_AUTHORIZED => 'api 功能未授权',
self::EMPTY_PARAMS => '部分参数为空',
self::INVALID_TIMESTAMP => '时间戳timestamp已失效',
self::INVALID_NONCE => '重复的nonce',
self::MISSING_PARAMS => '缺少必填参数'
];
......
......@@ -28,3 +28,23 @@ if (!function_exists('data_diff')) {
return $limit;
}
}
if (!function_exists('coverData')) {
/**
* 字符串转换为对应值的字符串
* @param $str
* @param $data
* @return string
*/
function coverData($str, $data): string
{
$arr = explode(',', $str);
$rs = [];
foreach ($arr as $value) {
if (isset($data[$value])) {
$rs[] = $data[$value];
}
}
return join(' ', $rs);
}
}
......
......@@ -58,4 +58,13 @@ class Response
return $paginator;
}
public static function successWithCode(string $code, string $msg, $data = [])
{
return response()->json([
'code' => $code,
'msg' => $msg,
'data' => $data
]);
}
}
......
<?php
namespace App\Http\Controllers\OpenApi;
use App\Helper\ErrorCode;
use App\Helper\RedisClient;
use App\Helper\Response;
use App\Models\Legal\ClientMap;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class ClientCredentialsController
{
const CHARS = ["a", "b", "c", "d", "e", "f",
"g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
"t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I",
"J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
"W", "X", "Y", "Z"];
const EXPIRE_IN = 7200;
const TOKEN_PREFIX = "access_token:";
/**
* 创建client获得id及secret
* @return JsonResponse
*/
public function getApp(): JsonResponse
{
$client_id = $this->getClientId();
$client_secret = $this->getClientSecret($client_id);
$rs = [
'client_id' => $client_id,
'client_secret' => $client_secret
];
ClientMap::create($rs);
return Response::successWithCode(ErrorCode::SERVER_OK, ErrorCode::$messages[ErrorCode::SERVER_OK], $rs);
}
/**
* 获取clientId
* @return string
*/
public function getClientId() : string
{
$uuid = str_replace('-', '', Str::uuid());
$client_id = '';
for ($i = 0; $i < 8; $i++) {
$s = Str::substr($uuid, $i * 4, $i * 4 + 4);
$client_id .= self::CHARS[base_convert($s, 16, 10) % 0x3e];
}
return $client_id;
}
/**
* 通过client_id获取client_secret
* @param string $client_id
* @return string
*/
public function getClientSecret(string $client_id) : string
{
return sha1($client_id . Str::uuid());
}
/**
* 使用client_id和secret获得access_token
* @param Request $request
* @return JsonResponse
*/
public function getAccessToken(Request $request): JsonResponse
{
if (!$request->has('client_id')) {
return Response::successWithCode(ErrorCode::MISSING_CLIENT_ID, ErrorCode::$messages[ErrorCode::MISSING_CLIENT_ID]);
}
if (!$request->has('client_secret')) {
return Response::successWithCode(ErrorCode::MISSING_CLIENT_SECRET, ErrorCode::$messages[ErrorCode::MISSING_CLIENT_SECRET]);
}
$conditions = $request->only(['client_id', 'client_secret']);
$client = ClientMap::where($conditions)->first();
if (!$client) {
return Response::successWithCode(ErrorCode::INVALID_CLIENT_ID, ErrorCode::$messages[ErrorCode::INVALID_CLIENT_ID]);
}
if (!$client->status) {
return Response::successWithCode(ErrorCode::API_NOT_AUTHORIZED, ErrorCode::$messages[ErrorCode::API_NOT_AUTHORIZED]);
}
$redis = RedisClient::instance();
$access_token = '';
if ($client->access_token) {
if ($redis->exists(self::TOKEN_PREFIX . $client->access_token)) {
$access_token = $client->access_token;
}
}
if (empty($access_token)) {
$access_token = Str::uuid();
//保存到redis和数据表
$redis->setex(self::TOKEN_PREFIX . $access_token, self::EXPIRE_IN, $client->client_id);
$client->access_token = $access_token;
$client->save();
}
return Response::successWithCode(ErrorCode::SERVER_OK,
ErrorCode::$messages[ErrorCode::SERVER_OK],
[
'access_token' => $access_token,
'expire_in' => self::EXPIRE_IN
]
);
}
public function test()
{
return '';
}
}
<?php
namespace App\Http\Controllers\OpenApi;
use App\Helper\ErrorCode;
use App\Helper\Response;
use App\Http\Controllers\Controller;
use App\Models\Legal\Song;
use App\Models\Legal\SongTagsClass;
use Illuminate\Http\Request;
class SongController extends Controller
{
protected static $songs_limit = 10;
protected static $input_types = [1,2,3];
public function list(Request $request)
{
if (!$request->has('type') || !$request->has('value')) {
return Response::successWithCode(ErrorCode::MISSING_PARAMS, ErrorCode::$messages[ErrorCode::MISSING_PARAMS]);
}
if (!$request->filled('type') || !$request->filled('value')) {
return Response::successWithCode(ErrorCode::EMPTY_PARAMS, ErrorCode::$messages[ErrorCode::EMPTY_PARAMS]);
}
if (!in_array($request->get('type'), static::$input_types)) {
return Response::successWithCode(ErrorCode::ILLEGAL_PARAMS, ErrorCode::$messages[ErrorCode::ILLEGAL_PARAMS]);
}
$songs = Song::with('subject', 'tags', 'files')
->where([
'lyrics_right' => 1,
'tune_right' => 1,
'performer_right' => 1,
'tape_right' => 1,
])
->when($request->filled('value'), function ($query) use ($request) {
switch ($request->get('type')) {
case 1:
$query->where('custom_id', $request->get('value'));
break;
case 2:
$query->where('name', 'like', "%{$request->get('value')}%");
break;
case 3:
$query->where('singer', 'like', "%{$request->get('value')}%");
break;
}
});
$total = $songs->count();
$songs = $songs->orderByDesc('id')
->forPage($request->get('page', 1), static::$songs_limit)
->get(['custom_id as song_id', 'name as song_name', 'lyricist', 'composer', 'singer', 'lyrics_right',
'tune_right', 'performer_right', 'tape_right', 'status', 'subject_no', 'tag_id'])
->toArray();
if (count($songs)) {
$tags = SongTagsClass::pluck('name', 'id')->toArray();
foreach ($songs as &$song) {
$song['lyrics_right'] = $song['tune_right'] = $song['performer_right'] = $song['tape_right'] = '自有';
$song['subject'] = $song['subject'] ? $song['subject']['name'] : '';
if ($song['tags']) {
$song['tags'] = $this->coverTags($song['tags'], $tags);
}
if ($song['files']) {
$song['files'] = $this->coverFiles($song['files']);
}
unset($song['subject_no'], $song['tag_id']);
}
}
return Response::successWithCode(ErrorCode::SERVER_OK,
ErrorCode::$messages[ErrorCode::SERVER_OK],
compact('total', 'songs')
);
}
public function coverTags($song_tags, $tags)
{
unset($song_tags['id'], $song_tags['token']);
foreach ($song_tags as $tag_name => $tag) {
if ('tag_speed' != $tag_name) {
$song_tags[$tag_name] = coverData($tag, $tags);
}
}
return $song_tags;
}
public function coverFiles($song_files): array
{
$files_need = ['mp3_url', 'accompany_mp3_url', 'cover_url', 'wav_url', 'accompany_wav_url'];
$files_arr = [];
foreach ($song_files as $file) {
switch ($file['type']) {
case 1:
$files_arr['mp3_url'] = $file['url'];
break;
case 2:
$files_arr['accompany_mp3_url'] = $file['url'];
break;
case 4:
$files_arr['cover_url'] = $file['url'];
break;
case 11:
$files_arr['wav_url'] = $file['url'];
break;
case 12:
$files_arr['accompany_wav_url'] = $file['url'];
break;
}
}
foreach ($files_need as $v) {
if (!isset($files_arr[$v])) {
$files_arr[$v] = null;
}
}
return $files_arr;
}
}
......@@ -66,5 +66,6 @@ class Kernel extends HttpKernel
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'check.signature' => \App\Http\Middleware\CheckSignature::class,
];
}
......
<?php
namespace App\Http\Middleware;
use App\Helper\ErrorCode;
use App\Helper\RedisClient;
use App\Helper\Response;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
class CheckSignature
{
//access_token前缀
const TOKEN_PREFIX = "access_token:";
//timestamp有效时间
const TIMESTAMP_LIMIT = 60;
//nonce前缀
const NONCE_PREFIX = 'nonce:';
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure $next
* @return \Illuminate\Http\JsonResponse|mixed
*/
public function handle(Request $request, Closure $next)
{
//检查参数
if (!$request->has('access_token')) {
return Response::successWithCode(ErrorCode::MISSING_ACCESS_TOKEN, ErrorCode::$messages[ErrorCode::MISSING_ACCESS_TOKEN]);
}
if (!$request->has('client_id')) {
return Response::successWithCode(ErrorCode::MISSING_CLIENT_ID, ErrorCode::$messages[ErrorCode::MISSING_CLIENT_ID]);
}
if (!$request->has('timestamp')) {
return Response::successWithCode(ErrorCode::MISSING_TIMESTAMP, ErrorCode::$messages[ErrorCode::MISSING_TIMESTAMP]);
}
if (!$request->has('nonce')) {
return Response::successWithCode(ErrorCode::MISSING_NONCE, ErrorCode::$messages[ErrorCode::MISSING_NONCE]);
}
if (!$request->filled('client_id') || !$request->filled('timestamp') || !$request->filled('nonce') || !$request->filled('access_token')) {
return Response::successWithCode(ErrorCode::EMPTY_PARAMS, ErrorCode::$messages[ErrorCode::EMPTY_PARAMS]);
}
$params = $request->all();
$redis = RedisClient::instance();
//验证token
$token_key = self::TOKEN_PREFIX . $params['access_token'];
if ($redis->exists($token_key)) {
if ($params['client_id'] != $redis->get($token_key)) {
return Response::successWithCode(ErrorCode::INVALID_ACCESS_TOKEN, ErrorCode::$messages[ErrorCode::INVALID_ACCESS_TOKEN]);
}
}
//验证签名
$only = Arr::only($params, ['client_id', 'timestamp', 'nonce']);
sort($only, SORT_STRING);
$tmpStr = sha1(join('&', $only));
if ($params['sign'] != $tmpStr) {
return Response::successWithCode(ErrorCode::INVALID_SIGNATURE, ErrorCode::$messages[ErrorCode::INVALID_SIGNATURE]);
}
//防重放机制
//检查时间戳是否有效
if (time() < $params['timestamp'] + self::TIMESTAMP_LIMIT) {
return Response::successWithCode(ErrorCode::INVALID_TIMESTAMP, ErrorCode::$messages[ErrorCode::INVALID_TIMESTAMP]);
}
//随机数是否已被使用
$key_nonce = self::NONCE_PREFIX . $params['nonce'];
if ($redis->exists($key_nonce)) {
return Response::successWithCode(ErrorCode::INVALID_NONCE, ErrorCode::$messages[ErrorCode::INVALID_NONCE]);
}
$redis->setex($key_nonce, self::TIMESTAMP_LIMIT, $params['nonce']);
return $next($request);
}
}
<?php
namespace App\Models\Legal;
use App\Models\BaseModel;
class ClientMap extends BaseModel
{
public $timestamps = false;
protected $guarded = [];
}
......@@ -14,6 +14,8 @@ class Song extends BaseModel
{
use HasFactory,SoftDeletes;
protected static $file_only = [1,2,4,11,12];
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
......@@ -38,4 +40,21 @@ class Song extends BaseModel
return $this->belongsToMany(Contract::class, 'contract_song', 'song_id', 'contract_id');
}
public function subject()
{
return $this->belongsTo(Subject::class, 'subject_no', 'no')->select('no', 'name');
}
public function tags()
{
return $this->belongsTo(SongTag::class, 'tag_id', 'id');
}
public function files()
{
return $this->hasMany(SongFile::class, 'song_no', 'song_id')
->whereIn('type', static::$file_only)
->orderBy('id')
->select(['id', 'song_id', 'song_no', 'type', 'url']);
}
}
......
<?php
namespace App\Models\Legal;
use App\Models\BaseModel;
class SongTag extends BaseModel
{
public $timestamps = false;
}
<?php
namespace App\Models\Legal;
use App\Models\BaseModel;
class SongTagsClass extends BaseModel
{
protected $table = 'song_tags_class';
const COLUMN_MAP = [
'tag_feeling' => 1,
'tag_style' => 2,
'tag_theme' => 3,
'tag_vertical' => 4,
'tag_adapter' => 5,
'tag_scene' => 6,
'tag_strength' => 7,
'tag_language' => 8,
'tag_role' => 9,
];
}
......@@ -60,6 +60,11 @@ class RouteServiceProvider extends ServiceProvider
Route::prefix('release')
->namespace($this->namespace . '\Release')
->group(base_path('routes/release.php'));
//开放平台接口
Route::domain('openApi.test')
->namespace($this->namespace . '\OpenApi')
->group(base_path('routes/openApi.php'));
});
}
......
<?php
use Illuminate\Support\Facades\Route;
Route::group(['prefix' => 'client-credentials'], function () {
Route::get('getApp', "ClientCredentialsController@getApp");
Route::get('token', "ClientCredentialsController@getAccessToken");
});
Route::get('test', "ClientCredentialsController@test");
Route::get('song/list', "SongController@list");
Route::group(['middleware' => 'check.signature'], function () {
});