@@ -236,7 +236,7 @@ export default function WechatAccountsPage() {
-
{account.nickname}
+ {account.nickname}
{account.status === "normal" ? "正常" : "异常"}
@@ -254,8 +254,8 @@ export default function WechatAccountsPage() {
-
微信号:{account.wechatId}
-
+
微信号:{account.wechatId}
+
好友数量:{account.friendCount}
今日新增:+{account.todayAdded}
@@ -281,9 +281,9 @@ export default function WechatAccountsPage() {
-
-
所属设备:{account.deviceName || '未知设备'}
-
最后活跃:{account.lastActive}
+
+
所属设备:{account.deviceName || '未知设备'}
+
最后活跃:{account.lastActive}
diff --git a/Cunkebao/app/workspace/moments-sync/components/device-selection-dialog.tsx b/Cunkebao/app/workspace/moments-sync/components/device-selection-dialog.tsx
index d87bb3d5..f4b9cfe3 100755
--- a/Cunkebao/app/workspace/moments-sync/components/device-selection-dialog.tsx
+++ b/Cunkebao/app/workspace/moments-sync/components/device-selection-dialog.tsx
@@ -9,6 +9,7 @@ import { Search, RefreshCw } from "lucide-react"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { ScrollArea } from "@/components/ui/scroll-area"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
+import { ImeiDisplay } from "@/components/ImeiDisplay"
interface Device {
id: string
@@ -123,12 +124,15 @@ export function DeviceSelectionDialog({ open, onOpenChange, selectedDevices, onS
{device.name}
-
+
{device.status === "online" ? "在线" : "离线"}
-
IMEI: {device.imei}
+
+ IMEI:
+
+
微信号: {device.wxid}
{device.usedInPlans > 0 && (
diff --git a/Cunkebao/app/workspace/moments-sync/new/steps/DeviceSelector.tsx b/Cunkebao/app/workspace/moments-sync/new/steps/DeviceSelector.tsx
index 2a0c9ce6..f5e2e0b5 100755
--- a/Cunkebao/app/workspace/moments-sync/new/steps/DeviceSelector.tsx
+++ b/Cunkebao/app/workspace/moments-sync/new/steps/DeviceSelector.tsx
@@ -4,7 +4,7 @@ import { useState, useEffect } from "react"
import { Card } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
-import { Search, RefreshCw, X } from "lucide-react"
+import { Search, RefreshCw, X, ChevronLeft, ChevronRight } from "lucide-react"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Checkbox } from "@/components/ui/checkbox"
import { toast } from "@/components/ui/use-toast"
@@ -17,6 +17,7 @@ import {
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination"
+import { ImeiDisplay } from "@/components/ImeiDisplay"
// 定义类型,避免导入错误
interface Device {
@@ -148,6 +149,16 @@ export function DeviceSelector({ formData, onChange, onNext, onPrev }: DeviceSel
}
}
+ const handlePrevPage = () => {
+ setCurrentPage((prev) => Math.max(1, prev - 1))
+ }
+
+ const handleNextPage = () => {
+ setCurrentPage((prev) => Math.min(Math.ceil(filteredDevices.length / itemsPerPage), prev + 1))
+ }
+
+ const isLastPage = currentPage === Math.ceil(filteredDevices.length / itemsPerPage)
+
return (
@@ -193,15 +204,14 @@ export function DeviceSelector({ formData, onChange, onNext, onPrev }: DeviceSel
{device.name}
-
+
{device.status === "online" ? "在线" : "离线"}
-
+
+
+
+ IMEI:
+
-
IMEI: {device.imei}
微信号: {device.wechatId}
{device.usedInPlans > 0 && (
已用于 {device.usedInPlans} 个计划
@@ -211,24 +221,25 @@ export function DeviceSelector({ formData, onChange, onNext, onPrev }: DeviceSel
))}
-
+
setCurrentPage((prev) => Math.max(1, prev - 1))}
+ onClick={handlePrevPage}
disabled={currentPage === 1}
/>
- {Array.from({ length: Math.ceil(filteredDevices.length / itemsPerPage) }, (_, i) => i + 1).map((page) => (
-
- setCurrentPage(page)} isActive={currentPage === page}>
- {page}
+ {Array.from({ length: Math.ceil(filteredDevices.length / itemsPerPage) }).map((_, index) => (
+
+ setCurrentPage(index + 1)}
+ isActive={currentPage === index + 1}
+ >
+ {index + 1}
))}
- setCurrentPage((prev) => Math.min(Math.ceil(filteredDevices.length / itemsPerPage), prev + 1))
- }
- disabled={currentPage === Math.ceil(filteredDevices.length / itemsPerPage)}
+ onClick={handleNextPage}
+ disabled={isLastPage}
/>
@@ -268,6 +279,30 @@ export function DeviceSelector({ formData, onChange, onNext, onPrev }: DeviceSel
下一步
+
+
+
+
+
+ {currentPage} / {Math.ceil(filteredDevices.length / itemsPerPage)}
+
+
+
+
)
diff --git a/Cunkebao/components/ImeiDisplay.tsx b/Cunkebao/components/ImeiDisplay.tsx
new file mode 100644
index 00000000..c120b913
--- /dev/null
+++ b/Cunkebao/components/ImeiDisplay.tsx
@@ -0,0 +1,140 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription
+} from "@/components/ui/dialog"
+import { Button } from "@/components/ui/button"
+import { Copy } from "lucide-react"
+import { toast } from "@/components/ui/use-toast"
+
+interface ImeiDisplayProps {
+ imei: string
+ className?: string
+ containerWidth?: string | number // 可以接受字符串或数字类型的容器宽度配置
+}
+
+export function ImeiDisplay({ imei, className = "", containerWidth = "max-w-[calc(100%-40px)]" }: ImeiDisplayProps) {
+ // 初始状态设为false,确保服务端渲染和客户端水合时状态一致
+ const [open, setOpen] = useState(false)
+ const [mounted, setMounted] = useState(false)
+
+ // 确保只在客户端执行的初始化逻辑
+ useEffect(() => {
+ setMounted(true)
+ }, [])
+
+ const handleCopy = () => {
+ // 只在客户端执行
+ if (typeof navigator === 'undefined') return
+
+ try {
+ if (navigator.clipboard) {
+ navigator.clipboard.writeText(imei)
+ toast({
+ title: "已复制",
+ description: "IMEI已复制到剪贴板",
+ })
+ } else {
+ // 备用复制方法
+ const textArea = document.createElement("textarea")
+ textArea.value = imei
+ document.body.appendChild(textArea)
+ textArea.focus()
+ textArea.select()
+
+ try {
+ document.execCommand('copy')
+ toast({
+ title: "已复制",
+ description: "IMEI已复制到剪贴板",
+ })
+ } catch (err) {
+ toast({
+ title: "复制失败",
+ description: "您的浏览器不支持自动复制",
+ variant: "destructive"
+ })
+ }
+
+ document.body.removeChild(textArea)
+ }
+ } catch (err) {
+ toast({
+ title: "复制失败",
+ description: "您的浏览器不支持自动复制",
+ variant: "destructive"
+ })
+ }
+ }
+
+ // 处理Dialog打开和关闭
+ const handleOpenChange = (newOpen: boolean) => {
+ // 如果是关闭操作,阻止事件冒泡
+ if (!newOpen) {
+ // 使用setTimeout确保事件处理完成后再关闭模态框
+ setTimeout(() => {
+ setOpen(false)
+ }, 0)
+ } else {
+ setOpen(true)
+ }
+ }
+
+ // 处理containerWidth为数字的情况
+ const widthStyle = typeof containerWidth === 'number'
+ ? `max-w-[${containerWidth}px]`
+ : containerWidth
+
+ // 服务端渲染时,仅返回静态内容
+ if (!mounted) {
+ return
{imei}
+ }
+
+ return (
+ <>
+
{
+ e.preventDefault()
+ e.stopPropagation()
+ setOpen(true)
+ // 防止冒泡到Card的点击事件
+ return false
+ }}
+ title={imei}
+ >
+ {imei}
+
+
+
+ >
+ )
+}
\ No newline at end of file
diff --git a/Cunkebao/components/device-grid.tsx b/Cunkebao/components/device-grid.tsx
index 8688f88f..6b93ac49 100755
--- a/Cunkebao/components/device-grid.tsx
+++ b/Cunkebao/components/device-grid.tsx
@@ -2,8 +2,9 @@
import { Card } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
-import { Smartphone, Battery, Users, MessageCircle } from "lucide-react"
+import { Smartphone, Battery, Users, MessageCircle, Clock } from "lucide-react"
import { Checkbox } from "@/components/ui/checkbox"
+import { ImeiDisplay } from "@/components/ImeiDisplay"
export interface Device {
id: string
@@ -54,7 +55,7 @@ export function DeviceGrid({ devices, selectable, selectedDevices, onSelect, dev
)}
-
+
{currentStatus.status === "online" ? "在线" : "离线"}
@@ -68,7 +69,10 @@ export function DeviceGrid({ devices, selectable, selectedDevices, onSelect, dev
{device.name}
-
IMEI-{device.imei}
+
+ IMEI:
+
+
{device.remark && 备注: {device.remark}
}
@@ -90,7 +94,7 @@ export function DeviceGrid({ devices, selectable, selectedDevices, onSelect, dev
今日添加:{device.todayAdded}
加友状态:
-
+
{device.addFriendStatus === "normal" ? "正常" : "异常"}
diff --git a/Server/.gitignore b/Server/.gitignore
index 16330ac4..06067d1d 100755
--- a/Server/.gitignore
+++ b/Server/.gitignore
@@ -7,4 +7,3 @@ public/upload
vendor
/public/.user.ini
/404.html
-thinkphp
diff --git a/Server/CHANGELOG.md b/Server/CHANGELOG.md
deleted file mode 100755
index e320da4a..00000000
--- a/Server/CHANGELOG.md
+++ /dev/null
@@ -1,927 +0,0 @@
-## V5.1.39 LTS(2019-11-18)
-
-本次更新为常规更新,主要包括:
-
-* 修正`memcached`驱动
-* 改进`HasManyThrough`关联查询
-* 改进`Request`类`isJson`方法
-* 改进关联查询
-* 改进`redis`驱动
-* 增加 Model类`getWhere`方法对复合主键的支持
-* 改进`newQuery`方法
-* 改进闭包查询的参数绑定
-* 修正`Validate`
-* 修复某些情况下URL会多一个冒号
-* 调整composer.json
-* 修复使用`Cache::clear()`时,报错缓存文件不存在问题
-* 使用File类的unlink方法进行文件删除
-* 改进`paraseData`方法
-* 修正image验证方法
-* 改进Url生成
-* 改进空操作对数字的支持
-* 改进一处PHP7.4兼容性问题
-
-## V5.1.38 LTS(2019-8-8)
-
-本次更新为常规更新,主要包括:
-
-* `Request`类增加`isJson`方法
-* 改进浮点型查询
-* 修正关联查询关联外键为空的查询错误
-* 远程一对多支持关联统计和预载入查询
-* 远程一对多关联支持`has`/`hasWhere`查询
-* 优化`parseIn`解析
-* 改进`parseLike`查询
-* 改进Url生成
-* 改进模型的`toArray`方法
-* 修正`notIn`查询
-* 改进`JSON`字段查询
-* 改进Controller类`display`/`fetch`方法返回`ViewResponse`对象
-* 改进`param`方法
-* 改进`mysql`驱动`getExplain`方法
-* 改进时间查询
-* 改进模型关联的`has`/`hasWhere`方法对软删除的支持
-* 修正社区反馈的BUG
-
-## V5.1.37 LTS(2019-5-26)
-
-本次更新为常规更新,主要更新如下:
-
-* 改进关联数据更新
-* 修正关联动态获取器
-* 改进`redis`驱动
-* 修复验证规则里面出现二维数组时的错误
-* 改进跨域请求支持
-* 完善模型`hidden`方法对关联属性的支持
-* 改进`where`查询方法传入`Query`对象的支持`bind`数据
-* 改进数据集对象的`load`方法
-* 修正缓存类`clear`方法对`tag`的支持
-
-## V5.1.36 LTS(2019-4-28)
-
-本次更新为常规更新,主要更新如下:
-
-* 修正`chunk`方法一处异常抛出的错误
-* 修正模型输出的`visible`
-* 改进环境变量加载
-* 改进命令行日志的`level`配置支持
-* 修复设置有缓存前缀时,无法清空缓存标签的问题
-* HasMony对象`saveAll`方法兼容`Collection`格式参数格式
-* 修正`whereOr`查询使用字符串的问题
-* 改进`dateFormat`设置对写入数据的影响
-* 修正查询缓存
-* 记住指定的跳转地址
-* 改进软删除
-* 改进聚合查询SQL去除limit 1
-* 改进缓存驱动
-
-## V5.1.35 LTS(2019-3-2)
-
-本次主要为常规更新,修正了一些反馈的问题。
-
-* 修正验证类自定义验证方法执行两次的问题
-* 模型增加`isEmpty`方法用于判断是否空模型
-* 改进获取器对`append`的支持
-* 修正一对多关联的`withCount`自关联问题
-* facade类注释调整
-* 改进关联属性的`visible`和`hidden`判断
-* 修正路由分组的`MISS`路由
-* 改进pgsql.sql
-
-## V5.1.34 LTS(2019-1-30)
-
-本次更新为常规更新,修正了一些反馈的问题。
-
-* 改进Request类的`has`方法,支持`patch`
-* 改进`unique`验证的多条件支持
-* 修复自定义上传验证,检测文件大小
-* 改进`in`查询支持表达式
-* 改进路由的`getBind`方法
-* 改进验证类的错误信息获取
-* 改进`response`助手函数默认值
-* 修正mysql的`regexp`查询
-* 改进模型类型强制转换写入对`Expression`对象的支持
-
-## V5.1.33 LTS(2019-1-16)
-
-* 修复路由中存在多个相同替换的正则BUG
-* 修正whereLike查询
-* join方法支持参数绑定
-* 改进union方法
-* 修正多对多关联的attach方法
-* 改进验证类的正则规则自定义
-* 改进Request类method方法
-* 改进File日志类型的CLI日志写入
-* 改进文件日志time_format配置对JSON格式的支持
-
-## V5.1.32 LTS(2018-12-24)
-
-本次主要为常规更新,修正了一些反馈的问题。
-
-
-* 改进多对多关联的`attach`方法
-* 改进聚合查询的`field`处理
-* 改进关联的`save`方法
-* 修正模型`exists`方法返回值
-* 改进时间字段写入和输出
-* 改进控制器中间件的调用
-* 改进路由变量替换的性能
-* 改进缓存标签的处理机制
-
-## V5.1.31 LTS (2018-12-9)
-
-本次版本包含一个安全更新,建议升级。
-
-* 改进`field`方法
-* 改进`count`方法返回类型
-* `download`函数增加在浏览器中显示文件功能
-* 修正多对多模型的中间表数据写入
-* 改进`sqlsrv`驱动支持多个Schemas模式查询
-* 统一助手函数与\think\response\Download函数文件过期时间
-* 完善关联模型的`save`方法 增加`make`方法仅创建对象不保存
-* 修改条件表达式对静态变量的支持
-* 修正控制器名获取
-* 改进view方法的`field`解析
-
-## V5.1.30 LTS(2018-11-30)
-
-该版本为常规更新,修正了一些社区反馈的问题。
-
-主要更新如下:
-
-* 改进查询类的`execute`方法
-* 判断路由规则定义添加对请求类型的判断
-* 修复`orderRaw`异常
-* 修正 `optimize:autoload`指令
-* 改进软删除的`destroy`方法造成重复执行事件的问题
-* 改进验证类对扩展验证规则 始终验证 不管是否`require`
-* 修复自定义验证`remove`所有规则的异常
-* 改进时间字段的自动写入支持微秒数据
-* 改进`Connection`类的`getrealsql`方法
-* 修正`https`地址的URL生成
-* 修复 `array_walk_recursive` 在低于PHP7.1消耗内部指针问题
-* 改进手动参数绑定使用
-* 改进聚合查询方法的`field`参数支持`Expression`
-
-## V5.1.29 LTS(2018-11-11)
-
-该版本主要改进了参数绑定的解析问题和提升性能,并修正了一些反馈的问题。
-
-* 改进手动参数绑定
-* 修正MISS路由的分组参数无效问题
-* 行为支持对象的方法
-* 修正全局查询范围
-* 改进`belongsto`关联的`has`方法
-* 改进`hasMany`关联
-* 改进模型观察者多次注册的问题
-* 改进`query`类的默认查询参数处理
-* 修正`parseBetween`解析方法
-* 改进路由地址生成的本地域名支持
-* 改进参数绑定的实际URL解析性能
-* 改进`Env`类的`getEnv`和`get`方法
-* 改进模板缓存的生成优化
-* 修复验证类的多语言支持
-* 修复自定义场景验证`remove`规则异常
-* File类添加是否自动补全扩展名的选项
-* 改进`strpos`对子串是否存在的判断
-* 修复`choice`无法用值选择第一个选项问题
-* 验证器支持多维数组取值验证
-* 改进解析`extend`和`block`标签的正则
-
-## V5.1.28 LTS(2018-10-29)
-
-该版本主要修正了上一个版本存在的一些问题,并改进了关联查询
-
-* 改进聚合查询方法的字段支持DISTINCT
-* 改进定义路由后url函数的端口生成
-* 改进控制器中间件对`swoole`等的支持
-* 改进Log类`save`方法
-* 改进验证类的闭包验证参数
-* 多对多关联支持指定中间表数据的名称
-* 关联聚合查询支持闭包方式指定聚合字段
-* 改进Lang类`get`方法
-* 多对多关联增加判断关联数据是否存在的方法
-* 改进关联查询使用`fetchsql`的情况
-* 改进修改器的是否已经执行判断
-* 增加`afterWith`和`beforeWith`验证规则 用于比较日期字段
-
-## V5.1.27 LTS(2018-10-22)
-
-该版本主要修正了路由绑定的参数,改进了修改器的执行多次问题,并正式宣布为LTS版本!
-
-
-* 修正路由绑定的参数丢失问题
-* 修正路由别名的参数获取
-* 改进修改器会执行多次的问题
-
-## V5.1.26(2018-10-12)
-
-该版本主要修正了上一个版本的一些问题,并改进了全局查询范围的支持,同时包含了一个安全更新。
-
-
-* 修正单一模块下注解路由无效的问题
-* 改进数据库的聚合查询的字段处理
-* 模型类增加`globalScope`属性定义 用于指定全局的查询范围
-* 模型的`useGlobalScope`方法支持传入数组 用于指定当前查询需要使用的全局查询范围
-* 改进数据集的`order`方法对数字类型的支持
-* 修正上一个版本`order`方法解析的一处BUG
-* 排序字段不合法或者错误的时候抛出异常
-* 改进`Request`类的`file`方法对上传文件的错误判断
-
-## V5.1.25(2018-9-21)
-
-该版本主要改进了查询参数绑定的性能和对浮点型的支持,以及一些细节的完善。
-
-* 修正一处命令行问题
-* 改进`Socketlog`日志驱动,支持自定义默认展开日志类别
-* 修正`MorphMany`一处bug
-* 跳转到上次记住的url,并支持默认值
-* 改进模型的异常提示
-* 改进参数绑定对浮点型的支持
-* 改进`order`方法解析
-* 改进`json`字段数据的自动编码
-* 改进日志`log_write`可能造成的日志写入死循环
-* Log类增加`log_level`行为标签位置,用于对某个类型的日志进行处理
-* Route类增加`clear`方法清空路由规则
-* 分布式数据库配置支持使用数组
-* 单日志文件也支持`max_files`参数
-* 改进查询参数绑定的性能
-* 改进别名路由的URL后缀参数检测
-* 控制器前置方法和控制器中间件的`only`和`except`定义不区分大小写
-
-## V5.1.24(2018-9-5)
-
-该版本主要增加了命令行的表格输出功能,并增加了查看路由定义的指令,以及修正了社区的一些反馈问题。
-
-* 修正`Request`类的`file`方法
-* 修正路由的`cache`方法
-* 修正路由缓存的一处问题
-* 改进上传文件获取的异常处理
-* 改进`fetchCollection`方法支持传入数据集类名
-* 修正多级控制器的注解路由生成
-* 改进`Middleware`类`clear`方法
-* 增加`route:list`指令用于[查看定义的路由](752690) 并支持排序
-* 命令行增加`Table`输出类
-* `Command`类增加`table`方法用于输出表格
-* 改进搜索器查询方法支持别名定义
-* 命令行配置增加`auto_path`参数用于定义自动载入的命令类路径
-* 增加`make:command`指令用于[快速生成指令](354146)
-* 改进`make:controller`指令对操作方法后缀的支持
-* 改进命令行的定义文件支持索引数组 用于指令对象的惰性加载
-* 改进`value`和`column`方法对后续查询结果的影响
-* 改进`RuleName`类的`setRule`方法
-
-## V5.1.23(2018-8-23)
-
-该版本主要改进了数据集对象的处理,增加了`findOrEmpty`方法,并且修正了一些社区反馈的BUG。
-
-* 数据集类增加`diff`/`intersect`方法用于获取差集和交集(默认根据主键值比较)
-* 数据集类增加`order`方法支持指定字段排序
-* 数据集类增加`map`方法使用回调函数处理数据并返回新的数据集对象
-* Db增加`allowEmpty`方法允许`find`方法在没有数据的时候返回空数组或者空模型对象而不是null
-* Db增加`findOrEmpty`方法
-* Db增加`fetchCollection`方法用于指定查询返回数据集对象
-* 改进`order`方法的数组方式解析,增强安全性
-* 改进`withSearch`方法,支持第三个参数传入字段前缀标识,用于多表查询字段搜索
-* 修正`optimize:route`指令开启类库后缀后的注解路由生成
-* 修正redis缓存及session驱动
-* 支持指定`Yaconf`的独立配置文件
-* 增加`yaconf`助手函数用于配置文件
-
-
-## V5.1.22(2018-8-9)
-
-该版本主要增加了模型搜索器和`withJoin`方法,完善了模型输出和对`Yaconf`的支持,修正了一些社区反馈的BUG。
-
-* 改进一对一关联的`table`识别问题
-* 改进内置`Facade`类
-* 增加`withJoin`方法支持`join`方式的[一对一关联](一对一关联.md)查询
-* 改进`join`预载入查询的空数据问题
-* 改进`Config`类的`load`方法支持快速加载配置文件
-* 改进`execute`方法和事务的断线重连
-* 改进`memcache`驱动的`has`方法
-* 模型类支持定义[搜索器](搜索器.md)方法
-* 完善`Config`类对`Yaconf`的支持
-* 改进模型的`hidden/visible/append/withAttr`方法,支持在[查询前后调用](数组访问.md),以及支持数据集对象
-* 数据集对象增加`where`方法根据字段或者关联数据[过滤数据](模型数据集.md)
-* 改进AJAX请求的`204`判断
-
-
-## V5.1.21(2018-8-2)
-
-该版本主要增加了下载响应对象和数组查询对象的支持,并修正了一些社区反馈的问题。
-
-* 改进核心对象的无用信息调试输出
-* 改进模型的`isRelationAttr`方法判断
-* 模型类的`get`和`all`方法并入Db类
-* 增加[下载响应对象](文件下载.md)和`download`助手函数
-* 修正别名路由配置定义读取
-* 改进`resultToModel`方法
-* 修正开启类库后缀后的注解路由生成
-* `Response`类增加`noCache`快捷方法
-* 改进路由对象在`Swoole`/`Workerman`下面参数多次合并问题
-* 修正路由`ajax`/`pjax`参数后路由变量无法正确获取的问题
-* 增加清除中间件的方法
-* 改进依赖注入的参数规范自动识别(便于对接前端小写+下划线规范)
-* 改进`hasWhere`的数组条件的字段判断
-* 增加[数组查询对象](高级查询.md)`Where`支持(喜欢数组查询的福音)
-* 改进多对多关联的闭包支持
-
-## V5.1.20(2018-7-25)
-
-该版本主要增加了Db和模型的动态获取器的支持,并修正了一些已知问题。
-
-* Db类添加[获取器支持](703981)
-* 支持模型及关联模型字段[动态定义获取器](354046)
-* 动态获取器支持`JSON`字段
-* 改进路由的`before`行为执行(匹配后执行)
-* `Config`类支持`Yaconf`
-* 改进Url生成的端口问题
-* Request类增加`setUrl`和`setBaseUrl`方法
-* 改进页面trace的信息显示
-* 修正`MorphOne`关联
-* 命令行添加[查看版本指令](703994)
-
-## V5.1.19 (2018-7-13)
-
-该版本是一个小幅改进版本,针对`Swoole`和`Workerman`的`Cookie`支持做了一些改进,并修正了一些已知的问题。
-
-
-* 改进query类`delete`方法对软删除条件判断
-* 修正分表查询的软删除问题
-* 模型查询的时候同时传入`table`和`name`属性
-* 容器类增加`IteratorAggregate`和`Countable`接口支持
-* 路由分组支持对下面的资源路由统一设置`only/except/vars`参数
-* 改进Cookie类更好支持扩展
-* 改进Request类`post`方法
-* 改进模型自关联的自动识别
-* 改进Request类对`php://input`数据的处理
-
-
-## V5.1.18 (2018-6-30)
-
-该版本主要完善了对`Swoole`和`Workerman`的`HttpServer`运行支持,改进`Request`类,并修正了一些已知的问题。
-
-* 改进关联`append`方法的处理
-* 路由初始化和检测方法分离
-* 修正`destroy`方法强制删除
-* `app_init`钩子位置移入`run`方法
-* `think-swoole`扩展更新到2.0版本
-* `think-worker`扩展更新到2.0版本
-* 改进Url生成的域名自动识别
-* `Request`类增加`setPathinfo`方法和`setHost`方法
-* `Request`类增加`withGet`/`withPost`/`withHeader`/`withServer`/`withCookie`/`withEnv`方法进行赋值操作
-* Route类改进`host`属性的获取
-* 解决注解路由配置不生效的问题
-* 取消Test日志驱动,改为使用`close`设置关闭全局日志写入
-* 修正路由的`response`参数
-* 修正204响应输出的判断
-
-## V5.1.17 (2018-6-18)
-
-该版本主要增加了控制器中间件的支持,改进了路由功能,并且修正了社区反馈的一些问题。
-
-* 修正软删除的`delete`方法
-* 修正Query类`Count`方法
-* 改进多对多`detach`方法
-* 改进Request类`Session`方法
-* 增加控制器中间件支持
-* 模型类增加`jsonAssoc`属性用于定义json数据是否返回数组
-* 修正Request类`method`方法的请求伪装
-* 改进静态路由的匹配
-* 分组首页路由自动完整匹配
-* 改进sqlsrv的`column`方法
-* 日志类的`apart_level`配置支持true自动生成对应类型的日志文件
-* 改进`204`输出判断
-* 修正cli下页面输出的BUG
-* 验证类使用更高效的`ctype`验证机制
-* 改进Request类`cookie`方法
-* 修正软删除的`withTrashed`方法
-* 改进多态一对多的预载入查询
-* 改进Query类`column`方法的缓存读取
-* Query类增加`whereBetweenTimeField`方法
-* 改进分组下多个相同路由规则的合并匹配问题
-* 路由类增加`getRule`/`getRuleList`方法获取定义的路由
-
-## V5.1.16 (2018-6-7)
-
-该版本主要修正了社区反馈的一些问题,并对Request类做了进一步规范和优化。
-
-* 改进Session类的`boot`方法
-* App类的初始化方法可以单独执行
-* 改进Request类的`param`方法
-* 改进资源路由的变量替换
-* Request类增加`__isset`方法
-* 改进`useGlobalScope`方法对软删除的影响
-* 修正命令行调用
-* 改进Cookie类`init`方法
-* 改进多对多关联删除的返回值
-* 一对多关联写入支持`replace`
-* 路由增加`filter`检测方法,用于通过请求参数检测路由是否匹配
-* 取消Request类`session/env/server`方法的`filter`参数
-* 改进关联的指定属性输出
-* 模型删除操作删除后不清空对象数据仅作标记
-* 调整模型的`save`方法返回值为布尔值
-* 修正Request类`isAjax`方法
-* 修正中间件的模块配置读取
-* 取消Request类的请求变量的设置功能
-* 取消请求变量获取的默认修饰符
-* Request类增加`setAction/setModule/setController`方法
-* 关联模型的`delete`方法调用Query类
-* 改进URL生成的域名识别
-* 改进URL检测对已定义路由的域名判断
-* 模型类增加`isExists`和`isForce`方法
-* 软删除的`destroy`和`restore`方法返回值调整为布尔值
-
-## V5.1.15 (2018-6-1)
-
-该版本主要改进了路由缓存的性能和缓存方式设置,增加了JSON格式文件日志的支持,并修正了社区反馈的一些问题。
-
-* 容器类增加`exists`方法 仅判断是否存在对象实例
-* 取消配置类的`autoload`方法
-* 改进路由缓存大小提高性能
-* 改进Dispatch类`init`方法
-* 增加`make:validate`指令生成验证器类
-* Config类`get`方法支持默认值参数
-* 修正字段缓存指令
-* 改进App类对`null`数据的返回
-* 改进模型类的`__isset`方法判断
-* 修正`Query`类的`withAggregate`方法
-* 改进`RuleItem`类的`setRuleName`方法
-* 修正依赖注入和参数的冲突问题
-* 修正Db类对第三方驱动的支持
-* 修正模型类查询对象问题
-* 修正File缓存驱动的`has`方法
-* 修正资源路由嵌套
-* 改进Request类对`$_SERVER`变量的读取
-* 改进请求缓存处理
-* 路由缓存支持指定单独的缓存方式和参数
-* 修正资源路由的中间件多次执行问题
-* 修正`optimize:config`指令
-* 文件日志支持`JSON`格式日志保存
-* 修正Db类`connect`方法
-* 改进Log类`write`方法不会自动写入之前日志
-* 模型的关联操作默认启用事务
-* 改进软删除的事件响应
-
-## V5.1.14 (2018-5-18)
-
-该版本主要对底层容器进行了一些优化改进,并增加了路由缓存功能,可以进一步提升路由性能。
-
-* 依赖注入的对象参数传入改进
-* 改进核心类的容器实例化
-* 改进日期字段的读取
-* 改进验证类的`getScene`方法
-* 模型的`create`方法和`save`方法支持`replace`操作
-* 改进`Db`类的调用机制
-* App类调整为容器类
-* 改进容器默认绑定
-* `Loader`类增加工厂类的实例化方法
-* 增加路由变量默认规则配置参数
-* 增加路由缓存设计
-* 错误处理机制改进
-* 增加清空路由缓存指令
-
-
-## V5.1.13 (2018-5-11)
-
-该版本主要增加了MySQL的XA事务支持,模型事件支持观察者,以及对Facade类的改进。
-
-* 改进自动缓存
-* 改进Url生成
-* 修正数据缓存
-* 修正`value`方法的缓存
-* `join`方法和`view`方法的条件支持使用`Expression`对象
-* 改进驱动的`parseKey`方法
-* 改进Request类`host`方法和`domain`方法对端口的处理
-* 模型增加`withEvent`方法用于控制当前操作是否需要执行模型事件
-* 模型`setInc/setDec`方法支持更新事件
-* 模型添加`before_restore/after_restore`事件
-* 增加模型事件观察者
-* 路由增加`mobile`方法设置是否允许手机访问
-* 数据库XA事务支持
-* 改进索引数组查询对`IN`查询的支持
-* 修正`invokeMethod`方法
-* 修正空数据写入返回值的BUG
-* redis驱动支持`predis`
-* 改进`parseData`方法
-* 改进模块加载
-* App类初始化方法调整
-* 改进数组查询对表达式`Expression`对象支持
-* 改进闭包的依赖注入调用
-* 改进多对多关联的中间表模型更新
-* 增加容器中对象的自定义实例化
-
-## V5.1.12 (2018-4-25)
-
-该版本主要改进了主从查询的及时性,并支持动态设置请求数据。
-
-* 支持动态设置请求数据
-* 改进`comment`方法解析
-* 修正App类`__unset`方法
-* 改进url生成的域名绑定
-* 改进主从查询的及时性
-* 修正`value`的数据缓存功能
-* 改进分页类的集合对象方法调用
-* 改进Db类的代码提示
-* SQL日志增加主从标记
-
-## V5.1.11 (2018-4-19)
-
-该版本为安全和修正版本,改进了JSON查询的参数绑定问题和容器类对象实例获取,并包含一处可能的安全隐患,建议更新。
-
-* 支持指定JSON数据查询的字段类型
-* 修正`selectInsert`方法
-* `whereColumn`方法支持数组方式
-* 改进容器类`make`方法
-* 容器类`delete`方法支持数组
-* 改进`composer`自动加载
-* 改进模板引擎
-* 修正`like`查询的一处安全隐患
-
-## V5.1.10 (2018-4-16)
-
-该版本为修正版本,修正上一个版本的一些BUG,并增强了`think clear`指令。
-
-* 改进`orderField`方法
-* 改进`exists`查询
-* 修改cli模式入口文件位置计算
-* 修正`null`查询
-* 改进`parseTime`方法
-* 修正关联预载入查询
-* 改进`mysql`驱动
-* 改进`think clear`指令 支持 `-c -l -r `选项
-* 改进路由规则对`/`结尾的支持
-
-## V5.1.9 (2018-4-12)
-
-该版本主要是一些改进和修正,并包含一个安全更新,是一个推荐更新版本。
-
-* 默认模板渲染规则支持配置保持操作方法名
-* 改进`Request`类的`ip`方法
-* 支持模型软删除字段的默认值定义
-* 改进路由变量规则对中文的支持
-* 使用闭包查询的时候使用`cache(true)` 抛出异常提示
-* 改进`Loader`类`loadComposerAutoloadFiles`方法
-* 改进查询方法安全性
-* 修正路由地址中控制器名驼峰问题
-* 调整上一个版本的`module_init`和`app_begin`的钩子顺序问题
-* 改进CLI命令行执行的问题
-* 修正社区反馈的其它问题
-
-## V5.1.8 (2018-4-5)
-
-该版本主要改进了中间件的域名和模块支持,并同时修正了几个已知问题。
-
-* 增加`template.auto_rule` 参数设置默认模板渲染的操作名自动转换规则
-* 默认模板渲染规则改由视图驱动实现
-* 修正路由标识定义
-* 修正控制器路由方法
-* 改进Request类`ip`方法支持自定义代理IP参数
-* 路由注册中间件支持数组方式别名
-* 改进命令行执行下的`composer`自动加载
-* 添加域名中间件注册支持
-* 全局中间件支持模块定义文件
-* Log日志配置支持`close`参数可以全局关闭日志写入
-* 中间件方法中捕获`HttpResponseException`异常
-* 改进中间件的闭包参数传入
-* 改进分组路由的延迟解析
-* 改进URL生成对域名绑定的支持
-* 改进文件缓存和文件日志驱动的并发支持
-
-## V5.1.7 (2018-3-28)
-
-该版本主要修正了路由的一些问题,并改进了查询的安全性。
-
-* 支持`middleware`配置文件预先定义中间件别名方便路由调用
-* 修正资源路由
-* 改进`field`方法 自动识别`fieldRaw`
-* 增加`Expression`类
-* Query类增加`raw`方法
-* Query类的`field`/ `order` 和` where`方法都支持使用`raw`表达式查询
-* 改进`inc/dec`查询 支持批量更新
-* 改进路由分组
-* 改进Response类`create`方法
-* 改进composer自动加载
-* 修正域名路由的`append`方法
-* 修正操作方法的初始化方法获取不到问题
-
-## V5.1.6 (2018-3-26)
-
-该版本主要改进了路由规则的匹配算法,大幅提升了路由性能。并正式引入了中间件的支持,可以在路由中定义或者全局定义。另外包含了一个安全更新,是一个建议更新版本。
-
-* 改进URL生成对路由`ext`方法的支持
-* 改进查询缓存对不同数据库相同表名的支持
-* 改进composer自动加载的性能
-* 改进空路由变量对默认参数的影响
-* mysql的`json`字段查询支持多级
-* Query类增加`option`方法
-* 优化路由匹配
-* 修复验证规则数字键名丢失问题
-* 改进路由Url生成
-* 改进一对一关联预载入查询
-* Request类增加`rootDomain`方法
-* 支持API资源控制器生成 `make:controller --api`
-* 优化Template类的标签解析
-* 容器类增加删除和清除对象实例的方法
-* 修正MorphMany关联的`eagerlyMorphToMany`方法一处错误
-* Container类的异常捕获改进
-* Domain对象支持`bind`方法
-* 修正分页参数
-* 默认模板的输出规则不受URL影响
-* 注解路由支持多级控制器
-* Query类增加`getNumRows`方法获取前次操作影响的记录数
-* 改进查询条件的性能
-* 改进模型类`readTransform`方法对序列化类型的处理
-* Log类增加`close`方法可以临时关闭当前请求的日志写入
-* 文件日志方式增加自动清理功能(设置`max_files`参数)
-* 修正Query类的`getPk`方法
-* 修正模板缓存的布局开关问题
-* 修正Query类`select`方法的缓存
-* 改进input助手函数
-* 改进断线重连的信息判断
-* 改进正则验证方法
-* 调整语言包的加载顺序 放到`app_init`之前
-* controller类`fetch`方法改为`final`
-* 路由地址中的变量支持使用``方式
-* 改进XMLResponse 支持传入编码过的xml内容
-* 修正Query类`view`方法的数组表名支持
-* 改进路由的模型闭包绑定
-* 改进分组变量规则的继承
-* 改进`cli-server`模式下的`composer`自动加载
-* 路由变量规则异常捕获
-* 引入中间件支持
-* 路由定义增加`middleware`方法
-* 增加生成中间件指令`make:middleware`
-* 增加全局中间件定义支持
-* 改进`optimize:config`指令对全局中间件的支持
-* 改进config类`has`方法
-* 改进时间查询的参数绑定
-* 改进`inc/dec/exp`查询的安全性
-
-
-## V5.1.5 (2018-1-31)
-
-该版本主要增强了数据库的JSON查询,并支持JSON字段的聚合查询,改进了一些性能问题,修正了路由的一些BUG,主要更新如下:
-
-* 改进数据集查询对`JSON`数据的支持
-* 改进聚合查询对`JSON`字段的支持
-* 模型类增加`getOrFail`方法
-* 改进数据库驱动的`parseKey`方法
-* 改进Query类`join`方法的自关联查询
-* 改进数据查询不存在不生成查询缓存
-* 增加`run`命令行指令启动内置服务器
-* `Request`类`pathinfo`方法改进对`cli-server`支持
-* `Session`类增加`use_lock`配置参数设置是否启用锁机制
-* 优化`File`缓存自动生成空目录的问题
-* 域名及分组路由支持`append`方法传递隐式参数
-* 改进日志的并发写入问题
-* 改进`Query`类的`where`方法支持传入`Query`对象
-* 支持设置单个日志文件的文件名
-* 修正路由规则的域名条件约束
-* `Request`类增加`subDomain`方法用于获取当前子域名
-* `Response`类增加`allowCache`方法控制是否允许请求缓存
-* `Request`类增加`sendData`方法便于扩展
-* 改进`Env`类不依赖`putenv`方法
-* 改进控制台`trace`显示错误
-* 改进`MorphTo`关联
-* 改进完整路由匹配后带斜线访问出错的情况
-* 改进路由的多级分组问题
-* 路由url地址生成支持多级分组
-* 改进路由Url生成的`url_convert`参数的影响
-* 改进`miss`和`auto`路由内部解析
-* 取消预载入关联查询缓存功能
-
-## V5.1.4 (2018-1-19)
-
-该版本主要增强了数据库和模型操作,主要更新如下:
-
-* 支持设置 `deleteTime`属性为`false` 关闭软删除
-* 模型增加`getError`方法
-* 改进Query类的`getTableFields`/`getFieldsType`方法 支持表名自动获取
-* 模型类`toCollection`方法增加参数指定数据集类
-* 改进`union`查询
-* 关联预载入`with`方法增加缓存参数
-* 改进模型类的`get`和`all`方法的缓存 支持关联缓存
-* 支持`order by field`操作
-* 改进`insertAll`分批写入
-* 改进`json`字段数据支持
-* 增加JSON数据的模型对象化操作
-* 改进路由`ext`参数检测
-* 修正`rule`方法的`method`参数使用 `get|post` 方式注册路由的问题
-
-## V5.1.3 (2018-1-12)
-
-该版本主要改进了路由及调整函数加载顺序,主要更新如下:
-
-* 增加`env`助手函数;
-* 增加`route`助手函数;
-* 增加视图路由方法;
-* 增加路由重定向方法;
-* 路由默认区分最后的目录斜杆(支持设置不区分);
-* 调整公共文件和配置文件的加载顺序(可以在配置文件中直接使用助手函数);
-* 视图类增加`filter`方法设置输出过滤;
-* `view`助手函数增加`filter`参数;
-* 改进缓存生成指令;
-* Session类的`get`方法支持获取多级;
-* Request类`only`方法支持指定默认值;
-* 改进路由分组;
-* 修正使用闭包查询的时候自动数据缓存出错的情况;
-* 废除`view_filter`钩子位置;
-* 修正分组下面的资源路由;
-* 改进session驱动;
-
-## V5.1.2 (2018-1-8)
-
-该版本改进了配置类及数据库类,主要更新如下:
-
-* 修正嵌套路由分组;
-* 修正自定义模板标签界定符后表达式语法出错的情况;
-* 修正自关联的多次调用问题;
-* 修正数组查询的`null`条件查询;
-* 修正Query类的`order`及`field`的一处可能的BUG;
-* 配置参数设置支持三级;
-* 配置对象支持`ArrayAccess`;
-* App类增加`path`方法用于设置应用目录;
-* 关联定义增加`selfRelation`方法用于设置是否为自关联;
-
-## V5.1.1 (2018-1-3)
-
-修正一些反馈的BUG,包括:
-
-* 修正Cookie类存取数组的问题
-* 修正Controller的`fetch`方法
-* 改进跨域请求
-* 修正`insertAll`方法
-* 修正`chunk`方法
-
-## V5.1.0 (2018-1-1)
-
-主要更新如下:
-
-* 增加注解路由支持
-* 路由支持跨域请求设置
-* 增加`app_dispatch`钩子位置
-* 修正多对多关联的`detach`方法
-* 修正软删除的`destroy`方法
-* Cookie类`httponly`参数默认为false
-* 日志File驱动增加`single`参数配置记录同一个文件(不按日期生成)
-* 路由的`ext`和`denyExt`方法支持不传任何参数
-* 改进模型的`save`方法对`oracle`的支持
-* Query类的`insertall`方法支持配合`data`和`limit`方法
-* 增加`whereOr`动态查询支持
-* 日志的ip地址记录改进
-* 模型`saveAll`方法支持`isUpdate`方法
-* 改进`Pivot`模型的实例化操作
-* 改进Model类的`data`方法
-* 改进多对多中间表模型类
-* 模型增加`force`方法强制更新所有数据
-* Hook类支持设置入口方法名称
-* 改进验证类
-* 改进`hasWhere`查询的数据重复问题
-* 模型的`saveall`方法返回数据集对象
-* 改进File缓存的`clear`方法
-* 缓存添加统一的序列化机制
-* 改进泛三级域名的绑定
-* 改进泛域名的传值和取值
-* Request类增加`panDomain`方法
-* 改进废弃字段判断
-* App类增加`create`方法用于实例化应用类库
-* 容器类增加`has`方法
-* 改进多数据库切换连接
-* 改进断线重连的异常捕获
-* 改进模型类`buildQuery`方法
-* Query类增加`unionAll`方法
-* 关联统计功能增强(支持Sum/Max/Min/Avg)
-* 修正延迟写入
-* chunk方法支持复合主键
-* 改进JSON类型的写入
-* 改进Mysql的insertAll方法
-* Model类`save`方法改进复合主键包含自增的情况
-* 改进Query类`inc`和`dec`方法的关键字处理
-* File缓存inc和dec方法保持原来的有效期
-* 改进redis缓存的有效期判断
-* 增加checkRule方法用于单独数据的多个验证规则
-* 修正setDec方法的延迟写入
-* max和min方法增加force参数
-* 二级配置参数区分大小写
-* 改进join方法自关联的问题
-* 修正关联模型自定义表名的情况
-* Query类增加getFieldsType和getTableFields方法
-* 取消视图替换功能及view_replace_str配置参数
-* 改进域名绑定模块后的额外路由规则问题
-* 改进mysql的insertAll方法
-* 改进insertAll方法写入json字段数据的支持
-* 改进redis长连接多编号库的情况
-
-## RC3版本(2017-11-6)
-
-主要更新如下:
-
-* 改进redis驱动的`get`方法
-* 修正Query类的`alias`方法
-* `File`类错误信息支持多语言
-* 修正路由的额外参数解析
-* 改进`whereTime`方法
-* 改进Model类`getAttr`方法
-* 改进App类的`controller`和`validate`方法支持多层
-* 改进`HasManyThrough`类
-* 修正软删除的`restore`方法
-* 改进`MorpthTo`关联
-* 改进数据库驱动类的`parseKey`方法
-* 增加`whereField`动态查询方法
-* 模型增加废弃字段功能
-* 改进路由的`after`行为检查和`before`行为机制
-* 改进路由分组的检查
-* 修正mysql的`json`字段查询
-* 取消Connection类的`quote`方法
-* 改进命令行的支持
-* 验证信息支持多语言
-* 修正路由模型绑定
-* 改进参数绑定类型对枚举类型的支持
-* 修正模板的`{$Think.version} `输出
-* 改进模板`date`函数解析
-* 改进`insertAll`方法支持分批执行
-* Request类`host`方法支持反向代理
-* 改进`JumpResponse`支持区分成功和错误模板
-* 改进开启类库后缀后的关联外键自动识别问题
-* 修正一对一关联的JOIN方式预载入查询问题
-* Query类增加`hidden`方法
-
-## RC2版本(2017-10-17)
-
-主要更新如下:
-
-* 修正视图查询
-* 修正资源路由
-* 修正`HasMany`关联 修正`where`方法的闭包查询
-* 一对一关联绑定属性到父模型后 关联属性不再保留
-* 修正应用的命令行配置文件读取
-* 改进`Connection`类的`getCacheKey`方法
-* 改进文件上传的非法图像异常
-* 改进验证类的`unique`规则
-* Config类`get`方法支持获取一级配置
-* 修正count方法对`fetchSql`的支持
-* 修正mysql驱动对`socket`支持
-* 改进Connection类的`getRealSql`方法
-* 修正`view`助手函数
-* Query类增加`leftJoin` `rightJoin` 和 `fullJoin`方法
-* 改进app_namespace的获取
-* 改进`append`方法对一对一`bind`属性的支持
-* 改进关联的`saveall`方法的返回值
-* 路由标识设置异常修复
-* 改进Route类`rule`方法
-* 改进模型的`table`属性设置
-* 改进composer autofile的加载顺序
-* 改进`exception_handle`配置对闭包的支持
-* 改进app助手函数增加参数
-* 改进composer的加载路径判断
-* 修正路由组合变量的URL生成
-* 修正路由URL生成
-* 改进`whereTime`查询并支持扩展规则
-* File类的`move`方法第二个参数支持`false`
-* 改进Config类
-* 改进缓存类`remember`方法
-* 惯例配置文件调整 Url类当普通模式参数的时候不做`urlencode`处理
-* 取消`ROOT_PATH`和`APP_PATH`常量定义 如需更改应用目录 自己重新定义入口文件
-* 增加`app_debug`的`Env`获取
-* 修正泛域名绑定
-* 改进查询表达式的解析机制
-* mysql增加`regexp`查询表达式 支持正则查询
-* 改进查询表达式的异常判断
-* 改进model类的`destroy`方法
-* 改进Builder类 取消`parseValue`方法
-* 修正like查询的参数绑定问题
-* console和start文件移出核心纳入应用库
-* 改进Db类主键删除方法
-* 改进泛域名绑定模块
-* 取消`BIND_MODULE`常量 改为在入口文件使用`bind`方法设置
-* 改进数组查询
-* 改进模板渲染的异常处理
-* 改进控制器基类的架构方法参数
-* 改进Controller类的`success`和`error`方法
-* 改进对浏览器`JSON-Handle`插件的支持
-* 优化跳转模板的移动端显示
-* 修正模型查询的`chunk`方法对时间字段的支持
-* 改进trace驱动
-* Collection类增加`push`方法
-* 改进Redis Session驱动
-* 增加JumpResponse驱动
-
-
-## RC1(2017-9-8)
-
-主要新特性为:
-
-* 引入容器和Facade支持
-* 依赖注入完善和支持更多场景
-* 重构的(对象化)路由
-* 配置和路由目录独立
-* 取消系统常量
-* 助手函数增强
-* 类库别名机制
-* 模型和数据库增强
-* 验证类增强
-* 模板引擎改进
-* 支持PSR-3日志规范
-* RC1版本取消了5.0多个字段批量数组查询的方式
\ No newline at end of file
diff --git a/Server/README.md b/Server/README.md
deleted file mode 100755
index 4b96c8dc..00000000
--- a/Server/README.md
+++ /dev/null
@@ -1,180 +0,0 @@
-
-
-ThinkPHP 5.1(LTS版本) —— 12载初心,你值得信赖的PHP框架
-===============
-
-[](https://scrutinizer-ci.com/g/top-think/framework/?branch=5.1)
-[](https://travis-ci.org/top-think/framework)
-[](https://packagist.org/packages/topthink/framework)
-[](https://packagist.org/packages/topthink/framework)
-[](http://www.php.net/)
-[](https://packagist.org/packages/topthink/framework)
-
-ThinkPHP5.1对底层架构做了进一步的改进,减少依赖,其主要特性包括:
-
- + 采用容器统一管理对象
- + 支持Facade
- + 注解路由支持
- + 路由跨域请求支持
- + 配置和路由目录独立
- + 取消系统常量
- + 助手函数增强
- + 类库别名机制
- + 增加条件查询
- + 改进查询机制
- + 配置采用二级
- + 依赖注入完善
- + 支持`PSR-3`日志规范
- + 中间件支持(V5.1.6+)
- + Swoole/Workerman支持(V5.1.18+)
-
-
-> ThinkPHP5的运行环境要求PHP5.6以上。
-
-## 安装
-
-使用composer安装
-
-~~~
-composer create-project topthink/think tp
-~~~
-
-启动服务
-
-~~~
-cd tp
-php think run
-~~~
-
-然后就可以在浏览器中访问
-
-~~~
-http://localhost:8000
-~~~
-
-更新框架
-~~~
-composer update topthink/framework
-~~~
-
-
-## 在线手册
-
-+ [完全开发手册](https://www.kancloud.cn/manual/thinkphp5_1/content)
-+ [升级指导](https://www.kancloud.cn/manual/thinkphp5_1/354155)
-
-## 目录结构
-
-初始的目录结构如下:
-
-~~~
-www WEB部署目录(或者子目录)
-├─application 应用目录
-│ ├─common 公共模块目录(可以更改)
-│ ├─module_name 模块目录
-│ │ ├─common.php 模块函数文件
-│ │ ├─controller 控制器目录
-│ │ ├─model 模型目录
-│ │ ├─view 视图目录
-│ │ └─ ... 更多类库目录
-│ │
-│ ├─command.php 命令行定义文件
-│ ├─common.php 公共函数文件
-│ └─tags.php 应用行为扩展定义文件
-│
-├─config 应用配置目录
-│ ├─module_name 模块配置目录
-│ │ ├─database.php 数据库配置
-│ │ ├─cache 缓存配置
-│ │ └─ ...
-│ │
-│ ├─app.php 应用配置
-│ ├─cache.php 缓存配置
-│ ├─cookie.php Cookie配置
-│ ├─database.php 数据库配置
-│ ├─log.php 日志配置
-│ ├─session.php Session配置
-│ ├─template.php 模板引擎配置
-│ └─trace.php Trace配置
-│
-├─route 路由定义目录
-│ ├─route.php 路由定义
-│ └─... 更多
-│
-├─public WEB目录(对外访问目录)
-│ ├─index.php 入口文件
-│ ├─router.php 快速测试文件
-│ └─.htaccess 用于apache的重写
-│
-├─thinkphp 框架系统目录
-│ ├─lang 语言文件目录
-│ ├─library 框架类库目录
-│ │ ├─think Think类库包目录
-│ │ └─traits 系统Trait目录
-│ │
-│ ├─tpl 系统模板目录
-│ ├─base.php 基础定义文件
-│ ├─console.php 控制台入口文件
-│ ├─convention.php 框架惯例配置文件
-│ ├─helper.php 助手函数文件
-│ ├─phpunit.xml phpunit配置文件
-│ └─start.php 框架入口文件
-│
-├─extend 扩展类库目录
-├─runtime 应用的运行时目录(可写,可定制)
-├─vendor 第三方类库目录(Composer依赖库)
-├─build.php 自动生成定义文件(参考)
-├─composer.json composer 定义文件
-├─LICENSE.txt 授权说明文件
-├─README.md README 文件
-├─think 命令行入口文件
-~~~
-
-> 可以使用php自带webserver快速测试
-> 切换到根目录后,启动命令:php think run
-
-## 命名规范
-
-`ThinkPHP5`遵循PSR-2命名规范和PSR-4自动加载规范,并且注意如下规范:
-
-### 目录和文件
-
-* 目录不强制规范,驼峰和小写+下划线模式均支持;
-* 类库、函数文件统一以`.php`为后缀;
-* 类的文件名均以命名空间定义,并且命名空间的路径和类库文件所在路径一致;
-* 类名和类文件名保持一致,统一采用驼峰法命名(首字母大写);
-
-### 函数和类、属性命名
-
-* 类的命名采用驼峰法,并且首字母大写,例如 `User`、`UserType`,默认不需要添加后缀,例如`UserController`应该直接命名为`User`;
-* 函数的命名使用小写字母和下划线(小写字母开头)的方式,例如 `get_client_ip`;
-* 方法的命名使用驼峰法,并且首字母小写,例如 `getUserName`;
-* 属性的命名使用驼峰法,并且首字母小写,例如 `tableName`、`instance`;
-* 以双下划线“__”打头的函数或方法作为魔法方法,例如 `__call` 和 `__autoload`;
-
-### 常量和配置
-
-* 常量以大写字母和下划线命名,例如 `APP_PATH`和 `THINK_PATH`;
-* 配置参数以小写字母和下划线命名,例如 `url_route_on` 和`url_convert`;
-
-### 数据表和字段
-
-* 数据表和字段采用小写加下划线方式命名,并注意字段名不要以下划线开头,例如 `think_user` 表和 `user_name`字段,不建议使用驼峰和中文作为数据表字段命名。
-
-## 参与开发
-
-请参阅 [ThinkPHP5 核心框架包](https://github.com/top-think/framework)。
-
-## 版权信息
-
-ThinkPHP遵循Apache2开源协议发布,并提供免费使用。
-
-本项目包含的第三方源码和二进制文件之版权信息另行标注。
-
-版权所有Copyright © 2006-2018 by ThinkPHP (http://thinkphp.cn)
-
-All rights reserved。
-
-ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
-
-更多细节参阅 [LICENSE.txt](LICENSE.txt)
diff --git a/Server/README_moments.md b/Server/README_moments.md
deleted file mode 100755
index 9e31075d..00000000
--- a/Server/README_moments.md
+++ /dev/null
@@ -1,147 +0,0 @@
-# 微信朋友圈数据处理功能
-
-本模块提供了微信朋友圈数据的获取、存储和查询功能,支持保留驼峰命名结构的原始数据。
-
-## 数据库表结构
-
-项目包含一个数据表:
-
-**wechat_moments** - 存储朋友圈基本信息
-- `id`: 自增主键
-- `wechatAccountId`: 微信账号ID
-- `wechatFriendId`: 微信好友ID
-- `snsId`: 朋友圈消息ID
-- `commentList`: 评论列表JSON
-- `createTime`: 创建时间戳
-- `likeList`: 点赞列表JSON
-- `content`: 朋友圈内容
-- `lat`: 纬度
-- `lng`: 经度
-- `location`: 位置信息
-- `picSize`: 图片大小
-- `resUrls`: 资源URL列表
-- `userName`: 用户名
-- `type`: 朋友圈类型
-- `create_time`: 数据创建时间
-- `update_time`: 数据更新时间
-
-## API接口
-
-### 1. 获取朋友圈信息
-
-```
-GET/POST /api/websocket/getMoments
-```
-
-**参数:**
-- `wechatAccountId`: 微信账号ID
-- `wechatFriendId`: 微信好友ID
-- `count`: 获取条数,默认5条
-
-获取指定账号和好友的朋友圈信息,并自动保存到数据库。
-
-### 2. 保存单条朋友圈数据
-
-```
-POST /api/websocket/saveSingleMoment
-```
-
-**参数:**
-- `commentList`: 评论列表
-- `createTime`: 创建时间戳
-- `likeList`: 点赞列表
-- `momentEntity`: 朋友圈实体,包含以下字段:
- - `content`: 朋友圈内容
- - `lat`: 纬度
- - `lng`: 经度
- - `location`: 位置信息
- - `picSize`: 图片大小
- - `resUrls`: 资源URL列表
- - `urls`: 媒体URL列表
- - `userName`: 用户名
-- `snsId`: 朋友圈ID
-- `type`: 朋友圈类型
-- `wechatAccountId`: 微信账号ID
-- `wechatFriendId`: 微信好友ID
-
-保存单条朋友圈数据到数据库,保持原有的驼峰数据结构。系统会将`momentEntity`中的字段提取并单独存储,不包括`objectType`和`createTime`字段。
-
-### 3. 获取朋友圈数据列表
-
-```
-GET/POST /api/websocket/getMomentsList
-```
-
-**参数:**
-- `wechatAccountId`: 微信账号ID (可选)
-- `wechatFriendId`: 微信好友ID (可选)
-- `page`: 页码,默认1
-- `pageSize`: 每页条数,默认10
-- `startTime`: 开始时间戳 (可选)
-- `endTime`: 结束时间戳 (可选)
-
-获取已保存的朋友圈数据列表,支持分页和条件筛选。返回的数据会自动构建`momentEntity`字段以保持API兼容性。
-
-### 4. 获取朋友圈详情
-
-```
-GET/POST /api/websocket/getMomentDetail
-```
-
-**参数:**
-- `snsId`: 朋友圈ID
-- `wechatAccountId`: 微信账号ID
-
-获取单条朋友圈的详细信息,包括评论、点赞和资源URL等。返回的数据会自动构建`momentEntity`字段以保持API兼容性。
-
-## 使用示例
-
-### 保存单条朋友圈数据
-
-```php
-$data = [
- 'commentList' => [],
- 'createTime' => 1742777232,
- 'likeList' => [],
- 'momentEntity' => [
- 'content' => "第一位个人与Stussy联名的中国名人,不是陈冠希,不是葛民辉,而是周杰伦!",
- 'lat' => 0,
- 'lng' => 0,
- 'location' => "",
- 'picSize' => 0,
- 'resUrls' => [],
- 'snsId' => "-3827269039168736643",
- 'urls' => ["http://wxapp.tc.qq.com/251/20304/stodownload?encfilekey=..."],
- 'userName' => "wxid_afixeeh53lt012"
- ],
- 'snsId' => "-3827269039168736643",
- 'type' => 28,
- 'wechatAccountId' => 123456, // 替换为实际的微信账号ID
- 'wechatFriendId' => "wxid_example" // 替换为实际的微信好友ID
-];
-
-// 发送请求
-$result = curl_post('/api/websocket/saveSingleMoment', $data);
-```
-
-### 查询朋友圈列表
-
-```php
-// 获取特定账号的朋友圈
-$params = [
- 'wechatAccountId' => 123456,
- 'page' => 1,
- 'pageSize' => 20
-];
-
-// 发送请求
-$result = curl_get('/api/websocket/getMomentsList', $params);
-```
-
-## 注意事项
-
-1. 所有JSON格式的数据在保存时都会进行编码,查询时会自动解码并还原为原始数据结构。
-2. 数据库中的字段名保持驼峰命名格式,与微信API返回的数据结构保持一致。
-3. 尽管数据库中将`momentEntity`的字段拆分为独立字段存储,但API接口返回时会重新构建`momentEntity`结构,以保持与原始API的兼容性。
-4. `objectType`和`createTime`字段已从`momentEntity`中移除,不再单独存储。
-5. 图片或视频资源URLs直接存储在朋友圈主表中,不再单独存储到资源表。
\ No newline at end of file
diff --git a/Server/README_wechat_chatroom_sync.md b/Server/README_wechat_chatroom_sync.md
deleted file mode 100755
index fb2e1868..00000000
--- a/Server/README_wechat_chatroom_sync.md
+++ /dev/null
@@ -1,105 +0,0 @@
-# 微信群聊同步功能
-
-本功能用于自动同步微信群聊数据,支持分页获取群聊列表以及群成员信息,并将数据保存到数据库中。
-
-## 功能特点
-
-1. 支持分页获取微信群聊列表
-2. 自动获取每个群聊的成员信息
-3. 支持通过关键词筛选群聊
-4. 支持按微信账号筛选群聊
-5. 可选择是否包含已删除的群聊
-6. 使用队列处理,支持大量数据的同步
-7. 支持失败重试机制
-8. 提供命令行和HTTP接口两种触发方式
-
-## 数据表结构
-
-本功能使用以下数据表:
-
-1. **wechat_chatroom** - 存储微信群聊信息
-2. **wechat_chatroom_member** - 存储微信群聊成员信息
-
-## 使用方法
-
-### 1. HTTP接口触发
-
-```
-GET/POST /api/wechat_chatroom/syncChatrooms
-```
-
-**参数:**
-- `pageIndex`: 起始页码,默认0
-- `pageSize`: 每页大小,默认100
-- `keyword`: 群名关键词,可选
-- `wechatAccountKeyword`: 微信账号关键词,可选
-- `isDeleted`: 是否包含已删除群聊,可选
-
-**示例:**
-```
-/api/wechat_chatroom/syncChatrooms?pageSize=50
-```
-
-### 2. 命令行触发
-
-```bash
-php think sync:wechat:chatrooms [选项]
-```
-
-**选项:**
-- `-p, --pageIndex`: 起始页码,默认0
-- `-s, --pageSize`: 每页大小,默认100
-- `-k, --keyword`: 群名关键词,可选
-- `-a, --account`: 微信账号关键词,可选
-- `-d, --deleted`: 是否包含已删除群聊,可选
-
-**示例:**
-```bash
-# 基本用法
-php think sync:wechat:chatrooms
-
-# 指定页大小和关键词
-php think sync:wechat:chatrooms -s 50 -k "测试群"
-
-# 指定账号关键词
-php think sync:wechat:chatrooms --account "张三"
-```
-
-### 3. 定时任务配置
-
-可以将命令添加到系统的定时任务(crontab)中,实现定期自动同步:
-
-```
-# 每天凌晨3点执行微信群聊同步
-0 3 * * * cd /path/to/your/project && php think sync:wechat:chatrooms
-```
-
-## 队列消费者配置
-
-为了处理同步任务,需要启动队列消费者:
-
-```bash
-# 启动微信群聊队列消费者
-php think queue:work --queue wechat_chatrooms
-```
-
-建议在生产环境中使用supervisor等工具来管理队列消费者进程。
-
-## 同步过程
-
-1. 触发同步任务,将初始页任务加入队列
-2. 队列消费者处理任务,获取当前页的群聊列表
-3. 如果当前页有数据且数量等于页大小,则将下一页任务加入队列
-4. 对每个获取到的群聊,添加获取群成员的任务
-5. 所有数据会自动保存到数据库中
-
-## 调试与日志
-
-同步过程的日志会记录在应用的日志目录中,可以通过查看日志了解同步状态和错误信息。
-
-## 注意事项
-
-1. 页大小建议设置为合理值(50-100),过大会导致请求超时
-2. 当数据量较大时,建议增加队列消费者的数量
-3. 确保系统授权信息正确,否则无法获取数据
-4. 数据同步是增量的,会自动更新已存在的记录
\ No newline at end of file
diff --git a/Server/application/devices/config/route.php b/Server/application/devices/config/route.php
index 58190466..05de555b 100755
--- a/Server/application/devices/config/route.php
+++ b/Server/application/devices/config/route.php
@@ -23,6 +23,7 @@ Route::group('v1/', function () {
// 设备微信相关
Route::group('device/wechats', function () {
+ Route::get('friends', 'app\\devices\\controller\\DeviceWechat@getFriends'); // 获取微信好友列表
Route::get('count', 'app\\devices\\controller\\DeviceWechat@count'); // 获取在线微信账号数量
Route::get('device-count', 'app\\devices\\controller\\DeviceWechat@deviceCount'); // 获取有登录微信的设备数量
Route::get('', 'app\\devices\\controller\\DeviceWechat@index'); // 获取在线微信账号列表
diff --git a/Server/application/devices/controller/DeviceWechat.php b/Server/application/devices/controller/DeviceWechat.php
index 49d816f0..545f4706 100755
--- a/Server/application/devices/controller/DeviceWechat.php
+++ b/Server/application/devices/controller/DeviceWechat.php
@@ -274,23 +274,6 @@ class DeviceWechat extends Controller
]
];
- // 获取微信好友列表
- $friends = Db::table('tk_wechat_friend')
- ->where('wechatAccountId', $id)
- ->where('isDeleted', 0)
- ->field([
- 'id',
- 'wechatId',
- 'nickname',
- 'avatar',
- 'gender',
- 'region',
- 'signature',
- 'labels',
- 'createTime'
- ])
- ->select();
-
// 处理返回数据
$data = [
'basicInfo' => [
@@ -322,7 +305,6 @@ class DeviceWechat extends Controller
'lastUpdateTime' => $wechat['updateTime']
],
'restrictions' => $restrictions,
- 'friends' => $friends
];
return json([
@@ -528,4 +510,73 @@ class DeviceWechat extends Controller
]);
}
}
+
+ /**
+ * 获取微信好友列表
+ * 根据wechatId查询微信好友,支持分页和关键词筛选
+ *
+ * @return \think\response\Json
+ */
+ public function getFriends()
+ {
+ try {
+ // 获取请求参数
+ $wechatId = Request::param('wechatId');
+ $page = (int)Request::param('page', 1);
+ $limit = (int)Request::param('limit', 20);
+ $keyword = Request::param('keyword', '');
+
+ // 参数验证
+ if (empty($wechatId)) {
+ return json([
+ 'code' => 400,
+ 'msg' => '参数错误:微信ID不能为空'
+ ]);
+ }
+
+ // 查询参数
+ $params = [];
+ if (!empty($keyword)) {
+ $params['keyword'] = $keyword;
+ }
+
+ // 调用模型方法获取好友列表
+ $result = \app\devices\model\WechatFriend::getFriendsByWechatId($wechatId, $params, $page, $limit);
+
+
+
+ // 处理返回的数据
+ $friendsList = [];
+ foreach ($result['list'] as $friend) {
+ $friendsList[] = [
+ 'wechatId' => $friend['wechatId'],
+ 'avatar' => $friend['avatar'] ?: '/placeholder.svg',
+ 'labels' => $friend['labels'] ?: [],
+ 'accountNickname' => $friend['accountNickname'] ?: '',
+ 'accountRealName' => $friend['accountRealName'] ?: '',
+ 'nickname' => $friend['nickname'] ?: '',
+ 'remark' => $friend['conRemark'] ?: '',
+ 'alias' => $friend['alias'] ?: '',
+ 'gender' => $friend['gender'] ?: 0,
+ 'region' => $friend['region'] ?: ''
+ ];
+ }
+
+ return json([
+ 'code' => 200,
+ 'msg' => '获取成功',
+ 'data' => [
+ 'total' => $result['total'],
+ 'page' => $result['page'],
+ 'limit' => $result['limit'],
+ 'list' => $friendsList
+ ]
+ ]);
+ } catch (\Exception $e) {
+ return json([
+ 'code' => 500,
+ 'msg' => '获取失败:' . $e->getMessage()
+ ]);
+ }
+ }
}
\ No newline at end of file
diff --git a/Server/application/devices/model/WechatFriend.php b/Server/application/devices/model/WechatFriend.php
new file mode 100644
index 00000000..c166d922
--- /dev/null
+++ b/Server/application/devices/model/WechatFriend.php
@@ -0,0 +1,84 @@
+ 'integer',
+ 'wechatAccountId' => 'integer',
+ 'gender' => 'integer',
+ 'addFrom' => 'integer',
+ 'isDeleted' => 'integer',
+ 'isPassed' => 'integer',
+ 'accountId' => 'integer',
+ 'groupId' => 'integer',
+ 'labels' => 'json',
+ 'deleteTime' => 'datetime',
+ 'passTime' => 'datetime',
+ 'createTime' => 'datetime'
+ ];
+
+ /**
+ * 根据微信账号ID获取好友列表
+ *
+ * @param string $ownerWechatId 所有者微信ID
+ * @param array $params 查询条件参数
+ * @param int $page 页码
+ * @param int $limit 每页数量
+ * @return array 好友列表和总数
+ */
+ public static function getFriendsByWechatId($ownerWechatId, $params = [], $page = 1, $limit = 20)
+ {
+ // 构建基础查询
+ $query = self::where('ownerWechatId', $ownerWechatId)
+ ->where('isDeleted', 0);
+
+ // 添加筛选条件(昵称、备注、微信号、标签)
+ if (!empty($params['keyword'])) {
+ $keyword = $params['keyword'];
+ $query->where(function($q) use ($keyword) {
+ $q->whereOr('nickname', 'like', "%{$keyword}%")
+ ->whereOr('conRemark', 'like', "%{$keyword}%")
+ ->whereOr('alias', 'like', "%{$keyword}%")
+ ->whereOr("JSON_SEARCH(labels, 'one', '%{$keyword}%') IS NOT NULL");
+ });
+ }
+
+ // 计算总数
+ $total = $query->count();
+
+ // 分页查询数据
+ $friends = $query->page($page, $limit)
+ ->order('createTime desc')
+ ->field('wechatId, alias, avatar, labels, accountNickname, accountRealName, nickname, conRemark, gender, region')
+ ->select();
+
+ return [
+ 'list' => $friends,
+ 'total' => $total,
+ 'page' => $page,
+ 'limit' => $limit
+ ];
+ }
+}
\ No newline at end of file
diff --git a/Server/thinkphp/.gitignore b/Server/thinkphp/.gitignore
new file mode 100644
index 00000000..2b671b8e
--- /dev/null
+++ b/Server/thinkphp/.gitignore
@@ -0,0 +1,7 @@
+composer.phar
+composer.lock
+.DS_Store
+Thumbs.db
+/phpunit.xml
+/.idea
+/.vscode
\ No newline at end of file
diff --git a/Server/thinkphp/.htaccess b/Server/thinkphp/.htaccess
new file mode 100644
index 00000000..3418e55a
--- /dev/null
+++ b/Server/thinkphp/.htaccess
@@ -0,0 +1 @@
+deny from all
\ No newline at end of file
diff --git a/Server/thinkphp/CONTRIBUTING.md b/Server/thinkphp/CONTRIBUTING.md
new file mode 100644
index 00000000..6cefcb38
--- /dev/null
+++ b/Server/thinkphp/CONTRIBUTING.md
@@ -0,0 +1,119 @@
+如何贡献我的源代码
+===
+
+此文档介绍了 ThinkPHP 团队的组成以及运转机制,您提交的代码将给 ThinkPHP 项目带来什么好处,以及如何才能加入我们的行列。
+
+## 通过 Github 贡献代码
+
+ThinkPHP 目前使用 Git 来控制程序版本,如果你想为 ThinkPHP 贡献源代码,请先大致了解 Git 的使用方法。我们目前把项目托管在 GitHub 上,任何 GitHub 用户都可以向我们贡献代码。
+
+参与的方式很简单,`fork`一份 ThinkPHP 的代码到你的仓库中,修改后提交,并向我们发起`pull request`申请,我们会及时对代码进行审查并处理你的申请并。审查通过后,你的代码将被`merge`进我们的仓库中,这样你就会自动出现在贡献者名单里了,非常方便。
+
+我们希望你贡献的代码符合:
+
+* ThinkPHP 的编码规范
+* 适当的注释,能让其他人读懂
+* 遵循 Apache2 开源协议
+
+**如果想要了解更多细节或有任何疑问,请继续阅读下面的内容**
+
+### 注意事项
+
+* 本项目代码格式化标准选用 [**PSR-2**](http://www.kancloud.cn/thinkphp/php-fig-psr/3141);
+* 类名和类文件名遵循 [**PSR-4**](http://www.kancloud.cn/thinkphp/php-fig-psr/3144);
+* 对于 Issues 的处理,请使用诸如 `fix #xxx(Issue ID)` 的 commit title 直接关闭 issue。
+* 系统会自动在 PHP 5.4 5.5 5.6 7.0 和 HHVM 上测试修改,其中 HHVM 下的测试容许报错,请确保你的修改符合 PHP 5.4 ~ 5.6 和 PHP 7.0 的语法规范;
+* 管理员不会合并造成 CI faild 的修改,若出现 CI faild 请检查自己的源代码或修改相应的[单元测试文件](tests);
+
+## GitHub Issue
+
+GitHub 提供了 Issue 功能,该功能可以用于:
+
+* 提出 bug
+* 提出功能改进
+* 反馈使用体验
+
+该功能不应该用于:
+
+ * 提出修改意见(涉及代码署名和修订追溯问题)
+ * 不友善的言论
+
+## 快速修改
+
+**GitHub 提供了快速编辑文件的功能**
+
+1. 登录 GitHub 帐号;
+2. 浏览项目文件,找到要进行修改的文件;
+3. 点击右上角铅笔图标进行修改;
+4. 填写 `Commit changes` 相关内容(Title 必填);
+5. 提交修改,等待 CI 验证和管理员合并。
+
+**若您需要一次提交大量修改,请继续阅读下面的内容**
+
+## 完整流程
+
+1. `fork`本项目;
+2. 克隆(`clone`)你 `fork` 的项目到本地;
+3. 新建分支(`branch`)并检出(`checkout`)新分支;
+4. 添加本项目到你的本地 git 仓库作为上游(`upstream`);
+5. 进行修改,若你的修改包含方法或函数的增减,请记得修改[单元测试文件](tests);
+6. 变基(衍合 `rebase`)你的分支到上游 master 分支;
+7. `push` 你的本地仓库到 GitHub;
+8. 提交 `pull request`;
+9. 等待 CI 验证(若不通过则重复 5~7,GitHub 会自动更新你的 `pull request`);
+10. 等待管理员处理,并及时 `rebase` 你的分支到上游 master 分支(若上游 master 分支有修改)。
+
+*若有必要,可以 `git push -f` 强行推送 rebase 后的分支到自己的 `fork`*
+
+*绝对不可以使用 `git push -f` 强行推送修改到上游*
+
+### 注意事项
+
+* 若对上述流程有任何不清楚的地方,请查阅 GIT 教程,如 [这个](http://backlogtool.com/git-guide/cn/);
+* 对于代码**不同方面**的修改,请在自己 `fork` 的项目中**创建不同的分支**(原因参见`完整流程`第9条备注部分);
+* 变基及交互式变基操作参见 [Git 交互式变基](http://pakchoi.me/2015/03/17/git-interactive-rebase/)
+
+## 推荐资源
+
+### 开发环境
+
+* XAMPP for Windows 5.5.x
+* WampServer (for Windows)
+* upupw Apache PHP5.4 ( for Windows)
+
+或自行安装
+
+- Apache / Nginx
+- PHP 5.4 ~ 5.6
+- MySQL / MariaDB
+
+*Windows 用户推荐添加 PHP bin 目录到 PATH,方便使用 composer*
+
+*Linux 用户自行配置环境, Mac 用户推荐使用内置 Apache 配合 Homebrew 安装 PHP 和 MariaDB*
+
+### 编辑器
+
+Sublime Text 3 + phpfmt 插件
+
+phpfmt 插件参数
+
+```json
+{
+ "autocomplete": true,
+ "enable_auto_align": true,
+ "format_on_save": true,
+ "indent_with_space": true,
+ "psr1_naming": false,
+ "psr2": true,
+ "version": 4
+}
+```
+
+或其他 编辑器 / IDE 配合 PSR2 自动格式化工具
+
+### Git GUI
+
+* SourceTree
+* GitHub Desktop
+
+或其他 Git 图形界面客户端
diff --git a/Server/LICENSE.txt b/Server/thinkphp/LICENSE.txt
old mode 100755
new mode 100644
similarity index 100%
rename from Server/LICENSE.txt
rename to Server/thinkphp/LICENSE.txt
diff --git a/Server/thinkphp/README.md b/Server/thinkphp/README.md
new file mode 100644
index 00000000..1339e6c7
--- /dev/null
+++ b/Server/thinkphp/README.md
@@ -0,0 +1,99 @@
+
+
+ThinkPHP 5.1(LTS) —— 12载初心,你值得信赖的PHP框架
+===============
+
+[](https://scrutinizer-ci.com/g/top-think/framework/?branch=5.1)
+[](https://travis-ci.org/top-think/framework)
+[](https://packagist.org/packages/topthink/framework)
+[](https://packagist.org/packages/topthink/framework)
+[](http://www.php.net/)
+[](https://packagist.org/packages/topthink/framework)
+
+ThinkPHP5.1对底层架构做了进一步的改进,减少依赖,其主要特性包括:
+
+ + 采用容器统一管理对象
+ + 支持Facade
+ + 更易用的路由
+ + 注解路由支持
+ + 路由跨域请求支持
+ + 验证类增强
+ + 配置和路由目录独立
+ + 取消系统常量
+ + 类库别名机制
+ + 模型和数据库增强
+ + 依赖注入完善
+ + 支持PSR-3日志规范
+ + 中间件支持(`V5.1.6+`)
+ + 支持`Swoole`/`Workerman`运行(`V5.1.18+`)
+
+官方已经正式宣布`5.1.27`版本为LTS版本。
+
+### 废除的功能:
+
+ + 聚合模型
+ + 内置控制器扩展类
+ + 模型自动验证
+
+> ThinkPHP5.1的运行环境要求PHP5.6+ 兼容PHP8.0。
+
+
+## 安装
+
+使用composer安装
+
+~~~
+composer create-project topthink/think tp
+~~~
+
+启动服务
+
+~~~
+cd tp
+php think run
+~~~
+
+然后就可以在浏览器中访问
+
+~~~
+http://localhost:8000
+~~~
+
+更新框架
+~~~
+composer update topthink/framework
+~~~
+
+
+## 在线手册
+
++ [完全开发手册](https://www.kancloud.cn/manual/thinkphp5_1/content)
++ [升级指导](https://www.kancloud.cn/manual/thinkphp5_1/354155)
+
+
+## 官方服务
+
++ [应用服务市场](https://market.topthink.com/)
++ [ThinkAPI——统一API服务](https://docs.topthink.com/think-api)
+
+## 命名规范
+
+`ThinkPHP5.1`遵循PSR-2命名规范和PSR-4自动加载规范。
+
+## 参与开发
+
+请参阅 [ThinkPHP5 核心框架包](https://github.com/top-think/framework)。
+
+## 版权信息
+
+ThinkPHP遵循Apache2开源协议发布,并提供免费使用。
+
+本项目包含的第三方源码和二进制文件之版权信息另行标注。
+
+版权所有Copyright © 2006-2018 by ThinkPHP (http://thinkphp.cn)
+
+All rights reserved。
+
+ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
+
+更多细节参阅 [LICENSE.txt](LICENSE.txt)
diff --git a/Server/thinkphp/base.php b/Server/thinkphp/base.php
new file mode 100644
index 00000000..d7238cc6
--- /dev/null
+++ b/Server/thinkphp/base.php
@@ -0,0 +1,52 @@
+
+// +----------------------------------------------------------------------
+namespace think;
+
+// 载入Loader类
+require __DIR__ . '/library/think/Loader.php';
+
+// 注册自动加载
+Loader::register();
+
+// 注册错误和异常处理机制
+Error::register();
+
+// 实现日志接口
+if (interface_exists('Psr\Log\LoggerInterface')) {
+ interface LoggerInterface extends \Psr\Log\LoggerInterface
+ {}
+} else {
+ interface LoggerInterface
+ {}
+}
+
+// 注册类库别名
+Loader::addClassAlias([
+ 'App' => facade\App::class,
+ 'Build' => facade\Build::class,
+ 'Cache' => facade\Cache::class,
+ 'Config' => facade\Config::class,
+ 'Cookie' => facade\Cookie::class,
+ 'Db' => Db::class,
+ 'Debug' => facade\Debug::class,
+ 'Env' => facade\Env::class,
+ 'Facade' => Facade::class,
+ 'Hook' => facade\Hook::class,
+ 'Lang' => facade\Lang::class,
+ 'Log' => facade\Log::class,
+ 'Request' => facade\Request::class,
+ 'Response' => facade\Response::class,
+ 'Route' => facade\Route::class,
+ 'Session' => facade\Session::class,
+ 'Url' => facade\Url::class,
+ 'Validate' => facade\Validate::class,
+ 'View' => facade\View::class,
+]);
diff --git a/Server/thinkphp/composer.json b/Server/thinkphp/composer.json
new file mode 100644
index 00000000..33477b1d
--- /dev/null
+++ b/Server/thinkphp/composer.json
@@ -0,0 +1,35 @@
+{
+ "name": "topthink/framework",
+ "description": "the new thinkphp framework",
+ "type": "think-framework",
+ "keywords": [
+ "framework",
+ "thinkphp",
+ "ORM"
+ ],
+ "homepage": "http://thinkphp.cn/",
+ "license": "Apache-2.0",
+ "authors": [
+ {
+ "name": "liu21st",
+ "email": "liu21st@gmail.com"
+ },
+ {
+ "name": "yunwuxin",
+ "email": "448901948@qq.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.6.0",
+ "topthink/think-installer": "2.*"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5.0|^6.0",
+ "johnkary/phpunit-speedtrap": "^1.0",
+ "mikey179/vfsstream": "~1.6",
+ "phploc/phploc": "2.*",
+ "sebastian/phpcpd": "2.*",
+ "squizlabs/php_codesniffer": "2.*",
+ "phpdocumentor/reflection-docblock": "^2.0"
+ }
+}
diff --git a/Server/thinkphp/convention.php b/Server/thinkphp/convention.php
new file mode 100644
index 00000000..1d85e56e
--- /dev/null
+++ b/Server/thinkphp/convention.php
@@ -0,0 +1,327 @@
+ [
+ // 应用名称
+ 'app_name' => '',
+ // 应用地址
+ 'app_host' => '',
+ // 应用调试模式
+ 'app_debug' => false,
+ // 应用Trace
+ 'app_trace' => false,
+ // 应用模式状态
+ 'app_status' => '',
+ // 是否HTTPS
+ 'is_https' => false,
+ // 入口自动绑定模块
+ 'auto_bind_module' => false,
+ // 注册的根命名空间
+ 'root_namespace' => [],
+ // 默认输出类型
+ 'default_return_type' => 'html',
+ // 默认AJAX 数据返回格式,可选json xml ...
+ 'default_ajax_return' => 'json',
+ // 默认JSONP格式返回的处理方法
+ 'default_jsonp_handler' => 'jsonpReturn',
+ // 默认JSONP处理方法
+ 'var_jsonp_handler' => 'callback',
+ // 默认时区
+ 'default_timezone' => 'Asia/Shanghai',
+ // 是否开启多语言
+ 'lang_switch_on' => false,
+ // 默认验证器
+ 'default_validate' => '',
+ // 默认语言
+ 'default_lang' => 'zh-cn',
+
+ // +----------------------------------------------------------------------
+ // | 模块设置
+ // +----------------------------------------------------------------------
+
+ // 自动搜索控制器
+ 'controller_auto_search' => false,
+ // 操作方法前缀
+ 'use_action_prefix' => false,
+ // 操作方法后缀
+ 'action_suffix' => '',
+ // 默认的空控制器名
+ 'empty_controller' => 'Error',
+ // 默认的空模块名
+ 'empty_module' => '',
+ // 默认模块名
+ 'default_module' => 'index',
+ // 是否支持多模块
+ 'app_multi_module' => true,
+ // 禁止访问模块
+ 'deny_module_list' => ['common'],
+ // 默认控制器名
+ 'default_controller' => 'Index',
+ // 默认操作名
+ 'default_action' => 'index',
+ // 是否自动转换URL中的控制器和操作名
+ 'url_convert' => true,
+ // 默认的访问控制器层
+ 'url_controller_layer' => 'controller',
+ // 应用类库后缀
+ 'class_suffix' => false,
+ // 控制器类后缀
+ 'controller_suffix' => false,
+
+ // +----------------------------------------------------------------------
+ // | URL请求设置
+ // +----------------------------------------------------------------------
+
+ // 默认全局过滤方法 用逗号分隔多个
+ 'default_filter' => '',
+ // PATHINFO变量名 用于兼容模式
+ 'var_pathinfo' => 's',
+ // 兼容PATH_INFO获取
+ 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
+ // HTTPS代理标识
+ 'https_agent_name' => '',
+ // IP代理获取标识
+ 'http_agent_ip' => 'HTTP_X_REAL_IP',
+ // URL伪静态后缀
+ 'url_html_suffix' => 'html',
+ // 域名根,如thinkphp.cn
+ 'url_domain_root' => '',
+ // 表单请求类型伪装变量
+ 'var_method' => '_method',
+ // 表单ajax伪装变量
+ 'var_ajax' => '_ajax',
+ // 表单pjax伪装变量
+ 'var_pjax' => '_pjax',
+ // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则
+ 'request_cache' => false,
+ // 请求缓存有效期
+ 'request_cache_expire' => null,
+ // 全局请求缓存排除规则
+ 'request_cache_except' => [],
+
+ // +----------------------------------------------------------------------
+ // | 路由设置
+ // +----------------------------------------------------------------------
+
+ // pathinfo分隔符
+ 'pathinfo_depr' => '/',
+ // URL普通方式参数 用于自动生成
+ 'url_common_param' => false,
+ // URL参数方式 0 按名称成对解析 1 按顺序解析
+ 'url_param_type' => 0,
+ // 是否开启路由延迟解析
+ 'url_lazy_route' => false,
+ // 是否强制使用路由
+ 'url_route_must' => false,
+ // 合并路由规则
+ 'route_rule_merge' => false,
+ // 路由是否完全匹配
+ 'route_complete_match' => false,
+ // 使用注解路由
+ 'route_annotation' => false,
+ // 默认的路由变量规则
+ 'default_route_pattern' => '\w+',
+ // 是否开启路由缓存
+ 'route_check_cache' => false,
+ // 路由缓存的Key自定义设置(闭包),默认为当前URL和请求类型的md5
+ 'route_check_cache_key' => '',
+ // 路由缓存的设置
+ 'route_cache_option' => [],
+
+ // +----------------------------------------------------------------------
+ // | 异常及错误设置
+ // +----------------------------------------------------------------------
+
+ // 默认跳转页面对应的模板文件
+ 'dispatch_success_tmpl' => __DIR__ . '/tpl/dispatch_jump.tpl',
+ 'dispatch_error_tmpl' => __DIR__ . '/tpl/dispatch_jump.tpl',
+ // 异常页面的模板文件
+ 'exception_tmpl' => __DIR__ . '/tpl/think_exception.tpl',
+ // 错误显示信息,非调试模式有效
+ 'error_message' => '页面错误!请稍后再试~',
+ // 显示错误信息
+ 'show_error_msg' => false,
+ // 异常处理handle类 留空使用 \think\exception\Handle
+ 'exception_handle' => '',
+ ],
+
+ // +----------------------------------------------------------------------
+ // | 模板设置
+ // +----------------------------------------------------------------------
+
+ 'template' => [
+ // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写
+ 'auto_rule' => 1,
+ // 模板引擎类型 支持 php think 支持扩展
+ 'type' => 'Think',
+ // 视图基础目录,配置目录为所有模块的视图起始目录
+ 'view_base' => '',
+ // 当前模板的视图目录 留空为自动获取
+ 'view_path' => '',
+ // 模板后缀
+ 'view_suffix' => 'html',
+ // 模板文件名分隔符
+ 'view_depr' => DIRECTORY_SEPARATOR,
+ // 模板引擎普通标签开始标记
+ 'tpl_begin' => '{',
+ // 模板引擎普通标签结束标记
+ 'tpl_end' => '}',
+ // 标签库标签开始标记
+ 'taglib_begin' => '{',
+ // 标签库标签结束标记
+ 'taglib_end' => '}',
+ ],
+
+ // +----------------------------------------------------------------------
+ // | 日志设置
+ // +----------------------------------------------------------------------
+
+ 'log' => [
+ // 日志记录方式,内置 file socket 支持扩展
+ 'type' => 'File',
+ // 日志保存目录
+ //'path' => LOG_PATH,
+ // 日志记录级别
+ 'level' => [],
+ // 是否记录trace信息到日志
+ 'record_trace' => false,
+ // 是否JSON格式记录
+ 'json' => false,
+ ],
+
+ // +----------------------------------------------------------------------
+ // | Trace设置 开启 app_trace 后 有效
+ // +----------------------------------------------------------------------
+
+ 'trace' => [
+ // 内置Html Console 支持扩展
+ 'type' => 'Html',
+ 'file' => __DIR__ . '/tpl/page_trace.tpl',
+ ],
+
+ // +----------------------------------------------------------------------
+ // | 缓存设置
+ // +----------------------------------------------------------------------
+
+ 'cache' => [
+ // 驱动方式
+ 'type' => 'File',
+ // 缓存保存目录
+ //'path' => CACHE_PATH,
+ // 缓存前缀
+ 'prefix' => '',
+ // 缓存有效期 0表示永久缓存
+ 'expire' => 0,
+ ],
+
+ // +----------------------------------------------------------------------
+ // | 会话设置
+ // +----------------------------------------------------------------------
+
+ 'session' => [
+ 'id' => '',
+ // SESSION_ID的提交变量,解决flash上传跨域
+ 'var_session_id' => '',
+ // SESSION 前缀
+ 'prefix' => 'think',
+ // 驱动方式 支持redis memcache memcached
+ 'type' => '',
+ // 是否自动开启 SESSION
+ 'auto_start' => true,
+ 'httponly' => true,
+ 'secure' => false,
+ ],
+
+ // +----------------------------------------------------------------------
+ // | Cookie设置
+ // +----------------------------------------------------------------------
+
+ 'cookie' => [
+ // cookie 名称前缀
+ 'prefix' => '',
+ // cookie 保存时间
+ 'expire' => 0,
+ // cookie 保存路径
+ 'path' => '/',
+ // cookie 有效域名
+ 'domain' => '',
+ // cookie 启用安全传输
+ 'secure' => false,
+ // httponly设置
+ 'httponly' => '',
+ // 是否使用 setcookie
+ 'setcookie' => true,
+ ],
+
+ // +----------------------------------------------------------------------
+ // | 数据库设置
+ // +----------------------------------------------------------------------
+
+ 'database' => [
+ // 数据库类型
+ 'type' => 'mysql',
+ // 数据库连接DSN配置
+ 'dsn' => '',
+ // 服务器地址
+ 'hostname' => '127.0.0.1',
+ // 数据库名
+ 'database' => '',
+ // 数据库用户名
+ 'username' => 'root',
+ // 数据库密码
+ 'password' => '',
+ // 数据库连接端口
+ 'hostport' => '',
+ // 数据库连接参数
+ 'params' => [],
+ // 数据库编码默认采用utf8
+ 'charset' => 'utf8',
+ // 数据库表前缀
+ 'prefix' => '',
+ // 数据库调试模式
+ 'debug' => false,
+ // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
+ 'deploy' => 0,
+ // 数据库读写是否分离 主从式有效
+ 'rw_separate' => false,
+ // 读写分离后 主服务器数量
+ 'master_num' => 1,
+ // 指定从服务器序号
+ 'slave_no' => '',
+ // 是否严格检查字段是否存在
+ 'fields_strict' => true,
+ // 数据集返回类型
+ 'resultset_type' => 'array',
+ // 自动写入时间戳字段
+ 'auto_timestamp' => false,
+ // 时间字段取出后的默认时间格式
+ 'datetime_format' => 'Y-m-d H:i:s',
+ // 是否需要进行SQL性能分析
+ 'sql_explain' => false,
+ // 查询对象
+ 'query' => '\\think\\db\\Query',
+ ],
+
+ //分页配置
+ 'paginate' => [
+ 'type' => 'bootstrap',
+ 'var_page' => 'page',
+ 'list_rows' => 15,
+ ],
+
+ //控制台配置
+ 'console' => [
+ 'name' => 'Think Console',
+ 'version' => '0.1',
+ 'user' => null,
+ 'auto_path' => '',
+ ],
+
+ // 中间件配置
+ 'middleware' => [
+ 'default_namespace' => 'app\\http\\middleware\\',
+ ],
+];
diff --git a/Server/thinkphp/helper.php b/Server/thinkphp/helper.php
new file mode 100644
index 00000000..72b9e9fd
--- /dev/null
+++ b/Server/thinkphp/helper.php
@@ -0,0 +1,726 @@
+
+// +----------------------------------------------------------------------
+
+//------------------------
+// ThinkPHP 助手函数
+//-------------------------
+
+use think\Container;
+use think\Db;
+use think\exception\HttpException;
+use think\exception\HttpResponseException;
+use think\facade\Cache;
+use think\facade\Config;
+use think\facade\Cookie;
+use think\facade\Debug;
+use think\facade\Env;
+use think\facade\Hook;
+use think\facade\Lang;
+use think\facade\Log;
+use think\facade\Request;
+use think\facade\Route;
+use think\facade\Session;
+use think\facade\Url;
+use think\Response;
+use think\route\RuleItem;
+
+if (!function_exists('abort')) {
+ /**
+ * 抛出HTTP异常
+ * @param integer|Response $code 状态码 或者 Response对象实例
+ * @param string $message 错误信息
+ * @param array $header 参数
+ */
+ function abort($code, $message = null, $header = [])
+ {
+ if ($code instanceof Response) {
+ throw new HttpResponseException($code);
+ } else {
+ throw new HttpException($code, $message, null, $header);
+ }
+ }
+}
+
+if (!function_exists('action')) {
+ /**
+ * 调用模块的操作方法 参数格式 [模块/控制器/]操作
+ * @param string $url 调用地址
+ * @param string|array $vars 调用参数 支持字符串和数组
+ * @param string $layer 要调用的控制层名称
+ * @param bool $appendSuffix 是否添加类名后缀
+ * @return mixed
+ */
+ function action($url, $vars = [], $layer = 'controller', $appendSuffix = false)
+ {
+ return app()->action($url, $vars, $layer, $appendSuffix);
+ }
+}
+
+if (!function_exists('app')) {
+ /**
+ * 快速获取容器中的实例 支持依赖注入
+ * @param string $name 类名或标识 默认获取当前应用实例
+ * @param array $args 参数
+ * @param bool $newInstance 是否每次创建新的实例
+ * @return mixed|\think\App
+ */
+ function app($name = 'think\App', $args = [], $newInstance = false)
+ {
+ return Container::get($name, $args, $newInstance);
+ }
+}
+
+if (!function_exists('behavior')) {
+ /**
+ * 执行某个行为(run方法) 支持依赖注入
+ * @param mixed $behavior 行为类名或者别名
+ * @param mixed $args 参数
+ * @return mixed
+ */
+ function behavior($behavior, $args = null)
+ {
+ return Hook::exec($behavior, $args);
+ }
+}
+
+if (!function_exists('bind')) {
+ /**
+ * 绑定一个类到容器
+ * @access public
+ * @param string $abstract 类标识、接口
+ * @param mixed $concrete 要绑定的类、闭包或者实例
+ * @return Container
+ */
+ function bind($abstract, $concrete = null)
+ {
+ return Container::getInstance()->bindTo($abstract, $concrete);
+ }
+}
+
+if (!function_exists('cache')) {
+ /**
+ * 缓存管理
+ * @param mixed $name 缓存名称,如果为数组表示进行缓存设置
+ * @param mixed $value 缓存值
+ * @param mixed $options 缓存参数
+ * @param string $tag 缓存标签
+ * @return mixed
+ */
+ function cache($name, $value = '', $options = null, $tag = null)
+ {
+ if (is_array($options)) {
+ // 缓存操作的同时初始化
+ Cache::connect($options);
+ } elseif (is_array($name)) {
+ // 缓存初始化
+ return Cache::connect($name);
+ }
+
+ if ('' === $value) {
+ // 获取缓存
+ return 0 === strpos($name, '?') ? Cache::has(substr($name, 1)) : Cache::get($name);
+ } elseif (is_null($value)) {
+ // 删除缓存
+ return Cache::rm($name);
+ }
+
+ // 缓存数据
+ if (is_array($options)) {
+ $expire = isset($options['expire']) ? $options['expire'] : null; //修复查询缓存无法设置过期时间
+ } else {
+ $expire = is_numeric($options) ? $options : null; //默认快捷缓存设置过期时间
+ }
+
+ if (is_null($tag)) {
+ return Cache::set($name, $value, $expire);
+ } else {
+ return Cache::tag($tag)->set($name, $value, $expire);
+ }
+ }
+}
+
+if (!function_exists('call')) {
+ /**
+ * 调用反射执行callable 支持依赖注入
+ * @param mixed $callable 支持闭包等callable写法
+ * @param array $args 参数
+ * @return mixed
+ */
+ function call($callable, $args = [])
+ {
+ return Container::getInstance()->invoke($callable, $args);
+ }
+}
+
+if (!function_exists('class_basename')) {
+ /**
+ * 获取类名(不包含命名空间)
+ *
+ * @param string|object $class
+ * @return string
+ */
+ function class_basename($class)
+ {
+ $class = is_object($class) ? get_class($class) : $class;
+ return basename(str_replace('\\', '/', $class));
+ }
+}
+
+if (!function_exists('class_uses_recursive')) {
+ /**
+ *获取一个类里所有用到的trait,包括父类的
+ *
+ * @param $class
+ * @return array
+ */
+ function class_uses_recursive($class)
+ {
+ if (is_object($class)) {
+ $class = get_class($class);
+ }
+
+ $results = [];
+ $classes = array_merge([$class => $class], class_parents($class));
+ foreach ($classes as $class) {
+ $results += trait_uses_recursive($class);
+ }
+
+ return array_unique($results);
+ }
+}
+
+if (!function_exists('config')) {
+ /**
+ * 获取和设置配置参数
+ * @param string|array $name 参数名
+ * @param mixed $value 参数值
+ * @return mixed
+ */
+ function config($name = '', $value = null)
+ {
+ if (is_null($value) && is_string($name)) {
+ if ('.' == substr($name, -1)) {
+ return Config::pull(substr($name, 0, -1));
+ }
+
+ return 0 === strpos($name, '?') ? Config::has(substr($name, 1)) : Config::get($name);
+ } else {
+ return Config::set($name, $value);
+ }
+ }
+}
+
+if (!function_exists('container')) {
+ /**
+ * 获取容器对象实例
+ * @return Container
+ */
+ function container()
+ {
+ return Container::getInstance();
+ }
+}
+
+if (!function_exists('controller')) {
+ /**
+ * 实例化控制器 格式:[模块/]控制器
+ * @param string $name 资源地址
+ * @param string $layer 控制层名称
+ * @param bool $appendSuffix 是否添加类名后缀
+ * @return \think\Controller
+ */
+ function controller($name, $layer = 'controller', $appendSuffix = false)
+ {
+ return app()->controller($name, $layer, $appendSuffix);
+ }
+}
+
+if (!function_exists('cookie')) {
+ /**
+ * Cookie管理
+ * @param string|array $name cookie名称,如果为数组表示进行cookie设置
+ * @param mixed $value cookie值
+ * @param mixed $option 参数
+ * @return mixed
+ */
+ function cookie($name, $value = '', $option = null)
+ {
+ if (is_array($name)) {
+ // 初始化
+ Cookie::init($name);
+ } elseif (is_null($name)) {
+ // 清除
+ Cookie::clear($value);
+ } elseif ('' === $value) {
+ // 获取
+ return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1), $option) : Cookie::get($name);
+ } elseif (is_null($value)) {
+ // 删除
+ return Cookie::delete($name);
+ } else {
+ // 设置
+ return Cookie::set($name, $value, $option);
+ }
+ }
+}
+
+if (!function_exists('db')) {
+ /**
+ * 实例化数据库类
+ * @param string $name 操作的数据表名称(不含前缀)
+ * @param array|string $config 数据库配置参数
+ * @param bool $force 是否强制重新连接
+ * @return \think\db\Query
+ */
+ function db($name = '', $config = [], $force = true)
+ {
+ return Db::connect($config, $force)->name($name);
+ }
+}
+
+if (!function_exists('debug')) {
+ /**
+ * 记录时间(微秒)和内存使用情况
+ * @param string $start 开始标签
+ * @param string $end 结束标签
+ * @param integer|string $dec 小数位 如果是m 表示统计内存占用
+ * @return mixed
+ */
+ function debug($start, $end = '', $dec = 6)
+ {
+ if ('' == $end) {
+ Debug::remark($start);
+ } else {
+ return 'm' == $dec ? Debug::getRangeMem($start, $end) : Debug::getRangeTime($start, $end, $dec);
+ }
+ }
+}
+
+if (!function_exists('download')) {
+ /**
+ * 获取\think\response\Download对象实例
+ * @param string $filename 要下载的文件
+ * @param string $name 显示文件名
+ * @param bool $content 是否为内容
+ * @param integer $expire 有效期(秒)
+ * @return \think\response\Download
+ */
+ function download($filename, $name = '', $content = false, $expire = 360, $openinBrowser = false)
+ {
+ return Response::create($filename, 'download')->name($name)->isContent($content)->expire($expire)->openinBrowser($openinBrowser);
+ }
+}
+
+if (!function_exists('dump')) {
+ /**
+ * 浏览器友好的变量输出
+ * @param mixed $var 变量
+ * @param boolean $echo 是否输出 默认为true 如果为false 则返回输出字符串
+ * @param string $label 标签 默认为空
+ * @return void|string
+ */
+ function dump($var, $echo = true, $label = null)
+ {
+ return Debug::dump($var, $echo, $label);
+ }
+}
+
+if (!function_exists('env')) {
+ /**
+ * 获取环境变量值
+ * @access public
+ * @param string $name 环境变量名(支持二级 .号分割)
+ * @param string $default 默认值
+ * @return mixed
+ */
+ function env($name = null, $default = null)
+ {
+ return Env::get($name, $default);
+ }
+}
+
+if (!function_exists('exception')) {
+ /**
+ * 抛出异常处理
+ *
+ * @param string $msg 异常消息
+ * @param integer $code 异常代码 默认为0
+ * @param string $exception 异常类
+ *
+ * @throws Exception
+ */
+ function exception($msg, $code = 0, $exception = '')
+ {
+ $e = $exception ?: '\think\Exception';
+ throw new $e($msg, $code);
+ }
+}
+
+if (!function_exists('halt')) {
+ /**
+ * 调试变量并且中断输出
+ * @param mixed $var 调试变量或者信息
+ */
+ function halt($var)
+ {
+ dump($var);
+
+ throw new HttpResponseException(new Response);
+ }
+}
+
+if (!function_exists('input')) {
+ /**
+ * 获取输入数据 支持默认值和过滤
+ * @param string $key 获取的变量名
+ * @param mixed $default 默认值
+ * @param string $filter 过滤方法
+ * @return mixed
+ */
+ function input($key = '', $default = null, $filter = '')
+ {
+ if (0 === strpos($key, '?')) {
+ $key = substr($key, 1);
+ $has = true;
+ }
+
+ if ($pos = strpos($key, '.')) {
+ // 指定参数来源
+ $method = substr($key, 0, $pos);
+ if (in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'route', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) {
+ $key = substr($key, $pos + 1);
+ } else {
+ $method = 'param';
+ }
+ } else {
+ // 默认为自动判断
+ $method = 'param';
+ }
+
+ if (isset($has)) {
+ return request()->has($key, $method, $default);
+ } else {
+ return request()->$method($key, $default, $filter);
+ }
+ }
+}
+
+if (!function_exists('json')) {
+ /**
+ * 获取\think\response\Json对象实例
+ * @param mixed $data 返回的数据
+ * @param integer $code 状态码
+ * @param array $header 头部
+ * @param array $options 参数
+ * @return \think\response\Json
+ */
+ function json($data = [], $code = 200, $header = [], $options = [])
+ {
+ return Response::create($data, 'json', $code, $header, $options);
+ }
+}
+
+if (!function_exists('jsonp')) {
+ /**
+ * 获取\think\response\Jsonp对象实例
+ * @param mixed $data 返回的数据
+ * @param integer $code 状态码
+ * @param array $header 头部
+ * @param array $options 参数
+ * @return \think\response\Jsonp
+ */
+ function jsonp($data = [], $code = 200, $header = [], $options = [])
+ {
+ return Response::create($data, 'jsonp', $code, $header, $options);
+ }
+}
+
+if (!function_exists('lang')) {
+ /**
+ * 获取语言变量值
+ * @param string $name 语言变量名
+ * @param array $vars 动态变量值
+ * @param string $lang 语言
+ * @return mixed
+ */
+ function lang($name, $vars = [], $lang = '')
+ {
+ return Lang::get($name, $vars, $lang);
+ }
+}
+
+if (!function_exists('model')) {
+ /**
+ * 实例化Model
+ * @param string $name Model名称
+ * @param string $layer 业务层名称
+ * @param bool $appendSuffix 是否添加类名后缀
+ * @return \think\Model
+ */
+ function model($name = '', $layer = 'model', $appendSuffix = false)
+ {
+ return app()->model($name, $layer, $appendSuffix);
+ }
+}
+
+if (!function_exists('parse_name')) {
+ /**
+ * 字符串命名风格转换
+ * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格
+ * @param string $name 字符串
+ * @param integer $type 转换类型
+ * @param bool $ucfirst 首字母是否大写(驼峰规则)
+ * @return string
+ */
+ function parse_name($name, $type = 0, $ucfirst = true)
+ {
+ if ($type) {
+ $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) {
+ return strtoupper($match[1]);
+ }, $name);
+
+ return $ucfirst ? ucfirst($name) : lcfirst($name);
+ } else {
+ return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));
+ }
+ }
+}
+
+if (!function_exists('redirect')) {
+ /**
+ * 获取\think\response\Redirect对象实例
+ * @param mixed $url 重定向地址 支持Url::build方法的地址
+ * @param array|integer $params 额外参数
+ * @param integer $code 状态码
+ * @return \think\response\Redirect
+ */
+ function redirect($url = [], $params = [], $code = 302)
+ {
+ if (is_integer($params)) {
+ $code = $params;
+ $params = [];
+ }
+
+ return Response::create($url, 'redirect', $code)->params($params);
+ }
+}
+
+if (!function_exists('request')) {
+ /**
+ * 获取当前Request对象实例
+ * @return Request
+ */
+ function request()
+ {
+ return app('request');
+ }
+}
+
+if (!function_exists('response')) {
+ /**
+ * 创建普通 Response 对象实例
+ * @param mixed $data 输出数据
+ * @param int|string $code 状态码
+ * @param array $header 头信息
+ * @param string $type
+ * @return Response
+ */
+ function response($data = '', $code = 200, $header = [], $type = 'html')
+ {
+ return Response::create($data, $type, $code, $header);
+ }
+}
+
+if (!function_exists('route')) {
+ /**
+ * 路由注册
+ * @param string $rule 路由规则
+ * @param mixed $route 路由地址
+ * @param array $option 路由参数
+ * @param array $pattern 变量规则
+ * @return RuleItem
+ */
+ function route($rule, $route, $option = [], $pattern = [])
+ {
+ return Route::rule($rule, $route, '*', $option, $pattern);
+ }
+}
+
+if (!function_exists('session')) {
+ /**
+ * Session管理
+ * @param string|array $name session名称,如果为数组表示进行session设置
+ * @param mixed $value session值
+ * @param string $prefix 前缀
+ * @return mixed
+ */
+ function session($name, $value = '', $prefix = null)
+ {
+ if (is_array($name)) {
+ // 初始化
+ Session::init($name);
+ } elseif (is_null($name)) {
+ // 清除
+ Session::clear($value);
+ } elseif ('' === $value) {
+ // 判断或获取
+ return 0 === strpos($name, '?') ? Session::has(substr($name, 1), $prefix) : Session::get($name, $prefix);
+ } elseif (is_null($value)) {
+ // 删除
+ return Session::delete($name, $prefix);
+ } else {
+ // 设置
+ return Session::set($name, $value, $prefix);
+ }
+ }
+}
+
+if (!function_exists('token')) {
+ /**
+ * 生成表单令牌
+ * @param string $name 令牌名称
+ * @param mixed $type 令牌生成方法
+ * @return string
+ */
+ function token($name = '__token__', $type = 'md5')
+ {
+ $token = Request::token($name, $type);
+
+ return '';
+ }
+}
+
+if (!function_exists('trace')) {
+ /**
+ * 记录日志信息
+ * @param mixed $log log信息 支持字符串和数组
+ * @param string $level 日志级别
+ * @return array|void
+ */
+ function trace($log = '[think]', $level = 'log')
+ {
+ if ('[think]' === $log) {
+ return Log::getLog();
+ } else {
+ Log::record($log, $level);
+ }
+ }
+}
+
+if (!function_exists('trait_uses_recursive')) {
+ /**
+ * 获取一个trait里所有引用到的trait
+ *
+ * @param string $trait
+ * @return array
+ */
+ function trait_uses_recursive($trait)
+ {
+ $traits = class_uses($trait);
+ foreach ($traits as $trait) {
+ $traits += trait_uses_recursive($trait);
+ }
+
+ return $traits;
+ }
+}
+
+if (!function_exists('url')) {
+ /**
+ * Url生成
+ * @param string $url 路由地址
+ * @param string|array $vars 变量
+ * @param bool|string $suffix 生成的URL后缀
+ * @param bool|string $domain 域名
+ * @return string
+ */
+ function url($url = '', $vars = '', $suffix = true, $domain = false)
+ {
+ return Url::build($url, $vars, $suffix, $domain);
+ }
+}
+
+if (!function_exists('validate')) {
+ /**
+ * 实例化验证器
+ * @param string $name 验证器名称
+ * @param string $layer 业务层名称
+ * @param bool $appendSuffix 是否添加类名后缀
+ * @return \think\Validate
+ */
+ function validate($name = '', $layer = 'validate', $appendSuffix = false)
+ {
+ return app()->validate($name, $layer, $appendSuffix);
+ }
+}
+
+if (!function_exists('view')) {
+ /**
+ * 渲染模板输出
+ * @param string $template 模板文件
+ * @param array $vars 模板变量
+ * @param integer $code 状态码
+ * @param callable $filter 内容过滤
+ * @return \think\response\View
+ */
+ function view($template = '', $vars = [], $code = 200, $filter = null)
+ {
+ return Response::create($template, 'view', $code)->assign($vars)->filter($filter);
+ }
+}
+
+if (!function_exists('widget')) {
+ /**
+ * 渲染输出Widget
+ * @param string $name Widget名称
+ * @param array $data 传入的参数
+ * @return mixed
+ */
+ function widget($name, $data = [])
+ {
+ $result = app()->action($name, $data, 'widget');
+
+ if (is_object($result)) {
+ $result = $result->getContent();
+ }
+
+ return $result;
+ }
+}
+
+if (!function_exists('xml')) {
+ /**
+ * 获取\think\response\Xml对象实例
+ * @param mixed $data 返回的数据
+ * @param integer $code 状态码
+ * @param array $header 头部
+ * @param array $options 参数
+ * @return \think\response\Xml
+ */
+ function xml($data = [], $code = 200, $header = [], $options = [])
+ {
+ return Response::create($data, 'xml', $code, $header, $options);
+ }
+}
+
+if (!function_exists('yaconf')) {
+ /**
+ * 获取yaconf配置
+ *
+ * @param string $name 配置参数名
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ function yaconf($name, $default = null)
+ {
+ return Config::yaconf($name, $default);
+ }
+}
diff --git a/Server/thinkphp/lang/zh-cn.php b/Server/thinkphp/lang/zh-cn.php
new file mode 100644
index 00000000..1e050820
--- /dev/null
+++ b/Server/thinkphp/lang/zh-cn.php
@@ -0,0 +1,144 @@
+
+// +----------------------------------------------------------------------
+
+// 核心中文语言包
+return [
+ // 系统错误提示
+ 'Undefined variable' => '未定义变量',
+ 'Undefined index' => '未定义数组索引',
+ 'Undefined offset' => '未定义数组下标',
+ 'Parse error' => '语法解析错误',
+ 'Type error' => '类型错误',
+ 'Fatal error' => '致命错误',
+ 'syntax error' => '语法错误',
+
+ // 框架核心错误提示
+ 'dispatch type not support' => '不支持的调度类型',
+ 'method param miss' => '方法参数错误',
+ 'method not exists' => '方法不存在',
+ 'function not exists' => '函数不存在',
+ 'file not exists' => '文件不存在',
+ 'module not exists' => '模块不存在',
+ 'controller not exists' => '控制器不存在',
+ 'class not exists' => '类不存在',
+ 'property not exists' => '类的属性不存在',
+ 'template not exists' => '模板文件不存在',
+ 'illegal controller name' => '非法的控制器名称',
+ 'illegal action name' => '非法的操作名称',
+ 'url suffix deny' => '禁止的URL后缀访问',
+ 'Route Not Found' => '当前访问路由未定义或不匹配',
+ 'Undefined db type' => '未定义数据库类型',
+ 'variable type error' => '变量类型错误',
+ 'PSR-4 error' => 'PSR-4 规范错误',
+ 'not support total' => '简洁模式下不能获取数据总数',
+ 'not support last' => '简洁模式下不能获取最后一页',
+ 'error session handler' => '错误的SESSION处理器类',
+ 'not allow php tag' => '模板不允许使用PHP语法',
+ 'not support' => '不支持',
+ 'redisd master' => 'Redisd 主服务器错误',
+ 'redisd slave' => 'Redisd 从服务器错误',
+ 'must run at sae' => '必须在SAE运行',
+ 'memcache init error' => '未开通Memcache服务,请在SAE管理平台初始化Memcache服务',
+ 'KVDB init error' => '没有初始化KVDB,请在SAE管理平台初始化KVDB服务',
+ 'fields not exists' => '数据表字段不存在',
+ 'where express error' => '查询表达式错误',
+ 'order express error' => '排序表达式错误',
+ 'no data to update' => '没有任何数据需要更新',
+ 'miss data to insert' => '缺少需要写入的数据',
+ 'not support data' => '不支持的数据表达式',
+ 'miss complex primary data' => '缺少复合主键数据',
+ 'miss update condition' => '缺少更新条件',
+ 'model data Not Found' => '模型数据不存在',
+ 'table data not Found' => '表数据不存在',
+ 'delete without condition' => '没有条件不会执行删除操作',
+ 'miss relation data' => '缺少关联表数据',
+ 'tag attr must' => '模板标签属性必须',
+ 'tag error' => '模板标签错误',
+ 'cache write error' => '缓存写入失败',
+ 'sae mc write error' => 'SAE mc 写入错误',
+ 'route name not exists' => '路由标识不存在(或参数不够)',
+ 'invalid request' => '非法请求',
+ 'bind attr has exists' => '模型的属性已经存在',
+ 'relation data not exists' => '关联数据不存在',
+ 'relation not support' => '关联不支持',
+ 'chunk not support order' => 'Chunk不支持调用order方法',
+ 'route pattern error' => '路由变量规则定义错误',
+ 'route behavior will not support' => '路由行为废弃(使用中间件替代)',
+ 'closure not support cache(true)' => '使用闭包查询不支持cache(true),请指定缓存Key',
+
+ // 上传错误信息
+ 'unknown upload error' => '未知上传错误!',
+ 'file write error' => '文件写入失败!',
+ 'upload temp dir not found' => '找不到临时文件夹!',
+ 'no file to uploaded' => '没有文件被上传!',
+ 'only the portion of file is uploaded' => '文件只有部分被上传!',
+ 'upload File size exceeds the maximum value' => '上传文件大小超过了最大值!',
+ 'upload write error' => '文件上传保存错误!',
+ 'has the same filename: {:filename}' => '存在同名文件:{:filename}',
+ 'upload illegal files' => '非法上传文件',
+ 'illegal image files' => '非法图片文件',
+ 'extensions to upload is not allowed' => '上传文件后缀不允许',
+ 'mimetype to upload is not allowed' => '上传文件MIME类型不允许!',
+ 'filesize not match' => '上传文件大小不符!',
+ 'directory {:path} creation failed' => '目录 {:path} 创建失败!',
+
+ 'The middleware must return Response instance' => '中间件方法必须返回Response对象实例',
+ 'The queue was exhausted, with no response returned' => '中间件队列为空',
+ // Validate Error Message
+ ':attribute require' => ':attribute不能为空',
+ ':attribute must' => ':attribute必须',
+ ':attribute must be numeric' => ':attribute必须是数字',
+ ':attribute must be integer' => ':attribute必须是整数',
+ ':attribute must be float' => ':attribute必须是浮点数',
+ ':attribute must be bool' => ':attribute必须是布尔值',
+ ':attribute not a valid email address' => ':attribute格式不符',
+ ':attribute not a valid mobile' => ':attribute格式不符',
+ ':attribute must be a array' => ':attribute必须是数组',
+ ':attribute must be yes,on or 1' => ':attribute必须是yes、on或者1',
+ ':attribute not a valid datetime' => ':attribute不是一个有效的日期或时间格式',
+ ':attribute not a valid file' => ':attribute不是有效的上传文件',
+ ':attribute not a valid image' => ':attribute不是有效的图像文件',
+ ':attribute must be alpha' => ':attribute只能是字母',
+ ':attribute must be alpha-numeric' => ':attribute只能是字母和数字',
+ ':attribute must be alpha-numeric, dash, underscore' => ':attribute只能是字母、数字和下划线_及破折号-',
+ ':attribute not a valid domain or ip' => ':attribute不是有效的域名或者IP',
+ ':attribute must be chinese' => ':attribute只能是汉字',
+ ':attribute must be chinese or alpha' => ':attribute只能是汉字、字母',
+ ':attribute must be chinese,alpha-numeric' => ':attribute只能是汉字、字母和数字',
+ ':attribute must be chinese,alpha-numeric,underscore, dash' => ':attribute只能是汉字、字母、数字和下划线_及破折号-',
+ ':attribute not a valid url' => ':attribute不是有效的URL地址',
+ ':attribute not a valid ip' => ':attribute不是有效的IP地址',
+ ':attribute must be dateFormat of :rule' => ':attribute必须使用日期格式 :rule',
+ ':attribute must be in :rule' => ':attribute必须在 :rule 范围内',
+ ':attribute be notin :rule' => ':attribute不能在 :rule 范围内',
+ ':attribute must between :1 - :2' => ':attribute只能在 :1 - :2 之间',
+ ':attribute not between :1 - :2' => ':attribute不能在 :1 - :2 之间',
+ 'size of :attribute must be :rule' => ':attribute长度不符合要求 :rule',
+ 'max size of :attribute must be :rule' => ':attribute长度不能超过 :rule',
+ 'min size of :attribute must be :rule' => ':attribute长度不能小于 :rule',
+ ':attribute cannot be less than :rule' => ':attribute日期不能小于 :rule',
+ ':attribute cannot exceed :rule' => ':attribute日期不能超过 :rule',
+ ':attribute not within :rule' => '不在有效期内 :rule',
+ 'access IP is not allowed' => '不允许的IP访问',
+ 'access IP denied' => '禁止的IP访问',
+ ':attribute out of accord with :2' => ':attribute和确认字段:2不一致',
+ ':attribute cannot be same with :2' => ':attribute和比较字段:2不能相同',
+ ':attribute must greater than or equal :rule' => ':attribute必须大于等于 :rule',
+ ':attribute must greater than :rule' => ':attribute必须大于 :rule',
+ ':attribute must less than or equal :rule' => ':attribute必须小于等于 :rule',
+ ':attribute must less than :rule' => ':attribute必须小于 :rule',
+ ':attribute must equal :rule' => ':attribute必须等于 :rule',
+ ':attribute has exists' => ':attribute已存在',
+ ':attribute not conform to the rules' => ':attribute不符合指定规则',
+ 'invalid Request method' => '无效的请求类型',
+ 'invalid token' => '令牌数据无效',
+ 'not conform to the rules' => '规则错误',
+];
diff --git a/Server/thinkphp/library/think/App.php b/Server/thinkphp/library/think/App.php
new file mode 100644
index 00000000..35924a5b
--- /dev/null
+++ b/Server/thinkphp/library/think/App.php
@@ -0,0 +1,979 @@
+
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\exception\ClassNotFoundException;
+use think\exception\HttpResponseException;
+use think\route\Dispatch;
+
+/**
+ * App 应用管理
+ */
+class App extends Container
+{
+ const VERSION = '5.1.41 LTS';
+
+ /**
+ * 当前模块路径
+ * @var string
+ */
+ protected $modulePath;
+
+ /**
+ * 应用调试模式
+ * @var bool
+ */
+ protected $appDebug = true;
+
+ /**
+ * 应用开始时间
+ * @var float
+ */
+ protected $beginTime;
+
+ /**
+ * 应用内存初始占用
+ * @var integer
+ */
+ protected $beginMem;
+
+ /**
+ * 应用类库命名空间
+ * @var string
+ */
+ protected $namespace = 'app';
+
+ /**
+ * 应用类库后缀
+ * @var bool
+ */
+ protected $suffix = false;
+
+ /**
+ * 严格路由检测
+ * @var bool
+ */
+ protected $routeMust;
+
+ /**
+ * 应用类库目录
+ * @var string
+ */
+ protected $appPath;
+
+ /**
+ * 框架目录
+ * @var string
+ */
+ protected $thinkPath;
+
+ /**
+ * 应用根目录
+ * @var string
+ */
+ protected $rootPath;
+
+ /**
+ * 运行时目录
+ * @var string
+ */
+ protected $runtimePath;
+
+ /**
+ * 配置目录
+ * @var string
+ */
+ protected $configPath;
+
+ /**
+ * 路由目录
+ * @var string
+ */
+ protected $routePath;
+
+ /**
+ * 配置后缀
+ * @var string
+ */
+ protected $configExt;
+
+ /**
+ * 应用调度实例
+ * @var Dispatch
+ */
+ protected $dispatch;
+
+ /**
+ * 绑定模块(控制器)
+ * @var string
+ */
+ protected $bindModule;
+
+ /**
+ * 初始化
+ * @var bool
+ */
+ protected $initialized = false;
+
+ public function __construct($appPath = '')
+ {
+ $this->thinkPath = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR;
+ $this->path($appPath);
+ }
+
+ /**
+ * 绑定模块或者控制器
+ * @access public
+ * @param string $bind
+ * @return $this
+ */
+ public function bind($bind)
+ {
+ $this->bindModule = $bind;
+ return $this;
+ }
+
+ /**
+ * 设置应用类库目录
+ * @access public
+ * @param string $path 路径
+ * @return $this
+ */
+ public function path($path)
+ {
+ $this->appPath = $path ? realpath($path) . DIRECTORY_SEPARATOR : $this->getAppPath();
+
+ return $this;
+ }
+
+ /**
+ * 初始化应用
+ * @access public
+ * @return void
+ */
+ public function initialize()
+ {
+ if ($this->initialized) {
+ return;
+ }
+
+ $this->initialized = true;
+ $this->beginTime = microtime(true);
+ $this->beginMem = memory_get_usage();
+
+ $this->rootPath = dirname($this->appPath) . DIRECTORY_SEPARATOR;
+ $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;
+ $this->routePath = $this->rootPath . 'route' . DIRECTORY_SEPARATOR;
+ $this->configPath = $this->rootPath . 'config' . DIRECTORY_SEPARATOR;
+
+ static::setInstance($this);
+
+ $this->instance('app', $this);
+
+ // 加载环境变量配置文件
+ if (is_file($this->rootPath . '.env')) {
+ $this->env->load($this->rootPath . '.env');
+ }
+
+ $this->configExt = $this->env->get('config_ext', '.php');
+
+ // 加载惯例配置文件
+ $this->config->set(include $this->thinkPath . 'convention.php');
+
+ // 设置路径环境变量
+ $this->env->set([
+ 'think_path' => $this->thinkPath,
+ 'root_path' => $this->rootPath,
+ 'app_path' => $this->appPath,
+ 'config_path' => $this->configPath,
+ 'route_path' => $this->routePath,
+ 'runtime_path' => $this->runtimePath,
+ 'extend_path' => $this->rootPath . 'extend' . DIRECTORY_SEPARATOR,
+ 'vendor_path' => $this->rootPath . 'vendor' . DIRECTORY_SEPARATOR,
+ ]);
+
+ $this->namespace = $this->env->get('app_namespace', $this->namespace);
+ $this->env->set('app_namespace', $this->namespace);
+
+ // 注册应用命名空间
+ Loader::addNamespace($this->namespace, $this->appPath);
+
+ // 初始化应用
+ $this->init();
+
+ // 开启类名后缀
+ $this->suffix = $this->config('app.class_suffix');
+
+ // 应用调试模式
+ $this->appDebug = $this->env->get('app_debug', $this->config('app.app_debug'));
+ $this->env->set('app_debug', $this->appDebug);
+
+ if (!$this->appDebug) {
+ ini_set('display_errors', 'Off');
+ } elseif (PHP_SAPI != 'cli') {
+ //重新申请一块比较大的buffer
+ if (ob_get_level() > 0) {
+ $output = ob_get_clean();
+ }
+ ob_start();
+ if (!empty($output)) {
+ echo $output;
+ }
+ }
+
+ // 注册异常处理类
+ if ($this->config('app.exception_handle')) {
+ Error::setExceptionHandler($this->config('app.exception_handle'));
+ }
+
+ // 注册根命名空间
+ if (!empty($this->config('app.root_namespace'))) {
+ Loader::addNamespace($this->config('app.root_namespace'));
+ }
+
+ // 加载composer autofile文件
+ Loader::loadComposerAutoloadFiles();
+
+ // 注册类库别名
+ Loader::addClassAlias($this->config->pull('alias'));
+
+ // 数据库配置初始化
+ Db::init($this->config->pull('database'));
+
+ // 设置系统时区
+ date_default_timezone_set($this->config('app.default_timezone'));
+
+ // 读取语言包
+ $this->loadLangPack();
+
+ // 路由初始化
+ $this->routeInit();
+ }
+
+ /**
+ * 初始化应用或模块
+ * @access public
+ * @param string $module 模块名
+ * @return void
+ */
+ public function init($module = '')
+ {
+ // 定位模块目录
+ $module = $module ? $module . DIRECTORY_SEPARATOR : '';
+ $path = $this->appPath . $module;
+
+ // 加载初始化文件
+ if (is_file($path . 'init.php')) {
+ include $path . 'init.php';
+ } elseif (is_file($this->runtimePath . $module . 'init.php')) {
+ include $this->runtimePath . $module . 'init.php';
+ } else {
+ // 加载行为扩展文件
+ if (is_file($path . 'tags.php')) {
+ $tags = include $path . 'tags.php';
+ if (is_array($tags)) {
+ $this->hook->import($tags);
+ }
+ }
+
+ // 加载公共文件
+ if (is_file($path . 'common.php')) {
+ include_once $path . 'common.php';
+ }
+
+ if ('' == $module) {
+ // 加载系统助手函数
+ include $this->thinkPath . 'helper.php';
+ }
+
+ // 加载中间件
+ if (is_file($path . 'middleware.php')) {
+ $middleware = include $path . 'middleware.php';
+ if (is_array($middleware)) {
+ $this->middleware->import($middleware);
+ }
+ }
+
+ // 注册服务的容器对象实例
+ if (is_file($path . 'provider.php')) {
+ $provider = include $path . 'provider.php';
+ if (is_array($provider)) {
+ $this->bindTo($provider);
+ }
+ }
+
+ // 自动读取配置文件
+ if (is_dir($path . 'config')) {
+ $dir = $path . 'config' . DIRECTORY_SEPARATOR;
+ } elseif (is_dir($this->configPath . $module)) {
+ $dir = $this->configPath . $module;
+ }
+
+ $files = isset($dir) ? scandir($dir) : [];
+
+ foreach ($files as $file) {
+ if ('.' . pathinfo($file, PATHINFO_EXTENSION) === $this->configExt) {
+ $this->config->load($dir . $file, pathinfo($file, PATHINFO_FILENAME));
+ }
+ }
+ }
+
+ $this->setModulePath($path);
+
+ if ($module) {
+ // 对容器中的对象实例进行配置更新
+ $this->containerConfigUpdate($module);
+ }
+ }
+
+ protected function containerConfigUpdate($module)
+ {
+ $config = $this->config->get();
+
+ // 注册异常处理类
+ if ($config['app']['exception_handle']) {
+ Error::setExceptionHandler($config['app']['exception_handle']);
+ }
+
+ Db::init($config['database']);
+ $this->middleware->setConfig($config['middleware']);
+ $this->route->setConfig($config['app']);
+ $this->request->init($config['app']);
+ $this->cookie->init($config['cookie']);
+ $this->view->init($config['template']);
+ $this->log->init($config['log']);
+ $this->session->setConfig($config['session']);
+ $this->debug->setConfig($config['trace']);
+ $this->cache->init($config['cache'], true);
+
+ // 加载当前模块语言包
+ $this->lang->load($this->appPath . $module . DIRECTORY_SEPARATOR . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php');
+
+ // 模块请求缓存检查
+ $this->checkRequestCache(
+ $config['app']['request_cache'],
+ $config['app']['request_cache_expire'],
+ $config['app']['request_cache_except']
+ );
+ }
+
+ /**
+ * 执行应用程序
+ * @access public
+ * @return Response
+ * @throws Exception
+ */
+ public function run()
+ {
+ try {
+ // 初始化应用
+ $this->initialize();
+
+ // 监听app_init
+ $this->hook->listen('app_init');
+
+ if ($this->bindModule) {
+ // 模块/控制器绑定
+ $this->route->bind($this->bindModule);
+ } elseif ($this->config('app.auto_bind_module')) {
+ // 入口自动绑定
+ $name = pathinfo($this->request->baseFile(), PATHINFO_FILENAME);
+ if ($name && 'index' != $name && is_dir($this->appPath . $name)) {
+ $this->route->bind($name);
+ }
+ }
+
+ // 监听app_dispatch
+ $this->hook->listen('app_dispatch');
+
+ $dispatch = $this->dispatch;
+
+ if (empty($dispatch)) {
+ // 路由检测
+ $dispatch = $this->routeCheck()->init();
+ }
+
+ // 记录当前调度信息
+ $this->request->dispatch($dispatch);
+
+ // 记录路由和请求信息
+ if ($this->appDebug) {
+ $this->log('[ ROUTE ] ' . var_export($this->request->routeInfo(), true));
+ $this->log('[ HEADER ] ' . var_export($this->request->header(), true));
+ $this->log('[ PARAM ] ' . var_export($this->request->param(), true));
+ }
+
+ // 监听app_begin
+ $this->hook->listen('app_begin');
+
+ // 请求缓存检查
+ $this->checkRequestCache(
+ $this->config('request_cache'),
+ $this->config('request_cache_expire'),
+ $this->config('request_cache_except')
+ );
+
+ $data = null;
+ } catch (HttpResponseException $exception) {
+ $dispatch = null;
+ $data = $exception->getResponse();
+ }
+
+ $this->middleware->add(function (Request $request, $next) use ($dispatch, $data) {
+ return is_null($data) ? $dispatch->run() : $data;
+ });
+
+ $response = $this->middleware->dispatch($this->request);
+
+ // 监听app_end
+ $this->hook->listen('app_end', $response);
+
+ return $response;
+ }
+
+ protected function getRouteCacheKey()
+ {
+ if ($this->config->get('route_check_cache_key')) {
+ $closure = $this->config->get('route_check_cache_key');
+ $routeKey = $closure($this->request);
+ } else {
+ $routeKey = md5($this->request->baseUrl(true) . ':' . $this->request->method());
+ }
+
+ return $routeKey;
+ }
+
+ protected function loadLangPack()
+ {
+ // 读取默认语言
+ $this->lang->range($this->config('app.default_lang'));
+
+ if ($this->config('app.lang_switch_on')) {
+ // 开启多语言机制 检测当前语言
+ $this->lang->detect();
+ }
+
+ $this->request->setLangset($this->lang->range());
+
+ // 加载系统语言包
+ $this->lang->load([
+ $this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php',
+ $this->appPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php',
+ ]);
+ }
+
+ /**
+ * 设置当前地址的请求缓存
+ * @access public
+ * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id
+ * @param mixed $expire 缓存有效期
+ * @param array $except 缓存排除
+ * @param string $tag 缓存标签
+ * @return void
+ */
+ public function checkRequestCache($key, $expire = null, $except = [], $tag = null)
+ {
+ $cache = $this->request->cache($key, $expire, $except, $tag);
+
+ if ($cache) {
+ $this->setResponseCache($cache);
+ }
+ }
+
+ public function setResponseCache($cache)
+ {
+ list($key, $expire, $tag) = $cache;
+
+ if (strtotime($this->request->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $this->request->server('REQUEST_TIME')) {
+ // 读取缓存
+ $response = Response::create()->code(304);
+ throw new HttpResponseException($response);
+ } elseif ($this->cache->has($key)) {
+ list($content, $header) = $this->cache->get($key);
+
+ $response = Response::create($content)->header($header);
+ throw new HttpResponseException($response);
+ }
+ }
+
+ /**
+ * 设置当前请求的调度信息
+ * @access public
+ * @param Dispatch $dispatch 调度信息
+ * @return $this
+ */
+ public function dispatch(Dispatch $dispatch)
+ {
+ $this->dispatch = $dispatch;
+ return $this;
+ }
+
+ /**
+ * 记录调试信息
+ * @access public
+ * @param mixed $msg 调试信息
+ * @param string $type 信息类型
+ * @return void
+ */
+ public function log($msg, $type = 'info')
+ {
+ $this->appDebug && $this->log->record($msg, $type);
+ }
+
+ /**
+ * 获取配置参数 为空则获取所有配置
+ * @access public
+ * @param string $name 配置参数名(支持二级配置 .号分割)
+ * @return mixed
+ */
+ public function config($name = '')
+ {
+ return $this->config->get($name);
+ }
+
+ /**
+ * 路由初始化 导入路由定义规则
+ * @access public
+ * @return void
+ */
+ public function routeInit()
+ {
+ // 路由检测
+ if (is_dir($this->routePath)) {
+ $files = glob($this->routePath . '*.php');
+ foreach ($files as $file) {
+ $rules = include $file;
+ if (is_array($rules)) {
+ $this->route->import($rules);
+ }
+ }
+ }
+
+ if ($this->route->config('route_annotation')) {
+ // 自动生成路由定义
+ if ($this->appDebug) {
+ $suffix = $this->route->config('controller_suffix') || $this->route->config('class_suffix');
+ $this->build->buildRoute($suffix);
+ }
+
+ $filename = $this->runtimePath . 'build_route.php';
+
+ if (is_file($filename)) {
+ include $filename;
+ }
+ }
+ }
+
+ /**
+ * URL路由检测(根据PATH_INFO)
+ * @access public
+ * @return Dispatch
+ */
+ public function routeCheck()
+ {
+ // 检测路由缓存
+ if (!$this->appDebug && $this->config->get('route_check_cache')) {
+ $routeKey = $this->getRouteCacheKey();
+ $option = $this->config->get('route_cache_option');
+
+ if ($option && $this->cache->connect($option)->has($routeKey)) {
+ return $this->cache->connect($option)->get($routeKey);
+ } elseif ($this->cache->has($routeKey)) {
+ return $this->cache->get($routeKey);
+ }
+ }
+
+ // 获取应用调度信息
+ $path = $this->request->path();
+
+ // 是否强制路由模式
+ $must = !is_null($this->routeMust) ? $this->routeMust : $this->route->config('url_route_must');
+
+ // 路由检测 返回一个Dispatch对象
+ $dispatch = $this->route->check($path, $must);
+
+ if (!empty($routeKey)) {
+ try {
+ if ($option) {
+ $this->cache->connect($option)->tag('route_cache')->set($routeKey, $dispatch);
+ } else {
+ $this->cache->tag('route_cache')->set($routeKey, $dispatch);
+ }
+ } catch (\Exception $e) {
+ // 存在闭包的时候缓存无效
+ }
+ }
+
+ return $dispatch;
+ }
+
+ /**
+ * 设置应用的路由检测机制
+ * @access public
+ * @param bool $must 是否强制检测路由
+ * @return $this
+ */
+ public function routeMust($must = false)
+ {
+ $this->routeMust = $must;
+ return $this;
+ }
+
+ /**
+ * 解析模块和类名
+ * @access protected
+ * @param string $name 资源地址
+ * @param string $layer 验证层名称
+ * @param bool $appendSuffix 是否添加类名后缀
+ * @return array
+ */
+ protected function parseModuleAndClass($name, $layer, $appendSuffix)
+ {
+ if (false !== strpos($name, '\\')) {
+ $class = $name;
+ $module = $this->request->module();
+ } else {
+ if (strpos($name, '/')) {
+ list($module, $name) = explode('/', $name, 2);
+ } else {
+ $module = $this->request->module();
+ }
+
+ $class = $this->parseClass($module, $layer, $name, $appendSuffix);
+ }
+
+ return [$module, $class];
+ }
+
+ /**
+ * 实例化应用类库
+ * @access public
+ * @param string $name 类名称
+ * @param string $layer 业务层名称
+ * @param bool $appendSuffix 是否添加类名后缀
+ * @param string $common 公共模块名
+ * @return object
+ * @throws ClassNotFoundException
+ */
+ public function create($name, $layer, $appendSuffix = false, $common = 'common')
+ {
+ $guid = $name . $layer;
+
+ if ($this->__isset($guid)) {
+ return $this->__get($guid);
+ }
+
+ list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix);
+
+ if (class_exists($class)) {
+ $object = $this->__get($class);
+ } else {
+ $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class);
+ if (class_exists($class)) {
+ $object = $this->__get($class);
+ } else {
+ throw new ClassNotFoundException('class not exists:' . $class, $class);
+ }
+ }
+
+ $this->__set($guid, $class);
+
+ return $object;
+ }
+
+ /**
+ * 实例化(分层)模型
+ * @access public
+ * @param string $name Model名称
+ * @param string $layer 业务层名称
+ * @param bool $appendSuffix 是否添加类名后缀
+ * @param string $common 公共模块名
+ * @return Model
+ * @throws ClassNotFoundException
+ */
+ public function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common')
+ {
+ return $this->create($name, $layer, $appendSuffix, $common);
+ }
+
+ /**
+ * 实例化(分层)控制器 格式:[模块名/]控制器名
+ * @access public
+ * @param string $name 资源地址
+ * @param string $layer 控制层名称
+ * @param bool $appendSuffix 是否添加类名后缀
+ * @param string $empty 空控制器名称
+ * @return object
+ * @throws ClassNotFoundException
+ */
+ public function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '')
+ {
+ list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix);
+
+ if (class_exists($class)) {
+ return $this->make($class, true);
+ } elseif ($empty && class_exists($emptyClass = $this->parseClass($module, $layer, $empty, $appendSuffix))) {
+ return $this->make($emptyClass, true);
+ }
+
+ throw new ClassNotFoundException('class not exists:' . $class, $class);
+ }
+
+ /**
+ * 实例化验证类 格式:[模块名/]验证器名
+ * @access public
+ * @param string $name 资源地址
+ * @param string $layer 验证层名称
+ * @param bool $appendSuffix 是否添加类名后缀
+ * @param string $common 公共模块名
+ * @return Validate
+ * @throws ClassNotFoundException
+ */
+ public function validate($name = '', $layer = 'validate', $appendSuffix = false, $common = 'common')
+ {
+ $name = $name ?: $this->config('default_validate');
+
+ if (empty($name)) {
+ return new Validate;
+ }
+
+ return $this->create($name, $layer, $appendSuffix, $common);
+ }
+
+ /**
+ * 数据库初始化
+ * @access public
+ * @param mixed $config 数据库配置
+ * @param bool|string $name 连接标识 true 强制重新连接
+ * @return \think\db\Query
+ */
+ public function db($config = [], $name = false)
+ {
+ return Db::connect($config, $name);
+ }
+
+ /**
+ * 远程调用模块的操作方法 参数格式 [模块/控制器/]操作
+ * @access public
+ * @param string $url 调用地址
+ * @param string|array $vars 调用参数 支持字符串和数组
+ * @param string $layer 要调用的控制层名称
+ * @param bool $appendSuffix 是否添加类名后缀
+ * @return mixed
+ * @throws ClassNotFoundException
+ */
+ public function action($url, $vars = [], $layer = 'controller', $appendSuffix = false)
+ {
+ $info = pathinfo($url);
+ $action = $info['basename'];
+ $module = '.' != $info['dirname'] ? $info['dirname'] : $this->request->controller();
+ $class = $this->controller($module, $layer, $appendSuffix);
+
+ if (is_scalar($vars)) {
+ if (strpos($vars, '=')) {
+ parse_str($vars, $vars);
+ } else {
+ $vars = [$vars];
+ }
+ }
+
+ return $this->invokeMethod([$class, $action . $this->config('action_suffix')], $vars);
+ }
+
+ /**
+ * 解析应用类的类名
+ * @access public
+ * @param string $module 模块名
+ * @param string $layer 层名 controller model ...
+ * @param string $name 类名
+ * @param bool $appendSuffix
+ * @return string
+ */
+ public function parseClass($module, $layer, $name, $appendSuffix = false)
+ {
+ $name = str_replace(['/', '.'], '\\', $name);
+ $array = explode('\\', $name);
+ $class = Loader::parseName(array_pop($array), 1) . ($this->suffix || $appendSuffix ? ucfirst($layer) : '');
+ $path = $array ? implode('\\', $array) . '\\' : '';
+
+ return $this->namespace . '\\' . ($module ? $module . '\\' : '') . $layer . '\\' . $path . $class;
+ }
+
+ /**
+ * 获取框架版本
+ * @access public
+ * @return string
+ */
+ public function version()
+ {
+ return static::VERSION;
+ }
+
+ /**
+ * 是否为调试模式
+ * @access public
+ * @return bool
+ */
+ public function isDebug()
+ {
+ return $this->appDebug;
+ }
+
+ /**
+ * 获取模块路径
+ * @access public
+ * @return string
+ */
+ public function getModulePath()
+ {
+ return $this->modulePath;
+ }
+
+ /**
+ * 设置模块路径
+ * @access public
+ * @param string $path 路径
+ * @return void
+ */
+ public function setModulePath($path)
+ {
+ $this->modulePath = $path;
+ $this->env->set('module_path', $path);
+ }
+
+ /**
+ * 获取应用根目录
+ * @access public
+ * @return string
+ */
+ public function getRootPath()
+ {
+ return $this->rootPath;
+ }
+
+ /**
+ * 获取应用类库目录
+ * @access public
+ * @return string
+ */
+ public function getAppPath()
+ {
+ if (is_null($this->appPath)) {
+ $this->appPath = Loader::getRootPath() . 'application' . DIRECTORY_SEPARATOR;
+ }
+
+ return $this->appPath;
+ }
+
+ /**
+ * 获取应用运行时目录
+ * @access public
+ * @return string
+ */
+ public function getRuntimePath()
+ {
+ return $this->runtimePath;
+ }
+
+ /**
+ * 获取核心框架目录
+ * @access public
+ * @return string
+ */
+ public function getThinkPath()
+ {
+ return $this->thinkPath;
+ }
+
+ /**
+ * 获取路由目录
+ * @access public
+ * @return string
+ */
+ public function getRoutePath()
+ {
+ return $this->routePath;
+ }
+
+ /**
+ * 获取应用配置目录
+ * @access public
+ * @return string
+ */
+ public function getConfigPath()
+ {
+ return $this->configPath;
+ }
+
+ /**
+ * 获取配置后缀
+ * @access public
+ * @return string
+ */
+ public function getConfigExt()
+ {
+ return $this->configExt;
+ }
+
+ /**
+ * 获取应用类库命名空间
+ * @access public
+ * @return string
+ */
+ public function getNamespace()
+ {
+ return $this->namespace;
+ }
+
+ /**
+ * 设置应用类库命名空间
+ * @access public
+ * @param string $namespace 命名空间名称
+ * @return $this
+ */
+ public function setNamespace($namespace)
+ {
+ $this->namespace = $namespace;
+ return $this;
+ }
+
+ /**
+ * 是否启用类库后缀
+ * @access public
+ * @return bool
+ */
+ public function getSuffix()
+ {
+ return $this->suffix;
+ }
+
+ /**
+ * 获取应用开启时间
+ * @access public
+ * @return float
+ */
+ public function getBeginTime()
+ {
+ return $this->beginTime;
+ }
+
+ /**
+ * 获取应用初始内存占用
+ * @access public
+ * @return integer
+ */
+ public function getBeginMem()
+ {
+ return $this->beginMem;
+ }
+
+}
diff --git a/Server/thinkphp/library/think/Build.php b/Server/thinkphp/library/think/Build.php
new file mode 100644
index 00000000..7a531d74
--- /dev/null
+++ b/Server/thinkphp/library/think/Build.php
@@ -0,0 +1,415 @@
+
+// +----------------------------------------------------------------------
+
+namespace think;
+
+class Build
+{
+ /**
+ * 应用对象
+ * @var App
+ */
+ protected $app;
+
+ /**
+ * 应用目录
+ * @var string
+ */
+ protected $basePath;
+
+ public function __construct(App $app)
+ {
+ $this->app = $app;
+ $this->basePath = $this->app->getAppPath();
+ }
+
+ /**
+ * 根据传入的build资料创建目录和文件
+ * @access public
+ * @param array $build build列表
+ * @param string $namespace 应用类库命名空间
+ * @param bool $suffix 类库后缀
+ * @return void
+ */
+ public function run(array $build = [], $namespace = 'app', $suffix = false)
+ {
+ // 锁定
+ $lockfile = $this->basePath . 'build.lock';
+
+ if (is_writable($lockfile)) {
+ return;
+ } elseif (!touch($lockfile)) {
+ throw new Exception('应用目录[' . $this->basePath . ']不可写,目录无法自动生成!
请手动生成项目目录~', 10006);
+ }
+
+ foreach ($build as $module => $list) {
+ if ('__dir__' == $module) {
+ // 创建目录列表
+ $this->buildDir($list);
+ } elseif ('__file__' == $module) {
+ // 创建文件列表
+ $this->buildFile($list);
+ } else {
+ // 创建模块
+ $this->module($module, $list, $namespace, $suffix);
+ }
+ }
+
+ // 解除锁定
+ unlink($lockfile);
+ }
+
+ /**
+ * 创建目录
+ * @access protected
+ * @param array $list 目录列表
+ * @return void
+ */
+ protected function buildDir($list)
+ {
+ foreach ($list as $dir) {
+ $this->checkDirBuild($this->basePath . $dir);
+ }
+ }
+
+ /**
+ * 创建文件
+ * @access protected
+ * @param array $list 文件列表
+ * @return void
+ */
+ protected function buildFile($list)
+ {
+ foreach ($list as $file) {
+ if (!is_dir($this->basePath . dirname($file))) {
+ // 创建目录
+ mkdir($this->basePath . dirname($file), 0755, true);
+ }
+
+ if (!is_file($this->basePath . $file)) {
+ file_put_contents($this->basePath . $file, 'php' == pathinfo($file, PATHINFO_EXTENSION) ? "basePath . $module)) {
+ // 创建模块目录
+ mkdir($this->basePath . $module);
+ }
+
+ if (basename($this->app->getRuntimePath()) != $module) {
+ // 创建配置文件和公共文件
+ $this->buildCommon($module);
+ // 创建模块的默认页面
+ $this->buildHello($module, $namespace, $suffix);
+ }
+
+ if (empty($list)) {
+ // 创建默认的模块目录和文件
+ $list = [
+ '__file__' => ['common.php'],
+ '__dir__' => ['controller', 'model', 'view', 'config'],
+ ];
+ }
+
+ // 创建子目录和文件
+ foreach ($list as $path => $file) {
+ $modulePath = $this->basePath . $module . DIRECTORY_SEPARATOR;
+ if ('__dir__' == $path) {
+ // 生成子目录
+ foreach ($file as $dir) {
+ $this->checkDirBuild($modulePath . $dir);
+ }
+ } elseif ('__file__' == $path) {
+ // 生成(空白)文件
+ foreach ($file as $name) {
+ if (!is_file($modulePath . $name)) {
+ file_put_contents($modulePath . $name, 'php' == pathinfo($name, PATHINFO_EXTENSION) ? "checkDirBuild(dirname($filename));
+ $content = '';
+ break;
+ default:
+ // 其他文件
+ $content = "app->getNameSpace();
+ $content = 'app->config('app.url_controller_layer');
+ }
+
+ if ($this->app->config('app.app_multi_module')) {
+ $modules = glob($this->basePath . '*', GLOB_ONLYDIR);
+
+ foreach ($modules as $module) {
+ $module = basename($module);
+
+ if (in_array($module, $this->app->config('app.deny_module_list'))) {
+ continue;
+ }
+
+ $path = $this->basePath . $module . DIRECTORY_SEPARATOR . $layer . DIRECTORY_SEPARATOR;
+ $content .= $this->buildDirRoute($path, $namespace, $module, $suffix, $layer);
+ }
+ } else {
+ $path = $this->basePath . $layer . DIRECTORY_SEPARATOR;
+ $content .= $this->buildDirRoute($path, $namespace, '', $suffix, $layer);
+ }
+
+ $filename = $this->app->getRuntimePath() . 'build_route.php';
+ file_put_contents($filename, $content);
+
+ return $filename;
+ }
+
+ /**
+ * 生成子目录控制器类的路由规则
+ * @access protected
+ * @param string $path 控制器目录
+ * @param string $namespace 应用命名空间
+ * @param string $module 模块
+ * @param bool $suffix 类库后缀
+ * @param string $layer 控制器层目录名
+ * @return string
+ */
+ protected function buildDirRoute($path, $namespace, $module, $suffix, $layer)
+ {
+ $content = '';
+ $controllers = glob($path . '*.php');
+
+ foreach ($controllers as $controller) {
+ $controller = basename($controller, '.php');
+
+ $class = new \ReflectionClass($namespace . '\\' . ($module ? $module . '\\' : '') . $layer . '\\' . $controller);
+
+ if (strpos($layer, '\\')) {
+ // 多级控制器
+ $level = str_replace(DIRECTORY_SEPARATOR, '.', substr($layer, 11));
+ $controller = $level . '.' . $controller;
+ $length = strlen(strstr($layer, '\\', true));
+ } else {
+ $length = strlen($layer);
+ }
+
+ if ($suffix) {
+ $controller = substr($controller, 0, -$length);
+ }
+
+ $content .= $this->getControllerRoute($class, $module, $controller);
+ }
+
+ $subDir = glob($path . '*', GLOB_ONLYDIR);
+
+ foreach ($subDir as $dir) {
+ $content .= $this->buildDirRoute($dir . DIRECTORY_SEPARATOR, $namespace, $module, $suffix, $layer . '\\' . basename($dir));
+ }
+
+ return $content;
+ }
+
+ /**
+ * 生成控制器类的路由规则
+ * @access protected
+ * @param string $class 控制器完整类名
+ * @param string $module 模块名
+ * @param string $controller 控制器名
+ * @return string
+ */
+ protected function getControllerRoute($class, $module, $controller)
+ {
+ $content = '';
+ $comment = $class->getDocComment();
+
+ if (false !== strpos($comment, '@route(')) {
+ $comment = $this->parseRouteComment($comment);
+ $route = ($module ? $module . '/' : '') . $controller;
+ $comment = preg_replace('/route\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\)/is', 'Route::resource(\1,\'' . $route . '\')', $comment);
+ $content .= PHP_EOL . $comment;
+ } elseif (false !== strpos($comment, '@alias(')) {
+ $comment = $this->parseRouteComment($comment, '@alias(');
+ $route = ($module ? $module . '/' : '') . $controller;
+ $comment = preg_replace('/alias\(\s?([\'\"][\-\_\/\w]+[\'\"])\s?\)/is', 'Route::alias(\1,\'' . $route . '\')', $comment);
+ $content .= PHP_EOL . $comment;
+ }
+
+ $methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC);
+
+ foreach ($methods as $method) {
+ $comment = $this->getMethodRouteComment($module, $controller, $method);
+ if ($comment) {
+ $content .= PHP_EOL . $comment;
+ }
+ }
+
+ return $content;
+ }
+
+ /**
+ * 解析路由注释
+ * @access protected
+ * @param string $comment
+ * @param string $tag
+ * @return string
+ */
+ protected function parseRouteComment($comment, $tag = '@route(')
+ {
+ $comment = substr($comment, 3, -2);
+ $comment = explode(PHP_EOL, substr(strstr(trim($comment), $tag), 1));
+ $comment = array_map(function ($item) {return trim(trim($item), ' \t*');}, $comment);
+
+ if (count($comment) > 1) {
+ $key = array_search('', $comment);
+ $comment = array_slice($comment, 0, false === $key ? 1 : $key);
+ }
+
+ $comment = implode(PHP_EOL . "\t", $comment) . ';';
+
+ if (strpos($comment, '{')) {
+ $comment = preg_replace_callback('/\{\s?.*?\s?\}/s', function ($matches) {
+ return false !== strpos($matches[0], '"') ? '[' . substr(var_export(json_decode($matches[0], true), true), 7, -1) . ']' : $matches[0];
+ }, $comment);
+ }
+ return $comment;
+ }
+
+ /**
+ * 获取方法的路由注释
+ * @access protected
+ * @param string $module 模块
+ * @param string $controller 控制器名
+ * @param \ReflectMethod $reflectMethod
+ * @return string|void
+ */
+ protected function getMethodRouteComment($module, $controller, $reflectMethod)
+ {
+ $comment = $reflectMethod->getDocComment();
+
+ if (false !== strpos($comment, '@route(')) {
+ $comment = $this->parseRouteComment($comment);
+ $action = $reflectMethod->getName();
+
+ if ($suffix = $this->app->config('app.action_suffix')) {
+ $action = substr($action, 0, -strlen($suffix));
+ }
+
+ $route = ($module ? $module . '/' : '') . $controller . '/' . $action;
+ $comment = preg_replace('/route\s?\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\,?\s?[\'\"]?(\w+?)[\'\"]?\s?\)/is', 'Route::\2(\1,\'' . $route . '\')', $comment);
+ $comment = preg_replace('/route\s?\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\)/is', 'Route::rule(\1,\'' . $route . '\')', $comment);
+
+ return $comment;
+ }
+ }
+
+ /**
+ * 创建模块的欢迎页面
+ * @access protected
+ * @param string $module 模块名
+ * @param string $namespace 应用类库命名空间
+ * @param bool $suffix 类库后缀
+ * @return void
+ */
+ protected function buildHello($module, $namespace, $suffix = false)
+ {
+ $filename = $this->basePath . ($module ? $module . DIRECTORY_SEPARATOR : '') . 'controller' . DIRECTORY_SEPARATOR . 'Index' . ($suffix ? 'Controller' : '') . '.php';
+ if (!is_file($filename)) {
+ $content = file_get_contents($this->app->getThinkPath() . 'tpl' . DIRECTORY_SEPARATOR . 'default_index.tpl');
+ $content = str_replace(['{$app}', '{$module}', '{layer}', '{$suffix}'], [$namespace, $module ? $module . '\\' : '', 'controller', $suffix ? 'Controller' : ''], $content);
+ $this->checkDirBuild(dirname($filename));
+
+ file_put_contents($filename, $content);
+ }
+ }
+
+ /**
+ * 创建模块的公共文件
+ * @access protected
+ * @param string $module 模块名
+ * @return void
+ */
+ protected function buildCommon($module)
+ {
+ $filename = $this->app->getConfigPath() . ($module ? $module . DIRECTORY_SEPARATOR : '') . 'app.php';
+ $this->checkDirBuild(dirname($filename));
+
+ if (!is_file($filename)) {
+ file_put_contents($filename, "basePath . ($module ? $module . DIRECTORY_SEPARATOR : '') . 'common.php';
+
+ if (!is_file($filename)) {
+ file_put_contents($filename, "
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\cache\Driver;
+
+/**
+ * Class Cache
+ *
+ * @package think
+ *
+ * @mixin Driver
+ * @mixin \think\cache\driver\File
+ */
+class Cache
+{
+ /**
+ * 缓存实例
+ * @var array
+ */
+ protected $instance = [];
+
+ /**
+ * 缓存配置
+ * @var array
+ */
+ protected $config = [];
+
+ /**
+ * 操作句柄
+ * @var object
+ */
+ protected $handler;
+
+ public function __construct(array $config = [])
+ {
+ $this->config = $config;
+ $this->init($config);
+ }
+
+ /**
+ * 连接缓存
+ * @access public
+ * @param array $options 配置数组
+ * @param bool|string $name 缓存连接标识 true 强制重新连接
+ * @return Driver
+ */
+ public function connect(array $options = [], $name = false)
+ {
+ if (false === $name) {
+ $name = md5(serialize($options));
+ }
+
+ if (true === $name || !isset($this->instance[$name])) {
+ $type = !empty($options['type']) ? $options['type'] : 'File';
+
+ if (true === $name) {
+ $name = md5(serialize($options));
+ }
+
+ $this->instance[$name] = Loader::factory($type, '\\think\\cache\\driver\\', $options);
+ }
+
+ return $this->instance[$name];
+ }
+
+ /**
+ * 自动初始化缓存
+ * @access public
+ * @param array $options 配置数组
+ * @param bool $force 强制更新
+ * @return Driver
+ */
+ public function init(array $options = [], $force = false)
+ {
+ if (is_null($this->handler) || $force) {
+
+ if ('complex' == $options['type']) {
+ $default = $options['default'];
+ $options = isset($options[$default['type']]) ? $options[$default['type']] : $default;
+ }
+
+ $this->handler = $this->connect($options);
+ }
+
+ return $this->handler;
+ }
+
+ public static function __make(Config $config)
+ {
+ return new static($config->pull('cache'));
+ }
+
+ public function getConfig()
+ {
+ return $this->config;
+ }
+
+ public function setConfig(array $config)
+ {
+ $this->config = array_merge($this->config, $config);
+ }
+
+ /**
+ * 切换缓存类型 需要配置 cache.type 为 complex
+ * @access public
+ * @param string $name 缓存标识
+ * @return Driver
+ */
+ public function store($name = '')
+ {
+ if ('' !== $name && 'complex' == $this->config['type']) {
+ return $this->connect($this->config[$name], strtolower($name));
+ }
+
+ return $this->init();
+ }
+
+ public function __call($method, $args)
+ {
+ return call_user_func_array([$this->init(), $method], $args);
+ }
+
+}
diff --git a/Server/thinkphp/library/think/Collection.php b/Server/thinkphp/library/think/Collection.php
new file mode 100644
index 00000000..d7454ec5
--- /dev/null
+++ b/Server/thinkphp/library/think/Collection.php
@@ -0,0 +1,552 @@
+
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use ArrayAccess;
+use ArrayIterator;
+use Countable;
+use IteratorAggregate;
+use JsonSerializable;
+
+class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable
+{
+ /**
+ * 数据集数据
+ * @var array
+ */
+ protected $items = [];
+
+ public function __construct($items = [])
+ {
+ $this->items = $this->convertToArray($items);
+ }
+
+ public static function make($items = [])
+ {
+ return new static($items);
+ }
+
+ /**
+ * 是否为空
+ * @access public
+ * @return bool
+ */
+ public function isEmpty()
+ {
+ return empty($this->items);
+ }
+
+ public function toArray()
+ {
+ return array_map(function ($value) {
+ return ($value instanceof Model || $value instanceof self) ? $value->toArray() : $value;
+ }, $this->items);
+ }
+
+ public function all()
+ {
+ return $this->items;
+ }
+
+ /**
+ * 合并数组
+ *
+ * @access public
+ * @param mixed $items
+ * @return static
+ */
+ public function merge($items)
+ {
+ return new static(array_merge($this->items, $this->convertToArray($items)));
+ }
+
+ /**
+ * 交换数组中的键和值
+ *
+ * @access public
+ * @return static
+ */
+ public function flip()
+ {
+ return new static(array_flip($this->items));
+ }
+
+ /**
+ * 按指定键整理数据
+ *
+ * @access public
+ * @param mixed $items 数据
+ * @param string $indexKey 键名
+ * @return array
+ */
+ public function dictionary($items = null, &$indexKey = null)
+ {
+ if ($items instanceof self || $items instanceof Paginator) {
+ $items = $items->all();
+ }
+
+ $items = is_null($items) ? $this->items : $items;
+
+ if ($items && empty($indexKey)) {
+ $indexKey = is_array($items[0]) ? 'id' : $items[0]->getPk();
+ }
+
+ if (isset($indexKey) && is_string($indexKey)) {
+ return array_column($items, null, $indexKey);
+ }
+
+ return $items;
+ }
+
+ /**
+ * 比较数组,返回差集
+ *
+ * @access public
+ * @param mixed $items 数据
+ * @param string $indexKey 指定比较的键名
+ * @return static
+ */
+ public function diff($items, $indexKey = null)
+ {
+ if ($this->isEmpty() || is_scalar($this->items[0])) {
+ return new static(array_diff($this->items, $this->convertToArray($items)));
+ }
+
+ $diff = [];
+ $dictionary = $this->dictionary($items, $indexKey);
+
+ if (is_string($indexKey)) {
+ foreach ($this->items as $item) {
+ if (!isset($dictionary[$item[$indexKey]])) {
+ $diff[] = $item;
+ }
+ }
+ }
+
+ return new static($diff);
+ }
+
+ /**
+ * 比较数组,返回交集
+ *
+ * @access public
+ * @param mixed $items 数据
+ * @param string $indexKey 指定比较的键名
+ * @return static
+ */
+ public function intersect($items, $indexKey = null)
+ {
+ if ($this->isEmpty() || is_scalar($this->items[0])) {
+ return new static(array_diff($this->items, $this->convertToArray($items)));
+ }
+
+ $intersect = [];
+ $dictionary = $this->dictionary($items, $indexKey);
+
+ if (is_string($indexKey)) {
+ foreach ($this->items as $item) {
+ if (isset($dictionary[$item[$indexKey]])) {
+ $intersect[] = $item;
+ }
+ }
+ }
+
+ return new static($intersect);
+ }
+
+ /**
+ * 返回数组中所有的键名
+ *
+ * @access public
+ * @return array
+ */
+ public function keys()
+ {
+ $current = current($this->items);
+
+ if (is_scalar($current)) {
+ $array = $this->items;
+ } elseif (is_array($current)) {
+ $array = $current;
+ } else {
+ $array = $current->toArray();
+ }
+
+ return array_keys($array);
+ }
+
+ /**
+ * 删除数组的最后一个元素(出栈)
+ *
+ * @access public
+ * @return mixed
+ */
+ public function pop()
+ {
+ return array_pop($this->items);
+ }
+
+ /**
+ * 通过使用用户自定义函数,以字符串返回数组
+ *
+ * @access public
+ * @param callable $callback
+ * @param mixed $initial
+ * @return mixed
+ */
+ public function reduce(callable $callback, $initial = null)
+ {
+ return array_reduce($this->items, $callback, $initial);
+ }
+
+ /**
+ * 以相反的顺序返回数组。
+ *
+ * @access public
+ * @return static
+ */
+ public function reverse()
+ {
+ return new static(array_reverse($this->items));
+ }
+
+ /**
+ * 删除数组中首个元素,并返回被删除元素的值
+ *
+ * @access public
+ * @return mixed
+ */
+ public function shift()
+ {
+ return array_shift($this->items);
+ }
+
+ /**
+ * 在数组结尾插入一个元素
+ * @access public
+ * @param mixed $value
+ * @param mixed $key
+ * @return void
+ */
+ public function push($value, $key = null)
+ {
+ if (is_null($key)) {
+ $this->items[] = $value;
+ } else {
+ $this->items[$key] = $value;
+ }
+ }
+
+ /**
+ * 把一个数组分割为新的数组块.
+ *
+ * @access public
+ * @param int $size
+ * @param bool $preserveKeys
+ * @return static
+ */
+ public function chunk($size, $preserveKeys = false)
+ {
+ $chunks = [];
+
+ foreach (array_chunk($this->items, $size, $preserveKeys) as $chunk) {
+ $chunks[] = new static($chunk);
+ }
+
+ return new static($chunks);
+ }
+
+ /**
+ * 在数组开头插入一个元素
+ * @access public
+ * @param mixed $value
+ * @param mixed $key
+ * @return void
+ */
+ public function unshift($value, $key = null)
+ {
+ if (is_null($key)) {
+ array_unshift($this->items, $value);
+ } else {
+ $this->items = [$key => $value] + $this->items;
+ }
+ }
+
+ /**
+ * 给每个元素执行个回调
+ *
+ * @access public
+ * @param callable $callback
+ * @return $this
+ */
+ public function each(callable $callback)
+ {
+ foreach ($this->items as $key => $item) {
+ $result = $callback($item, $key);
+
+ if (false === $result) {
+ break;
+ } elseif (!is_object($item)) {
+ $this->items[$key] = $result;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 用回调函数处理数组中的元素
+ * @access public
+ * @param callable|null $callback
+ * @return static
+ */
+ public function map(callable $callback)
+ {
+ return new static(array_map($callback, $this->items));
+ }
+
+ /**
+ * 用回调函数过滤数组中的元素
+ * @access public
+ * @param callable|null $callback
+ * @return static
+ */
+ public function filter(callable $callback = null)
+ {
+ if ($callback) {
+ return new static(array_filter($this->items, $callback));
+ }
+
+ return new static(array_filter($this->items));
+ }
+
+ /**
+ * 根据字段条件过滤数组中的元素
+ * @access public
+ * @param string $field 字段名
+ * @param mixed $operator 操作符
+ * @param mixed $value 数据
+ * @return static
+ */
+ public function where($field, $operator, $value = null)
+ {
+ if (is_null($value)) {
+ $value = $operator;
+ $operator = '=';
+ }
+
+ return $this->filter(function ($data) use ($field, $operator, $value) {
+ if (strpos($field, '.')) {
+ list($field, $relation) = explode('.', $field);
+
+ $result = isset($data[$field][$relation]) ? $data[$field][$relation] : null;
+ } else {
+ $result = isset($data[$field]) ? $data[$field] : null;
+ }
+
+ switch (strtolower($operator)) {
+ case '===':
+ return $result === $value;
+ case '!==':
+ return $result !== $value;
+ case '!=':
+ case '<>':
+ return $result != $value;
+ case '>':
+ return $result > $value;
+ case '>=':
+ return $result >= $value;
+ case '<':
+ return $result < $value;
+ case '<=':
+ return $result <= $value;
+ case 'like':
+ return is_string($result) && false !== strpos($result, $value);
+ case 'not like':
+ return is_string($result) && false === strpos($result, $value);
+ case 'in':
+ return is_scalar($result) && in_array($result, $value, true);
+ case 'not in':
+ return is_scalar($result) && !in_array($result, $value, true);
+ case 'between':
+ list($min, $max) = is_string($value) ? explode(',', $value) : $value;
+ return is_scalar($result) && $result >= $min && $result <= $max;
+ case 'not between':
+ list($min, $max) = is_string($value) ? explode(',', $value) : $value;
+ return is_scalar($result) && $result > $max || $result < $min;
+ case '==':
+ case '=':
+ default:
+ return $result == $value;
+ }
+ });
+ }
+
+ /**
+ * 返回数据中指定的一列
+ * @access public
+ * @param mixed $columnKey 键名
+ * @param mixed $indexKey 作为索引值的列
+ * @return array
+ */
+ public function column($columnKey, $indexKey = null)
+ {
+ return array_column($this->toArray(), $columnKey, $indexKey);
+ }
+
+ /**
+ * 对数组排序
+ *
+ * @access public
+ * @param callable|null $callback
+ * @return static
+ */
+ public function sort(callable $callback = null)
+ {
+ $items = $this->items;
+
+ $callback = $callback ?: function ($a, $b) {
+ return $a == $b ? 0 : (($a < $b) ? -1 : 1);
+
+ };
+
+ uasort($items, $callback);
+
+ return new static($items);
+ }
+
+ /**
+ * 指定字段排序
+ * @access public
+ * @param string $field 排序字段
+ * @param string $order 排序
+ * @param bool $intSort 是否为数字排序
+ * @return $this
+ */
+ public function order($field, $order = null, $intSort = true)
+ {
+ return $this->sort(function ($a, $b) use ($field, $order, $intSort) {
+ $fieldA = isset($a[$field]) ? $a[$field] : null;
+ $fieldB = isset($b[$field]) ? $b[$field] : null;
+
+ if ($intSort) {
+ return 'desc' == strtolower($order) ? $fieldB >= $fieldA : $fieldA >= $fieldB;
+ } else {
+ return 'desc' == strtolower($order) ? strcmp($fieldB, $fieldA) : strcmp($fieldA, $fieldB);
+ }
+ });
+ }
+
+ /**
+ * 将数组打乱
+ *
+ * @access public
+ * @return static
+ */
+ public function shuffle()
+ {
+ $items = $this->items;
+
+ shuffle($items);
+
+ return new static($items);
+ }
+
+ /**
+ * 截取数组
+ *
+ * @access public
+ * @param int $offset
+ * @param int $length
+ * @param bool $preserveKeys
+ * @return static
+ */
+ public function slice($offset, $length = null, $preserveKeys = false)
+ {
+ return new static(array_slice($this->items, $offset, $length, $preserveKeys));
+ }
+
+ // ArrayAccess
+ public function offsetExists($offset)
+ {
+ return array_key_exists($offset, $this->items);
+ }
+
+ public function offsetGet($offset)
+ {
+ return $this->items[$offset];
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ if (is_null($offset)) {
+ $this->items[] = $value;
+ } else {
+ $this->items[$offset] = $value;
+ }
+ }
+
+ public function offsetUnset($offset)
+ {
+ unset($this->items[$offset]);
+ }
+
+ //Countable
+ public function count()
+ {
+ return count($this->items);
+ }
+
+ //IteratorAggregate
+ public function getIterator()
+ {
+ return new ArrayIterator($this->items);
+ }
+
+ //JsonSerializable
+ public function jsonSerialize()
+ {
+ return $this->toArray();
+ }
+
+ /**
+ * 转换当前数据集为JSON字符串
+ * @access public
+ * @param integer $options json参数
+ * @return string
+ */
+ public function toJson($options = JSON_UNESCAPED_UNICODE)
+ {
+ return json_encode($this->toArray(), $options);
+ }
+
+ public function __toString()
+ {
+ return $this->toJson();
+ }
+
+ /**
+ * 转换成数组
+ *
+ * @access public
+ * @param mixed $items
+ * @return array
+ */
+ protected function convertToArray($items)
+ {
+ if ($items instanceof self) {
+ return $items->all();
+ }
+
+ return (array) $items;
+ }
+}
diff --git a/Server/thinkphp/library/think/Config.php b/Server/thinkphp/library/think/Config.php
new file mode 100644
index 00000000..bec6222a
--- /dev/null
+++ b/Server/thinkphp/library/think/Config.php
@@ -0,0 +1,398 @@
+
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use Yaconf;
+
+class Config implements \ArrayAccess
+{
+ /**
+ * 配置参数
+ * @var array
+ */
+ protected $config = [];
+
+ /**
+ * 配置前缀
+ * @var string
+ */
+ protected $prefix = 'app';
+
+ /**
+ * 配置文件目录
+ * @var string
+ */
+ protected $path;
+
+ /**
+ * 配置文件后缀
+ * @var string
+ */
+ protected $ext;
+
+ /**
+ * 是否支持Yaconf
+ * @var bool
+ */
+ protected $yaconf;
+
+ /**
+ * 构造方法
+ * @access public
+ */
+ public function __construct($path = '', $ext = '.php')
+ {
+ $this->path = $path;
+ $this->ext = $ext;
+ $this->yaconf = class_exists('Yaconf');
+ }
+
+ public static function __make(App $app)
+ {
+ $path = $app->getConfigPath();
+ $ext = $app->getConfigExt();
+ return new static($path, $ext);
+ }
+
+ /**
+ * 设置开启Yaconf
+ * @access public
+ * @param bool|string $yaconf 是否使用Yaconf
+ * @return void
+ */
+ public function setYaconf($yaconf)
+ {
+ if ($this->yaconf) {
+ $this->yaconf = $yaconf;
+ }
+ }
+
+ /**
+ * 设置配置参数默认前缀
+ * @access public
+ * @param string $prefix 前缀
+ * @return void
+ */
+ public function setDefaultPrefix($prefix)
+ {
+ $this->prefix = $prefix;
+ }
+
+ /**
+ * 解析配置文件或内容
+ * @access public
+ * @param string $config 配置文件路径或内容
+ * @param string $type 配置解析类型
+ * @param string $name 配置名(如设置即表示二级配置)
+ * @return mixed
+ */
+ public function parse($config, $type = '', $name = '')
+ {
+ if (empty($type)) {
+ $type = pathinfo($config, PATHINFO_EXTENSION);
+ }
+
+ $object = Loader::factory($type, '\\think\\config\\driver\\', $config);
+
+ return $this->set($object->parse(), $name);
+ }
+
+ /**
+ * 加载配置文件(多种格式)
+ * @access public
+ * @param string $file 配置文件名
+ * @param string $name 一级配置名
+ * @return mixed
+ */
+ public function load($file, $name = '')
+ {
+ if (is_file($file)) {
+ $filename = $file;
+ } elseif (is_file($this->path . $file . $this->ext)) {
+ $filename = $this->path . $file . $this->ext;
+ }
+
+ if (isset($filename)) {
+ return $this->loadFile($filename, $name);
+ } elseif ($this->yaconf && Yaconf::has($file)) {
+ return $this->set(Yaconf::get($file), $name);
+ }
+
+ return $this->config;
+ }
+
+ /**
+ * 获取实际的yaconf配置参数
+ * @access protected
+ * @param string $name 配置参数名
+ * @return string
+ */
+ protected function getYaconfName($name)
+ {
+ if ($this->yaconf && is_string($this->yaconf)) {
+ return $this->yaconf . '.' . $name;
+ }
+
+ return $name;
+ }
+
+ /**
+ * 获取yaconf配置
+ * @access public
+ * @param string $name 配置参数名
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function yaconf($name, $default = null)
+ {
+ if ($this->yaconf) {
+ $yaconfName = $this->getYaconfName($name);
+
+ if (Yaconf::has($yaconfName)) {
+ return Yaconf::get($yaconfName);
+ }
+ }
+
+ return $default;
+ }
+
+ protected function loadFile($file, $name)
+ {
+ $name = strtolower($name);
+ $type = pathinfo($file, PATHINFO_EXTENSION);
+
+ if ('php' == $type) {
+ return $this->set(include $file, $name);
+ } elseif ('yaml' == $type && function_exists('yaml_parse_file')) {
+ return $this->set(yaml_parse_file($file), $name);
+ }
+
+ return $this->parse($file, $type, $name);
+ }
+
+ /**
+ * 检测配置是否存在
+ * @access public
+ * @param string $name 配置参数名(支持多级配置 .号分割)
+ * @return bool
+ */
+ public function has($name)
+ {
+ if (false === strpos($name, '.')) {
+ $name = $this->prefix . '.' . $name;
+ }
+
+ return !is_null($this->get($name));
+ }
+
+ /**
+ * 获取一级配置
+ * @access public
+ * @param string $name 一级配置名
+ * @return array
+ */
+ public function pull($name)
+ {
+ $name = strtolower($name);
+
+ if ($this->yaconf) {
+ $yaconfName = $this->getYaconfName($name);
+
+ if (Yaconf::has($yaconfName)) {
+ $config = Yaconf::get($yaconfName);
+ return isset($this->config[$name]) ? array_merge($this->config[$name], $config) : $config;
+ }
+ }
+
+ return isset($this->config[$name]) ? $this->config[$name] : [];
+ }
+
+ /**
+ * 获取配置参数 为空则获取所有配置
+ * @access public
+ * @param string $name 配置参数名(支持多级配置 .号分割)
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function get($name = null, $default = null)
+ {
+ if ($name && false === strpos($name, '.')) {
+ $name = $this->prefix . '.' . $name;
+ }
+
+ // 无参数时获取所有
+ if (empty($name)) {
+ return $this->config;
+ }
+
+ if ('.' == substr($name, -1)) {
+ return $this->pull(substr($name, 0, -1));
+ }
+
+ if ($this->yaconf) {
+ $yaconfName = $this->getYaconfName($name);
+
+ if (Yaconf::has($yaconfName)) {
+ return Yaconf::get($yaconfName);
+ }
+ }
+
+ $name = explode('.', $name);
+ $name[0] = strtolower($name[0]);
+ $config = $this->config;
+
+ // 按.拆分成多维数组进行判断
+ foreach ($name as $val) {
+ if (isset($config[$val])) {
+ $config = $config[$val];
+ } else {
+ return $default;
+ }
+ }
+
+ return $config;
+ }
+
+ /**
+ * 设置配置参数 name为数组则为批量设置
+ * @access public
+ * @param string|array $name 配置参数名(支持三级配置 .号分割)
+ * @param mixed $value 配置值
+ * @return mixed
+ */
+ public function set($name, $value = null)
+ {
+ if (is_string($name)) {
+ if (false === strpos($name, '.')) {
+ $name = $this->prefix . '.' . $name;
+ }
+
+ $name = explode('.', $name, 3);
+
+ if (count($name) == 2) {
+ $this->config[strtolower($name[0])][$name[1]] = $value;
+ } else {
+ $this->config[strtolower($name[0])][$name[1]][$name[2]] = $value;
+ }
+
+ return $value;
+ } elseif (is_array($name)) {
+ // 批量设置
+ if (!empty($value)) {
+ if (isset($this->config[$value])) {
+ $result = array_merge($this->config[$value], $name);
+ } else {
+ $result = $name;
+ }
+
+ $this->config[$value] = $result;
+ } else {
+ $result = $this->config = array_merge($this->config, $name);
+ }
+ } else {
+ // 为空直接返回 已有配置
+ $result = $this->config;
+ }
+
+ return $result;
+ }
+
+ /**
+ * 移除配置
+ * @access public
+ * @param string $name 配置参数名(支持三级配置 .号分割)
+ * @return void
+ */
+ public function remove($name)
+ {
+ if (false === strpos($name, '.')) {
+ $name = $this->prefix . '.' . $name;
+ }
+
+ $name = explode('.', $name, 3);
+
+ if (count($name) == 2) {
+ unset($this->config[strtolower($name[0])][$name[1]]);
+ } else {
+ unset($this->config[strtolower($name[0])][$name[1]][$name[2]]);
+ }
+ }
+
+ /**
+ * 重置配置参数
+ * @access public
+ * @param string $prefix 配置前缀名
+ * @return void
+ */
+ public function reset($prefix = '')
+ {
+ if ('' === $prefix) {
+ $this->config = [];
+ } else {
+ $this->config[$prefix] = [];
+ }
+ }
+
+ /**
+ * 设置配置
+ * @access public
+ * @param string $name 参数名
+ * @param mixed $value 值
+ */
+ public function __set($name, $value)
+ {
+ return $this->set($name, $value);
+ }
+
+ /**
+ * 获取配置参数
+ * @access public
+ * @param string $name 参数名
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ return $this->get($name);
+ }
+
+ /**
+ * 检测是否存在参数
+ * @access public
+ * @param string $name 参数名
+ * @return bool
+ */
+ public function __isset($name)
+ {
+ return $this->has($name);
+ }
+
+ // ArrayAccess
+ public function offsetSet($name, $value)
+ {
+ $this->set($name, $value);
+ }
+
+ public function offsetExists($name)
+ {
+ return $this->has($name);
+ }
+
+ public function offsetUnset($name)
+ {
+ $this->remove($name);
+ }
+
+ public function offsetGet($name)
+ {
+ return $this->get($name);
+ }
+}
diff --git a/Server/thinkphp/library/think/Console.php b/Server/thinkphp/library/think/Console.php
new file mode 100644
index 00000000..22f3e2c5
--- /dev/null
+++ b/Server/thinkphp/library/think/Console.php
@@ -0,0 +1,829 @@
+
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\console\Command;
+use think\console\command\Help as HelpCommand;
+use think\console\Input;
+use think\console\input\Argument as InputArgument;
+use think\console\input\Definition as InputDefinition;
+use think\console\input\Option as InputOption;
+use think\console\Output;
+use think\console\output\driver\Buffer;
+
+class Console
+{
+
+ private $name;
+ private $version;
+
+ /** @var Command[] */
+ private $commands = [];
+
+ private $wantHelps = false;
+
+ private $catchExceptions = true;
+ private $autoExit = true;
+ private $definition;
+ private $defaultCommand;
+
+ private static $defaultCommands = [
+ 'help' => "think\\console\\command\\Help",
+ 'list' => "think\\console\\command\\Lists",
+ 'build' => "think\\console\\command\\Build",
+ 'clear' => "think\\console\\command\\Clear",
+ 'make:command' => "think\\console\\command\\make\\Command",
+ 'make:controller' => "think\\console\\command\\make\\Controller",
+ 'make:model' => "think\\console\\command\\make\\Model",
+ 'make:middleware' => "think\\console\\command\\make\\Middleware",
+ 'make:validate' => "think\\console\\command\\make\\Validate",
+ 'optimize:autoload' => "think\\console\\command\\optimize\\Autoload",
+ 'optimize:config' => "think\\console\\command\\optimize\\Config",
+ 'optimize:schema' => "think\\console\\command\\optimize\\Schema",
+ 'optimize:route' => "think\\console\\command\\optimize\\Route",
+ 'run' => "think\\console\\command\\RunServer",
+ 'version' => "think\\console\\command\\Version",
+ 'route:list' => "think\\console\\command\\RouteList",
+ ];
+
+ /**
+ * Console constructor.
+ * @access public
+ * @param string $name 名称
+ * @param string $version 版本
+ * @param null|string $user 执行用户
+ */
+ public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN', $user = null)
+ {
+ $this->name = $name;
+ $this->version = $version;
+
+ if ($user) {
+ $this->setUser($user);
+ }
+
+ $this->defaultCommand = 'list';
+ $this->definition = $this->getDefaultInputDefinition();
+ }
+
+ /**
+ * 设置执行用户
+ * @param $user
+ */
+ public function setUser($user)
+ {
+ if (DIRECTORY_SEPARATOR == '\\') {
+ return;
+ }
+
+ $user = posix_getpwnam($user);
+ if ($user) {
+ posix_setuid($user['uid']);
+ posix_setgid($user['gid']);
+ }
+ }
+
+ /**
+ * 初始化 Console
+ * @access public
+ * @param bool $run 是否运行 Console
+ * @return int|Console
+ */
+ public static function init($run = true)
+ {
+ static $console;
+
+ if (!$console) {
+ $config = Container::get('config')->pull('console');
+ $console = new self($config['name'], $config['version'], $config['user']);
+
+ $commands = $console->getDefinedCommands($config);
+
+ // 添加指令集
+ $console->addCommands($commands);
+ }
+
+ if ($run) {
+ // 运行
+ return $console->run();
+ } else {
+ return $console;
+ }
+ }
+
+ /**
+ * @access public
+ * @param array $config
+ * @return array
+ */
+ public function getDefinedCommands(array $config = [])
+ {
+ $commands = self::$defaultCommands;
+
+ if (!empty($config['auto_path']) && is_dir($config['auto_path'])) {
+ // 自动加载指令类
+ $files = scandir($config['auto_path']);
+
+ if (count($files) > 2) {
+ $beforeClass = get_declared_classes();
+
+ foreach ($files as $file) {
+ if (pathinfo($file, PATHINFO_EXTENSION) == 'php') {
+ include $config['auto_path'] . $file;
+ }
+ }
+
+ $afterClass = get_declared_classes();
+ $commands = array_merge($commands, array_diff($afterClass, $beforeClass));
+ }
+ }
+
+ $file = Container::get('env')->get('app_path') . 'command.php';
+
+ if (is_file($file)) {
+ $appCommands = include $file;
+
+ if (is_array($appCommands)) {
+ $commands = array_merge($commands, $appCommands);
+ }
+ }
+
+ return $commands;
+ }
+
+ /**
+ * @access public
+ * @param string $command
+ * @param array $parameters
+ * @param string $driver
+ * @return Output|Buffer
+ */
+ public static function call($command, array $parameters = [], $driver = 'buffer')
+ {
+ $console = self::init(false);
+
+ array_unshift($parameters, $command);
+
+ $input = new Input($parameters);
+ $output = new Output($driver);
+
+ $console->setCatchExceptions(false);
+ $console->find($command)->run($input, $output);
+
+ return $output;
+ }
+
+ /**
+ * 执行当前的指令
+ * @access public
+ * @return int
+ * @throws \Exception
+ * @api
+ */
+ public function run()
+ {
+ $input = new Input();
+ $output = new Output();
+
+ $this->configureIO($input, $output);
+
+ try {
+ $exitCode = $this->doRun($input, $output);
+ } catch (\Exception $e) {
+ if (!$this->catchExceptions) {
+ throw $e;
+ }
+
+ $output->renderException($e);
+
+ $exitCode = $e->getCode();
+ if (is_numeric($exitCode)) {
+ $exitCode = (int) $exitCode;
+ if (0 === $exitCode) {
+ $exitCode = 1;
+ }
+ } else {
+ $exitCode = 1;
+ }
+ }
+
+ if ($this->autoExit) {
+ if ($exitCode > 255) {
+ $exitCode = 255;
+ }
+
+ exit($exitCode);
+ }
+
+ return $exitCode;
+ }
+
+ /**
+ * 执行指令
+ * @access public
+ * @param Input $input
+ * @param Output $output
+ * @return int
+ */
+ public function doRun(Input $input, Output $output)
+ {
+ if (true === $input->hasParameterOption(['--version', '-V'])) {
+ $output->writeln($this->getLongVersion());
+
+ return 0;
+ }
+
+ $name = $this->getCommandName($input);
+
+ if (true === $input->hasParameterOption(['--help', '-h'])) {
+ if (!$name) {
+ $name = 'help';
+ $input = new Input(['help']);
+ } else {
+ $this->wantHelps = true;
+ }
+ }
+
+ if (!$name) {
+ $name = $this->defaultCommand;
+ $input = new Input([$this->defaultCommand]);
+ }
+
+ $command = $this->find($name);
+
+ $exitCode = $this->doRunCommand($command, $input, $output);
+
+ return $exitCode;
+ }
+
+ /**
+ * 设置输入参数定义
+ * @access public
+ * @param InputDefinition $definition
+ */
+ public function setDefinition(InputDefinition $definition)
+ {
+ $this->definition = $definition;
+ }
+
+ /**
+ * 获取输入参数定义
+ * @access public
+ * @return InputDefinition The InputDefinition instance
+ */
+ public function getDefinition()
+ {
+ return $this->definition;
+ }
+
+ /**
+ * Gets the help message.
+ * @access public
+ * @return string A help message.
+ */
+ public function getHelp()
+ {
+ return $this->getLongVersion();
+ }
+
+ /**
+ * 是否捕获异常
+ * @access public
+ * @param bool $boolean
+ * @api
+ */
+ public function setCatchExceptions($boolean)
+ {
+ $this->catchExceptions = (bool) $boolean;
+ }
+
+ /**
+ * 是否自动退出
+ * @access public
+ * @param bool $boolean
+ * @api
+ */
+ public function setAutoExit($boolean)
+ {
+ $this->autoExit = (bool) $boolean;
+ }
+
+ /**
+ * 获取名称
+ * @access public
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * 设置名称
+ * @access public
+ * @param string $name
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * 获取版本
+ * @access public
+ * @return string
+ * @api
+ */
+ public function getVersion()
+ {
+ return $this->version;
+ }
+
+ /**
+ * 设置版本
+ * @access public
+ * @param string $version
+ */
+ public function setVersion($version)
+ {
+ $this->version = $version;
+ }
+
+ /**
+ * 获取完整的版本号
+ * @access public
+ * @return string
+ */
+ public function getLongVersion()
+ {
+ if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) {
+ return sprintf('%s version %s', $this->getName(), $this->getVersion());
+ }
+
+ return 'Console Tool';
+ }
+
+ /**
+ * 注册一个指令 (便于动态创建指令)
+ * @access public
+ * @param string $name 指令名
+ * @return Command
+ */
+ public function register($name)
+ {
+ return $this->add(new Command($name));
+ }
+
+ /**
+ * 添加指令集
+ * @access public
+ * @param array $commands
+ */
+ public function addCommands(array $commands)
+ {
+ foreach ($commands as $key => $command) {
+ if (is_subclass_of($command, "\\think\\console\\Command")) {
+ // 注册指令
+ $this->add($command, is_numeric($key) ? '' : $key);
+ }
+ }
+ }
+
+ /**
+ * 注册一个指令(对象)
+ * @access public
+ * @param mixed $command 指令对象或者指令类名
+ * @param string $name 指令名 留空则自动获取
+ * @return mixed
+ */
+ public function add($command, $name)
+ {
+ if ($name) {
+ $this->commands[$name] = $command;
+ return;
+ }
+
+ if (is_string($command)) {
+ $command = new $command();
+ }
+
+ $command->setConsole($this);
+
+ if (!$command->isEnabled()) {
+ $command->setConsole(null);
+ return;
+ }
+
+ if (null === $command->getDefinition()) {
+ throw new \LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)));
+ }
+
+ $this->commands[$command->getName()] = $command;
+
+ foreach ($command->getAliases() as $alias) {
+ $this->commands[$alias] = $command;
+ }
+
+ return $command;
+ }
+
+ /**
+ * 获取指令
+ * @access public
+ * @param string $name 指令名称
+ * @return Command
+ * @throws \InvalidArgumentException
+ */
+ public function get($name)
+ {
+ if (!isset($this->commands[$name])) {
+ throw new \InvalidArgumentException(sprintf('The command "%s" does not exist.', $name));
+ }
+
+ $command = $this->commands[$name];
+
+ if (is_string($command)) {
+ $command = new $command();
+ }
+
+ $command->setConsole($this);
+
+ if ($this->wantHelps) {
+ $this->wantHelps = false;
+
+ /** @var HelpCommand $helpCommand */
+ $helpCommand = $this->get('help');
+ $helpCommand->setCommand($command);
+
+ return $helpCommand;
+ }
+
+ return $command;
+ }
+
+ /**
+ * 某个指令是否存在
+ * @access public
+ * @param string $name 指令名称
+ * @return bool
+ */
+ public function has($name)
+ {
+ return isset($this->commands[$name]);
+ }
+
+ /**
+ * 获取所有的命名空间
+ * @access public
+ * @return array
+ */
+ public function getNamespaces()
+ {
+ $namespaces = [];
+ foreach ($this->commands as $name => $command) {
+ if (is_string($command)) {
+ $namespaces = array_merge($namespaces, $this->extractAllNamespaces($name));
+ } else {
+ $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));
+
+ foreach ($command->getAliases() as $alias) {
+ $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias));
+ }
+ }
+
+ }
+
+ return array_values(array_unique(array_filter($namespaces)));
+ }
+
+ /**
+ * 查找注册命名空间中的名称或缩写。
+ * @access public
+ * @param string $namespace
+ * @return string
+ * @throws \InvalidArgumentException
+ */
+ public function findNamespace($namespace)
+ {
+ $allNamespaces = $this->getNamespaces();
+ $expr = preg_replace_callback('{([^:]+|)}', function ($matches) {
+ return preg_quote($matches[1]) . '[^:]*';
+ }, $namespace);
+ $namespaces = preg_grep('{^' . $expr . '}', $allNamespaces);
+
+ if (empty($namespaces)) {
+ $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
+
+ if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) {
+ if (1 == count($alternatives)) {
+ $message .= "\n\nDid you mean this?\n ";
+ } else {
+ $message .= "\n\nDid you mean one of these?\n ";
+ }
+
+ $message .= implode("\n ", $alternatives);
+ }
+
+ throw new \InvalidArgumentException($message);
+ }
+
+ $exact = in_array($namespace, $namespaces, true);
+ if (count($namespaces) > 1 && !$exact) {
+ throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))));
+ }
+
+ return $exact ? $namespace : reset($namespaces);
+ }
+
+ /**
+ * 查找指令
+ * @access public
+ * @param string $name 名称或者别名
+ * @return Command
+ * @throws \InvalidArgumentException
+ */
+ public function find($name)
+ {
+ $allCommands = array_keys($this->commands);
+
+ $expr = preg_replace_callback('{([^:]+|)}', function ($matches) {
+ return preg_quote($matches[1]) . '[^:]*';
+ }, $name);
+
+ $commands = preg_grep('{^' . $expr . '}', $allCommands);
+
+ if (empty($commands) || count(preg_grep('{^' . $expr . '$}', $commands)) < 1) {
+ if (false !== $pos = strrpos($name, ':')) {
+ $this->findNamespace(substr($name, 0, $pos));
+ }
+
+ $message = sprintf('Command "%s" is not defined.', $name);
+
+ if ($alternatives = $this->findAlternatives($name, $allCommands)) {
+ if (1 == count($alternatives)) {
+ $message .= "\n\nDid you mean this?\n ";
+ } else {
+ $message .= "\n\nDid you mean one of these?\n ";
+ }
+ $message .= implode("\n ", $alternatives);
+ }
+
+ throw new \InvalidArgumentException($message);
+ }
+
+ $exact = in_array($name, $commands, true);
+ if (count($commands) > 1 && !$exact) {
+ $suggestions = $this->getAbbreviationSuggestions(array_values($commands));
+
+ throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions));
+ }
+
+ return $this->get($exact ? $name : reset($commands));
+ }
+
+ /**
+ * 获取所有的指令
+ * @access public
+ * @param string $namespace 命名空间
+ * @return Command[]
+ * @api
+ */
+ public function all($namespace = null)
+ {
+ if (null === $namespace) {
+ return $this->commands;
+ }
+
+ $commands = [];
+ foreach ($this->commands as $name => $command) {
+ if ($this->extractNamespace($name, substr_count($namespace, ':') + 1) === $namespace) {
+ $commands[$name] = $command;
+ }
+ }
+
+ return $commands;
+ }
+
+ /**
+ * 获取可能的指令名
+ * @access public
+ * @param array $names
+ * @return array
+ */
+ public static function getAbbreviations($names)
+ {
+ $abbrevs = [];
+ foreach ($names as $name) {
+ for ($len = strlen($name); $len > 0; --$len) {
+ $abbrev = substr($name, 0, $len);
+ $abbrevs[$abbrev][] = $name;
+ }
+ }
+
+ return $abbrevs;
+ }
+
+ /**
+ * 配置基于用户的参数和选项的输入和输出实例。
+ * @access protected
+ * @param Input $input 输入实例
+ * @param Output $output 输出实例
+ */
+ protected function configureIO(Input $input, Output $output)
+ {
+ if (true === $input->hasParameterOption(['--ansi'])) {
+ $output->setDecorated(true);
+ } elseif (true === $input->hasParameterOption(['--no-ansi'])) {
+ $output->setDecorated(false);
+ }
+
+ if (true === $input->hasParameterOption(['--no-interaction', '-n'])) {
+ $input->setInteractive(false);
+ }
+
+ if (true === $input->hasParameterOption(['--quiet', '-q'])) {
+ $output->setVerbosity(Output::VERBOSITY_QUIET);
+ } else {
+ if ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || $input->getParameterOption('--verbose') === 3) {
+ $output->setVerbosity(Output::VERBOSITY_DEBUG);
+ } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || $input->getParameterOption('--verbose') === 2) {
+ $output->setVerbosity(Output::VERBOSITY_VERY_VERBOSE);
+ } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) {
+ $output->setVerbosity(Output::VERBOSITY_VERBOSE);
+ }
+ }
+ }
+
+ /**
+ * 执行指令
+ * @access protected
+ * @param Command $command 指令实例
+ * @param Input $input 输入实例
+ * @param Output $output 输出实例
+ * @return int
+ * @throws \Exception
+ */
+ protected function doRunCommand(Command $command, Input $input, Output $output)
+ {
+ return $command->run($input, $output);
+ }
+
+ /**
+ * 获取指令的基础名称
+ * @access protected
+ * @param Input $input
+ * @return string
+ */
+ protected function getCommandName(Input $input)
+ {
+ return $input->getFirstArgument();
+ }
+
+ /**
+ * 获取默认输入定义
+ * @access protected
+ * @return InputDefinition
+ */
+ protected function getDefaultInputDefinition()
+ {
+ return new InputDefinition([
+ new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
+ new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'),
+ new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this console version'),
+ new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
+ new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'),
+ new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'),
+ new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'),
+ new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
+ ]);
+ }
+
+ public static function addDefaultCommands(array $classnames)
+ {
+ self::$defaultCommands = array_merge(self::$defaultCommands, $classnames);
+ }
+
+ /**
+ * 获取可能的建议
+ * @access private
+ * @param array $abbrevs
+ * @return string
+ */
+ private function getAbbreviationSuggestions($abbrevs)
+ {
+ return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '');
+ }
+
+ /**
+ * 返回命名空间部分
+ * @access public
+ * @param string $name 指令
+ * @param string $limit 部分的命名空间的最大数量
+ * @return string
+ */
+ public function extractNamespace($name, $limit = null)
+ {
+ $parts = explode(':', $name);
+ array_pop($parts);
+
+ return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit));
+ }
+
+ /**
+ * 查找可替代的建议
+ * @access private
+ * @param string $name
+ * @param array|\Traversable $collection
+ * @return array
+ */
+ private function findAlternatives($name, $collection)
+ {
+ $threshold = 1e3;
+ $alternatives = [];
+
+ $collectionParts = [];
+ foreach ($collection as $item) {
+ $collectionParts[$item] = explode(':', $item);
+ }
+
+ foreach (explode(':', $name) as $i => $subname) {
+ foreach ($collectionParts as $collectionName => $parts) {
+ $exists = isset($alternatives[$collectionName]);
+ if (!isset($parts[$i]) && $exists) {
+ $alternatives[$collectionName] += $threshold;
+ continue;
+ } elseif (!isset($parts[$i])) {
+ continue;
+ }
+
+ $lev = levenshtein($subname, $parts[$i]);
+ if ($lev <= strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) {
+ $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
+ } elseif ($exists) {
+ $alternatives[$collectionName] += $threshold;
+ }
+ }
+ }
+
+ foreach ($collection as $item) {
+ $lev = levenshtein($name, $item);
+ if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
+ $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
+ }
+ }
+
+ $alternatives = array_filter($alternatives, function ($lev) use ($threshold) {
+ return $lev < 2 * $threshold;
+ });
+ asort($alternatives);
+
+ return array_keys($alternatives);
+ }
+
+ /**
+ * 设置默认的指令
+ * @access public
+ * @param string $commandName The Command name
+ */
+ public function setDefaultCommand($commandName)
+ {
+ $this->defaultCommand = $commandName;
+ }
+
+ /**
+ * 返回所有的命名空间
+ * @access private
+ * @param string $name
+ * @return array
+ */
+ private function extractAllNamespaces($name)
+ {
+ $parts = explode(':', $name, -1);
+ $namespaces = [];
+
+ foreach ($parts as $part) {
+ if (count($namespaces)) {
+ $namespaces[] = end($namespaces) . ':' . $part;
+ } else {
+ $namespaces[] = $part;
+ }
+ }
+
+ return $namespaces;
+ }
+
+ public function __debugInfo()
+ {
+ $data = get_object_vars($this);
+ unset($data['commands'], $data['definition']);
+
+ return $data;
+ }
+}
diff --git a/Server/thinkphp/library/think/Container.php b/Server/thinkphp/library/think/Container.php
new file mode 100644
index 00000000..91b32aa6
--- /dev/null
+++ b/Server/thinkphp/library/think/Container.php
@@ -0,0 +1,618 @@
+
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use ArrayAccess;
+use ArrayIterator;
+use Closure;
+use Countable;
+use InvalidArgumentException;
+use IteratorAggregate;
+use ReflectionClass;
+use ReflectionException;
+use ReflectionFunction;
+use ReflectionMethod;
+use think\exception\ClassNotFoundException;
+
+/**
+ * @package think
+ * @property Build $build
+ * @property Cache $cache
+ * @property Config $config
+ * @property Cookie $cookie
+ * @property Debug $debug
+ * @property Env $env
+ * @property Hook $hook
+ * @property Lang $lang
+ * @property Middleware $middleware
+ * @property Request $request
+ * @property Response $response
+ * @property Route $route
+ * @property Session $session
+ * @property Template $template
+ * @property Url $url
+ * @property Validate $validate
+ * @property View $view
+ * @property route\RuleName $rule_name
+ * @property Log $log
+ */
+class Container implements ArrayAccess, IteratorAggregate, Countable
+{
+ /**
+ * 容器对象实例
+ * @var Container
+ */
+ protected static $instance;
+
+ /**
+ * 容器中的对象实例
+ * @var array
+ */
+ protected $instances = [];
+
+ /**
+ * 容器绑定标识
+ * @var array
+ */
+ protected $bind = [
+ 'app' => App::class,
+ 'build' => Build::class,
+ 'cache' => Cache::class,
+ 'config' => Config::class,
+ 'cookie' => Cookie::class,
+ 'debug' => Debug::class,
+ 'env' => Env::class,
+ 'hook' => Hook::class,
+ 'lang' => Lang::class,
+ 'log' => Log::class,
+ 'middleware' => Middleware::class,
+ 'request' => Request::class,
+ 'response' => Response::class,
+ 'route' => Route::class,
+ 'session' => Session::class,
+ 'template' => Template::class,
+ 'url' => Url::class,
+ 'validate' => Validate::class,
+ 'view' => View::class,
+ 'rule_name' => route\RuleName::class,
+ // 接口依赖注入
+ 'think\LoggerInterface' => Log::class,
+ ];
+
+ /**
+ * 容器标识别名
+ * @var array
+ */
+ protected $name = [];
+
+ /**
+ * 获取当前容器的实例(单例)
+ * @access public
+ * @return static
+ */
+ public static function getInstance()
+ {
+ if (is_null(static::$instance)) {
+ static::$instance = new static;
+ }
+
+ return static::$instance;
+ }
+
+ /**
+ * 设置当前容器的实例
+ * @access public
+ * @param object $instance
+ * @return void
+ */
+ public static function setInstance($instance)
+ {
+ static::$instance = $instance;
+ }
+
+ /**
+ * 获取容器中的对象实例
+ * @access public
+ * @param string $abstract 类名或者标识
+ * @param array|true $vars 变量
+ * @param bool $newInstance 是否每次创建新的实例
+ * @return object
+ */
+ public static function get($abstract, $vars = [], $newInstance = false)
+ {
+ return static::getInstance()->make($abstract, $vars, $newInstance);
+ }
+
+ /**
+ * 绑定一个类、闭包、实例、接口实现到容器
+ * @access public
+ * @param string $abstract 类标识、接口
+ * @param mixed $concrete 要绑定的类、闭包或者实例
+ * @return Container
+ */
+ public static function set($abstract, $concrete = null)
+ {
+ return static::getInstance()->bindTo($abstract, $concrete);
+ }
+
+ /**
+ * 移除容器中的对象实例
+ * @access public
+ * @param string $abstract 类标识、接口
+ * @return void
+ */
+ public static function remove($abstract)
+ {
+ return static::getInstance()->delete($abstract);
+ }
+
+ /**
+ * 清除容器中的对象实例
+ * @access public
+ * @return void
+ */
+ public static function clear()
+ {
+ return static::getInstance()->flush();
+ }
+
+ /**
+ * 绑定一个类、闭包、实例、接口实现到容器
+ * @access public
+ * @param string|array $abstract 类标识、接口
+ * @param mixed $concrete 要绑定的类、闭包或者实例
+ * @return $this
+ */
+ public function bindTo($abstract, $concrete = null)
+ {
+ if (is_array($abstract)) {
+ $this->bind = array_merge($this->bind, $abstract);
+ } elseif ($concrete instanceof Closure) {
+ $this->bind[$abstract] = $concrete;
+ } elseif (is_object($concrete)) {
+ if (isset($this->bind[$abstract])) {
+ $abstract = $this->bind[$abstract];
+ }
+ $this->instances[$abstract] = $concrete;
+ } else {
+ $this->bind[$abstract] = $concrete;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 绑定一个类实例当容器
+ * @access public
+ * @param string $abstract 类名或者标识
+ * @param object|\Closure $instance 类的实例
+ * @return $this
+ */
+ public function instance($abstract, $instance)
+ {
+ if ($instance instanceof \Closure) {
+ $this->bind[$abstract] = $instance;
+ } else {
+ if (isset($this->bind[$abstract])) {
+ $abstract = $this->bind[$abstract];
+ }
+
+ $this->instances[$abstract] = $instance;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 判断容器中是否存在类及标识
+ * @access public
+ * @param string $abstract 类名或者标识
+ * @return bool
+ */
+ public function bound($abstract)
+ {
+ return isset($this->bind[$abstract]) || isset($this->instances[$abstract]);
+ }
+
+ /**
+ * 判断容器中是否存在对象实例
+ * @access public
+ * @param string $abstract 类名或者标识
+ * @return bool
+ */
+ public function exists($abstract)
+ {
+ if (isset($this->bind[$abstract])) {
+ $abstract = $this->bind[$abstract];
+ }
+
+ return isset($this->instances[$abstract]);
+ }
+
+ /**
+ * 判断容器中是否存在类及标识
+ * @access public
+ * @param string $name 类名或者标识
+ * @return bool
+ */
+ public function has($name)
+ {
+ return $this->bound($name);
+ }
+
+ /**
+ * 创建类的实例
+ * @access public
+ * @param string $abstract 类名或者标识
+ * @param array|true $vars 变量
+ * @param bool $newInstance 是否每次创建新的实例
+ * @return object
+ */
+ public function make($abstract, $vars = [], $newInstance = false)
+ {
+ if (true === $vars) {
+ // 总是创建新的实例化对象
+ $newInstance = true;
+ $vars = [];
+ }
+
+ $abstract = isset($this->name[$abstract]) ? $this->name[$abstract] : $abstract;
+
+ if (isset($this->instances[$abstract]) && !$newInstance) {
+ return $this->instances[$abstract];
+ }
+
+ if (isset($this->bind[$abstract])) {
+ $concrete = $this->bind[$abstract];
+
+ if ($concrete instanceof Closure) {
+ $object = $this->invokeFunction($concrete, $vars);
+ } else {
+ $this->name[$abstract] = $concrete;
+ return $this->make($concrete, $vars, $newInstance);
+ }
+ } else {
+ $object = $this->invokeClass($abstract, $vars);
+ }
+
+ if (!$newInstance) {
+ $this->instances[$abstract] = $object;
+ }
+
+ return $object;
+ }
+
+ /**
+ * 删除容器中的对象实例
+ * @access public
+ * @param string|array $abstract 类名或者标识
+ * @return void
+ */
+ public function delete($abstract)
+ {
+ foreach ((array) $abstract as $name) {
+ $name = isset($this->name[$name]) ? $this->name[$name] : $name;
+
+ if (isset($this->instances[$name])) {
+ unset($this->instances[$name]);
+ }
+ }
+ }
+
+ /**
+ * 获取容器中的对象实例
+ * @access public
+ * @return array
+ */
+ public function all()
+ {
+ return $this->instances;
+ }
+
+ /**
+ * 清除容器中的对象实例
+ * @access public
+ * @return void
+ */
+ public function flush()
+ {
+ $this->instances = [];
+ $this->bind = [];
+ $this->name = [];
+ }
+
+ /**
+ * 执行函数或者闭包方法 支持参数调用
+ * @access public
+ * @param mixed $function 函数或者闭包
+ * @param array $vars 参数
+ * @return mixed
+ */
+ public function invokeFunction($function, $vars = [])
+ {
+ try {
+ $reflect = new ReflectionFunction($function);
+
+ $args = $this->bindParams($reflect, $vars);
+
+ return call_user_func_array($function, $args);
+ } catch (ReflectionException $e) {
+ throw new Exception('function not exists: ' . $function . '()');
+ }
+ }
+
+ /**
+ * 调用反射执行类的方法 支持参数绑定
+ * @access public
+ * @param mixed $method 方法
+ * @param array $vars 参数
+ * @return mixed
+ */
+ public function invokeMethod($method, $vars = [])
+ {
+ try {
+ if (is_array($method)) {
+ $class = is_object($method[0]) ? $method[0] : $this->invokeClass($method[0]);
+ $reflect = new ReflectionMethod($class, $method[1]);
+ } else {
+ // 静态方法
+ $reflect = new ReflectionMethod($method);
+ }
+
+ $args = $this->bindParams($reflect, $vars);
+
+ return $reflect->invokeArgs(isset($class) ? $class : null, $args);
+ } catch (ReflectionException $e) {
+ if (is_array($method) && is_object($method[0])) {
+ $method[0] = get_class($method[0]);
+ }
+
+ throw new Exception('method not exists: ' . (is_array($method) ? $method[0] . '::' . $method[1] : $method) . '()');
+ }
+ }
+
+ /**
+ * 调用反射执行类的方法 支持参数绑定
+ * @access public
+ * @param object $instance 对象实例
+ * @param mixed $reflect 反射类
+ * @param array $vars 参数
+ * @return mixed
+ */
+ public function invokeReflectMethod($instance, $reflect, $vars = [])
+ {
+ $args = $this->bindParams($reflect, $vars);
+
+ return $reflect->invokeArgs($instance, $args);
+ }
+
+ /**
+ * 调用反射执行callable 支持参数绑定
+ * @access public
+ * @param mixed $callable
+ * @param array $vars 参数
+ * @return mixed
+ */
+ public function invoke($callable, $vars = [])
+ {
+ if ($callable instanceof Closure) {
+ return $this->invokeFunction($callable, $vars);
+ }
+
+ return $this->invokeMethod($callable, $vars);
+ }
+
+ /**
+ * 调用反射执行类的实例化 支持依赖注入
+ * @access public
+ * @param string $class 类名
+ * @param array $vars 参数
+ * @return mixed
+ */
+ public function invokeClass($class, $vars = [])
+ {
+ try {
+ $reflect = new ReflectionClass($class);
+
+ if ($reflect->hasMethod('__make')) {
+ $method = new ReflectionMethod($class, '__make');
+
+ if ($method->isPublic() && $method->isStatic()) {
+ $args = $this->bindParams($method, $vars);
+ return $method->invokeArgs(null, $args);
+ }
+ }
+
+ $constructor = $reflect->getConstructor();
+
+ $args = $constructor ? $this->bindParams($constructor, $vars) : [];
+
+ return $reflect->newInstanceArgs($args);
+
+ } catch (ReflectionException $e) {
+ throw new ClassNotFoundException('class not exists: ' . $class, $class);
+ }
+ }
+
+ /**
+ * 绑定参数
+ * @access protected
+ * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类
+ * @param array $vars 参数
+ * @return array
+ */
+ protected function bindParams($reflect, $vars = [])
+ {
+ if ($reflect->getNumberOfParameters() == 0) {
+ return [];
+ }
+
+ // 判断数组类型 数字数组时按顺序绑定参数
+ reset($vars);
+ $type = key($vars) === 0 ? 1 : 0;
+ $params = $reflect->getParameters();
+
+ if (PHP_VERSION > 8.0) {
+ $args = $this->parseParamsForPHP8($params, $vars, $type);
+ } else {
+ $args = $this->parseParams($params, $vars, $type);
+ }
+
+ return $args;
+ }
+
+ /**
+ * 解析参数
+ * @access protected
+ * @param array $params 参数列表
+ * @param array $vars 参数数据
+ * @param int $type 参数类别
+ * @return array
+ */
+ protected function parseParams($params, $vars, $type)
+ {
+ foreach ($params as $param) {
+ $name = $param->getName();
+ $lowerName = Loader::parseName($name);
+ $class = $param->getClass();
+
+ if ($class) {
+ $args[] = $this->getObjectParam($class->getName(), $vars);
+ } elseif (1 == $type && !empty($vars)) {
+ $args[] = array_shift($vars);
+ } elseif (0 == $type && isset($vars[$name])) {
+ $args[] = $vars[$name];
+ } elseif (0 == $type && isset($vars[$lowerName])) {
+ $args[] = $vars[$lowerName];
+ } elseif ($param->isDefaultValueAvailable()) {
+ $args[] = $param->getDefaultValue();
+ } else {
+ throw new InvalidArgumentException('method param miss:' . $name);
+ }
+ }
+ return $args;
+ }
+
+ /**
+ * 解析参数
+ * @access protected
+ * @param array $params 参数列表
+ * @param array $vars 参数数据
+ * @param int $type 参数类别
+ * @return array
+ */
+ protected function parseParamsForPHP8($params, $vars, $type)
+ {
+ foreach ($params as $param) {
+ $name = $param->getName();
+ $lowerName = Loader::parseName($name);
+ $reflectionType = $param->getType();
+
+ if ($reflectionType && $reflectionType->isBuiltin() === false) {
+ $args[] = $this->getObjectParam($reflectionType->getName(), $vars);
+ } elseif (1 == $type && !empty($vars)) {
+ $args[] = array_shift($vars);
+ } elseif (0 == $type && array_key_exists($name, $vars)) {
+ $args[] = $vars[$name];
+ } elseif (0 == $type && array_key_exists($lowerName, $vars)) {
+ $args[] = $vars[$lowerName];
+ } elseif ($param->isDefaultValueAvailable()) {
+ $args[] = $param->getDefaultValue();
+ } else {
+ throw new InvalidArgumentException('method param miss:' . $name);
+ }
+ }
+ return $args;
+ }
+
+ /**
+ * 获取对象类型的参数值
+ * @access protected
+ * @param string $className 类名
+ * @param array $vars 参数
+ * @return mixed
+ */
+ protected function getObjectParam($className, &$vars)
+ {
+ $array = $vars;
+ $value = array_shift($array);
+
+ if ($value instanceof $className) {
+ $result = $value;
+ array_shift($vars);
+ } else {
+ $result = $this->make($className);
+ }
+
+ return $result;
+ }
+
+ public function __set($name, $value)
+ {
+ $this->bindTo($name, $value);
+ }
+
+ public function __get($name)
+ {
+ return $this->make($name);
+ }
+
+ public function __isset($name)
+ {
+ return $this->bound($name);
+ }
+
+ public function __unset($name)
+ {
+ $this->delete($name);
+ }
+
+ public function offsetExists($key)
+ {
+ return $this->__isset($key);
+ }
+
+ public function offsetGet($key)
+ {
+ return $this->__get($key);
+ }
+
+ public function offsetSet($key, $value)
+ {
+ $this->__set($key, $value);
+ }
+
+ public function offsetUnset($key)
+ {
+ $this->__unset($key);
+ }
+
+ //Countable
+ public function count()
+ {
+ return count($this->instances);
+ }
+
+ //IteratorAggregate
+ public function getIterator()
+ {
+ return new ArrayIterator($this->instances);
+ }
+
+ public function __debugInfo()
+ {
+ $data = get_object_vars($this);
+ unset($data['instances'], $data['instance']);
+
+ return $data;
+ }
+}
diff --git a/Server/thinkphp/library/think/Controller.php b/Server/thinkphp/library/think/Controller.php
new file mode 100644
index 00000000..966eaaa8
--- /dev/null
+++ b/Server/thinkphp/library/think/Controller.php
@@ -0,0 +1,287 @@
+
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\exception\ValidateException;
+use traits\controller\Jump;
+
+class Controller
+{
+ use Jump;
+
+ /**
+ * 视图类实例
+ * @var \think\View
+ */
+ protected $view;
+
+ /**
+ * Request实例
+ * @var \think\Request
+ */
+ protected $request;
+
+ /**
+ * 验证失败是否抛出异常
+ * @var bool
+ */
+ protected $failException = false;
+
+ /**
+ * 是否批量验证
+ * @var bool
+ */
+ protected $batchValidate = false;
+
+ /**
+ * 前置操作方法列表(即将废弃)
+ * @var array $beforeActionList
+ */
+ protected $beforeActionList = [];
+
+ /**
+ * 控制器中间件
+ * @var array
+ */
+ protected $middleware = [];
+
+ /**
+ * 构造方法
+ * @access public
+ */
+ public function __construct(App $app = null)
+ {
+ $this->app = $app ?: Container::get('app');
+ $this->request = $this->app['request'];
+ $this->view = $this->app['view'];
+
+ // 控制器初始化
+ $this->initialize();
+
+ $this->registerMiddleware();
+
+ // 前置操作方法 即将废弃
+ foreach ((array) $this->beforeActionList as $method => $options) {
+ is_numeric($method) ?
+ $this->beforeAction($options) :
+ $this->beforeAction($method, $options);
+ }
+ }
+
+ // 初始化
+ protected function initialize()
+ {}
+
+ // 注册控制器中间件
+ public function registerMiddleware()
+ {
+ foreach ($this->middleware as $key => $val) {
+ if (!is_int($key)) {
+ $only = $except = null;
+
+ if (isset($val['only'])) {
+ $only = array_map(function ($item) {
+ return strtolower($item);
+ }, $val['only']);
+ } elseif (isset($val['except'])) {
+ $except = array_map(function ($item) {
+ return strtolower($item);
+ }, $val['except']);
+ }
+
+ if (isset($only) && !in_array($this->request->action(), $only)) {
+ continue;
+ } elseif (isset($except) && in_array($this->request->action(), $except)) {
+ continue;
+ } else {
+ $val = $key;
+ }
+ }
+
+ $this->app['middleware']->controller($val);
+ }
+ }
+
+ /**
+ * 前置操作
+ * @access protected
+ * @param string $method 前置操作方法名
+ * @param array $options 调用参数 ['only'=>[...]] 或者['except'=>[...]]
+ */
+ protected function beforeAction($method, $options = [])
+ {
+ if (isset($options['only'])) {
+ if (is_string($options['only'])) {
+ $options['only'] = explode(',', $options['only']);
+ }
+
+ $only = array_map(function ($val) {
+ return strtolower($val);
+ }, $options['only']);
+
+ if (!in_array($this->request->action(), $only)) {
+ return;
+ }
+ } elseif (isset($options['except'])) {
+ if (is_string($options['except'])) {
+ $options['except'] = explode(',', $options['except']);
+ }
+
+ $except = array_map(function ($val) {
+ return strtolower($val);
+ }, $options['except']);
+
+ if (in_array($this->request->action(), $except)) {
+ return;
+ }
+ }
+
+ call_user_func([$this, $method]);
+ }
+
+ /**
+ * 加载模板输出
+ * @access protected
+ * @param string $template 模板文件名
+ * @param array $vars 模板输出变量
+ * @param array $config 模板参数
+ * @return mixed
+ */
+ protected function fetch($template = '', $vars = [], $config = [])
+ {
+ return Response::create($template, 'view')->assign($vars)->config($config);
+ }
+
+ /**
+ * 渲染内容输出
+ * @access protected
+ * @param string $content 模板内容
+ * @param array $vars 模板输出变量
+ * @param array $config 模板参数
+ * @return mixed
+ */
+ protected function display($content = '', $vars = [], $config = [])
+ {
+ return Response::create($content, 'view')->assign($vars)->config($config)->isContent(true);
+ }
+
+ /**
+ * 模板变量赋值
+ * @access protected
+ * @param mixed $name 要显示的模板变量
+ * @param mixed $value 变量的值
+ * @return $this
+ */
+ protected function assign($name, $value = '')
+ {
+ $this->view->assign($name, $value);
+
+ return $this;
+ }
+
+ /**
+ * 视图过滤
+ * @access protected
+ * @param Callable $filter 过滤方法或闭包
+ * @return $this
+ */
+ protected function filter($filter)
+ {
+ $this->view->filter($filter);
+
+ return $this;
+ }
+
+ /**
+ * 初始化模板引擎
+ * @access protected
+ * @param array|string $engine 引擎参数
+ * @return $this
+ */
+ protected function engine($engine)
+ {
+ $this->view->engine($engine);
+
+ return $this;
+ }
+
+ /**
+ * 设置验证失败后是否抛出异常
+ * @access protected
+ * @param bool $fail 是否抛出异常
+ * @return $this
+ */
+ protected function validateFailException($fail = true)
+ {
+ $this->failException = $fail;
+
+ return $this;
+ }
+
+ /**
+ * 验证数据
+ * @access protected
+ * @param array $data 数据
+ * @param string|array $validate 验证器名或者验证规则数组
+ * @param array $message 提示信息
+ * @param bool $batch 是否批量验证
+ * @param mixed $callback 回调方法(闭包)
+ * @return array|string|true
+ * @throws ValidateException
+ */
+ protected function validate($data, $validate, $message = [], $batch = false, $callback = null)
+ {
+ if (is_array($validate)) {
+ $v = $this->app->validate();
+ $v->rule($validate);
+ } else {
+ if (strpos($validate, '.')) {
+ // 支持场景
+ list($validate, $scene) = explode('.', $validate);
+ }
+ $v = $this->app->validate($validate);
+ if (!empty($scene)) {
+ $v->scene($scene);
+ }
+ }
+
+ // 是否批量验证
+ if ($batch || $this->batchValidate) {
+ $v->batch(true);
+ }
+
+ if (is_array($message)) {
+ $v->message($message);
+ }
+
+ if ($callback && is_callable($callback)) {
+ call_user_func_array($callback, [$v, &$data]);
+ }
+
+ if (!$v->check($data)) {
+ if ($this->failException) {
+ throw new ValidateException($v->getError());
+ }
+ return $v->getError();
+ }
+
+ return true;
+ }
+
+ public function __debugInfo()
+ {
+ $data = get_object_vars($this);
+ unset($data['app'], $data['request']);
+
+ return $data;
+ }
+}
diff --git a/Server/thinkphp/library/think/Cookie.php b/Server/thinkphp/library/think/Cookie.php
new file mode 100644
index 00000000..6a9fb1ee
--- /dev/null
+++ b/Server/thinkphp/library/think/Cookie.php
@@ -0,0 +1,268 @@
+
+// +----------------------------------------------------------------------
+
+namespace think;
+
+class Cookie
+{
+ /**
+ * 配置参数
+ * @var array
+ */
+ protected $config = [
+ // cookie 名称前缀
+ 'prefix' => '',
+ // cookie 保存时间
+ 'expire' => 0,
+ // cookie 保存路径
+ 'path' => '/',
+ // cookie 有效域名
+ 'domain' => '',
+ // cookie 启用安全传输
+ 'secure' => false,
+ // httponly设置
+ 'httponly' => false,
+ // 是否使用 setcookie
+ 'setcookie' => true,
+ ];
+
+ /**
+ * 构造方法
+ * @access public
+ */
+ public function __construct(array $config = [])
+ {
+ $this->init($config);
+ }
+
+ /**
+ * Cookie初始化
+ * @access public
+ * @param array $config
+ * @return void
+ */
+ public function init(array $config = [])
+ {
+ $this->config = array_merge($this->config, array_change_key_case($config));
+
+ if (!empty($this->config['httponly']) && PHP_SESSION_ACTIVE != session_status()) {
+ ini_set('session.cookie_httponly', 1);
+ }
+ }
+
+ public static function __make(Config $config)
+ {
+ return new static($config->pull('cookie'));
+ }
+
+ /**
+ * 设置或者获取cookie作用域(前缀)
+ * @access public
+ * @param string $prefix
+ * @return string|void
+ */
+ public function prefix($prefix = '')
+ {
+ if (empty($prefix)) {
+ return $this->config['prefix'];
+ }
+
+ $this->config['prefix'] = $prefix;
+ }
+
+ /**
+ * Cookie 设置、获取、删除
+ *
+ * @access public
+ * @param string $name cookie名称
+ * @param mixed $value cookie值
+ * @param mixed $option 可选参数 可能会是 null|integer|string
+ * @return void
+ */
+ public function set($name, $value = '', $option = null)
+ {
+ // 参数设置(会覆盖黙认设置)
+ if (!is_null($option)) {
+ if (is_numeric($option)) {
+ $option = ['expire' => $option];
+ } elseif (is_string($option)) {
+ parse_str($option, $option);
+ }
+
+ $config = array_merge($this->config, array_change_key_case($option));
+ } else {
+ $config = $this->config;
+ }
+
+ $name = $config['prefix'] . $name;
+
+ // 设置cookie
+ if (is_array($value)) {
+ array_walk_recursive($value, [$this, 'jsonFormatProtect'], 'encode');
+ $value = 'think:' . json_encode($value);
+ }
+
+ $expire = !empty($config['expire']) ? $_SERVER['REQUEST_TIME'] + intval($config['expire']) : 0;
+
+ if ($config['setcookie']) {
+ $this->setCookie($name, $value, $expire, $config);
+ }
+
+ $_COOKIE[$name] = $value;
+ }
+
+ /**
+ * Cookie 设置保存
+ *
+ * @access public
+ * @param string $name cookie名称
+ * @param mixed $value cookie值
+ * @param array $option 可选参数
+ * @return void
+ */
+ protected function setCookie($name, $value, $expire, $option = [])
+ {
+ setcookie($name, $value, $expire, $option['path'], $option['domain'], $option['secure'], $option['httponly']);
+ }
+
+ /**
+ * 永久保存Cookie数据
+ * @access public
+ * @param string $name cookie名称
+ * @param mixed $value cookie值
+ * @param mixed $option 可选参数 可能会是 null|integer|string
+ * @return void
+ */
+ public function forever($name, $value = '', $option = null)
+ {
+ if (is_null($option) || is_numeric($option)) {
+ $option = [];
+ }
+
+ $option['expire'] = 315360000;
+
+ $this->set($name, $value, $option);
+ }
+
+ /**
+ * 判断Cookie数据
+ * @access public
+ * @param string $name cookie名称
+ * @param string|null $prefix cookie前缀
+ * @return bool
+ */
+ public function has($name, $prefix = null)
+ {
+ $prefix = !is_null($prefix) ? $prefix : $this->config['prefix'];
+ $name = $prefix . $name;
+
+ return isset($_COOKIE[$name]);
+ }
+
+ /**
+ * Cookie获取
+ * @access public
+ * @param string $name cookie名称 留空获取全部
+ * @param string|null $prefix cookie前缀
+ * @return mixed
+ */
+ public function get($name = '', $prefix = null)
+ {
+ $prefix = !is_null($prefix) ? $prefix : $this->config['prefix'];
+ $key = $prefix . $name;
+
+ if ('' == $name) {
+ if ($prefix) {
+ $value = [];
+ foreach ($_COOKIE as $k => $val) {
+ if (0 === strpos($k, $prefix)) {
+ $value[$k] = $val;
+ }
+ }
+ } else {
+ $value = $_COOKIE;
+ }
+ } elseif (isset($_COOKIE[$key])) {
+ $value = $_COOKIE[$key];
+
+ if (0 === strpos($value, 'think:')) {
+ $value = substr($value, 6);
+ $value = json_decode($value, true);
+ array_walk_recursive($value, [$this, 'jsonFormatProtect'], 'decode');
+ }
+ } else {
+ $value = null;
+ }
+
+ return $value;
+ }
+
+ /**
+ * Cookie删除
+ * @access public
+ * @param string $name cookie名称
+ * @param string|null $prefix cookie前缀
+ * @return void
+ */
+ public function delete($name, $prefix = null)
+ {
+ $config = $this->config;
+ $prefix = !is_null($prefix) ? $prefix : $config['prefix'];
+ $name = $prefix . $name;
+
+ if ($config['setcookie']) {
+ $this->setcookie($name, '', $_SERVER['REQUEST_TIME'] - 3600, $config);
+ }
+
+ // 删除指定cookie
+ unset($_COOKIE[$name]);
+ }
+
+ /**
+ * Cookie清空
+ * @access public
+ * @param string|null $prefix cookie前缀
+ * @return void
+ */
+ public function clear($prefix = null)
+ {
+ // 清除指定前缀的所有cookie
+ if (empty($_COOKIE)) {
+ return;
+ }
+
+ // 要删除的cookie前缀,不指定则删除config设置的指定前缀
+ $config = $this->config;
+ $prefix = !is_null($prefix) ? $prefix : $config['prefix'];
+
+ if ($prefix) {
+ // 如果前缀为空字符串将不作处理直接返回
+ foreach ($_COOKIE as $key => $val) {
+ if (0 === strpos($key, $prefix)) {
+ if ($config['setcookie']) {
+ $this->setcookie($key, '', $_SERVER['REQUEST_TIME'] - 3600, $config);
+ }
+ unset($_COOKIE[$key]);
+ }
+ }
+ }
+
+ return;
+ }
+
+ private function jsonFormatProtect(&$val, $key, $type = 'encode')
+ {
+ if (!empty($val) && true !== $val) {
+ $val = 'decode' == $type ? urldecode($val) : urlencode($val);
+ }
+ }
+
+}
diff --git a/Server/thinkphp/library/think/Db.php b/Server/thinkphp/library/think/Db.php
new file mode 100644
index 00000000..9280eac0
--- /dev/null
+++ b/Server/thinkphp/library/think/Db.php
@@ -0,0 +1,197 @@
+
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\db\Connection;
+
+/**
+ * Class Db
+ * @package think
+ * @method \think\db\Query master() static 从主服务器读取数据
+ * @method \think\db\Query readMaster(bool $all = false) static 后续从主服务器读取数据
+ * @method \think\db\Query table(string $table) static 指定数据表(含前缀)
+ * @method \think\db\Query name(string $name) static 指定数据表(不含前缀)
+ * @method \think\db\Expression raw(string $value) static 使用表达式设置数据
+ * @method \think\db\Query where(mixed $field, string $op = null, mixed $condition = null) static 查询条件
+ * @method \think\db\Query whereRaw(string $where, array $bind = []) static 表达式查询
+ * @method \think\db\Query whereExp(string $field, string $condition, array $bind = []) static 字段表达式查询
+ * @method \think\db\Query when(mixed $condition, mixed $query, mixed $otherwise = null) static 条件查询
+ * @method \think\db\Query join(mixed $join, mixed $condition = null, string $type = 'INNER') static JOIN查询
+ * @method \think\db\Query view(mixed $join, mixed $field = null, mixed $on = null, string $type = 'INNER') static 视图查询
+ * @method \think\db\Query field(mixed $field, boolean $except = false) static 指定查询字段
+ * @method \think\db\Query fieldRaw(string $field, array $bind = []) static 指定查询字段
+ * @method \think\db\Query union(mixed $union, boolean $all = false) static UNION查询
+ * @method \think\db\Query limit(mixed $offset, integer $length = null) static 查询LIMIT
+ * @method \think\db\Query order(mixed $field, string $order = null) static 查询ORDER
+ * @method \think\db\Query orderRaw(string $field, array $bind = []) static 查询ORDER
+ * @method \think\db\Query cache(mixed $key = null , integer $expire = null) static 设置查询缓存
+ * @method \think\db\Query withAttr(string $name,callable $callback = null) static 使用获取器获取数据
+ * @method mixed value(string $field) static 获取某个字段的值
+ * @method array column(string $field, string $key = '') static 获取某个列的值
+ * @method mixed find(mixed $data = null) static 查询单个记录
+ * @method mixed select(mixed $data = null) static 查询多个记录
+ * @method integer insert(array $data, boolean $replace = false, boolean $getLastInsID = false, string $sequence = null) static 插入一条记录
+ * @method integer insertGetId(array $data, boolean $replace = false, string $sequence = null) static 插入一条记录并返回自增ID
+ * @method integer insertAll(array $dataSet) static 插入多条记录
+ * @method integer update(array $data) static 更新记录
+ * @method integer delete(mixed $data = null) static 删除记录
+ * @method boolean chunk(integer $count, callable $callback, string $column = null) static 分块获取数据
+ * @method \Generator cursor(mixed $data = null) static 使用游标查找记录
+ * @method mixed query(string $sql, array $bind = [], boolean $master = false, bool $pdo = false) static SQL查询
+ * @method integer execute(string $sql, array $bind = [], boolean $fetch = false, boolean $getLastInsID = false, string $sequence = null) static SQL执行
+ * @method \think\Paginator paginate(integer $listRows = 15, mixed $simple = null, array $config = []) static 分页查询
+ * @method mixed transaction(callable $callback) static 执行数据库事务
+ * @method void startTrans() static 启动事务
+ * @method void commit() static 用于非自动提交状态下面的查询提交
+ * @method void rollback() static 事务回滚
+ * @method boolean batchQuery(array $sqlArray) static 批处理执行SQL语句
+ * @method string getLastInsID(string $sequence = null) static 获取最近插入的ID
+ */
+class Db
+{
+ /**
+ * 当前数据库连接对象
+ * @var Connection
+ */
+ protected static $connection;
+
+ /**
+ * 数据库配置
+ * @var array
+ */
+ protected static $config = [];
+
+ /**
+ * 查询次数
+ * @var integer
+ */
+ public static $queryTimes = 0;
+
+ /**
+ * 执行次数
+ * @var integer
+ */
+ public static $executeTimes = 0;
+
+ /**
+ * 配置
+ * @access public
+ * @param mixed $config
+ * @return void
+ */
+ public static function init($config = [])
+ {
+ self::$config = $config;
+
+ if (empty($config['query'])) {
+ self::$config['query'] = '\\think\\db\\Query';
+ }
+ }
+
+ /**
+ * 获取数据库配置
+ * @access public
+ * @param string $config 配置名称
+ * @return mixed
+ */
+ public static function getConfig($name = '')
+ {
+ if ('' === $name) {
+ return self::$config;
+ }
+
+ return isset(self::$config[$name]) ? self::$config[$name] : null;
+ }
+
+ /**
+ * 切换数据库连接
+ * @access public
+ * @param mixed $config 连接配置
+ * @param bool|string $name 连接标识 true 强制重新连接
+ * @param string $query 查询对象类名
+ * @return mixed 返回查询对象实例
+ * @throws Exception
+ */
+ public static function connect($config = [], $name = false, $query = '')
+ {
+ // 解析配置参数
+ $options = self::parseConfig($config ?: self::$config);
+
+ $query = $query ?: $options['query'];
+
+ // 创建数据库连接对象实例
+ self::$connection = Connection::instance($options, $name);
+
+ return new $query(self::$connection);
+ }
+
+ /**
+ * 数据库连接参数解析
+ * @access private
+ * @param mixed $config
+ * @return array
+ */
+ private static function parseConfig($config)
+ {
+ if (is_string($config) && false === strpos($config, '/')) {
+ // 支持读取配置参数
+ $config = isset(self::$config[$config]) ? self::$config[$config] : self::$config;
+ }
+
+ $result = is_string($config) ? self::parseDsnConfig($config) : $config;
+
+ if (empty($result['query'])) {
+ $result['query'] = self::$config['query'];
+ }
+
+ return $result;
+ }
+
+ /**
+ * DSN解析
+ * 格式: mysql://username:passwd@localhost:3306/DbName?param1=val1¶m2=val2#utf8
+ * @access private
+ * @param string $dsnStr
+ * @return array
+ */
+ private static function parseDsnConfig($dsnStr)
+ {
+ $info = parse_url($dsnStr);
+
+ if (!$info) {
+ return [];
+ }
+
+ $dsn = [
+ 'type' => $info['scheme'],
+ 'username' => isset($info['user']) ? $info['user'] : '',
+ 'password' => isset($info['pass']) ? $info['pass'] : '',
+ 'hostname' => isset($info['host']) ? $info['host'] : '',
+ 'hostport' => isset($info['port']) ? $info['port'] : '',
+ 'database' => !empty($info['path']) ? ltrim($info['path'], '/') : '',
+ 'charset' => isset($info['fragment']) ? $info['fragment'] : 'utf8',
+ ];
+
+ if (isset($info['query'])) {
+ parse_str($info['query'], $dsn['params']);
+ } else {
+ $dsn['params'] = [];
+ }
+
+ return $dsn;
+ }
+
+ public static function __callStatic($method, $args)
+ {
+ return call_user_func_array([static::connect(), $method], $args);
+ }
+}
diff --git a/Server/thinkphp/library/think/Debug.php b/Server/thinkphp/library/think/Debug.php
new file mode 100644
index 00000000..776e1787
--- /dev/null
+++ b/Server/thinkphp/library/think/Debug.php
@@ -0,0 +1,278 @@
+
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\model\Collection as ModelCollection;
+use think\response\Redirect;
+
+class Debug
+{
+ /**
+ * 配置参数
+ * @var array
+ */
+ protected $config = [];
+
+ /**
+ * 区间时间信息
+ * @var array
+ */
+ protected $info = [];
+
+ /**
+ * 区间内存信息
+ * @var array
+ */
+ protected $mem = [];
+
+ /**
+ * 应用对象
+ * @var App
+ */
+ protected $app;
+
+ public function __construct(App $app, array $config = [])
+ {
+ $this->app = $app;
+ $this->config = $config;
+ }
+
+ public static function __make(App $app, Config $config)
+ {
+ return new static($app, $config->pull('trace'));
+ }
+
+ public function setConfig(array $config)
+ {
+ $this->config = array_merge($this->config, $config);
+ }
+
+ /**
+ * 记录时间(微秒)和内存使用情况
+ * @access public
+ * @param string $name 标记位置
+ * @param mixed $value 标记值 留空则取当前 time 表示仅记录时间 否则同时记录时间和内存
+ * @return void
+ */
+ public function remark($name, $value = '')
+ {
+ // 记录时间和内存使用
+ $this->info[$name] = is_float($value) ? $value : microtime(true);
+
+ if ('time' != $value) {
+ $this->mem['mem'][$name] = is_float($value) ? $value : memory_get_usage();
+ $this->mem['peak'][$name] = memory_get_peak_usage();
+ }
+ }
+
+ /**
+ * 统计某个区间的时间(微秒)使用情况
+ * @access public
+ * @param string $start 开始标签
+ * @param string $end 结束标签
+ * @param integer|string $dec 小数位
+ * @return integer
+ */
+ public function getRangeTime($start, $end, $dec = 6)
+ {
+ if (!isset($this->info[$end])) {
+ $this->info[$end] = microtime(true);
+ }
+
+ return number_format(($this->info[$end] - $this->info[$start]), $dec);
+ }
+
+ /**
+ * 统计从开始到统计时的时间(微秒)使用情况
+ * @access public
+ * @param integer|string $dec 小数位
+ * @return integer
+ */
+ public function getUseTime($dec = 6)
+ {
+ return number_format((microtime(true) - $this->app->getBeginTime()), $dec);
+ }
+
+ /**
+ * 获取当前访问的吞吐率情况
+ * @access public
+ * @return string
+ */
+ public function getThroughputRate()
+ {
+ return number_format(1 / $this->getUseTime(), 2) . 'req/s';
+ }
+
+ /**
+ * 记录区间的内存使用情况
+ * @access public
+ * @param string $start 开始标签
+ * @param string $end 结束标签
+ * @param integer|string $dec 小数位
+ * @return string
+ */
+ public function getRangeMem($start, $end, $dec = 2)
+ {
+ if (!isset($this->mem['mem'][$end])) {
+ $this->mem['mem'][$end] = memory_get_usage();
+ }
+
+ $size = $this->mem['mem'][$end] - $this->mem['mem'][$start];
+ $a = ['B', 'KB', 'MB', 'GB', 'TB'];
+ $pos = 0;
+
+ while ($size >= 1024) {
+ $size /= 1024;
+ $pos++;
+ }
+
+ return round($size, $dec) . " " . $a[$pos];
+ }
+
+ /**
+ * 统计从开始到统计时的内存使用情况
+ * @access public
+ * @param integer|string $dec 小数位
+ * @return string
+ */
+ public function getUseMem($dec = 2)
+ {
+ $size = memory_get_usage() - $this->app->getBeginMem();
+ $a = ['B', 'KB', 'MB', 'GB', 'TB'];
+ $pos = 0;
+
+ while ($size >= 1024) {
+ $size /= 1024;
+ $pos++;
+ }
+
+ return round($size, $dec) . " " . $a[$pos];
+ }
+
+ /**
+ * 统计区间的内存峰值情况
+ * @access public
+ * @param string $start 开始标签
+ * @param string $end 结束标签
+ * @param integer|string $dec 小数位
+ * @return string
+ */
+ public function getMemPeak($start, $end, $dec = 2)
+ {
+ if (!isset($this->mem['peak'][$end])) {
+ $this->mem['peak'][$end] = memory_get_peak_usage();
+ }
+
+ $size = $this->mem['peak'][$end] - $this->mem['peak'][$start];
+ $a = ['B', 'KB', 'MB', 'GB', 'TB'];
+ $pos = 0;
+
+ while ($size >= 1024) {
+ $size /= 1024;
+ $pos++;
+ }
+
+ return round($size, $dec) . " " . $a[$pos];
+ }
+
+ /**
+ * 获取文件加载信息
+ * @access public
+ * @param bool $detail 是否显示详细
+ * @return integer|array
+ */
+ public function getFile($detail = false)
+ {
+ if ($detail) {
+ $files = get_included_files();
+ $info = [];
+
+ foreach ($files as $key => $file) {
+ $info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )';
+ }
+
+ return $info;
+ }
+
+ return count(get_included_files());
+ }
+
+ /**
+ * 浏览器友好的变量输出
+ * @access public
+ * @param mixed $var 变量
+ * @param boolean $echo 是否输出 默认为true 如果为false 则返回输出字符串
+ * @param string $label 标签 默认为空
+ * @param integer $flags htmlspecialchars flags
+ * @return void|string
+ */
+ public function dump($var, $echo = true, $label = null, $flags = ENT_SUBSTITUTE)
+ {
+ $label = (null === $label) ? '' : rtrim($label) . ':';
+ if ($var instanceof Model || $var instanceof ModelCollection) {
+ $var = $var->toArray();
+ }
+
+ ob_start();
+ var_dump($var);
+
+ $output = ob_get_clean();
+ $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', $output);
+
+ if (PHP_SAPI == 'cli') {
+ $output = PHP_EOL . $label . $output . PHP_EOL;
+ } else {
+ if (!extension_loaded('xdebug')) {
+ $output = htmlspecialchars($output, $flags);
+ }
+ $output = '' . $label . $output . '
';
+ }
+ if ($echo) {
+ echo($output);
+ return;
+ }
+ return $output;
+ }
+
+ public function inject(Response $response, &$content)
+ {
+ $config = $this->config;
+ $type = isset($config['type']) ? $config['type'] : 'Html';
+
+ unset($config['type']);
+
+ $trace = Loader::factory($type, '\\think\\debug\\', $config);
+
+ if ($response instanceof Redirect) {
+ //TODO 记录
+ } else {
+ $output = $trace->output($response, $this->app['log']->getLog());
+ if (is_string($output)) {
+ // trace调试信息注入
+ $pos = strripos($content, '