Merge branch 'develop' of https://gitee.com/Tyssen/yi-shi into develop

This commit is contained in:
Ghost
2025-04-03 15:53:51 +08:00
237 changed files with 54047 additions and 1660 deletions

View File

@@ -204,4 +204,39 @@ const mapRestrictionType = (type: string): "friend_limit" | "marketing" | "spam"
};
return typeMap[type] || 'other';
};
};
/**
* 获取微信好友列表
* @param wechatId 微信账号ID
* @param page 页码
* @param limit 每页数量
* @param keyword 搜索关键词
* @returns 好友列表数据
*/
export const fetchWechatFriends = async (
wechatId: string | number,
page: number = 1,
limit: number = 20,
keyword: string = ""
): Promise<{ code: number; msg: string; data: any }> => {
try {
const params = new URLSearchParams({
wechatId: String(wechatId),
page: String(page),
limit: String(limit)
});
if (keyword) {
params.append('keyword', keyword);
}
const url = `/v1/device/wechats/friends?${params.toString()}`;
const response = await api.get<{ code: number; msg: string; data: any }>(url);
return response;
} catch (error) {
console.error('获取微信好友列表失败:', error);
throw error;
}
}

View File

@@ -18,6 +18,7 @@ import {
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination"
import { ImeiDisplay } from "@/components/ImeiDisplay"
interface WechatAccount {
wechatId: string
@@ -169,7 +170,10 @@ export function DeviceSelector({
{device.status === "online" ? "在线" : "离线"}
</div>
</div>
<div className="text-sm text-gray-500">IMEI: {device.imei}</div>
<div className="text-sm text-gray-500 flex items-center">
<span className="mr-1">IMEI:</span>
<ImeiDisplay imei={device.imei} containerWidth={160} />
</div>
<div className="mt-2 space-y-2">
{device.wechatAccounts.map((account) => (
<div key={account.wechatId} className="bg-gray-50 rounded-lg p-2">

View File

@@ -6,6 +6,7 @@ import { Badge } from "@/components/ui/badge"
import { Checkbox } from "@/components/ui/checkbox"
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Battery, Smartphone, MessageCircle, Users, Clock } from "lucide-react"
import { ImeiDisplay } from "@/components/ImeiDisplay"
export interface Device {
id: string
@@ -88,11 +89,15 @@ export function DeviceGrid({
/>
)}
<div className="flex-1 space-y-2">
<div className="flex items-center justify-between">
<div className="font-medium">{device.name}</div>
<Badge variant={device.status === "online" ? "success" : "secondary"}>
{device.status === "online" ? "在线" : "离线"}
</Badge>
<div className="relative">
<div className="flex items-center justify-between">
<div className="font-medium">{device.name}</div>
<div className="absolute top-2 right-2">
<Badge variant={device.status === "online" ? "default" : "secondary"}>
{device.status === "online" ? "在线" : "离线"}
</Badge>
</div>
</div>
</div>
<div className="grid grid-cols-2 gap-2 text-sm text-gray-500">
@@ -142,10 +147,13 @@ export function DeviceGrid({
</div>
<div>
<h3 className="font-medium">{selectedDevice.name}</h3>
<p className="text-sm text-gray-500">IMEI: {selectedDevice.imei}</p>
<p className="text-sm text-gray-500 flex items-center">
<span className="mr-1">IMEI:</span>
<ImeiDisplay imei={selectedDevice.imei} containerWidth={160} />
</p>
</div>
</div>
<Badge variant={selectedDevice.status === "online" ? "success" : "secondary"}>
<Badge variant={selectedDevice.status === "online" ? "default" : "secondary"}>
{selectedDevice.status === "online" ? "在线" : "离线"}
</Badge>
</div>

View File

@@ -11,6 +11,7 @@ import { Card } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { ScrollArea } from "@/components/ui/scroll-area"
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { ImeiDisplay } from "@/components/ImeiDisplay"
interface WechatAccount {
wechatId: string
@@ -278,7 +279,10 @@ export function DeviceSelectionDialog({
{device.status === "online" ? "在线" : "离线"}
</div>
</div>
<div className="text-sm text-gray-500">IMEI: {device.imei}</div>
<div className="text-sm text-gray-500 flex items-center">
<span className="mr-1">IMEI:</span>
<ImeiDisplay imei={device.imei} containerWidth={160} />
</div>
{/* 微信账号信息 */}
<div className="mt-2 space-y-2">

View File

@@ -12,6 +12,7 @@ import { Label } from "@/components/ui/label"
import { ScrollArea } from "@/components/ui/scroll-area"
import { fetchDeviceDetail, fetchDeviceRelatedAccounts, updateDeviceTaskConfig, fetchDeviceHandleLogs } from "@/api/devices"
import { toast } from "sonner"
import { ImeiDisplay } from "@/components/ImeiDisplay"
interface WechatAccount {
id: string
@@ -439,7 +440,10 @@ export default function DeviceDetailPage() {
{device.status === "online" ? "在线" : "离线"}
</Badge>
</div>
<div className="text-sm text-gray-500 mt-1">IMEI: {device.imei}</div>
<div className="text-sm text-gray-500 mt-1 flex items-center imei-display-area">
<span className="mr-1 whitespace-nowrap">IMEI:</span>
<ImeiDisplay imei={device.imei} containerWidth="max-w-[calc(100%-60px)]" />
</div>
{device.historicalIds && device.historicalIds.length > 0 && (
<div className="text-sm text-gray-500">ID: {device.historicalIds.join(", ")}</div>
)}

View File

@@ -15,6 +15,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { fetchDeviceList, deleteDevice } from "@/api/devices"
import { ServerDevice } from "@/types/device"
import { api } from "@/lib/api"
import { ImeiDisplay } from "@/components/ImeiDisplay"
// 设备接口更新为与服务端接口对应的类型
interface Device extends ServerDevice {
@@ -479,8 +480,23 @@ export default function DevicesPage() {
}
// 设备详情页跳转
const handleDeviceClick = (deviceId: number) => {
router.push(`/devices/${deviceId}`)
const handleDeviceClick = (deviceId: number, event: React.MouseEvent) => {
// 判断点击事件是否来自ImeiDisplay组件或其后代元素
// 如果点击事件已经被处理例如ImeiDisplay中已阻止传播则不执行跳转
if (event.defaultPrevented) {
return;
}
// 如果点击的元素或其父元素有imei-display类则不跳转
let target = event.target as HTMLElement;
while (target && target !== event.currentTarget) {
if (target.classList.contains('imei-display-area')) {
return;
}
target = target.parentElement as HTMLElement;
}
router.push(`/devices/${deviceId}`);
}
return (
@@ -573,7 +589,7 @@ export default function DevicesPage() {
<Card
key={device.id}
className="p-3 hover:shadow-md transition-shadow cursor-pointer relative"
onClick={() => handleDeviceClick(device.id)}
onClick={(e) => handleDeviceClick(device.id, e)}
>
<div className="flex items-center space-x-3">
<Checkbox
@@ -594,7 +610,10 @@ export default function DevicesPage() {
{device.status === "online" ? "在线" : "离线"}
</Badge>
</div>
<div className="text-sm text-gray-500">IMEI: {device.imei}</div>
<div className="text-sm text-gray-500 flex items-center imei-display-area">
<span className="mr-1">IMEI:</span>
<ImeiDisplay imei={device.imei} containerWidth={180} />
</div>
<div className="text-sm text-gray-500">: {device.wechatId || "未绑定"}</div>
<div className="flex items-center justify-between mt-1 text-sm">
<span className="text-gray-500">: {device.totalFriend}</span>

View File

@@ -58,3 +58,36 @@
}
}
/* 修复微信号页面横向滚动问题 */
body {
overflow-x: hidden;
}
/* 优化微信账号列表项布局 */
.Card[class*="wechat-accounts"] {
max-width: 100%;
overflow-x: hidden;
}
/* 确保进度条组件不会溢出 */
.Progress {
flex-shrink: 1;
min-width: 0;
}
/* 修复IMEI显示模态框点击事件问题 */
.imei-display {
position: relative;
z-index: 10;
}
.imei-display-area {
position: relative;
z-index: 5;
}
/* 增强模态框的点击隔离 */
[role="dialog"] {
isolation: isolate;
}

View File

@@ -1,6 +1,6 @@
"use client"
import { useState, useEffect } from "react"
import { useState, useEffect, useRef, useCallback } from "react"
import { useRouter } from "next/navigation"
import { Card } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
@@ -44,7 +44,7 @@ import {
PaginationPrevious,
} from "@/components/ui/pagination"
import { toast } from "@/components/ui/use-toast"
import { fetchWechatAccountDetail, transformWechatAccountDetail } from "@/api/wechat-accounts"
import { fetchWechatAccountDetail, transformWechatAccountDetail, fetchWechatFriends } from "@/api/wechat-accounts"
interface RestrictionRecord {
id: string
@@ -120,126 +120,34 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
const [showFriendDetail, setShowFriendDetail] = useState(false)
const [selectedFriend, setSelectedFriend] = useState<WechatFriend | null>(null)
const [searchQuery, setSearchQuery] = useState("")
const [currentPage, setCurrentPage] = useState(1)
const [activeTab, setActiveTab] = useState("overview")
const friendsPerPage = 10
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
// 模拟API调用获取账号详情
const fetchAccount = async () => {
try {
setIsLoading(true)
// 调用API获取微信账号详情
const response = await fetchWechatAccountDetail(params.id)
if (response && response.code === 200) {
// 转换数据格式
const transformedAccount = transformWechatAccountDetail(response)
setAccount(transformedAccount)
} else {
toast({
title: "获取微信账号详情失败",
description: response?.msg || "请稍后再试",
variant: "destructive"
})
// 获取失败时使用模拟数据
setAccount(generateMockAccountData())
}
} catch (error) {
console.error("获取微信账号详情失败:", error)
toast({
title: "获取微信账号详情失败",
description: "请检查网络连接或稍后再试",
variant: "destructive"
})
// 请求出错时使用模拟数据
setAccount(generateMockAccountData())
} finally {
setIsLoading(false)
}
}
// 好友列表相关状态
const [friends, setFriends] = useState<any[]>([])
const [friendsPage, setFriendsPage] = useState(1)
const [friendsTotal, setFriendsTotal] = useState(0)
const [hasMoreFriends, setHasMoreFriends] = useState(true)
const [isFetchingFriends, setIsFetchingFriends] = useState(false)
const [hasFriendLoadError, setHasFriendLoadError] = useState(false)
const friendsObserver = useRef<IntersectionObserver | null>(null)
const friendsLoadingRef = useRef<HTMLDivElement | null>(null)
const friendsContainerRef = useRef<HTMLDivElement | null>(null)
fetchAccount()
}, [params.id])
if (!account) {
return <div>...</div>
}
const getWeightColor = (weight: number) => {
if (weight >= 80) return "text-green-600"
if (weight >= 60) return "text-yellow-600"
return "text-red-600"
}
const getWeightDescription = (weight: number) => {
if (weight >= 80) return "账号状态良好"
if (weight >= 60) return "账号状态一般"
return "账号状态较差"
}
const calculateMaxDailyAdds = (weight: number) => {
const baseLimit = 20
return Math.floor(baseLimit * (weight / 100))
}
const getRestrictionTypeColor = (type: string) => {
switch (type) {
case "friend_limit":
return "text-yellow-600"
case "marketing":
return "text-red-600"
case "spam":
return "text-orange-600"
default:
return "text-gray-600"
}
}
const formatAccountAge = (age: { years: number; months: number }) => {
if (age.years === 0) {
return `${age.months}个月`
}
if (age.months === 0) {
return `${age.years}`
}
return `${age.years}${age.months}个月`
}
const handleTransferFriends = () => {
setShowTransferConfirm(true)
}
const confirmTransferFriends = () => {
setShowTransferConfirm(false)
// 跳转到新建计划的订单导入场景
router.push(`/scenarios/new?type=order&source=${account.wechatId}`)
}
const handleFriendClick = (friend: WechatFriend) => {
setSelectedFriend(friend)
setShowFriendDetail(true)
}
// 过滤好友
const filteredFriends = account.friends.filter(
(friend) =>
friend.nickname.toLowerCase().includes(searchQuery.toLowerCase()) ||
friend.wechatId.toLowerCase().includes(searchQuery.toLowerCase()) ||
friend.remark.toLowerCase().includes(searchQuery.toLowerCase()) ||
friend.tags.some((tag) => tag.name.toLowerCase().includes(searchQuery.toLowerCase())),
)
// 分页
const totalPages = Math.ceil(filteredFriends.length / friendsPerPage)
const paginatedFriends = filteredFriends.slice((currentPage - 1) * friendsPerPage, currentPage * friendsPerPage)
// 计算好友列表容器高度
const getFriendsContainerHeight = () => {
// 最少显示一条记录的高度,最多显示十条记录的高度
const minHeight = 80; // 单条记录高度
const maxHeight = 800; // 十条记录高度
if (friends.length === 0) return minHeight;
return Math.min(Math.max(friends.length * 80, minHeight), maxHeight);
};
// 生成模拟账号数据(作为备用,服务器请求失败时使用)
const generateMockAccountData = () => {
const generateMockAccountData = (): WechatAccountDetail => {
// 生成随机标签
const generateRandomTags = (count: number) => {
const generateRandomTags = (count: number): FriendTag[] => {
const tagPool = [
{ name: "潜在客户", color: "bg-blue-100 text-blue-800" },
{ name: "高意向", color: "bg-green-100 text-green-800" },
@@ -251,36 +159,36 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
{ name: "个人用户", color: "bg-pink-100 text-pink-800" },
{ name: "新增好友", color: "bg-emerald-100 text-emerald-800" },
{ name: "老客户", color: "bg-amber-100 text-amber-800" },
]
];
return Array.from({ length: Math.floor(Math.random() * count) + 1 }, () => {
const randomTag = tagPool[Math.floor(Math.random() * tagPool.length)]
const randomTag = tagPool[Math.floor(Math.random() * tagPool.length)];
return {
id: `tag-${Math.random().toString(36).substring(2, 9)}`,
name: randomTag.name,
color: randomTag.color,
}
})
}
};
});
};
// 生成随机好友
const friendCount = Math.floor(Math.random() * (300 - 150)) + 150
const generateFriends = (count: number) => {
const friendCount = Math.floor(Math.random() * (300 - 150)) + 150;
const generateFriends = (count: number): WechatFriend[] => {
return Array.from({ length: count }, (_, i) => {
const firstName = ["张", "王", "李", "赵", "陈", "刘", "杨", "黄", "周", "吴"][Math.floor(Math.random() * 10)]
const firstName = ["张", "王", "李", "赵", "陈", "刘", "杨", "黄", "周", "吴"][Math.floor(Math.random() * 10)];
const secondName = ["小", "大", "明", "华", "强", "伟", "芳", "娜", "秀", "英"][
Math.floor(Math.random() * 10)
]
const lastName = ["明", "华", "强", "伟", "芳", "娜", "秀", "英", "军", "杰"][Math.floor(Math.random() * 10)]
const nickname = firstName + secondName + lastName
];
const lastName = ["明", "华", "强", "伟", "芳", "娜", "秀", "英", "军", "杰"][Math.floor(Math.random() * 10)];
const nickname = firstName + secondName + lastName;
// 生成随机的添加时间过去1年内
const addDate = new Date()
addDate.setDate(addDate.getDate() - Math.floor(Math.random() * 365))
const addDate = new Date();
addDate.setDate(addDate.getDate() - Math.floor(Math.random() * 365));
// 生成随机的最后互动时间过去30天内
const lastDate = new Date()
lastDate.setDate(lastDate.getDate() - Math.floor(Math.random() * 30))
const lastDate = new Date();
lastDate.setDate(lastDate.getDate() - Math.floor(Math.random() * 30));
return {
id: `friend-${i}`,
@@ -306,11 +214,11 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
Math.floor(Math.random() * 5)
]
: "",
}
})
}
};
});
};
const friends = generateFriends(friendCount)
const friends = generateFriends(friendCount);
const mockAccount: WechatAccountDetail = {
id: params.id,
@@ -363,8 +271,241 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
messages: Math.floor(Math.random() * 100) + 100,
})),
friends: friends,
};
return mockAccount;
};
// 随机生成标签颜色
const getRandomTagColor = (): string => {
const colors = [
"bg-blue-100 text-blue-800",
"bg-green-100 text-green-800",
"bg-red-100 text-red-800",
"bg-pink-100 text-pink-800",
"bg-emerald-100 text-emerald-800",
"bg-amber-100 text-amber-800",
];
return colors[Math.floor(Math.random() * colors.length)];
};
// 获取好友列表数据
const fetchFriends = useCallback(async (page: number = 1, isNewSearch: boolean = false) => {
if (!account || isFetchingFriends) return;
try {
setIsFetchingFriends(true);
setHasFriendLoadError(false);
// 调用API获取好友列表
const response = await fetchWechatFriends(account.wechatId, page, 20, searchQuery);
if (response && response.code === 200) {
// 更新总数计数,确保在第一次加载时设置
if (isNewSearch || friendsTotal === 0) {
setFriendsTotal(response.data.total || 0);
}
const newFriends = response.data.list.map((friend: any) => ({
id: friend.wechatId,
avatar: friend.avatar,
nickname: friend.nickname || '未设置昵称',
wechatId: friend.wechatId,
remark: friend.remark || '',
addTime: '2024-01-01', // 接口未返回,使用默认值
lastInteraction: '2024-01-01', // 接口未返回,使用默认值
tags: (friend.labels || []).map((label: string, index: number) => ({
id: `tag-${index}`,
name: label,
color: getRandomTagColor(),
})),
region: friend.region || '未知地区',
source: '微信好友', // 接口未返回,使用默认值
notes: '',
}));
// 更新状态
if (isNewSearch) {
setFriends(newFriends);
} else {
setFriends(prev => [...prev, ...newFriends]);
}
setFriendsPage(page);
// 判断是否还有更多数据
setHasMoreFriends(page * 20 < response.data.total);
console.log("好友列表加载成功,总数:", response.data.total);
} else {
setHasFriendLoadError(true);
toast({
title: "获取好友列表失败",
description: response?.msg || "请稍后再试",
variant: "destructive"
});
}
} catch (error) {
setHasFriendLoadError(true);
console.error("获取好友列表失败:", error);
toast({
title: "获取好友列表失败",
description: "请检查网络连接或稍后再试",
variant: "destructive"
});
} finally {
setIsFetchingFriends(false);
}
return mockAccount
}, [account, searchQuery, friendsTotal]);
// 处理搜索
const handleSearch = useCallback(() => {
setFriends([]);
setFriendsPage(1);
setHasMoreFriends(true);
fetchFriends(1, true);
}, [fetchFriends]);
// 处理标签切换
useEffect(() => {
if (account && friends.length === 0) {
fetchFriends(1, true);
}
}, [account, friends.length, fetchFriends]);
// 设置IntersectionObserver用于懒加载
useEffect(() => {
friendsObserver.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasMoreFriends && !isFetchingFriends) {
fetchFriends(friendsPage + 1);
}
}, { threshold: 0.5 });
return () => {
if (friendsObserver.current) {
friendsObserver.current.disconnect();
}
};
}, [fetchFriends, friendsPage, hasMoreFriends, isFetchingFriends]);
// 观察加载指示器
useEffect(() => {
if (friendsLoadingRef.current && friendsObserver.current) {
friendsObserver.current.observe(friendsLoadingRef.current);
}
return () => {
if (friendsLoadingRef.current && friendsObserver.current) {
friendsObserver.current.unobserve(friendsLoadingRef.current);
}
};
}, [friendsLoadingRef.current, friendsObserver.current]);
useEffect(() => {
// 模拟API调用获取账号详情
const fetchAccount = async () => {
try {
setIsLoading(true)
// 调用API获取微信账号详情
const response = await fetchWechatAccountDetail(params.id)
if (response && response.code === 200) {
// 转换数据格式
const transformedAccount = transformWechatAccountDetail(response)
setAccount(transformedAccount)
// 如果有好友总数更新friendsTotal状态
if (transformedAccount && transformedAccount.friendCount > 0) {
setFriendsTotal(transformedAccount.friendCount);
}
} else {
toast({
title: "获取微信账号详情失败",
description: response?.msg || "请稍后再试",
variant: "destructive"
})
// 获取失败时使用模拟数据
const mockData = generateMockAccountData();
setAccount(mockData);
// 更新好友总数
setFriendsTotal(mockData.friendCount);
}
} catch (error) {
console.error("获取微信账号详情失败:", error)
toast({
title: "获取微信账号详情失败",
description: "请检查网络连接或稍后再试",
variant: "destructive"
})
// 请求出错时使用模拟数据
const mockData = generateMockAccountData();
setAccount(mockData);
// 更新好友总数
setFriendsTotal(mockData.friendCount);
} finally {
setIsLoading(false)
}
}
fetchAccount()
}, [params.id])
if (!account) {
return <div>...</div>
}
const getWeightColor = (weight: number) => {
if (weight >= 80) return "text-green-600"
if (weight >= 60) return "text-yellow-600"
return "text-red-600"
}
const getWeightDescription = (weight: number) => {
if (weight >= 80) return "账号状态良好"
if (weight >= 60) return "账号状态一般"
return "账号状态较差"
}
const calculateMaxDailyAdds = (weight: number) => {
const baseLimit = 20
return Math.floor(baseLimit * (weight / 100))
}
const getRestrictionTypeColor = (type: string) => {
switch (type) {
case "friend_limit":
return "text-yellow-600"
case "marketing":
return "text-red-600"
case "spam":
return "text-orange-600"
default:
return "text-gray-600"
}
}
const formatAccountAge = (age: { years: number; months: number }) => {
if (age.years > 0) {
return `${age.years}${age.months}个月`;
}
return `${age.months}个月`;
};
const handleTransferFriends = () => {
setShowTransferConfirm(true)
}
const confirmTransferFriends = () => {
// 模拟API调用
toast({
title: "好友转移成功",
description: `已成功转移 ${account?.friends.length} 个好友`,
});
setShowTransferConfirm(false)
}
const handleFriendClick = (friend: WechatFriend) => {
setSelectedFriend(friend)
setShowFriendDetail(true)
}
return (
@@ -374,7 +515,7 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
<Loader2 className="h-8 w-8 animate-spin text-blue-500" />
</div>
) : account ? (
<div className="flex-1 bg-gradient-to-b from-blue-50 to-white min-h-screen pb-16">
<div className="flex-1 bg-gradient-to-b from-blue-50 to-white min-h-screen pb-16 overflow-x-hidden">
<header className="sticky top-0 z-10 bg-white/80 backdrop-blur-sm border-b">
<div className="flex items-center p-4">
<Button variant="ghost" size="icon" onClick={() => router.back()}>
@@ -400,7 +541,7 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
</div>
<div className="flex-1">
<div className="flex items-center space-x-2">
<h2 className="text-xl font-semibold">{account.nickname}</h2>
<h2 className="text-xl font-semibold truncate max-w-[200px]">{account.nickname}</h2>
<Badge variant={account.status === "normal" ? "outline" : "destructive"}>
{account.status === "normal" ? "正常" : "异常"}
</Badge>
@@ -423,7 +564,9 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="overview"></TabsTrigger>
<TabsTrigger value="friends"> ({account.friendCount})</TabsTrigger>
<TabsTrigger value="friends">
({friendsTotal > 0 ? friendsTotal : account.friendCount})
</TabsTrigger>
</TabsList>
<TabsContent value="overview" className="space-y-4 mt-4">
@@ -463,24 +606,24 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
<p className="text-sm text-gray-500 mb-4">{getWeightDescription(account.accountWeight)}</p>
<div className="space-y-3">
<div className="flex items-center justify-between text-sm">
<span></span>
<Progress value={account.weightFactors.ageFactor * 100} className="w-32" />
<span>{(account.weightFactors.ageFactor * 100).toFixed(0)}%</span>
<span className="flex-shrink-0"></span>
<Progress value={account.weightFactors.ageFactor * 100} className="flex-1 min-w-0 mx-2" />
<span className="flex-shrink-0">{(account.weightFactors.ageFactor * 100).toFixed(0)}%</span>
</div>
<div className="flex items-center justify-between text-sm">
<span></span>
<Progress value={account.weightFactors.activityFactor * 100} className="w-32" />
<span>{(account.weightFactors.activityFactor * 100).toFixed(0)}%</span>
<span className="flex-shrink-0"></span>
<Progress value={account.weightFactors.activityFactor * 100} className="flex-1 min-w-0 mx-2" />
<span className="flex-shrink-0">{(account.weightFactors.activityFactor * 100).toFixed(0)}%</span>
</div>
<div className="flex items-center justify-between text-sm">
<span></span>
<Progress value={account.weightFactors.restrictionFactor * 100} className="w-32" />
<span>{(account.weightFactors.restrictionFactor * 100).toFixed(0)}%</span>
<span className="flex-shrink-0"></span>
<Progress value={account.weightFactors.restrictionFactor * 100} className="flex-1 min-w-0 mx-2" />
<span className="flex-shrink-0">{(account.weightFactors.restrictionFactor * 100).toFixed(0)}%</span>
</div>
<div className="flex items-center justify-between text-sm">
<span></span>
<Progress value={account.weightFactors.verificationFactor * 100} className="w-32" />
<span>{(account.weightFactors.verificationFactor * 100).toFixed(0)}%</span>
<span className="flex-shrink-0"></span>
<Progress value={account.weightFactors.verificationFactor * 100} className="flex-1 min-w-0 mx-2" />
<span className="flex-shrink-0">{(account.weightFactors.verificationFactor * 100).toFixed(0)}%</span>
</div>
</div>
</Card>
@@ -561,107 +704,97 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
placeholder="搜索好友昵称/微信号/备注/标签"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
className="pl-9"
/>
</div>
<Button variant="outline" size="icon">
<Button variant="outline" size="icon" onClick={handleSearch}>
<Filter className="h-4 w-4" />
</Button>
</div>
{/* 好友列表 */}
<div className="space-y-2">
{paginatedFriends.length === 0 ? (
<div
ref={friendsContainerRef}
className="space-y-2 transition-all duration-300"
style={{
minHeight: '80px',
height: `${getFriendsContainerHeight()}px`,
overflowY: 'auto'
}}
>
{isFetchingFriends && friends.length === 0 ? (
<div className="flex justify-center items-center py-8">
<Loader2 className="h-6 w-6 animate-spin text-blue-500" />
</div>
) : friends.length === 0 && hasFriendLoadError ? (
<div className="text-center py-8 text-gray-500">
<p></p>
<Button variant="outline" size="sm" className="mt-2" onClick={() => fetchFriends(1, true)}>
</Button>
</div>
) : friends.length === 0 ? (
<div className="text-center py-8 text-gray-500"></div>
) : (
paginatedFriends.map((friend) => (
<div
key={friend.id}
className="flex items-center p-3 border rounded-lg hover:bg-gray-50 cursor-pointer"
onClick={() => handleFriendClick(friend)}
>
<Avatar className="h-10 w-10 mr-3">
<AvatarImage src={friend.avatar} />
<AvatarFallback>{friend.nickname[0]}</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between">
<div className="font-medium truncate">
{friend.nickname}
{friend.remark && <span className="text-gray-500 ml-1">({friend.remark})</span>}
<>
{friends.map((friend) => (
<div
key={friend.id}
className="flex items-center p-3 border rounded-lg hover:bg-gray-50 cursor-pointer"
onClick={() => handleFriendClick(friend)}
>
<Avatar className="h-10 w-10 mr-3">
<AvatarImage src={friend.avatar} />
<AvatarFallback>{friend.nickname?.[0] || 'U'}</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between">
<div className="font-medium truncate max-w-[180px]">
{friend.nickname}
{friend.remark && <span className="text-gray-500 ml-1 truncate">({friend.remark})</span>}
</div>
<ChevronRight className="h-4 w-4 text-gray-400" />
</div>
<div className="text-sm text-gray-500 truncate">{friend.wechatId}</div>
<div className="flex flex-wrap gap-1 mt-1">
{friend.tags.slice(0, 3).map((tag: FriendTag) => (
<span key={tag.id} className={`text-xs px-2 py-0.5 rounded-full ${tag.color}`}>
{tag.name}
</span>
))}
{friend.tags.length > 3 && (
<span className="text-xs px-2 py-0.5 rounded-full bg-gray-100 text-gray-800">
+{friend.tags.length - 3}
</span>
)}
</div>
<ChevronRight className="h-4 w-4 text-gray-400" />
</div>
<div className="text-sm text-gray-500 truncate">{friend.wechatId}</div>
<div className="flex flex-wrap gap-1 mt-1">
{friend.tags.slice(0, 3).map((tag) => (
<span key={tag.id} className={`text-xs px-2 py-0.5 rounded-full ${tag.color}`}>
{tag.name}
</span>
))}
{friend.tags.length > 3 && (
<span className="text-xs px-2 py-0.5 rounded-full bg-gray-100 text-gray-800">
+{friend.tags.length - 3}
</span>
)}
</div>
</div>
</div>
))
))}
{/* 懒加载指示器 */}
{hasMoreFriends && (
<div ref={friendsLoadingRef} className="py-4 flex justify-center">
{isFetchingFriends && <Loader2 className="h-6 w-6 animate-spin text-blue-500" />}
</div>
)}
</>
)}
</div>
{/* 分页 */}
{totalPages > 1 && (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious
href="#"
onClick={(e) => {
e.preventDefault()
setCurrentPage((prev) => Math.max(1, prev - 1))
}}
/>
</PaginationItem>
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
let pageNumber
if (totalPages <= 5) {
pageNumber = i + 1
} else if (currentPage <= 3) {
pageNumber = i + 1
} else if (currentPage >= totalPages - 2) {
pageNumber = totalPages - 4 + i
} else {
pageNumber = currentPage - 2 + i
}
return (
<PaginationItem key={pageNumber}>
<PaginationLink
href="#"
isActive={currentPage === pageNumber}
onClick={(e) => {
e.preventDefault()
setCurrentPage(pageNumber)
}}
>
{pageNumber}
</PaginationLink>
</PaginationItem>
)
})}
<PaginationItem>
<PaginationNext
href="#"
onClick={(e) => {
e.preventDefault()
setCurrentPage((prev) => Math.min(totalPages, prev + 1))
}}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
)}
{/* 显示加载状态和总数 */}
<div className="text-sm text-gray-500 text-center">
{friendsTotal > 0 ? (
<span>
{Math.min(friends.length, friendsTotal)} / {friendsTotal}
</span>
) : !isFetchingFriends && !hasFriendLoadError && account ? (
<span>
{account.friendCount}
</span>
) : null}
</div>
</div>
</Card>
</TabsContent>
@@ -770,7 +903,7 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
</div>
<div className="flex flex-wrap gap-2">
{selectedFriend.tags.map((tag) => (
{selectedFriend.tags.map((tag: FriendTag) => (
<span key={tag.id} className={`text-sm px-2 py-1 rounded-full ${tag.color}`}>
{tag.name}
</span>

View File

@@ -225,7 +225,7 @@ export default function WechatAccountsPage() {
{accounts.map((account) => (
<Card
key={account.id}
className="p-4 hover:shadow-lg transition-all cursor-pointer"
className="p-4 hover:shadow-lg transition-all cursor-pointer overflow-hidden"
onClick={() => router.push(`/wechat-accounts/${account.id}`)}
>
<div className="flex items-start space-x-4">
@@ -236,7 +236,7 @@ export default function WechatAccountsPage() {
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<h3 className="font-medium truncate">{account.nickname}</h3>
<h3 className="font-medium truncate max-w-[180px]">{account.nickname}</h3>
<Badge variant={account.status === "normal" ? "outline" : "destructive"}>
{account.status === "normal" ? "正常" : "异常"}
</Badge>
@@ -254,8 +254,8 @@ export default function WechatAccountsPage() {
</Button>
</div>
<div className="mt-1 text-sm text-gray-500 space-y-1">
<div>{account.wechatId}</div>
<div className="flex items-center justify-between">
<div className="truncate">{account.wechatId}</div>
<div className="flex items-center justify-between flex-wrap gap-1">
<div>{account.friendCount}</div>
<div className="text-green-600">+{account.todayAdded}</div>
</div>
@@ -281,9 +281,9 @@ export default function WechatAccountsPage() {
</div>
<Progress value={(account.todayAdded / account.maxDailyAdds) * 100} className="h-2" />
</div>
<div className="flex items-center justify-between text-xs text-gray-500 pt-2">
<div>{account.deviceName || '未知设备'}</div>
<div>{account.lastActive}</div>
<div className="flex items-center justify-between text-xs text-gray-500 pt-2 flex-wrap gap-1">
<div className="truncate max-w-[150px]">{account.deviceName || '未知设备'}</div>
<div className="whitespace-nowrap">{account.lastActive}</div>
</div>
</div>
</div>

View File

@@ -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
<div className="flex-1">
<div className="flex items-center justify-between">
<span className="font-medium">{device.name}</span>
<Badge variant={device.status === "online" ? "success" : "secondary"}>
<Badge variant={device.status === "online" ? "default" : "secondary"}>
{device.status === "online" ? "在线" : "离线"}
</Badge>
</div>
<div className="text-sm text-gray-500 mt-1">
<div>IMEI: {device.imei}</div>
<div className="flex items-center">
<span className="mr-1">IMEI:</span>
<ImeiDisplay imei={device.imei} containerWidth={160} />
</div>
<div>: {device.wxid}</div>
</div>
{device.usedInPlans > 0 && (

View File

@@ -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 (
<Card className="p-6">
<div className="space-y-4">
@@ -193,15 +204,14 @@ export function DeviceSelector({ formData, onChange, onNext, onPrev }: DeviceSel
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between mb-1">
<div className="font-medium truncate">{device.name}</div>
<div
className={`px-2 py-1 rounded-full text-xs ${
device.status === "online" ? "bg-green-100 text-green-800" : "bg-gray-100 text-gray-800"
}`}
>
<Badge variant={device.status === "online" ? "default" : "secondary"}>
{device.status === "online" ? "在线" : "离线"}
</div>
</Badge>
</div>
<div className="text-sm text-gray-500 flex items-center">
<span className="mr-1">IMEI:</span>
<ImeiDisplay imei={device.imei} containerWidth={160} />
</div>
<div className="text-sm text-gray-500">IMEI: {device.imei}</div>
<div className="text-sm text-gray-500">: {device.wechatId}</div>
{device.usedInPlans > 0 && (
<div className="text-sm text-orange-500"> {device.usedInPlans} </div>
@@ -211,24 +221,25 @@ export function DeviceSelector({ formData, onChange, onNext, onPrev }: DeviceSel
</Card>
))}
</div>
<Pagination>
<Pagination className="mt-4">
<PaginationContent>
<PaginationPrevious
onClick={() => 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) => (
<PaginationItem key={page}>
<PaginationLink onClick={() => setCurrentPage(page)} isActive={currentPage === page}>
{page}
{Array.from({ length: Math.ceil(filteredDevices.length / itemsPerPage) }).map((_, index) => (
<PaginationItem key={index}>
<PaginationLink
onClick={() => setCurrentPage(index + 1)}
isActive={currentPage === index + 1}
>
{index + 1}
</PaginationLink>
</PaginationItem>
))}
<PaginationNext
onClick={() =>
setCurrentPage((prev) => Math.min(Math.ceil(filteredDevices.length / itemsPerPage), prev + 1))
}
disabled={currentPage === Math.ceil(filteredDevices.length / itemsPerPage)}
onClick={handleNextPage}
disabled={isLastPage}
/>
</PaginationContent>
</Pagination>
@@ -268,6 +279,30 @@ export function DeviceSelector({ formData, onChange, onNext, onPrev }: DeviceSel
</Button>
</div>
<div className="flex justify-center items-center space-x-2 mt-4">
<Button
variant="outline"
size="icon"
onClick={handlePrevPage}
disabled={currentPage === 1}
>
<ChevronLeft className="h-4 w-4" />
</Button>
<div className="text-sm text-gray-500">
{currentPage} / {Math.ceil(filteredDevices.length / itemsPerPage)}
</div>
<Button
variant="outline"
size="icon"
onClick={handleNextPage}
disabled={isLastPage}
>
<ChevronRight className="h-4 w-4" />
</Button>
</div>
</div>
</Card>
)

View File

@@ -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 <span className={`truncate inline-block ${widthStyle} ${className}`}>{imei}</span>
}
return (
<>
<span
className={`cursor-pointer hover:text-blue-600 truncate inline-block imei-display ${widthStyle} ${className}`}
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
setOpen(true)
// 防止冒泡到Card的点击事件
return false
}}
title={imei}
>
{imei}
</span>
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogContent className="sm:max-w-md" onClick={(e) => e.stopPropagation()}>
<DialogHeader>
<DialogTitle>IMEI</DialogTitle>
<DialogDescription>
</DialogDescription>
</DialogHeader>
<div className="flex items-center space-x-2 bg-gray-50 p-3 rounded-md">
<code className="flex-1 text-sm break-all">{imei}</code>
<Button
variant="outline"
size="icon"
onClick={(e) => {
e.stopPropagation()
handleCopy()
}}
title="复制IMEI"
>
<Copy className="h-4 w-4" />
</Button>
</div>
</DialogContent>
</Dialog>
</>
)
}

View File

@@ -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
)}
<div className="flex flex-col space-y-4">
<div className="flex items-center justify-between">
<Badge variant={currentStatus.status === "online" ? "success" : "secondary"}>
<Badge variant={currentStatus.status === "online" ? "default" : "secondary"}>
{currentStatus.status === "online" ? "在线" : "离线"}
</Badge>
<div className="flex items-center space-x-2">
@@ -68,7 +69,10 @@ export function DeviceGrid({ devices, selectable, selectedDevices, onSelect, dev
<Smartphone className="w-4 h-4 text-gray-400" />
<div>
<div className="text-sm font-medium">{device.name}</div>
<div className="text-xs text-gray-500">IMEI-{device.imei}</div>
<div className="text-xs text-gray-500 flex items-center">
<span className="mr-1">IMEI:</span>
<ImeiDisplay imei={device.imei} containerWidth={80} />
</div>
</div>
</div>
{device.remark && <div className="text-xs text-gray-500">: {device.remark}</div>}
@@ -90,7 +94,7 @@ export function DeviceGrid({ devices, selectable, selectedDevices, onSelect, dev
<div>{device.todayAdded}</div>
<div>
<Badge variant={device.addFriendStatus === "normal" ? "success" : "destructive"}>
<Badge variant={device.addFriendStatus === "normal" ? "default" : "destructive"}>
{device.addFriendStatus === "normal" ? "正常" : "异常"}
</Badge>
</div>

1
Server/.gitignore vendored
View File

@@ -7,4 +7,3 @@ public/upload
vendor
/public/.user.ini
/404.html
thinkphp

View File

@@ -1,927 +0,0 @@
## V5.1.39 LTS2019-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 LTS2019-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 LTS2019-5-26
本次更新为常规更新,主要更新如下:
* 改进关联数据更新
* 修正关联动态获取器
* 改进`redis`驱动
* 修复验证规则里面出现二维数组时的错误
* 改进跨域请求支持
* 完善模型`hidden`方法对关联属性的支持
* 改进`where`查询方法传入`Query`对象的支持`bind`数据
* 改进数据集对象的`load`方法
* 修正缓存类`clear`方法对`tag`的支持
## V5.1.36 LTS2019-4-28
本次更新为常规更新,主要更新如下:
* 修正`chunk`方法一处异常抛出的错误
* 修正模型输出的`visible`
* 改进环境变量加载
* 改进命令行日志的`level`配置支持
* 修复设置有缓存前缀时,无法清空缓存标签的问题
* HasMony对象`saveAll`方法兼容`Collection`格式参数格式
* 修正`whereOr`查询使用字符串的问题
* 改进`dateFormat`设置对写入数据的影响
* 修正查询缓存
* 记住指定的跳转地址
* 改进软删除
* 改进聚合查询SQL去除limit 1
* 改进缓存驱动
## V5.1.35 LTS2019-3-2
本次主要为常规更新,修正了一些反馈的问题。
* 修正验证类自定义验证方法执行两次的问题
* 模型增加`isEmpty`方法用于判断是否空模型
* 改进获取器对`append`的支持
* 修正一对多关联的`withCount`自关联问题
* facade类注释调整
* 改进关联属性的`visible``hidden`判断
* 修正路由分组的`MISS`路由
* 改进pgsql.sql
## V5.1.34 LTS2019-1-30
本次更新为常规更新,修正了一些反馈的问题。
* 改进Request类的`has`方法,支持`patch`
* 改进`unique`验证的多条件支持
* 修复自定义上传验证,检测文件大小
* 改进`in`查询支持表达式
* 改进路由的`getBind`方法
* 改进验证类的错误信息获取
* 改进`response`助手函数默认值
* 修正mysql的`regexp`查询
* 改进模型类型强制转换写入对`Expression`对象的支持
## V5.1.33 LTS2019-1-16
* 修复路由中存在多个相同替换的正则BUG
* 修正whereLike查询
* join方法支持参数绑定
* 改进union方法
* 修正多对多关联的attach方法
* 改进验证类的正则规则自定义
* 改进Request类method方法
* 改进File日志类型的CLI日志写入
* 改进文件日志time_format配置对JSON格式的支持
## V5.1.32 LTS2018-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 LTS2018-11-30
该版本为常规更新,修正了一些社区反馈的问题。
主要更新如下:
* 改进查询类的`execute`方法
* 判断路由规则定义添加对请求类型的判断
* 修复`orderRaw`异常
* 修正 `optimize:autoload`指令
* 改进软删除的`destroy`方法造成重复执行事件的问题
* 改进验证类对扩展验证规则 始终验证 不管是否`require`
* 修复自定义验证`remove`所有规则的异常
* 改进时间字段的自动写入支持微秒数据
* 改进`Connection`类的`getrealsql`方法
* 修正`https`地址的URL生成
* 修复 `array_walk_recursive` 在低于PHP7.1消耗内部指针问题
* 改进手动参数绑定使用
* 改进聚合查询方法的`field`参数支持`Expression`
## V5.1.29 LTS2018-11-11
该版本主要改进了参数绑定的解析问题和提升性能,并修正了一些反馈的问题。
* 改进手动参数绑定
* 修正MISS路由的分组参数无效问题
* 行为支持对象的方法
* 修正全局查询范围
* 改进`belongsto`关联的`has`方法
* 改进`hasMany`关联
* 改进模型观察者多次注册的问题
* 改进`query`类的默认查询参数处理
* 修正`parseBetween`解析方法
* 改进路由地址生成的本地域名支持
* 改进参数绑定的实际URL解析性能
* 改进`Env`类的`getEnv``get`方法
* 改进模板缓存的生成优化
* 修复验证类的多语言支持
* 修复自定义场景验证`remove`规则异常
* File类添加是否自动补全扩展名的选项
* 改进`strpos`对子串是否存在的判断
* 修复`choice`无法用值选择第一个选项问题
* 验证器支持多维数组取值验证
* 改进解析`extend``block`标签的正则
## V5.1.28 LTS2018-10-29
该版本主要修正了上一个版本存在的一些问题,并改进了关联查询
* 改进聚合查询方法的字段支持DISTINCT
* 改进定义路由后url函数的端口生成
* 改进控制器中间件对`swoole`等的支持
* 改进Log类`save`方法
* 改进验证类的闭包验证参数
* 多对多关联支持指定中间表数据的名称
* 关联聚合查询支持闭包方式指定聚合字段
* 改进Lang类`get`方法
* 多对多关联增加判断关联数据是否存在的方法
* 改进关联查询使用`fetchsql`的情况
* 改进修改器的是否已经执行判断
* 增加`afterWith``beforeWith`验证规则 用于比较日期字段
## V5.1.27 LTS2018-10-22
该版本主要修正了路由绑定的参数改进了修改器的执行多次问题并正式宣布为LTS版本
* 修正路由绑定的参数丢失问题
* 修正路由别名的参数获取
* 改进修改器会执行多次的问题
## V5.1.262018-10-12
该版本主要修正了上一个版本的一些问题,并改进了全局查询范围的支持,同时包含了一个安全更新。
* 修正单一模块下注解路由无效的问题
* 改进数据库的聚合查询的字段处理
* 模型类增加`globalScope`属性定义 用于指定全局的查询范围
* 模型的`useGlobalScope`方法支持传入数组 用于指定当前查询需要使用的全局查询范围
* 改进数据集的`order`方法对数字类型的支持
* 修正上一个版本`order`方法解析的一处BUG
* 排序字段不合法或者错误的时候抛出异常
* 改进`Request`类的`file`方法对上传文件的错误判断
## V5.1.252018-9-21
该版本主要改进了查询参数绑定的性能和对浮点型的支持,以及一些细节的完善。
* 修正一处命令行问题
* 改进`Socketlog`日志驱动,支持自定义默认展开日志类别
* 修正`MorphMany`一处bug
* 跳转到上次记住的url并支持默认值
* 改进模型的异常提示
* 改进参数绑定对浮点型的支持
* 改进`order`方法解析
* 改进`json`字段数据的自动编码
* 改进日志`log_write`可能造成的日志写入死循环
* Log类增加`log_level`行为标签位置,用于对某个类型的日志进行处理
* Route类增加`clear`方法清空路由规则
* 分布式数据库配置支持使用数组
* 单日志文件也支持`max_files`参数
* 改进查询参数绑定的性能
* 改进别名路由的URL后缀参数检测
* 控制器前置方法和控制器中间件的`only``except`定义不区分大小写
## V5.1.242018-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.232018-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.222018-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.212018-8-2
该版本主要增加了下载响应对象和数组查询对象的支持,并修正了一些社区反馈的问题。
* 改进核心对象的无用信息调试输出
* 改进模型的`isRelationAttr`方法判断
* 模型类的`get``all`方法并入Db类
* 增加[下载响应对象](文件下载.md)和`download`助手函数
* 修正别名路由配置定义读取
* 改进`resultToModel`方法
* 修正开启类库后缀后的注解路由生成
* `Response`类增加`noCache`快捷方法
* 改进路由对象在`Swoole`/`Workerman`下面参数多次合并问题
* 修正路由`ajax`/`pjax`参数后路由变量无法正确获取的问题
* 增加清除中间件的方法
* 改进依赖注入的参数规范自动识别(便于对接前端小写+下划线规范)
* 改进`hasWhere`的数组条件的字段判断
* 增加[数组查询对象](高级查询.md)`Where`支持(喜欢数组查询的福音)
* 改进多对多关联的闭包支持
## V5.1.202018-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`
* 路由地址中的变量支持使用`<var>`方式
* 改进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驱动
## RC12017-9-8
主要新特性为:
* 引入容器和Facade支持
* 依赖注入完善和支持更多场景
* 重构的(对象化)路由
* 配置和路由目录独立
* 取消系统常量
* 助手函数增强
* 类库别名机制
* 模型和数据库增强
* 验证类增强
* 模板引擎改进
* 支持PSR-3日志规范
* RC1版本取消了5.0多个字段批量数组查询的方式

View File

@@ -1,180 +0,0 @@
![](https://box.kancloud.cn/5a0aaa69a5ff42657b5c4715f3d49221)
ThinkPHP 5.1LTS版本 —— 12载初心你值得信赖的PHP框架
===============
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/top-think/framework/badges/quality-score.png?b=5.1)](https://scrutinizer-ci.com/g/top-think/framework/?branch=5.1)
[![Build Status](https://travis-ci.org/top-think/framework.svg?branch=master)](https://travis-ci.org/top-think/framework)
[![Total Downloads](https://poser.pugx.org/topthink/framework/downloads)](https://packagist.org/packages/topthink/framework)
[![Latest Stable Version](https://poser.pugx.org/topthink/framework/v/stable)](https://packagist.org/packages/topthink/framework)
[![PHP Version](https://img.shields.io/badge/php-%3E%3D5.6-8892BF.svg)](http://www.php.net/)
[![License](https://poser.pugx.org/topthink/framework/license)](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)

View File

@@ -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直接存储在朋友圈主表中不再单独存储到资源表。

View File

@@ -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. 数据同步是增量的,会自动更新已存在的记录

View File

@@ -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'); // 获取在线微信账号列表

View File

@@ -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()
]);
}
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace app\devices\model;
use think\Model;
use think\Db;
/**
* 微信好友模型类
*/
class WechatFriend extends Model
{
// 设置表名
protected $name = 'wechat_friend';
protected $prefix = 'tk_';
// 设置主键
protected $pk = 'id';
// 自动写入时间戳
protected $autoWriteTimestamp = 'datetime';
// 定义时间戳字段名
protected $createTime = 'createTime';
protected $updateTime = 'updateTime';
// 定义字段类型
protected $type = [
'id' => '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
];
}
}

7
Server/thinkphp/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
composer.phar
composer.lock
.DS_Store
Thumbs.db
/phpunit.xml
/.idea
/.vscode

View File

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

View File

@@ -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~7GitHub 会自动更新你的 `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 图形界面客户端

0
Server/LICENSE.txt → Server/thinkphp/LICENSE.txt Executable file → Normal file
View File

99
Server/thinkphp/README.md Normal file
View File

@@ -0,0 +1,99 @@
![](https://box.kancloud.cn/5a0aaa69a5ff42657b5c4715f3d49221)
ThinkPHP 5.1LTS —— 12载初心你值得信赖的PHP框架
===============
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/top-think/framework/badges/quality-score.png?b=5.1)](https://scrutinizer-ci.com/g/top-think/framework/?branch=5.1)
[![Build Status](https://travis-ci.org/top-think/framework.svg?branch=master)](https://travis-ci.org/top-think/framework)
[![Total Downloads](https://poser.pugx.org/topthink/framework/downloads)](https://packagist.org/packages/topthink/framework)
[![Latest Stable Version](https://poser.pugx.org/topthink/framework/v/stable)](https://packagist.org/packages/topthink/framework)
[![PHP Version](https://img.shields.io/badge/php-%3E%3D5.6-8892BF.svg)](http://www.php.net/)
[![License](https://poser.pugx.org/topthink/framework/license)](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)

52
Server/thinkphp/base.php Normal file
View File

@@ -0,0 +1,52 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
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,
]);

View File

@@ -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"
}
}

View File

@@ -0,0 +1,327 @@
<?php
return [
// +----------------------------------------------------------------------
// | 应用设置
// +----------------------------------------------------------------------
'app' => [
// 应用名称
'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\\',
],
];

726
Server/thinkphp/helper.php Normal file
View File

@@ -0,0 +1,726 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
//------------------------
// 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 '<input type="hidden" name="' . $name . '" value="' . $token . '" />';
}
}
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);
}
}

View File

@@ -0,0 +1,144 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
// 核心中文语言包
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' => '规则错误',
];

View File

@@ -0,0 +1,979 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
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;
}
}

View File

@@ -0,0 +1,415 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
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 . ']不可写,目录无法自动生成!<BR>请手动生成项目目录~', 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) ? "<?php\n" : '');
}
}
}
/**
* 创建模块
* @access public
* @param string $module 模块名
* @param array $list build列表
* @param string $namespace 应用类库命名空间
* @param bool $suffix 类库后缀
* @return void
*/
public function module($module = '', $list = [], $namespace = 'app', $suffix = false)
{
$module = $module ? $module : '';
if (!is_dir($this->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) ? "<?php\n" : '');
}
}
} else {
// 生成相关MVC文件
foreach ($file as $val) {
$val = trim($val);
$filename = $modulePath . $path . DIRECTORY_SEPARATOR . $val . ($suffix ? ucfirst($path) : '') . '.php';
$space = $namespace . '\\' . ($module ? $module . '\\' : '') . $path;
$class = $val . ($suffix ? ucfirst($path) : '');
switch ($path) {
case 'controller': // 控制器
$content = "<?php\nnamespace {$space};\n\nclass {$class}\n{\n\n}";
break;
case 'model': // 模型
$content = "<?php\nnamespace {$space};\n\nuse think\Model;\n\nclass {$class} extends Model\n{\n\n}";
break;
case 'view': // 视图
$filename = $modulePath . $path . DIRECTORY_SEPARATOR . $val . '.html';
$this->checkDirBuild(dirname($filename));
$content = '';
break;
default:
// 其他文件
$content = "<?php\nnamespace {$space};\n\nclass {$class}\n{\n\n}";
}
if (!is_file($filename)) {
file_put_contents($filename, $content);
}
}
}
}
}
/**
* 根据注释自动生成路由规则
* @access public
* @param bool $suffix 类库后缀
* @param string $layer 控制器层目录名
* @return string
*/
public function buildRoute($suffix = false, $layer = '')
{
$namespace = $this->app->getNameSpace();
$content = '<?php ' . PHP_EOL . '//根据 Annotation 自动生成的路由规则';
if (!$layer) {
$layer = $this->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, "<?php\n//配置文件\nreturn [\n\n];");
}
$filename = $this->basePath . ($module ? $module . DIRECTORY_SEPARATOR : '') . 'common.php';
if (!is_file($filename)) {
file_put_contents($filename, "<?php\n");
}
}
/**
* 创建目录
* @access protected
* @param string $dirname 目录名称
* @return void
*/
protected function checkDirBuild($dirname)
{
if (!is_dir($dirname)) {
mkdir($dirname, 0755, true);
}
}
}

View File

@@ -0,0 +1,133 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
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);
}
}

View File

@@ -0,0 +1,552 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: zhangyajun <448901948@qq.com>
// +----------------------------------------------------------------------
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;
}
}

View File

@@ -0,0 +1,398 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
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);
}
}

View File

@@ -0,0 +1,829 @@
<?php
// +----------------------------------------------------------------------
// | TopThink [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2015 http://www.topthink.com All rights reserved.
// +----------------------------------------------------------------------
// | Author: zhangyajun <448901948@qq.com>
// +----------------------------------------------------------------------
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('<info>%s</info> version <comment>%s</comment>', $this->getName(), $this->getVersion());
}
return '<info>Console Tool</info>';
}
/**
* 注册一个指令 (便于动态创建指令)
* @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;
}
}

View File

@@ -0,0 +1,618 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
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;
}
}

View File

@@ -0,0 +1,287 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
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;
}
}

View File

@@ -0,0 +1,268 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
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);
}
}
}

View File

@@ -0,0 +1,197 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
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&param2=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);
}
}

View File

@@ -0,0 +1,278 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
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 = '<pre>' . $label . $output . '</pre>';
}
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, '</body>');
if (false !== $pos) {
$content = substr($content, 0, $pos) . $output . substr($content, $pos);
} else {
$content = $content . $output;
}
}
}
}
public function __debugInfo()
{
$data = get_object_vars($this);
unset($data['app']);
return $data;
}
}

View File

@@ -0,0 +1,113 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think;
class Env
{
/**
* 环境变量数据
* @var array
*/
protected $data = [];
public function __construct()
{
$this->data = $_ENV;
}
/**
* 读取环境变量定义文件
* @access public
* @param string $file 环境变量定义文件
* @return void
*/
public function load($file)
{
$env = parse_ini_file($file, true);
$this->set($env);
}
/**
* 获取环境变量值
* @access public
* @param string $name 环境变量名
* @param mixed $default 默认值
* @return mixed
*/
public function get($name = null, $default = null, $php_prefix = true)
{
if (is_null($name)) {
return $this->data;
}
$name = strtoupper(str_replace('.', '_', $name));
if (isset($this->data[$name])) {
return $this->data[$name];
}
return $this->getEnv($name, $default, $php_prefix);
}
protected function getEnv($name, $default = null, $php_prefix = true)
{
if ($php_prefix) {
$name = 'PHP_' . $name;
}
$result = getenv($name);
if (false === $result) {
return $default;
}
if ('false' === $result) {
$result = false;
} elseif ('true' === $result) {
$result = true;
}
if (!isset($this->data[$name])) {
$this->data[$name] = $result;
}
return $result;
}
/**
* 设置环境变量值
* @access public
* @param string|array $env 环境变量
* @param mixed $value 值
* @return void
*/
public function set($env, $value = null)
{
if (is_array($env)) {
$env = array_change_key_case($env, CASE_UPPER);
foreach ($env as $key => $val) {
if (is_array($val)) {
foreach ($val as $k => $v) {
$this->data[$key . '_' . strtoupper($k)] = $v;
}
} else {
$this->data[$key] = $val;
}
}
} else {
$name = strtoupper(str_replace('.', '_', $env));
$this->data[$name] = $value;
}
}
}

View File

@@ -0,0 +1,147 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
// +----------------------------------------------------------------------
namespace think;
use think\console\Output as ConsoleOutput;
use think\exception\ErrorException;
use think\exception\Handle;
use think\exception\ThrowableError;
class Error
{
/**
* 配置参数
* @var array
*/
protected static $exceptionHandler;
/**
* 注册异常处理
* @access public
* @return void
*/
public static function register()
{
error_reporting(E_ALL);
set_error_handler([__CLASS__, 'appError']);
set_exception_handler([__CLASS__, 'appException']);
register_shutdown_function([__CLASS__, 'appShutdown']);
}
/**
* Exception Handler
* @access public
* @param \Exception|\Throwable $e
*/
public static function appException($e)
{
if (!$e instanceof \Exception) {
$e = new ThrowableError($e);
}
self::getExceptionHandler()->report($e);
if (PHP_SAPI == 'cli') {
self::getExceptionHandler()->renderForConsole(new ConsoleOutput, $e);
} else {
self::getExceptionHandler()->render($e)->send();
}
}
/**
* Error Handler
* @access public
* @param integer $errno 错误编号
* @param integer $errstr 详细错误信息
* @param string $errfile 出错的文件
* @param integer $errline 出错行号
* @throws ErrorException
*/
public static function appError($errno, $errstr, $errfile = '', $errline = 0)
{
$exception = new ErrorException($errno, $errstr, $errfile, $errline);
if (error_reporting() & $errno) {
// 将错误信息托管至 think\exception\ErrorException
throw $exception;
}
self::getExceptionHandler()->report($exception);
}
/**
* Shutdown Handler
* @access public
*/
public static function appShutdown()
{
if (!is_null($error = error_get_last()) && self::isFatal($error['type'])) {
// 将错误信息托管至think\ErrorException
$exception = new ErrorException($error['type'], $error['message'], $error['file'], $error['line']);
self::appException($exception);
}
// 写入日志
Container::get('log')->save();
}
/**
* 确定错误类型是否致命
*
* @access protected
* @param int $type
* @return bool
*/
protected static function isFatal($type)
{
return in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]);
}
/**
* 设置异常处理类
*
* @access public
* @param mixed $handle
* @return void
*/
public static function setExceptionHandler($handle)
{
self::$exceptionHandler = $handle;
}
/**
* Get an instance of the exception handler.
*
* @access public
* @return Handle
*/
public static function getExceptionHandler()
{
static $handle;
if (!$handle) {
// 异常处理handle
$class = self::$exceptionHandler;
if ($class && is_string($class) && class_exists($class) && is_subclass_of($class, "\\think\\exception\\Handle")) {
$handle = new $class;
} else {
$handle = new Handle;
if ($class instanceof \Closure) {
$handle->setRender($class);
}
}
}
return $handle;
}
}

View File

@@ -0,0 +1,56 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
// +----------------------------------------------------------------------
namespace think;
class Exception extends \Exception
{
/**
* 保存异常页面显示的额外Debug数据
* @var array
*/
protected $data = [];
/**
* 设置异常额外的Debug数据
* 数据将会显示为下面的格式
*
* Exception Data
* --------------------------------------------------
* Label 1
* key1 value1
* key2 value2
* Label 2
* key1 value1
* key2 value2
*
* @access protected
* @param string $label 数据分类,用于异常页面显示
* @param array $data 需要显示的数据,必须为关联数组
*/
final protected function setData($label, array $data)
{
$this->data[$label] = $data;
}
/**
* 获取异常额外Debug数据
* 主要用于输出到异常页面便于调试
* @access public
* @return array 由setData设置的Debug数据
*/
final public function getData()
{
return $this->data;
}
}

View File

@@ -0,0 +1,125 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think;
class Facade
{
/**
* 绑定对象
* @var array
*/
protected static $bind = [];
/**
* 始终创建新的对象实例
* @var bool
*/
protected static $alwaysNewInstance;
/**
* 绑定类的静态代理
* @static
* @access public
* @param string|array $name 类标识
* @param string $class 类名
* @return object
*/
public static function bind($name, $class = null)
{
if (__CLASS__ != static::class) {
return self::__callStatic('bind', func_get_args());
}
if (is_array($name)) {
self::$bind = array_merge(self::$bind, $name);
} else {
self::$bind[$name] = $class;
}
}
/**
* 创建Facade实例
* @static
* @access protected
* @param string $class 类名或标识
* @param array $args 变量
* @param bool $newInstance 是否每次创建新的实例
* @return object
*/
protected static function createFacade($class = '', $args = [], $newInstance = false)
{
$class = $class ?: static::class;
$facadeClass = static::getFacadeClass();
if ($facadeClass) {
$class = $facadeClass;
} elseif (isset(self::$bind[$class])) {
$class = self::$bind[$class];
}
if (static::$alwaysNewInstance) {
$newInstance = true;
}
return Container::getInstance()->make($class, $args, $newInstance);
}
/**
* 获取当前Facade对应类名或者已经绑定的容器对象标识
* @access protected
* @return string
*/
protected static function getFacadeClass()
{}
/**
* 带参数实例化当前Facade类
* @access public
* @return mixed
*/
public static function instance(...$args)
{
if (__CLASS__ != static::class) {
return self::createFacade('', $args);
}
}
/**
* 调用类的实例
* @access public
* @param string $class 类名或者标识
* @param array|true $args 变量
* @param bool $newInstance 是否每次创建新的实例
* @return mixed
*/
public static function make($class, $args = [], $newInstance = false)
{
if (__CLASS__ != static::class) {
return self::__callStatic('make', func_get_args());
}
if (true === $args) {
// 总是创建新的实例化对象
$newInstance = true;
$args = [];
}
return self::createFacade($class, $args, $newInstance);
}
// 调用实际类的方法
public static function __callStatic($method, $params)
{
return call_user_func_array([static::createFacade(), $method], $params);
}
}

View File

@@ -0,0 +1,496 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think;
use SplFileObject;
class File extends SplFileObject
{
/**
* 错误信息
* @var string
*/
private $error = '';
/**
* 当前完整文件名
* @var string
*/
protected $filename;
/**
* 上传文件名
* @var string
*/
protected $saveName;
/**
* 上传文件命名规则
* @var string
*/
protected $rule = 'date';
/**
* 上传文件验证规则
* @var array
*/
protected $validate = [];
/**
* 是否单元测试
* @var bool
*/
protected $isTest;
/**
* 上传文件信息
* @var array
*/
protected $info = [];
/**
* 文件hash规则
* @var array
*/
protected $hash = [];
public function __construct($filename, $mode = 'r')
{
parent::__construct($filename, $mode);
$this->filename = $this->getRealPath() ?: $this->getPathname();
}
/**
* 是否测试
* @access public
* @param bool $test 是否测试
* @return $this
*/
public function isTest($test = false)
{
$this->isTest = $test;
return $this;
}
/**
* 设置上传信息
* @access public
* @param array $info 上传文件信息
* @return $this
*/
public function setUploadInfo($info)
{
$this->info = $info;
return $this;
}
/**
* 获取上传文件的信息
* @access public
* @param string $name
* @return array|string
*/
public function getInfo($name = '')
{
return isset($this->info[$name]) ? $this->info[$name] : $this->info;
}
/**
* 获取上传文件的文件名
* @access public
* @return string
*/
public function getSaveName()
{
return $this->saveName;
}
/**
* 设置上传文件的保存文件名
* @access public
* @param string $saveName
* @return $this
*/
public function setSaveName($saveName)
{
$this->saveName = $saveName;
return $this;
}
/**
* 获取文件的哈希散列值
* @access public
* @param string $type
* @return string
*/
public function hash($type = 'sha1')
{
if (!isset($this->hash[$type])) {
$this->hash[$type] = hash_file($type, $this->filename);
}
return $this->hash[$type];
}
/**
* 检查目录是否可写
* @access protected
* @param string $path 目录
* @return boolean
*/
protected function checkPath($path)
{
if (is_dir($path)) {
return true;
}
if (mkdir($path, 0755, true)) {
return true;
}
$this->error = ['directory {:path} creation failed', ['path' => $path]];
return false;
}
/**
* 获取文件类型信息
* @access public
* @return string
*/
public function getMime()
{
$finfo = finfo_open(FILEINFO_MIME_TYPE);
return finfo_file($finfo, $this->filename);
}
/**
* 设置文件的命名规则
* @access public
* @param string $rule 文件命名规则
* @return $this
*/
public function rule($rule)
{
$this->rule = $rule;
return $this;
}
/**
* 设置上传文件的验证规则
* @access public
* @param array $rule 验证规则
* @return $this
*/
public function validate($rule = [])
{
$this->validate = $rule;
return $this;
}
/**
* 检测是否合法的上传文件
* @access public
* @return bool
*/
public function isValid()
{
if ($this->isTest) {
return is_file($this->filename);
}
return is_uploaded_file($this->filename);
}
/**
* 检测上传文件
* @access public
* @param array $rule 验证规则
* @return bool
*/
public function check($rule = [])
{
$rule = $rule ?: $this->validate;
if ((isset($rule['size']) && !$this->checkSize($rule['size']))
|| (isset($rule['type']) && !$this->checkMime($rule['type']))
|| (isset($rule['ext']) && !$this->checkExt($rule['ext']))
|| !$this->checkImg()) {
return false;
}
return true;
}
/**
* 检测上传文件后缀
* @access public
* @param array|string $ext 允许后缀
* @return bool
*/
public function checkExt($ext)
{
if (is_string($ext)) {
$ext = explode(',', $ext);
}
$extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION));
if (!in_array($extension, $ext)) {
$this->error = 'extensions to upload is not allowed';
return false;
}
return true;
}
/**
* 检测图像文件
* @access public
* @return bool
*/
public function checkImg()
{
$extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION));
/* 对图像文件进行严格检测 */
if (in_array($extension, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf']) && !in_array($this->getImageType($this->filename), [1, 2, 3, 4, 6, 13])) {
$this->error = 'illegal image files';
return false;
}
return true;
}
// 判断图像类型
protected function getImageType($image)
{
if (function_exists('exif_imagetype')) {
return exif_imagetype($image);
}
try {
$info = getimagesize($image);
return $info ? $info[2] : false;
} catch (\Exception $e) {
return false;
}
}
/**
* 检测上传文件大小
* @access public
* @param integer $size 最大大小
* @return bool
*/
public function checkSize($size)
{
if ($this->getSize() > (int) $size) {
$this->error = 'filesize not match';
return false;
}
return true;
}
/**
* 检测上传文件类型
* @access public
* @param array|string $mime 允许类型
* @return bool
*/
public function checkMime($mime)
{
if (is_string($mime)) {
$mime = explode(',', $mime);
}
if (!in_array(strtolower($this->getMime()), $mime)) {
$this->error = 'mimetype to upload is not allowed';
return false;
}
return true;
}
/**
* 移动文件
* @access public
* @param string $path 保存路径
* @param string|bool $savename 保存的文件名 默认自动生成
* @param boolean $replace 同名文件是否覆盖
* @param bool $autoAppendExt 自动补充扩展名
* @return false|File false-失败 否则返回File实例
*/
public function move($path, $savename = true, $replace = true, $autoAppendExt = true)
{
// 文件上传失败,捕获错误代码
if (!empty($this->info['error'])) {
$this->error($this->info['error']);
return false;
}
// 检测合法性
if (!$this->isValid()) {
$this->error = 'upload illegal files';
return false;
}
// 验证上传
if (!$this->check()) {
return false;
}
$path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
// 文件保存命名规则
$saveName = $this->buildSaveName($savename, $autoAppendExt);
$filename = $path . $saveName;
// 检测目录
if (false === $this->checkPath(dirname($filename))) {
return false;
}
/* 不覆盖同名文件 */
if (!$replace && is_file($filename)) {
$this->error = ['has the same filename: {:filename}', ['filename' => $filename]];
return false;
}
/* 移动文件 */
if ($this->isTest) {
rename($this->filename, $filename);
} elseif (!move_uploaded_file($this->filename, $filename)) {
$this->error = 'upload write error';
return false;
}
// 返回 File对象实例
$file = new self($filename);
$file->setSaveName($saveName);
$file->setUploadInfo($this->info);
return $file;
}
/**
* 获取保存文件名
* @access protected
* @param string|bool $savename 保存的文件名 默认自动生成
* @param bool $autoAppendExt 自动补充扩展名
* @return string
*/
protected function buildSaveName($savename, $autoAppendExt = true)
{
if (true === $savename) {
// 自动生成文件名
$savename = $this->autoBuildName();
} elseif ('' === $savename || false === $savename) {
// 保留原文件名
$savename = $this->getInfo('name');
}
if ($autoAppendExt && false === strpos($savename, '.')) {
$savename .= '.' . pathinfo($this->getInfo('name'), PATHINFO_EXTENSION);
}
return $savename;
}
/**
* 自动生成文件名
* @access protected
* @return string
*/
protected function autoBuildName()
{
if ($this->rule instanceof \Closure) {
$savename = call_user_func_array($this->rule, [$this]);
} else {
switch ($this->rule) {
case 'date':
$savename = date('Ymd') . DIRECTORY_SEPARATOR . md5(microtime(true));
break;
default:
if (in_array($this->rule, hash_algos())) {
$hash = $this->hash($this->rule);
$savename = substr($hash, 0, 2) . DIRECTORY_SEPARATOR . substr($hash, 2);
} elseif (is_callable($this->rule)) {
$savename = call_user_func($this->rule);
} else {
$savename = date('Ymd') . DIRECTORY_SEPARATOR . md5(microtime(true));
}
}
}
return $savename;
}
/**
* 获取错误代码信息
* @access private
* @param int $errorNo 错误号
*/
private function error($errorNo)
{
switch ($errorNo) {
case 1:
case 2:
$this->error = 'upload File size exceeds the maximum value';
break;
case 3:
$this->error = 'only the portion of file is uploaded';
break;
case 4:
$this->error = 'no file to uploaded';
break;
case 6:
$this->error = 'upload temp dir not found';
break;
case 7:
$this->error = 'file write error';
break;
default:
$this->error = 'unknown upload error';
}
}
/**
* 获取错误信息(支持多语言)
* @access public
* @return string
*/
public function getError()
{
$lang = Container::get('lang');
if (is_array($this->error)) {
list($msg, $vars) = $this->error;
} else {
$msg = $this->error;
$vars = [];
}
return $lang->has($msg) ? $lang->get($msg, $vars) : $msg;
}
public function __call($method, $args)
{
return $this->hash($method);
}
}

View File

@@ -0,0 +1,220 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think;
class Hook
{
/**
* 钩子行为定义
* @var array
*/
private $tags = [];
/**
* 绑定行为列表
* @var array
*/
protected $bind = [];
/**
* 入口方法名称
* @var string
*/
private static $portal = 'run';
/**
* 应用对象
* @var App
*/
protected $app;
public function __construct(App $app)
{
$this->app = $app;
}
/**
* 指定入口方法名称
* @access public
* @param string $name 方法名
* @return $this
*/
public function portal($name)
{
self::$portal = $name;
return $this;
}
/**
* 指定行为标识 便于调用
* @access public
* @param string|array $name 行为标识
* @param mixed $behavior 行为
* @return $this
*/
public function alias($name, $behavior = null)
{
if (is_array($name)) {
$this->bind = array_merge($this->bind, $name);
} else {
$this->bind[$name] = $behavior;
}
return $this;
}
/**
* 动态添加行为扩展到某个标签
* @access public
* @param string $tag 标签名称
* @param mixed $behavior 行为名称
* @param bool $first 是否放到开头执行
* @return void
*/
public function add($tag, $behavior, $first = false)
{
isset($this->tags[$tag]) || $this->tags[$tag] = [];
if (is_array($behavior) && !is_callable($behavior)) {
if (!array_key_exists('_overlay', $behavior)) {
$this->tags[$tag] = array_merge($this->tags[$tag], $behavior);
} else {
unset($behavior['_overlay']);
$this->tags[$tag] = $behavior;
}
} elseif ($first) {
array_unshift($this->tags[$tag], $behavior);
} else {
$this->tags[$tag][] = $behavior;
}
}
/**
* 批量导入插件
* @access public
* @param array $tags 插件信息
* @param bool $recursive 是否递归合并
* @return void
*/
public function import(array $tags, $recursive = true)
{
if ($recursive) {
foreach ($tags as $tag => $behavior) {
$this->add($tag, $behavior);
}
} else {
$this->tags = $tags + $this->tags;
}
}
/**
* 获取插件信息
* @access public
* @param string $tag 插件位置 留空获取全部
* @return array
*/
public function get($tag = '')
{
if (empty($tag)) {
//获取全部的插件信息
return $this->tags;
}
return array_key_exists($tag, $this->tags) ? $this->tags[$tag] : [];
}
/**
* 监听标签的行为
* @access public
* @param string $tag 标签名称
* @param mixed $params 传入参数
* @param bool $once 只获取一个有效返回值
* @return mixed
*/
public function listen($tag, $params = null, $once = false)
{
$results = [];
$tags = $this->get($tag);
foreach ($tags as $key => $name) {
$results[$key] = $this->execTag($name, $tag, $params);
if (false === $results[$key] || (!is_null($results[$key]) && $once)) {
break;
}
}
return $once ? end($results) : $results;
}
/**
* 执行行为
* @access public
* @param mixed $class 行为
* @param mixed $params 参数
* @return mixed
*/
public function exec($class, $params = null)
{
if ($class instanceof \Closure || is_array($class)) {
$method = $class;
} else {
if (isset($this->bind[$class])) {
$class = $this->bind[$class];
}
$method = [$class, self::$portal];
}
return $this->app->invoke($method, [$params]);
}
/**
* 执行某个标签的行为
* @access protected
* @param mixed $class 要执行的行为
* @param string $tag 方法名(标签名)
* @param mixed $params 参数
* @return mixed
*/
protected function execTag($class, $tag = '', $params = null)
{
$method = Loader::parseName($tag, 1, false);
if ($class instanceof \Closure) {
$call = $class;
$class = 'Closure';
} elseif (is_array($class) || strpos($class, '::')) {
$call = $class;
} else {
$obj = Container::get($class);
if (!is_callable([$obj, $method])) {
$method = self::$portal;
}
$call = [$class, $method];
$class = $class . '->' . $method;
}
$result = $this->app->invoke($call, [$params]);
return $result;
}
public function __debugInfo()
{
$data = get_object_vars($this);
unset($data['app']);
return $data;
}
}

View File

@@ -0,0 +1,284 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think;
class Lang
{
/**
* 多语言信息
* @var array
*/
private $lang = [];
/**
* 当前语言
* @var string
*/
private $range = 'zh-cn';
/**
* 多语言自动侦测变量名
* @var string
*/
protected $langDetectVar = 'lang';
/**
* 多语言cookie变量
* @var string
*/
protected $langCookieVar = 'think_var';
/**
* 允许的多语言列表
* @var array
*/
protected $allowLangList = [];
/**
* Accept-Language转义为对应语言包名称 系统默认配置
* @var string
*/
protected $acceptLanguage = [
'zh-hans-cn' => 'zh-cn',
];
/**
* 应用对象
* @var App
*/
protected $app;
public function __construct(App $app)
{
$this->app = $app;
}
// 设定当前的语言
public function range($range = '')
{
if ('' == $range) {
return $this->range;
} else {
$this->range = $range;
}
}
/**
* 设置语言定义(不区分大小写)
* @access public
* @param string|array $name 语言变量
* @param string $value 语言值
* @param string $range 语言作用域
* @return mixed
*/
public function set($name, $value = null, $range = '')
{
$range = $range ?: $this->range;
// 批量定义
if (!isset($this->lang[$range])) {
$this->lang[$range] = [];
}
if (is_array($name)) {
return $this->lang[$range] = array_change_key_case($name) + $this->lang[$range];
}
return $this->lang[$range][strtolower($name)] = $value;
}
/**
* 加载语言定义(不区分大小写)
* @access public
* @param string|array $file 语言文件
* @param string $range 语言作用域
* @return array
*/
public function load($file, $range = '')
{
$range = $range ?: $this->range;
if (!isset($this->lang[$range])) {
$this->lang[$range] = [];
}
// 批量定义
if (is_string($file)) {
$file = [$file];
}
$lang = [];
foreach ($file as $_file) {
if (is_file($_file)) {
// 记录加载信息
$this->app->log('[ LANG ] ' . $_file);
$_lang = include $_file;
if (is_array($_lang)) {
$lang = array_change_key_case($_lang) + $lang;
}
}
}
if (!empty($lang)) {
$this->lang[$range] = $lang + $this->lang[$range];
}
return $this->lang[$range];
}
/**
* 获取语言定义(不区分大小写)
* @access public
* @param string|null $name 语言变量
* @param string $range 语言作用域
* @return bool
*/
public function has($name, $range = '')
{
$range = $range ?: $this->range;
return isset($this->lang[$range][strtolower($name)]);
}
/**
* 获取语言定义(不区分大小写)
* @access public
* @param string|null $name 语言变量
* @param array $vars 变量替换
* @param string $range 语言作用域
* @return mixed
*/
public function get($name = null, $vars = [], $range = '')
{
$range = $range ?: $this->range;
// 空参数返回所有定义
if (is_null($name)) {
return $this->lang[$range];
}
$key = strtolower($name);
$value = isset($this->lang[$range][$key]) ? $this->lang[$range][$key] : $name;
// 变量解析
if (!empty($vars) && is_array($vars)) {
/**
* Notes:
* 为了检测的方便数字索引的判断仅仅是参数数组的第一个元素的key为数字0
* 数字索引采用的是系统的 sprintf 函数替换,用法请参考 sprintf 函数
*/
if (key($vars) === 0) {
// 数字索引解析
array_unshift($vars, $value);
$value = call_user_func_array('sprintf', $vars);
} else {
// 关联索引解析
$replace = array_keys($vars);
foreach ($replace as &$v) {
$v = "{:{$v}}";
}
$value = str_replace($replace, $vars, $value);
}
}
return $value;
}
/**
* 自动侦测设置获取语言选择
* @access public
* @return string
*/
public function detect()
{
// 自动侦测设置获取语言选择
$langSet = '';
if (isset($_GET[$this->langDetectVar])) {
// url中设置了语言变量
$langSet = strtolower($_GET[$this->langDetectVar]);
} elseif (isset($_COOKIE[$this->langCookieVar])) {
// Cookie中设置了语言变量
$langSet = strtolower($_COOKIE[$this->langCookieVar]);
} elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
// 自动侦测浏览器语言
preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches);
$langSet = strtolower($matches[1]);
if (isset($this->acceptLanguage[$langSet])) {
$langSet = $this->acceptLanguage[$langSet];
}
}
if (empty($this->allowLangList) || in_array($langSet, $this->allowLangList)) {
// 合法的语言
$this->range = $langSet ?: $this->range;
}
return $this->range;
}
/**
* 设置当前语言到Cookie
* @access public
* @param string $lang 语言
* @return void
*/
public function saveToCookie($lang = null)
{
$range = $lang ?: $this->range;
$_COOKIE[$this->langCookieVar] = $range;
}
/**
* 设置语言自动侦测的变量
* @access public
* @param string $var 变量名称
* @return void
*/
public function setLangDetectVar($var)
{
$this->langDetectVar = $var;
}
/**
* 设置语言的cookie保存变量
* @access public
* @param string $var 变量名称
* @return void
*/
public function setLangCookieVar($var)
{
$this->langCookieVar = $var;
}
/**
* 设置允许的语言列表
* @access public
* @param array $list 语言列表
* @return void
*/
public function setAllowLangList(array $list)
{
$this->allowLangList = $list;
}
/**
* 设置转义的语言列表
* @access public
* @param array $list 语言列表
* @return void
*/
public function setAcceptLanguage(array $list)
{
$this->acceptLanguage = array_merge($this->acceptLanguage, $list);
}
}

View File

@@ -0,0 +1,417 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think;
use think\exception\ClassNotFoundException;
class Loader
{
/**
* 类名映射信息
* @var array
*/
protected static $classMap = [];
/**
* 类库别名
* @var array
*/
protected static $classAlias = [];
/**
* PSR-4
* @var array
*/
private static $prefixLengthsPsr4 = [];
private static $prefixDirsPsr4 = [];
private static $fallbackDirsPsr4 = [];
/**
* PSR-0
* @var array
*/
private static $prefixesPsr0 = [];
private static $fallbackDirsPsr0 = [];
/**
* 需要加载的文件
* @var array
*/
private static $files = [];
/**
* Composer安装路径
* @var string
*/
private static $composerPath;
// 获取应用根目录
public static function getRootPath()
{
if ('cli' == PHP_SAPI) {
$scriptName = realpath($_SERVER['argv'][0]);
} else {
$scriptName = $_SERVER['SCRIPT_FILENAME'];
}
$path = realpath(dirname($scriptName));
if (!is_file($path . DIRECTORY_SEPARATOR . 'think')) {
$path = dirname($path);
}
return $path . DIRECTORY_SEPARATOR;
}
// 注册自动加载机制
public static function register($autoload = '')
{
// 注册系统自动加载
spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true);
$rootPath = self::getRootPath();
self::$composerPath = $rootPath . 'vendor' . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR;
// Composer自动加载支持
if (is_dir(self::$composerPath)) {
if (is_file(self::$composerPath . 'autoload_static.php')) {
require self::$composerPath . 'autoload_static.php';
$declaredClass = get_declared_classes();
$composerClass = array_pop($declaredClass);
foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) {
if (property_exists($composerClass, $attr)) {
self::${$attr} = $composerClass::${$attr};
}
}
} else {
self::registerComposerLoader(self::$composerPath);
}
}
// 注册命名空间定义
self::addNamespace([
'think' => __DIR__,
'traits' => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'traits',
]);
// 加载类库映射文件
if (is_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php')) {
self::addClassMap(__include_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php'));
}
// 自动加载extend目录
self::addAutoLoadDir($rootPath . 'extend');
}
// 自动加载
public static function autoload($class)
{
if (isset(self::$classAlias[$class])) {
return class_alias(self::$classAlias[$class], $class);
}
if ($file = self::findFile($class)) {
// Win环境严格区分大小写
if (strpos(PHP_OS, 'WIN') !== false && pathinfo($file, PATHINFO_FILENAME) != pathinfo(realpath($file), PATHINFO_FILENAME)) {
return false;
}
__include_file($file);
return true;
}
}
/**
* 查找文件
* @access private
* @param string $class
* @return string|false
*/
private static function findFile($class)
{
if (!empty(self::$classMap[$class])) {
// 类库映射
return self::$classMap[$class];
}
// 查找 PSR-4
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . '.php';
$first = $class[0];
if (isset(self::$prefixLengthsPsr4[$first])) {
foreach (self::$prefixLengthsPsr4[$first] as $prefix => $length) {
if (0 === strpos($class, $prefix)) {
foreach (self::$prefixDirsPsr4[$prefix] as $dir) {
if (is_file($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
return $file;
}
}
}
}
}
// 查找 PSR-4 fallback dirs
foreach (self::$fallbackDirsPsr4 as $dir) {
if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// 查找 PSR-0
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . '.php';
}
if (isset(self::$prefixesPsr0[$first])) {
foreach (self::$prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// 查找 PSR-0 fallback dirs
foreach (self::$fallbackDirsPsr0 as $dir) {
if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
return self::$classMap[$class] = false;
}
// 注册classmap
public static function addClassMap($class, $map = '')
{
if (is_array($class)) {
self::$classMap = array_merge(self::$classMap, $class);
} else {
self::$classMap[$class] = $map;
}
}
// 注册命名空间
public static function addNamespace($namespace, $path = '')
{
if (is_array($namespace)) {
foreach ($namespace as $prefix => $paths) {
self::addPsr4($prefix . '\\', rtrim($paths, DIRECTORY_SEPARATOR), true);
}
} else {
self::addPsr4($namespace . '\\', rtrim($path, DIRECTORY_SEPARATOR), true);
}
}
// 添加Ps0空间
private static function addPsr0($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
self::$fallbackDirsPsr0 = array_merge(
(array) $paths,
self::$fallbackDirsPsr0
);
} else {
self::$fallbackDirsPsr0 = array_merge(
self::$fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset(self::$prefixesPsr0[$first][$prefix])) {
self::$prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
self::$prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
self::$prefixesPsr0[$first][$prefix]
);
} else {
self::$prefixesPsr0[$first][$prefix] = array_merge(
self::$prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
// 添加Psr4空间
private static function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
self::$fallbackDirsPsr4 = array_merge(
(array) $paths,
self::$fallbackDirsPsr4
);
} else {
self::$fallbackDirsPsr4 = array_merge(
self::$fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset(self::$prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
self::$prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
self::$prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
self::$prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
self::$prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
self::$prefixDirsPsr4[$prefix] = array_merge(
self::$prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
// 注册自动加载类库目录
public static function addAutoLoadDir($path)
{
self::$fallbackDirsPsr4[] = $path;
}
// 注册类别名
public static function addClassAlias($alias, $class = null)
{
if (is_array($alias)) {
self::$classAlias = array_merge(self::$classAlias, $alias);
} else {
self::$classAlias[$alias] = $class;
}
}
// 注册composer自动加载
public static function registerComposerLoader($composerPath)
{
if (is_file($composerPath . 'autoload_namespaces.php')) {
$map = require $composerPath . 'autoload_namespaces.php';
foreach ($map as $namespace => $path) {
self::addPsr0($namespace, $path);
}
}
if (is_file($composerPath . 'autoload_psr4.php')) {
$map = require $composerPath . 'autoload_psr4.php';
foreach ($map as $namespace => $path) {
self::addPsr4($namespace, $path);
}
}
if (is_file($composerPath . 'autoload_classmap.php')) {
$classMap = require $composerPath . 'autoload_classmap.php';
if ($classMap) {
self::addClassMap($classMap);
}
}
if (is_file($composerPath . 'autoload_files.php')) {
self::$files = require $composerPath . 'autoload_files.php';
}
}
// 加载composer autofile文件
public static function loadComposerAutoloadFiles()
{
foreach (self::$files as $fileIdentifier => $file) {
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
__require_file($file);
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
}
}
}
/**
* 字符串命名风格转换
* type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格
* @access public
* @param string $name 字符串
* @param integer $type 转换类型
* @param bool $ucfirst 首字母是否大写(驼峰规则)
* @return string
*/
public static function parseName($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);
}
return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));
}
/**
* 创建工厂对象实例
* @access public
* @param string $name 工厂类名
* @param string $namespace 默认命名空间
* @return mixed
*/
public static function factory($name, $namespace = '', ...$args)
{
$class = false !== strpos($name, '\\') ? $name : $namespace . ucwords($name);
if (class_exists($class)) {
return Container::getInstance()->invokeClass($class, $args);
} else {
throw new ClassNotFoundException('class not exists:' . $class, $class);
}
}
}
/**
* 作用范围隔离
*
* @param $file
* @return mixed
*/
function __include_file($file)
{
return include $file;
}
function __require_file($file)
{
return require $file;
}

View File

@@ -0,0 +1,389 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think;
class Log implements LoggerInterface
{
const EMERGENCY = 'emergency';
const ALERT = 'alert';
const CRITICAL = 'critical';
const ERROR = 'error';
const WARNING = 'warning';
const NOTICE = 'notice';
const INFO = 'info';
const DEBUG = 'debug';
const SQL = 'sql';
/**
* 日志信息
* @var array
*/
protected $log = [];
/**
* 配置参数
* @var array
*/
protected $config = [];
/**
* 日志写入驱动
* @var object
*/
protected $driver;
/**
* 日志授权key
* @var string
*/
protected $key;
/**
* 是否允许日志写入
* @var bool
*/
protected $allowWrite = true;
/**
* 应用对象
* @var App
*/
protected $app;
public function __construct(App $app)
{
$this->app = $app;
}
public static function __make(App $app, Config $config)
{
return (new static($app))->init($config->pull('log'));
}
/**
* 日志初始化
* @access public
* @param array $config
* @return $this
*/
public function init($config = [])
{
$type = isset($config['type']) ? $config['type'] : 'File';
$this->config = $config;
unset($config['type']);
if (!empty($config['close'])) {
$this->allowWrite = false;
}
$this->driver = Loader::factory($type, '\\think\\log\\driver\\', $config);
return $this;
}
/**
* 获取日志信息
* @access public
* @param string $type 信息类型
* @return array
*/
public function getLog($type = '')
{
return $type ? $this->log[$type] : $this->log;
}
/**
* 记录日志信息
* @access public
* @param mixed $msg 日志信息
* @param string $type 日志级别
* @param array $context 替换内容
* @return $this
*/
public function record($msg, $type = 'info', array $context = [])
{
if (!$this->allowWrite) {
return;
}
if (is_string($msg) && !empty($context)) {
$replace = [];
foreach ($context as $key => $val) {
$replace['{' . $key . '}'] = $val;
}
$msg = strtr($msg, $replace);
}
if (PHP_SAPI == 'cli') {
if (empty($this->config['level']) || in_array($type, $this->config['level'])) {
// 命令行日志实时写入
$this->write($msg, $type, true);
}
} else {
$this->log[$type][] = $msg;
}
return $this;
}
/**
* 清空日志信息
* @access public
* @return $this
*/
public function clear()
{
$this->log = [];
return $this;
}
/**
* 当前日志记录的授权key
* @access public
* @param string $key 授权key
* @return $this
*/
public function key($key)
{
$this->key = $key;
return $this;
}
/**
* 检查日志写入权限
* @access public
* @param array $config 当前日志配置参数
* @return bool
*/
public function check($config)
{
if ($this->key && !empty($config['allow_key']) && !in_array($this->key, $config['allow_key'])) {
return false;
}
return true;
}
/**
* 关闭本次请求日志写入
* @access public
* @return $this
*/
public function close()
{
$this->allowWrite = false;
$this->log = [];
return $this;
}
/**
* 保存调试信息
* @access public
* @return bool
*/
public function save()
{
if (empty($this->log) || !$this->allowWrite) {
return true;
}
if (!$this->check($this->config)) {
// 检测日志写入权限
return false;
}
$log = [];
foreach ($this->log as $level => $info) {
if (!$this->app->isDebug() && 'debug' == $level) {
continue;
}
if (empty($this->config['level']) || in_array($level, $this->config['level'])) {
$log[$level] = $info;
$this->app['hook']->listen('log_level', [$level, $info]);
}
}
$result = $this->driver->save($log, true);
if ($result) {
$this->log = [];
}
return $result;
}
/**
* 实时写入日志信息 并支持行为
* @access public
* @param mixed $msg 调试信息
* @param string $type 日志级别
* @param bool $force 是否强制写入
* @return bool
*/
public function write($msg, $type = 'info', $force = false)
{
// 封装日志信息
if (empty($this->config['level'])) {
$force = true;
}
if (true === $force || in_array($type, $this->config['level'])) {
$log[$type][] = $msg;
} else {
return false;
}
// 监听log_write
$this->app['hook']->listen('log_write', $log);
// 写入日志
return $this->driver->save($log, false);
}
/**
* 记录日志信息
* @access public
* @param string $level 日志级别
* @param mixed $message 日志信息
* @param array $context 替换内容
* @return void
*/
public function log($level, $message, array $context = [])
{
$this->record($message, $level, $context);
}
/**
* 记录emergency信息
* @access public
* @param mixed $message 日志信息
* @param array $context 替换内容
* @return void
*/
public function emergency($message, array $context = [])
{
$this->log(__FUNCTION__, $message, $context);
}
/**
* 记录警报信息
* @access public
* @param mixed $message 日志信息
* @param array $context 替换内容
* @return void
*/
public function alert($message, array $context = [])
{
$this->log(__FUNCTION__, $message, $context);
}
/**
* 记录紧急情况
* @access public
* @param mixed $message 日志信息
* @param array $context 替换内容
* @return void
*/
public function critical($message, array $context = [])
{
$this->log(__FUNCTION__, $message, $context);
}
/**
* 记录错误信息
* @access public
* @param mixed $message 日志信息
* @param array $context 替换内容
* @return void
*/
public function error($message, array $context = [])
{
$this->log(__FUNCTION__, $message, $context);
}
/**
* 记录warning信息
* @access public
* @param mixed $message 日志信息
* @param array $context 替换内容
* @return void
*/
public function warning($message, array $context = [])
{
$this->log(__FUNCTION__, $message, $context);
}
/**
* 记录notice信息
* @access public
* @param mixed $message 日志信息
* @param array $context 替换内容
* @return void
*/
public function notice($message, array $context = [])
{
$this->log(__FUNCTION__, $message, $context);
}
/**
* 记录一般信息
* @access public
* @param mixed $message 日志信息
* @param array $context 替换内容
* @return void
*/
public function info($message, array $context = [])
{
$this->log(__FUNCTION__, $message, $context);
}
/**
* 记录调试信息
* @access public
* @param mixed $message 日志信息
* @param array $context 替换内容
* @return void
*/
public function debug($message, array $context = [])
{
$this->log(__FUNCTION__, $message, $context);
}
/**
* 记录sql信息
* @access public
* @param mixed $message 日志信息
* @param array $context 替换内容
* @return void
*/
public function sql($message, array $context = [])
{
$this->log(__FUNCTION__, $message, $context);
}
public function __debugInfo()
{
$data = get_object_vars($this);
unset($data['app']);
return $data;
}
}

View File

@@ -0,0 +1,205 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Slince <taosikai@yeah.net>
// +----------------------------------------------------------------------
namespace think;
use InvalidArgumentException;
use LogicException;
use think\exception\HttpResponseException;
class Middleware
{
protected $queue = [];
protected $app;
protected $config = [
'default_namespace' => 'app\\http\\middleware\\',
];
public function __construct(App $app, array $config = [])
{
$this->app = $app;
$this->config = array_merge($this->config, $config);
}
public static function __make(App $app, Config $config)
{
return new static($app, $config->pull('middleware'));
}
public function setConfig(array $config)
{
$this->config = array_merge($this->config, $config);
}
/**
* 导入中间件
* @access public
* @param array $middlewares
* @param string $type 中间件类型
*/
public function import(array $middlewares = [], $type = 'route')
{
foreach ($middlewares as $middleware) {
$this->add($middleware, $type);
}
}
/**
* 注册中间件
* @access public
* @param mixed $middleware
* @param string $type 中间件类型
*/
public function add($middleware, $type = 'route')
{
if (is_null($middleware)) {
return;
}
$middleware = $this->buildMiddleware($middleware, $type);
if ($middleware) {
$this->queue[$type][] = $middleware;
}
}
/**
* 注册控制器中间件
* @access public
* @param mixed $middleware
*/
public function controller($middleware)
{
return $this->add($middleware, 'controller');
}
/**
* 移除中间件
* @access public
* @param mixed $middleware
* @param string $type 中间件类型
*/
public function unshift($middleware, $type = 'route')
{
if (is_null($middleware)) {
return;
}
$middleware = $this->buildMiddleware($middleware, $type);
if ($middleware) {
array_unshift($this->queue[$type], $middleware);
}
}
/**
* 获取注册的中间件
* @access public
* @param string $type 中间件类型
*/
public function all($type = 'route')
{
return $this->queue[$type] ?: [];
}
/**
* 清除中间件
* @access public
*/
public function clear()
{
$this->queue = [];
}
/**
* 中间件调度
* @access public
* @param Request $request
* @param string $type 中间件类型
*/
public function dispatch(Request $request, $type = 'route')
{
return call_user_func($this->resolve($type), $request);
}
/**
* 解析中间件
* @access protected
* @param mixed $middleware
* @param string $type 中间件类型
*/
protected function buildMiddleware($middleware, $type = 'route')
{
if (is_array($middleware)) {
list($middleware, $param) = $middleware;
}
if ($middleware instanceof \Closure) {
return [$middleware, isset($param) ? $param : null];
}
if (!is_string($middleware)) {
throw new InvalidArgumentException('The middleware is invalid');
}
if (false === strpos($middleware, '\\')) {
if (isset($this->config[$middleware])) {
$middleware = $this->config[$middleware];
} else {
$middleware = $this->config['default_namespace'] . $middleware;
}
}
if (is_array($middleware)) {
return $this->import($middleware, $type);
}
if (strpos($middleware, ':')) {
list($middleware, $param) = explode(':', $middleware, 2);
}
return [[$this->app->make($middleware), 'handle'], isset($param) ? $param : null];
}
protected function resolve($type = 'route')
{
return function (Request $request) use ($type) {
$middleware = array_shift($this->queue[$type]);
if (null === $middleware) {
throw new InvalidArgumentException('The queue was exhausted, with no response returned');
}
list($call, $param) = $middleware;
try {
$response = call_user_func_array($call, [$request, $this->resolve($type), $param]);
} catch (HttpResponseException $exception) {
$response = $exception->getResponse();
}
if (!$response instanceof Response) {
throw new LogicException('The middleware must return Response instance');
}
return $response;
};
}
public function __debugInfo()
{
$data = get_object_vars($this);
unset($data['app']);
return $data;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,445 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: zhangyajun <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think;
use ArrayAccess;
use ArrayIterator;
use Countable;
use IteratorAggregate;
use JsonSerializable;
use Traversable;
abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable
{
/**
* 是否简洁模式
* @var bool
*/
protected $simple = false;
/**
* 数据集
* @var Collection
*/
protected $items;
/**
* 当前页
* @var integer
*/
protected $currentPage;
/**
* 最后一页
* @var integer
*/
protected $lastPage;
/**
* 数据总数
* @var integer|null
*/
protected $total;
/**
* 每页数量
* @var integer
*/
protected $listRows;
/**
* 是否有下一页
* @var bool
*/
protected $hasMore;
/**
* 分页配置
* @var array
*/
protected $options = [
'var_page' => 'page',
'path' => '/',
'query' => [],
'fragment' => '',
];
public function __construct($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = [])
{
$this->options = array_merge($this->options, $options);
$this->options['path'] = '/' != $this->options['path'] ? rtrim($this->options['path'], '/') : $this->options['path'];
$this->simple = $simple;
$this->listRows = $listRows;
if (!$items instanceof Collection) {
$items = Collection::make($items);
}
if ($simple) {
$this->currentPage = $this->setCurrentPage($currentPage);
$this->hasMore = count($items) > ($this->listRows);
$items = $items->slice(0, $this->listRows);
} else {
$this->total = $total;
$this->lastPage = (int) ceil($total / $listRows);
$this->currentPage = $this->setCurrentPage($currentPage);
$this->hasMore = $this->currentPage < $this->lastPage;
}
$this->items = $items;
}
/**
* @access public
* @param $items
* @param $listRows
* @param null $currentPage
* @param null $total
* @param bool $simple
* @param array $options
* @return Paginator
*/
public static function make($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = [])
{
return new static($items, $listRows, $currentPage, $total, $simple, $options);
}
protected function setCurrentPage($currentPage)
{
if (!$this->simple && $currentPage > $this->lastPage) {
return $this->lastPage > 0 ? $this->lastPage : 1;
}
return $currentPage;
}
/**
* 获取页码对应的链接
*
* @access protected
* @param $page
* @return string
*/
protected function url($page)
{
if ($page <= 0) {
$page = 1;
}
if (strpos($this->options['path'], '[PAGE]') === false) {
$parameters = [$this->options['var_page'] => $page];
$path = $this->options['path'];
} else {
$parameters = [];
$path = str_replace('[PAGE]', $page, $this->options['path']);
}
if (count($this->options['query']) > 0) {
$parameters = array_merge($this->options['query'], $parameters);
}
$url = $path;
if (!empty($parameters)) {
$url .= '?' . http_build_query($parameters, null, '&');
}
return $url . $this->buildFragment();
}
/**
* 自动获取当前页码
* @access public
* @param string $varPage
* @param int $default
* @return int
*/
public static function getCurrentPage($varPage = 'page', $default = 1)
{
$page = Container::get('request')->param($varPage);
if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int) $page >= 1) {
return $page;
}
return $default;
}
/**
* 自动获取当前的path
* @access public
* @return string
*/
public static function getCurrentPath()
{
return Container::get('request')->baseUrl();
}
public function total()
{
if ($this->simple) {
throw new \DomainException('not support total');
}
return $this->total;
}
public function listRows()
{
return $this->listRows;
}
public function currentPage()
{
return $this->currentPage;
}
public function lastPage()
{
if ($this->simple) {
throw new \DomainException('not support last');
}
return $this->lastPage;
}
/**
* 数据是否足够分页
* @access public
* @return boolean
*/
public function hasPages()
{
return !(1 == $this->currentPage && !$this->hasMore);
}
/**
* 创建一组分页链接
*
* @access public
* @param int $start
* @param int $end
* @return array
*/
public function getUrlRange($start, $end)
{
$urls = [];
for ($page = $start; $page <= $end; $page++) {
$urls[$page] = $this->url($page);
}
return $urls;
}
/**
* 设置URL锚点
*
* @access public
* @param string|null $fragment
* @return $this
*/
public function fragment($fragment)
{
$this->options['fragment'] = $fragment;
return $this;
}
/**
* 添加URL参数
*
* @access public
* @param array|string $key
* @param string|null $value
* @return $this
*/
public function appends($key, $value = null)
{
if (!is_array($key)) {
$queries = [$key => $value];
} else {
$queries = $key;
}
foreach ($queries as $k => $v) {
if ($k !== $this->options['var_page']) {
$this->options['query'][$k] = $v;
}
}
return $this;
}
/**
* 构造锚点字符串
*
* @access public
* @return string
*/
protected function buildFragment()
{
return $this->options['fragment'] ? '#' . $this->options['fragment'] : '';
}
/**
* 渲染分页html
* @access public
* @return mixed
*/
abstract public function render();
public function items()
{
return $this->items->all();
}
public function getCollection()
{
return $this->items;
}
public function isEmpty()
{
return $this->items->isEmpty();
}
/**
* 给每个元素执行个回调
*
* @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;
}
/**
* Retrieve an external iterator
* @access public
* @return Traversable An instance of an object implementing <b>Iterator</b> or
* <b>Traversable</b>
*/
public function getIterator()
{
return new ArrayIterator($this->items->all());
}
/**
* Whether a offset exists
* @access public
* @param mixed $offset
* @return bool
*/
public function offsetExists($offset)
{
return $this->items->offsetExists($offset);
}
/**
* Offset to retrieve
* @access public
* @param mixed $offset
* @return mixed
*/
public function offsetGet($offset)
{
return $this->items->offsetGet($offset);
}
/**
* Offset to set
* @access public
* @param mixed $offset
* @param mixed $value
*/
public function offsetSet($offset, $value)
{
$this->items->offsetSet($offset, $value);
}
/**
* Offset to unset
* @access public
* @param mixed $offset
* @return void
* @since 5.0.0
*/
public function offsetUnset($offset)
{
$this->items->offsetUnset($offset);
}
/**
* Count elements of an object
*/
public function count()
{
return $this->items->count();
}
public function __toString()
{
return (string) $this->render();
}
public function toArray()
{
try {
$total = $this->total();
} catch (\DomainException $e) {
$total = null;
}
return [
'total' => $total,
'per_page' => $this->listRows(),
'current_page' => $this->currentPage(),
'last_page' => $this->lastPage,
'data' => $this->items->toArray(),
];
}
/**
* Specify data which should be serialized to JSON
*/
public function jsonSerialize()
{
return $this->toArray();
}
public function __call($name, $arguments)
{
$collection = $this->getCollection();
$result = call_user_func_array([$collection, $name], $arguments);
if ($result === $collection) {
return $this;
}
return $result;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,429 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think;
use think\response\Redirect as RedirectResponse;
class Response
{
/**
* 原始数据
* @var mixed
*/
protected $data;
/**
* 应用对象实例
* @var App
*/
protected $app;
/**
* 当前contentType
* @var string
*/
protected $contentType = 'text/html';
/**
* 字符集
* @var string
*/
protected $charset = 'utf-8';
/**
* 状态码
* @var integer
*/
protected $code = 200;
/**
* 是否允许请求缓存
* @var bool
*/
protected $allowCache = true;
/**
* 输出参数
* @var array
*/
protected $options = [];
/**
* header参数
* @var array
*/
protected $header = [];
/**
* 输出内容
* @var string
*/
protected $content = null;
/**
* 架构函数
* @access public
* @param mixed $data 输出数据
* @param int $code
* @param array $header
* @param array $options 输出参数
*/
public function __construct($data = '', $code = 200, array $header = [], $options = [])
{
$this->data($data);
if (!empty($options)) {
$this->options = array_merge($this->options, $options);
}
$this->contentType($this->contentType, $this->charset);
$this->code = $code;
$this->app = Container::get('app');
$this->header = array_merge($this->header, $header);
}
/**
* 创建Response对象
* @access public
* @param mixed $data 输出数据
* @param string $type 输出类型
* @param int $code
* @param array $header
* @param array $options 输出参数
* @return Response
*/
public static function create($data = '', $type = '', $code = 200, array $header = [], $options = [])
{
$class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst(strtolower($type));
if (class_exists($class)) {
return new $class($data, $code, $header, $options);
}
return new static($data, $code, $header, $options);
}
/**
* 发送数据到客户端
* @access public
* @return void
* @throws \InvalidArgumentException
*/
public function send()
{
// 监听response_send
$this->app['hook']->listen('response_send', $this);
// 处理输出数据
$data = $this->getContent();
// Trace调试注入
if ('cli' != PHP_SAPI && $this->app['env']->get('app_trace', $this->app->config('app.app_trace'))) {
$this->app['debug']->inject($this, $data);
}
if (200 == $this->code && $this->allowCache) {
$cache = $this->app['request']->getCache();
if ($cache) {
$this->header['Cache-Control'] = 'max-age=' . $cache[1] . ',must-revalidate';
$this->header['Last-Modified'] = gmdate('D, d M Y H:i:s') . ' GMT';
$this->header['Expires'] = gmdate('D, d M Y H:i:s', $_SERVER['REQUEST_TIME'] + $cache[1]) . ' GMT';
$this->app['cache']->tag($cache[2])->set($cache[0], [$data, $this->header], $cache[1]);
}
}
if (!headers_sent() && !empty($this->header)) {
// 发送状态码
http_response_code($this->code);
// 发送头部信息
foreach ($this->header as $name => $val) {
header($name . (!is_null($val) ? ':' . $val : ''));
}
}
$this->sendData($data);
if (function_exists('fastcgi_finish_request')) {
// 提高页面响应
fastcgi_finish_request();
}
// 监听response_end
$this->app['hook']->listen('response_end', $this);
// 清空当次请求有效的数据
if (!($this instanceof RedirectResponse)) {
$this->app['session']->flush();
}
}
/**
* 处理数据
* @access protected
* @param mixed $data 要处理的数据
* @return mixed
*/
protected function output($data)
{
return $data;
}
/**
* 输出数据
* @access protected
* @param string $data 要处理的数据
* @return void
*/
protected function sendData($data)
{
echo $data;
}
/**
* 输出的参数
* @access public
* @param mixed $options 输出参数
* @return $this
*/
public function options($options = [])
{
$this->options = array_merge($this->options, $options);
return $this;
}
/**
* 输出数据设置
* @access public
* @param mixed $data 输出数据
* @return $this
*/
public function data($data)
{
$this->data = $data;
return $this;
}
/**
* 是否允许请求缓存
* @access public
* @param bool $cache 允许请求缓存
* @return $this
*/
public function allowCache($cache)
{
$this->allowCache = $cache;
return $this;
}
/**
* 设置响应头
* @access public
* @param string|array $name 参数名
* @param string $value 参数值
* @return $this
*/
public function header($name, $value = null)
{
if (is_array($name)) {
$this->header = array_merge($this->header, $name);
} else {
$this->header[$name] = $value;
}
return $this;
}
/**
* 设置页面输出内容
* @access public
* @param mixed $content
* @return $this
*/
public function content($content)
{
if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([
$content,
'__toString',
])
) {
throw new \InvalidArgumentException(sprintf('variable type error %s', gettype($content)));
}
$this->content = (string) $content;
return $this;
}
/**
* 发送HTTP状态
* @access public
* @param integer $code 状态码
* @return $this
*/
public function code($code)
{
$this->code = $code;
return $this;
}
/**
* LastModified
* @access public
* @param string $time
* @return $this
*/
public function lastModified($time)
{
$this->header['Last-Modified'] = $time;
return $this;
}
/**
* Expires
* @access public
* @param string $time
* @return $this
*/
public function expires($time)
{
$this->header['Expires'] = $time;
return $this;
}
/**
* ETag
* @access public
* @param string $eTag
* @return $this
*/
public function eTag($eTag)
{
$this->header['ETag'] = $eTag;
return $this;
}
/**
* 页面缓存控制
* @access public
* @param string $cache 缓存设置
* @return $this
*/
public function cacheControl($cache)
{
$this->header['Cache-control'] = $cache;
return $this;
}
/**
* 设置页面不做任何缓存
* @access public
* @return $this
*/
public function noCache()
{
$this->header['Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0';
$this->header['Pragma'] = 'no-cache';
return $this;
}
/**
* 页面输出类型
* @access public
* @param string $contentType 输出类型
* @param string $charset 输出编码
* @return $this
*/
public function contentType($contentType, $charset = 'utf-8')
{
$this->header['Content-Type'] = $contentType . '; charset=' . $charset;
return $this;
}
/**
* 获取头部信息
* @access public
* @param string $name 头部名称
* @return mixed
*/
public function getHeader($name = '')
{
if (!empty($name)) {
return isset($this->header[$name]) ? $this->header[$name] : null;
}
return $this->header;
}
/**
* 获取原始数据
* @access public
* @return mixed
*/
public function getData()
{
return $this->data;
}
/**
* 获取输出数据
* @access public
* @return mixed
*/
public function getContent()
{
if (null == $this->content) {
$content = $this->output($this->data);
if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([
$content,
'__toString',
])
) {
throw new \InvalidArgumentException(sprintf('variable type error %s', gettype($content)));
}
$this->content = (string) $content;
}
return $this->content;
}
/**
* 获取状态码
* @access public
* @return integer
*/
public function getCode()
{
return $this->code;
}
public function __debugInfo()
{
$data = get_object_vars($this);
unset($data['app']);
return $data;
}
}

View File

@@ -0,0 +1,992 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think;
use think\exception\RouteNotFoundException;
use think\route\AliasRule;
use think\route\Dispatch;
use think\route\dispatch\Url as UrlDispatch;
use think\route\Domain;
use think\route\Resource;
use think\route\Rule;
use think\route\RuleGroup;
use think\route\RuleItem;
class Route
{
/**
* REST定义
* @var array
*/
protected $rest = [
'index' => ['get', '', 'index'],
'create' => ['get', '/create', 'create'],
'edit' => ['get', '/<id>/edit', 'edit'],
'read' => ['get', '/<id>', 'read'],
'save' => ['post', '', 'save'],
'update' => ['put', '/<id>', 'update'],
'delete' => ['delete', '/<id>', 'delete'],
];
/**
* 请求方法前缀定义
* @var array
*/
protected $methodPrefix = [
'get' => 'get',
'post' => 'post',
'put' => 'put',
'delete' => 'delete',
'patch' => 'patch',
];
/**
* 应用对象
* @var App
*/
protected $app;
/**
* 请求对象
* @var Request
*/
protected $request;
/**
* 当前HOST
* @var string
*/
protected $host;
/**
* 当前域名
* @var string
*/
protected $domain;
/**
* 当前分组对象
* @var RuleGroup
*/
protected $group;
/**
* 配置参数
* @var array
*/
protected $config = [];
/**
* 路由绑定
* @var array
*/
protected $bind = [];
/**
* 域名对象
* @var array
*/
protected $domains = [];
/**
* 跨域路由规则
* @var RuleGroup
*/
protected $cross;
/**
* 路由别名
* @var array
*/
protected $alias = [];
/**
* 路由是否延迟解析
* @var bool
*/
protected $lazy = true;
/**
* 路由是否测试模式
* @var bool
*/
protected $isTest;
/**
* (分组)路由规则是否合并解析
* @var bool
*/
protected $mergeRuleRegex = true;
/**
* 路由解析自动搜索多级控制器
* @var bool
*/
protected $autoSearchController = true;
public function __construct(App $app, array $config = [])
{
$this->app = $app;
$this->request = $app['request'];
$this->config = $config;
$this->host = $this->request->host(true) ?: $config['app_host'];
$this->setDefaultDomain();
}
public function config($name = null)
{
if (is_null($name)) {
return $this->config;
}
return isset($this->config[$name]) ? $this->config[$name] : null;
}
/**
* 配置
* @access public
* @param array $config
* @return void
*/
public function setConfig(array $config = [])
{
$this->config = array_merge($this->config, array_change_key_case($config));
}
public static function __make(App $app, Config $config)
{
$config = $config->pull('app');
$route = new static($app, $config);
$route->lazy($config['url_lazy_route'])
->autoSearchController($config['controller_auto_search'])
->mergeRuleRegex($config['route_rule_merge']);
return $route;
}
/**
* 设置路由的请求对象实例
* @access public
* @param Request $request 请求对象实例
* @return void
*/
public function setRequest($request)
{
$this->request = $request;
}
/**
* 设置路由域名及分组(包括资源路由)是否延迟解析
* @access public
* @param bool $lazy 路由是否延迟解析
* @return $this
*/
public function lazy($lazy = true)
{
$this->lazy = $lazy;
return $this;
}
/**
* 设置路由为测试模式
* @access public
* @param bool $test 路由是否测试模式
* @return void
*/
public function setTestMode($test)
{
$this->isTest = $test;
}
/**
* 检查路由是否为测试模式
* @access public
* @return bool
*/
public function isTest()
{
return $this->isTest;
}
/**
* 设置路由域名及分组(包括资源路由)是否合并解析
* @access public
* @param bool $merge 路由是否合并解析
* @return $this
*/
public function mergeRuleRegex($merge = true)
{
$this->mergeRuleRegex = $merge;
$this->group->mergeRuleRegex($merge);
return $this;
}
/**
* 设置路由自动解析是否搜索多级控制器
* @access public
* @param bool $auto 是否自动搜索多级控制器
* @return $this
*/
public function autoSearchController($auto = true)
{
$this->autoSearchController = $auto;
return $this;
}
/**
* 初始化默认域名
* @access protected
* @return void
*/
protected function setDefaultDomain()
{
// 默认域名
$this->domain = $this->host;
// 注册默认域名
$domain = new Domain($this, $this->host);
$this->domains[$this->host] = $domain;
// 默认分组
$this->group = $domain;
}
/**
* 设置当前域名
* @access public
* @param RuleGroup $group 域名
* @return void
*/
public function setGroup(RuleGroup $group)
{
$this->group = $group;
}
/**
* 获取当前分组
* @access public
* @return RuleGroup
*/
public function getGroup()
{
return $this->group;
}
/**
* 注册变量规则
* @access public
* @param string|array $name 变量名
* @param string $rule 变量规则
* @return $this
*/
public function pattern($name, $rule = '')
{
$this->group->pattern($name, $rule);
return $this;
}
/**
* 注册路由参数
* @access public
* @param string|array $name 参数名
* @param mixed $value 值
* @return $this
*/
public function option($name, $value = '')
{
$this->group->option($name, $value);
return $this;
}
/**
* 注册域名路由
* @access public
* @param string|array $name 子域名
* @param mixed $rule 路由规则
* @param array $option 路由参数
* @param array $pattern 变量规则
* @return Domain
*/
public function domain($name, $rule = '', $option = [], $pattern = [])
{
// 支持多个域名使用相同路由规则
$domainName = is_array($name) ? array_shift($name) : $name;
if ('*' != $domainName && false === strpos($domainName, '.')) {
$domainName .= '.' . $this->request->rootDomain();
}
if (!isset($this->domains[$domainName])) {
$domain = (new Domain($this, $domainName, $rule, $option, $pattern))
->lazy($this->lazy)
->mergeRuleRegex($this->mergeRuleRegex);
$this->domains[$domainName] = $domain;
} else {
$domain = $this->domains[$domainName];
$domain->parseGroupRule($rule);
}
if (is_array($name) && !empty($name)) {
$root = $this->request->rootDomain();
foreach ($name as $item) {
if (false === strpos($item, '.')) {
$item .= '.' . $root;
}
$this->domains[$item] = $domainName;
}
}
// 返回域名对象
return $domain;
}
/**
* 获取域名
* @access public
* @return array
*/
public function getDomains()
{
return $this->domains;
}
/**
* 设置路由绑定
* @access public
* @param string $bind 绑定信息
* @param string $domain 域名
* @return $this
*/
public function bind($bind, $domain = null)
{
$domain = is_null($domain) ? $this->domain : $domain;
$this->bind[$domain] = $bind;
return $this;
}
/**
* 读取路由绑定
* @access public
* @param string $domain 域名
* @return string|null
*/
public function getBind($domain = null)
{
if (is_null($domain)) {
$domain = $this->domain;
} elseif (true === $domain) {
return $this->bind;
} elseif (false === strpos($domain, '.')) {
$domain .= '.' . $this->request->rootDomain();
}
$subDomain = $this->request->subDomain();
if (strpos($subDomain, '.')) {
$name = '*' . strstr($subDomain, '.');
}
if (isset($this->bind[$domain])) {
$result = $this->bind[$domain];
} elseif (isset($name) && isset($this->bind[$name])) {
$result = $this->bind[$name];
} elseif (!empty($subDomain) && isset($this->bind['*'])) {
$result = $this->bind['*'];
} else {
$result = null;
}
return $result;
}
/**
* 读取路由标识
* @access public
* @param string $name 路由标识
* @param string $domain 域名
* @return mixed
*/
public function getName($name = null, $domain = null, $method = '*')
{
return $this->app['rule_name']->get($name, $domain, $method);
}
/**
* 读取路由
* @access public
* @param string $rule 路由规则
* @param string $domain 域名
* @return array
*/
public function getRule($rule, $domain = null)
{
if (is_null($domain)) {
$domain = $this->domain;
}
return $this->app['rule_name']->getRule($rule, $domain);
}
/**
* 读取路由
* @access public
* @param string $domain 域名
* @return array
*/
public function getRuleList($domain = null)
{
return $this->app['rule_name']->getRuleList($domain);
}
/**
* 批量导入路由标识
* @access public
* @param array $name 路由标识
* @return $this
*/
public function setName($name)
{
$this->app['rule_name']->import($name);
return $this;
}
/**
* 导入配置文件的路由规则
* @access public
* @param array $rules 路由规则
* @param string $type 请求类型
* @return void
*/
public function import(array $rules, $type = '*')
{
// 检查域名部署
if (isset($rules['__domain__'])) {
foreach ($rules['__domain__'] as $key => $rule) {
$this->domain($key, $rule);
}
unset($rules['__domain__']);
}
// 检查变量规则
if (isset($rules['__pattern__'])) {
$this->pattern($rules['__pattern__']);
unset($rules['__pattern__']);
}
// 检查路由别名
if (isset($rules['__alias__'])) {
foreach ($rules['__alias__'] as $key => $val) {
$this->alias($key, $val);
}
unset($rules['__alias__']);
}
// 检查资源路由
if (isset($rules['__rest__'])) {
foreach ($rules['__rest__'] as $key => $rule) {
$this->resource($key, $rule);
}
unset($rules['__rest__']);
}
// 检查路由规则(包含分组)
foreach ($rules as $key => $val) {
if (is_numeric($key)) {
$key = array_shift($val);
}
if (empty($val)) {
continue;
}
if (is_string($key) && 0 === strpos($key, '[')) {
$key = substr($key, 1, -1);
$this->group($key, $val);
} elseif (is_array($val)) {
$this->rule($key, $val[0], $type, $val[1], isset($val[2]) ? $val[2] : []);
} else {
$this->rule($key, $val, $type);
}
}
}
/**
* 注册路由规则
* @access public
* @param string $rule 路由规则
* @param mixed $route 路由地址
* @param string $method 请求类型
* @param array $option 路由参数
* @param array $pattern 变量规则
* @return RuleItem
*/
public function rule($rule, $route, $method = '*', array $option = [], array $pattern = [])
{
return $this->group->addRule($rule, $route, $method, $option, $pattern);
}
/**
* 设置跨域有效路由规则
* @access public
* @param Rule $rule 路由规则
* @param string $method 请求类型
* @return $this
*/
public function setCrossDomainRule($rule, $method = '*')
{
if (!isset($this->cross)) {
$this->cross = (new RuleGroup($this))->mergeRuleRegex($this->mergeRuleRegex);
}
$this->cross->addRuleItem($rule, $method);
return $this;
}
/**
* 批量注册路由规则
* @access public
* @param array $rules 路由规则
* @param string $method 请求类型
* @param array $option 路由参数
* @param array $pattern 变量规则
* @return void
*/
public function rules($rules, $method = '*', array $option = [], array $pattern = [])
{
$this->group->addRules($rules, $method, $option, $pattern);
}
/**
* 注册路由分组
* @access public
* @param string|array $name 分组名称或者参数
* @param array|\Closure $route 分组路由
* @param array $option 路由参数
* @param array $pattern 变量规则
* @return RuleGroup
*/
public function group($name, $route, array $option = [], array $pattern = [])
{
if (is_array($name)) {
$option = $name;
$name = isset($option['name']) ? $option['name'] : '';
}
return (new RuleGroup($this, $this->group, $name, $route, $option, $pattern))
->lazy($this->lazy)
->mergeRuleRegex($this->mergeRuleRegex);
}
/**
* 注册路由
* @access public
* @param string $rule 路由规则
* @param mixed $route 路由地址
* @param array $option 路由参数
* @param array $pattern 变量规则
* @return RuleItem
*/
public function any($rule, $route = '', array $option = [], array $pattern = [])
{
return $this->rule($rule, $route, '*', $option, $pattern);
}
/**
* 注册GET路由
* @access public
* @param string $rule 路由规则
* @param mixed $route 路由地址
* @param array $option 路由参数
* @param array $pattern 变量规则
* @return RuleItem
*/
public function get($rule, $route = '', array $option = [], array $pattern = [])
{
return $this->rule($rule, $route, 'GET', $option, $pattern);
}
/**
* 注册POST路由
* @access public
* @param string $rule 路由规则
* @param mixed $route 路由地址
* @param array $option 路由参数
* @param array $pattern 变量规则
* @return RuleItem
*/
public function post($rule, $route = '', array $option = [], array $pattern = [])
{
return $this->rule($rule, $route, 'POST', $option, $pattern);
}
/**
* 注册PUT路由
* @access public
* @param string $rule 路由规则
* @param mixed $route 路由地址
* @param array $option 路由参数
* @param array $pattern 变量规则
* @return RuleItem
*/
public function put($rule, $route = '', array $option = [], array $pattern = [])
{
return $this->rule($rule, $route, 'PUT', $option, $pattern);
}
/**
* 注册DELETE路由
* @access public
* @param string $rule 路由规则
* @param mixed $route 路由地址
* @param array $option 路由参数
* @param array $pattern 变量规则
* @return RuleItem
*/
public function delete($rule, $route = '', array $option = [], array $pattern = [])
{
return $this->rule($rule, $route, 'DELETE', $option, $pattern);
}
/**
* 注册PATCH路由
* @access public
* @param string $rule 路由规则
* @param mixed $route 路由地址
* @param array $option 路由参数
* @param array $pattern 变量规则
* @return RuleItem
*/
public function patch($rule, $route = '', array $option = [], array $pattern = [])
{
return $this->rule($rule, $route, 'PATCH', $option, $pattern);
}
/**
* 注册资源路由
* @access public
* @param string $rule 路由规则
* @param string $route 路由地址
* @param array $option 路由参数
* @param array $pattern 变量规则
* @return Resource
*/
public function resource($rule, $route = '', array $option = [], array $pattern = [])
{
return (new Resource($this, $this->group, $rule, $route, $option, $pattern, $this->rest))
->lazy($this->lazy);
}
/**
* 注册控制器路由 操作方法对应不同的请求前缀
* @access public
* @param string $rule 路由规则
* @param string $route 路由地址
* @param array $option 路由参数
* @param array $pattern 变量规则
* @return RuleGroup
*/
public function controller($rule, $route = '', array $option = [], array $pattern = [])
{
$group = new RuleGroup($this, $this->group, $rule, null, $option, $pattern);
foreach ($this->methodPrefix as $type => $val) {
$group->addRule('<action>', $val . '<action>', $type);
}
return $group->prefix($route . '/');
}
/**
* 注册视图路由
* @access public
* @param string|array $rule 路由规则
* @param string $template 路由模板地址
* @param array $vars 模板变量
* @param array $option 路由参数
* @param array $pattern 变量规则
* @return RuleItem
*/
public function view($rule, $template = '', array $vars = [], array $option = [], array $pattern = [])
{
return $this->rule($rule, $template, 'GET', $option, $pattern)->view($vars);
}
/**
* 注册重定向路由
* @access public
* @param string|array $rule 路由规则
* @param string $route 路由地址
* @param array $status 状态码
* @param array $option 路由参数
* @param array $pattern 变量规则
* @return RuleItem
*/
public function redirect($rule, $route = '', $status = 301, array $option = [], array $pattern = [])
{
return $this->rule($rule, $route, '*', $option, $pattern)->redirect()->status($status);
}
/**
* 注册别名路由
* @access public
* @param string $rule 路由别名
* @param string $route 路由地址
* @param array $option 路由参数
* @return AliasRule
*/
public function alias($rule, $route, array $option = [])
{
$aliasRule = new AliasRule($this, $this->group, $rule, $route, $option);
$this->alias[$rule] = $aliasRule;
return $aliasRule;
}
/**
* 获取别名路由定义
* @access public
* @param string $name 路由别名
* @return string|array|null
*/
public function getAlias($name = null)
{
if (is_null($name)) {
return $this->alias;
}
return isset($this->alias[$name]) ? $this->alias[$name] : null;
}
/**
* 设置不同请求类型下面的方法前缀
* @access public
* @param string|array $method 请求类型
* @param string $prefix 类型前缀
* @return $this
*/
public function setMethodPrefix($method, $prefix = '')
{
if (is_array($method)) {
$this->methodPrefix = array_merge($this->methodPrefix, array_change_key_case($method));
} else {
$this->methodPrefix[strtolower($method)] = $prefix;
}
return $this;
}
/**
* 获取请求类型的方法前缀
* @access public
* @param string $method 请求类型
* @param string $prefix 类型前缀
* @return string|null
*/
public function getMethodPrefix($method)
{
$method = strtolower($method);
return isset($this->methodPrefix[$method]) ? $this->methodPrefix[$method] : null;
}
/**
* rest方法定义和修改
* @access public
* @param string $name 方法名称
* @param array|bool $resource 资源
* @return $this
*/
public function rest($name, $resource = [])
{
if (is_array($name)) {
$this->rest = $resource ? $name : array_merge($this->rest, $name);
} else {
$this->rest[$name] = $resource;
}
return $this;
}
/**
* 获取rest方法定义的参数
* @access public
* @param string $name 方法名称
* @return array|null
*/
public function getRest($name = null)
{
if (is_null($name)) {
return $this->rest;
}
return isset($this->rest[$name]) ? $this->rest[$name] : null;
}
/**
* 注册未匹配路由规则后的处理
* @access public
* @param string $route 路由地址
* @param string $method 请求类型
* @param array $option 路由参数
* @return RuleItem
*/
public function miss($route, $method = '*', array $option = [])
{
return $this->group->addMissRule($route, $method, $option);
}
/**
* 注册一个自动解析的URL路由
* @access public
* @param string $route 路由地址
* @return RuleItem
*/
public function auto($route)
{
return $this->group->addAutoRule($route);
}
/**
* 检测URL路由
* @access public
* @param string $url URL地址
* @param bool $must 是否强制路由
* @return Dispatch
* @throws RouteNotFoundException
*/
public function check($url, $must = false)
{
// 自动检测域名路由
$domain = $this->checkDomain();
$url = str_replace($this->config['pathinfo_depr'], '|', $url);
$completeMatch = $this->config['route_complete_match'];
$result = $domain->check($this->request, $url, $completeMatch);
if (false === $result && !empty($this->cross)) {
// 检测跨域路由
$result = $this->cross->check($this->request, $url, $completeMatch);
}
if (false !== $result) {
// 路由匹配
return $result;
} elseif ($must) {
// 强制路由不匹配则抛出异常
throw new RouteNotFoundException();
}
// 默认路由解析
return new UrlDispatch($this->request, $this->group, $url, [
'auto_search' => $this->autoSearchController,
]);
}
/**
* 检测域名的路由规则
* @access protected
* @return Domain
*/
protected function checkDomain()
{
// 获取当前子域名
$subDomain = $this->request->subDomain();
$item = false;
if ($subDomain && count($this->domains) > 1) {
$domain = explode('.', $subDomain);
$domain2 = array_pop($domain);
if ($domain) {
// 存在三级域名
$domain3 = array_pop($domain);
}
if ($subDomain && isset($this->domains[$subDomain])) {
// 子域名配置
$item = $this->domains[$subDomain];
} elseif (isset($this->domains['*.' . $domain2]) && !empty($domain3)) {
// 泛三级域名
$item = $this->domains['*.' . $domain2];
$panDomain = $domain3;
} elseif (isset($this->domains['*']) && !empty($domain2)) {
// 泛二级域名
if ('www' != $domain2) {
$item = $this->domains['*'];
$panDomain = $domain2;
}
}
if (isset($panDomain)) {
// 保存当前泛域名
$this->request->setPanDomain($panDomain);
}
}
if (false === $item) {
// 检测当前完整域名
$item = $this->domains[$this->host];
}
if (is_string($item)) {
$item = $this->domains[$item];
}
return $item;
}
/**
* 清空路由规则
* @access public
* @return void
*/
public function clear()
{
$this->app['rule_name']->clear();
$this->group->clear();
}
/**
* 设置全局的路由分组参数
* @access public
* @param string $method 方法名
* @param array $args 调用参数
* @return RuleGroup
*/
public function __call($method, $args)
{
return call_user_func_array([$this->group, $method], $args);
}
public function __debugInfo()
{
$data = get_object_vars($this);
unset($data['app'], $data['request']);
return $data;
}
}

View File

@@ -0,0 +1,579 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think;
use think\exception\ClassNotFoundException;
class Session
{
/**
* 配置参数
* @var array
*/
protected $config = [];
/**
* 前缀
* @var string
*/
protected $prefix = '';
/**
* 是否初始化
* @var bool
*/
protected $init = null;
/**
* 锁驱动
* @var object
*/
protected $lockDriver = null;
/**
* 锁key
* @var string
*/
protected $sessKey = 'PHPSESSID';
/**
* 锁超时时间
* @var integer
*/
protected $lockTimeout = 3;
/**
* 是否启用锁机制
* @var bool
*/
protected $lock = false;
public function __construct(array $config = [])
{
$this->config = $config;
}
/**
* 设置或者获取session作用域前缀
* @access public
* @param string $prefix
* @return string|void
*/
public function prefix($prefix = '')
{
empty($this->init) && $this->boot();
if (empty($prefix) && null !== $prefix) {
return $this->prefix;
} else {
$this->prefix = $prefix;
}
}
public static function __make(Config $config)
{
return new static($config->pull('session'));
}
/**
* 配置
* @access public
* @param array $config
* @return void
*/
public function setConfig(array $config = [])
{
$this->config = array_merge($this->config, array_change_key_case($config));
if (isset($config['prefix'])) {
$this->prefix = $config['prefix'];
}
if (isset($config['use_lock'])) {
$this->lock = $config['use_lock'];
}
}
/**
* 设置已经初始化
* @access public
* @return void
*/
public function inited()
{
$this->init = true;
}
/**
* session初始化
* @access public
* @param array $config
* @return void
* @throws \think\Exception
*/
public function init(array $config = [])
{
$config = $config ?: $this->config;
$isDoStart = false;
if (isset($config['use_trans_sid'])) {
ini_set('session.use_trans_sid', $config['use_trans_sid'] ? 1 : 0);
}
// 启动session
if (!empty($config['auto_start']) && PHP_SESSION_ACTIVE != session_status()) {
ini_set('session.auto_start', 0);
$isDoStart = true;
}
if (isset($config['prefix'])) {
$this->prefix = $config['prefix'];
}
if (isset($config['use_lock'])) {
$this->lock = $config['use_lock'];
}
if (isset($config['var_session_id']) && isset($_REQUEST[$config['var_session_id']])) {
session_id($_REQUEST[$config['var_session_id']]);
} elseif (isset($config['id']) && !empty($config['id'])) {
session_id($config['id']);
}
if (isset($config['name'])) {
session_name($config['name']);
}
if (isset($config['path'])) {
session_save_path($config['path']);
}
if (isset($config['domain'])) {
ini_set('session.cookie_domain', $config['domain']);
}
if (isset($config['expire'])) {
ini_set('session.gc_maxlifetime', $config['expire']);
ini_set('session.cookie_lifetime', $config['expire']);
}
if (isset($config['secure'])) {
ini_set('session.cookie_secure', $config['secure']);
}
if (isset($config['httponly'])) {
ini_set('session.cookie_httponly', $config['httponly']);
}
if (isset($config['use_cookies'])) {
ini_set('session.use_cookies', $config['use_cookies'] ? 1 : 0);
}
if (isset($config['cache_limiter'])) {
session_cache_limiter($config['cache_limiter']);
}
if (isset($config['cache_expire'])) {
session_cache_expire($config['cache_expire']);
}
if (!empty($config['type'])) {
// 读取session驱动
$class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\session\\driver\\' . ucwords($config['type']);
// 检查驱动类
if (!class_exists($class) || !session_set_save_handler(new $class($config))) {
throw new ClassNotFoundException('error session handler:' . $class, $class);
}
}
if ($isDoStart) {
$this->start();
} else {
$this->init = false;
}
return $this;
}
/**
* session自动启动或者初始化
* @access public
* @return void
*/
public function boot()
{
if (is_null($this->init)) {
$this->init();
}
if (false === $this->init) {
if (PHP_SESSION_ACTIVE != session_status()) {
$this->start();
}
$this->init = true;
}
}
/**
* session设置
* @access public
* @param string $name session名称
* @param mixed $value session值
* @param string|null $prefix 作用域(前缀)
* @return void
*/
public function set($name, $value, $prefix = null)
{
$this->lock();
empty($this->init) && $this->boot();
$prefix = !is_null($prefix) ? $prefix : $this->prefix;
if (strpos($name, '.')) {
// 二维数组赋值
list($name1, $name2) = explode('.', $name);
if ($prefix) {
$_SESSION[$prefix][$name1][$name2] = $value;
} else {
$_SESSION[$name1][$name2] = $value;
}
} elseif ($prefix) {
$_SESSION[$prefix][$name] = $value;
} else {
$_SESSION[$name] = $value;
}
$this->unlock();
}
/**
* session获取
* @access public
* @param string $name session名称
* @param string|null $prefix 作用域(前缀)
* @return mixed
*/
public function get($name = '', $prefix = null)
{
$this->lock();
empty($this->init) && $this->boot();
$prefix = !is_null($prefix) ? $prefix : $this->prefix;
$value = $prefix ? (!empty($_SESSION[$prefix]) ? $_SESSION[$prefix] : []) : $_SESSION;
if ('' != $name) {
$name = explode('.', $name);
foreach ($name as $val) {
if (isset($value[$val])) {
$value = $value[$val];
} else {
$value = null;
break;
}
}
}
$this->unlock();
return $value;
}
/**
* session 读写锁驱动实例化
*/
protected function initDriver()
{
$config = $this->config;
if (!empty($config['type']) && isset($config['use_lock']) && $config['use_lock']) {
// 读取session驱动
$class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\session\\driver\\' . ucwords($config['type']);
// 检查驱动类及类中是否存在 lock 和 unlock 函数
if (class_exists($class) && method_exists($class, 'lock') && method_exists($class, 'unlock')) {
$this->lockDriver = new $class($config);
}
}
// 通过cookie获得session_id
if (isset($config['name']) && $config['name']) {
$this->sessKey = $config['name'];
}
if (isset($config['lock_timeout']) && $config['lock_timeout'] > 0) {
$this->lockTimeout = $config['lock_timeout'];
}
}
/**
* session 读写加锁
* @access protected
* @return void
*/
protected function lock()
{
if (empty($this->lock)) {
return;
}
$this->initDriver();
if (null !== $this->lockDriver && method_exists($this->lockDriver, 'lock')) {
$t = time();
// 使用 session_id 作为互斥条件,即只对同一 session_id 的会话互斥。第一次请求没有 session_id
$sessID = isset($_COOKIE[$this->sessKey]) ? $_COOKIE[$this->sessKey] : '';
do {
if (time() - $t > $this->lockTimeout) {
$this->unlock();
}
} while (!$this->lockDriver->lock($sessID, $this->lockTimeout));
}
}
/**
* session 读写解锁
* @access protected
* @return void
*/
protected function unlock()
{
if (empty($this->lock)) {
return;
}
$this->pause();
if ($this->lockDriver && method_exists($this->lockDriver, 'unlock')) {
$sessID = isset($_COOKIE[$this->sessKey]) ? $_COOKIE[$this->sessKey] : '';
$this->lockDriver->unlock($sessID);
}
}
/**
* session获取并删除
* @access public
* @param string $name session名称
* @param string|null $prefix 作用域(前缀)
* @return mixed
*/
public function pull($name, $prefix = null)
{
$result = $this->get($name, $prefix);
if ($result) {
$this->delete($name, $prefix);
return $result;
} else {
return;
}
}
/**
* session设置 下一次请求有效
* @access public
* @param string $name session名称
* @param mixed $value session值
* @param string|null $prefix 作用域(前缀)
* @return void
*/
public function flash($name, $value)
{
$this->set($name, $value);
if (!$this->has('__flash__.__time__')) {
$this->set('__flash__.__time__', $_SERVER['REQUEST_TIME_FLOAT']);
}
$this->push('__flash__', $name);
}
/**
* 清空当前请求的session数据
* @access public
* @return void
*/
public function flush()
{
if (!$this->init) {
return;
}
$item = $this->get('__flash__');
if (!empty($item)) {
$time = $item['__time__'];
if ($_SERVER['REQUEST_TIME_FLOAT'] > $time) {
unset($item['__time__']);
$this->delete($item);
$this->set('__flash__', []);
}
}
}
/**
* 删除session数据
* @access public
* @param string|array $name session名称
* @param string|null $prefix 作用域(前缀)
* @return void
*/
public function delete($name, $prefix = null)
{
empty($this->init) && $this->boot();
$prefix = !is_null($prefix) ? $prefix : $this->prefix;
if (is_array($name)) {
foreach ($name as $key) {
$this->delete($key, $prefix);
}
} elseif (strpos($name, '.')) {
list($name1, $name2) = explode('.', $name);
if ($prefix) {
unset($_SESSION[$prefix][$name1][$name2]);
} else {
unset($_SESSION[$name1][$name2]);
}
} else {
if ($prefix) {
unset($_SESSION[$prefix][$name]);
} else {
unset($_SESSION[$name]);
}
}
}
/**
* 清空session数据
* @access public
* @param string|null $prefix 作用域(前缀)
* @return void
*/
public function clear($prefix = null)
{
empty($this->init) && $this->boot();
$prefix = !is_null($prefix) ? $prefix : $this->prefix;
if ($prefix) {
unset($_SESSION[$prefix]);
} else {
$_SESSION = [];
}
}
/**
* 判断session数据
* @access public
* @param string $name session名称
* @param string|null $prefix
* @return bool
*/
public function has($name, $prefix = null)
{
empty($this->init) && $this->boot();
$prefix = !is_null($prefix) ? $prefix : $this->prefix;
$value = $prefix ? (!empty($_SESSION[$prefix]) ? $_SESSION[$prefix] : []) : $_SESSION;
$name = explode('.', $name);
foreach ($name as $val) {
if (!isset($value[$val])) {
return false;
} else {
$value = $value[$val];
}
}
return true;
}
/**
* 添加数据到一个session数组
* @access public
* @param string $key
* @param mixed $value
* @return void
*/
public function push($key, $value)
{
$array = $this->get($key);
if (is_null($array)) {
$array = [];
}
$array[] = $value;
$this->set($key, $array);
}
/**
* 启动session
* @access public
* @return void
*/
public function start()
{
session_start();
$this->init = true;
}
/**
* 销毁session
* @access public
* @return void
*/
public function destroy()
{
if (!empty($_SESSION)) {
$_SESSION = [];
}
session_unset();
session_destroy();
$this->init = null;
$this->lockDriver = null;
}
/**
* 重新生成session_id
* @access public
* @param bool $delete 是否删除关联会话文件
* @return void
*/
public function regenerate($delete = false)
{
session_regenerate_id($delete);
}
/**
* 暂停session
* @access public
* @return void
*/
public function pause()
{
// 暂停session
session_write_close();
$this->init = false;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,412 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think;
class Url
{
/**
* 配置参数
* @var array
*/
protected $config = [];
/**
* ROOT地址
* @var string
*/
protected $root;
/**
* 绑定检查
* @var bool
*/
protected $bindCheck;
/**
* 应用对象
* @var App
*/
protected $app;
public function __construct(App $app, array $config = [])
{
$this->app = $app;
$this->config = $config;
if (is_file($app->getRuntimePath() . 'route.php')) {
// 读取路由映射文件
$app['route']->setName(include $app->getRuntimePath() . 'route.php');
}
}
/**
* 初始化
* @access public
* @param array $config
* @return void
*/
public function init(array $config = [])
{
$this->config = array_merge($this->config, array_change_key_case($config));
}
public static function __make(App $app, Config $config)
{
return new static($app, $config->pull('app'));
}
/**
* URL生成 支持路由反射
* @access public
* @param string $url 路由地址
* @param string|array $vars 参数支持数组和字符串a=val&b=val2... ['a'=>'val1', 'b'=>'val2']
* @param string|bool $suffix 伪静态后缀默认为true表示获取配置值
* @param boolean|string $domain 是否显示域名 或者直接传入域名
* @return string
*/
public function build($url = '', $vars = '', $suffix = true, $domain = false)
{
// 解析URL
if (0 === strpos($url, '[') && $pos = strpos($url, ']')) {
// [name] 表示使用路由命名标识生成URL
$name = substr($url, 1, $pos - 1);
$url = 'name' . substr($url, $pos + 1);
}
if (false === strpos($url, '://') && 0 !== strpos($url, '/')) {
$info = parse_url($url);
$url = !empty($info['path']) ? $info['path'] : '';
if (isset($info['fragment'])) {
// 解析锚点
$anchor = $info['fragment'];
if (false !== strpos($anchor, '?')) {
// 解析参数
list($anchor, $info['query']) = explode('?', $anchor, 2);
}
if (false !== strpos($anchor, '@')) {
// 解析域名
list($anchor, $domain) = explode('@', $anchor, 2);
}
} elseif (strpos($url, '@') && false === strpos($url, '\\')) {
// 解析域名
list($url, $domain) = explode('@', $url, 2);
}
}
// 解析参数
if (is_string($vars)) {
// aaa=1&bbb=2 转换成数组
parse_str($vars, $vars);
}
if ($url) {
$checkName = isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : '');
$checkDomain = $domain && is_string($domain) ? $domain : null;
$rule = $this->app['route']->getName($checkName, $checkDomain);
if (is_null($rule) && isset($info['query'])) {
$rule = $this->app['route']->getName($url);
// 解析地址里面参数 合并到vars
parse_str($info['query'], $params);
$vars = array_merge($params, $vars);
unset($info['query']);
}
}
if (!empty($rule) && $match = $this->getRuleUrl($rule, $vars, $domain)) {
// 匹配路由命名标识
$url = $match[0];
if ($domain) {
$domain = $match[1];
}
if (!is_null($match[2])) {
$suffix = $match[2];
}
} elseif (!empty($rule) && isset($name)) {
throw new \InvalidArgumentException('route name not exists:' . $name);
} else {
// 检查别名路由
$alias = $this->app['route']->getAlias();
$matchAlias = false;
if ($alias) {
// 别名路由解析
foreach ($alias as $key => $item) {
$val = $item->getRoute();
if (0 === strpos($url, $val)) {
$url = $key . substr($url, strlen($val));
$matchAlias = true;
break;
}
}
}
if (!$matchAlias) {
// 路由标识不存在 直接解析
$url = $this->parseUrl($url);
}
// 检测URL绑定
if (!$this->bindCheck) {
$bind = $this->app['route']->getBind($domain && is_string($domain) ? $domain : null);
if ($bind && 0 === strpos($url, $bind)) {
$url = substr($url, strlen($bind) + 1);
} else {
$binds = $this->app['route']->getBind(true);
foreach ($binds as $key => $val) {
if (is_string($val) && 0 === strpos($url, $val) && substr_count($val, '/') > 1) {
$url = substr($url, strlen($val) + 1);
$domain = $key;
break;
}
}
}
}
if (isset($info['query'])) {
// 解析地址里面参数 合并到vars
parse_str($info['query'], $params);
$vars = array_merge($params, $vars);
}
}
// 还原URL分隔符
$depr = $this->config['pathinfo_depr'];
$url = str_replace('/', $depr, $url);
// URL后缀
if ('/' == substr($url, -1) || '' == $url) {
$suffix = '';
} else {
$suffix = $this->parseSuffix($suffix);
}
// 锚点
$anchor = !empty($anchor) ? '#' . $anchor : '';
// 参数组装
if (!empty($vars)) {
// 添加参数
if ($this->config['url_common_param']) {
$vars = http_build_query($vars);
$url .= $suffix . '?' . $vars . $anchor;
} else {
$paramType = $this->config['url_param_type'];
foreach ($vars as $var => $val) {
if ('' !== trim($val)) {
if ($paramType) {
$url .= $depr . urlencode($val);
} else {
$url .= $depr . $var . $depr . urlencode($val);
}
}
}
$url .= $suffix . $anchor;
}
} else {
$url .= $suffix . $anchor;
}
// 检测域名
$domain = $this->parseDomain($url, $domain);
// URL组装
$url = $domain . rtrim($this->root ?: $this->app['request']->root(), '/') . '/' . ltrim($url, '/');
$this->bindCheck = false;
return $url;
}
// 直接解析URL地址
protected function parseUrl($url)
{
$request = $this->app['request'];
if (0 === strpos($url, '/')) {
// 直接作为路由地址解析
$url = substr($url, 1);
} elseif (false !== strpos($url, '\\')) {
// 解析到类
$url = ltrim(str_replace('\\', '/', $url), '/');
} elseif (0 === strpos($url, '@')) {
// 解析到控制器
$url = substr($url, 1);
} else {
// 解析到 模块/控制器/操作
$module = $request->module();
$module = $module ? $module . '/' : '';
$controller = $request->controller();
if ('' == $url) {
$action = $request->action();
} else {
$path = explode('/', $url);
$action = array_pop($path);
$controller = empty($path) ? $controller : array_pop($path);
$module = empty($path) ? $module : array_pop($path) . '/';
}
if ($this->config['url_convert']) {
$action = strtolower($action);
$controller = Loader::parseName($controller);
}
$url = $module . $controller . '/' . $action;
}
return $url;
}
// 检测域名
protected function parseDomain(&$url, $domain)
{
if (!$domain) {
return '';
}
$rootDomain = $this->app['request']->rootDomain();
if (true === $domain) {
// 自动判断域名
$domain = $this->config['app_host'] ?: $this->app['request']->host();
$domains = $this->app['route']->getDomains();
if ($domains) {
$route_domain = array_keys($domains);
foreach ($route_domain as $domain_prefix) {
if (0 === strpos($domain_prefix, '*.') && strpos($domain, ltrim($domain_prefix, '*.')) !== false) {
foreach ($domains as $key => $rule) {
$rule = is_array($rule) ? $rule[0] : $rule;
if (is_string($rule) && false === strpos($key, '*') && 0 === strpos($url, $rule)) {
$url = ltrim($url, $rule);
$domain = $key;
// 生成对应子域名
if (!empty($rootDomain)) {
$domain .= $rootDomain;
}
break;
} elseif (false !== strpos($key, '*')) {
if (!empty($rootDomain)) {
$domain .= $rootDomain;
}
break;
}
}
}
}
}
} elseif (0 !== strpos($domain, $rootDomain) && false === strpos($domain, '.')) {
$domain .= '.' . $rootDomain;
}
if (false !== strpos($domain, '://')) {
$scheme = '';
} else {
$scheme = $this->app['request']->isSsl() || $this->config['is_https'] ? 'https://' : 'http://';
}
return $scheme . $domain;
}
// 解析URL后缀
protected function parseSuffix($suffix)
{
if ($suffix) {
$suffix = true === $suffix ? $this->config['url_html_suffix'] : $suffix;
if ($pos = strpos($suffix, '|')) {
$suffix = substr($suffix, 0, $pos);
}
}
return (empty($suffix) || 0 === strpos($suffix, '.')) ? $suffix : '.' . $suffix;
}
// 匹配路由地址
public function getRuleUrl($rule, &$vars = [], $allowDomain = '')
{
$port = $this->app['request']->port();
foreach ($rule as $item) {
list($url, $pattern, $domain, $suffix, $method) = $item;
if (is_string($allowDomain) && $domain != $allowDomain) {
continue;
}
if ($port && !in_array($port, [80, 443])) {
$domain .= ':' . $port;
}
if (empty($pattern)) {
return [rtrim($url, '?/-'), $domain, $suffix];
}
$type = $this->config['url_common_param'];
$keys = [];
foreach ($pattern as $key => $val) {
if (isset($vars[$key])) {
$url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key, '<' . $key . '>'], $type ? $vars[$key] : urlencode($vars[$key]), $url);
$keys[] = $key;
$url = str_replace(['/?', '-?'], ['/', '-'], $url);
$result = [rtrim($url, '?/-'), $domain, $suffix];
} elseif (2 == $val) {
$url = str_replace(['/[:' . $key . ']', '[:' . $key . ']', '<' . $key . '?>'], '', $url);
$url = str_replace(['/?', '-?'], ['/', '-'], $url);
$result = [rtrim($url, '?/-'), $domain, $suffix];
} else {
$result = null;
$keys = [];
break;
}
}
$vars = array_diff_key($vars, array_flip($keys));
if (isset($result)) {
return $result;
}
}
return false;
}
// 指定当前生成URL地址的root
public function root($root)
{
$this->root = $root;
$this->app['request']->setRoot($root);
}
public function __debugInfo()
{
$data = get_object_vars($this);
unset($data['app']);
return $data;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,253 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think;
class View
{
/**
* 模板引擎实例
* @var object
*/
public $engine;
/**
* 模板变量
* @var array
*/
protected $data = [];
/**
* 内容过滤
* @var mixed
*/
protected $filter;
/**
* 全局模板变量
* @var array
*/
protected static $var = [];
/**
* 初始化
* @access public
* @param mixed $engine 模板引擎参数
* @return $this
*/
public function init($engine = [])
{
// 初始化模板引擎
$this->engine($engine);
return $this;
}
public static function __make(Config $config)
{
return (new static())->init($config->pull('template'));
}
/**
* 模板变量静态赋值
* @access public
* @param mixed $name 变量名
* @param mixed $value 变量值
* @return $this
*/
public function share($name, $value = '')
{
if (is_array($name)) {
self::$var = array_merge(self::$var, $name);
} else {
self::$var[$name] = $value;
}
return $this;
}
/**
* 清理模板变量
* @access public
* @return void
*/
public function clear()
{
self::$var = [];
$this->data = [];
}
/**
* 模板变量赋值
* @access public
* @param mixed $name 变量名
* @param mixed $value 变量值
* @return $this
*/
public function assign($name, $value = '')
{
if (is_array($name)) {
$this->data = array_merge($this->data, $name);
} else {
$this->data[$name] = $value;
}
return $this;
}
/**
* 设置当前模板解析的引擎
* @access public
* @param array|string $options 引擎参数
* @return $this
*/
public function engine($options = [])
{
if (is_string($options)) {
$type = $options;
$options = [];
} else {
$type = !empty($options['type']) ? $options['type'] : 'Think';
}
if (isset($options['type'])) {
unset($options['type']);
}
$this->engine = Loader::factory($type, '\\think\\view\\driver\\', $options);
return $this;
}
/**
* 配置模板引擎
* @access public
* @param string|array $name 参数名
* @param mixed $value 参数值
* @return $this
*/
public function config($name, $value = null)
{
$this->engine->config($name, $value);
return $this;
}
/**
* 检查模板是否存在
* @access public
* @param string|array $name 参数名
* @return bool
*/
public function exists($name)
{
return $this->engine->exists($name);
}
/**
* 视图过滤
* @access public
* @param Callable $filter 过滤方法或闭包
* @return $this
*/
public function filter($filter)
{
if ($filter) {
$this->filter = $filter;
}
return $this;
}
/**
* 解析和获取模板内容 用于输出
* @access public
* @param string $template 模板文件名或者内容
* @param array $vars 模板输出变量
* @param array $config 模板参数
* @param bool $renderContent 是否渲染内容
* @return string
* @throws \Exception
*/
public function fetch($template = '', $vars = [], $config = [], $renderContent = false)
{
// 模板变量
$vars = array_merge(self::$var, $this->data, $vars);
// 页面缓存
ob_start();
ob_implicit_flush(0);
// 渲染输出
try {
$method = $renderContent ? 'display' : 'fetch';
$this->engine->$method($template, $vars, $config);
} catch (\Exception $e) {
ob_end_clean();
throw $e;
}
// 获取并清空缓存
$content = ob_get_clean();
if ($this->filter) {
$content = call_user_func_array($this->filter, [$content]);
}
return $content;
}
/**
* 渲染内容输出
* @access public
* @param string $content 内容
* @param array $vars 模板输出变量
* @param array $config 模板参数
* @return mixed
*/
public function display($content, $vars = [], $config = [])
{
return $this->fetch($content, $vars, $config, true);
}
/**
* 模板变量赋值
* @access public
* @param string $name 变量名
* @param mixed $value 变量值
*/
public function __set($name, $value)
{
$this->data[$name] = $value;
}
/**
* 取得模板显示变量的值
* @access protected
* @param string $name 模板变量
* @return mixed
*/
public function __get($name)
{
return $this->data[$name];
}
/**
* 检测模板变量是否设置
* @access public
* @param string $name 模板变量名
* @return bool
*/
public function __isset($name)
{
return isset($this->data[$name]);
}
}

View File

@@ -0,0 +1,366 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\cache;
use think\Container;
/**
* 缓存基础类
*/
abstract class Driver
{
/**
* 驱动句柄
* @var object
*/
protected $handler = null;
/**
* 缓存读取次数
* @var integer
*/
protected $readTimes = 0;
/**
* 缓存写入次数
* @var integer
*/
protected $writeTimes = 0;
/**
* 缓存参数
* @var array
*/
protected $options = [];
/**
* 缓存标签
* @var string
*/
protected $tag;
/**
* 序列化方法
* @var array
*/
protected static $serialize = ['serialize', 'unserialize', 'think_serialize:', 16];
/**
* 判断缓存是否存在
* @access public
* @param string $name 缓存变量名
* @return bool
*/
abstract public function has($name);
/**
* 读取缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $default 默认值
* @return mixed
*/
abstract public function get($name, $default = false);
/**
* 写入缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $value 存储数据
* @param int $expire 有效时间 0为永久
* @return boolean
*/
abstract public function set($name, $value, $expire = null);
/**
* 自增缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
abstract public function inc($name, $step = 1);
/**
* 自减缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
abstract public function dec($name, $step = 1);
/**
* 删除缓存
* @access public
* @param string $name 缓存变量名
* @return boolean
*/
abstract public function rm($name);
/**
* 清除缓存
* @access public
* @param string $tag 标签名
* @return boolean
*/
abstract public function clear($tag = null);
/**
* 获取有效期
* @access protected
* @param integer|\DateTime $expire 有效期
* @return integer
*/
protected function getExpireTime($expire)
{
if ($expire instanceof \DateTime) {
$expire = $expire->getTimestamp() - time();
}
return $expire;
}
/**
* 获取实际的缓存标识
* @access protected
* @param string $name 缓存名
* @return string
*/
protected function getCacheKey($name)
{
return $this->options['prefix'] . $name;
}
/**
* 读取缓存并删除
* @access public
* @param string $name 缓存变量名
* @return mixed
*/
public function pull($name)
{
$result = $this->get($name, false);
if ($result) {
$this->rm($name);
return $result;
} else {
return;
}
}
/**
* 如果不存在则写入缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $value 存储数据
* @param int $expire 有效时间 0为永久
* @return mixed
*/
public function remember($name, $value, $expire = null)
{
if (!$this->has($name)) {
$time = time();
while ($time + 5 > time() && $this->has($name . '_lock')) {
// 存在锁定则等待
usleep(200000);
}
try {
// 锁定
$this->set($name . '_lock', true);
if ($value instanceof \Closure) {
// 获取缓存数据
$value = Container::getInstance()->invokeFunction($value);
}
// 缓存数据
$this->set($name, $value, $expire);
// 解锁
$this->rm($name . '_lock');
} catch (\Exception $e) {
$this->rm($name . '_lock');
throw $e;
} catch (\throwable $e) {
$this->rm($name . '_lock');
throw $e;
}
} else {
$value = $this->get($name);
}
return $value;
}
/**
* 缓存标签
* @access public
* @param string $name 标签名
* @param string|array $keys 缓存标识
* @param bool $overlay 是否覆盖
* @return $this
*/
public function tag($name, $keys = null, $overlay = false)
{
if (is_null($name)) {
} elseif (is_null($keys)) {
$this->tag = $name;
} else {
$key = $this->getTagkey($name);
if (is_string($keys)) {
$keys = explode(',', $keys);
}
$keys = array_map([$this, 'getCacheKey'], $keys);
if ($overlay) {
$value = $keys;
} else {
$value = array_unique(array_merge($this->getTagItem($name), $keys));
}
$this->set($key, implode(',', $value), 0);
}
return $this;
}
/**
* 更新标签
* @access protected
* @param string $name 缓存标识
* @return void
*/
protected function setTagItem($name)
{
if ($this->tag) {
$key = $this->getTagkey($this->tag);
$this->tag = null;
if ($this->has($key)) {
$value = explode(',', $this->get($key));
$value[] = $name;
if (count($value) > 1000) {
array_shift($value);
}
$value = implode(',', array_unique($value));
} else {
$value = $name;
}
$this->set($key, $value, 0);
}
}
/**
* 获取标签包含的缓存标识
* @access protected
* @param string $tag 缓存标签
* @return array
*/
protected function getTagItem($tag)
{
$key = $this->getTagkey($tag);
$value = $this->get($key);
if ($value) {
return array_filter(explode(',', $value));
} else {
return [];
}
}
protected function getTagKey($tag)
{
return 'tag_' . md5($tag);
}
/**
* 序列化数据
* @access protected
* @param mixed $data
* @return string
*/
protected function serialize($data)
{
if (is_scalar($data) || !$this->options['serialize']) {
return $data;
}
$serialize = self::$serialize[0];
return self::$serialize[2] . $serialize($data);
}
/**
* 反序列化数据
* @access protected
* @param string $data
* @return mixed
*/
protected function unserialize($data)
{
if ($this->options['serialize'] && 0 === strpos($data, self::$serialize[2])) {
$unserialize = self::$serialize[1];
return $unserialize(substr($data, self::$serialize[3]));
} else {
return $data;
}
}
/**
* 注册序列化机制
* @access public
* @param callable $serialize 序列化方法
* @param callable $unserialize 反序列化方法
* @param string $prefix 序列化前缀标识
* @return $this
*/
public static function registerSerialize($serialize, $unserialize, $prefix = 'think_serialize:')
{
self::$serialize = [$serialize, $unserialize, $prefix, strlen($prefix)];
}
/**
* 返回句柄对象,可执行其它高级方法
*
* @access public
* @return object
*/
public function handler()
{
return $this->handler;
}
public function getReadTimes()
{
return $this->readTimes;
}
public function getWriteTimes()
{
return $this->writeTimes;
}
public function __call($method, $args)
{
return call_user_func_array([$this->handler, $method], $args);
}
}

View File

@@ -0,0 +1,307 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\cache\driver;
use think\cache\Driver;
use think\Container;
/**
* 文件类型缓存类
* @author liu21st <liu21st@gmail.com>
*/
class File extends Driver
{
protected $options = [
'expire' => 0,
'cache_subdir' => true,
'prefix' => '',
'path' => '',
'hash_type' => 'md5',
'data_compress' => false,
'serialize' => true,
];
protected $expire;
/**
* 架构函数
* @param array $options
*/
public function __construct($options = [])
{
if (!empty($options)) {
$this->options = array_merge($this->options, $options);
}
if (empty($this->options['path'])) {
$this->options['path'] = Container::get('app')->getRuntimePath() . 'cache' . DIRECTORY_SEPARATOR;
} elseif (substr($this->options['path'], -1) != DIRECTORY_SEPARATOR) {
$this->options['path'] .= DIRECTORY_SEPARATOR;
}
$this->init();
}
/**
* 初始化检查
* @access private
* @return boolean
*/
private function init()
{
// 创建项目缓存目录
try {
if (!is_dir($this->options['path']) && mkdir($this->options['path'], 0755, true)) {
return true;
}
} catch (\Exception $e) {
}
return false;
}
/**
* 取得变量的存储文件名
* @access protected
* @param string $name 缓存变量名
* @param bool $auto 是否自动创建目录
* @return string
*/
protected function getCacheKey($name, $auto = false)
{
$name = hash($this->options['hash_type'], $name);
if ($this->options['cache_subdir']) {
// 使用子目录
$name = substr($name, 0, 2) . DIRECTORY_SEPARATOR . substr($name, 2);
}
if ($this->options['prefix']) {
$name = $this->options['prefix'] . DIRECTORY_SEPARATOR . $name;
}
$filename = $this->options['path'] . $name . '.php';
$dir = dirname($filename);
if ($auto && !is_dir($dir)) {
try {
mkdir($dir, 0755, true);
} catch (\Exception $e) {
}
}
return $filename;
}
/**
* 判断缓存是否存在
* @access public
* @param string $name 缓存变量名
* @return bool
*/
public function has($name)
{
return false !== $this->get($name) ? true : false;
}
/**
* 读取缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $default 默认值
* @return mixed
*/
public function get($name, $default = false)
{
$this->readTimes++;
$filename = $this->getCacheKey($name);
if (!is_file($filename)) {
return $default;
}
$content = file_get_contents($filename);
$this->expire = null;
if (false !== $content) {
$expire = (int) substr($content, 8, 12);
if (0 != $expire && time() > filemtime($filename) + $expire) {
//缓存过期删除缓存文件
$this->unlink($filename);
return $default;
}
$this->expire = $expire;
$content = substr($content, 32);
if ($this->options['data_compress'] && function_exists('gzcompress')) {
//启用数据压缩
$content = gzuncompress($content);
}
return $this->unserialize($content);
} else {
return $default;
}
}
/**
* 写入缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $value 存储数据
* @param int|\DateTime $expire 有效时间 0为永久
* @return boolean
*/
public function set($name, $value, $expire = null)
{
$this->writeTimes++;
if (is_null($expire)) {
$expire = $this->options['expire'];
}
$expire = $this->getExpireTime($expire);
$filename = $this->getCacheKey($name, true);
if ($this->tag && !is_file($filename)) {
$first = true;
}
$data = $this->serialize($value);
if ($this->options['data_compress'] && function_exists('gzcompress')) {
//数据压缩
$data = gzcompress($data, 3);
}
$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);
if ($result) {
isset($first) && $this->setTagItem($filename);
clearstatcache();
return true;
} else {
return false;
}
}
/**
* 自增缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function inc($name, $step = 1)
{
if ($this->has($name)) {
$value = $this->get($name) + $step;
$expire = $this->expire;
} else {
$value = $step;
$expire = 0;
}
return $this->set($name, $value, $expire) ? $value : false;
}
/**
* 自减缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function dec($name, $step = 1)
{
if ($this->has($name)) {
$value = $this->get($name) - $step;
$expire = $this->expire;
} else {
$value = -$step;
$expire = 0;
}
return $this->set($name, $value, $expire) ? $value : false;
}
/**
* 删除缓存
* @access public
* @param string $name 缓存变量名
* @return boolean
*/
public function rm($name)
{
$this->writeTimes++;
try {
return $this->unlink($this->getCacheKey($name));
} catch (\Exception $e) {
}
}
/**
* 清除缓存
* @access public
* @param string $tag 标签名
* @return boolean
*/
public function clear($tag = null)
{
if ($tag) {
// 指定标签清除
$keys = $this->getTagItem($tag);
foreach ($keys as $key) {
$this->unlink($key);
}
$this->rm($this->getTagKey($tag));
return true;
}
$this->writeTimes++;
$files = (array) glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DIRECTORY_SEPARATOR : '') . '*');
foreach ($files as $path) {
if (is_dir($path)) {
$matches = glob($path . DIRECTORY_SEPARATOR . '*.php');
if (is_array($matches)) {
array_map(function ($v) {
$this->unlink($v);
}, $matches);
}
rmdir($path);
} else {
$this->unlink($path);
}
}
return true;
}
/**
* 判断文件是否存在后,删除
* @access private
* @param string $path
* @return bool
* @author byron sampson <xiaobo.sun@qq.com>
* @return boolean
*/
private function unlink($path)
{
return is_file($path) && unlink($path);
}
}

View File

@@ -0,0 +1,209 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\cache\driver;
use think\cache\Driver;
/**
* 文件类型缓存类
* @author liu21st <liu21st@gmail.com>
*/
class Lite extends Driver
{
protected $options = [
'prefix' => '',
'path' => '',
'expire' => 0, // 等于 10*365*24*360010年
];
/**
* 架构函数
* @access public
*
* @param array $options
*/
public function __construct($options = [])
{
if (!empty($options)) {
$this->options = array_merge($this->options, $options);
}
if (substr($this->options['path'], -1) != DIRECTORY_SEPARATOR) {
$this->options['path'] .= DIRECTORY_SEPARATOR;
}
}
/**
* 取得变量的存储文件名
* @access protected
* @param string $name 缓存变量名
* @return string
*/
protected function getCacheKey($name)
{
return $this->options['path'] . $this->options['prefix'] . md5($name) . '.php';
}
/**
* 判断缓存是否存在
* @access public
* @param string $name 缓存变量名
* @return mixed
*/
public function has($name)
{
return $this->get($name) ? true : false;
}
/**
* 读取缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $default 默认值
* @return mixed
*/
public function get($name, $default = false)
{
$this->readTimes++;
$filename = $this->getCacheKey($name);
if (is_file($filename)) {
// 判断是否过期
$mtime = filemtime($filename);
if ($mtime < time()) {
// 清除已经过期的文件
unlink($filename);
return $default;
}
return include $filename;
} else {
return $default;
}
}
/**
* 写入缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $value 存储数据
* @param int|\DateTime $expire 有效时间 0为永久
* @return bool
*/
public function set($name, $value, $expire = null)
{
$this->writeTimes++;
if (is_null($expire)) {
$expire = $this->options['expire'];
}
if ($expire instanceof \DateTime) {
$expire = $expire->getTimestamp();
} else {
$expire = 0 === $expire ? 10 * 365 * 24 * 3600 : $expire;
$expire = time() + $expire;
}
$filename = $this->getCacheKey($name);
if ($this->tag && !is_file($filename)) {
$first = true;
}
$ret = file_put_contents($filename, ("<?php return " . var_export($value, true) . ";"));
// 通过设置修改时间实现有效期
if ($ret) {
isset($first) && $this->setTagItem($filename);
touch($filename, $expire);
}
return $ret;
}
/**
* 自增缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function inc($name, $step = 1)
{
if ($this->has($name)) {
$value = $this->get($name) + $step;
} else {
$value = $step;
}
return $this->set($name, $value, 0) ? $value : false;
}
/**
* 自减缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function dec($name, $step = 1)
{
if ($this->has($name)) {
$value = $this->get($name) - $step;
} else {
$value = -$step;
}
return $this->set($name, $value, 0) ? $value : false;
}
/**
* 删除缓存
* @access public
* @param string $name 缓存变量名
* @return boolean
*/
public function rm($name)
{
$this->writeTimes++;
return unlink($this->getCacheKey($name));
}
/**
* 清除缓存
* @access public
* @param string $tag 标签名
* @return bool
*/
public function clear($tag = null)
{
if ($tag) {
// 指定标签清除
$keys = $this->getTagItem($tag);
foreach ($keys as $key) {
unlink($key);
}
$this->rm($this->getTagKey($tag));
return true;
}
$this->writeTimes++;
array_map("unlink", glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DIRECTORY_SEPARATOR : '') . '*.php'));
}
}

View File

@@ -0,0 +1,206 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\cache\driver;
use think\cache\Driver;
class Memcache extends Driver
{
protected $options = [
'host' => '127.0.0.1',
'port' => 11211,
'expire' => 0,
'timeout' => 0, // 超时时间(单位:毫秒)
'persistent' => true,
'prefix' => '',
'serialize' => true,
];
/**
* 架构函数
* @access public
* @param array $options 缓存参数
* @throws \BadFunctionCallException
*/
public function __construct($options = [])
{
if (!extension_loaded('memcache')) {
throw new \BadFunctionCallException('not support: memcache');
}
if (!empty($options)) {
$this->options = array_merge($this->options, $options);
}
$this->handler = new \Memcache;
// 支持集群
$hosts = explode(',', $this->options['host']);
$ports = explode(',', $this->options['port']);
if (empty($ports[0])) {
$ports[0] = 11211;
}
// 建立连接
foreach ((array) $hosts as $i => $host) {
$port = isset($ports[$i]) ? $ports[$i] : $ports[0];
$this->options['timeout'] > 0 ?
$this->handler->addServer($host, $port, $this->options['persistent'], 1, $this->options['timeout']) :
$this->handler->addServer($host, $port, $this->options['persistent'], 1);
}
}
/**
* 判断缓存
* @access public
* @param string $name 缓存变量名
* @return bool
*/
public function has($name)
{
$key = $this->getCacheKey($name);
return false !== $this->handler->get($key);
}
/**
* 读取缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $default 默认值
* @return mixed
*/
public function get($name, $default = false)
{
$this->readTimes++;
$result = $this->handler->get($this->getCacheKey($name));
return false !== $result ? $this->unserialize($result) : $default;
}
/**
* 写入缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $value 存储数据
* @param int|DateTime $expire 有效时间(秒)
* @return bool
*/
public function set($name, $value, $expire = null)
{
$this->writeTimes++;
if (is_null($expire)) {
$expire = $this->options['expire'];
}
if ($this->tag && !$this->has($name)) {
$first = true;
}
$key = $this->getCacheKey($name);
$expire = $this->getExpireTime($expire);
$value = $this->serialize($value);
if ($this->handler->set($key, $value, 0, $expire)) {
isset($first) && $this->setTagItem($key);
return true;
}
return false;
}
/**
* 自增缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function inc($name, $step = 1)
{
$this->writeTimes++;
$key = $this->getCacheKey($name);
if ($this->handler->get($key)) {
return $this->handler->increment($key, $step);
}
return $this->handler->set($key, $step);
}
/**
* 自减缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function dec($name, $step = 1)
{
$this->writeTimes++;
$key = $this->getCacheKey($name);
$value = $this->handler->get($key) - $step;
$res = $this->handler->set($key, $value);
return !$res ? false : $value;
}
/**
* 删除缓存
* @access public
* @param string $name 缓存变量名
* @param bool|false $ttl
* @return bool
*/
public function rm($name, $ttl = false)
{
$this->writeTimes++;
$key = $this->getCacheKey($name);
return false === $ttl ?
$this->handler->delete($key) :
$this->handler->delete($key, $ttl);
}
/**
* 清除缓存
* @access public
* @param string $tag 标签名
* @return bool
*/
public function clear($tag = null)
{
if ($tag) {
// 指定标签清除
$keys = $this->getTagItem($tag);
foreach ($keys as $key) {
$this->handler->delete($key);
}
$tagName = $this->getTagKey($tag);
$this->rm($tagName);
return true;
}
$this->writeTimes++;
return $this->handler->flush();
}
}

View File

@@ -0,0 +1,279 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\cache\driver;
use think\cache\Driver;
class Memcached extends Driver
{
protected $options = [
'host' => '127.0.0.1',
'port' => 11211,
'expire' => 0,
'timeout' => 0, // 超时时间(单位:毫秒)
'prefix' => '',
'username' => '', //账号
'password' => '', //密码
'option' => [],
'serialize' => true,
];
/**
* 架构函数
* @access public
* @param array $options 缓存参数
*/
public function __construct($options = [])
{
if (!extension_loaded('memcached')) {
throw new \BadFunctionCallException('not support: memcached');
}
if (!empty($options)) {
$this->options = array_merge($this->options, $options);
}
$this->handler = new \Memcached;
if (!empty($this->options['option'])) {
$this->handler->setOptions($this->options['option']);
}
// 设置连接超时时间(单位:毫秒)
if ($this->options['timeout'] > 0) {
$this->handler->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->options['timeout']);
}
// 支持集群
$hosts = explode(',', $this->options['host']);
$ports = explode(',', $this->options['port']);
if (empty($ports[0])) {
$ports[0] = 11211;
}
// 建立连接
$servers = [];
foreach ((array) $hosts as $i => $host) {
$servers[] = [$host, (isset($ports[$i]) ? $ports[$i] : $ports[0]), 1];
}
$this->handler->addServers($servers);
$this->handler->setOption(\Memcached::OPT_COMPRESSION, false);
if ('' != $this->options['username']) {
$this->handler->setOption(\Memcached::OPT_BINARY_PROTOCOL, true);
$this->handler->setSaslAuthData($this->options['username'], $this->options['password']);
}
}
/**
* 判断缓存
* @access public
* @param string $name 缓存变量名
* @return bool
*/
public function has($name)
{
$key = $this->getCacheKey($name);
return $this->handler->get($key) ? true : false;
}
/**
* 读取缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $default 默认值
* @return mixed
*/
public function get($name, $default = false)
{
$this->readTimes++;
$result = $this->handler->get($this->getCacheKey($name));
return false !== $result ? $this->unserialize($result) : $default;
}
/**
* 写入缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $value 存储数据
* @param integer|\DateTime $expire 有效时间(秒)
* @return bool
*/
public function set($name, $value, $expire = null)
{
$this->writeTimes++;
if (is_null($expire)) {
$expire = $this->options['expire'];
}
if ($this->tag && !$this->has($name)) {
$first = true;
}
$key = $this->getCacheKey($name);
$expire = $this->getExpireTime($expire);
$value = $this->serialize($value);
if ($this->handler->set($key, $value, $expire)) {
isset($first) && $this->setTagItem($key);
return true;
}
return false;
}
/**
* 自增缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function inc($name, $step = 1)
{
$this->writeTimes++;
$key = $this->getCacheKey($name);
if ($this->handler->get($key)) {
return $this->handler->increment($key, $step);
}
return $this->handler->set($key, $step);
}
/**
* 自减缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function dec($name, $step = 1)
{
$this->writeTimes++;
$key = $this->getCacheKey($name);
$value = $this->handler->get($key) - $step;
$res = $this->handler->set($key, $value);
return !$res ? false : $value;
}
/**
* 删除缓存
* @access public
* @param string $name 缓存变量名
* @param bool|false $ttl
* @return bool
*/
public function rm($name, $ttl = false)
{
$this->writeTimes++;
$key = $this->getCacheKey($name);
return false === $ttl ?
$this->handler->delete($key) :
$this->handler->delete($key, $ttl);
}
/**
* 清除缓存
* @access public
* @param string $tag 标签名
* @return bool
*/
public function clear($tag = null)
{
if ($tag) {
// 指定标签清除
$keys = $this->getTagItem($tag);
$this->handler->deleteMulti($keys);
$this->rm($this->getTagKey($tag));
return true;
}
$this->writeTimes++;
return $this->handler->flush();
}
/**
* 缓存标签
* @access public
* @param string $name 标签名
* @param string|array $keys 缓存标识
* @param bool $overlay 是否覆盖
* @return $this
*/
public function tag($name, $keys = null, $overlay = false)
{
if (is_null($keys)) {
$this->tag = $name;
} else {
$tagName = $this->getTagKey($name);
if ($overlay) {
$this->handler->delete($tagName);
}
if (!$this->has($tagName)) {
$this->handler->set($tagName, '');
}
foreach ($keys as $key) {
$this->handler->append($tagName, ',' . $key);
}
}
return $this;
}
/**
* 更新标签
* @access protected
* @param string $name 缓存标识
* @return void
*/
protected function setTagItem($name)
{
if ($this->tag) {
$tagName = $this->getTagKey($this->tag);
if ($this->has($tagName)) {
$this->handler->append($tagName, ',' . $name);
} else {
$this->handler->set($tagName, $name);
}
$this->tag = null;
}
}
/**
* 获取标签包含的缓存标识
* @access public
* @param string $tag 缓存标签
* @return array
*/
public function getTagItem($tag)
{
$tagName = $this->getTagKey($tag);
return explode(',', trim($this->handler->get($tagName), ','));
}
}

View File

@@ -0,0 +1,272 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\cache\driver;
use think\cache\Driver;
/**
* Redis缓存驱动适合单机部署、有前端代理实现高可用的场景性能最好
* 有需要在业务层实现读写分离、或者使用RedisCluster的需求请使用Redisd驱动
*
* 要求安装phpredis扩展https://github.com/nicolasff/phpredis
* @author 尘缘 <130775@qq.com>
*/
class Redis extends Driver
{
protected $options = [
'host' => '127.0.0.1',
'port' => 6379,
'password' => '',
'select' => 0,
'timeout' => 0,
'expire' => 0,
'persistent' => false,
'prefix' => '',
'serialize' => true,
];
/**
* 架构函数
* @access public
* @param array $options 缓存参数
*/
public function __construct($options = [])
{
if (!empty($options)) {
$this->options = array_merge($this->options, $options);
}
if (extension_loaded('redis')) {
$this->handler = new \Redis;
if ($this->options['persistent']) {
$this->handler->pconnect($this->options['host'], $this->options['port'], $this->options['timeout'], 'persistent_id_' . $this->options['select']);
} else {
$this->handler->connect($this->options['host'], $this->options['port'], $this->options['timeout']);
}
if ('' != $this->options['password']) {
$this->handler->auth($this->options['password']);
}
if (0 != $this->options['select']) {
$this->handler->select($this->options['select']);
}
} elseif (class_exists('\Predis\Client')) {
$params = [];
foreach ($this->options as $key => $val) {
if (in_array($key, ['aggregate', 'cluster', 'connections', 'exceptions', 'prefix', 'profile', 'replication', 'parameters'])) {
$params[$key] = $val;
unset($this->options[$key]);
}
}
if ('' == $this->options['password']) {
unset($this->options['password']);
}
$this->handler = new \Predis\Client($this->options, $params);
$this->options['prefix'] = '';
} else {
throw new \BadFunctionCallException('not support: redis');
}
}
/**
* 判断缓存
* @access public
* @param string $name 缓存变量名
* @return bool
*/
public function has($name)
{
return $this->handler->exists($this->getCacheKey($name)) ? true : false;
}
/**
* 读取缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $default 默认值
* @return mixed
*/
public function get($name, $default = false)
{
$this->readTimes++;
$value = $this->handler->get($this->getCacheKey($name));
if (is_null($value) || false === $value) {
return $default;
}
return $this->unserialize($value);
}
/**
* 写入缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $value 存储数据
* @param integer|\DateTime $expire 有效时间(秒)
* @return boolean
*/
public function set($name, $value, $expire = null)
{
$this->writeTimes++;
if (is_null($expire)) {
$expire = $this->options['expire'];
}
if ($this->tag && !$this->has($name)) {
$first = true;
}
$key = $this->getCacheKey($name);
$expire = $this->getExpireTime($expire);
$value = $this->serialize($value);
if ($expire) {
$result = $this->handler->setex($key, $expire, $value);
} else {
$result = $this->handler->set($key, $value);
}
isset($first) && $this->setTagItem($key);
return $result;
}
/**
* 自增缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function inc($name, $step = 1)
{
$this->writeTimes++;
$key = $this->getCacheKey($name);
return $this->handler->incrby($key, $step);
}
/**
* 自减缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function dec($name, $step = 1)
{
$this->writeTimes++;
$key = $this->getCacheKey($name);
return $this->handler->decrby($key, $step);
}
/**
* 删除缓存
* @access public
* @param string $name 缓存变量名
* @return boolean
*/
public function rm($name)
{
$this->writeTimes++;
return $this->handler->del($this->getCacheKey($name));
}
/**
* 清除缓存
* @access public
* @param string $tag 标签名
* @return boolean
*/
public function clear($tag = null)
{
if ($tag) {
// 指定标签清除
$keys = $this->getTagItem($tag);
$this->handler->del($keys);
$tagName = $this->getTagKey($tag);
$this->handler->del($tagName);
return true;
}
$this->writeTimes++;
return $this->handler->flushDB();
}
/**
* 缓存标签
* @access public
* @param string $name 标签名
* @param string|array $keys 缓存标识
* @param bool $overlay 是否覆盖
* @return $this
*/
public function tag($name, $keys = null, $overlay = false)
{
if (is_null($keys)) {
$this->tag = $name;
} else {
$tagName = $this->getTagKey($name);
if ($overlay) {
$this->handler->del($tagName);
}
foreach ($keys as $key) {
$this->handler->sAdd($tagName, $key);
}
}
return $this;
}
/**
* 更新标签
* @access protected
* @param string $name 缓存标识
* @return void
*/
protected function setTagItem($name)
{
if ($this->tag) {
$tagName = $this->getTagKey($this->tag);
$this->handler->sAdd($tagName, $name);
}
}
/**
* 获取标签包含的缓存标识
* @access protected
* @param string $tag 缓存标签
* @return array
*/
protected function getTagItem($tag)
{
$tagName = $this->getTagKey($tag);
return $this->handler->sMembers($tagName);
}
}

View File

@@ -0,0 +1,233 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\cache\driver;
use think\cache\Driver;
/**
* Sqlite缓存驱动
* @author liu21st <liu21st@gmail.com>
*/
class Sqlite extends Driver
{
protected $options = [
'db' => ':memory:',
'table' => 'sharedmemory',
'prefix' => '',
'expire' => 0,
'persistent' => false,
'serialize' => true,
];
/**
* 架构函数
* @access public
* @param array $options 缓存参数
* @throws \BadFunctionCallException
*/
public function __construct($options = [])
{
if (!extension_loaded('sqlite')) {
throw new \BadFunctionCallException('not support: sqlite');
}
if (!empty($options)) {
$this->options = array_merge($this->options, $options);
}
$func = $this->options['persistent'] ? 'sqlite_popen' : 'sqlite_open';
$this->handler = $func($this->options['db']);
}
/**
* 获取实际的缓存标识
* @access public
* @param string $name 缓存名
* @return string
*/
protected function getCacheKey($name)
{
return $this->options['prefix'] . sqlite_escape_string($name);
}
/**
* 判断缓存
* @access public
* @param string $name 缓存变量名
* @return bool
*/
public function has($name)
{
$name = $this->getCacheKey($name);
$sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . time() . ') LIMIT 1';
$result = sqlite_query($this->handler, $sql);
return sqlite_num_rows($result);
}
/**
* 读取缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $default 默认值
* @return mixed
*/
public function get($name, $default = false)
{
$this->readTimes++;
$name = $this->getCacheKey($name);
$sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . time() . ') LIMIT 1';
$result = sqlite_query($this->handler, $sql);
if (sqlite_num_rows($result)) {
$content = sqlite_fetch_single($result);
if (function_exists('gzcompress')) {
//启用数据压缩
$content = gzuncompress($content);
}
return $this->unserialize($content);
}
return $default;
}
/**
* 写入缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $value 存储数据
* @param integer|\DateTime $expire 有效时间(秒)
* @return boolean
*/
public function set($name, $value, $expire = null)
{
$this->writeTimes++;
$name = $this->getCacheKey($name);
$value = sqlite_escape_string($this->serialize($value));
if (is_null($expire)) {
$expire = $this->options['expire'];
}
if ($expire instanceof \DateTime) {
$expire = $expire->getTimestamp();
} else {
$expire = (0 == $expire) ? 0 : (time() + $expire); //缓存有效期为0表示永久缓存
}
if (function_exists('gzcompress')) {
//数据压缩
$value = gzcompress($value, 3);
}
if ($this->tag) {
$tag = $this->tag;
$this->tag = null;
} else {
$tag = '';
}
$sql = 'REPLACE INTO ' . $this->options['table'] . ' (var, value, expire, tag) VALUES (\'' . $name . '\', \'' . $value . '\', \'' . $expire . '\', \'' . $tag . '\')';
if (sqlite_query($this->handler, $sql)) {
return true;
}
return false;
}
/**
* 自增缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function inc($name, $step = 1)
{
if ($this->has($name)) {
$value = $this->get($name) + $step;
} else {
$value = $step;
}
return $this->set($name, $value, 0) ? $value : false;
}
/**
* 自减缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function dec($name, $step = 1)
{
if ($this->has($name)) {
$value = $this->get($name) - $step;
} else {
$value = -$step;
}
return $this->set($name, $value, 0) ? $value : false;
}
/**
* 删除缓存
* @access public
* @param string $name 缓存变量名
* @return boolean
*/
public function rm($name)
{
$this->writeTimes++;
$name = $this->getCacheKey($name);
$sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\'';
sqlite_query($this->handler, $sql);
return true;
}
/**
* 清除缓存
* @access public
* @param string $tag 标签名
* @return boolean
*/
public function clear($tag = null)
{
if ($tag) {
$name = sqlite_escape_string($this->getTagKey($tag));
$sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE tag=\'' . $name . '\'';
sqlite_query($this->handler, $sql);
return true;
}
$this->writeTimes++;
$sql = 'DELETE FROM ' . $this->options['table'];
sqlite_query($this->handler, $sql);
return true;
}
}

View File

@@ -0,0 +1,175 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\cache\driver;
use think\cache\Driver;
/**
* Wincache缓存驱动
* @author liu21st <liu21st@gmail.com>
*/
class Wincache extends Driver
{
protected $options = [
'prefix' => '',
'expire' => 0,
'serialize' => true,
];
/**
* 架构函数
* @access public
* @param array $options 缓存参数
* @throws \BadFunctionCallException
*/
public function __construct($options = [])
{
if (!function_exists('wincache_ucache_info')) {
throw new \BadFunctionCallException('not support: WinCache');
}
if (!empty($options)) {
$this->options = array_merge($this->options, $options);
}
}
/**
* 判断缓存
* @access public
* @param string $name 缓存变量名
* @return bool
*/
public function has($name)
{
$this->readTimes++;
$key = $this->getCacheKey($name);
return wincache_ucache_exists($key);
}
/**
* 读取缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $default 默认值
* @return mixed
*/
public function get($name, $default = false)
{
$this->readTimes++;
$key = $this->getCacheKey($name);
return wincache_ucache_exists($key) ? $this->unserialize(wincache_ucache_get($key)) : $default;
}
/**
* 写入缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $value 存储数据
* @param integer|\DateTime $expire 有效时间(秒)
* @return boolean
*/
public function set($name, $value, $expire = null)
{
$this->writeTimes++;
if (is_null($expire)) {
$expire = $this->options['expire'];
}
$key = $this->getCacheKey($name);
$expire = $this->getExpireTime($expire);
$value = $this->serialize($value);
if ($this->tag && !$this->has($name)) {
$first = true;
}
if (wincache_ucache_set($key, $value, $expire)) {
isset($first) && $this->setTagItem($key);
return true;
}
return false;
}
/**
* 自增缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function inc($name, $step = 1)
{
$this->writeTimes++;
$key = $this->getCacheKey($name);
return wincache_ucache_inc($key, $step);
}
/**
* 自减缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function dec($name, $step = 1)
{
$this->writeTimes++;
$key = $this->getCacheKey($name);
return wincache_ucache_dec($key, $step);
}
/**
* 删除缓存
* @access public
* @param string $name 缓存变量名
* @return boolean
*/
public function rm($name)
{
$this->writeTimes++;
return wincache_ucache_delete($this->getCacheKey($name));
}
/**
* 清除缓存
* @access public
* @param string $tag 标签名
* @return boolean
*/
public function clear($tag = null)
{
if ($tag) {
$keys = $this->getTagItem($tag);
wincache_ucache_delete($keys);
$tagName = $this->getTagkey($tag);
$this->rm($tagName);
return true;
}
$this->writeTimes++;
return wincache_ucache_clear();
}
}

View File

@@ -0,0 +1,179 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\cache\driver;
use think\cache\Driver;
/**
* Xcache缓存驱动
* @author liu21st <liu21st@gmail.com>
*/
class Xcache extends Driver
{
protected $options = [
'prefix' => '',
'expire' => 0,
'serialize' => true,
];
/**
* 架构函数
* @access public
* @param array $options 缓存参数
* @throws \BadFunctionCallException
*/
public function __construct($options = [])
{
if (!function_exists('xcache_info')) {
throw new \BadFunctionCallException('not support: Xcache');
}
if (!empty($options)) {
$this->options = array_merge($this->options, $options);
}
}
/**
* 判断缓存
* @access public
* @param string $name 缓存变量名
* @return bool
*/
public function has($name)
{
$key = $this->getCacheKey($name);
return xcache_isset($key);
}
/**
* 读取缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $default 默认值
* @return mixed
*/
public function get($name, $default = false)
{
$this->readTimes++;
$key = $this->getCacheKey($name);
return xcache_isset($key) ? $this->unserialize(xcache_get($key)) : $default;
}
/**
* 写入缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $value 存储数据
* @param integer|\DateTime $expire 有效时间(秒)
* @return boolean
*/
public function set($name, $value, $expire = null)
{
$this->writeTimes++;
if (is_null($expire)) {
$expire = $this->options['expire'];
}
if ($this->tag && !$this->has($name)) {
$first = true;
}
$key = $this->getCacheKey($name);
$expire = $this->getExpireTime($expire);
$value = $this->serialize($value);
if (xcache_set($key, $value, $expire)) {
isset($first) && $this->setTagItem($key);
return true;
}
return false;
}
/**
* 自增缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function inc($name, $step = 1)
{
$this->writeTimes++;
$key = $this->getCacheKey($name);
return xcache_inc($key, $step);
}
/**
* 自减缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function dec($name, $step = 1)
{
$this->writeTimes++;
$key = $this->getCacheKey($name);
return xcache_dec($key, $step);
}
/**
* 删除缓存
* @access public
* @param string $name 缓存变量名
* @return boolean
*/
public function rm($name)
{
$this->writeTimes++;
return xcache_unset($this->getCacheKey($name));
}
/**
* 清除缓存
* @access public
* @param string $tag 标签名
* @return boolean
*/
public function clear($tag = null)
{
if ($tag) {
// 指定标签清除
$keys = $this->getTagItem($tag);
foreach ($keys as $key) {
xcache_unset($key);
}
$this->rm($this->getTagKey($tag));
return true;
}
$this->writeTimes++;
if (function_exists('xcache_unset_by_prefix')) {
return xcache_unset_by_prefix($this->options['prefix']);
} else {
return false;
}
}
}

View File

@@ -0,0 +1,31 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\config\driver;
class Ini
{
protected $config;
public function __construct($config)
{
$this->config = $config;
}
public function parse()
{
if (is_file($this->config)) {
return parse_ini_file($this->config, true);
} else {
return parse_ini_string($this->config, true);
}
}
}

View File

@@ -0,0 +1,31 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\config\driver;
class Json
{
protected $config;
public function __construct($config)
{
if (is_file($config)) {
$config = file_get_contents($config);
}
$this->config = $config;
}
public function parse()
{
return json_decode($this->config, true);
}
}

View File

@@ -0,0 +1,40 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\config\driver;
class Xml
{
protected $config;
public function __construct($config)
{
$this->config = $config;
}
public function parse()
{
if (is_file($this->config)) {
$content = simplexml_load_file($this->config);
} else {
$content = simplexml_load_string($this->config);
}
$result = (array) $content;
foreach ($result as $key => $val) {
if (is_object($val)) {
$result[$key] = (array) $val;
}
}
return $result;
}
}

View File

@@ -0,0 +1,482 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console;
use think\Console;
use think\console\input\Argument;
use think\console\input\Definition;
use think\console\input\Option;
class Command
{
/** @var Console */
private $console;
private $name;
private $aliases = [];
private $definition;
private $help;
private $description;
private $ignoreValidationErrors = false;
private $consoleDefinitionMerged = false;
private $consoleDefinitionMergedWithArgs = false;
private $code;
private $synopsis = [];
private $usages = [];
/** @var Input */
protected $input;
/** @var Output */
protected $output;
/**
* 构造方法
* @param string|null $name 命令名称,如果没有设置则比如在 configure() 里设置
* @throws \LogicException
* @api
*/
public function __construct($name = null)
{
$this->definition = new Definition();
if (null !== $name) {
$this->setName($name);
}
$this->configure();
if (!$this->name) {
throw new \LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this)));
}
}
/**
* 忽略验证错误
*/
public function ignoreValidationErrors()
{
$this->ignoreValidationErrors = true;
}
/**
* 设置控制台
* @param Console $console
*/
public function setConsole(Console $console = null)
{
$this->console = $console;
}
/**
* 获取控制台
* @return Console
* @api
*/
public function getConsole()
{
return $this->console;
}
/**
* 是否有效
* @return bool
*/
public function isEnabled()
{
return true;
}
/**
* 配置指令
*/
protected function configure()
{
}
/**
* 执行指令
* @param Input $input
* @param Output $output
* @return null|int
* @throws \LogicException
* @see setCode()
*/
protected function execute(Input $input, Output $output)
{
throw new \LogicException('You must override the execute() method in the concrete command class.');
}
/**
* 用户验证
* @param Input $input
* @param Output $output
*/
protected function interact(Input $input, Output $output)
{
}
/**
* 初始化
* @param Input $input An InputInterface instance
* @param Output $output An OutputInterface instance
*/
protected function initialize(Input $input, Output $output)
{
}
/**
* 执行
* @param Input $input
* @param Output $output
* @return int
* @throws \Exception
* @see setCode()
* @see execute()
*/
public function run(Input $input, Output $output)
{
$this->input = $input;
$this->output = $output;
$this->getSynopsis(true);
$this->getSynopsis(false);
$this->mergeConsoleDefinition();
try {
$input->bind($this->definition);
} catch (\Exception $e) {
if (!$this->ignoreValidationErrors) {
throw $e;
}
}
$this->initialize($input, $output);
if ($input->isInteractive()) {
$this->interact($input, $output);
}
$input->validate();
if ($this->code) {
$statusCode = call_user_func($this->code, $input, $output);
} else {
$statusCode = $this->execute($input, $output);
}
return is_numeric($statusCode) ? (int) $statusCode : 0;
}
/**
* 设置执行代码
* @param callable $code callable(InputInterface $input, OutputInterface $output)
* @return Command
* @throws \InvalidArgumentException
* @see execute()
*/
public function setCode(callable $code)
{
if (!is_callable($code)) {
throw new \InvalidArgumentException('Invalid callable provided to Command::setCode.');
}
if (PHP_VERSION_ID >= 50400 && $code instanceof \Closure) {
$r = new \ReflectionFunction($code);
if (null === $r->getClosureThis()) {
$code = \Closure::bind($code, $this);
}
}
$this->code = $code;
return $this;
}
/**
* 合并参数定义
* @param bool $mergeArgs
*/
public function mergeConsoleDefinition($mergeArgs = true)
{
if (null === $this->console
|| (true === $this->consoleDefinitionMerged
&& ($this->consoleDefinitionMergedWithArgs || !$mergeArgs))
) {
return;
}
if ($mergeArgs) {
$currentArguments = $this->definition->getArguments();
$this->definition->setArguments($this->console->getDefinition()->getArguments());
$this->definition->addArguments($currentArguments);
}
$this->definition->addOptions($this->console->getDefinition()->getOptions());
$this->consoleDefinitionMerged = true;
if ($mergeArgs) {
$this->consoleDefinitionMergedWithArgs = true;
}
}
/**
* 设置参数定义
* @param array|Definition $definition
* @return Command
* @api
*/
public function setDefinition($definition)
{
if ($definition instanceof Definition) {
$this->definition = $definition;
} else {
$this->definition->setDefinition($definition);
}
$this->consoleDefinitionMerged = false;
return $this;
}
/**
* 获取参数定义
* @return Definition
* @api
*/
public function getDefinition()
{
return $this->definition;
}
/**
* 获取当前指令的参数定义
* @return Definition
*/
public function getNativeDefinition()
{
return $this->getDefinition();
}
/**
* 添加参数
* @param string $name 名称
* @param int $mode 类型
* @param string $description 描述
* @param mixed $default 默认值
* @return Command
*/
public function addArgument($name, $mode = null, $description = '', $default = null)
{
$this->definition->addArgument(new Argument($name, $mode, $description, $default));
return $this;
}
/**
* 添加选项
* @param string $name 选项名称
* @param string $shortcut 别名
* @param int $mode 类型
* @param string $description 描述
* @param mixed $default 默认值
* @return Command
*/
public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null)
{
$this->definition->addOption(new Option($name, $shortcut, $mode, $description, $default));
return $this;
}
/**
* 设置指令名称
* @param string $name
* @return Command
* @throws \InvalidArgumentException
*/
public function setName($name)
{
$this->validateName($name);
$this->name = $name;
return $this;
}
/**
* 获取指令名称
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* 设置描述
* @param string $description
* @return Command
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* 获取描述
* @return string
*/
public function getDescription()
{
return $this->description;
}
/**
* 设置帮助信息
* @param string $help
* @return Command
*/
public function setHelp($help)
{
$this->help = $help;
return $this;
}
/**
* 获取帮助信息
* @return string
*/
public function getHelp()
{
return $this->help;
}
/**
* 描述信息
* @return string
*/
public function getProcessedHelp()
{
$name = $this->name;
$placeholders = [
'%command.name%',
'%command.full_name%',
];
$replacements = [
$name,
$_SERVER['PHP_SELF'] . ' ' . $name,
];
return str_replace($placeholders, $replacements, $this->getHelp());
}
/**
* 设置别名
* @param string[] $aliases
* @return Command
* @throws \InvalidArgumentException
*/
public function setAliases($aliases)
{
if (!is_array($aliases) && !$aliases instanceof \Traversable) {
throw new \InvalidArgumentException('$aliases must be an array or an instance of \Traversable');
}
foreach ($aliases as $alias) {
$this->validateName($alias);
}
$this->aliases = $aliases;
return $this;
}
/**
* 获取别名
* @return array
*/
public function getAliases()
{
return $this->aliases;
}
/**
* 获取简介
* @param bool $short 是否简单的
* @return string
*/
public function getSynopsis($short = false)
{
$key = $short ? 'short' : 'long';
if (!isset($this->synopsis[$key])) {
$this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short)));
}
return $this->synopsis[$key];
}
/**
* 添加用法介绍
* @param string $usage
* @return $this
*/
public function addUsage($usage)
{
if (0 !== strpos($usage, $this->name)) {
$usage = sprintf('%s %s', $this->name, $usage);
}
$this->usages[] = $usage;
return $this;
}
/**
* 获取用法介绍
* @return array
*/
public function getUsages()
{
return $this->usages;
}
/**
* 验证指令名称
* @param string $name
* @throws \InvalidArgumentException
*/
private function validateName($name)
{
if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) {
throw new \InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name));
}
}
/**
* 输出表格
* @param Table $table
* @return string
*/
protected function table(Table $table)
{
$content = $table->render();
$this->output->writeln($content);
return $content;
}
}

View File

@@ -0,0 +1,464 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console;
use think\console\input\Argument;
use think\console\input\Definition;
use think\console\input\Option;
class Input
{
/**
* @var Definition
*/
protected $definition;
/**
* @var Option[]
*/
protected $options = [];
/**
* @var Argument[]
*/
protected $arguments = [];
protected $interactive = true;
private $tokens;
private $parsed;
public function __construct($argv = null)
{
if (null === $argv) {
$argv = $_SERVER['argv'];
// 去除命令名
array_shift($argv);
}
$this->tokens = $argv;
$this->definition = new Definition();
}
protected function setTokens(array $tokens)
{
$this->tokens = $tokens;
}
/**
* 绑定实例
* @param Definition $definition A InputDefinition instance
*/
public function bind(Definition $definition)
{
$this->arguments = [];
$this->options = [];
$this->definition = $definition;
$this->parse();
}
/**
* 解析参数
*/
protected function parse()
{
$parseOptions = true;
$this->parsed = $this->tokens;
while (null !== $token = array_shift($this->parsed)) {
if ($parseOptions && '' == $token) {
$this->parseArgument($token);
} elseif ($parseOptions && '--' == $token) {
$parseOptions = false;
} elseif ($parseOptions && 0 === strpos($token, '--')) {
$this->parseLongOption($token);
} elseif ($parseOptions && '-' === $token[0] && '-' !== $token) {
$this->parseShortOption($token);
} else {
$this->parseArgument($token);
}
}
}
/**
* 解析短选项
* @param string $token 当前的指令.
*/
private function parseShortOption($token)
{
$name = substr($token, 1);
if (strlen($name) > 1) {
if ($this->definition->hasShortcut($name[0])
&& $this->definition->getOptionForShortcut($name[0])->acceptValue()
) {
$this->addShortOption($name[0], substr($name, 1));
} else {
$this->parseShortOptionSet($name);
}
} else {
$this->addShortOption($name, null);
}
}
/**
* 解析短选项
* @param string $name 当前指令
* @throws \RuntimeException
*/
private function parseShortOptionSet($name)
{
$len = strlen($name);
for ($i = 0; $i < $len; ++$i) {
if (!$this->definition->hasShortcut($name[$i])) {
throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i]));
}
$option = $this->definition->getOptionForShortcut($name[$i]);
if ($option->acceptValue()) {
$this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1));
break;
} else {
$this->addLongOption($option->getName(), null);
}
}
}
/**
* 解析完整选项
* @param string $token 当前指令
*/
private function parseLongOption($token)
{
$name = substr($token, 2);
if (false !== $pos = strpos($name, '=')) {
$this->addLongOption(substr($name, 0, $pos), substr($name, $pos + 1));
} else {
$this->addLongOption($name, null);
}
}
/**
* 解析参数
* @param string $token 当前指令
* @throws \RuntimeException
*/
private function parseArgument($token)
{
$c = count($this->arguments);
if ($this->definition->hasArgument($c)) {
$arg = $this->definition->getArgument($c);
$this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token;
} elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) {
$arg = $this->definition->getArgument($c - 1);
$this->arguments[$arg->getName()][] = $token;
} else {
throw new \RuntimeException('Too many arguments.');
}
}
/**
* 添加一个短选项的值
* @param string $shortcut 短名称
* @param mixed $value 值
* @throws \RuntimeException
*/
private function addShortOption($shortcut, $value)
{
if (!$this->definition->hasShortcut($shortcut)) {
throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut));
}
$this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
}
/**
* 添加一个完整选项的值
* @param string $name 选项名
* @param mixed $value 值
* @throws \RuntimeException
*/
private function addLongOption($name, $value)
{
if (!$this->definition->hasOption($name)) {
throw new \RuntimeException(sprintf('The "--%s" option does not exist.', $name));
}
$option = $this->definition->getOption($name);
if (false === $value) {
$value = null;
}
if (null !== $value && !$option->acceptValue()) {
throw new \RuntimeException(sprintf('The "--%s" option does not accept a value.', $name, $value));
}
if (null === $value && $option->acceptValue() && count($this->parsed)) {
$next = array_shift($this->parsed);
if (isset($next[0]) && '-' !== $next[0]) {
$value = $next;
} elseif (empty($next)) {
$value = '';
} else {
array_unshift($this->parsed, $next);
}
}
if (null === $value) {
if ($option->isValueRequired()) {
throw new \RuntimeException(sprintf('The "--%s" option requires a value.', $name));
}
if (!$option->isArray()) {
$value = $option->isValueOptional() ? $option->getDefault() : true;
}
}
if ($option->isArray()) {
$this->options[$name][] = $value;
} else {
$this->options[$name] = $value;
}
}
/**
* 获取第一个参数
* @return string|null
*/
public function getFirstArgument()
{
foreach ($this->tokens as $token) {
if ($token && '-' === $token[0]) {
continue;
}
return $token;
}
return;
}
/**
* 检查原始参数是否包含某个值
* @param string|array $values 需要检查的值
* @return bool
*/
public function hasParameterOption($values)
{
$values = (array) $values;
foreach ($this->tokens as $token) {
foreach ($values as $value) {
if ($token === $value || 0 === strpos($token, $value . '=')) {
return true;
}
}
}
return false;
}
/**
* 获取原始选项的值
* @param string|array $values 需要检查的值
* @param mixed $default 默认值
* @return mixed The option value
*/
public function getParameterOption($values, $default = false)
{
$values = (array) $values;
$tokens = $this->tokens;
while (0 < count($tokens)) {
$token = array_shift($tokens);
foreach ($values as $value) {
if ($token === $value || 0 === strpos($token, $value . '=')) {
if (false !== $pos = strpos($token, '=')) {
return substr($token, $pos + 1);
}
return array_shift($tokens);
}
}
}
return $default;
}
/**
* 验证输入
* @throws \RuntimeException
*/
public function validate()
{
if (count($this->arguments) < $this->definition->getArgumentRequiredCount()) {
throw new \RuntimeException('Not enough arguments.');
}
}
/**
* 检查输入是否是交互的
* @return bool
*/
public function isInteractive()
{
return $this->interactive;
}
/**
* 设置输入的交互
* @param bool
*/
public function setInteractive($interactive)
{
$this->interactive = (bool) $interactive;
}
/**
* 获取所有的参数
* @return Argument[]
*/
public function getArguments()
{
return array_merge($this->definition->getArgumentDefaults(), $this->arguments);
}
/**
* 根据名称获取参数
* @param string $name 参数名
* @return mixed
* @throws \InvalidArgumentException
*/
public function getArgument($name)
{
if (!$this->definition->hasArgument($name)) {
throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name)
->getDefault();
}
/**
* 设置参数的值
* @param string $name 参数名
* @param string $value 值
* @throws \InvalidArgumentException
*/
public function setArgument($name, $value)
{
if (!$this->definition->hasArgument($name)) {
throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
$this->arguments[$name] = $value;
}
/**
* 检查是否存在某个参数
* @param string|int $name 参数名或位置
* @return bool
*/
public function hasArgument($name)
{
return $this->definition->hasArgument($name);
}
/**
* 获取所有的选项
* @return Option[]
*/
public function getOptions()
{
return array_merge($this->definition->getOptionDefaults(), $this->options);
}
/**
* 获取选项值
* @param string $name 选项名称
* @return mixed
* @throws \InvalidArgumentException
*/
public function getOption($name)
{
if (!$this->definition->hasOption($name)) {
throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
}
return isset($this->options[$name]) ? $this->options[$name] : $this->definition->getOption($name)->getDefault();
}
/**
* 设置选项值
* @param string $name 选项名
* @param string|bool $value 值
* @throws \InvalidArgumentException
*/
public function setOption($name, $value)
{
if (!$this->definition->hasOption($name)) {
throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
}
$this->options[$name] = $value;
}
/**
* 是否有某个选项
* @param string $name 选项名
* @return bool
*/
public function hasOption($name)
{
return $this->definition->hasOption($name) && isset($this->options[$name]);
}
/**
* 转义指令
* @param string $token
* @return string
*/
public function escapeToken($token)
{
return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token);
}
/**
* 返回传递给命令的参数的字符串
* @return string
*/
public function __toString()
{
$tokens = array_map(function ($token) {
if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) {
return $match[1] . $this->escapeToken($match[2]);
}
if ($token && '-' !== $token[0]) {
return $this->escapeToken($token);
}
return $token;
}, $this->tokens);
return implode(' ', $tokens);
}
}

View File

@@ -0,0 +1,19 @@
Copyright (c) 2004-2016 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,222 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console;
use Exception;
use think\console\output\Ask;
use think\console\output\Descriptor;
use think\console\output\driver\Buffer;
use think\console\output\driver\Console;
use think\console\output\driver\Nothing;
use think\console\output\Question;
use think\console\output\question\Choice;
use think\console\output\question\Confirmation;
/**
* Class Output
* @package think\console
*
* @see \think\console\output\driver\Console::setDecorated
* @method void setDecorated($decorated)
*
* @see \think\console\output\driver\Buffer::fetch
* @method string fetch()
*
* @method void info($message)
* @method void error($message)
* @method void comment($message)
* @method void warning($message)
* @method void highlight($message)
* @method void question($message)
*/
class Output
{
const VERBOSITY_QUIET = 0;
const VERBOSITY_NORMAL = 1;
const VERBOSITY_VERBOSE = 2;
const VERBOSITY_VERY_VERBOSE = 3;
const VERBOSITY_DEBUG = 4;
const OUTPUT_NORMAL = 0;
const OUTPUT_RAW = 1;
const OUTPUT_PLAIN = 2;
private $verbosity = self::VERBOSITY_NORMAL;
/** @var Buffer|Console|Nothing */
private $handle = null;
protected $styles = [
'info',
'error',
'comment',
'question',
'highlight',
'warning'
];
public function __construct($driver = 'console')
{
$class = '\\think\\console\\output\\driver\\' . ucwords($driver);
$this->handle = new $class($this);
}
public function ask(Input $input, $question, $default = null, $validator = null)
{
$question = new Question($question, $default);
$question->setValidator($validator);
return $this->askQuestion($input, $question);
}
public function askHidden(Input $input, $question, $validator = null)
{
$question = new Question($question);
$question->setHidden(true);
$question->setValidator($validator);
return $this->askQuestion($input, $question);
}
public function confirm(Input $input, $question, $default = true)
{
return $this->askQuestion($input, new Confirmation($question, $default));
}
/**
* {@inheritdoc}
*/
public function choice(Input $input, $question, array $choices, $default = null)
{
if (null !== $default) {
$values = array_flip($choices);
$default = $values[$default];
}
return $this->askQuestion($input, new Choice($question, $choices, $default));
}
protected function askQuestion(Input $input, Question $question)
{
$ask = new Ask($input, $this, $question);
$answer = $ask->run();
if ($input->isInteractive()) {
$this->newLine();
}
return $answer;
}
protected function block($style, $message)
{
$this->writeln("<{$style}>{$message}</$style>");
}
/**
* 输出空行
* @param int $count
*/
public function newLine($count = 1)
{
$this->write(str_repeat(PHP_EOL, $count));
}
/**
* 输出信息并换行
* @param string $messages
* @param int $type
*/
public function writeln($messages, $type = self::OUTPUT_NORMAL)
{
$this->write($messages, true, $type);
}
/**
* 输出信息
* @param string $messages
* @param bool $newline
* @param int $type
*/
public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
{
$this->handle->write($messages, $newline, $type);
}
public function renderException(\Exception $e)
{
$this->handle->renderException($e);
}
/**
* {@inheritdoc}
*/
public function setVerbosity($level)
{
$this->verbosity = (int) $level;
}
/**
* {@inheritdoc}
*/
public function getVerbosity()
{
return $this->verbosity;
}
public function isQuiet()
{
return self::VERBOSITY_QUIET === $this->verbosity;
}
public function isVerbose()
{
return self::VERBOSITY_VERBOSE <= $this->verbosity;
}
public function isVeryVerbose()
{
return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity;
}
public function isDebug()
{
return self::VERBOSITY_DEBUG <= $this->verbosity;
}
public function describe($object, array $options = [])
{
$descriptor = new Descriptor();
$options = array_merge([
'raw_text' => false,
], $options);
$descriptor->describe($this, $object, $options);
}
public function __call($method, $args)
{
if (in_array($method, $this->styles)) {
array_unshift($args, $method);
return call_user_func_array([$this, 'block'], $args);
}
if ($this->handle && method_exists($this->handle, $method)) {
return call_user_func_array([$this->handle, $method], $args);
} else {
throw new Exception('method not exists:' . __CLASS__ . '->' . $method);
}
}
}

View File

@@ -0,0 +1,281 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console;
class Table
{
const ALIGN_LEFT = 1;
const ALIGN_RIGHT = 0;
const ALIGN_CENTER = 2;
/**
* 头信息数据
* @var array
*/
protected $header = [];
/**
* 头部对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER
* @var int
*/
protected $headerAlign = 1;
/**
* 表格数据(二维数组)
* @var array
*/
protected $rows = [];
/**
* 单元格对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER
* @var int
*/
protected $cellAlign = 1;
/**
* 单元格宽度信息
* @var array
*/
protected $colWidth = [];
/**
* 表格输出样式
* @var string
*/
protected $style = 'default';
/**
* 表格样式定义
* @var array
*/
protected $format = [
'compact' => [],
'default' => [
'top' => ['+', '-', '+', '+'],
'cell' => ['|', ' ', '|', '|'],
'middle' => ['+', '-', '+', '+'],
'bottom' => ['+', '-', '+', '+'],
'cross-top' => ['+', '-', '-', '+'],
'cross-bottom' => ['+', '-', '-', '+'],
],
'markdown' => [
'top' => [' ', ' ', ' ', ' '],
'cell' => ['|', ' ', '|', '|'],
'middle' => ['|', '-', '|', '|'],
'bottom' => [' ', ' ', ' ', ' '],
'cross-top' => ['|', ' ', ' ', '|'],
'cross-bottom' => ['|', ' ', ' ', '|'],
],
'borderless' => [
'top' => ['=', '=', ' ', '='],
'cell' => [' ', ' ', ' ', ' '],
'middle' => ['=', '=', ' ', '='],
'bottom' => ['=', '=', ' ', '='],
'cross-top' => ['=', '=', ' ', '='],
'cross-bottom' => ['=', '=', ' ', '='],
],
'box' => [
'top' => ['┌', '─', '┬', '┐'],
'cell' => ['│', ' ', '│', '│'],
'middle' => ['├', '─', '┼', '┤'],
'bottom' => ['└', '─', '┴', '┘'],
'cross-top' => ['├', '─', '┴', '┤'],
'cross-bottom' => ['├', '─', '┬', '┤'],
],
'box-double' => [
'top' => ['╔', '═', '╤', '╗'],
'cell' => ['║', ' ', '│', '║'],
'middle' => ['╠', '─', '╪', '╣'],
'bottom' => ['╚', '═', '╧', '╝'],
'cross-top' => ['╠', '═', '╧', '╣'],
'cross-bottom' => ['╠', '═', '╤', '╣'],
],
];
/**
* 设置表格头信息 以及对齐方式
* @access public
* @param array $header 要输出的Header信息
* @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER
* @return void
*/
public function setHeader(array $header, $align = self::ALIGN_LEFT)
{
$this->header = $header;
$this->headerAlign = $align;
$this->checkColWidth($header);
}
/**
* 设置输出表格数据 及对齐方式
* @access public
* @param array $rows 要输出的表格数据(二维数组)
* @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER
* @return void
*/
public function setRows(array $rows, $align = self::ALIGN_LEFT)
{
$this->rows = $rows;
$this->cellAlign = $align;
foreach ($rows as $row) {
$this->checkColWidth($row);
}
}
/**
* 检查列数据的显示宽度
* @access public
* @param mixed $row 行数据
* @return void
*/
protected function checkColWidth($row)
{
if (is_array($row)) {
foreach ($row as $key => $cell) {
if (!isset($this->colWidth[$key]) || strlen($cell) > $this->colWidth[$key]) {
$this->colWidth[$key] = strlen($cell);
}
}
}
}
/**
* 增加一行表格数据
* @access public
* @param mixed $row 行数据
* @param bool $first 是否在开头插入
* @return void
*/
public function addRow($row, $first = false)
{
if ($first) {
array_unshift($this->rows, $row);
} else {
$this->rows[] = $row;
}
$this->checkColWidth($row);
}
/**
* 设置输出表格的样式
* @access public
* @param string $style 样式名
* @return void
*/
public function setStyle($style)
{
$this->style = isset($this->format[$style]) ? $style : 'default';
}
/**
* 输出分隔行
* @access public
* @param string $pos 位置
* @return string
*/
protected function renderSeparator($pos)
{
$style = $this->getStyle($pos);
$array = [];
foreach ($this->colWidth as $width) {
$array[] = str_repeat($style[1], $width + 2);
}
return $style[0] . implode($style[2], $array) . $style[3] . PHP_EOL;
}
/**
* 输出表格头部
* @access public
* @return string
*/
protected function renderHeader()
{
$style = $this->getStyle('cell');
$content = $this->renderSeparator('top');
foreach ($this->header as $key => $header) {
$array[] = ' ' . str_pad($header, $this->colWidth[$key], $style[1], $this->headerAlign);
}
if (!empty($array)) {
$content .= $style[0] . implode(' ' . $style[2], $array) . ' ' . $style[3] . PHP_EOL;
if ($this->rows) {
$content .= $this->renderSeparator('middle');
}
}
return $content;
}
protected function getStyle($style)
{
if ($this->format[$this->style]) {
$style = $this->format[$this->style][$style];
} else {
$style = [' ', ' ', ' ', ' '];
}
return $style;
}
/**
* 输出表格
* @access public
* @param array $dataList 表格数据
* @return string
*/
public function render($dataList = [])
{
if ($dataList) {
$this->setRows($dataList);
}
// 输出头部
$content = $this->renderHeader();
$style = $this->getStyle('cell');
if ($this->rows) {
foreach ($this->rows as $row) {
if (is_string($row) && '-' === $row) {
$content .= $this->renderSeparator('middle');
} elseif (is_scalar($row)) {
$content .= $this->renderSeparator('cross-top');
$array = str_pad($row, 3 * (count($this->colWidth) - 1) + array_reduce($this->colWidth, function ($a, $b) {
return $a + $b;
}));
$content .= $style[0] . ' ' . $array . ' ' . $style[3] . PHP_EOL;
$content .= $this->renderSeparator('cross-bottom');
} else {
$array = [];
foreach ($row as $key => $val) {
$array[] = ' ' . str_pad($val, $this->colWidth[$key], ' ', $this->cellAlign);
}
$content .= $style[0] . implode(' ' . $style[2], $array) . ' ' . $style[3] . PHP_EOL;
}
}
}
$content .= $this->renderSeparator('bottom');
return $content;
}
}

View File

@@ -0,0 +1 @@
console 工具使用 hiddeninput.exe 在 windows 上隐藏密码输入,该二进制文件由第三方提供,相关源码和其他细节可以在 [Hidden Input](https://github.com/Seldaek/hidden-input) 找到。

View File

@@ -0,0 +1,59 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\command;
use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
use think\facade\App;
use think\facade\Build as AppBuild;
class Build extends Command
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this->setName('build')
->setDefinition([
new Option('config', null, Option::VALUE_OPTIONAL, "build.php path"),
new Option('module', null, Option::VALUE_OPTIONAL, "module name"),
])
->setDescription('Build Application Dirs');
}
protected function execute(Input $input, Output $output)
{
if ($input->hasOption('module')) {
AppBuild::module($input->getOption('module'));
$output->writeln("Successed");
return;
}
if ($input->hasOption('config')) {
$build = include $input->getOption('config');
} else {
$build = include App::getAppPath() . 'build.php';
}
if (empty($build)) {
$output->writeln("Build Config Is Empty");
return;
}
AppBuild::run($build);
$output->writeln("Successed");
}
}

View File

@@ -0,0 +1,70 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\console\command;
use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
use think\facade\App;
use think\facade\Cache;
class Clear extends Command
{
protected function configure()
{
// 指令配置
$this
->setName('clear')
->addOption('path', 'd', Option::VALUE_OPTIONAL, 'path to clear', null)
->addOption('cache', 'c', Option::VALUE_NONE, 'clear cache file')
->addOption('route', 'u', Option::VALUE_NONE, 'clear route cache')
->addOption('log', 'l', Option::VALUE_NONE, 'clear log file')
->addOption('dir', 'r', Option::VALUE_NONE, 'clear empty dir')
->setDescription('Clear runtime file');
}
protected function execute(Input $input, Output $output)
{
if ($input->getOption('route')) {
Cache::clear('route_cache');
} else {
if ($input->getOption('cache')) {
$path = App::getRuntimePath() . 'cache';
} elseif ($input->getOption('log')) {
$path = App::getRuntimePath() . 'log';
} else {
$path = $input->getOption('path') ?: App::getRuntimePath();
}
$rmdir = $input->getOption('dir') ? true : false;
$this->clear(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR, $rmdir);
}
$output->writeln("<info>Clear Successed</info>");
}
protected function clear($path, $rmdir)
{
$files = is_dir($path) ? scandir($path) : [];
foreach ($files as $file) {
if ('.' != $file && '..' != $file && is_dir($path . $file)) {
array_map('unlink', glob($path . $file . DIRECTORY_SEPARATOR . '*.*'));
if ($rmdir) {
rmdir($path . $file);
}
} elseif ('.gitignore' != $file && is_file($path . $file)) {
unlink($path . $file);
}
}
}
}

View File

@@ -0,0 +1,68 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\command;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument as InputArgument;
use think\console\input\Option as InputOption;
use think\console\Output;
class Help extends Command
{
private $command;
/**
* {@inheritdoc}
*/
protected function configure()
{
$this->ignoreValidationErrors();
$this->setName('help')->setDefinition([
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'),
])->setDescription('Displays help for a command')->setHelp(<<<EOF
The <info>%command.name%</info> command displays help for a given command:
<info>php %command.full_name% list</info>
To display the list of available commands, please use the <info>list</info> command.
EOF
);
}
/**
* Sets the command.
* @param Command $command The command to set
*/
public function setCommand(Command $command)
{
$this->command = $command;
}
/**
* {@inheritdoc}
*/
protected function execute(Input $input, Output $output)
{
if (null === $this->command) {
$this->command = $this->getConsole()->find($input->getArgument('command_name'));
}
$output->describe($this->command, [
'raw_text' => $input->getOption('raw'),
]);
$this->command = null;
}
}

View File

@@ -0,0 +1,73 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\command;
use think\console\Command;
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;
class Lists extends Command
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this->setName('list')->setDefinition($this->createDefinition())->setDescription('Lists commands')->setHelp(<<<EOF
The <info>%command.name%</info> command lists all commands:
<info>php %command.full_name%</info>
You can also display the commands for a specific namespace:
<info>php %command.full_name% test</info>
It's also possible to get raw list of commands (useful for embedding command runner):
<info>php %command.full_name% --raw</info>
EOF
);
}
/**
* {@inheritdoc}
*/
public function getNativeDefinition()
{
return $this->createDefinition();
}
/**
* {@inheritdoc}
*/
protected function execute(Input $input, Output $output)
{
$output->describe($this->getConsole(), [
'raw_text' => $input->getOption('raw'),
'namespace' => $input->getArgument('namespace'),
]);
}
/**
* {@inheritdoc}
*/
private function createDefinition()
{
return new InputDefinition([
new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'),
]);
}
}

View File

@@ -0,0 +1,110 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 刘志淳 <chun@engineer.com>
// +----------------------------------------------------------------------
namespace think\console\command;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\Output;
use think\facade\App;
use think\facade\Config;
use think\facade\Env;
abstract class Make extends Command
{
protected $type;
abstract protected function getStub();
protected function configure()
{
$this->addArgument('name', Argument::REQUIRED, "The name of the class");
}
protected function execute(Input $input, Output $output)
{
$name = trim($input->getArgument('name'));
$classname = $this->getClassName($name);
$pathname = $this->getPathName($classname);
if (is_file($pathname)) {
$output->writeln('<error>' . $this->type . ' already exists!</error>');
return false;
}
if (!is_dir(dirname($pathname))) {
mkdir(dirname($pathname), 0755, true);
}
file_put_contents($pathname, $this->buildClass($classname));
$output->writeln('<info>' . $this->type . ' created successfully.</info>');
}
protected function buildClass($name)
{
$stub = file_get_contents($this->getStub());
$namespace = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\');
$class = str_replace($namespace . '\\', '', $name);
return str_replace(['{%className%}', '{%actionSuffix%}', '{%namespace%}', '{%app_namespace%}'], [
$class,
Config::get('action_suffix'),
$namespace,
App::getNamespace(),
], $stub);
}
protected function getPathName($name)
{
$name = str_replace(App::getNamespace() . '\\', '', $name);
return Env::get('app_path') . ltrim(str_replace('\\', '/', $name), '/') . '.php';
}
protected function getClassName($name)
{
$appNamespace = App::getNamespace();
if (strpos($name, $appNamespace . '\\') !== false) {
return $name;
}
if (Config::get('app_multi_module')) {
if (strpos($name, '/')) {
list($module, $name) = explode('/', $name, 2);
} else {
$module = 'common';
}
} else {
$module = null;
}
if (strpos($name, '/') !== false) {
$name = str_replace('/', '\\', $name);
}
return $this->getNamespace($appNamespace, $module) . '\\' . $name;
}
protected function getNamespace($appNamespace, $module)
{
return $module ? ($appNamespace . '\\' . $module) : $appNamespace;
}
}

View File

@@ -0,0 +1,130 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\command;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
use think\console\Table;
use think\Container;
class RouteList extends Command
{
protected $sortBy = [
'rule' => 0,
'route' => 1,
'method' => 2,
'name' => 3,
'domain' => 4,
];
protected function configure()
{
$this->setName('route:list')
->addArgument('style', Argument::OPTIONAL, "the style of the table.", 'default')
->addOption('sort', 's', Option::VALUE_OPTIONAL, 'order by rule name.', 0)
->addOption('more', 'm', Option::VALUE_NONE, 'show route options.')
->setDescription('show route list.');
}
protected function execute(Input $input, Output $output)
{
$filename = Container::get('app')->getRuntimePath() . 'route_list.php';
if (is_file($filename)) {
unlink($filename);
}
$content = $this->getRouteList();
file_put_contents($filename, 'Route List' . PHP_EOL . $content);
}
protected function getRouteList()
{
Container::get('route')->setTestMode(true);
// 路由检测
$path = Container::get('app')->getRoutePath();
$files = is_dir($path) ? scandir($path) : [];
foreach ($files as $file) {
if (strpos($file, '.php')) {
$filename = $path . DIRECTORY_SEPARATOR . $file;
// 导入路由配置
$rules = include $filename;
if (is_array($rules)) {
Container::get('route')->import($rules);
}
}
}
if (Container::get('config')->get('route_annotation')) {
$suffix = Container::get('config')->get('controller_suffix') || Container::get('config')->get('class_suffix');
include Container::get('build')->buildRoute($suffix);
}
$table = new Table();
if ($this->input->hasOption('more')) {
$header = ['Rule', 'Route', 'Method', 'Name', 'Domain', 'Option', 'Pattern'];
} else {
$header = ['Rule', 'Route', 'Method', 'Name', 'Domain'];
}
$table->setHeader($header);
$routeList = Container::get('route')->getRuleList();
$rows = [];
foreach ($routeList as $domain => $items) {
foreach ($items as $item) {
$item['route'] = $item['route'] instanceof \Closure ? '<Closure>' : $item['route'];
if ($this->input->hasOption('more')) {
$item = [$item['rule'], $item['route'], $item['method'], $item['name'], $domain, json_encode($item['option']), json_encode($item['pattern'])];
} else {
$item = [$item['rule'], $item['route'], $item['method'], $item['name'], $domain];
}
$rows[] = $item;
}
}
if ($this->input->getOption('sort')) {
$sort = $this->input->getOption('sort');
if (isset($this->sortBy[$sort])) {
$sort = $this->sortBy[$sort];
}
uasort($rows, function ($a, $b) use ($sort) {
$itemA = isset($a[$sort]) ? $a[$sort] : null;
$itemB = isset($b[$sort]) ? $b[$sort] : null;
return strcasecmp($itemA, $itemB);
});
}
$table->setRows($rows);
if ($this->input->getArgument('style')) {
$style = $this->input->getArgument('style');
$table->setStyle($style);
}
return $this->table($table);
}
}

View File

@@ -0,0 +1,53 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Slince <taosikai@yeah.net>
// +----------------------------------------------------------------------
namespace think\console\command;
use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
use think\facade\App;
class RunServer extends Command
{
public function configure()
{
$this->setName('run')
->addOption('host', 'H', Option::VALUE_OPTIONAL,
'The host to server the application on', '127.0.0.1')
->addOption('port', 'p', Option::VALUE_OPTIONAL,
'The port to server the application on', 8000)
->addOption('root', 'r', Option::VALUE_OPTIONAL,
'The document root of the application', App::getRootPath() . 'public')
->setDescription('PHP Built-in Server for ThinkPHP');
}
public function execute(Input $input, Output $output)
{
$host = $input->getOption('host');
$port = $input->getOption('port');
$root = $input->getOption('root');
$command = sprintf(
'php -S %s:%d -t %s %s',
$host,
$port,
escapeshellarg($root),
escapeshellarg($root . DIRECTORY_SEPARATOR . 'router.php')
);
$output->writeln(sprintf('ThinkPHP Development server is started On <http://%s:%s/>', $host, $port));
$output->writeln(sprintf('You can exit with <info>`CTRL-C`</info>'));
$output->writeln(sprintf('Document root is: %s', $root));
passthru($command);
}
}

View File

@@ -0,0 +1,31 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\console\command;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\facade\App;
class Version extends Command
{
protected function configure()
{
// 指令配置
$this->setName('version')
->setDescription('show thinkphp framework version');
}
protected function execute(Input $input, Output $output)
{
$output->writeln('v' . App::version());
}
}

View File

@@ -0,0 +1,56 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 刘志淳 <chun@engineer.com>
// +----------------------------------------------------------------------
namespace think\console\command\make;
use think\console\command\Make;
use think\console\input\Argument;
use think\facade\App;
class Command extends Make
{
protected $type = "Command";
protected function configure()
{
parent::configure();
$this->setName('make:command')
->addArgument('commandName', Argument::OPTIONAL, "The name of the command")
->setDescription('Create a new command class');
}
protected function buildClass($name)
{
$commandName = $this->input->getArgument('commandName') ?: strtolower(basename($name));
$namespace = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\');
$class = str_replace($namespace . '\\', '', $name);
$stub = file_get_contents($this->getStub());
return str_replace(['{%commandName%}', '{%className%}', '{%namespace%}', '{%app_namespace%}'], [
$commandName,
$class,
$namespace,
App::getNamespace(),
], $stub);
}
protected function getStub()
{
return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'command.stub';
}
protected function getNamespace($appNamespace, $module)
{
return $appNamespace . '\\command';
}
}

View File

@@ -0,0 +1,56 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 刘志淳 <chun@engineer.com>
// +----------------------------------------------------------------------
namespace think\console\command\make;
use think\console\command\Make;
use think\console\input\Option;
use think\facade\Config;
class Controller extends Make
{
protected $type = "Controller";
protected function configure()
{
parent::configure();
$this->setName('make:controller')
->addOption('api', null, Option::VALUE_NONE, 'Generate an api controller class.')
->addOption('plain', null, Option::VALUE_NONE, 'Generate an empty controller class.')
->setDescription('Create a new resource controller class');
}
protected function getStub()
{
$stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR;
if ($this->input->getOption('api')) {
return $stubPath . 'controller.api.stub';
}
if ($this->input->getOption('plain')) {
return $stubPath . 'controller.plain.stub';
}
return $stubPath . 'controller.stub';
}
protected function getClassName($name)
{
return parent::getClassName($name) . (Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : '');
}
protected function getNamespace($appNamespace, $module)
{
return parent::getNamespace($appNamespace, $module) . '\controller';
}
}

View File

@@ -0,0 +1,36 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 刘志淳 <chun@engineer.com>
// +----------------------------------------------------------------------
namespace think\console\command\make;
use think\console\command\Make;
class Middleware extends Make
{
protected $type = "Middleware";
protected function configure()
{
parent::configure();
$this->setName('make:middleware')
->setDescription('Create a new middleware class');
}
protected function getStub()
{
return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'middleware.stub';
}
protected function getNamespace($appNamespace, $module)
{
return parent::getNamespace($appNamespace, 'http') . '\middleware';
}
}

View File

@@ -0,0 +1,36 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 刘志淳 <chun@engineer.com>
// +----------------------------------------------------------------------
namespace think\console\command\make;
use think\console\command\Make;
class Model extends Make
{
protected $type = "Model";
protected function configure()
{
parent::configure();
$this->setName('make:model')
->setDescription('Create a new model class');
}
protected function getStub()
{
return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'model.stub';
}
protected function getNamespace($appNamespace, $module)
{
return parent::getNamespace($appNamespace, $module) . '\model';
}
}

View File

@@ -0,0 +1,39 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 刘志淳 <chun@engineer.com>
// +----------------------------------------------------------------------
namespace think\console\command\make;
use think\console\command\Make;
class Validate extends Make
{
protected $type = "Validate";
protected function configure()
{
parent::configure();
$this->setName('make:validate')
->setDescription('Create a validate class');
}
protected function getStub()
{
$stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR;
return $stubPath . 'validate.stub';
}
protected function getNamespace($appNamespace, $module)
{
return parent::getNamespace($appNamespace, $module) . '\validate';
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace {%namespace%};
use think\console\Command;
use think\console\Input;
use think\console\Output;
class {%className%} extends Command
{
protected function configure()
{
// 指令配置
$this->setName('{%commandName%}');
// 设置参数
}
protected function execute(Input $input, Output $output)
{
// 指令输出
$output->writeln('{%commandName%}');
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace {%namespace%};
use think\Controller;
use think\Request;
class {%className%} extends Controller
{
/**
* 显示资源列表
*
* @return \think\Response
*/
public function index{%actionSuffix%}()
{
//
}
/**
* 保存新建的资源
*
* @param \think\Request $request
* @return \think\Response
*/
public function save{%actionSuffix%}(Request $request)
{
//
}
/**
* 显示指定的资源
*
* @param int $id
* @return \think\Response
*/
public function read{%actionSuffix%}($id)
{
//
}
/**
* 保存更新的资源
*
* @param \think\Request $request
* @param int $id
* @return \think\Response
*/
public function update{%actionSuffix%}(Request $request, $id)
{
//
}
/**
* 删除指定资源
*
* @param int $id
* @return \think\Response
*/
public function delete{%actionSuffix%}($id)
{
//
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace {%namespace%};
use think\Controller;
class {%className%} extends Controller
{
//
}

View File

@@ -0,0 +1,85 @@
<?php
namespace {%namespace%};
use think\Controller;
use think\Request;
class {%className%} extends Controller
{
/**
* 显示资源列表
*
* @return \think\Response
*/
public function index{%actionSuffix%}()
{
//
}
/**
* 显示创建资源表单页.
*
* @return \think\Response
*/
public function create{%actionSuffix%}()
{
//
}
/**
* 保存新建的资源
*
* @param \think\Request $request
* @return \think\Response
*/
public function save{%actionSuffix%}(Request $request)
{
//
}
/**
* 显示指定的资源
*
* @param int $id
* @return \think\Response
*/
public function read{%actionSuffix%}($id)
{
//
}
/**
* 显示编辑资源表单页.
*
* @param int $id
* @return \think\Response
*/
public function edit{%actionSuffix%}($id)
{
//
}
/**
* 保存更新的资源
*
* @param \think\Request $request
* @param int $id
* @return \think\Response
*/
public function update{%actionSuffix%}(Request $request, $id)
{
//
}
/**
* 删除指定资源
*
* @param int $id
* @return \think\Response
*/
public function delete{%actionSuffix%}($id)
{
//
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace {%namespace%};
class {%className%}
{
public function handle($request, \Closure $next)
{
}
}

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