## 阿里云盤
阿里云盤是阿里巴巴集團推出的一款個人云存儲服務產品。它為用戶提供了一個安全、穩定、高效的云端存儲空間,用戶可以在其中存儲、管理和同步各種類型的文件和數據。
阿里云盤是個人和團隊管理數字資產的有力工具,尤其適合需要在多設備間同步工作文件和數據的用戶。
## 開放平臺
阿里云盤開放個人云存儲能力,允許開發者通過對接 API 的方式,集成阿里云盤個人云存儲能力到開發者的應用中。
目前開放的能力包括文件上傳下載等基礎文件管理能力、音視頻文件的在線轉碼與播放等媒體在線播放能力、用戶授權與信息查詢等能力。
> 阿里云網盤對接文檔地址:https://www.yuque.com/aliyundrive/zpfszx/gogo34oi2gy98w5d

## 接入流程
> 服務端 API 調用流程如下圖所示

### 創建應用


> 創建應用以獲取應用接入憑證,包括 `appid`、`secret`
### 編寫應用
核心調用類
```
<?php
/**
* @desc ADrive https://www.yuque.com/aliyundrive/zpfszx
* @author Tinywan(ShaoBo Wan)
* @date 2024/8/7 22:57
*/
declare(strict_types=1);
namespace app\common\service;
use Psr\SimpleCache\InvalidArgumentException;
use support\exception\BusinessException;
use support\Log;
use think\facade\Cache;
class ADrive
{
const ACCESS_TOKEN = 'ADRIVE_ACCESS_TOKEN:';
/** @var array */
private array $config = [];
/** @var string */
private string $http = 'https://openapi.aliyundrive.com/';
/** @var string */
private string $redirect_uri = 'http://webman2024.tinywan.com:8484/test/adrive-callback';
/** @var array */
private array $url = [
'access_token' => 'oauth/access_token',
'authorize' => 'oauth/authorize',
'drive' => 'adrive/v1.0/user/getDriveInfo',
'create' => 'adrive/v1.0/openFile/create',
'complete' => 'adrive/v1.0/openFile/complete',
'fileList' => 'adrive/v1.0/openFile/list',
'deleteFile' => 'adrive/v1.0/openFile/recyclebin/trash',
'searchList' => 'adrive/v1.0/openFile/search',
'updateFile' => 'adrive/v1.0/openFile/update',
'starredList' => 'adrive/v1.0/openFile/starredList',
'getDownloadUrl' => 'adrive/v1.0/openFile/getDownloadUrl'
];
/**
* @var PublicHttp|null
*/
private ?PublicHttp $publicHttp = null;
/**
* @param int $agencyId
*/
public function __construct(int $agencyId = 0)
{
$agencyInfo['id'] = 2024;
$agencyInfo['aly_appid'] = '282392bf68014e13b5db2a2b35d9b3ce';
$agencyInfo['aly_secret'] = 'a524d58d17a44c1faaa914db460be16a';
if ($this->publicHttp == null) {
$this->publicHttp = new PublicHttp();
}
$this->config = $agencyInfo;
}
/**
* @desc 登錄授權
* @author Tinywan(ShaoBo Wan)
*/
public function authorize(): string
{
return $this->http . $this->url['authorize'] . '?client_id=' . $this->config['aly_appid'] .
'&redirect_uri=' . $this->redirect_uri . '&scope=user:base,file:all:read,file:all:write&response_type=code&state=' . $this->config['id'];
}
/**
* @desc getToken
* @param int $type
* @param string $code
* @return bool|mixed|string
* @throws InvalidArgumentException
* @author Tinywan(ShaoBo Wan)
*/
public function getToken(string $code, int $type = 1)
{
$info = [
'client_id' => $this->config['aly_appid'],
'client_secret' => $this->config['aly_secret'],
'grant_type' => $type == 1 ? 'authorization_code' : 'refresh_token',
];
if ($type == 1) {
$info['code'] = $code;
} else {
if (empty($this->config['id'])) {
throw new \Exception('未傳入旅行社標識');
}
$token = Cache::get(self::ACCESS_TOKEN . $this->config['id']);
if (empty($token)) {
throw new \Exception('未有阿里云盤緩存');
}
$value = $token['refresh_token'];
$info['refresh_token'] = $value;
}
$result = $this->publicHttp->postAlyFile($this->http . $this->url['access_token'], $info);
Log::info('[阿里云云盤獲取令牌]' . json_encode($result, JSON_UNESCAPED_UNICODE));
if (!is_array($result)) {
$result = json_decode($result, true);
}
Cache::set(self::ACCESS_TOKEN . $this->config['id'], $result, 7200);
return $result;
}
/**
* @desc 獲取阿里云盤用戶drive_id
* @throws \Exception
* @author Tinywan(ShaoBo Wan)
*/
public function getUserDrive(): array
{
$userToken = Cache::get(self::ACCESS_TOKEN . $this->config['id']);
if (empty($userToken)) {
throw new \Exception('登錄失效,請重新授權阿里網盤');
}
$result = $this->publicHttp->postAlyFile($this->http . $this->url['drive'], [], ['Authorization:' . $userToken['token_type'] . ' ' . $userToken['access_token']]);
if (!is_array($result)) {
$result = json_decode($result, true);
}
if (empty($result['default_drive_id'])) {
throw new \Exception('獲取阿里云盤drive_id失敗');
}
Cache::set('aly_drive_' . $this->config['id'], $result['default_drive_id']);
Cache::set('aly_userInfo_' . $this->config['id'], $result);
return ['status' => 0, 'msg' => '獲取用戶drive成功', 'data' => $result['default_drive_id']];
}
/**
* 阿里云盤文件上傳
* file_name 文件名稱
* file_path 文件路徑
* file_id 上傳的目錄
*/
public function updateFile(array $input): array
{
$userToken = Cache::get(self::ACCESS_TOKEN . $this->config['id']);
if (empty($userToken)) {
throw new \Exception('登錄失效,請重新授權阿里網盤');
}
$drive = Cache::get('aly_drive_' . $this->config['id']);
if (empty($drive)) {
try {
$driveInfo = $this->getUserDrive();
$drive = $driveInfo['data'];
} catch (\Throwable $exception) {
return ['status' => 1, 'msg' => $exception->getMessage()];
}
}
$fileName = $input['file_name'] . date('Y-m-d-H:i:s');
$result = $this->publicHttp->postAlyFile($this->http . $this->url['create'], [
'drive_id' => $drive,
'parent_file_id' => empty($input['file_id']) ? 'root' : $input['file_id'],
'type' => 'file',
'check_name_mode' => 'ignore',
'name' => $fileName,
], ['Authorization:' . $userToken['token_type'] . ' ' . $userToken['access_token']]);
if (!is_array($result)) {
$result = json_decode($result, true);
}
if (empty($result['part_info_list'][0]['upload_url'])) {
throw new \Exception('獲取上傳路徑失敗');
}
$sourceFile = fopen($input['file_path'], "rb");
$res = $this->publicHttp->putAlyFile($result['part_info_list'][0]['upload_url'],
$sourceFile,
['Authorization:' . $userToken['token_type'] . ' ' . $userToken['access_token']]);
if ($res['code'] != 1) {
throw new \Exception('上傳文件失敗');
}
//上傳成功后調用上傳完畢
$onMsg = $this->publicHttp->postAlyFile($this->http . $this->url['complete'], [
'drive_id' => $drive,
'file_id' => $result['file_id'],
'upload_id' => $result['upload_id'],
], ['Authorization:' . $userToken['token_type'] . ' ' . $userToken['access_token']]);
if (!is_array($onMsg)) {
$onMsg = json_decode($onMsg, true);
}
if (empty($onMsg['name'])) {
throw new \Exception('同步阿里云網盤失敗');
}
return ['status' => 0, 'msg' => '上傳文件成功', 'file_id' => $result['file_id']];
}
/**
* 新建文件夾
* file_name 文件夾名稱
* file_type =1 不追加日期 =2追加日期
**/
public function addFile($input)
{
$userToken = Cache::get(self::ACCESS_TOKEN . $this->config['id']);
if (empty($userToken)) {
throw new \Exception('登錄失效,請重新授權阿里網盤');
}
$drive = Cache::get('aly_drive_' . $this->config['id']);
if (empty($drive)) {
try {
$driveInfo = $this->getUserDrive();
$drive = $driveInfo['data'];
} catch (\Throwable $exception) {
return ['status' => 1, 'msg' => $exception->getMessage()];
}
}
$list = [
'drive_id' => $drive,
'parent_file_id' => empty($input['file_id']) ? 'root' : $input['file_id'],
'type' => 'folder',
'check_name_mode' => 'ignore',
'name' => ($input['file_type'] == 1 ? '' : $input['file_name'] . '-時間:' . date('Y-m-d H:i:s'))
];
$result = $this->publicHttp->postAlyFile($this->http . $this->url['create'], $list, ['Authorization:' . $userToken['token_type'] . ' ' . $userToken['access_token']]);
if (!is_array($result)) {
$result = json_decode($result, true);
}
if (empty($result['file_id'])) {
return ['status' => 1, 'msg' => '新建文件失敗'];
}
return ['status' => 0, 'msg' => '操作成功', 'file_id' => $result['file_id']];
}
/**
* 獲取文件夾列表
* limit 最大數量 50-100
* marker 分頁標記
* parent_file_id root 等于根目錄 否則傳入文件列表ID
* order_by 排序字段 updated_at created_at name size
* order_direction DESC ASC
*
*/
public function fileList($input)
{
$userToken = Cache::get(self::ACCESS_TOKEN . $this->config['id']);
if (empty($userToken)) {
throw new \Exception('登錄失效,請重新授權阿里網盤');
}
$drive = Cache::get('aly_drive_' . $this->config['id']);
if (empty($drive)) {
try {
$driveInfo = $this->getUserDrive();
$drive = $driveInfo['data'];
} catch (\Throwable $exception) {
return ['status' => 1, 'msg' => $exception->getMessage()];
}
}
$list = [
'drive_id' => $drive,
'order_by' => empty($input['order_by']) ? 'updated_at' : $input['order_by'],
'order_direction' => empty($input['order_direction']) ? 'DESC' : $input['order_direction'],
'parent_file_id' => empty($input['parent_file_id']) ? 'root' : $input['parent_file_id'],
'type' => 'all',
'limit' => (int)$input['limit'],
'fields' => '*'
];
if (!empty($input['marker'])) {
$list['marker'] = $input['marker'];
}
$result = $this->publicHttp->postAlyFile($this->http . $this->url['fileList'], $list, ['Authorization:' . $userToken['token_type'] . ' ' . $userToken['access_token']]);
if (!is_array($result)) {
$result = json_decode($result, true);
}
foreach ($result['items'] as &$value) {
$value['created_at'] = date('Y-m-d H:i:s', strtotime($value['created_at']));
$value['updated_at'] = date('Y-m-d H:i:s', strtotime($value['updated_at']));
$value['show_ico'] = false;
if ($value['type'] == 'folder') {
$value['type_name'] = '文件夾';
} else {
$value['type_name'] = '文件';
}
}
return ['status' => 0, 'rows' => $result['items'], 'next_marker' => $result['next_marker']];
}
/**
* @desc 文件名稱
* keyword 文件名稱
* limit 條數
* @param array $input
* @return array
* @throws \Exception
* @author Tinywan(ShaoBo Wan)
*/
public function searchList(array $input)
{
$userToken = Cache::get(self::ACCESS_TOKEN . $this->config['id']);
if (empty($userToken)) {
throw new \Exception('登錄失效,請重新授權阿里網盤');
}
$drive = Cache::get('aly_drive_' . $this->config['id']);
if (empty($drive)) {
try {
$driveInfo = $this->getUserDrive();
$drive = $driveInfo['data'];
} catch (\Throwable $exception) {
return ['status' => 1, 'msg' => $exception->getMessage()];
}
}
$list = [
'drive_id' => $drive,
'limit' => (int)$input['limit'],
'fields' => '*',
'query' => 'name match "' . $input['keyword'] . '"'
];
if (!empty($input['marker'])) {
$list['marker'] = $input['marker'];
}
$result = $this->publicHttp->postAlyFile($this->http . $this->url['searchList'], $list, ['Authorization:' . $userToken['token_type'] . ' ' . $userToken['access_token']]);
if (!is_array($result)) {
$result = json_decode($result, true);
}
foreach ($result['items'] as &$value) {
$value['created_at'] = date('Y-m-d H:i:s', strtotime($value['created_at']));
$value['updated_at'] = date('Y-m-d H:i:s', strtotime($value['updated_at']));
$value['show_ico'] = false;
if ($value['type'] == 'folder') {
$value['type_name'] = '文件夾';
} else {
$value['type_name'] = '文件';
}
}
return ['status' => 0, 'rows' => $result['items'], 'next_marker' => $result['next_marker']];
}
/**
* @desc 獲取收藏列表
* @param $input
* @return array
* @throws BusinessException|InvalidArgumentException
* @author Tinywan(ShaoBo Wan)
*/
public function starredList($input): array
{
$userToken = Cache::get(self::ACCESS_TOKEN . $this->config['id']);
if (empty($userToken)) {
throw new BusinessException('登錄失效,請重新授權阿里網盤');
}
$drive = Cache::get('aly_drive_' . $this->config['id']);
if (empty($drive)) {
try {
$driveInfo = $this->getUserDrive();
$drive = $driveInfo['data'];
} catch (\Throwable $exception) {
return ['status' => 1, 'msg' => $exception->getMessage()];
}
}
$list = [
'drive_id' => $drive,
'order_by' => empty($input['order_by']) ? 'updated_at' : $input['order_by'],
'order_direction' => empty($input['order_direction']) ? 'DESC' : $input['order_direction'],
'limit' => (int)$input['limit'],
];
if (!empty($input['marker'])) {
$list['marker'] = $input['marker'];
}
$result = $this->publicHttp->postAlyFile($this->http . $this->url['starredList'], $list, ['Authorization:' . $userToken['token_type'] . ' ' . $userToken['access_token']]);
if (!is_array($result)) {
$result = json_decode($result, true);
}
foreach ($result['items'] as &$value) {
$value['created_at'] = date('Y-m-d H:i:s', strtotime($value['created_at']));
$value['updated_at'] = date('Y-m-d H:i:s', strtotime($value['updated_at']));
$value['show_ico'] = false;
if ($value['type'] == 'folder') {
$value['type_name'] = '文件夾';
} else {
$value['type_name'] = '文件';
}
}
return ['status' => 0, 'rows' => $result['items'], 'next_marker' => $result['next_marker']];
}
/**
* @desc 文件重命名或收藏
* @param $input
* @return array
* @throws \Exception|InvalidArgumentException
* @author Tinywan(ShaoBo Wan)
*/
public function fileRename($input)
{
$userToken = Cache::get(self::ACCESS_TOKEN . $this->config['id']);
if (empty($userToken)) {
throw new \Exception('登錄失效,請重新授權阿里網盤');
}
$drive = Cache::get('aly_drive_' . $this->config['id']);
if (empty($drive)) {
try {
$driveInfo = $this->getUserDrive();
$drive = $driveInfo['data'];
} catch (\Throwable $exception) {
return ['status' => 1, 'msg' => $exception->getMessage()];
}
}
$list = [
'drive_id' => $drive,
'file_id' => $input['file_id'],
'name' => $input['name'],
'starred' => $input['starred']
];
$result = $this->publicHttp->postAlyFile($this->http . $this->url['updateFile'], $list, ['Authorization:' . $userToken['token_type'] . ' ' . $userToken['access_token']]);
if (!is_array($result)) {
$result = json_decode($result, true);
}
if (empty($result['file_id'])) {
return ['status' => 1, 'msg' => '操作失敗'];
}
return ['status' => 0, 'msg' => '操作成功'];
}
/**
* @desc 放入回收站
* @param array $input
* @return array
* @throws \Exception
* @author Tinywan(ShaoBo Wan)
*/
public function deleteFile(array $input): array
{
$userToken = Cache::get(self::ACCESS_TOKEN . $this->config['id']);
if (empty($userToken)) {
throw new \Exception('登錄失效,請重新授權阿里網盤');
}
$drive = Cache::get('aly_drive_' . $this->config['id']);
if (empty($drive)) {
try {
$driveInfo = $this->getUserDrive();
$drive = $driveInfo['data'];
} catch (\Throwable $exception) {
return ['status' => 1, 'msg' => $exception->getMessage()];
}
}
$this->publicHttp->postAlyFile($this->http . $this->url['deleteFile'], [
'drive_id' => $drive,
'file_id' => $input['file_id']
], ['Authorization:' . $userToken['token_type'] . ' ' . $userToken['access_token']]);
return ['status' => 0, 'msg' => '放入成功'];
}
/**
* @desc 獲取下載鏈接
* @param array $input
* @return array
* @throws \Exception
* @throws InvalidArgumentException
* @author Tinywan(ShaoBo Wan)
*/
public function getDownloadUrl(array $input): array
{
$userToken = Cache::get(self::ACCESS_TOKEN . $this->config['id']);
if (empty($userToken)) {
throw new \Exception('登錄失效,請重新授權阿里網盤');
}
$drive = Cache::get('aly_drive_' . $this->config['id']);
if (empty($drive)) {
try {
$driveInfo = $this->getUserDrive();
$drive = $driveInfo['data'];
} catch (\Throwable $exception) {
return ['status' => 1, 'msg' => $exception->getMessage()];
}
}
$list = [
'drive_id' => $drive,
'file_id' => $input['file_id'],
];
$result = $this->publicHttp->postAlyFile($this->http . $this->url['getDownloadUrl'],
$list,
['Authorization:' . $userToken['token_type'] . ' ' . $userToken['access_token']]);
if (!is_array($result)) {
$result = json_decode($result, true);
}
if (empty($result['url'])) {
return ['status' => 1, 'msg' => '獲取下載鏈接失敗'];
}
return ['status' => 0, 'msg' => '操作成功', 'url' => $result['url']];
}
}
```
簡單請求類
```
<?php
/**
* @desc PublicHttp.php 描述信息
* @author Tinywan(ShaoBo Wan)
* @date 2024/8/7 23:05
*/
declare(strict_types=1);
namespace app\common\service;
class PublicHttp
{
/**
* @desc postAlyFile
* @param string $_url
* @param array $params
* @param array $header
* @return bool|string
* @author Tinywan(ShaoBo Wan)
*/
public function postAlyFile(string $_url, array $params,array $header=[])
{
$headerArray = array("Content-Type:application/json;charset=utf-8", "Accept:application/json");
if (!empty($header)) {
$headerArray = array_merge($headerArray, $header);
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($ch, CURLOPT_URL, $_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; MSIE 5.01; Windows NT 5.0)');
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params));
curl_setopt($ch, CURLOPT_HTTPHEADER, $headerArray);
$output = curl_exec($ch);
curl_close($ch);
return $output;
}
/**
* @desc put請求
* @param string $url
* @param $sourceFile
* @param array $headerArr
* @param int $timeout
* @return array
* @author Tinywan(ShaoBo Wan)
*/
function putAlyFile(string $url, $sourceFile, array $headerArr = [], int $timeout = 30): array
{
$ch = curl_init(); //初始化curl
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); //返回字符串,而不直接輸出
curl_setopt($ch, CURLOPT_URL, $url); //設置put到的url
curl_setopt($ch, CURLOPT_HTTPHEADER, $headerArr);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_HEADER, 1); // 啟用時會將頭文件的信息作為數據流輸出。
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); //設置請求方式
curl_setopt($ch, CURLOPT_PUT, true); //設置為PUT請求
curl_setopt($ch, CURLOPT_INFILE, $sourceFile); //設置資源句柄
$response = curl_exec($ch);
if ($error = curl_error($ch)){
$bkArr = array(
'code' => 0,
'msg' => $error,
);
}
else{
$bkArr = array(
'code' => 1,
'msg' => 'ok',
'data' => $response
);
}
curl_close($ch); // 關閉 cURL 釋放資源
return $bkArr;
}
}
```
### 調用編寫
獲取授權碼
```php
/**
* @desc: 獲取授權碼
* @param Request $request
* @return Response
* @author Tinywan(ShaoBo Wan)
*/
public function authorize(Request $request): Response
{
$aDriver = new ADrive();
return redirect($aDriver->authorize());
}
```
回調配置參考
```
/**
* @param Request $request
* @return Response
* @throws InvalidArgumentException
* @author Tinywan(ShaoBo Wan)
*/
public function aDriveCallback(Request $request): Response
{
Log::info('[請求回調參數]:'.json_encode($request->get()));
$code = $request->get()['code'];
$aDriver = new ADrive();
$token = $aDriver->getToken($code);
Log::info('[獲取訪問憑證 (access_token)]:'.json_encode($token));
return response_json(200, '請求成功',['token'=>$token]);
}
```
> 以上回調地址必須可以通過公網訪問的到。我這里配置的回調地址是`http://webman2024.tinywan.com:8484/test/adrive-callback`
令牌響應格式為:
```json
{
"token_type": "Bearer",
"access_token": "eyJraWQiOiJxxxxxxxxxxxx",
"refresh_token": "eyJ0eXAxxxxxxxxxxxxxxmt7WOaMbEXVWNKJdw",
"expires_in": 7200
}
```
> 阿里云盤開放平臺使用 OAuth 2.0 授權標準,接入前請閱讀。https://www.yuque.com/aliyundrive/zpfszx/rgg2p1qnsfdux61r?singleDoc#
### 授權應用
> 獲取授權頁面鏈接:http://webman2024.tinywan.com:8484/test/authorize

瀏覽器打開鏈接后使用阿里云網盤掃碼授權成功后,回調獲取用戶信息就可以直接調用用戶的阿里云網盤功能.
## 上傳文件
```
/**
* @desc 文件上傳
* @param Request $request
* @return Response
* @throws \Exception
* @author Tinywan(ShaoBo Wan)
*/
public function uploadFile(Request $request): Response
{
$aDriver = new ADrive();
$param = [
'file_name' => '開源技術小棧-文件上傳測試',
'file_path' => runtime_path().DIRECTORY_SEPARATOR.'swoole.jpg',
];
$res = $aDriver->updateFile($param);
Log::info('[文件上傳-響應結果] '.json_encode($res));
return response_json(200, '請求成功',$res);
}
```
響應結果
```json
{
"code": 200,
"msg": "請求成功",
"data": {
"status": 0,
"msg": "上傳文件成功",
"file_id": "66b5b06c8e278ff7d300479fac0fb623fdfe299a"
}
}
```
> 云盤上傳結果

- 設計模式系列
- 工廠方法模式
- 序言
- Windows程序注冊為服務的工具WinSW
- 基礎
- 安裝
- 開發規范
- 目錄結構
- 配置
- 快速入門
- 架構
- 請求流程
- 架構總覽
- URL訪問
- 容器和依賴注入
- 中間件
- 事件
- 代碼層結構
- 四個層次
- 路由
- 控制器
- 請求
- 響應
- 數據庫
- MySQL實時同步數據到ES解決方案
- 阿里云DTS數據MySQL同步至Elasticsearch實戰
- PHP中的MySQL連接池
- PHP異步非阻塞MySQL客戶端連接池
- 模型
- 視圖
- 注解
- @SpringBootApplication(exclude={DataSourceAutoConfiguration.calss})
- @EnableFeignClients(basePackages = "com.wotu.feign")
- @EnableAspectJAutoProxy
- @EnableDiscoveryClient
- 錯誤和日志
- 異常處理
- 日志處理
- 調試
- 驗證
- 驗證器
- 驗證規則
- 擴展庫
- 附錄
- Spring框架知識體系詳解
- Maven
- Maven和Composer
- 構建Maven項目
- 實操課程
- 01.初識SpringBoot
- 第1章 Java Web發展史與學習Java的方法
- 第2章 環境與常見問題踩坑
- 第3章 springboot的路由與控制器
- 02.Java編程思想深度理論知識
- 第1章 Java編程思想總體
- 第2章 英雄聯盟的小案例理解Java中最為抽象的概念
- 第3章 徹底理解IOC、DI與DIP
- 03.Spring與SpringBoot理論篇
- 第1章 Spring與SpringBoot導學
- 第2章 Spring IOC的核心機制:實例化與注入
- 第3章 SpringBoot基本配置原理
- 04.SprinBoot的條件注解與配置
- 第1章 conditonal 條件注解
- 第2章 SpringBoot自動裝配解析
- 05.Java異常深度剖析
- 第1章 Java異常分類剖析與自定義異常
- 第2章 自動配置Url前綴
- 06.參數校驗機制與LomBok工具集的使用
- 第1章 LomBok工具集的使用
- 第2章 參數校驗機制以及自定義校驗
- 07.項目分層設計與JPA技術
- 第1章 項目分層原則與層與層的松耦合原則
- 第2章 數據庫設計、實體關系與查詢方案探討
- 第3章 JPA的關聯關系與規則查詢
- 08.ORM的概念與思維
- 第1章 ORM的概念與思維
- 第2章 Banner等相關業務
- 第3章 再談數據庫設計技巧與VO層對象的技巧
- 09.JPA的多種查詢規則
- 第1章 DozerBeanMapper的使用
- 第2章 詳解SKU的規格設計
- 第3章 通用泛型Converter
- 10.令牌與權限
- 第1章 通用泛型類與java泛型的思考
- 常見問題
- 微服務
- demo
- PHP中Self、Static和parent的區別
- Swoole-Cli
- 為什么要使用現代化PHP框架?
- 公眾號
- 一鍵部署微信公眾號Markdown編輯器(支持適配和主題設計)
- Autodesigner 2.0發布
- Luya 一個現代化PHP開發框架
- PHPZip - 創建、讀取和管理 ZIP 文件的簡單庫
- 吊打Golang的PHP界天花板webman壓測對比
- 簡潔而強大的 YAML 解析庫
- 推薦一個革命性的PHP測試框架:Kahlan
- ServBay下一代Web開發環境
- 基于Websocket和Canvas實現多人協作實時共享白板
- Apipost預執行腳本如何調用外部PHP語言
- 認證和授權的安全令牌 Bearer Token
- Laradock PHP 的 Docker 完整本地開發環境
- 高效接口防抖策略,確保數據安全,避免重復提交的終極解決方案!
- TIOBE 6月榜單:PHP穩步前行,編程語言生態的微妙變化
- Aho-Corasick字符串匹配算法的實現
- Redis鍵空間通知 Keyspace Notification 事件訂閱
- ServBay如何啟用并運行Webman項目
- 使用mpdf實現導出pdf文件功能
- Medoo 輕量級PHP數據庫框架
- 在PHP中編寫和運行單元測試
- 9 PHP運行時基準性能測試
- QR碼生成器在PHP中的源代碼
- 使用Gogs極易搭建的自助Git服務
- Gitea
- webman如何記錄SQL到日志?
- Sentry PHP: 實時監測并處理PHP應用程序中的錯誤
- Swoole v6 Alpha 版本已發布
- Proxypin
- Rust實現的Redis內存數據庫發布
- PHP 8.4.0 Alpha 1 測試版本發布
- 121
- Golang + Vue 開發的開源輕量 Linux 服務器運維管理面板
- 內網穿透 FRP VS Tailscale
- 新一代開源代碼托管平臺Gitea
- 微服務系列
- Nacos云原生配置中心介紹與使用
- 輕量級的開源高性能事件庫libevent
- 國密算法
- 國密算法(商用密碼)
- GmSSL 支持國密SM2/SM3/SM4/SM9/SSL 密碼工具箱
- GmSSL PHP 使用
- 數據庫
- SQLite數據庫的Web管理工具
- 阿里巴巴MySQL數據庫強制規范
- PHP
- PHP安全測試秘密武器 PHPGGC
- 使用declare(strict_types=1)來獲得更健壯的PHP代碼
- PHP中的魔術常量
- OSS 直傳阿里騰訊示例
- PHP源碼編譯安裝APCu擴展實現數據緩存
- BI性能DuckDB數據管理系統
- 為什么別人可以是架構師!而我卻不是?
- 密碼還在用 MD5 加鹽?不如試試 password_hash
- Elasticsearch 在電商領域的應用與實踐
- Cron 定時任務入門
- 如何動態設置定時任務!而不是寫死在Linux Crontab
- Elasticsearch的四種查詢方式,你知道多少?
- Meilisearch vs Elasticsearch
- OpenSearch vs Elasticsearch
- Emlog 輕量級開源博客及建站系統
- 現代化PHP原生協程引擎 PRipple
- 使用Zephir編寫C擴展將PHP源代碼編譯加密
- 如何將PHP源代碼編譯加密,同時保證代碼能正常的運行
- 為什么選擇Zephir給PHP編寫動態擴展庫?
- 使用 PHP + XlsWriter實現百萬級數據導入導出
- Rust編寫PHP擴展
- 阿里云盤開放平臺對接進行文件同步
- 如何構建自己的PHP靜態可執行文件
- IM后端架構
- RESTful設計方法和規范
- PHP編譯器BPC 7.3 發布,成功編譯ThinkPHP8
- 高性能的配置管理擴展 Yaconf
- PHP實現雪花算法庫 Snowflake
- PHP官方現代化核心加密庫Sodium
- pie
- 現代化、精簡、非阻塞PHP標準庫PSL
- PHP泛型和集合
- 手把手教你正確使用 Composer包管理
- JWT雙令牌認證實現無感Token自動續期
- 最先進PHP大模型深度學習庫TransformersPHP
- PHP如何啟用 FFI 擴展
- PHP超集語言PXP
- 低延遲雙向實時事件通信 Socket.IO
- PHP OOP中的繼承和多態
- 強大的現代PHP高級調試工具Kint
- PHP基金會
- 基于webman+vue3高質量中后臺框架SaiAdmin
- 開源免費的定時任務管理系統:Gocron
- 簡單強大OCR工具EasyOCR在PHP中使用
- PHP代碼抽象語法樹工具PHP AST Viewer
- MySQL數據庫管理工具PHPMyAdmin
- Rust編寫的一款高性能多人代碼編輯器Zed
- 超高性能PHP框架Workerman v5.0.0-beta.8 發布
- 高并發系列
- 入門介紹及安裝
- Lua腳本開發 Hello World
- 執行流程與階段詳解
- Nginx Lua API 接口開發
- Lua模塊開發
- OpenResty 高性能的正式原因
- 記一次查找 lua-resty-mysql 庫 insert_id 的 bug
- 包管理工具OPM和LuaRocks使用
- 異步非阻塞HTTP客戶端庫 lua-resty-http
- Nginx 內置綁定變量
- Redis協程網絡庫 lua-resty-redis
- 動態HTML渲染庫 lua-testy-template
- 單獨的
- StackBlitz在線開發環境
- AI
- 基礎概念
- 12312
- 基礎鏡像的坑
- 利用phpy實現 PHP 編寫 Vision Transformer (ViT) 模型
- 語義化版本 2.0.0