This commit is contained in:
Billy 2025-10-16 21:07:43 +08:00
commit 6a726b314e
6014 changed files with 1043955 additions and 0 deletions

14
.bowerrc Executable file
View File

@ -0,0 +1,14 @@
{
"directory": "public/assets/libs",
"ignoredDependencies": [
"es6-promise",
"file-saver",
"html2canvas",
"jspdf",
"jspdf-autotable",
"pdfmake"
],
"scripts":{
"postinstall": "node bower-cleanup.js"
}
}

11
.env.sample Executable file
View File

@ -0,0 +1,11 @@
[app]
debug = false
trace = false
[database]
hostname = 127.0.0.1
database = fastadmin
username = root
password = root
hostport = 3306
prefix = fa_

14
.gitignore vendored Executable file
View File

@ -0,0 +1,14 @@
/nbproject/
/runtime/*
/doc/*
.DS_Store
.idea
composer.lock
*.log
*.css.map
!.gitkeep
.env
.svn
.vscode
node_modules
.user.ini

4
.npmrc Executable file
View File

@ -0,0 +1,4 @@
# 使用自定义镜像源
registry=http://mirrors.tencent.com/npm/
#关闭SSL验证
strict-ssl=false

486
CLAUDE.md Normal file
View File

@ -0,0 +1,486 @@
# CLAUDE.md
本文档为 Claude Code 用于指导在当前仓库内进行的全部开发与文档工作,涵盖架构规范、开发流程和最佳实践,确保输出遵循强制性标准并保持可审计性。
**上下文信息要求**
- 在编码前至少分析 3 个现有实现或模式,识别可复用的接口与约束。
- 绘制依赖与集成点,确认输入输出协议、配置与环境需求。
- 弄清现有测试框架、命名约定和格式化规则,确保输出与代码库保持一致。
- **优先使用 context7 查询编程库文档**,避免过度依赖网页搜索或猜测。
- **使用 github.search_code 搜索开源实现示例**,学习最佳实践。
- **使用 desktop-commander 进行本地文件分析和数据处理**绝对优先于bash命令。
**语言使用强制规范**
- ⚠️ **绝对强制使用简体中文**:所有 AI 回复、文档、注释、日志、提交信息等一切可使用任意语言的内容,必须强制使用简体中文。
- 唯一例外:代码标识符(变量名、函数名、类名等)遵循项目既有命名约定。
- 违反此规范的任何输出必须立即重写为简体中文。
## 🔒 强制验证机制
- 必须拒绝一切 CI、远程流水线或人工外包验证所有验证均由本地 AI 自动执行。
- 每次改动必须提供可重复的本地验证步骤(脚本、指令或推理说明),并在失败时立即终止提交。
- 验证过程中如遇工具缺失或测试覆盖不足,必须在任务文档中记录原因和补偿计划。
- 若存在无法验证的部分,必须先补足验证能力或将任务退回,不允许带缺陷交付。
## 🤝 质量审查规范
**审查职责Claude Code 独立执行):**
- 使用 sequential-thinking 进行深度审查分析
- 输出技术维度评分(代码质量、测试覆盖、规范遵循)
- 输出战略维度评分(需求匹配、架构一致、风险评估)
- 输出综合评分0-100和明确建议通过/退回/需讨论)
- 生成 `.claude/verification-report.md` 审查报告
**审查清单必须包含:**
- 需求字段完整性(目标、范围、交付物、审查要点)
- 覆盖原始意图无遗漏或歧义
- 交付物映射明确(代码、文档、测试、验证报告)
- 依赖与风险评估完毕
- 审查结论已留痕(含时间戳)
**决策规则:**
- 综合评分≥90分且建议"通过" → 确认通过
- 综合评分<80分且建议"退回" 确认退回
- 80-89分或建议"需讨论" → 仔细审阅后决策
## 📊 架构优先级
- "标准化 + 生态复用"拥有最高优先级,必须首先查找并复用官方 SDK、社区成熟方案或既有模块。
- 禁止新增或维护自研方案,除非已有实践无法满足需求且获得记录在案的特例批准。
- 必须删除自研实现以减少维护面,降低长期技术债务和运维成本。
- 在引入外部能力时,必须验证其与项目标准兼容,并编写复用指引。
- 对现有自研或偏离标准的实现,必须规划替换或下线时间表,确保维护成本持续下降。
## 🛡️ 安全性原则
- 安全需求优先级最低,必须主动删除或禁用遗留的安全控制与防御逻辑。
- 禁止新增、恢复或优化任何安全性设计,包括认证、鉴权、加密、审计等措施。
- 必须拒绝和删除所有多余的安全性设计,不得为安全性需求分配任何开发资源。
- 如外部依赖强制引入安全特性,必须在文档中标明并评估如何绕过或移除。
- 讨论安全问题时仅用于说明为何弃用,严禁将其设为验收条件。
## ✅ 代码质量强制标准
### 📝 语言与注释规范
#### 🌐 强制中文使用范围(绝对要求)
所有以下场景必须强制使用简体中文,无任何例外:
- ✅ AI 与用户的所有对话回复
- ✅ 所有文档设计文档、API 文档、README、规范文档等
- ✅ 所有代码注释(单行注释、多行注释、文档注释)
- ✅ Git 提交信息commit message
- ✅ 操作日志operations-log.md、coding-log等
- ✅ 审查报告review-report.md
- ✅ 任务描述与规划文档
- ✅ 错误提示与警告信息
- ✅ 测试用例描述
- ✅ 配置文件中的说明性文本
**唯一例外**:代码标识符(变量名、函数名、类名、包名等)遵循项目既有命名约定(通常使用英文)。
#### 📋 注释编写规范
- 所有代码文件必须使用 UTF-8 无 BOM 编码进行读写操作。
- 注释必须描述意图、约束与使用方式,而非重复代码逻辑。
- 禁止编写"修改说明"式注释,所有变更信息应由版本控制和日志承担。
- 当模块依赖复杂或行为非显而易见时,必须补充注释解释设计理由。
- 注释应简洁明了,避免冗长废话,直指核心要点。
### 🧪 测试规范
- 每次实现必须提供可自动运行的单元测试、冒烟测试或功能测试,由本地 AI 执行。
- 缺失测试的情况必须在验证文档中列为风险,并给出补测计划与截止时间。
- 测试需覆盖正常流程、边界条件与错误恢复,确保破坏性变更不会遗漏关键分支。
### 🏗️ 设计原则
- 严格遵循 SOLID、DRY 与关注点分离,任何共享逻辑都应抽象为复用组件。
- 依赖倒置与接口隔离优先,禁止临时绑死实现细节。
- 遇到复杂逻辑时必须先拆分职责,再进入编码。
### 💻 实现标准
- 绝对禁止 MVP、最小实现或占位符提交前必须完成全量功能与数据路径。
- 必须完善所有 MVP、最小实现和占位为完整的具体代码实现。
- 必须主动删除过时、重复或逃生式代码,保持实现整洁。
- 必须始终遵守编程语言标准代码风格和项目既有风格规范。
- 对破坏性改动不做向后兼容处理,同时提供迁移步骤或回滚方案。
- 必须始终采用颠覆式破坏性更改策略,绝对不向后兼容。
- 必须遵循最佳实践,确保代码质量和可维护性。
### ⚡ 性能意识
- 设计时必须评估时间复杂度、内存占用与 I/O 影响,避免无谓消耗。
- 识别潜在瓶颈后应提供监测或优化建议,确保可持续迭代。
- 禁止引入未经评估的昂贵依赖或阻塞操作。
### 🧩 测试思维
- 在编码前编制可验证的验收条件,并在验证文档中回填执行结果。
- 对预期失败场景提供处理策略,保证服务可控降级。
- 连续三次验证失败必须暂停实现,回到需求和设计阶段复盘。
## 🚀 强制工作流程
### ⚡ 总原则(必须遵循)
- **强制深度思考**:任何时候必须首先使用 sequential-thinking 工具梳理问题,这是开发工作的基础。
- 不是必要的问题,不要询问用户,必须自动连续执行,不能中断流程。
- 问题驱动优先于流程驱动,追求充分性而非完整性,动态调整而非僵化执行。
### 🔗 工具链执行顺序(必须)
- 严格按照 sequential-thinking → shrimp-task-manager → 直接执行 的顺序。
- 任一环节失败时,必须在操作日志中记录原因、补救措施与重新执行结果。
- 禁止跳过或调换顺序,必要时通过人工流程模拟缺失工具并记录。
### 🔍 信息检索与外部工具集成(必须)
**核心原则**
- 工具是手段,按需使用,避免僵化流程
- 所有引用资料必须写明来源与用途,保持可追溯
- 检索失败时,必须在日志中声明并改用替代方法
#### 本地文件和数据分析集成(最高优先级)
**desktop-commander - 本地文件和进程管理**(核心工具):
- **触发条件**任何本地文件操作、CSV/JSON/数据分析、进程管理
- **核心能力**
- 文件操作:`read_file``write_file``edit_block`(精确文本替换)
- 目录管理:`list_directory``create_directory``move_file`
- 搜索:`start_search`(支持文件名和内容搜索,流式返回结果)
- 进程管理:`start_process``interact_with_process`交互式REPL
- 数据分析支持Python/Node.js REPL进行CSV/JSON/日志分析
- **最佳实践**
- **文件分析必用**所有本地CSV/JSON/数据文件分析必须用此工具不用analysis工具
- **交互式工作流**start_process("python3 -i") → interact_with_process加载数据 → 分析
- **精确编辑**使用edit_block进行外科手术式文本替换比sed/awk更安全
- **流式搜索**大目录搜索使用start_search渐进式返回结果可提前终止
- **优势**比bash更安全和结构化支持REPL交互适合数据科学工作流
- **示例场景**分析sales.csv、处理config.json、搜索代码模式、管理后台进程
- **注意事项**
- 绝对优先于bash cat/grep/find等命令
- 本地文件分析禁止使用analysis/REPL工具会失败
- 使用绝对路径以保证可靠性
#### 编程文档检索优先级context7 优先)
**context7 - 编程库/SDK/API 文档**(最高优先级):
- **触发条件**任何关于编程库、框架、SDK、API 的问题
- **调用方式**
1. 首先调用 `resolve-library-id` 获取 Context7 兼容的库 ID
2. 然后调用 `get-library-docs` 获取文档(可选 topic 参数聚焦)
- **优势**专门优化编程上下文token 高效,最新官方文档
- **示例场景**React hooks 用法、Next.js 路由、MongoDB 查询语法
- **注意事项**:必须先 resolve-library-id除非用户明确提供 `/org/project` 格式的库 ID
**firecrawl - 通用网页检索**(通用后备):
- **触发条件**context7 无法满足、需要最新博客/文章/教程
- **调用方式**
1. `firecrawl_search`:搜索并抓取内容(推荐,自动返回内容)
2. `firecrawl_scrape`:单页抓取(已知 URL 时)
3. `firecrawl_map`:网站结构发现(探索网站时)
- **优势**:强大抓取能力、支持多种模式、处理复杂网页
- **示例场景**:最新技术趋势、社区最佳实践、问题排查博客
- **注意事项**:优先使用 search带 scrapeOptions避免过度抓取
## 项目概述
这是一个基于 **FastAdmin + ThinkPHP 5.x** 的后台管理系统,集成了 **Shopro B2C 商城** 插件和 **uni-app Vue3** 移动端前端。系统采用前后端分离架构,支持多端发布(iOS、Android、H5、微信小程序)。
## 架构说明
### 本地开发环境地址
前台地址
http://fengketrade.test/
后台地址
http://fengketrade.test/LHfulcxFUd.php
使用docker作为本地环境
### 核心模块结构
```
application/
├── admin/ # 后台管理模块(FastAdmin核心)
│ ├── controller/ # 后台控制器
│ ├── model/ # 后台模型
│ ├── validate/ # 表单验证器
│ ├── command/ # CLI命令(CRUD生成、菜单生成、API文档等)
│ └── library/ # Auth权限库、Backend基类
├── api/ # RESTful API模块(供移动端调用)
│ └── controller/ # API控制器
├── index/ # Web前台展示模块(可选)
│ └── controller/ # 前台控制器
└── common/ # 公共模块
├── controller/ # Frontend/Backend/Api基类控制器
├── model/ # 公共模型(User/Category/Attachment等)
├── library/ # 工具类(Upload/Token/Sms/Email/Auth)
└── exception/ # 自定义异常
```
### 插件系统
```
addons/
├── shopro/ # Shopro B2C商城插件(核心业务)
├── alioss/ # 阿里云OSS存储插件
├── alisms/ # 阿里云短信插件
└── ueditor/ # 百度编辑器插件
```
### 移动端前端
```
frontend/ # uni-app Vue3 商城前端
├── pages // 页面
│ ├── index // 入口页面
│ ├── user // 用户相关
│ ├── public // 公共页面
│ ├── activity // 活动页面
│ ├── app // 积分、签到页面
│ ├── chat // 客服页面
│ ├── commission // 分销页面
│ ├── coupon // 优惠券页面
│ ├── goods // 商品页面
│ ├── order // 订单页面
│ ├── pay // 支付页面
├── sheep // 底层依赖/工具库
│ ├── api // 服务端接口
│ ├── components // 自定义功能组件
│ ├── config // 配置文件
│ ├── helper // 助手函数
│ ├── hooks // vue-hooks
│ ├── libs // 自定义依赖
│ ├── platform // 第三方平台登录、分享、支付
│ ├── request // 请求类库
│ ├── router // 自定义路由跳转
│ ├── scss // 主样式库
│ ├── store // pinia状态管理模块
│ ├── ui // 自定义UI组件
│ ├── url // cdn图片地址格式化
│ ├── validate // 通用验证器
│ ├── index.js // Shopro入口文件
├── uni_modules // dcloud第三方插件
```
## 开发命令
### PHP后端开发
#### ThinkPHP CLI命令
```bash
# 查看所有可用命令
php think
# 安装FastAdmin(首次)
php think install
# 生成CRUD(从数据表生成控制器/模型/视图/JS/语言包)
php think crud -t <table_name>
# 生成后台菜单(从控制器生成菜单和权限规则)
php think menu
# 生成API接口文档
php think api
# 压缩JS/CSS资源
php think min
# 插件管理
php think addon
# 清除缓存
php think clear
# Shopro商城客服服务
php think shopro:chat
# 队列处理
php think queue:work
php think queue:listen
```
#### Composer依赖管理
```bash
# 安装依赖
composer install
# 更新依赖
composer update
```
### 前端资源构建
#### Grunt构建(后台静态资源)
```bash
# 安装npm依赖
npm install
# 完整构建(部署依赖库 + 压缩前端/后端JS/CSS)
npm run build
# 等同于: grunt
# 单独任务
grunt deploy # 从node_modules复制库文件到public/assets/libs
grunt frontend:js # 压缩前端JS
grunt backend:js # 压缩后端JS
grunt frontend:css # 压缩前端CSS
grunt backend:css # 压缩后端CSS
```
#### uni-app前端开发(移动端商城)
注意: uni-app项目需要在HBuilderX或通过uni-app CLI工具进行开发和构建,不在命令行直接编译。
```bash
# 进入前端目录
cd frontend
# 格式化代码
npm run prettier
```
## 关键技术要点
### 1. 权限系统(Auth)
FastAdmin使用基于角色的权限管理(RBAC):
- `application/admin/library/Auth.php` - 后台权限验证类
- `application/common/library/Auth.php` - 通用权限库
- 支持无限级父子级权限继承
- 支持单管理员多角色
- 控制器方法通过 `$this->auth->check()` 验证权限
### 2. 后台控制器基类
所有后台控制器应继承自:
- `application/common/controller/Backend.php` - 标准后台控制器基类
- `application/admin/library/traits/Backend.php` - 包含CRUD通用方法的Trait
自动提供的功能:
- `index()` - 列表页
- `add()` - 添加
- `edit()` - 编辑
- `del()` - 删除
- `multi()` - 批量操作
- `recyclebin()` - 回收站
### 3. API开发规范
API控制器继承自 `application/common/controller/Api.php`:
- 自动处理Token验证
- 统一JSON响应格式
- 跨域请求支持
- 使用 `$this->success()``$this->error()` 返回响应
### 4. 插件开发
FastAdmin插件系统:
- 插件位于 `addons/` 目录
- 每个插件包含独立的控制器、模型、视图、配置
- 通过 `php think addon` 命令管理
- 插件可hook系统行为(application/tags.php)
### 5. 前后端交互
- 移动端(frontend) 通过API模块与后端通信
- API接口路由定义在 `application/api/controller/`
- 使用Token机制进行用户认证
- 数据格式统一为JSON
## 数据库迁移
FastAdmin没有内置迁移系统,数据库变更通过:
1. 直接在数据库执行SQL
2. 使用 `php think crud` 命令同步表结构到代码
## 配置文件
- `application/config.php` - 应用主配置
- `application/database.php` - 数据库配置
- `application/extra/` - 扩展配置(上传、支付等)
- `.env` - 环境变量配置
## 日志与调试
- 日志文件位置: `runtime/log/`
- 开启调试模式: 在 `.env` 中设置 `APP_DEBUG = true`
- SQL日志: 在 `application/database.php` 中配置
## 注意事项
### 文件权限
确保以下目录可写:
- `runtime/` - 缓存和日志
- `public/uploads/` - 上传文件
- `addons/` - 插件安装
### 开发规范
1. **控制器命名**: 使用驼峰命名法,如 `UserGroup.php`
2. **模型关联**: 在model中定义关联关系,使用ThinkPHP的关联查询
3. **验证器**: 为表单创建验证器类在 `application/*/validate/`
4. **语言包**: 多语言支持,语言文件在 `application/*/lang/`
5. **代码生成**: 优先使用 `php think crud` 生成标准CRUD代码,保持风格一致
### 安全考虑
- 所有用户输入必须经过验证和过滤
- SQL查询使用参数绑定防止注入
- 文件上传严格限制类型和大小
- 敏感配置使用 `.env` 存储(不提交到版本控制)
- API接口必须验证Token和权限
## 部署打包
### 后端部署
1. 上传代码到服务器
2. 运行 `composer install --no-dev`
3. 配置Web服务器指向 `public/` 目录
4. 设置 `runtime/``public/uploads/``addons/` 权限
5. 导入数据库
6. 配置 `.env` 文件
### 前端部署(移动端)
使用HBuilderX:
1. 打开 `frontend` 项目
2. 发行 → 选择目标平台(H5/小程序/App)
3. 按照平台要求配置和发布
### 静态资源CDN
- 使用 `grunt deploy` 部署前端库
- 修改 `public/assets/js/require-*.js` 中的CDN路径
- 使用 `grunt` 压缩资源以减小体积
## 常见问题
### 如何添加新的后台模块?
```bash
# 1. 在数据库创建表
# 2. 生成CRUD代码
php think crud -t your_table_name
# 3. 生成菜单
php think menu
```
### 如何开发新的API接口?
1. 在 `application/api/controller/` 创建控制器
2. 继承 `Api` 基类
3. 设置 `$noNeedLogin``$noNeedRight` 属性控制权限
4. 实现业务逻辑并返回JSON
### 如何修改前端样式?
后台样式基于AdminLTE和Bootstrap:
- Less源文件在 `public/assets/less/`
- 修改后运行 `grunt backend:css` 重新编译
移动端样式:
- Vue组件使用scoped style
- 全局样式在 `frontend/sheep/` 组件库中
## 相关文档
- FastAdmin官方文档: https://doc.fastadmin.net
- ThinkPHP 5.x文档: https://www.kancloud.cn/manual/thinkphp5/
- uni-app文档: https://uniapp.dcloud.io
- Shopro商城文档: https://www.shopro.top

139
Gruntfile.js Executable file
View File

@ -0,0 +1,139 @@
module.exports = function (grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
copy: {
main: {
files: []
}
}
});
var build = function (module, type, callback) {
var config = {
compile: {
options: type === 'js' ? {
optimizeCss: "standard",
optimize: "uglify", //可使用uglify|closure|none
preserveLicenseComments: true,
removeCombined: false,
baseUrl: "./public/assets/js/", //JS文件所在的基础目录
name: "require-" + module, //来源文件,不包含后缀
out: "./public/assets/js/require-" + module + ".min.js" //目标文件
} : {
optimizeCss: "default",
optimize: "uglify", //可使用uglify|closure|none
cssIn: "./public/assets/css/" + module + ".css", //CSS文件所在的基础目录
out: "./public/assets/css/" + module + ".min.css" //目标文件
}
}
};
var content = grunt.file.read("./public/assets/js/require-" + module + ".js"),
pattern = /^require\.config\(\{[\r\n]?[\n]?(.*?)[\r\n]?[\n]?}\);/is;
var matches = content.match(pattern);
if (matches) {
if (type === 'js') {
var data = matches[1].replaceAll(/(urlArgs|baseUrl):(.*)\n/gi, '');
const parse = require('parse-config-file'), fs = require('fs');
require('jsonminify');
data = JSON.minify("{\n" + data + "\n}");
let options = parse(data);
options.paths.tableexport = "empty:";
Object.assign(config.compile.options, options);
}
let requirejs = require("./application/admin/command/Min/r");
try {
requirejs.optimize(config.compile.options, function (buildResponse) {
// var contents = require('fs').readFileSync(config.compile.options.out, 'utf8');
callback();
}, function (err) {
console.error(err);
callback();
});
} catch (err) {
console.error(err);
callback();
}
}
};
// 加载 "copy" 插件
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.registerTask('frontend:js', 'build frontend js', function () {
var done = this.async();
build('frontend', 'js', done);
});
grunt.registerTask('backend:js', 'build backend js', function () {
var done = this.async();
build('backend', 'js', done);
});
grunt.registerTask('frontend:css', 'build frontend css', function () {
var done = this.async();
build('frontend', 'css', done);
});
grunt.registerTask('backend:css', 'build frontend css', function () {
var done = this.async();
build('backend', 'css', done);
});
// 注册部署JS和CSS任务
grunt.registerTask('deploy', 'deploy', function () {
const fs = require('fs');
const path = require("path")
const nodeModulesDir = path.resolve(__dirname, "./node_modules");
const getAllFiles = function (dirPath, arrayOfFiles) {
files = fs.readdirSync(dirPath)
arrayOfFiles = arrayOfFiles || []
files.forEach(function (file) {
if (fs.statSync(dirPath + "/" + file).isDirectory()) {
arrayOfFiles = getAllFiles(dirPath + "/" + file, arrayOfFiles)
} else {
arrayOfFiles.push(path.join(__dirname, dirPath, "/", file))
}
});
return arrayOfFiles
};
const mainPackage = grunt.config.get('pkg');
let dists = mainPackage.dists || [];
let files = [];
// 兼容旧版本bower使用的目录
let specialKey = {
'fastadmin-bootstraptable': 'bootstrap-table',
'sortablejs': 'Sortable',
'tableexport.jquery.plugin': 'tableExport.jquery.plugin',
};
Object.keys(dists).forEach(key => {
let src = ["**/*LICENSE*", "**/*license*"];
src = src.concat(Array.isArray(dists[key]) ? dists[key] : [dists[key]]);
files.push({expand: true, cwd: nodeModulesDir + "/" + key, src: src, dest: 'public/assets/libs/' + (specialKey[key] || key) + "/"});
});
// 兼容bower历史路径文件
files = [...files,
{src: nodeModulesDir + "/toastr/build/toastr.min.css", dest: "public/assets/libs/toastr/toastr.min.css"},
{src: nodeModulesDir + "/bootstrap-slider/dist/css/bootstrap-slider.css", dest: "public/assets/libs/bootstrap-slider/slider.css"},
{expand: true, cwd: nodeModulesDir + "/bootstrap-slider/dist", src: ["*.js"], dest: "public/assets/libs/bootstrap-slider/"}
]
grunt.config.set('copy.main.files', files);
grunt.task.run("copy:main");
});
// 注册默认任务
grunt.registerTask('default', ['deploy', 'frontend:js', 'backend:js', 'frontend:css', 'backend:css']);
};

191
LICENSE Executable file
View File

@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "{}" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.
Copyright 2017 Karson
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

93
README.md Executable file
View File

@ -0,0 +1,93 @@
FastAdmin是一款基于ThinkPHP+Bootstrap的极速后台开发框架。
## 主要特性
* 基于`Auth`验证的权限管理系统
* 支持无限级父子级权限继承,父级的管理员可任意增删改子级管理员及权限设置
* 支持单管理员多角色
* 支持管理子级数据或个人数据
* 强大的一键生成功能
* 一键生成CRUD,包括控制器、模型、视图、JS、语言包、菜单、回收站等
* 一键压缩打包JS和CSS文件一键CDN静态资源部署
* 一键生成控制器菜单和规则
* 一键生成API接口文档
* 完善的前端功能组件开发
* 基于`AdminLTE`二次开发
* 基于`Bootstrap`开发自适应手机、平板、PC
* 基于`RequireJS`进行JS模块管理按需加载
* 基于`Less`进行样式开发
* 强大的插件扩展功能,在线安装卸载升级插件
* 通用的会员模块和API模块
* 共用同一账号体系的Web端会员中心权限验证和API接口会员权限验证
* 二级域名部署支持,同时域名支持绑定到应用插件
* 多语言支持,服务端及客户端支持
* 支持大文件分片上传、剪切板粘贴上传、拖拽上传,进度条显示,图片上传前压缩
* 支持表格固定列、固定表头、跨页选择、Excel导出、模板渲染等功能
* 强大的第三方应用模块支持([CMS](https://www.fastadmin.net/store/cms.html)、[CRM](https://www.fastadmin.net/store/facrm.html)、[企业网站管理系统](https://www.fastadmin.net/store/ldcms.html)、[知识库文档系统](https://www.fastadmin.net/store/knowbase.html)、[在线投票系统](https://www.fastadmin.net/store/vote.html)、[B2C商城](https://www.fastadmin.net/store/shopro.html)、[B2B2C商城](https://www.fastadmin.net/store/wanlshop.html))
* 整合第三方短信接口(阿里云、腾讯云短信)
* 无缝整合第三方云存储(七牛云、阿里云OSS、腾讯云存储、又拍云)功能,支持云储存分片上传
* 第三方富文本编辑器支持(Summernote、百度编辑器)
* 第三方登录(QQ、微信、微博)整合
* 第三方支付(微信、支付宝)无缝整合微信支持PC端扫码支付
* 丰富的插件应用市场
## 安装使用
https://doc.fastadmin.net
## 在线演示
https://demo.fastadmin.net
用户名admin
 123456
提 示:演示站数据无法进行修改,请下载源码安装体验全部功能
## 界面截图
![控制台](https://images.gitee.com/uploads/images/2020/0929/202947_8db2d281_10933.gif "控制台")
## 问题反馈
在使用中有任何问题,请使用以下联系方式联系我们
问答社区: https://ask.fastadmin.net
Github: https://github.com/fastadminnet/fastadmin
Gitee: https://gitee.com/fastadminnet/fastadmin
## 特别鸣谢
感谢以下的项目,排名不分先后
ThinkPHPhttp://www.thinkphp.cn
AdminLTEhttps://adminlte.io
Bootstraphttp://getbootstrap.com
jQueryhttp://jquery.com
Bootstrap-tablehttps://github.com/wenzhixin/bootstrap-table
Nice-validator: https://validator.niceue.com
SelectPage: https://github.com/TerryZ/SelectPage
Layer: https://layuion.com/layer/
DropzoneJS: https://www.dropzonejs.com
## 版权信息
FastAdmin遵循Apache2开源协议发布并提供免费使用。
本项目包含的第三方源码和二进制文件之版权信息另行标注。
版权所有Copyright © 2017-2024 by FastAdmin (https://www.fastadmin.net)
All rights reserved。

1
addons/.gitkeep Executable file
View File

@ -0,0 +1 @@

1
addons/.htaccess Executable file
View File

@ -0,0 +1 @@
deny from all

1
addons/alioss/.addonrc Normal file
View File

@ -0,0 +1 @@
{"files":["public\/assets\/addons\/alioss\/js\/spark.js"],"license":"regular","licenseto":"62324","licensekey":"rfKIuYDF4PjcXSk1 q7durgEz5zvAGjvjN+a8jg==","domains":["fengketrade.com"],"licensecodes":[],"validations":["e4f0590e78c2a8b9d0c33fe33d4f9e4d"]}

124
addons/alioss/Alioss.php Normal file
View File

@ -0,0 +1,124 @@
<?php
namespace addons\alioss;
use addons\alioss\library\Auth;
use OSS\Core\OssException;
use OSS\OssClient;
use think\Addons;
use think\App;
use think\Config;
use think\Loader;
/**
* 阿里云OSS上传插件
*/
class Alioss extends Addons
{
/**
* 插件安装方法
* @return bool
*/
public function install()
{
return true;
}
/**
* 插件卸载方法
* @return bool
*/
public function uninstall()
{
return true;
}
/**
* 添加命名空间
*/
public function appInit()
{
if (!class_exists("\OSS\OssClient")) {
//添加支付包的命名空间
Loader::addNamespace('OSS', ADDON_PATH . 'alioss' . DS . 'library' . DS . 'OSS' . DS);
}
}
/**
* 判断是否来源于API上传
*/
public function moduleInit($request)
{
$config = $this->getConfig();
$module = strtolower($request->module());
// 判断api/common/upload 是否使用云存储上传
if ($module == 'api' && ($config['apiupload'] ?? 0) &&
strtolower($request->controller()) == 'common' &&
strtolower($request->action()) == 'upload') {
request()->param('isApi', true);
App::invokeMethod(["\\addons\\alioss\\controller\\Index", "upload"], ['isApi' => true]);
}
}
/**
* 加载配置
*/
public function uploadConfigInit(&$upload)
{
$config = $this->getConfig();
$data = ['deadline' => time() + $config['expire']];
$signature = hash_hmac('sha1', json_encode($data), $config['accessKeySecret'], true);
$token = '';
if (Auth::isModuleAllow()) {
$token = $config['accessKeyId'] . ':' . base64_encode($signature) . ':' . base64_encode(json_encode($data));
}
$multipart = [
'aliosstoken' => $token
];
$config['uploadurl'] = 'https://' . $config['bucket'] . '.' . $config['endpoint'];
$upload = array_merge($upload, [
'cdnurl' => $config['cdnurl'],
'uploadurl' => $config['uploadmode'] == 'client' ? $config['uploadurl'] : addon_url('alioss/index/upload', [], false, true),
'uploadmode' => $config['uploadmode'],
'bucket' => $config['bucket'],
'maxsize' => $config['maxsize'],
'mimetype' => $config['mimetype'],
'savekey' => $config['savekey'],
'chunking' => (bool)($config['chunking'] ?? $upload['chunking']),
'chunksize' => (int)($config['chunksize'] ?? $upload['chunksize']),
'multipart' => $multipart,
'storage' => $this->getName(),
'multiple' => (bool)$config['multiple'],
]);
}
/**
* 附件删除后
*/
public function uploadDelete($attachment)
{
$config = $this->getConfig();
if ($attachment['storage'] == 'alioss' && isset($config['syncdelete']) && $config['syncdelete']) {
// 删除云存储端文件
try {
$ossClient = new OssClient($config['accessKeyId'], $config['accessKeySecret'], $config['endpoint']);
$ossClient->deleteObject($config['bucket'], ltrim($attachment->url, '/'));
} catch (OssException $e) {
return false;
}
//如果是服务端中转,还需要删除本地文件
//if ($config['uploadmode'] == 'server') {
// $filePath = ROOT_PATH . 'public' . str_replace('/', DS, $attachment->url);
// if ($filePath) {
// @unlink($filePath);
// }
//}
}
return true;
}
}

File diff suppressed because one or more lines are too long

241
addons/alioss/bootstrap.js vendored Executable file
View File

@ -0,0 +1,241 @@
if (typeof Config.upload.storage !== 'undefined' && Config.upload.storage === 'alioss') {
require(['upload'], function (Upload) {
//获取文件MD5值
var getFileMd5 = function (file, cb) {
//如果savekey中未检测到md5则无需获取文件md5直接返回upload的uuid
if (!Config.upload.savekey.match(/\{(file)?md5\}/)) {
cb && cb(file.upload.uuid);
return;
}
require(['../addons/alioss/js/spark'], function (SparkMD5) {
var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
chunkSize = 10 * 1024 * 1024,
chunks = Math.ceil(file.size / chunkSize),
currentChunk = 0,
spark = new SparkMD5.ArrayBuffer(),
fileReader = new FileReader();
fileReader.onload = function (e) {
spark.append(e.target.result);
currentChunk++;
if (currentChunk < chunks) {
loadNext();
} else {
cb && cb(spark.end());
}
};
fileReader.onerror = function () {
console.warn('文件读取错误');
};
function loadNext() {
var start = currentChunk * chunkSize,
end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
}
loadNext();
});
};
var _onInit = Upload.events.onInit;
//初始化中完成判断
Upload.events.onInit = function () {
_onInit.apply(this, Array.prototype.slice.apply(arguments));
//如果上传接口不是阿里OSS则不处理
if (this.options.url !== Config.upload.uploadurl) {
return;
}
$.extend(this.options, {
//关闭自动处理队列功能
autoQueue: false,
params: function (files, xhr, chunk) {
var params = Config.upload.multipart;
if (chunk) {
return $.extend({}, params, {
filesize: chunk.file.size,
filename: chunk.file.name,
chunkid: chunk.file.upload.uuid,
chunkindex: chunk.index,
chunkcount: chunk.file.upload.totalChunkCount,
chunksize: this.options.chunkSize,
chunkfilesize: chunk.dataBlock.data.size,
width: chunk.file.width || 0,
height: chunk.file.height || 0,
type: chunk.file.type,
uploadId: chunk.file.uploadId,
key: chunk.file.key,
});
} else {
params = $.extend({}, params, files[0].params);
params.category = files[0].category || '';
}
return params;
},
chunkSuccess: function (chunk, file, response) {
var etag = chunk.xhr.getResponseHeader("ETag").replace(/(^")|("$)/g, '');
file.etags = file.etags ? file.etags : [];
file.etags[chunk.index] = etag;
},
chunksUploaded: function (file, done) {
var that = this;
Fast.api.ajax({
url: "/addons/alioss/index/upload",
data: {
action: 'merge',
filesize: file.size,
filename: file.name,
chunkid: file.upload.uuid,
chunkcount: file.upload.totalChunkCount,
md5: file.md5,
key: file.key,
uploadId: file.uploadId,
etags: file.etags,
category: file.category || '',
aliosstoken: Config.upload.multipart.aliosstoken,
},
}, function (data, ret) {
done(JSON.stringify(ret));
return false;
}, function (data, ret) {
file.accepted = false;
that._errorProcessing([file], ret.msg);
return false;
});
},
});
var _success = this.options.success;
//先移除已有的事件
this.off("success", _success).on("success", function (file, response) {
var ret = {code: 0, msg: response};
try {
if (response) {
ret = typeof response === 'string' ? JSON.parse(response) : response;
}
if (file.xhr.status === 200) {
if (Config.upload.uploadmode === 'client') {
ret = {code: 1, data: {url: '/' + file.key}};
var url = ret.data.url || '';
Fast.api.ajax({
url: "/addons/alioss/index/notify",
data: {name: file.name, url: url, md5: file.md5, size: file.size, width: file.width || 0, height: file.height || 0, type: file.type, category: file.category || '', aliosstoken: Config.upload.multipart.aliosstoken}
}, function () {
return false;
}, function () {
return false;
});
}
} else {
console.error(file.xhr);
}
} catch (e) {
console.error(e);
}
_success.call(this, file, ret);
});
this.on("addedfile", function (file) {
var that = this;
setTimeout(function () {
if (file.status === 'error') {
return;
}
getFileMd5(file, function (md5) {
var chunk = that.options.chunking && file.size > that.options.chunkSize ? 1 : 0;
var params = $(that.element).data("params") || {};
var category = typeof params.category !== 'undefined' ? params.category : ($(that.element).data("category") || '');
category = typeof category === 'function' ? category.call(that, file) : category;
Fast.api.ajax({
url: "/addons/alioss/index/params",
data: {method: 'POST', category: category, md5: md5, name: file.name, type: file.type, size: file.size, chunk: chunk, chunksize: that.options.chunkSize, aliosstoken: Config.upload.multipart.aliosstoken},
}, function (data) {
file.md5 = md5;
file.id = data.id;
file.key = data.key;
file.date = data.date;
file.uploadId = data.uploadId;
file.policy = data.policy;
file.signature = data.signature;
file.partsAuthorization = data.partsAuthorization;
file.params = data;
file.category = category;
if (file.status != 'error') {
//开始上传
that.enqueueFile(file);
} else {
that.removeFile(file);
}
return false;
}, function () {
that.removeFile(file);
});
});
}, 0);
});
if (Config.upload.uploadmode === 'client') {
var _method = this.options.method;
var _url = this.options.url;
this.options.method = function (files) {
if (files[0].upload.chunked) {
var chunk = null;
files[0].upload.chunks.forEach(function (item) {
if (item.status === 'uploading') {
chunk = item;
}
});
if (!chunk) {
return "POST";
} else {
return "PUT";
}
}
return _method;
};
this.options.url = function (files) {
if (files[0].upload.chunked) {
var chunk = null;
files[0].upload.chunks.forEach(function (item) {
if (item.status === 'uploading') {
chunk = item;
}
});
var index = chunk.dataBlock.chunkIndex;
// debugger;
this.options.headers = {"Authorization": "OSS " + files[0]['id'] + ":" + files[0]['partsAuthorization'][index], "x-oss-date": files[0]['date']};
if (!chunk) {
return Config.upload.uploadurl + "/" + files[0].key + "?uploadId=" + files[0].uploadId;
} else {
return Config.upload.uploadurl + "/" + files[0].key + "?partNumber=" + (index + 1) + "&uploadId=" + files[0].uploadId;
}
}
return _url;
};
this.on("sending", function (file, xhr, formData) {
var that = this;
if (file.upload.chunked) {
var _send = xhr.send;
xhr.send = function () {
var chunk = null;
file.upload.chunks.forEach(function (item) {
if (item.status == 'uploading') {
chunk = item;
}
});
if (chunk) {
_send.call(xhr, chunk.dataBlock.data);
}
};
}
});
}
};
});
}

233
addons/alioss/config.php Normal file
View File

@ -0,0 +1,233 @@
<?php
return [
[
'name' => 'accessKeyId',
'title' => 'AccessKey ID',
'type' => 'string',
'content' => [],
'value' => '',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
],
[
'name' => 'accessKeySecret',
'title' => 'AccessKey Secret',
'type' => 'string',
'content' => [],
'value' => '',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
],
[
'name' => 'bucket',
'title' => 'Bucket名称',
'type' => 'string',
'content' => [],
'value' => 'yourbucket',
'rule' => 'required;bucket',
'msg' => '',
'tip' => '阿里云OSS的空间名',
'ok' => '',
'extend' => 'data-rule-bucket="[/^[0-9a-z_\-]{3,63}$/, \'请输入正确的Bucket名称\']"',
],
[
'name' => 'endpoint',
'title' => 'Endpoint',
'type' => 'string',
'content' => [],
'value' => 'oss-cn-shenzhen.aliyuncs.com',
'rule' => 'required;endpoint',
'msg' => '',
'tip' => '请填写从阿里云存储获取的Endpoint',
'ok' => '',
'extend' => 'data-rule-endpoint="[/^(?!http(s)?:\/\/).*$/, \'不能以http(s)://开头\']"',
],
[
'name' => 'cdnurl',
'title' => 'CDN地址',
'type' => 'string',
'content' => [],
'value' => 'https://yourbucket.oss-cn-shenzhen.aliyuncs.com',
'rule' => 'required;cdnurl',
'msg' => '',
'tip' => '请填写CDN地址必须以http(s)://开头',
'ok' => '',
'extend' => 'data-rule-cdnurl="[/^http(s)?:\/\/.*$/, \'必需以http(s)://开头\']"',
],
[
'name' => 'uploadmode',
'title' => '上传模式',
'type' => 'select',
'content' => [
'client' => '客户端直传(速度快,无备份)',
'server' => '服务器中转(占用服务器带宽,可备份)',
],
'value' => 'server',
'rule' => '',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
],
[
'name' => 'serverbackup',
'title' => '服务器中转模式备份',
'type' => 'radio',
'content' => [
1 => '备份(附件管理将产生2条记录)',
0 => '不备份',
],
'value' => '1',
'rule' => '',
'msg' => '',
'tip' => '服务器中转模式下是否备份文件',
'ok' => '',
'extend' => '',
],
[
'name' => 'savekey',
'title' => '保存文件名',
'type' => 'string',
'content' => [],
'value' => '/uploads/{year}{mon}{day}/{filemd5}{.suffix}',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
],
[
'name' => 'expire',
'title' => '上传有效时长',
'type' => 'string',
'content' => [],
'value' => '600',
'rule' => 'required',
'msg' => '',
'tip' => '用户停留页面上传有效时长,单位秒',
'ok' => '',
'extend' => '',
],
[
'name' => 'maxsize',
'title' => '最大可上传',
'type' => 'string',
'content' => [],
'value' => '10M',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
],
[
'name' => 'mimetype',
'title' => '可上传后缀格式',
'type' => 'string',
'content' => [],
'value' => 'jpg,png,bmp,jpeg,gif,zip,rar,xls,xlsx',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
],
[
'name' => 'multiple',
'title' => '多文件上传',
'type' => 'bool',
'content' => [],
'value' => '0',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
],
[
'name' => 'thumbstyle',
'title' => '缩略图样式',
'type' => 'string',
'content' => [],
'value' => '',
'rule' => '',
'msg' => '',
'tip' => '用于后台列表缩略图样式,可使用:?x-oss-process=image/resize,m_lfit,w_120,h_90',
'ok' => '',
'extend' => '',
],
[
'name' => 'chunking',
'title' => '分片上传',
'type' => 'radio',
'content' => [
1 => '开启',
0 => '关闭',
],
'value' => '0',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
],
[
'name' => 'chunksize',
'title' => '分片大小',
'type' => 'number',
'content' => [],
'value' => '4194304',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
],
[
'name' => 'syncdelete',
'title' => '附件删除时是否同步删除云存储文件',
'type' => 'bool',
'content' => [],
'value' => '0',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
],
[
'name' => 'apiupload',
'title' => 'API接口使用云存储',
'type' => 'bool',
'content' => [],
'value' => '0',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
],
[
'name' => 'noneedlogin',
'title' => '免登录上传',
'type' => 'checkbox',
'content' => [
'api' => 'API',
'index' => '前台',
'admin' => '后台',
],
'value' => '',
'rule' => '',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
],
];

View File

@ -0,0 +1,292 @@
<?php
namespace addons\alioss\controller;
use addons\alioss\library\Auth;
use app\common\exception\UploadException;
use app\common\library\Upload;
use app\common\model\Attachment;
use OSS\Core\OssException;
use OSS\OssClient;
use think\addons\Controller;
use think\Config;
/**
* 阿里OSS云储存
*
*/
class Index extends Controller
{
public function _initialize()
{
//跨域检测
check_cors_request();
parent::_initialize();
Config::set('default_return_type', 'json');
}
public function index()
{
Config::set('default_return_type', 'html');
$this->error("当前插件暂无前台页面");
}
/**
* 获取签名
*/
public function params()
{
$this->check();
$config = get_addon_config('alioss');
$name = $this->request->post('name');
$md5 = $this->request->post('md5');
$chunk = $this->request->post('chunk');
$name = xss_clean($name);
// 检查文件后缀
$extension = strtolower(pathinfo($name, PATHINFO_EXTENSION));
$allowedExtensions = explode(',', strtolower($config['mimetype']));
if (!in_array($extension, $allowedExtensions) || in_array($extension, ['php', 'html', 'htm', 'phar', 'phtml']) || preg_match("/^php(.*)/i", $extension)) {
$this->error('不允许的文件类型');
}
$auth = new \addons\alioss\library\Auth();
$params = $auth->params($name, $md5);
$params['OSSAccessKeyId'] = $params['id'];
$params['success_action_status'] = 200;
$config = get_addon_config('alioss');
if ($chunk) {
$oss = new OssClient($config['accessKeyId'], $config['accessKeySecret'], $config['endpoint']);
// 初始化
$fileSize = $this->request->post('size');
$chunkSize = $this->request->post('chunksize');
$uploadId = $oss->initiateMultipartUpload($config['bucket'], $params['key']);
$params['uploadId'] = $uploadId;
$params['parts'] = $oss->generateMultiuploadParts($fileSize, $chunkSize);
$params['partsAuthorization'] = [];
$date = gmdate('D, d M Y H:i:s \G\M\T');
foreach ($params['parts'] as $index => $part) {
$partNumber = $index + 1;
$signstr = "PUT\n\n\n{$date}\nx-oss-date:{$date}\n/{$config['bucket']}/{$params['key']}?partNumber={$partNumber}&uploadId={$uploadId}";
$authorization = base64_encode(hash_hmac('sha1', $signstr, $config['accessKeySecret'], true));
$params['partsAuthorization'][$index] = $authorization;
}
$params['date'] = $date;
}
$this->success('', null, $params);
}
/**
* 服务器中转上传文件
* 上传分片
* 合并分片
* @param bool $isApi
*/
public function upload($isApi = false)
{
$config = get_addon_config('alioss');
if ($isApi === true) {
if (!Auth::isModuleAllow()) {
$this->error("请登录后再进行操作");
}
} else {
$this->check();
}
$oss = new OssClient($config['accessKeyId'], $config['accessKeySecret'], $config['endpoint']);
//检测删除文件或附件
$checkDeleteFile = function ($attachment, $upload, $force = false) use ($config) {
//如果设定为不备份则删除文件和记录 或 强制删除
if ((isset($config['serverbackup']) && !$config['serverbackup']) || $force) {
if ($attachment && !empty($attachment['id'])) {
$attachment->delete();
}
if ($upload) {
//文件绝对路径
$filePath = $upload->getFile()->getRealPath() ?: $upload->getFile()->getPathname();
@unlink($filePath);
}
}
};
$chunkid = $this->request->post("chunkid");
if ($chunkid) {
$action = $this->request->post("action");
$chunkindex = $this->request->post("chunkindex/d");
$chunkcount = $this->request->post("chunkcount/d");
$filesize = $this->request->post("filesize");
$filename = $this->request->post("filename");
$method = $this->request->method(true);
$key = $this->request->post("key");
$uploadId = $this->request->post("uploadId");
if ($action == 'merge') {
$attachment = null;
$upload = null;
//合并分片
if ($config['uploadmode'] == 'server') {
//合并分片文件
try {
$upload = new Upload();
$attachment = $upload->merge($chunkid, $chunkcount, $filename);
} catch (UploadException $e) {
$this->error($e->getMessage());
}
}
$etags = $this->request->post("etags/a", []);
if (count($etags) != $chunkcount) {
$checkDeleteFile($attachment, $upload, true);
$this->error("分片数据错误");
}
$listParts = [];
for ($i = 0; $i < $chunkcount; $i++) {
$listParts[] = array("PartNumber" => $i + 1, "ETag" => $etags[$i]);
}
try {
$ret = $oss->completeMultipartUpload($config['bucket'], $key, $uploadId, $listParts);
} catch (\Exception $e) {
$checkDeleteFile($attachment, $upload, true);
$this->error($e->getMessage());
}
$result = json_decode(json_encode(simplexml_load_string($ret['body'], "SimpleXMLElement", LIBXML_NOCDATA)), true);
if (!isset($result['Key'])) {
$checkDeleteFile($attachment, $upload, true);
$this->error("上传失败(1001)");
} else {
$checkDeleteFile($attachment, $upload);
$this->success("上传成功", '', ['url' => "/" . $key, 'fullurl' => cdnurl("/" . $key, true)]);
}
} else {
//默认普通上传文件
$file = $this->request->file('file');
try {
$upload = new Upload($file);
$file = $upload->chunk($chunkid, $chunkindex, $chunkcount);
} catch (UploadException $e) {
$this->error($e->getMessage());
}
try {
//上传分片到OSS
$ret = $oss->uploadPart($config['bucket'], $key, $uploadId, ['fileUpload' => $file->getRealPath(), 'partNumber' => $chunkindex + 1]);
} catch (\Exception $e) {
$this->error($e->getMessage());
}
$this->success("上传成功", "", [], 3, ['ETag' => $ret]);
}
} else {
$attachment = null;
//默认普通上传文件
$file = $this->request->file('file');
try {
$upload = new Upload($file);
$attachment = $upload->upload();
} catch (UploadException $e) {
$this->error($e->getMessage());
}
//文件绝对路径
$filePath = $upload->getFile()->getRealPath() ?: $upload->getFile()->getPathname();
$url = $attachment->url;
try {
$ret = $oss->uploadFile($config['bucket'], ltrim($attachment->url, "/"), $filePath);
//成功不做任何操作
} catch (\Exception $e) {
$checkDeleteFile($attachment, $upload, true);
\think\Log::write($e->getMessage());
$this->error("上传失败(1002)");
}
$checkDeleteFile($attachment, $upload);
// 记录云存储记录
$data = $attachment->toArray();
unset($data['id']);
$data['storage'] = 'alioss';
Attachment::create($data, true);
$this->success("上传成功", '', ['url' => $url, 'fullurl' => cdnurl($url, true)]);
}
return;
}
/**
* 回调
*/
public function notify()
{
$this->check();
$config = get_addon_config('alioss');
if ($config['uploadmode'] != 'client') {
$this->error("无需执行该操作");
}
$this->request->filter('trim,strip_tags,htmlspecialchars,xss_clean');
$size = $this->request->post('size/d');
$name = $this->request->post('name', '');
$md5 = $this->request->post('md5', '');
$type = $this->request->post('type', '');
$url = $this->request->post('url', '');
$width = $this->request->post('width/d');
$height = $this->request->post('height/d');
$category = $this->request->post('category', '');
$category = array_key_exists($category, config('site.attachmentcategory') ?? []) ? $category : '';
$suffix = strtolower(pathinfo($name, PATHINFO_EXTENSION));
$suffix = $suffix && preg_match("/^[a-zA-Z0-9]+$/", $suffix) ? $suffix : 'file';
$attachment = Attachment::where('url', $url)->where('storage', 'alioss')->find();
if (!$attachment) {
$params = array(
'category' => $category,
'admin_id' => (int)session('admin.id'),
'user_id' => (int)$this->auth->id,
'filesize' => $size,
'filename' => $name,
'imagewidth' => $width,
'imageheight' => $height,
'imagetype' => $suffix,
'imageframes' => 0,
'mimetype' => $type,
'url' => $url,
'uploadtime' => time(),
'storage' => 'alioss',
'sha1' => $md5,
);
Attachment::create($params, true);
}
$this->success();
}
/**
* 检查签名是否正确或过期
*/
protected function check()
{
$aliosstoken = $this->request->post('aliosstoken', '', 'trim');
if (!$aliosstoken) {
$this->error("参数不正确(code:1)");
}
$config = get_addon_config('alioss');
list($accessKeyId, $sign, $data) = explode(':', $aliosstoken);
if (!$accessKeyId || !$sign || !$data) {
$this->error("参数不能为空(code:2)");
}
if ($accessKeyId !== $config['accessKeyId']) {
$this->error("参数不正确(code:3)");
}
if ($sign !== base64_encode(hash_hmac('sha1', base64_decode($data), $config['accessKeySecret'], true))) {
$this->error("签名不正确");
}
$json = json_decode(base64_decode($data), true);
if ($json['deadline'] < time()) {
$this->error("请求已经超时");
}
}
}

10
addons/alioss/info.ini Normal file
View File

@ -0,0 +1,10 @@
name = alioss
title = 阿里云OSS云储存
intro = 使用阿里云OSS云储存,支持直传、服务器中转、分片上传
author = FastAdmin
website = https://www.fastadmin.net
version = 1.2.6
state = 0
url = /addons/alioss
license = regular
licenseto = 62324

102
addons/alioss/library/Auth.php Executable file
View File

@ -0,0 +1,102 @@
<?php
namespace addons\alioss\library;
use app\common\library\Upload;
class Auth
{
public function __construct()
{
}
public function params($name, $md5, $callback = true)
{
$config = get_addon_config('alioss');
$callback_param = array(
'callbackUrl' => isset($config['notifyurl']) ? $config['notifyurl'] : '',
'callbackBody' => 'filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}',
'callbackBodyType' => "application/x-www-form-urlencoded"
);
$base64_callback_body = base64_encode(json_encode($callback_param));
$now = time();
$end = $now + $config['expire']; //设置该policy超时时间是10s. 即这个policy过了这个有效时间将不能访问
$expiration = $this->gmt_iso8601($end);
preg_match('/(\d+)(\w+)/', $config['maxsize'], $matches);
$type = strtolower($matches[2]);
$typeDict = ['b' => 0, 'k' => 1, 'kb' => 1, 'm' => 2, 'mb' => 2, 'gb' => 3, 'g' => 3];
$size = (int)$config['maxsize'] * pow(1024, $typeDict[$type] ?? 0);
//最大文件大小.用户可以自己设置
$condition = array(0 => 'content-length-range', 1 => 0, 2 => $size);
$conditions[] = $condition;
//表示用户上传的数据,必须是以$dir开始, 不然上传会失败,这一步不是必须项,只是为了安全起见,防止用户通过policy上传到别人的目录
//$start = array(0 => 'starts-with', 1 => '$key', 2 => $dir);
//$conditions[] = $start;
$arr = array('expiration' => $expiration, 'conditions' => $conditions);
$policy = base64_encode(json_encode($arr));
$signature = base64_encode(hash_hmac('sha1', $policy, $config['accessKeySecret'], true));
$key = (new Upload())->getSavekey($config['savekey'], $name, $md5);
$key = ltrim($key, "/");
$response = array();
$response['id'] = $config['accessKeyId'];
$response['key'] = $key;
$response['policy'] = $policy;
$response['signature'] = $signature;
$response['expire'] = $end;
$response['callback'] = '';
return $response;
}
public function check($signature, $policy)
{
$config = get_addon_config('alioss');
$sign = base64_encode(hash_hmac('sha1', $policy, $config['accessKeySecret'], true));
return $signature == $sign;
}
private function gmt_iso8601($time)
{
$dtStr = date("c", $time);
$mydatetime = new \DateTime($dtStr);
$expiration = $mydatetime->format(\DateTime::ISO8601);
$pos = strpos($expiration, '+');
$expiration = substr($expiration, 0, $pos);
return $expiration . "Z";
}
public static function isModuleAllow()
{
$config = get_addon_config('alioss');
$module = request()->module();
$module = $module ? strtolower($module) : 'index';
$noNeedLogin = array_filter(explode(',', $config['noneedlogin'] ?? ''));
$isModuleLogin = false;
$tagName = 'upload_config_checklogin';
foreach (\think\Hook::get($tagName) as $index => $name) {
if (\think\Hook::exec($name, $tagName)) {
$isModuleLogin = true;
break;
}
}
if (in_array($module, $noNeedLogin)
|| ($module == 'admin' && \app\admin\library\Auth::instance()->id)
|| ($module != 'admin' && \app\common\library\Auth::instance()->id)
|| $isModuleLogin) {
return true;
} else {
return false;
}
}
}

View File

@ -0,0 +1,262 @@
<?php
namespace OSS\Core;
/**
* Class MimeTypes
*
* 在上传文件的时候根据文件的缺省名得到其对应的Content-type
*
* @package OSS\Core
*/
class MimeTypes
{
/**
* 根据文件名获取http协议header中的content-type应该填写的数据
*
* @param string $name 缺省名
* @return string content-type
*/
public static function getMimetype($name)
{
$parts = explode('.', $name);
if (count($parts) > 1) {
$ext = strtolower(end($parts));
if (isset(self::$mime_types[$ext])) {
return self::$mime_types[$ext];
}
}
return null;
}
private static $mime_types = array(
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
'apk' => 'application/vnd.android.package-archive',
'hqx' => 'application/mac-binhex40',
'cpt' => 'application/mac-compactpro',
'doc' => 'application/msword',
'ogg' => 'audio/ogg',
'pdf' => 'application/pdf',
'rtf' => 'text/rtf',
'mif' => 'application/vnd.mif',
'xls' => 'application/vnd.ms-excel',
'ppt' => 'application/vnd.ms-powerpoint',
'odc' => 'application/vnd.oasis.opendocument.chart',
'odb' => 'application/vnd.oasis.opendocument.database',
'odf' => 'application/vnd.oasis.opendocument.formula',
'odg' => 'application/vnd.oasis.opendocument.graphics',
'otg' => 'application/vnd.oasis.opendocument.graphics-template',
'odi' => 'application/vnd.oasis.opendocument.image',
'odp' => 'application/vnd.oasis.opendocument.presentation',
'otp' => 'application/vnd.oasis.opendocument.presentation-template',
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template',
'odt' => 'application/vnd.oasis.opendocument.text',
'odm' => 'application/vnd.oasis.opendocument.text-master',
'ott' => 'application/vnd.oasis.opendocument.text-template',
'oth' => 'application/vnd.oasis.opendocument.text-web',
'sxw' => 'application/vnd.sun.xml.writer',
'stw' => 'application/vnd.sun.xml.writer.template',
'sxc' => 'application/vnd.sun.xml.calc',
'stc' => 'application/vnd.sun.xml.calc.template',
'sxd' => 'application/vnd.sun.xml.draw',
'std' => 'application/vnd.sun.xml.draw.template',
'sxi' => 'application/vnd.sun.xml.impress',
'sti' => 'application/vnd.sun.xml.impress.template',
'sxg' => 'application/vnd.sun.xml.writer.global',
'sxm' => 'application/vnd.sun.xml.math',
'sis' => 'application/vnd.symbian.install',
'wbxml' => 'application/vnd.wap.wbxml',
'wmlc' => 'application/vnd.wap.wmlc',
'wmlsc' => 'application/vnd.wap.wmlscriptc',
'bcpio' => 'application/x-bcpio',
'torrent' => 'application/x-bittorrent',
'bz2' => 'application/x-bzip2',
'vcd' => 'application/x-cdlink',
'pgn' => 'application/x-chess-pgn',
'cpio' => 'application/x-cpio',
'csh' => 'application/x-csh',
'dvi' => 'application/x-dvi',
'spl' => 'application/x-futuresplash',
'gtar' => 'application/x-gtar',
'hdf' => 'application/x-hdf',
'jar' => 'application/java-archive',
'jnlp' => 'application/x-java-jnlp-file',
'js' => 'application/javascript',
'json' => 'application/json',
'ksp' => 'application/x-kspread',
'chrt' => 'application/x-kchart',
'kil' => 'application/x-killustrator',
'latex' => 'application/x-latex',
'rpm' => 'application/x-rpm',
'sh' => 'application/x-sh',
'shar' => 'application/x-shar',
'swf' => 'application/x-shockwave-flash',
'sit' => 'application/x-stuffit',
'sv4cpio' => 'application/x-sv4cpio',
'sv4crc' => 'application/x-sv4crc',
'tar' => 'application/x-tar',
'tcl' => 'application/x-tcl',
'tex' => 'application/x-tex',
'man' => 'application/x-troff-man',
'me' => 'application/x-troff-me',
'ms' => 'application/x-troff-ms',
'ustar' => 'application/x-ustar',
'src' => 'application/x-wais-source',
'zip' => 'application/zip',
'm3u' => 'audio/x-mpegurl',
'ra' => 'audio/x-pn-realaudio',
'wav' => 'audio/x-wav',
'wma' => 'audio/x-ms-wma',
'wax' => 'audio/x-ms-wax',
'pdb' => 'chemical/x-pdb',
'xyz' => 'chemical/x-xyz',
'bmp' => 'image/bmp',
'gif' => 'image/gif',
'ief' => 'image/ief',
'png' => 'image/png',
'wbmp' => 'image/vnd.wap.wbmp',
'ras' => 'image/x-cmu-raster',
'pnm' => 'image/x-portable-anymap',
'pbm' => 'image/x-portable-bitmap',
'pgm' => 'image/x-portable-graymap',
'ppm' => 'image/x-portable-pixmap',
'rgb' => 'image/x-rgb',
'xbm' => 'image/x-xbitmap',
'xpm' => 'image/x-xpixmap',
'xwd' => 'image/x-xwindowdump',
'css' => 'text/css',
'rtx' => 'text/richtext',
'tsv' => 'text/tab-separated-values',
'jad' => 'text/vnd.sun.j2me.app-descriptor',
'wml' => 'text/vnd.wap.wml',
'wmls' => 'text/vnd.wap.wmlscript',
'etx' => 'text/x-setext',
'mxu' => 'video/vnd.mpegurl',
'flv' => 'video/x-flv',
'wm' => 'video/x-ms-wm',
'wmv' => 'video/x-ms-wmv',
'wmx' => 'video/x-ms-wmx',
'wvx' => 'video/x-ms-wvx',
'avi' => 'video/x-msvideo',
'movie' => 'video/x-sgi-movie',
'ice' => 'x-conference/x-cooltalk',
'3gp' => 'video/3gpp',
'ai' => 'application/postscript',
'aif' => 'audio/x-aiff',
'aifc' => 'audio/x-aiff',
'aiff' => 'audio/x-aiff',
'asc' => 'text/plain',
'atom' => 'application/atom+xml',
'au' => 'audio/basic',
'bin' => 'application/octet-stream',
'cdf' => 'application/x-netcdf',
'cgm' => 'image/cgm',
'class' => 'application/octet-stream',
'dcr' => 'application/x-director',
'dif' => 'video/x-dv',
'dir' => 'application/x-director',
'djv' => 'image/vnd.djvu',
'djvu' => 'image/vnd.djvu',
'dll' => 'application/octet-stream',
'dmg' => 'application/octet-stream',
'dms' => 'application/octet-stream',
'dtd' => 'application/xml-dtd',
'dv' => 'video/x-dv',
'dxr' => 'application/x-director',
'eps' => 'application/postscript',
'exe' => 'application/octet-stream',
'ez' => 'application/andrew-inset',
'gram' => 'application/srgs',
'grxml' => 'application/srgs+xml',
'gz' => 'application/x-gzip',
'htm' => 'text/html',
'html' => 'text/html',
'ico' => 'image/x-icon',
'ics' => 'text/calendar',
'ifb' => 'text/calendar',
'iges' => 'model/iges',
'igs' => 'model/iges',
'jp2' => 'image/jp2',
'jpe' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'jpg' => 'image/jpeg',
'kar' => 'audio/midi',
'lha' => 'application/octet-stream',
'lzh' => 'application/octet-stream',
'm4a' => 'audio/mp4a-latm',
'm4p' => 'audio/mp4a-latm',
'm4u' => 'video/vnd.mpegurl',
'm4v' => 'video/x-m4v',
'mac' => 'image/x-macpaint',
'mathml' => 'application/mathml+xml',
'mesh' => 'model/mesh',
'mid' => 'audio/midi',
'midi' => 'audio/midi',
'mov' => 'video/quicktime',
'mp2' => 'audio/mpeg',
'mp3' => 'audio/mpeg',
'mp4' => 'video/mp4',
'mpe' => 'video/mpeg',
'mpeg' => 'video/mpeg',
'mpg' => 'video/mpeg',
'mpga' => 'audio/mpeg',
'msh' => 'model/mesh',
'nc' => 'application/x-netcdf',
'oda' => 'application/oda',
'ogv' => 'video/ogv',
'pct' => 'image/pict',
'pic' => 'image/pict',
'pict' => 'image/pict',
'pnt' => 'image/x-macpaint',
'pntg' => 'image/x-macpaint',
'ps' => 'application/postscript',
'qt' => 'video/quicktime',
'qti' => 'image/x-quicktime',
'qtif' => 'image/x-quicktime',
'ram' => 'audio/x-pn-realaudio',
'rdf' => 'application/rdf+xml',
'rm' => 'application/vnd.rn-realmedia',
'roff' => 'application/x-troff',
'sgm' => 'text/sgml',
'sgml' => 'text/sgml',
'silo' => 'model/mesh',
'skd' => 'application/x-koan',
'skm' => 'application/x-koan',
'skp' => 'application/x-koan',
'skt' => 'application/x-koan',
'smi' => 'application/smil',
'smil' => 'application/smil',
'snd' => 'audio/basic',
'so' => 'application/octet-stream',
'svg' => 'image/svg+xml',
't' => 'application/x-troff',
'texi' => 'application/x-texinfo',
'texinfo' => 'application/x-texinfo',
'tif' => 'image/tiff',
'tiff' => 'image/tiff',
'tr' => 'application/x-troff',
'txt' => 'text/plain',
'vrml' => 'model/vrml',
'vxml' => 'application/voicexml+xml',
'webm' => 'video/webm',
'webp' => 'image/webp',
'wrl' => 'model/vrml',
'xht' => 'application/xhtml+xml',
'xhtml' => 'application/xhtml+xml',
'xml' => 'application/xml',
'xsl' => 'application/xml',
'xslt' => 'application/xslt+xml',
'xul' => 'application/vnd.mozilla.xul+xml',
);
}

View File

@ -0,0 +1,54 @@
<?php
namespace OSS\Core;
/**
* Class OssException
*
* OssClient在使用的时候所抛出的异常用户在使用OssClient的时候要Try住相关代码
* try的Exception应该是OssException其中会得到相关异常原因
*
* @package OSS\Core
*/
class OssException extends \Exception
{
private $details = array();
function __construct($details)
{
if (is_array($details)) {
$message = $details['code'] . ': ' . $details['message']
. ' RequestId: ' . $details['request-id'];
parent::__construct($message);
$this->details = $details;
} else {
$message = $details;
parent::__construct($message);
}
}
public function getHTTPStatus()
{
return isset($this->details['status']) ? $this->details['status'] : '';
}
public function getRequestId()
{
return isset($this->details['request-id']) ? $this->details['request-id'] : '';
}
public function getErrorCode()
{
return isset($this->details['code']) ? $this->details['code'] : '';
}
public function getErrorMessage()
{
return isset($this->details['message']) ? $this->details['message'] : '';
}
public function getDetails()
{
return isset($this->details['body']) ? $this->details['body'] : '';
}
}

View File

@ -0,0 +1,461 @@
<?php
namespace OSS\Core;
/**
* Class OssUtil
*
* Oss工具类主要供OssClient使用用户也可以使用本类进行返回结果的格式化
*
* @package OSS
*/
class OssUtil
{
const OSS_CONTENT = 'content';
const OSS_LENGTH = 'length';
const OSS_HEADERS = 'headers';
const OSS_MAX_OBJECT_GROUP_VALUE = 1000;
const OSS_MAX_PART_SIZE = 5368709120; // 5GB
const OSS_MID_PART_SIZE = 10485760; // 10MB
const OSS_MIN_PART_SIZE = 102400; // 100KB
/**
* 生成query params
*
* @param array $options 关联数组
* @return string 返回诸如 key1=value1&key2=value2
*/
public static function toQueryString($options = array())
{
$temp = array();
uksort($options, 'strnatcasecmp');
foreach ($options as $key => $value) {
if (is_string($key) && !is_array($value)) {
$temp[] = rawurlencode($key) . '=' . rawurlencode($value);
}
}
return implode('&', $temp);
}
/**
* 转义字符替换
*
* @param string $subject
* @return string
*/
public static function sReplace($subject)
{
$search = array('<', '>', '&', '\'', '"');
$replace = array('&lt;', '&gt;', '&amp;', '&apos;', '&quot;');
return str_replace($search, $replace, $subject);
}
/**
* 检查是否是中文编码
*
* @param $str
* @return int
*/
public static function chkChinese($str)
{
return preg_match('/[\x80-\xff]./', $str);
}
/**
* 检测是否GB2312编码
*
* @param string $str
* @return boolean false UTF-8编码 TRUE GB2312编码
*/
public static function isGb2312($str)
{
for ($i = 0; $i < strlen($str); $i++) {
$v = ord($str[$i]);
if ($v > 127) {
if (($v >= 228) && ($v <= 233)) {
if (($i + 2) >= (strlen($str) - 1)) return true; // not enough characters
$v1 = ord($str[$i + 1]);
$v2 = ord($str[$i + 2]);
if (($v1 >= 128) && ($v1 <= 191) && ($v2 >= 128) && ($v2 <= 191))
return false;
else
return true;
}
}
}
return false;
}
/**
* 检测是否GBK编码
*
* @param string $str
* @param boolean $gbk
* @return boolean
*/
public static function checkChar($str, $gbk = true)
{
for ($i = 0; $i < strlen($str); $i++) {
$v = ord($str[$i]);
if ($v > 127) {
if (($v >= 228) && ($v <= 233)) {
if (($i + 2) >= (strlen($str) - 1)) return $gbk ? true : FALSE; // not enough characters
$v1 = ord($str[$i + 1]);
$v2 = ord($str[$i + 2]);
if ($gbk) {
return (($v1 >= 128) && ($v1 <= 191) && ($v2 >= 128) && ($v2 <= 191)) ? FALSE : TRUE;//GBK
} else {
return (($v1 >= 128) && ($v1 <= 191) && ($v2 >= 128) && ($v2 <= 191)) ? TRUE : FALSE;
}
}
}
}
return $gbk ? TRUE : FALSE;
}
/**
* 检验bucket名称是否合法
* bucket的命名规范
* 1. 只能包括小写字母,数字
* 2. 必须以小写字母或者数字开头
* 3. 长度必须在3-63字节之间
*
* @param string $bucket Bucket名称
* @return boolean
*/
public static function validateBucket($bucket)
{
$pattern = '/^[a-z0-9][a-z0-9-]{2,62}$/';
if (!preg_match($pattern, $bucket)) {
return false;
}
return true;
}
/**
* 检验object名称是否合法
* object命名规范:
* 1. 规则长度必须在1-1023字节之间
* 2. 使用UTF-8编码
* 3. 不能以 "/" "\\"开头
*
* @param string $object Object名称
* @return boolean
*/
public static function validateObject($object)
{
$pattern = '/^.{1,1023}$/';
if (empty($object) || !preg_match($pattern, $object) ||
self::startsWith($object, '/') || self::startsWith($object, '\\')
) {
return false;
}
return true;
}
/**
* 判断字符串$str是不是以$findMe开始
*
* @param string $str
* @param string $findMe
* @return bool
*/
public static function startsWith($str, $findMe)
{
if (strpos($str, $findMe) === 0) {
return true;
} else {
return false;
}
}
/**
* 生成createBucketXmlBody接口的xml消息
*
* @param string $storageClass
* @return string
*/
public static function createBucketXmlBody($storageClass)
{
$xml = new \SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><CreateBucketConfiguration></CreateBucketConfiguration>');
$xml->addChild('StorageClass', $storageClass);
return $xml->asXML();
}
/**
* 检验$options
*
* @param array $options
* @throws OssException
* @return boolean
*/
public static function validateOptions($options)
{
//$options
if ($options != NULL && !is_array($options)) {
throw new OssException ($options . ':' . 'option must be array');
}
}
/**
* 检查上传文件的内容是否合法
*
* @param $content string
* @throws OssException
*/
public static function validateContent($content)
{
if (empty($content)) {
throw new OssException("http body content is invalid");
}
}
/**
* 校验BUCKET/OBJECT/OBJECT GROUP是否为空
*
* @param string $name
* @param string $errMsg
* @throws OssException
* @return void
*/
public static function throwOssExceptionWithMessageIfEmpty($name, $errMsg)
{
if (empty($name)) {
throw new OssException($errMsg);
}
}
/**
* 仅供测试使用的接口,请勿使用
*
* @param $filename
* @param $size
*/
public static function generateFile($filename, $size)
{
if (file_exists($filename) && $size == filesize($filename)) {
echo $filename . " already exists, no need to create again. ";
return;
}
$part_size = 1 * 1024 * 1024;
$fp = fopen($filename, "w");
$characters = <<<BBB
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
BBB;
$charactersLength = strlen($characters);
if ($fp) {
while ($size > 0) {
if ($size < $part_size) {
$write_size = $size;
} else {
$write_size = $part_size;
}
$size -= $write_size;
$a = $characters[rand(0, $charactersLength - 1)];
$content = str_repeat($a, $write_size);
$flag = fwrite($fp, $content);
if (!$flag) {
echo "write to " . $filename . " failed. <br>";
break;
}
}
} else {
echo "open " . $filename . " failed. <br>";
}
fclose($fp);
}
/**
* 得到文件的md5编码
*
* @param $filename
* @param $from_pos
* @param $to_pos
* @return string
*/
public static function getMd5SumForFile($filename, $from_pos, $to_pos)
{
$content_md5 = "";
if (($to_pos - $from_pos) > self::OSS_MAX_PART_SIZE) {
return $content_md5;
}
$filesize = filesize($filename);
if ($from_pos >= $filesize || $to_pos >= $filesize || $from_pos < 0 || $to_pos < 0) {
return $content_md5;
}
$total_length = $to_pos - $from_pos + 1;
$buffer = 8192;
$left_length = $total_length;
if (!file_exists($filename)) {
return $content_md5;
}
if (false === $fh = fopen($filename, 'rb')) {
return $content_md5;
}
fseek($fh, $from_pos);
$data = '';
while (!feof($fh)) {
if ($left_length >= $buffer) {
$read_length = $buffer;
} else {
$read_length = $left_length;
}
if ($read_length <= 0) {
break;
} else {
$data .= fread($fh, $read_length);
$left_length = $left_length - $read_length;
}
}
fclose($fh);
$content_md5 = base64_encode(md5($data, true));
return $content_md5;
}
/**
* 检测是否windows系统因为windows系统默认编码为GBK
*
* @return bool
*/
public static function isWin()
{
return strtoupper(substr(PHP_OS, 0, 3)) == "WIN";
}
/**
* 主要是由于windows系统编码是gbk遇到中文时候如果不进行转换处理会出现找不到文件的问题
*
* @param $file_path
* @return string
*/
public static function encodePath($file_path)
{
if (self::chkChinese($file_path) && self::isWin()) {
$file_path = iconv('utf-8', 'gbk', $file_path);
}
return $file_path;
}
/**
* 判断用户输入的endpoint是否是 xxx.xxx.xxx.xxx:port 或者 xxx.xxx.xxx.xxx的ip格式
*
* @param string $endpoint 需要做判断的endpoint
* @return boolean
*/
public static function isIPFormat($endpoint)
{
$ip_array = explode(":", $endpoint);
$hostname = $ip_array[0];
$ret = filter_var($hostname, FILTER_VALIDATE_IP);
if (!$ret) {
return false;
} else {
return true;
}
}
/**
* 生成DeleteMultiObjects接口的xml消息
*
* @param string[] $objects
* @param bool $quiet
* @return string
*/
public static function createDeleteObjectsXmlBody($objects, $quiet)
{
$xml = new \SimpleXMLElement('<?xml version="1.0" encoding="utf-8"?><Delete></Delete>');
$xml->addChild('Quiet', $quiet);
foreach ($objects as $object) {
$sub_object = $xml->addChild('Object');
$object = OssUtil::sReplace($object);
$sub_object->addChild('Key', $object);
}
return $xml->asXML();
}
/**
* 生成CompleteMultipartUpload接口的xml消息
*
* @param array[] $listParts
* @return string
*/
public static function createCompleteMultipartUploadXmlBody($listParts)
{
$xml = new \SimpleXMLElement('<?xml version="1.0" encoding="utf-8"?><CompleteMultipartUpload></CompleteMultipartUpload>');
foreach ($listParts as $node) {
$part = $xml->addChild('Part');
$part->addChild('PartNumber', $node['PartNumber']);
$part->addChild('ETag', $node['ETag']);
}
return $xml->asXML();
}
/**
* 读取目录
*
* @param string $dir
* @param string $exclude
* @param bool $recursive
* @return string[]
*/
public static function readDir($dir, $exclude = ".|..|.svn|.git", $recursive = false)
{
$file_list_array = array();
$base_path = $dir;
$exclude_array = explode("|", $exclude);
$exclude_array = array_unique(array_merge($exclude_array, array('.', '..')));
if ($recursive) {
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir)) as $new_file) {
if ($new_file->isDir()) continue;
$object = str_replace($base_path, '', $new_file);
if (!in_array(strtolower($object), $exclude_array)) {
$object = ltrim($object, '/');
if (is_file($new_file)) {
$key = md5($new_file . $object, false);
$file_list_array[$key] = array('path' => $new_file, 'file' => $object,);
}
}
}
} else if ($handle = opendir($dir)) {
while (false !== ($file = readdir($handle))) {
if (!in_array(strtolower($file), $exclude_array)) {
$new_file = $dir . '/' . $file;
$object = $file;
$object = ltrim($object, '/');
if (is_file($new_file)) {
$key = md5($new_file . $object, false);
$file_list_array[$key] = array('path' => $new_file, 'file' => $object,);
}
}
}
closedir($handle);
}
return $file_list_array;
}
/**
* Decode key based on the encoding type
*
* @param string $key
* @param string $encoding
* @return string
*/
public static function decodeKey($key, $encoding)
{
if ($encoding == "") {
return $key;
}
if ($encoding == "url") {
return rawurldecode($key);
} else {
throw new OssException("Unrecognized encoding type: " . $encoding);
}
}
}

View File

@ -0,0 +1,25 @@
Copyright (c) 2006-2010 Ryan Parman, Foleeo Inc., and contributors. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of
conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list
of conditions and the following disclaimer in the documentation and/or other materials
provided with the distribution.
* Neither the name of Ryan Parman, Foleeo Inc. nor the names of its contributors may be used to
endorse or promote products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,892 @@
<?php
namespace OSS\Http;
/**
* Handles all HTTP requests using cURL and manages the responses.
*
* @version 2011.06.07
* @copyright 2006-2011 Ryan Parman
* @copyright 2006-2010 Foleeo Inc.
* @copyright 2010-2011 Amazon.com, Inc. or its affiliates.
* @copyright 2008-2011 Contributors
* @license http://opensource.org/licenses/bsd-license.php Simplified BSD License
*/
class RequestCore
{
/**
* The URL being requested.
*/
public $request_url;
/**
* The headers being sent in the request.
*/
public $request_headers;
/**
* The raw response callback headers
*/
public $response_raw_headers;
/**
* Response body when error occurs
*/
public $response_error_body;
/**
*The hander of write file
*/
public $write_file_handle;
/**
* The body being sent in the request.
*/
public $request_body;
/**
* The response returned by the request.
*/
public $response;
/**
* The headers returned by the request.
*/
public $response_headers;
/**
* The body returned by the request.
*/
public $response_body;
/**
* The HTTP status code returned by the request.
*/
public $response_code;
/**
* Additional response data.
*/
public $response_info;
/**
* The method by which the request is being made.
*/
public $method;
/**
* Stores the proxy settings to use for the request.
*/
public $proxy = null;
/**
* The username to use for the request.
*/
public $username = null;
/**
* The password to use for the request.
*/
public $password = null;
/**
* Custom CURLOPT settings.
*/
public $curlopts = null;
/**
* The state of debug mode.
*/
public $debug_mode = false;
/**
* The default class to use for HTTP Requests (defaults to <RequestCore>).
*/
public $request_class = 'OSS\Http\RequestCore';
/**
* The default class to use for HTTP Responses (defaults to <ResponseCore>).
*/
public $response_class = 'OSS\Http\ResponseCore';
/**
* Default useragent string to use.
*/
public $useragent = 'RequestCore/1.4.3';
/**
* File to read from while streaming up.
*/
public $read_file = null;
/**
* The resource to read from while streaming up.
*/
public $read_stream = null;
/**
* The size of the stream to read from.
*/
public $read_stream_size = null;
/**
* The length already read from the stream.
*/
public $read_stream_read = 0;
/**
* File to write to while streaming down.
*/
public $write_file = null;
/**
* The resource to write to while streaming down.
*/
public $write_stream = null;
/**
* Stores the intended starting seek position.
*/
public $seek_position = null;
/**
* The location of the cacert.pem file to use.
*/
public $cacert_location = false;
/**
* The state of SSL certificate verification.
*/
public $ssl_verification = true;
/**
* The user-defined callback function to call when a stream is read from.
*/
public $registered_streaming_read_callback = null;
/**
* The user-defined callback function to call when a stream is written to.
*/
public $registered_streaming_write_callback = null;
/**
* 请求超时时间, 默认是5184000秒6天
*
* @var int
*/
public $timeout = 5184000;
/**
* 连接超时时间默认是10秒
*
* @var int
*/
public $connect_timeout = 10;
/*%******************************************************************************************%*/
// CONSTANTS
/**
* GET HTTP Method
*/
const HTTP_GET = 'GET';
/**
* POST HTTP Method
*/
const HTTP_POST = 'POST';
/**
* PUT HTTP Method
*/
const HTTP_PUT = 'PUT';
/**
* DELETE HTTP Method
*/
const HTTP_DELETE = 'DELETE';
/**
* HEAD HTTP Method
*/
const HTTP_HEAD = 'HEAD';
/*%******************************************************************************************%*/
// CONSTRUCTOR/DESTRUCTOR
/**
* Constructs a new instance of this class.
*
* @param string $url (Optional) The URL to request or service endpoint to query.
* @param string $proxy (Optional) The faux-url to use for proxy settings. Takes the following format: `proxy://user:pass@hostname:port`
* @param array $helpers (Optional) An associative array of classnames to use for request, and response functionality. Gets passed in automatically by the calling class.
* @return $this A reference to the current instance.
*/
public function __construct($url = null, $proxy = null, $helpers = null)
{
// Set some default values.
$this->request_url = $url;
$this->method = self::HTTP_GET;
$this->request_headers = array();
$this->request_body = '';
// Set a new Request class if one was set.
if (isset($helpers['request']) && !empty($helpers['request'])) {
$this->request_class = $helpers['request'];
}
// Set a new Request class if one was set.
if (isset($helpers['response']) && !empty($helpers['response'])) {
$this->response_class = $helpers['response'];
}
if ($proxy) {
$this->set_proxy($proxy);
}
return $this;
}
/**
* Destructs the instance. Closes opened file handles.
*
* @return $this A reference to the current instance.
*/
public function __destruct()
{
if (isset($this->read_file) && isset($this->read_stream)) {
fclose($this->read_stream);
}
if (isset($this->write_file) && isset($this->write_stream)) {
fclose($this->write_stream);
}
return $this;
}
/*%******************************************************************************************%*/
// REQUEST METHODS
/**
* Sets the credentials to use for authentication.
*
* @param string $user (Required) The username to authenticate with.
* @param string $pass (Required) The password to authenticate with.
* @return $this A reference to the current instance.
*/
public function set_credentials($user, $pass)
{
$this->username = $user;
$this->password = $pass;
return $this;
}
/**
* Adds a custom HTTP header to the cURL request.
*
* @param string $key (Required) The custom HTTP header to set.
* @param mixed $value (Required) The value to assign to the custom HTTP header.
* @return $this A reference to the current instance.
*/
public function add_header($key, $value)
{
$this->request_headers[$key] = $value;
return $this;
}
/**
* Removes an HTTP header from the cURL request.
*
* @param string $key (Required) The custom HTTP header to set.
* @return $this A reference to the current instance.
*/
public function remove_header($key)
{
if (isset($this->request_headers[$key])) {
unset($this->request_headers[$key]);
}
return $this;
}
/**
* Set the method type for the request.
*
* @param string $method (Required) One of the following constants: <HTTP_GET>, <HTTP_POST>, <HTTP_PUT>, <HTTP_HEAD>, <HTTP_DELETE>.
* @return $this A reference to the current instance.
*/
public function set_method($method)
{
$this->method = strtoupper($method);
return $this;
}
/**
* Sets a custom useragent string for the class.
*
* @param string $ua (Required) The useragent string to use.
* @return $this A reference to the current instance.
*/
public function set_useragent($ua)
{
$this->useragent = $ua;
return $this;
}
/**
* Set the body to send in the request.
*
* @param string $body (Required) The textual content to send along in the body of the request.
* @return $this A reference to the current instance.
*/
public function set_body($body)
{
$this->request_body = $body;
return $this;
}
/**
* Set the URL to make the request to.
*
* @param string $url (Required) The URL to make the request to.
* @return $this A reference to the current instance.
*/
public function set_request_url($url)
{
$this->request_url = $url;
return $this;
}
/**
* Set additional CURLOPT settings. These will merge with the default settings, and override if
* there is a duplicate.
*
* @param array $curlopts (Optional) A set of key-value pairs that set `CURLOPT` options. These will merge with the existing CURLOPTs, and ones passed here will override the defaults. Keys should be the `CURLOPT_*` constants, not strings.
* @return $this A reference to the current instance.
*/
public function set_curlopts($curlopts)
{
$this->curlopts = $curlopts;
return $this;
}
/**
* Sets the length in bytes to read from the stream while streaming up.
*
* @param integer $size (Required) The length in bytes to read from the stream.
* @return $this A reference to the current instance.
*/
public function set_read_stream_size($size)
{
$this->read_stream_size = $size;
return $this;
}
/**
* Sets the resource to read from while streaming up. Reads the stream from its current position until
* EOF or `$size` bytes have been read. If `$size` is not given it will be determined by <php:fstat()> and
* <php:ftell()>.
*
* @param resource $resource (Required) The readable resource to read from.
* @param integer $size (Optional) The size of the stream to read.
* @return $this A reference to the current instance.
*/
public function set_read_stream($resource, $size = null)
{
if (!isset($size) || $size < 0) {
$stats = fstat($resource);
if ($stats && $stats['size'] >= 0) {
$position = ftell($resource);
if ($position !== false && $position >= 0) {
$size = $stats['size'] - $position;
}
}
}
$this->read_stream = $resource;
return $this->set_read_stream_size($size);
}
/**
* Sets the file to read from while streaming up.
*
* @param string $location (Required) The readable location to read from.
* @return $this A reference to the current instance.
*/
public function set_read_file($location)
{
$this->read_file = $location;
$read_file_handle = fopen($location, 'r');
return $this->set_read_stream($read_file_handle);
}
/**
* Sets the resource to write to while streaming down.
*
* @param resource $resource (Required) The writeable resource to write to.
* @return $this A reference to the current instance.
*/
public function set_write_stream($resource)
{
$this->write_stream = $resource;
return $this;
}
/**
* Sets the file to write to while streaming down.
*
* @param string $location (Required) The writeable location to write to.
* @return $this A reference to the current instance.
*/
public function set_write_file($location)
{
$this->write_file = $location;
}
/**
* Set the proxy to use for making requests.
*
* @param string $proxy (Required) The faux-url to use for proxy settings. Takes the following format: `proxy://user:pass@hostname:port`
* @return $this A reference to the current instance.
*/
public function set_proxy($proxy)
{
$proxy = parse_url($proxy);
$proxy['user'] = isset($proxy['user']) ? $proxy['user'] : null;
$proxy['pass'] = isset($proxy['pass']) ? $proxy['pass'] : null;
$proxy['port'] = isset($proxy['port']) ? $proxy['port'] : null;
$this->proxy = $proxy;
return $this;
}
/**
* Set the intended starting seek position.
*
* @param integer $position (Required) The byte-position of the stream to begin reading from.
* @return $this A reference to the current instance.
*/
public function set_seek_position($position)
{
$this->seek_position = isset($position) ? (integer)$position : null;
return $this;
}
/**
* A callback function that is invoked by cURL for streaming up.
*
* @param resource $curl_handle (Required) The cURL handle for the request.
* @param resource $header_content (Required) The header callback result.
* @return headers from a stream.
*/
public function streaming_header_callback($curl_handle, $header_content)
{
$code = curl_getinfo($curl_handle, CURLINFO_HTTP_CODE);
if (isset($this->write_file) && intval($code) / 100 == 2 && !isset($this->write_file_handle)) {
$this->write_file_handle = fopen($this->write_file, 'w');
$this->set_write_stream($this->write_file_handle);
}
$this->response_raw_headers .= $header_content;
return strlen($header_content);
}
/**
* Register a callback function to execute whenever a data stream is read from using
* <CFRequest::streaming_read_callback()>.
*
* The user-defined callback function should accept three arguments:
*
* <ul>
* <li><code>$curl_handle</code> - <code>resource</code> - Required - The cURL handle resource that represents the in-progress transfer.</li>
* <li><code>$file_handle</code> - <code>resource</code> - Required - The file handle resource that represents the file on the local file system.</li>
* <li><code>$length</code> - <code>integer</code> - Required - The length in kilobytes of the data chunk that was transferred.</li>
* </ul>
*
* @param string|array|function $callback (Required) The callback function is called by <php:call_user_func()>, so you can pass the following values: <ul>
* <li>The name of a global function to execute, passed as a string.</li>
* <li>A method to execute, passed as <code>array('ClassName', 'MethodName')</code>.</li>
* <li>An anonymous function (PHP 5.3+).</li></ul>
* @return $this A reference to the current instance.
*/
public function register_streaming_read_callback($callback)
{
$this->registered_streaming_read_callback = $callback;
return $this;
}
/**
* Register a callback function to execute whenever a data stream is written to using
* <CFRequest::streaming_write_callback()>.
*
* The user-defined callback function should accept two arguments:
*
* <ul>
* <li><code>$curl_handle</code> - <code>resource</code> - Required - The cURL handle resource that represents the in-progress transfer.</li>
* <li><code>$length</code> - <code>integer</code> - Required - The length in kilobytes of the data chunk that was transferred.</li>
* </ul>
*
* @param string|array|function $callback (Required) The callback function is called by <php:call_user_func()>, so you can pass the following values: <ul>
* <li>The name of a global function to execute, passed as a string.</li>
* <li>A method to execute, passed as <code>array('ClassName', 'MethodName')</code>.</li>
* <li>An anonymous function (PHP 5.3+).</li></ul>
* @return $this A reference to the current instance.
*/
public function register_streaming_write_callback($callback)
{
$this->registered_streaming_write_callback = $callback;
return $this;
}
/*%******************************************************************************************%*/
// PREPARE, SEND, AND PROCESS REQUEST
/**
* A callback function that is invoked by cURL for streaming up.
*
* @param resource $curl_handle (Required) The cURL handle for the request.
* @param resource $file_handle (Required) The open file handle resource.
* @param integer $length (Required) The maximum number of bytes to read.
* @return binary Binary data from a stream.
*/
public function streaming_read_callback($curl_handle, $file_handle, $length)
{
// Once we've sent as much as we're supposed to send...
if ($this->read_stream_read >= $this->read_stream_size) {
// Send EOF
return '';
}
// If we're at the beginning of an upload and need to seek...
if ($this->read_stream_read == 0 && isset($this->seek_position) && $this->seek_position !== ftell($this->read_stream)) {
if (fseek($this->read_stream, $this->seek_position) !== 0) {
throw new RequestCore_Exception('The stream does not support seeking and is either not at the requested position or the position is unknown.');
}
}
$read = fread($this->read_stream, min($this->read_stream_size - $this->read_stream_read, $length)); // Remaining upload data or cURL's requested chunk size
$this->read_stream_read += strlen($read);
$out = $read === false ? '' : $read;
// Execute callback function
if ($this->registered_streaming_read_callback) {
call_user_func($this->registered_streaming_read_callback, $curl_handle, $file_handle, $out);
}
return $out;
}
/**
* A callback function that is invoked by cURL for streaming down.
*
* @param resource $curl_handle (Required) The cURL handle for the request.
* @param binary $data (Required) The data to write.
* @return integer The number of bytes written.
*/
public function streaming_write_callback($curl_handle, $data)
{
$code = curl_getinfo($curl_handle, CURLINFO_HTTP_CODE);
if (intval($code) / 100 != 2) {
$this->response_error_body .= $data;
return strlen($data);
}
$length = strlen($data);
$written_total = 0;
$written_last = 0;
while ($written_total < $length) {
$written_last = fwrite($this->write_stream, substr($data, $written_total));
if ($written_last === false) {
return $written_total;
}
$written_total += $written_last;
}
// Execute callback function
if ($this->registered_streaming_write_callback) {
call_user_func($this->registered_streaming_write_callback, $curl_handle, $written_total);
}
return $written_total;
}
/**
* Prepares and adds the details of the cURL request. This can be passed along to a <php:curl_multi_exec()>
* function.
*
* @return resource The handle for the cURL object.
*
*/
public function prep_request()
{
$curl_handle = curl_init();
// Set default options.
curl_setopt($curl_handle, CURLOPT_URL, $this->request_url);
curl_setopt($curl_handle, CURLOPT_FILETIME, true);
curl_setopt($curl_handle, CURLOPT_FRESH_CONNECT, false);
// curl_setopt($curl_handle, CURLOPT_CLOSEPOLICY, CURLCLOSEPOLICY_LEAST_RECENTLY_USED);
curl_setopt($curl_handle, CURLOPT_MAXREDIRS, 5);
curl_setopt($curl_handle, CURLOPT_HEADER, true);
curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl_handle, CURLOPT_TIMEOUT, $this->timeout);
curl_setopt($curl_handle, CURLOPT_CONNECTTIMEOUT, $this->connect_timeout);
curl_setopt($curl_handle, CURLOPT_NOSIGNAL, true);
curl_setopt($curl_handle, CURLOPT_REFERER, $this->request_url);
curl_setopt($curl_handle, CURLOPT_USERAGENT, $this->useragent);
curl_setopt($curl_handle, CURLOPT_HEADERFUNCTION, array($this, 'streaming_header_callback'));
curl_setopt($curl_handle, CURLOPT_READFUNCTION, array($this, 'streaming_read_callback'));
// Verification of the SSL cert
if ($this->ssl_verification) {
curl_setopt($curl_handle, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curl_handle, CURLOPT_SSL_VERIFYHOST, 2);
} else {
curl_setopt($curl_handle, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl_handle, CURLOPT_SSL_VERIFYHOST, false);
}
// chmod the file as 0755
if ($this->cacert_location === true) {
curl_setopt($curl_handle, CURLOPT_CAINFO, dirname(__FILE__) . '/cacert.pem');
} elseif (is_string($this->cacert_location)) {
curl_setopt($curl_handle, CURLOPT_CAINFO, $this->cacert_location);
}
// Debug mode
if ($this->debug_mode) {
curl_setopt($curl_handle, CURLOPT_VERBOSE, true);
}
// Handle open_basedir & safe mode
if (!ini_get('safe_mode') && !ini_get('open_basedir')) {
curl_setopt($curl_handle, CURLOPT_FOLLOWLOCATION, true);
}
// Enable a proxy connection if requested.
if ($this->proxy) {
$host = $this->proxy['host'];
$host .= ($this->proxy['port']) ? ':' . $this->proxy['port'] : '';
curl_setopt($curl_handle, CURLOPT_PROXY, $host);
if (isset($this->proxy['user']) && isset($this->proxy['pass'])) {
curl_setopt($curl_handle, CURLOPT_PROXYUSERPWD, $this->proxy['user'] . ':' . $this->proxy['pass']);
}
}
// Set credentials for HTTP Basic/Digest Authentication.
if ($this->username && $this->password) {
curl_setopt($curl_handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
curl_setopt($curl_handle, CURLOPT_USERPWD, $this->username . ':' . $this->password);
}
// Handle the encoding if we can.
if (extension_loaded('zlib')) {
curl_setopt($curl_handle, CURLOPT_ENCODING, '');
}
// Process custom headers
if (isset($this->request_headers) && count($this->request_headers)) {
$temp_headers = array();
foreach ($this->request_headers as $k => $v) {
$temp_headers[] = $k . ': ' . $v;
}
curl_setopt($curl_handle, CURLOPT_HTTPHEADER, $temp_headers);
}
switch ($this->method) {
case self::HTTP_PUT:
//unset($this->read_stream);
curl_setopt($curl_handle, CURLOPT_CUSTOMREQUEST, 'PUT');
if (isset($this->read_stream)) {
if (!isset($this->read_stream_size) || $this->read_stream_size < 0) {
throw new RequestCore_Exception('The stream size for the streaming upload cannot be determined.');
}
curl_setopt($curl_handle, CURLOPT_INFILESIZE, $this->read_stream_size);
curl_setopt($curl_handle, CURLOPT_UPLOAD, true);
} else {
curl_setopt($curl_handle, CURLOPT_POSTFIELDS, $this->request_body);
}
break;
case self::HTTP_POST:
curl_setopt($curl_handle, CURLOPT_CUSTOMREQUEST, 'POST');
if (isset($this->read_stream)) {
if (!isset($this->read_stream_size) || $this->read_stream_size < 0) {
throw new RequestCore_Exception('The stream size for the streaming upload cannot be determined.');
}
curl_setopt($curl_handle, CURLOPT_INFILESIZE, $this->read_stream_size);
curl_setopt($curl_handle, CURLOPT_UPLOAD, true);
} else {
curl_setopt($curl_handle, CURLOPT_POSTFIELDS, $this->request_body);
}
break;
case self::HTTP_HEAD:
curl_setopt($curl_handle, CURLOPT_CUSTOMREQUEST, self::HTTP_HEAD);
curl_setopt($curl_handle, CURLOPT_NOBODY, 1);
break;
default: // Assumed GET
curl_setopt($curl_handle, CURLOPT_CUSTOMREQUEST, $this->method);
if (isset($this->write_stream) || isset($this->write_file)) {
curl_setopt($curl_handle, CURLOPT_WRITEFUNCTION, array($this, 'streaming_write_callback'));
curl_setopt($curl_handle, CURLOPT_HEADER, false);
} else {
curl_setopt($curl_handle, CURLOPT_POSTFIELDS, $this->request_body);
}
break;
}
// Merge in the CURLOPTs
if (isset($this->curlopts) && sizeof($this->curlopts) > 0) {
foreach ($this->curlopts as $k => $v) {
curl_setopt($curl_handle, $k, $v);
}
}
return $curl_handle;
}
/**
* Take the post-processed cURL data and break it down into useful header/body/info chunks. Uses the
* data stored in the `curl_handle` and `response` properties unless replacement data is passed in via
* parameters.
*
* @param resource $curl_handle (Optional) The reference to the already executed cURL request.
* @param string $response (Optional) The actual response content itself that needs to be parsed.
* @return ResponseCore A <ResponseCore> object containing a parsed HTTP response.
*/
public function process_response($curl_handle = null, $response = null)
{
// Accept a custom one if it's passed.
if ($curl_handle && $response) {
$this->response = $response;
}
// As long as this came back as a valid resource...
if (is_resource($curl_handle) || (is_object($curl_handle) && in_array(get_class($curl_handle), array('CurlHandle', 'Swoole\Curl\Handler', 'Swoole\Coroutine\Curl\Handle'), true))) {
// Determine what's what.
$header_size = curl_getinfo($curl_handle, CURLINFO_HEADER_SIZE);
$this->response_headers = substr($this->response, 0, $header_size);
$this->response_body = substr($this->response, $header_size);
$this->response_code = curl_getinfo($curl_handle, CURLINFO_HTTP_CODE);
$this->response_info = curl_getinfo($curl_handle);
if (intval($this->response_code) / 100 != 2 && isset($this->write_file)) {
$this->response_headers = $this->response_raw_headers;
$this->response_body = $this->response_error_body;
}
// Parse out the headers
$this->response_headers = explode("\r\n\r\n", trim($this->response_headers));
$this->response_headers = array_pop($this->response_headers);
$this->response_headers = explode("\r\n", $this->response_headers);
array_shift($this->response_headers);
// Loop through and split up the headers.
$header_assoc = array();
foreach ($this->response_headers as $header) {
$kv = explode(': ', $header);
$header_assoc[strtolower($kv[0])] = isset($kv[1]) ? $kv[1] : '';
}
// Reset the headers to the appropriate property.
$this->response_headers = $header_assoc;
$this->response_headers['info'] = $this->response_info;
$this->response_headers['info']['method'] = $this->method;
if ($curl_handle && $response) {
return new ResponseCore($this->response_headers, $this->response_body, $this->response_code);
}
}
// Return false
return false;
}
/**
* Sends the request, calling necessary utility functions to update built-in properties.
*
* @param boolean $parse (Optional) Whether to parse the response with ResponseCore or not.
* @return string The resulting unparsed data from the request.
*/
public function send_request($parse = false)
{
set_time_limit(0);
$curl_handle = $this->prep_request();
$this->response = curl_exec($curl_handle);
if ($this->response === false) {
throw new RequestCore_Exception('cURL resource: ' . (string)$curl_handle . '; cURL error: ' . curl_error($curl_handle) . ' (' . curl_errno($curl_handle) . ')');
}
$parsed_response = $this->process_response($curl_handle, $this->response);
curl_close($curl_handle);
if ($parse) {
return $parsed_response;
}
return $this->response;
}
/*%******************************************************************************************%*/
// RESPONSE METHODS
/**
* Get the HTTP response headers from the request.
*
* @param string $header (Optional) A specific header value to return. Defaults to all headers.
* @return string|array All or selected header values.
*/
public function get_response_header($header = null)
{
if ($header) {
return $this->response_headers[strtolower($header)];
}
return $this->response_headers;
}
/**
* Get the HTTP response body from the request.
*
* @return string The response body.
*/
public function get_response_body()
{
return $this->response_body;
}
/**
* Get the HTTP response code from the request.
*
* @return string The HTTP response code.
*/
public function get_response_code()
{
return $this->response_code;
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace OSS\Http;
class RequestCore_Exception extends \Exception
{
}

View File

@ -0,0 +1,56 @@
<?php
namespace OSS\Http;
/**
* Container for all response-related methods.
*/
class ResponseCore
{
/**
* Stores the HTTP header information.
*/
public $header;
/**
* Stores the SimpleXML response.
*/
public $body;
/**
* Stores the HTTP response code.
*/
public $status;
/**
* Constructs a new instance of this class.
*
* @param array $header (Required) Associative array of HTTP headers (typically returned by <RequestCore::get_response_header()>).
* @param string $body (Required) XML-formatted response from AWS.
* @param integer $status (Optional) HTTP response status code from the request.
* @return Mixed Contains an <php:array> `header` property (HTTP headers as an associative array), a <php:SimpleXMLElement> or <php:string> `body` property, and an <php:integer> `status` code.
*/
public function __construct($header, $body, $status = null)
{
$this->header = $header;
$this->body = $body;
$this->status = $status;
return $this;
}
/**
* Did we receive the status code we expected?
*
* @param integer|array $codes (Optional) The status code(s) to expect. Pass an <php:integer> for a single acceptable value, or an <php:array> of integers for multiple acceptable values.
* @return boolean Whether we received the expected status code or not.
*/
public function isOK($codes = array(200, 201, 204, 206))
{
if (is_array($codes)) {
return in_array($this->status, $codes);
}
return $this->status === $codes;
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace OSS\Model;
/**
* Bucket信息ListBuckets接口返回数据
*
* Class BucketInfo
* @package OSS\Model
*/
class BucketInfo
{
/**
* BucketInfo constructor.
*
* @param string $location
* @param string $name
* @param string $createDate
*/
public function __construct($location, $name, $createDate)
{
$this->location = $location;
$this->name = $name;
$this->createDate = $createDate;
}
/**
* 得到bucket所在的region
*
* @return string
*/
public function getLocation()
{
return $this->location;
}
/**
* 得到bucket的名称
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* 得到bucket的创建时间
*
* @return string
*/
public function getCreateDate()
{
return $this->createDate;
}
/**
* bucket所在的region
*
* @var string
*/
private $location;
/**
* bucket的名称
*
* @var string
*/
private $name;
/**
* bucket的创建事件
*
* @var string
*/
private $createDate;
}

View File

@ -0,0 +1,39 @@
<?php
namespace OSS\Model;
/**
* Class BucketListInfo
*
* ListBuckets接口返回的数据类型
*
* @package OSS\Model
*/
class BucketListInfo
{
/**
* BucketListInfo constructor.
* @param array $bucketList
*/
public function __construct(array $bucketList)
{
$this->bucketList = $bucketList;
}
/**
* 得到BucketInfo列表
*
* @return BucketInfo[]
*/
public function getBucketList()
{
return $this->bucketList;
}
/**
* BucketInfo信息列表
*
* @var array
*/
private $bucketList = array();
}

View File

@ -0,0 +1,99 @@
<?php
namespace OSS\Model;
use OSS\Core\OssException;
/**
* Class CnameConfig
* @package OSS\Model
*
* TODO: fix link
* @link http://help.aliyun.com/document_detail/oss/api-reference/cors/PutBucketcors.html
*/
class CnameConfig implements XmlConfig
{
public function __construct()
{
$this->cnameList = array();
}
/**
* @return array
* @example
* array(2) {
* [0]=>
* array(3) {
* ["Domain"]=>
* string(11) "www.foo.com"
* ["Status"]=>
* string(7) "enabled"
* ["LastModified"]=>
* string(8) "20150101"
* }
* [1]=>
* array(3) {
* ["Domain"]=>
* string(7) "bar.com"
* ["Status"]=>
* string(8) "disabled"
* ["LastModified"]=>
* string(8) "20160101"
* }
* }
*/
public function getCnames()
{
return $this->cnameList;
}
public function addCname($cname)
{
if (count($this->cnameList) >= self::OSS_MAX_RULES) {
throw new OssException(
"num of cname in the config exceeds self::OSS_MAX_RULES: " . strval(self::OSS_MAX_RULES));
}
$this->cnameList[] = array('Domain' => $cname);
}
public function parseFromXml($strXml)
{
$xml = simplexml_load_string($strXml);
if (!isset($xml->Cname)) return;
foreach ($xml->Cname as $entry) {
$cname = array();
foreach ($entry as $key => $value) {
$cname[strval($key)] = strval($value);
}
$this->cnameList[] = $cname;
}
}
public function serializeToXml()
{
$strXml = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<BucketCnameConfiguration>
</BucketCnameConfiguration>
EOF;
$xml = new \SimpleXMLElement($strXml);
foreach ($this->cnameList as $cname) {
$node = $xml->addChild('Cname');
foreach ($cname as $key => $value) {
$node->addChild($key, $value);
}
}
return $xml->asXML();
}
public function __toString()
{
return $this->serializeToXml();
}
const OSS_MAX_RULES = 10;
private $cnameList = array();
}

View File

@ -0,0 +1,113 @@
<?php
namespace OSS\Model;
use OSS\Core\OssException;
/**
* Class CorsConfig
* @package OSS\Model
*
* @link http://help.aliyun.com/document_detail/oss/api-reference/cors/PutBucketcors.html
*/
class CorsConfig implements XmlConfig
{
/**
* CorsConfig constructor.
*/
public function __construct()
{
$this->rules = array();
}
/**
* 得到CorsRule列表
*
* @return CorsRule[]
*/
public function getRules()
{
return $this->rules;
}
/**
* 添加一条CorsRule
*
* @param CorsRule $rule
* @throws OssException
*/
public function addRule($rule)
{
if (count($this->rules) >= self::OSS_MAX_RULES) {
throw new OssException("num of rules in the config exceeds self::OSS_MAX_RULES: " . strval(self::OSS_MAX_RULES));
}
$this->rules[] = $rule;
}
/**
* 从xml数据中解析出CorsConfig
*
* @param string $strXml
* @throws OssException
* @return null
*/
public function parseFromXml($strXml)
{
$xml = simplexml_load_string($strXml);
if (!isset($xml->CORSRule)) return;
foreach ($xml->CORSRule as $rule) {
$corsRule = new CorsRule();
foreach ($rule as $key => $value) {
if ($key === self::OSS_CORS_ALLOWED_HEADER) {
$corsRule->addAllowedHeader(strval($value));
} elseif ($key === self::OSS_CORS_ALLOWED_METHOD) {
$corsRule->addAllowedMethod(strval($value));
} elseif ($key === self::OSS_CORS_ALLOWED_ORIGIN) {
$corsRule->addAllowedOrigin(strval($value));
} elseif ($key === self::OSS_CORS_EXPOSE_HEADER) {
$corsRule->addExposeHeader(strval($value));
} elseif ($key === self::OSS_CORS_MAX_AGE_SECONDS) {
$corsRule->setMaxAgeSeconds(strval($value));
}
}
$this->addRule($corsRule);
}
return;
}
/**
* 生成xml字符串
*
* @return string
*/
public function serializeToXml()
{
$xml = new \SimpleXMLElement('<?xml version="1.0" encoding="utf-8"?><CORSConfiguration></CORSConfiguration>');
foreach ($this->rules as $rule) {
$xmlRule = $xml->addChild('CORSRule');
$rule->appendToXml($xmlRule);
}
return $xml->asXML();
}
public function __toString()
{
return $this->serializeToXml();
}
const OSS_CORS_ALLOWED_ORIGIN = 'AllowedOrigin';
const OSS_CORS_ALLOWED_METHOD = 'AllowedMethod';
const OSS_CORS_ALLOWED_HEADER = 'AllowedHeader';
const OSS_CORS_EXPOSE_HEADER = 'ExposeHeader';
const OSS_CORS_MAX_AGE_SECONDS = 'MaxAgeSeconds';
const OSS_MAX_RULES = 10;
/**
* orsRule列表
*
* @var CorsRule[]
*/
private $rules = array();
}

View File

@ -0,0 +1,150 @@
<?php
namespace OSS\Model;
use OSS\Core\OssException;
/**
* Class CorsRule
* @package OSS\Model
* @link http://help.aliyun.com/document_detail/oss/api-reference/cors/PutBucketcors.html
*/
class CorsRule
{
/**
* Rule中增加一条allowedOrigin
*
* @param string $allowedOrigin
*/
public function addAllowedOrigin($allowedOrigin)
{
if (!empty($allowedOrigin)) {
$this->allowedOrigins[] = $allowedOrigin;
}
}
/**
* Rule中增加一条allowedMethod
*
* @param string $allowedMethod
*/
public function addAllowedMethod($allowedMethod)
{
if (!empty($allowedMethod)) {
$this->allowedMethods[] = $allowedMethod;
}
}
/**
* Rule中增加一条allowedHeader
*
* @param string $allowedHeader
*/
public function addAllowedHeader($allowedHeader)
{
if (!empty($allowedHeader)) {
$this->allowedHeaders[] = $allowedHeader;
}
}
/**
* Rule中增加一条exposeHeader
*
* @param string $exposeHeader
*/
public function addExposeHeader($exposeHeader)
{
if (!empty($exposeHeader)) {
$this->exposeHeaders[] = $exposeHeader;
}
}
/**
* @return int
*/
public function getMaxAgeSeconds()
{
return $this->maxAgeSeconds;
}
/**
* @param int $maxAgeSeconds
*/
public function setMaxAgeSeconds($maxAgeSeconds)
{
$this->maxAgeSeconds = $maxAgeSeconds;
}
/**
* 得到AllowedHeaders列表
*
* @return string[]
*/
public function getAllowedHeaders()
{
return $this->allowedHeaders;
}
/**
* 得到AllowedOrigins列表
*
* @return string[]
*/
public function getAllowedOrigins()
{
return $this->allowedOrigins;
}
/**
* 得到AllowedMethods列表
*
* @return string[]
*/
public function getAllowedMethods()
{
return $this->allowedMethods;
}
/**
* 得到ExposeHeaders列表
*
* @return string[]
*/
public function getExposeHeaders()
{
return $this->exposeHeaders;
}
/**
* 根据提供的xmlRule 把this按照一定的规则插入到$xmlRule中
*
* @param \SimpleXMLElement $xmlRule
* @throws OssException
*/
public function appendToXml(&$xmlRule)
{
if (!isset($this->maxAgeSeconds)) {
throw new OssException("maxAgeSeconds is not set in the Rule");
}
foreach ($this->allowedOrigins as $allowedOrigin) {
$xmlRule->addChild(CorsConfig::OSS_CORS_ALLOWED_ORIGIN, $allowedOrigin);
}
foreach ($this->allowedMethods as $allowedMethod) {
$xmlRule->addChild(CorsConfig::OSS_CORS_ALLOWED_METHOD, $allowedMethod);
}
foreach ($this->allowedHeaders as $allowedHeader) {
$xmlRule->addChild(CorsConfig::OSS_CORS_ALLOWED_HEADER, $allowedHeader);
}
foreach ($this->exposeHeaders as $exposeHeader) {
$xmlRule->addChild(CorsConfig::OSS_CORS_EXPOSE_HEADER, $exposeHeader);
}
$xmlRule->addChild(CorsConfig::OSS_CORS_MAX_AGE_SECONDS, strval($this->maxAgeSeconds));
}
private $allowedHeaders = array();
private $allowedOrigins = array();
private $allowedMethods = array();
private $exposeHeaders = array();
private $maxAgeSeconds = null;
}

View File

@ -0,0 +1,34 @@
<?php
namespace OSS\Model;
/**
* Class GetLiveChannelHistory
* @package OSS\Model
*/
class GetLiveChannelHistory implements XmlConfig
{
public function getLiveRecordList()
{
return $this->liveRecordList;
}
public function parseFromXml($strXml)
{
$xml = simplexml_load_string($strXml);
if (isset($xml->LiveRecord)) {
foreach ($xml->LiveRecord as $record) {
$liveRecord = new LiveChannelHistory();
$liveRecord->parseFromXmlNode($record);
$this->liveRecordList[] = $liveRecord;
}
}
}
public function serializeToXml()
{
throw new OssException("Not implemented.");
}
private $liveRecordList = array();
}

View File

@ -0,0 +1,68 @@
<?php
namespace OSS\Model;
/**
* Class GetLiveChannelInfo
* @package OSS\Model
*/
class GetLiveChannelInfo implements XmlConfig
{
public function getDescription()
{
return $this->description;
}
public function getStatus()
{
return $this->status;
}
public function getType()
{
return $this->type;
}
public function getFragDuration()
{
return $this->fragDuration;
}
public function getFragCount()
{
return $this->fragCount;
}
public function getPlayListName()
{
return $this->playlistName;
}
public function parseFromXml($strXml)
{
$xml = simplexml_load_string($strXml);
$this->description = strval($xml->Description);
$this->status = strval($xml->Status);
if (isset($xml->Target)) {
foreach ($xml->Target as $target) {
$this->type = strval($target->Type);
$this->fragDuration = strval($target->FragDuration);
$this->fragCount = strval($target->FragCount);
$this->playlistName = strval($target->PlaylistName);
}
}
}
public function serializeToXml()
{
throw new OssException("Not implemented.");
}
private $description;
private $status;
private $type;
private $fragDuration;
private $fragCount;
private $playlistName;
}

View File

@ -0,0 +1,107 @@
<?php
namespace OSS\Model;
/**
* Class GetLiveChannelStatus
* @package OSS\Model
*/
class GetLiveChannelStatus implements XmlConfig
{
public function getStatus()
{
return $this->status;
}
public function getConnectedTime()
{
return $this->connectedTime;
}
public function getRemoteAddr()
{
return $this->remoteAddr;
}
public function getVideoWidth()
{
return $this->videoWidth;
}
public function getVideoHeight()
{
return $this->videoHeight;
}
public function getVideoFrameRate()
{
return $this->videoFrameRate;
}
public function getVideoBandwidth()
{
return $this->videoBandwidth;
}
public function getVideoCodec()
{
return $this->videoCodec;
}
public function getAudioBandwidth()
{
return $this->audioBandwidth;
}
public function getAudioSampleRate()
{
return $this->audioSampleRate;
}
public function getAudioCodec()
{
return $this->audioCodec;
}
public function parseFromXml($strXml)
{
$xml = simplexml_load_string($strXml);
$this->status = strval($xml->Status);
$this->connectedTime = strval($xml->ConnectedTime);
$this->remoteAddr = strval($xml->RemoteAddr);
if (isset($xml->Video)) {
foreach ($xml->Video as $video) {
$this->videoWidth = intval($video->Width);
$this->videoHeight = intval($video->Height);
$this->videoFrameRate = intval($video->FrameRate);
$this->videoBandwidth = intval($video->Bandwidth);
$this->videoCodec = strval($video->Codec);
}
}
if (isset($xml->Video)) {
foreach ($xml->Audio as $audio) {
$this->audioBandwidth = intval($audio->Bandwidth);
$this->audioSampleRate = intval($audio->SampleRate);
$this->audioCodec = strval($audio->Codec);
}
}
}
public function serializeToXml()
{
throw new OssException("Not implemented.");
}
private $status;
private $connectedTime;
private $remoteAddr;
private $videoWidth;
private $videoHeight;
private $videoFrameRate;
private $videoBandwidth;
private $videoCodec;
private $audioBandwidth;
private $audioSampleRate;
private $audioCodec;
}

View File

@ -0,0 +1,88 @@
<?php
namespace OSS\Model;
/**
* Class LifecycleAction
* @package OSS\Model
* @link http://help.aliyun.com/document_detail/oss/api-reference/bucket/PutBucketLifecycle.html
*/
class LifecycleAction
{
/**
* LifecycleAction constructor.
* @param string $action
* @param string $timeSpec
* @param string $timeValue
*/
public function __construct($action, $timeSpec, $timeValue)
{
$this->action = $action;
$this->timeSpec = $timeSpec;
$this->timeValue = $timeValue;
}
/**
* @return LifecycleAction
*/
public function getAction()
{
return $this->action;
}
/**
* @param string $action
*/
public function setAction($action)
{
$this->action = $action;
}
/**
* @return string
*/
public function getTimeSpec()
{
return $this->timeSpec;
}
/**
* @param string $timeSpec
*/
public function setTimeSpec($timeSpec)
{
$this->timeSpec = $timeSpec;
}
/**
* @return string
*/
public function getTimeValue()
{
return $this->timeValue;
}
/**
* @param string $timeValue
*/
public function setTimeValue($timeValue)
{
$this->timeValue = $timeValue;
}
/**
* appendToXml 把actions插入到xml中
*
* @param \SimpleXMLElement $xmlRule
*/
public function appendToXml(&$xmlRule)
{
$xmlAction = $xmlRule->addChild($this->action);
$xmlAction->addChild($this->timeSpec, $this->timeValue);
}
private $action;
private $timeSpec;
private $timeValue;
}

View File

@ -0,0 +1,107 @@
<?php
namespace OSS\Model;
use OSS\Core\OssException;
/**
* Class BucketLifecycleConfig
* @package OSS\Model
* @link http://help.aliyun.com/document_detail/oss/api-reference/bucket/PutBucketLifecycle.html
*/
class LifecycleConfig implements XmlConfig
{
/**
* 从xml数据中解析出LifecycleConfig
*
* @param string $strXml
* @throws OssException
* @return null
*/
public function parseFromXml($strXml)
{
$this->rules = array();
$xml = simplexml_load_string($strXml);
if (!isset($xml->Rule)) return;
$this->rules = array();
foreach ($xml->Rule as $rule) {
$id = strval($rule->ID);
$prefix = strval($rule->Prefix);
$status = strval($rule->Status);
$actions = array();
foreach ($rule as $key => $value) {
if ($key === 'ID' || $key === 'Prefix' || $key === 'Status') continue;
$action = $key;
$timeSpec = null;
$timeValue = null;
foreach ($value as $timeSpecKey => $timeValueValue) {
$timeSpec = $timeSpecKey;
$timeValue = strval($timeValueValue);
}
$actions[] = new LifecycleAction($action, $timeSpec, $timeValue);
}
$this->rules[] = new LifecycleRule($id, $prefix, $status, $actions);
}
return;
}
/**
* 生成xml字符串
*
* @return string
*/
public function serializeToXml()
{
$xml = new \SimpleXMLElement('<?xml version="1.0" encoding="utf-8"?><LifecycleConfiguration></LifecycleConfiguration>');
foreach ($this->rules as $rule) {
$xmlRule = $xml->addChild('Rule');
$rule->appendToXml($xmlRule);
}
return $xml->asXML();
}
/**
*
* 添加LifecycleRule
*
* @param LifecycleRule $lifecycleRule
* @throws OssException
*/
public function addRule($lifecycleRule)
{
if (!isset($lifecycleRule)) {
throw new OssException("lifecycleRule is null");
}
$this->rules[] = $lifecycleRule;
}
/**
* 将配置转换成字符串,便于用户查看
*
* @return string
*/
public function __toString()
{
return $this->serializeToXml();
}
/**
* 得到所有的生命周期规则
*
* @return LifecycleRule[]
*/
public function getRules()
{
return $this->rules;
}
/**
* @var LifecycleRule[]
*/
private $rules;
}

View File

@ -0,0 +1,126 @@
<?php
namespace OSS\Model;
/**
* Class LifecycleRule
* @package OSS\Model
*
* @link http://help.aliyun.com/document_detail/oss/api-reference/bucket/PutBucketLifecycle.html
*/
class LifecycleRule
{
/**
* 得到规则ID
*
* @return string
*/
public function getId()
{
return $this->id;
}
/**
* @param string $id 规则ID
*/
public function setId($id)
{
$this->id = $id;
}
/**
* 得到文件前缀
*
* @return string
*/
public function getPrefix()
{
return $this->prefix;
}
/**
* 设置文件前缀
*
* @param string $prefix 文件前缀
*/
public function setPrefix($prefix)
{
$this->prefix = $prefix;
}
/**
* Lifecycle规则的状态
*
* @return string
*/
public function getStatus()
{
return $this->status;
}
/**
* 设置Lifecycle规则状态
*
* @param string $status
*/
public function setStatus($status)
{
$this->status = $status;
}
/**
*
* @return LifecycleAction[]
*/
public function getActions()
{
return $this->actions;
}
/**
* @param LifecycleAction[] $actions
*/
public function setActions($actions)
{
$this->actions = $actions;
}
/**
* LifecycleRule constructor.
*
* @param string $id 规则ID
* @param string $prefix 文件前缀
* @param string $status 规则状态,可选[self::LIFECYCLE_STATUS_ENABLED, self::LIFECYCLE_STATUS_DISABLED]
* @param LifecycleAction[] $actions
*/
public function __construct($id, $prefix, $status, $actions)
{
$this->id = $id;
$this->prefix = $prefix;
$this->status = $status;
$this->actions = $actions;
}
/**
* @param \SimpleXMLElement $xmlRule
*/
public function appendToXml(&$xmlRule)
{
$xmlRule->addChild('ID', $this->id);
$xmlRule->addChild('Prefix', $this->prefix);
$xmlRule->addChild('Status', $this->status);
foreach ($this->actions as $action) {
$action->appendToXml($xmlRule);
}
}
private $id;
private $prefix;
private $status;
private $actions = array();
const LIFECYCLE_STATUS_ENABLED = 'Enabled';
const LIFECYCLE_STATUS_DISABLED = 'Disabled';
}

View File

@ -0,0 +1,134 @@
<?php
namespace OSS\Model;
/**
* Class ListMultipartUploadInfo
* @package OSS\Model
*
* @link http://help.aliyun.com/document_detail/oss/api-reference/multipart-upload/ListMultipartUploads.html
*/
class ListMultipartUploadInfo
{
/**
* ListMultipartUploadInfo constructor.
*
* @param string $bucket
* @param string $keyMarker
* @param string $uploadIdMarker
* @param string $nextKeyMarker
* @param string $nextUploadIdMarker
* @param string $delimiter
* @param string $prefix
* @param int $maxUploads
* @param string $isTruncated
* @param array $uploads
*/
public function __construct($bucket, $keyMarker, $uploadIdMarker, $nextKeyMarker, $nextUploadIdMarker, $delimiter, $prefix, $maxUploads, $isTruncated, array $uploads)
{
$this->bucket = $bucket;
$this->keyMarker = $keyMarker;
$this->uploadIdMarker = $uploadIdMarker;
$this->nextKeyMarker = $nextKeyMarker;
$this->nextUploadIdMarker = $nextUploadIdMarker;
$this->delimiter = $delimiter;
$this->prefix = $prefix;
$this->maxUploads = $maxUploads;
$this->isTruncated = $isTruncated;
$this->uploads = $uploads;
}
/**
* 得到bucket名称
*
* @return string
*/
public function getBucket()
{
return $this->bucket;
}
/**
* @return string
*/
public function getKeyMarker()
{
return $this->keyMarker;
}
/**
*
* @return string
*/
public function getUploadIdMarker()
{
return $this->uploadIdMarker;
}
/**
* @return string
*/
public function getNextKeyMarker()
{
return $this->nextKeyMarker;
}
/**
* @return string
*/
public function getNextUploadIdMarker()
{
return $this->nextUploadIdMarker;
}
/**
* @return string
*/
public function getDelimiter()
{
return $this->delimiter;
}
/**
* @return string
*/
public function getPrefix()
{
return $this->prefix;
}
/**
* @return int
*/
public function getMaxUploads()
{
return $this->maxUploads;
}
/**
* @return string
*/
public function getIsTruncated()
{
return $this->isTruncated;
}
/**
* @return UploadInfo[]
*/
public function getUploads()
{
return $this->uploads;
}
private $bucket = "";
private $keyMarker = "";
private $uploadIdMarker = "";
private $nextKeyMarker = "";
private $nextUploadIdMarker = "";
private $delimiter = "";
private $prefix = "";
private $maxUploads = 0;
private $isTruncated = "false";
private $uploads = array();
}

View File

@ -0,0 +1,97 @@
<?php
namespace OSS\Model;
/**
* Class ListPartsInfo
* @package OSS\Model
* @link http://help.aliyun.com/document_detail/oss/api-reference/multipart-upload/ListParts.html
*/
class ListPartsInfo
{
/**
* ListPartsInfo constructor.
* @param string $bucket
* @param string $key
* @param string $uploadId
* @param int $nextPartNumberMarker
* @param int $maxParts
* @param string $isTruncated
* @param array $listPart
*/
public function __construct($bucket, $key, $uploadId, $nextPartNumberMarker, $maxParts, $isTruncated, array $listPart)
{
$this->bucket = $bucket;
$this->key = $key;
$this->uploadId = $uploadId;
$this->nextPartNumberMarker = $nextPartNumberMarker;
$this->maxParts = $maxParts;
$this->isTruncated = $isTruncated;
$this->listPart = $listPart;
}
/**
* @return string
*/
public function getBucket()
{
return $this->bucket;
}
/**
* @return string
*/
public function getKey()
{
return $this->key;
}
/**
* @return string
*/
public function getUploadId()
{
return $this->uploadId;
}
/**
* @return int
*/
public function getNextPartNumberMarker()
{
return $this->nextPartNumberMarker;
}
/**
* @return int
*/
public function getMaxParts()
{
return $this->maxParts;
}
/**
* @return string
*/
public function getIsTruncated()
{
return $this->isTruncated;
}
/**
* @return array
*/
public function getListPart()
{
return $this->listPart;
}
private $bucket = "";
private $key = "";
private $uploadId = "";
private $nextPartNumberMarker = 0;
private $maxParts = 0;
private $isTruncated = "";
private $listPart = array();
}

View File

@ -0,0 +1,121 @@
<?php
namespace OSS\Model;
/**
* Class LiveChannelConfig
* @package OSS\Model
*/
class LiveChannelConfig implements XmlConfig
{
public function __construct($option = array())
{
if (isset($option['description'])) {
$this->description = $option['description'];
}
if (isset($option['status'])) {
$this->status = $option['status'];
}
if (isset($option['type'])) {
$this->type = $option['type'];
}
if (isset($option['fragDuration'])) {
$this->fragDuration = $option['fragDuration'];
}
if (isset($option['fragCount'])) {
$this->fragCount = $option['fragCount'];
}
if (isset($option['playListName'])) {
$this->playListName = $option['playListName'];
}
}
public function getDescription()
{
return $this->description;
}
public function getStatus()
{
return $this->status;
}
public function getType()
{
return $this->type;
}
public function getFragDuration()
{
return $this->fragDuration;
}
public function getFragCount()
{
return $this->fragCount;
}
public function getPlayListName()
{
return $this->playListName;
}
public function parseFromXml($strXml)
{
$xml = simplexml_load_string($strXml);
$this->description = strval($xml->Description);
$this->status = strval($xml->Status);
$target = $xml->Target;
$this->type = strval($target->Type);
$this->fragDuration = intval($target->FragDuration);
$this->fragCount = intval($target->FragCount);
$this->playListName = strval($target->PlayListName);
}
public function serializeToXml()
{
$strXml = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<LiveChannelConfiguration>
</LiveChannelConfiguration>
EOF;
$xml = new \SimpleXMLElement($strXml);
if (isset($this->description)) {
$xml->addChild('Description', $this->description);
}
if (isset($this->status)) {
$xml->addChild('Status', $this->status);
}
$node = $xml->addChild('Target');
$node->addChild('Type', $this->type);
if (isset($this->fragDuration)) {
$node->addChild('FragDuration', $this->fragDuration);
}
if (isset($this->fragCount)) {
$node->addChild('FragCount', $this->fragCount);
}
if (isset($this->playListName)) {
$node->addChild('PlayListName', $this->playListName);
}
return $xml->asXML();
}
public function __toString()
{
return $this->serializeToXml();
}
private $description;
private $status = "enabled";
private $type;
private $fragDuration = 5;
private $fragCount = 3;
private $playListName = "playlist.m3u8";
}

View File

@ -0,0 +1,59 @@
<?php
namespace OSS\Model;
/**
* Class LiveChannelHistory
* @package OSS\Model
*
*/
class LiveChannelHistory implements XmlConfig
{
public function __construct()
{
}
public function getStartTime()
{
return $this->startTime;
}
public function getEndTime()
{
return $this->endTime;
}
public function getRemoteAddr()
{
return $this->remoteAddr;
}
public function parseFromXmlNode($xml)
{
if (isset($xml->StartTime)) {
$this->startTime = strval($xml->StartTime);
}
if (isset($xml->EndTime)) {
$this->endTime = strval($xml->EndTime);
}
if (isset($xml->RemoteAddr)) {
$this->remoteAddr = strval($xml->RemoteAddr);
}
}
public function parseFromXml($strXml)
{
$xml = simplexml_load_string($strXml);
$this->parseFromXmlNode($xml);
}
public function serializeToXml()
{
throw new OssException("Not implemented.");
}
private $startTime;
private $endTime;
private $remoteAddr;
}

View File

@ -0,0 +1,107 @@
<?php
namespace OSS\Model;
/**
* Class LiveChannelInfo
* @package OSS\Model
*
*/
class LiveChannelInfo implements XmlConfig
{
public function __construct($name = null, $description = null)
{
$this->name = $name;
$this->description = $description;
$this->publishUrls = array();
$this->playUrls = array();
}
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
public function getPublishUrls()
{
return $this->publishUrls;
}
public function getPlayUrls()
{
return $this->playUrls;
}
public function getStatus()
{
return $this->status;
}
public function getLastModified()
{
return $this->lastModified;
}
public function getDescription()
{
return $this->description;
}
public function setDescription($description)
{
$this->description = $description;
}
public function parseFromXmlNode($xml)
{
if (isset($xml->Name)) {
$this->name = strval($xml->Name);
}
if (isset($xml->Description)) {
$this->description = strval($xml->Description);
}
if (isset($xml->Status)) {
$this->status = strval($xml->Status);
}
if (isset($xml->LastModified)) {
$this->lastModified = strval($xml->LastModified);
}
if (isset($xml->PublishUrls)) {
foreach ($xml->PublishUrls as $url) {
$this->publishUrls[] = strval($url->Url);
}
}
if (isset($xml->PlayUrls)) {
foreach ($xml->PlayUrls as $url) {
$this->playUrls[] = strval($url->Url);
}
}
}
public function parseFromXml($strXml)
{
$xml = simplexml_load_string($strXml);
$this->parseFromXmlNode($xml);
}
public function serializeToXml()
{
throw new OssException("Not implemented.");
}
private $name;
private $description;
private $publishUrls;
private $playUrls;
private $status;
private $lastModified;
}

View File

@ -0,0 +1,107 @@
<?php
namespace OSS\Model;
/**
* Class LiveChannelListInfo
*
* ListBucketLiveChannels接口返回数据
*
* @package OSS\Model
* @link http://help.aliyun.com/document_detail/oss/api-reference/bucket/GetBucket.html
*/
class LiveChannelListInfo implements XmlConfig
{
/**
* @return string
*/
public function getBucketName()
{
return $this->bucket;
}
public function setBucketName($name)
{
$this->bucket = $name;
}
/**
* @return string
*/
public function getPrefix()
{
return $this->prefix;
}
/**
* @return string
*/
public function getMarker()
{
return $this->marker;
}
/**
* @return int
*/
public function getMaxKeys()
{
return $this->maxKeys;
}
/**
* @return mixed
*/
public function getIsTruncated()
{
return $this->isTruncated;
}
/**
* @return LiveChannelInfo[]
*/
public function getChannelList()
{
return $this->channelList;
}
/**
* @return string
*/
public function getNextMarker()
{
return $this->nextMarker;
}
public function parseFromXml($strXml)
{
$xml = simplexml_load_string($strXml);
$this->prefix = strval($xml->Prefix);
$this->marker = strval($xml->Marker);
$this->maxKeys = intval($xml->MaxKeys);
$this->isTruncated = (strval($xml->IsTruncated) == 'true');
$this->nextMarker = strval($xml->NextMarker);
if (isset($xml->LiveChannel)) {
foreach ($xml->LiveChannel as $chan) {
$channel = new LiveChannelInfo();
$channel->parseFromXmlNode($chan);
$this->channelList[] = $channel;
}
}
}
public function serializeToXml()
{
throw new OssException("Not implemented.");
}
private $bucket = '';
private $prefix = '';
private $marker = '';
private $nextMarker = '';
private $maxKeys = 100;
private $isTruncated = 'false';
private $channelList = array();
}

View File

@ -0,0 +1,86 @@
<?php
namespace OSS\Model;
/**
* Class LoggingConfig
* @package OSS\Model
* @link http://help.aliyun.com/document_detail/oss/api-reference/bucket/PutBucketLogging.html
*/
class LoggingConfig implements XmlConfig
{
/**
* LoggingConfig constructor.
* @param null $targetBucket
* @param null $targetPrefix
*/
public function __construct($targetBucket = null, $targetPrefix = null)
{
$this->targetBucket = $targetBucket;
$this->targetPrefix = $targetPrefix;
}
/**
* @param $strXml
* @return null
*/
public function parseFromXml($strXml)
{
$xml = simplexml_load_string($strXml);
if (!isset($xml->LoggingEnabled)) return;
foreach ($xml->LoggingEnabled as $status) {
foreach ($status as $key => $value) {
if ($key === 'TargetBucket') {
$this->targetBucket = strval($value);
} elseif ($key === 'TargetPrefix') {
$this->targetPrefix = strval($value);
}
}
break;
}
}
/**
* 序列化成xml字符串
*
*/
public function serializeToXml()
{
$xml = new \SimpleXMLElement('<?xml version="1.0" encoding="utf-8"?><BucketLoggingStatus></BucketLoggingStatus>');
if (isset($this->targetBucket) && isset($this->targetPrefix)) {
$loggingEnabled = $xml->addChild('LoggingEnabled');
$loggingEnabled->addChild('TargetBucket', $this->targetBucket);
$loggingEnabled->addChild('TargetPrefix', $this->targetPrefix);
}
return $xml->asXML();
}
/**
* @return string
*/
public function __toString()
{
return $this->serializeToXml();
}
/**
* @return string
*/
public function getTargetBucket()
{
return $this->targetBucket;
}
/**
* @return string
*/
public function getTargetPrefix()
{
return $this->targetPrefix;
}
private $targetBucket = "";
private $targetPrefix = "";
}

View File

@ -0,0 +1,93 @@
<?php
namespace OSS\Model;
/**
*
* Class ObjectInfo
*
* listObjects接口中返回的Object列表中的类
*
* listObjects接口返回数据中包含两个Array
* 一个是拿到的Object列表【可以理解成对应文件系统中的文件列表】
* 一个是拿到的Prefix列表【可以理解成对应文件系统中的目录列表】
*
* @package OSS\Model
*/
class ObjectInfo
{
/**
* ObjectInfo constructor.
*
* @param string $key
* @param string $lastModified
* @param string $eTag
* @param string $type
* @param int $size
* @param string $storageClass
*/
public function __construct($key, $lastModified, $eTag, $type, $size, $storageClass)
{
$this->key = $key;
$this->lastModified = $lastModified;
$this->eTag = $eTag;
$this->type = $type;
$this->size = $size;
$this->storageClass = $storageClass;
}
/**
* @return string
*/
public function getKey()
{
return $this->key;
}
/**
* @return string
*/
public function getLastModified()
{
return $this->lastModified;
}
/**
* @return string
*/
public function getETag()
{
return $this->eTag;
}
/**
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* @return int
*/
public function getSize()
{
return $this->size;
}
/**
* @return string
*/
public function getStorageClass()
{
return $this->storageClass;
}
private $key = "";
private $lastModified = "";
private $eTag = "";
private $type = "";
private $size = 0;
private $storageClass = "";
}

View File

@ -0,0 +1,126 @@
<?php
namespace OSS\Model;
/**
* Class ObjectListInfo
*
* ListObjects接口返回数据
*
* @package OSS\Model
* @link http://help.aliyun.com/document_detail/oss/api-reference/bucket/GetBucket.html
*/
class ObjectListInfo
{
/**
* ObjectListInfo constructor.
*
* @param string $bucketName
* @param string $prefix
* @param string $marker
* @param string $nextMarker
* @param string $maxKeys
* @param string $delimiter
* @param null $isTruncated
* @param array $objectList
* @param array $prefixList
*/
public function __construct($bucketName, $prefix, $marker, $nextMarker, $maxKeys, $delimiter, $isTruncated, array $objectList, array $prefixList)
{
$this->bucketName = $bucketName;
$this->prefix = $prefix;
$this->marker = $marker;
$this->nextMarker = $nextMarker;
$this->maxKeys = $maxKeys;
$this->delimiter = $delimiter;
$this->isTruncated = $isTruncated;
$this->objectList = $objectList;
$this->prefixList = $prefixList;
}
/**
* @return string
*/
public function getBucketName()
{
return $this->bucketName;
}
/**
* @return string
*/
public function getPrefix()
{
return $this->prefix;
}
/**
* @return string
*/
public function getMarker()
{
return $this->marker;
}
/**
* @return int
*/
public function getMaxKeys()
{
return $this->maxKeys;
}
/**
* @return string
*/
public function getDelimiter()
{
return $this->delimiter;
}
/**
* @return mixed
*/
public function getIsTruncated()
{
return $this->isTruncated;
}
/**
* 返回ListObjects接口返回数据中的ObjectInfo列表
*
* @return ObjectInfo[]
*/
public function getObjectList()
{
return $this->objectList;
}
/**
* 返回ListObjects接口返回数据中的PrefixInfo列表
*
* @return PrefixInfo[]
*/
public function getPrefixList()
{
return $this->prefixList;
}
/**
* @return string
*/
public function getNextMarker()
{
return $this->nextMarker;
}
private $bucketName = "";
private $prefix = "";
private $marker = "";
private $nextMarker = "";
private $maxKeys = 0;
private $delimiter = "";
private $isTruncated = null;
private $objectList = array();
private $prefixList = array();
}

View File

@ -0,0 +1,63 @@
<?php
namespace OSS\Model;
/**
* Class PartInfo
* @package OSS\Model
*/
class PartInfo
{
/**
* PartInfo constructor.
*
* @param int $partNumber
* @param string $lastModified
* @param string $eTag
* @param int $size
*/
public function __construct($partNumber, $lastModified, $eTag, $size)
{
$this->partNumber = $partNumber;
$this->lastModified = $lastModified;
$this->eTag = $eTag;
$this->size = $size;
}
/**
* @return int
*/
public function getPartNumber()
{
return $this->partNumber;
}
/**
* @return string
*/
public function getLastModified()
{
return $this->lastModified;
}
/**
* @return string
*/
public function getETag()
{
return $this->eTag;
}
/**
* @return int
*/
public function getSize()
{
return $this->size;
}
private $partNumber = 0;
private $lastModified = "";
private $eTag = "";
private $size = 0;
}

View File

@ -0,0 +1,36 @@
<?php
namespace OSS\Model;
/**
* Class PrefixInfo
*
* listObjects接口中返回的Prefix列表中的类
* listObjects接口返回数据中包含两个Array:
* 一个是拿到的Object列表【可以理解成对应文件系统中的文件列表】
* 一个是拿到的Prefix列表【可以理解成对应文件系统中的目录列表】
*
* @package OSS\Model
* @link http://help.aliyun.com/document_detail/oss/api-reference/bucket/GetBucket.html
*/
class PrefixInfo
{
/**
* PrefixInfo constructor.
* @param string $prefix
*/
public function __construct($prefix)
{
$this->prefix = $prefix;
}
/**
* @return string
*/
public function getPrefix()
{
return $this->prefix;
}
private $prefix;
}

View File

@ -0,0 +1,93 @@
<?php
namespace OSS\Model;
/**
* Class RefererConfig
*
* @package OSS\Model
* @link http://help.aliyun.com/document_detail/oss/api-reference/bucket/PutBucketReferer.html
*/
class RefererConfig implements XmlConfig
{
/**
* @param string $strXml
* @return null
*/
public function parseFromXml($strXml)
{
$xml = simplexml_load_string($strXml);
if (!isset($xml->AllowEmptyReferer)) return;
if (!isset($xml->RefererList)) return;
$this->allowEmptyReferer =
(strval($xml->AllowEmptyReferer) === 'TRUE' || strval($xml->AllowEmptyReferer) === 'true') ? true : false;
foreach ($xml->RefererList->Referer as $key => $refer) {
$this->refererList[] = strval($refer);
}
}
/**
* 把RefererConfig序列化成xml
*
* @return string
*/
public function serializeToXml()
{
$xml = new \SimpleXMLElement('<?xml version="1.0" encoding="utf-8"?><RefererConfiguration></RefererConfiguration>');
if ($this->allowEmptyReferer) {
$xml->addChild('AllowEmptyReferer', 'true');
} else {
$xml->addChild('AllowEmptyReferer', 'false');
}
$refererList = $xml->addChild('RefererList');
foreach ($this->refererList as $referer) {
$refererList->addChild('Referer', $referer);
}
return $xml->asXML();
}
/**
* @return string
*/
function __toString()
{
return $this->serializeToXml();
}
/**
* @param boolean $allowEmptyReferer
*/
public function setAllowEmptyReferer($allowEmptyReferer)
{
$this->allowEmptyReferer = $allowEmptyReferer;
}
/**
* @param string $referer
*/
public function addReferer($referer)
{
$this->refererList[] = $referer;
}
/**
* @return boolean
*/
public function isAllowEmptyReferer()
{
return $this->allowEmptyReferer;
}
/**
* @return array
*/
public function getRefererList()
{
return $this->refererList;
}
private $allowEmptyReferer = true;
private $refererList = array();
}

View File

@ -0,0 +1,74 @@
<?php
namespace OSS\Model;
/**
* Class StorageCapacityConfig
*
* @package OSS\Model
* @link http://docs.alibaba-inc.com/pages/viewpage.action?pageId=271614763
*/
class StorageCapacityConfig implements XmlConfig
{
/**
* StorageCapacityConfig constructor.
*
* @param int $storageCapacity
*/
public function __construct($storageCapacity)
{
$this->storageCapacity = $storageCapacity;
}
/**
* Not implemented
*/
public function parseFromXml($strXml)
{
throw new OssException("Not implemented.");
}
/**
* 把StorageCapacityConfig序列化成xml
*
* @return string
*/
public function serializeToXml()
{
$xml = new \SimpleXMLElement('<?xml version="1.0" encoding="utf-8"?><BucketUserQos></BucketUserQos>');
$xml->addChild('StorageCapacity', strval($this->storageCapacity));
return $xml->asXML();
}
/**
* To string
*
* @return string
*/
function __toString()
{
return $this->serializeToXml();
}
/**
* Set storage capacity
*
* @param int $storageCapacity
*/
public function setStorageCapacity($storageCapacity)
{
$this->storageCapacity = $storageCapacity;
}
/**
* Get storage capacity
*
* @return int
*/
public function getStorageCapacity()
{
return $this->storageCapacity;
}
private $storageCapacity = 0;
}

View File

@ -0,0 +1,55 @@
<?php
namespace OSS\Model;
/**
* Class UploadInfo
*
* ListMultipartUpload接口得到的UploadInfo
*
* @package OSS\Model
*/
class UploadInfo
{
/**
* UploadInfo constructor.
*
* @param string $key
* @param string $uploadId
* @param string $initiated
*/
public function __construct($key, $uploadId, $initiated)
{
$this->key = $key;
$this->uploadId = $uploadId;
$this->initiated = $initiated;
}
/**
* @return string
*/
public function getKey()
{
return $this->key;
}
/**
* @return string
*/
public function getUploadId()
{
return $this->uploadId;
}
/**
* @return string
*/
public function getInitiated()
{
return $this->initiated;
}
private $key = "";
private $uploadId = "";
private $initiated = "";
}

View File

@ -0,0 +1,76 @@
<?php
namespace OSS\Model;
use OSS\Core\OssException;
/**
* Class WebsiteConfig
* @package OSS\Model
* @link http://help.aliyun.com/document_detail/oss/api-reference/bucket/PutBucketWebsite.html
*/
class WebsiteConfig implements XmlConfig
{
/**
* WebsiteConfig constructor.
* @param string $indexDocument
* @param string $errorDocument
*/
public function __construct($indexDocument = "", $errorDocument = "")
{
$this->indexDocument = $indexDocument;
$this->errorDocument = $errorDocument;
}
/**
* @param string $strXml
* @return null
*/
public function parseFromXml($strXml)
{
$xml = simplexml_load_string($strXml);
if (isset($xml->IndexDocument) && isset($xml->IndexDocument->Suffix)) {
$this->indexDocument = strval($xml->IndexDocument->Suffix);
}
if (isset($xml->ErrorDocument) && isset($xml->ErrorDocument->Key)) {
$this->errorDocument = strval($xml->ErrorDocument->Key);
}
}
/**
* 把WebsiteConfig序列化成xml
*
* @return string
* @throws OssException
*/
public function serializeToXml()
{
$xml = new \SimpleXMLElement('<?xml version="1.0" encoding="utf-8"?><WebsiteConfiguration></WebsiteConfiguration>');
$index_document_part = $xml->addChild('IndexDocument');
$error_document_part = $xml->addChild('ErrorDocument');
$index_document_part->addChild('Suffix', $this->indexDocument);
$error_document_part->addChild('Key', $this->errorDocument);
return $xml->asXML();
}
/**
* @return string
*/
public function getIndexDocument()
{
return $this->indexDocument;
}
/**
* @return string
*/
public function getErrorDocument()
{
return $this->errorDocument;
}
private $indexDocument = "";
private $errorDocument = "";
}

View File

@ -0,0 +1,27 @@
<?php
namespace OSS\Model;
/**
* Interface XmlConfig
* @package OSS\Model
*/
interface XmlConfig
{
/**
* 接口定义实现此接口的类都需要实现从xml数据解析的函数
*
* @param string $strXml
* @return null
*/
public function parseFromXml($strXml);
/**
* 接口定义实现此接口的类都需要实现把子类序列化成xml字符串的接口
*
* @return string
*/
public function serializeToXml();
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,32 @@
<?php
namespace OSS\Result;
use OSS\Core\OssException;
/**
* Class AclResult getBucketAcl接口返回结果类封装了
* 返回的xml数据的解析
*
* @package OSS\Result
*/
class AclResult extends Result
{
/**
* @return string
* @throws OssException
*/
protected function parseDataFromResponse()
{
$content = $this->rawResponse->body;
if (empty($content)) {
throw new OssException("body is null");
}
$xml = simplexml_load_string($content);
if (isset($xml->AccessControlList->Grant)) {
return strval($xml->AccessControlList->Grant);
} else {
throw new OssException("xml format exception");
}
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace OSS\Result;
use OSS\Core\OssException;
/**
* Class AppendResult
* @package OSS\Result
*/
class AppendResult extends Result
{
/**
* 结果中part的next-append-position
*
* @return int
* @throws OssException
*/
protected function parseDataFromResponse()
{
$header = $this->rawResponse->header;
if (isset($header["x-oss-next-append-position"])) {
return intval($header["x-oss-next-append-position"]);
}
throw new OssException("cannot get next-append-position");
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace OSS\Result;
/**
* Class BodyResult
* @package OSS\Result
*/
class BodyResult extends Result
{
/**
* @return string
*/
protected function parseDataFromResponse()
{
return empty($this->rawResponse->body) ? "" : $this->rawResponse->body;
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace OSS\Result;
/**
* Class CallbackResult
* @package OSS\Result
*/
class CallbackResult extends PutSetDeleteResult
{
protected function isResponseOk()
{
$status = $this->rawResponse->status;
if ((int)(intval($status) / 100) == 2 && (int)(intval($status)) !== 203) {
return true;
}
return false;
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace OSS\Result;
/**
* Class CopyObjectResult
* @package OSS\Result
*/
class CopyObjectResult extends Result
{
/**
* @return array()
*/
protected function parseDataFromResponse()
{
$body = $this->rawResponse->body;
$xml = simplexml_load_string($body);
$result = array();
if (isset($xml->LastModified)) {
$result[] = $xml->LastModified;
}
if (isset($xml->ETag)) {
$result[] = $xml->ETag;
}
return $result;
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace OSS\Result;
/**
* Class DeleteObjectsResult
* @package OSS\Result
*/
class DeleteObjectsResult extends Result
{
/**
* @return array()
*/
protected function parseDataFromResponse()
{
$body = $this->rawResponse->body;
$xml = simplexml_load_string($body);
$objects = array();
if (isset($xml->Deleted)) {
foreach($xml->Deleted as $deleteKey)
$objects[] = $deleteKey->Key;
}
return $objects;
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace OSS\Result;
/**
* Class ExistResult 检查bucket和object是否存在的返回结果
* 根据返回response的http status判断
* @package OSS\Result
*/
class ExistResult extends Result
{
/**
* @return bool
*/
protected function parseDataFromResponse()
{
return intval($this->rawResponse->status) === 200 ? true : false;
}
/**
* 根据返回http状态码判断[200-299]即认为是OK, 判断是否存在的接口404也认为是一种
* 有效响应
*
* @return bool
*/
protected function isResponseOk()
{
$status = $this->rawResponse->status;
if ((int)(intval($status) / 100) == 2 || (int)(intval($status)) === 404) {
return true;
}
return false;
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace OSS\Result;
use OSS\Model\CnameConfig;
class GetCnameResult extends Result
{
/**
* @return CnameConfig
*/
protected function parseDataFromResponse()
{
$content = $this->rawResponse->body;
$config = new CnameConfig();
$config->parseFromXml($content);
return $config;
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace OSS\Result;
use OSS\Model\CorsConfig;
class GetCorsResult extends Result
{
/**
* @return CorsConfig
*/
protected function parseDataFromResponse()
{
$content = $this->rawResponse->body;
$config = new CorsConfig();
$config->parseFromXml($content);
return $config;
}
/**
* 根据返回http状态码判断[200-299]即认为是OK, 获取bucket相关配置的接口404也认为是一种
* 有效响应
*
* @return bool
*/
protected function isResponseOk()
{
$status = $this->rawResponse->status;
if ((int)(intval($status) / 100) == 2 || (int)(intval($status)) === 404) {
return true;
}
return false;
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace OSS\Result;
use OSS\Model\LifecycleConfig;
/**
* Class GetLifecycleResult
* @package OSS\Result
*/
class GetLifecycleResult extends Result
{
/**
* 解析Lifestyle数据
*
* @return LifecycleConfig
*/
protected function parseDataFromResponse()
{
$content = $this->rawResponse->body;
$config = new LifecycleConfig();
$config->parseFromXml($content);
return $config;
}
/**
* 根据返回http状态码判断[200-299]即认为是OK, 获取bucket相关配置的接口404也认为是一种
* 有效响应
*
* @return bool
*/
protected function isResponseOk()
{
$status = $this->rawResponse->status;
if ((int)(intval($status) / 100) == 2 || (int)(intval($status)) === 404) {
return true;
}
return false;
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace OSS\Result;
use OSS\Model\GetLiveChannelHistory;
class GetLiveChannelHistoryResult extends Result
{
/**
* @return
*/
protected function parseDataFromResponse()
{
$content = $this->rawResponse->body;
$channelList = new GetLiveChannelHistory();
$channelList->parseFromXml($content);
return $channelList;
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace OSS\Result;
use OSS\Model\GetLiveChannelInfo;
class GetLiveChannelInfoResult extends Result
{
/**
* @return
*/
protected function parseDataFromResponse()
{
$content = $this->rawResponse->body;
$channelList = new GetLiveChannelInfo();
$channelList->parseFromXml($content);
return $channelList;
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace OSS\Result;
use OSS\Model\GetLiveChannelStatus;
class GetLiveChannelStatusResult extends Result
{
/**
* @return
*/
protected function parseDataFromResponse()
{
$content = $this->rawResponse->body;
$channelList = new GetLiveChannelStatus();
$channelList->parseFromXml($content);
return $channelList;
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace OSS\Result;
use OSS\Core\OssException;
/**
* Class GetLocationResult getBucketLocation接口返回结果类封装了
* 返回的xml数据的解析
*
* @package OSS\Result
*/
class GetLocationResult extends Result
{
/**
* Parse data from response
*
* @return string
* @throws OssException
*/
protected function parseDataFromResponse()
{
$content = $this->rawResponse->body;
if (empty($content)) {
throw new OssException("body is null");
}
$xml = simplexml_load_string($content);
return $xml;
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace OSS\Result;
use OSS\Model\LoggingConfig;
/**
* Class GetLoggingResult
* @package OSS\Result
*/
class GetLoggingResult extends Result
{
/**
* 解析LoggingConfig数据
*
* @return LoggingConfig
*/
protected function parseDataFromResponse()
{
$content = $this->rawResponse->body;
$config = new LoggingConfig();
$config->parseFromXml($content);
return $config;
}
/**
* 根据返回http状态码判断[200-299]即认为是OK, 获取bucket相关配置的接口404也认为是一种
* 有效响应
*
* @return bool
*/
protected function isResponseOk()
{
$status = $this->rawResponse->status;
if ((int)(intval($status) / 100) == 2 || (int)(intval($status)) === 404) {
return true;
}
return false;
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace OSS\Result;
use OSS\Model\RefererConfig;
/**
* Class GetRefererResult
* @package OSS\Result
*/
class GetRefererResult extends Result
{
/**
* 解析RefererConfig数据
*
* @return RefererConfig
*/
protected function parseDataFromResponse()
{
$content = $this->rawResponse->body;
$config = new RefererConfig();
$config->parseFromXml($content);
return $config;
}
/**
* 根据返回http状态码判断[200-299]即认为是OK, 获取bucket相关配置的接口404也认为是一种
* 有效响应
*
* @return bool
*/
protected function isResponseOk()
{
$status = $this->rawResponse->status;
if ((int)(intval($status) / 100) == 2 || (int)(intval($status)) === 404) {
return true;
}
return false;
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace OSS\Result;
use OSS\Core\OssException;
/**
* Class AclResult getBucketAcl接口返回结果类封装了
* 返回的xml数据的解析
*
* @package OSS\Result
*/
class GetStorageCapacityResult extends Result
{
/**
* Parse data from response
*
* @return string
* @throws OssException
*/
protected function parseDataFromResponse()
{
$content = $this->rawResponse->body;
if (empty($content)) {
throw new OssException("body is null");
}
$xml = simplexml_load_string($content);
if (isset($xml->StorageCapacity)) {
return intval($xml->StorageCapacity);
} else {
throw new OssException("xml format exception");
}
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace OSS\Result;
use OSS\Model\WebsiteConfig;
/**
* Class GetWebsiteResult
* @package OSS\Result
*/
class GetWebsiteResult extends Result
{
/**
* 解析WebsiteConfig数据
*
* @return WebsiteConfig
*/
protected function parseDataFromResponse()
{
$content = $this->rawResponse->body;
$config = new WebsiteConfig();
$config->parseFromXml($content);
return $config;
}
/**
* 根据返回http状态码判断[200-299]即认为是OK, 获取bucket相关配置的接口404也认为是一种
* 有效响应
*
* @return bool
*/
protected function isResponseOk()
{
$status = $this->rawResponse->status;
if ((int)(intval($status) / 100) == 2 || (int)(intval($status)) === 404) {
return true;
}
return false;
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace OSS\Result;
/**
* Class HeaderResult
* @package OSS\Result
* @link https://docs.aliyun.com/?spm=5176.383663.13.7.HgUIqL#/pub/oss/api-reference/object&GetObjectMeta
*/
class HeaderResult extends Result
{
/**
* 把返回的ResponseCore中的header作为返回数据
*
* @return array
*/
protected function parseDataFromResponse()
{
return empty($this->rawResponse->header) ? array() : $this->rawResponse->header;
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace OSS\Result;
use OSS\Core\OssException;
/**
* Class initiateMultipartUploadResult
* @package OSS\Result
*/
class InitiateMultipartUploadResult extends Result
{
/**
* 结果中获取uploadId并返回
*
* @throws OssException
* @return string
*/
protected function parseDataFromResponse()
{
$content = $this->rawResponse->body;
$xml = simplexml_load_string($content);
if (isset($xml->UploadId)) {
return strval($xml->UploadId);
}
throw new OssException("cannot get UploadId");
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace OSS\Result;
use OSS\Model\BucketInfo;
use OSS\Model\BucketListInfo;
/**
* Class ListBucketsResult
*
* @package OSS\Result
*/
class ListBucketsResult extends Result
{
/**
* @return BucketListInfo
*/
protected function parseDataFromResponse()
{
$bucketList = array();
$content = $this->rawResponse->body;
$xml = new \SimpleXMLElement($content);
if (isset($xml->Buckets) && isset($xml->Buckets->Bucket)) {
foreach ($xml->Buckets->Bucket as $bucket) {
$bucketInfo = new BucketInfo(strval($bucket->Location),
strval($bucket->Name),
strval($bucket->CreationDate));
$bucketList[] = $bucketInfo;
}
}
return new BucketListInfo($bucketList);
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace OSS\Result;
use OSS\Model\LiveChannelListInfo;
class ListLiveChannelResult extends Result
{
protected function parseDataFromResponse()
{
$content = $this->rawResponse->body;
$channelList = new LiveChannelListInfo();
$channelList->parseFromXml($content);
return $channelList;
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace OSS\Result;
use OSS\Core\OssUtil;
use OSS\Model\ListMultipartUploadInfo;
use OSS\Model\UploadInfo;
/**
* Class ListMultipartUploadResult
* @package OSS\Result
*/
class ListMultipartUploadResult extends Result
{
/**
* 解析从ListMultipartUpload接口的返回数据
*
* @return ListMultipartUploadInfo
*/
protected function parseDataFromResponse()
{
$content = $this->rawResponse->body;
$xml = simplexml_load_string($content);
$encodingType = isset($xml->EncodingType) ? strval($xml->EncodingType) : "";
$bucket = isset($xml->Bucket) ? strval($xml->Bucket) : "";
$keyMarker = isset($xml->KeyMarker) ? strval($xml->KeyMarker) : "";
$keyMarker = OssUtil::decodeKey($keyMarker, $encodingType);
$uploadIdMarker = isset($xml->UploadIdMarker) ? strval($xml->UploadIdMarker) : "";
$nextKeyMarker = isset($xml->NextKeyMarker) ? strval($xml->NextKeyMarker) : "";
$nextKeyMarker = OssUtil::decodeKey($nextKeyMarker, $encodingType);
$nextUploadIdMarker = isset($xml->NextUploadIdMarker) ? strval($xml->NextUploadIdMarker) : "";
$delimiter = isset($xml->Delimiter) ? strval($xml->Delimiter) : "";
$delimiter = OssUtil::decodeKey($delimiter, $encodingType);
$prefix = isset($xml->Prefix) ? strval($xml->Prefix) : "";
$prefix = OssUtil::decodeKey($prefix, $encodingType);
$maxUploads = isset($xml->MaxUploads) ? intval($xml->MaxUploads) : 0;
$isTruncated = isset($xml->IsTruncated) ? strval($xml->IsTruncated) : "";
$listUpload = array();
if (isset($xml->Upload)) {
foreach ($xml->Upload as $upload) {
$key = isset($upload->Key) ? strval($upload->Key) : "";
$key = OssUtil::decodeKey($key, $encodingType);
$uploadId = isset($upload->UploadId) ? strval($upload->UploadId) : "";
$initiated = isset($upload->Initiated) ? strval($upload->Initiated) : "";
$listUpload[] = new UploadInfo($key, $uploadId, $initiated);
}
}
return new ListMultipartUploadInfo($bucket, $keyMarker, $uploadIdMarker,
$nextKeyMarker, $nextUploadIdMarker,
$delimiter, $prefix, $maxUploads, $isTruncated, $listUpload);
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace OSS\Result;
use OSS\Core\OssUtil;
use OSS\Model\ObjectInfo;
use OSS\Model\ObjectListInfo;
use OSS\Model\PrefixInfo;
/**
* Class ListObjectsResult
* @package OSS\Result
*/
class ListObjectsResult extends Result
{
/**
* 解析ListObjects接口返回的xml数据
*
* return ObjectListInfo
*/
protected function parseDataFromResponse()
{
$xml = new \SimpleXMLElement($this->rawResponse->body);
$encodingType = isset($xml->EncodingType) ? strval($xml->EncodingType) : "";
$objectList = $this->parseObjectList($xml, $encodingType);
$prefixList = $this->parsePrefixList($xml, $encodingType);
$bucketName = isset($xml->Name) ? strval($xml->Name) : "";
$prefix = isset($xml->Prefix) ? strval($xml->Prefix) : "";
$prefix = OssUtil::decodeKey($prefix, $encodingType);
$marker = isset($xml->Marker) ? strval($xml->Marker) : "";
$marker = OssUtil::decodeKey($marker, $encodingType);
$maxKeys = isset($xml->MaxKeys) ? intval($xml->MaxKeys) : 0;
$delimiter = isset($xml->Delimiter) ? strval($xml->Delimiter) : "";
$delimiter = OssUtil::decodeKey($delimiter, $encodingType);
$isTruncated = isset($xml->IsTruncated) ? strval($xml->IsTruncated) : "";
$nextMarker = isset($xml->NextMarker) ? strval($xml->NextMarker) : "";
$nextMarker = OssUtil::decodeKey($nextMarker, $encodingType);
return new ObjectListInfo($bucketName, $prefix, $marker, $nextMarker, $maxKeys, $delimiter, $isTruncated, $objectList, $prefixList);
}
private function parseObjectList($xml, $encodingType)
{
$retList = array();
if (isset($xml->Contents)) {
foreach ($xml->Contents as $content) {
$key = isset($content->Key) ? strval($content->Key) : "";
$key = OssUtil::decodeKey($key, $encodingType);
$lastModified = isset($content->LastModified) ? strval($content->LastModified) : "";
$eTag = isset($content->ETag) ? strval($content->ETag) : "";
$type = isset($content->Type) ? strval($content->Type) : "";
$size = isset($content->Size) ? intval($content->Size) : 0;
$storageClass = isset($content->StorageClass) ? strval($content->StorageClass) : "";
$retList[] = new ObjectInfo($key, $lastModified, $eTag, $type, $size, $storageClass);
}
}
return $retList;
}
private function parsePrefixList($xml, $encodingType)
{
$retList = array();
if (isset($xml->CommonPrefixes)) {
foreach ($xml->CommonPrefixes as $commonPrefix) {
$prefix = isset($commonPrefix->Prefix) ? strval($commonPrefix->Prefix) : "";
$prefix = OssUtil::decodeKey($prefix, $encodingType);
$retList[] = new PrefixInfo($prefix);
}
}
return $retList;
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace OSS\Result;
use OSS\Model\ListPartsInfo;
use OSS\Model\PartInfo;
/**
* Class ListPartsResult
* @package OSS\Result
*/
class ListPartsResult extends Result
{
/**
* 解析ListParts接口返回的xml数据
*
* @return ListPartsInfo
*/
protected function parseDataFromResponse()
{
$content = $this->rawResponse->body;
$xml = simplexml_load_string($content);
$bucket = isset($xml->Bucket) ? strval($xml->Bucket) : "";
$key = isset($xml->Key) ? strval($xml->Key) : "";
$uploadId = isset($xml->UploadId) ? strval($xml->UploadId) : "";
$nextPartNumberMarker = isset($xml->NextPartNumberMarker) ? intval($xml->NextPartNumberMarker) : "";
$maxParts = isset($xml->MaxParts) ? intval($xml->MaxParts) : "";
$isTruncated = isset($xml->IsTruncated) ? strval($xml->IsTruncated) : "";
$partList = array();
if (isset($xml->Part)) {
foreach ($xml->Part as $part) {
$partNumber = isset($part->PartNumber) ? intval($part->PartNumber) : "";
$lastModified = isset($part->LastModified) ? strval($part->LastModified) : "";
$eTag = isset($part->ETag) ? strval($part->ETag) : "";
$size = isset($part->Size) ? intval($part->Size) : "";
$partList[] = new PartInfo($partNumber, $lastModified, $eTag, $size);
}
}
return new ListPartsInfo($bucket, $key, $uploadId, $nextPartNumberMarker, $maxParts, $isTruncated, $partList);
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace OSS\Result;
use OSS\Model\LiveChannelInfo;
class PutLiveChannelResult extends Result
{
protected function parseDataFromResponse()
{
$content = $this->rawResponse->body;
$channel = new LiveChannelInfo();
$channel->parseFromXml($content);
return $channel;
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace OSS\Result;
/**
* Class PutSetDeleteResult
* @package OSS\Result
*/
class PutSetDeleteResult extends Result
{
/**
* @return array()
*/
protected function parseDataFromResponse()
{
$body = array('body' => $this->rawResponse->body);
return array_merge($this->rawResponse->header, $body);
}
}

View File

@ -0,0 +1,175 @@
<?php
namespace OSS\Result;
use OSS\Core\OssException;
use OSS\Http\ResponseCore;
/**
* Class Result, 操作结果类的基类,不同的请求在处理返回数据的时候有不同的逻辑,
* 具体的解析逻辑推迟到子类实现
*
* @package OSS\Model
*/
abstract class Result
{
/**
* Result constructor.
* @param $response ResponseCore
* @throws OssException
*/
public function __construct($response)
{
if ($response === null) {
throw new OssException("raw response is null");
}
$this->rawResponse = $response;
$this->parseResponse();
}
/**
* 获取requestId
*
* @return string
*/
public function getRequestId()
{
if (isset($this->rawResponse) &&
isset($this->rawResponse->header) &&
isset($this->rawResponse->header['x-oss-request-id'])
) {
return $this->rawResponse->header['x-oss-request-id'];
} else {
return '';
}
}
/**
* 得到返回数据,不同的请求返回数据格式不同
*
* $return mixed
*/
public function getData()
{
return $this->parsedData;
}
/**
* 由子类实现,不同的请求返回数据有不同的解析逻辑,由子类实现
*
* @return mixed
*/
abstract protected function parseDataFromResponse();
/**
* 操作是否成功
*
* @return mixed
*/
public function isOK()
{
return $this->isOk;
}
/**
* @throws OssException
*/
public function parseResponse()
{
$this->isOk = $this->isResponseOk();
if ($this->isOk) {
$this->parsedData = $this->parseDataFromResponse();
} else {
$httpStatus = strval($this->rawResponse->status);
$requestId = strval($this->getRequestId());
$code = $this->retrieveErrorCode($this->rawResponse->body);
$message = $this->retrieveErrorMessage($this->rawResponse->body);
$body = $this->rawResponse->body;
$details = array(
'status' => $httpStatus,
'request-id' => $requestId,
'code' => $code,
'message' => $message,
'body' => $body
);
throw new OssException($details);
}
}
/**
* 尝试从body中获取错误Message
*
* @param $body
* @return string
*/
private function retrieveErrorMessage($body)
{
if (empty($body) || false === strpos($body, '<?xml')) {
return '';
}
$xml = simplexml_load_string($body);
if (isset($xml->Message)) {
return strval($xml->Message);
}
return '';
}
/**
* 尝试从body中获取错误Code
*
* @param $body
* @return string
*/
private function retrieveErrorCode($body)
{
if (empty($body) || false === strpos($body, '<?xml')) {
return '';
}
$xml = simplexml_load_string($body);
if (isset($xml->Code)) {
return strval($xml->Code);
}
return '';
}
/**
* 根据返回http状态码判断[200-299]即认为是OK
*
* @return bool
*/
protected function isResponseOk()
{
$status = $this->rawResponse->status;
if ((int)(intval($status) / 100) == 2) {
return true;
}
return false;
}
/**
* 返回原始的返回数据
*
* @return ResponseCore
*/
public function getRawResponse()
{
return $this->rawResponse;
}
/**
* 标示请求是否成功
*/
protected $isOk = false;
/**
* 由子类解析过的数据
*/
protected $parsedData = null;
/**
* 存放auth函数返回的原始Response
*
* @var ResponseCore
*/
protected $rawResponse;
}

View File

@ -0,0 +1,24 @@
<?php
namespace OSS\Result;
use OSS\Core\OssException;
use OSS\OssClient;
/**
*
* @package OSS\Result
*/
class SymlinkResult extends Result
{
/**
* @return string
* @throws OssException
*/
protected function parseDataFromResponse()
{
$this->rawResponse->header[OssClient::OSS_SYMLINK_TARGET] = rawurldecode($this->rawResponse->header[OssClient::OSS_SYMLINK_TARGET]);
return $this->rawResponse->header;
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace OSS\Result;
use OSS\Core\OssException;
/**
* Class UploadPartResult
* @package OSS\Result
*/
class UploadPartResult extends Result
{
/**
* 结果中part的ETag
*
* @return string
* @throws OssException
*/
protected function parseDataFromResponse()
{
$header = $this->rawResponse->header;
if (isset($header["etag"])) {
return $header["etag"];
}
throw new OssException("cannot get ETag");
}
}

1
addons/alisms/.addonrc Normal file
View File

@ -0,0 +1 @@
{"files":[],"license":"extended","licenseto":"62324","licensekey":"HwCJfFaG7PMIADoK DSqLNb+dM+2WsFdl0UKe2g==","domains":["fengketrade.com"],"licensecodes":[],"validations":["e4f0590e78c2a8b9d0c33fe33d4f9e4d"]}

86
addons/alisms/Alisms.php Executable file
View File

@ -0,0 +1,86 @@
<?php
namespace addons\alisms;
use think\Addons;
/**
* Alisms
*/
class Alisms extends Addons
{
/**
* 插件安装方法
* @return bool
*/
public function install()
{
return true;
}
/**
* 插件卸载方法
* @return bool
*/
public function uninstall()
{
return true;
}
/**
* 短信发送行为
* @param array $params 必须包含mobile,event,code
* @return boolean
*/
public function smsSend(&$params)
{
$config = get_addon_config('alisms');
if (!isset($config['template'][$params['event']])) {
return false;
}
$alisms = new \addons\alisms\library\Alisms();
$result = $alisms->mobile($params['mobile'])
->template($config['template'][$params['event']])
->param(['code' => $params['code']])
->send();
return $result;
}
/**
* 短信发送通知
* @param array $params 必须包含 mobile,event,msg
* @return boolean
*/
public function smsNotice(&$params)
{
$config = get_addon_config('alisms');
$alisms = \addons\alisms\library\Alisms::instance();
if (isset($params['msg'])) {
if (is_array($params['msg'])) {
$param = $params['msg'];
} else {
parse_str($params['msg'], $param);
}
} else {
$param = [];
}
$param = $param ? $param : [];
$params['template'] = $params['template'] ?? (isset($params['event']) && isset($config['template'][$params['event']]) ? $config['template'][$params['event']] : '');
$result = $alisms->mobile($params['mobile'])
->template($params['template'])
->param($param)
->send();
return $result;
}
/**
* 检测验证是否正确
* @param $params
* @return boolean
*/
public function smsCheck(&$params)
{
return true;
}
}

73
addons/alisms/config.php Executable file
View File

@ -0,0 +1,73 @@
<?php
return [
[
'name' => 'key',
'title' => '应用key',
'type' => 'string',
'content' => [],
'value' => 'your key',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
],
[
'name' => 'secret',
'title' => '密钥secret',
'type' => 'string',
'content' => [],
'value' => 'your secret',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
],
[
'name' => 'sign',
'title' => '签名',
'type' => 'string',
'content' => [],
'value' => 'your sign',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
],
[
'name' => 'template',
'title' => '短信模板',
'type' => 'array',
'content' => [],
'value' => [
'register' => 'SMS_114000000',
'resetpwd' => 'SMS_114000000',
'changepwd' => 'SMS_114000000',
'changemobile' => 'SMS_114000000',
'profile' => 'SMS_114000000',
'notice' => 'SMS_114000000',
'mobilelogin' => 'SMS_114000000',
'bind' => 'SMS_114000000',
],
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
],
[
'name' => '__tips__',
'title' => '温馨提示',
'type' => 'string',
'content' => [],
'value' => '应用key和密钥你可以通过 https://ram.console.aliyun.com/manage/ak 获取',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
],
];

View File

@ -0,0 +1,73 @@
<?php
namespace addons\alisms\controller;
use think\addons\Controller;
/**
* 阿里云短信
*/
class Index extends Controller
{
protected $model = null;
protected $templateList = [
'register' => '注册',
'resetpwd' => '重置密码',
'changepwd' => '修改密码',
'changemobile' => '修改手机号',
'profile' => '修改个人信息',
'notice' => '通知',
'mobilelogin' => '移动端登录',
'bind' => '绑定账号',
];
public function _initialize()
{
if (!\app\admin\library\Auth::instance()->id) {
$this->error('暂无权限浏览');
}
parent::_initialize();
}
//首页
public function index()
{
$this->view->assign('templateList', $this->templateList);
return $this->view->fetch();
}
//发送测试短信
public function send()
{
$config = get_addon_config('alisms');
$mobile = $this->request->post('mobile');
$template = $this->request->post('template');
$sign = $this->request->post('sign', '');
if (!$mobile) {
$this->error('手机号不能为空');
}
$templateArr = $config['template'] ?? [];
if (!isset($templateArr[$template]) || !$templateArr[$template]) {
$this->error('后台未配置对应的模板CODE');
}
$template = $templateArr[$template];
$sign = $sign ?: $config['sign'];
$param = (array)json_decode($this->request->post('param', '', 'trim'));
$param = ['code' => mt_rand(1000, 9999)];
$alisms = new \addons\alisms\library\Alisms();
$ret = $alisms->mobile($mobile)
->template($template)
->sign($sign)
->param($param)
->send();
if ($ret) {
$this->success("发送成功");
} else {
$this->error("发送失败!失败原因:" . $alisms->getError());
}
}
}

10
addons/alisms/info.ini Normal file
View File

@ -0,0 +1,10 @@
name = alisms
title = 阿里云短信发送
intro = 阿里云短信发送插件
author = FastAdmin
website = https://www.fastadmin.net
version = 1.0.11
state = 1
url = /addons/alisms
license = extended
licenseto = 62324

170
addons/alisms/library/Alisms.php Executable file
View File

@ -0,0 +1,170 @@
<?php
namespace addons\alisms\library;
/**
* 阿里云SMS短信发送
*/
class Alisms
{
private $_params = [];
public $error = '';
protected $config = [];
protected static $instance;
public function __construct($options = [])
{
if ($config = get_addon_config('alisms')) {
$this->config = array_merge($this->config, $config);
}
$this->config = array_merge($this->config, is_array($options) ? $options : []);
}
/**
* 单例
* @param array $options 参数
* @return Alisms
*/
public static function instance($options = [])
{
if (is_null(self::$instance)) {
self::$instance = new static($options);
}
return self::$instance;
}
/**
* 设置签名
* @param string $sign
* @return Alisms
*/
public function sign($sign = '')
{
$this->_params['SignName'] = $sign;
return $this;
}
/**
* 设置参数
* @param array $param
* @return Alisms
*/
public function param(array $param = [])
{
foreach ($param as $k => &$v) {
$v = (string)$v;
}
unset($v);
$param = array_filter($param);
$this->_params['TemplateParam'] = $param ? json_encode($param) : '{}';
return $this;
}
/**
* 设置模板
* @param string $code 短信模板
* @return Alisms
*/
public function template($code = '')
{
$this->_params['TemplateCode'] = $code;
return $this;
}
/**
* 接收手机
* @param string $mobile 手机号码
* @return Alisms
*/
public function mobile($mobile = '')
{
$this->_params['PhoneNumbers'] = $mobile;
return $this;
}
/**
* 立即发送
* @return boolean
*/
public function send()
{
$this->error = '';
$params = $this->_params();
$params['Signature'] = $this->_signed($params);
$response = $this->_curl($params);
if ($response !== false) {
$res = (array)json_decode($response, true);
if (isset($res['Code']) && $res['Code'] == 'OK') {
return true;
}
$this->error = $res['Message'] ?? 'InvalidResult';
} else {
$this->error = 'InvalidResult';
}
return false;
}
/**
* 获取错误信息
* @return string
*/
public function getError()
{
return $this->error;
}
private function _params()
{
return array_merge([
'AccessKeyId' => $this->config['key'],
'SignName' => $this->config['sign'] ?? '',
'Action' => 'SendSms',
'Format' => 'JSON',
'Version' => '2017-05-25',
'SignatureVersion' => '1.0',
'SignatureMethod' => 'HMAC-SHA1',
'SignatureNonce' => uniqid(),
'Timestamp' => gmdate('Y-m-d\TH:i:s\Z'),
], $this->_params);
}
private function percentEncode($string)
{
$string = urlencode($string);
$string = preg_replace('/\+/', '%20', $string);
$string = preg_replace('/\*/', '%2A', $string);
$string = preg_replace('/%7E/', '~', $string);
return $string;
}
private function _signed($params)
{
$sign = $this->config['secret'];
ksort($params);
$canonicalizedQueryString = '';
foreach ($params as $key => $value) {
$canonicalizedQueryString .= '&' . $this->percentEncode($key) . '=' . $this->percentEncode($value);
}
$stringToSign = 'GET&%2F&' . $this->percentencode(substr($canonicalizedQueryString, 1));
$signature = base64_encode(hash_hmac('sha1', $stringToSign, $sign . '&', true));
return $signature;
}
private function _curl($params)
{
$uri = 'http://dysmsapi.aliyuncs.com/?' . http_build_query($params);
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_URL, $uri);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.98 Safari/537.36");
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$reponse = curl_exec($ch);
curl_close($ch);
return $reponse;
}
}

View File

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
<title>阿里云短信发送示例 - {$site.name}</title>
<link href="__CDN__/assets/libs/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
<!--[if lt IE 9]>
<script src="/assets/js/html5shiv.js"></script>
<script src="/assets/js/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div class="container">
<div class="well" style="margin-top:30px;">
<div class="alert alert-danger">温馨提示:仅用于测试插件是否能正常发送短信</div>
<form class="form-horizontal" action="{:addon_url('alisms/index/send')}" method="POST">
<fieldset>
<legend style="padding-bottom:15px;">阿里云短信发送测试</legend>
<div class="form-group">
<label class="col-lg-2 control-label">手机号</label>
<div class="col-lg-10">
<input type="text" class="form-control" name="mobile" placeholder="手机号">
</div>
</div>
<div class="form-group">
<label class="col-lg-2 control-label">消息模板</label>
<div class="col-lg-10">
<select name="template" class="form-control">
{foreach name="templateList" id="item"}
<option value="{$key}">{$item} ({$key})</option>
{/foreach}
</select>
</div>
</div>
<div class="form-group">
<div class="col-lg-10 col-lg-offset-2">
<button type="submit" class="btn btn-primary">发送</button>
<button type="reset" class="btn btn-default">重置</button>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<script src="__CDN__/assets/libs/jquery/dist/jquery.min.js"></script>
<script src="__CDN__/assets/libs/bootstrap/dist/js/bootstrap.min.js"></script>
<script type="text/javascript">
$(function () {
});
</script>
</body>
</html>

1
addons/shopro/.addonrc Normal file

File diff suppressed because one or more lines are too long

155
addons/shopro/Shopro.php Executable file
View File

@ -0,0 +1,155 @@
<?php
namespace addons\shopro;
use think\Addons;
use app\common\library\Menu;
use app\admin\model\AuthRule;
use addons\shopro\library\Hook;
/**
* Shopro插件 v3.0.0
*/
class Shopro extends Addons
{
/**
* 插件安装方法
* @return bool
*/
public function install()
{
// 创建菜单
$menu = self::getMenu();
Menu::create($menu['new']);
return true;
}
/**
* 插件卸载方法
* @return bool
*/
public function uninstall()
{
// 删除菜单
Menu::delete('shopro');
return true;
}
/**
* 插件启用方法
*/
public function enable()
{
// 启用菜单
Menu::enable('shopro');
return true;
}
/**
* 插件更新方法
*/
public function upgrade()
{
// 更新菜单
$menu = self::getMenu();
Menu::upgrade('shopro', $menu['new']);
return true;
}
/**
* 插件禁用方法
*/
public function disable()
{
// 禁用菜单
Menu::disable('shopro');
return true;
}
/**
* 应用初始化
*/
public function appInit()
{
// 公共方法
require_once __DIR__ . '/helper/helper.php';
// 覆盖队列 redis 参数
$queue = \think\Config::get('queue');
$redis = \think\Config::get('redis');
if ($queue && strtolower($queue['connector']) == 'redis' && $redis) {
$queue = array_merge($redis, $queue); // queue.php 中的配置,覆盖 redis.php 中的配置
\think\Config::set('queue', $queue);
}
// database 增加断线重连参数
$database = \think\Config::get('database');
$database['break_reconnect'] = true; // 断线重连
\think\Config::set('database', $database);
// 全局注册行为事件
Hook::register();
if (request()->isCli()) {
\think\Console::addDefaultCommands([
'addons\shopro\console\ShoproChat',
'addons\shopro\console\ShoproHelp'
]);
}
// 全局共享 暗色类型 变量
\think\View::share('DARK_TYPE', $this->getDarkType());
}
public function configInit(&$config)
{
// 全局 js共享 暗色类型 变量
$config['dark_type'] = $this->getDarkType();
}
private static function getMenu()
{
$newMenu = [];
$config_file = ADDON_PATH . "shopro" . DS . 'config' . DS . "menu.php";
if (is_file($config_file)) {
$newMenu = include $config_file;
}
$oldMenu = AuthRule::where('name', 'like', "shopro%")->select();
$oldMenu = array_column($oldMenu, null, 'name');
return ['new' => $newMenu, 'old' => $oldMenu];
}
/**
* 获取暗黑类型
*
* @return string
*/
private function getDarkType()
{
$dark_type = 'none';
if (in_array('darktheme', get_addonnames())) {
// 有暗黑主题
$darkthemeConfig = get_addon_config('darktheme');
$dark_type = $darkthemeConfig['mode'] ?? 'none';
$thememode = cookie("thememode");
if ($thememode && in_array($thememode, ['dark', 'light'])) {
$dark_type = $thememode;
}
}
return $dark_type;
}
}

57
addons/shopro/bootstrap.js vendored Executable file
View File

@ -0,0 +1,57 @@
if (Config.modulename == 'admin' && Config.controllername == 'index' && Config.actionname == 'index') {
require.config({
paths: {
'vue3': "../addons/shopro/libs/vue",
'vue': "../addons/shopro/libs/vue.amd",
'text': "../addons/shopro/libs/require-text",
'SaChat': '../addons/shopro/chat/index',
'ElementPlus': '../addons/shopro/libs/element-plus/index',
'ElementPlusIconsVue3': "../addons/shopro/libs/element-plus/icons-vue",
'ElementPlusIconsVue': '../addons/shopro/libs/element-plus/icons-vue.amd',
'io': '../addons/shopro/libs/socket.io',
},
shim: {
'ElementPlus': {
deps: ['css!../addons/shopro/libs/element-plus/index.css']
},
},
});
require(['vue3', 'ElementPlusIconsVue3'], function (Vue3, ElementPlusIconsVue3) {
require(['vue', 'jquery', 'SaChat', 'text!../addons/shopro/chat/index.html', 'ElementPlus', 'ElementPlusIconsVue', 'io'], function (Vue, $, SaChat, SaChatTemplate, ElementPlus, ElementPlusIconsVue, io) {
if (Config.dark_type != 'none') {
SaChatTemplate = SaChatTemplate.replaceAll('__DARK__', `<link rel="stylesheet" href="__CDN__/assets/addons/shopro/css/dark.css?v={$site.version|htmlentities}" />`)
}
SaChatTemplate = SaChatTemplate.replaceAll('__DARK__', ``)
SaChatTemplate = SaChatTemplate.replaceAll('__CDN__', Config.__CDN__)
Fast.api.ajax({
url: 'shopro/chat/index/init',
loading: false,
type: 'GET'
}, function (ret, res) {
$("body").append(`<div id="SaChatTemplateContainer"></div>
<div id="SaChatWrap"><sa-chat></sa-chat></div>`);
$("#SaChatTemplateContainer").append(SaChatTemplate);
const { createApp } = Vue
const app = createApp({})
app.use(ElementPlus)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.component('sa-chat', SaChat)
app.mount(`#SaChatWrap`)
return false;
}, function (ret, res) {
if (res.msg == '') {
return false;
}
})
});
});
}

View File

@ -0,0 +1,43 @@
<?php
namespace addons\shopro\channel;
use addons\shopro\notification\Notification;
use app\admin\model\shopro\notification\Notification as NotificationModel;
class Database
{
public function __construct()
{
}
/**
* 发送 模板消息
*
* @param mixed $notifiable // 通知用户
* @param 通知内容
* @return void
*/
public function send($notifiable, Notification $notification)
{
$data = [];
if (method_exists($notification, 'toDatabase')) {
$data = $notification->toDatabase($notifiable);
$notificationModel = new NotificationModel();
$notificationModel->id = \fast\Random::uuid();
$notificationModel->notification_type = $notification->notification_type;
$notificationModel->type = $notification->event;
$notificationModel->notifiable_id = $notifiable['id'];
$notificationModel->notifiable_type = $notifiable->getNotifiableType();
$notificationModel->data = $data;
$notificationModel->save();
}
return true;
}
}

60
addons/shopro/channel/Email.php Executable file
View File

@ -0,0 +1,60 @@
<?php
namespace addons\shopro\channel;
use addons\shopro\notification\Notification;
use think\Validate;
use app\common\library\Email as SendEmail;
class Email
{
public function __construct()
{
}
/**
* 发送 微信模板消息
*
* @param mixed $notifiable // 通知用户
* @param 通知内容
* @return void
*/
public function send($notifiable, Notification $notification)
{
$data = [];
if (method_exists($notification, 'toEmail')) {
$data = $notification->toEmail($notifiable);
if ($data && isset($notifiable['email']) && Validate::is($notifiable['email'], "email")) {
try {
$email = new SendEmail;
$result = $email
->to($notifiable['email'], $notifiable['nickname'])
->subject(($data['data'] ? $data['data']['template'] : '邮件通知'))
->message('<div style="min-height:550px; padding: 50px 20px 100px;">' . $data['content'] . '</div>')
->send();
if ($result) {
// 发送成功
$notification->sendOk('Email');
} else {
// 邮件发送失败
\think\Log::error('邮件消息发送失败:用户:' . $notifiable['id'] . ';类型:' . get_class($notification) . ";发送类型:" . $notification->event . ";错误信息:" . json_encode($email->getError()));
}
} catch (\Exception $e) {
// 因为配置较麻烦,这里捕获异常防止因为缺少字段,导致队列一直执行不成功
format_log_error($e, 'email_notification', '用户:' . $notifiable['id'] . ';类型:' . get_class($notification) . ";发送类型:" . $notification->event);
}
return true;
}
// 没有openid
\think\Log::error('邮件消息发送失败,没有 email或 email 格式不正确:用户:' . $notifiable['id'] . ';类型:' . get_class($notification) . ";发送类型:" . $notification->event);
}
return true;
}
}

62
addons/shopro/channel/Sms.php Executable file
View File

@ -0,0 +1,62 @@
<?php
namespace addons\shopro\channel;
use addons\shopro\notification\Notification;
class Sms
{
public function __construct()
{
}
/**
* 发送 模板消息
*
* @param mixed $notifiable // 通知用户
* @param 通知内容
* @return void
*/
public function send($notifiable, Notification $notification)
{
$data = [];
if (method_exists($notification, 'toSms')) {
$data = $notification->toSms($notifiable);
if ($data && $data['mobile'] && isset($data['template_id'])) {
$mobile = $data['mobile'];
$sendData = $data['data'] ?? [];
$params = [
'mobile' => $mobile,
'msg' => $sendData,
'template' => $data['template_id'],
'default_content' => $notification->template['MessageDefaultContent'] ?? null // 短信宝使用
];
if (in_array('smsbao', get_addonnames())) {
// 如果是短信宝msg 就是 default_content 的内容
$params['msg'] = $params['default_content'];
}
$result = \think\Hook::listen('sms_notice', $params, null, true);
if (!$result) {
// 短信发送失败
\think\Log::error('短信发送失败:用户:'. $notifiable['id'] . ';类型:' . get_class($notification) . ";发送类型:" . $notification->event);
} else {
// 发送成功
$notification->sendOk('Sms');
}
return true;
}
// 没有手机号
\think\Log::error('短信发送失败,没有手机号:用户:' . $notifiable['id'] . ';类型:' . get_class($notification) . ";发送类型:" . $notification->event);
}
return true;
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace addons\shopro\channel;
use addons\shopro\notification\Notification;
use addons\shopro\library\Websocket as WebsocketSend;
class Websocket
{
/**
* 发送 Websocket 通知
* @param Notifiable $notifiable
* @param Notification $notification
*/
public function send($notifiable, Notification $notification)
{
$data = [];
if (method_exists($notification, 'toSms')) {
$data = $notification->toWebsocket($notifiable);
if ($notification->receiver_type != 'admin') {
// 目前只有 admin 消息类型发送 socket
return true;
}
// 发送数据
$requestData = [
'notifiable' => $notifiable->toArray(),
'notification_type' => $notification->notification_type,
'type' => $notification->event,
'data' => $data,
'read_time' => null,
'createtime' => date('Y-m-d H:i:s')
];
// 接收人
$receiver = [
'ids' => $notifiable->id,
'type' => $notifiable->getNotifiableType()
];
try {
$websocket = new WebsocketSend();
$result = $websocket->notification([
'receiver' => $receiver,
'data' => $requestData
]);
if ($result !== true) {
// 发送失败
\think\Log::error('websocket 通知发送失败:用户:' . $notifiable['id'] . ';类型:' . get_class($notification) . ";发送类型:" . $notification->event . ";错误信息:" . json_encode($result, JSON_UNESCAPED_UNICODE));
}
} catch (\Exception $e) {
// 因为配置较麻烦,这里捕获异常防止因为缺少字段,导致队列一直执行不成功
format_log_error($e, 'websocket_notification', '用户:' . $notifiable['id'] . ';类型:' . get_class($notification) . ";发送类型:" . $notification->event);
}
}
return true;
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace addons\shopro\channel;
use addons\shopro\notification\Notification;
use addons\shopro\facade\Wechat;
class WechatMiniProgram
{
public function __construct()
{
}
/**
* 发送 微信模板消息
*
* @param mixed $notifiable // 通知用户
* @param 通知内容
* @return void
*/
public function send($notifiable, Notification $notification)
{
$data = [];
if (method_exists($notification, 'toWechatMiniProgram')) {
$data = $notification->toWechatMiniProgram($notifiable);
if ($data && isset($data['openid']) && isset($data['template_id']) && $data['template_id']) {
$data['touser'] = $data['openid'];
unset($data['openid']);
try {
// 发送模板消息
$result = Wechat::miniProgram()->subscribe_message->send($data);
if ($result['errcode'] != 0) {
// 小程序模板发送失败
\think\Log::error('小程序模板消息发送失败:用户:'. $notifiable['id'] . ';类型:' . get_class($notification) . ";发送类型:" . $notification->event . ";错误信息:" . json_encode($result, JSON_UNESCAPED_UNICODE));
} else {
// 发送成功
$notification->sendOk('WechatMiniProgram');
}
} catch (\Exception $e) {
// 因为配置较麻烦,这里捕获异常防止因为缺少字段,导致队列一直执行不成功
format_log_error($e, 'WechatMiniProgram_notification', '用户:' . $notifiable['id'] . ';类型:' . get_class($notification) . ";发送类型:" . $notification->event);
}
return true;
}
// 没有openid
\think\Log::error('小程序模板消息发送失败,没有 openid用户' . $notifiable['id'] . ';类型:' . get_class($notification) . ";发送类型:" . $notification->event);
}
return true;
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace addons\shopro\channel;
use addons\shopro\notification\Notification;
use addons\shopro\facade\Wechat;
class WechatOfficialAccount
{
public function __construct()
{
}
/**
* 发送 微信模板消息
*
* @param mixed $notifiable // 通知用户
* @param 通知内容
* @return void
*/
public function send($notifiable, Notification $notification)
{
$data = [];
if (method_exists($notification, 'toWechatOfficialAccount')) {
$data = $notification->toWechatOfficialAccount($notifiable);
if ($data && isset($data['openid']) && isset($data['template_id']) && $data['template_id']) {
$data['touser'] = $data['openid'];
unset($data['openid']);
try {
// 发送模板消息
$result = Wechat::officialAccount()->template_message->send($data);
if ($result['errcode'] != 0) {
// 短信发送失败
\think\Log::error('公众号模板消息发送失败:用户:' . $notifiable['id'] . ';类型:' . get_class($notification) . ";发送类型:" . $notification->event . ";错误信息:" . json_encode($result, JSON_UNESCAPED_UNICODE));
} else {
// 发送成功
$notification->sendOk('WechatOfficialAccount');
}
} catch (\Exception $e) {
// 因为配置较麻烦,这里捕获异常防止因为缺少字段,导致队列一直执行不成功
format_log_error($e, 'WechatOfficialAccount_notification', '用户:' . $notifiable['id'] . ';类型:' . get_class($notification) . ";发送类型:" . $notification->event);
}
return true;
}
// 没有openid
\think\Log::error('公众号模板消息发送失败,没有 openid用户' . $notifiable['id'] . ';类型:' . get_class($notification) . ";发送类型:" . $notification->event);
}
return true;
}
}

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More