Commit 74fb33ac 74fb33ac3bf3d7f2944395999c7bae0a3b599bf6 by wei.feng

商用授权接口

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