Merge branch 'develop' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into develop
This commit is contained in:
@@ -7,6 +7,7 @@ import { Card } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import { AlertCircle } from "lucide-react";
|
||||
import { toast } from "@/components/ui/use-toast";
|
||||
|
||||
interface ContactData {
|
||||
mobile: number;
|
||||
|
||||
@@ -134,13 +134,9 @@ export default function DeviceDetailPage() {
|
||||
} else if (serverData.taskConfig) {
|
||||
try {
|
||||
// 解析taskConfig字段
|
||||
let taskConfig = serverData.taskConfig
|
||||
if (typeof taskConfig === 'string') {
|
||||
taskConfig = JSON.parse(taskConfig)
|
||||
}
|
||||
const taskConfig = JSON.parse(serverData.taskConfig || '{}');
|
||||
|
||||
if (taskConfig) {
|
||||
console.log('解析的taskConfig:', taskConfig);
|
||||
formattedDevice.features = {
|
||||
autoAddFriend: Boolean(taskConfig.autoAddFriend),
|
||||
autoReply: Boolean(taskConfig.autoReply),
|
||||
|
||||
@@ -171,30 +171,37 @@ export default function DevicesPage() {
|
||||
setIsLoadingQRCode(true)
|
||||
setQrCodeImage("") // 清空当前二维码
|
||||
|
||||
console.log("正在请求二维码...");
|
||||
// 获取保存的accountId
|
||||
const accountId = localStorage.getItem('s2_accountId')
|
||||
if (!accountId) {
|
||||
toast({
|
||||
title: "获取二维码失败",
|
||||
description: "未获取到用户信息,请重新登录",
|
||||
variant: "destructive",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 发起请求获取二维码 - 直接使用fetch避免api工具添加基础URL
|
||||
const response = await fetch('http://yi.54word.com/v1/api/device/add', {
|
||||
const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/api/device/add`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({})
|
||||
body: JSON.stringify({
|
||||
accountId: accountId
|
||||
})
|
||||
})
|
||||
|
||||
console.log("二维码请求响应状态:", response.status);
|
||||
|
||||
// 保存原始响应文本以便调试
|
||||
const responseText = await response.text();
|
||||
console.log("原始响应内容:", responseText);
|
||||
|
||||
// 尝试将响应解析为JSON
|
||||
let result;
|
||||
try {
|
||||
result = JSON.parse(responseText);
|
||||
} catch (e) {
|
||||
console.error("响应不是有效的JSON:", e);
|
||||
toast({
|
||||
title: "获取二维码失败",
|
||||
description: "服务器返回的数据格式无效",
|
||||
@@ -203,25 +210,19 @@ export default function DevicesPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("二维码响应数据:", result);
|
||||
|
||||
if (result && result.code === 200) {
|
||||
// 尝试多种可能的返回数据结构
|
||||
let qrcodeData = null;
|
||||
|
||||
if (result.data?.qrCode) {
|
||||
qrcodeData = result.data.qrCode;
|
||||
console.log("找到二维码数据在 result.data.qrCode");
|
||||
} else if (result.data?.qrcode) {
|
||||
qrcodeData = result.data.qrcode;
|
||||
console.log("找到二维码数据在 result.data.qrcode");
|
||||
} else if (result.data?.image) {
|
||||
qrcodeData = result.data.image;
|
||||
console.log("找到二维码数据在 result.data.image");
|
||||
} else if (result.data?.url) {
|
||||
// 如果返回的是URL而不是base64
|
||||
qrcodeData = result.data.url;
|
||||
console.log("找到二维码URL在 result.data.url");
|
||||
setQrCodeImage(qrcodeData);
|
||||
|
||||
toast({
|
||||
@@ -233,9 +234,7 @@ export default function DevicesPage() {
|
||||
} else if (typeof result.data === 'string') {
|
||||
// 如果data直接是字符串
|
||||
qrcodeData = result.data;
|
||||
console.log("二维码数据直接在 result.data 字符串中");
|
||||
} else {
|
||||
console.error("无法找到二维码数据:", result);
|
||||
toast({
|
||||
title: "获取二维码失败",
|
||||
description: "返回数据格式不正确",
|
||||
@@ -246,7 +245,6 @@ export default function DevicesPage() {
|
||||
|
||||
// 检查数据是否为空
|
||||
if (!qrcodeData) {
|
||||
console.error("二维码数据为空");
|
||||
toast({
|
||||
title: "获取二维码失败",
|
||||
description: "服务器返回的二维码数据为空",
|
||||
@@ -255,16 +253,12 @@ export default function DevicesPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("处理前的二维码数据:", qrcodeData);
|
||||
|
||||
// 检查是否已经是完整的data URL
|
||||
if (qrcodeData.startsWith('data:image')) {
|
||||
console.log("数据已包含data:image前缀");
|
||||
setQrCodeImage(qrcodeData);
|
||||
}
|
||||
// 检查是否是URL
|
||||
else if (qrcodeData.startsWith('http')) {
|
||||
console.log("数据是HTTP URL");
|
||||
setQrCodeImage(qrcodeData);
|
||||
}
|
||||
// 尝试作为base64处理
|
||||
@@ -272,7 +266,6 @@ export default function DevicesPage() {
|
||||
try {
|
||||
// 确保base64字符串没有空格等干扰字符
|
||||
const cleanedBase64 = qrcodeData.trim();
|
||||
console.log("处理后的base64数据:", cleanedBase64.substring(0, 30) + "...");
|
||||
|
||||
// 直接以图片src格式设置
|
||||
setQrCodeImage(`data:image/png;base64,${cleanedBase64}`);
|
||||
@@ -280,10 +273,9 @@ export default function DevicesPage() {
|
||||
// 预加载图片,确认是否有效
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
console.log("二维码图片加载成功");
|
||||
// 图片加载成功
|
||||
};
|
||||
img.onerror = (e) => {
|
||||
console.error("二维码图片加载失败:", e);
|
||||
toast({
|
||||
title: "二维码加载失败",
|
||||
description: "服务器返回的数据无法显示为图片",
|
||||
@@ -292,7 +284,6 @@ export default function DevicesPage() {
|
||||
};
|
||||
img.src = `data:image/png;base64,${cleanedBase64}`;
|
||||
} catch (e) {
|
||||
console.error("处理base64数据出错:", e);
|
||||
toast({
|
||||
title: "获取二维码失败",
|
||||
description: "图片数据处理失败",
|
||||
@@ -307,7 +298,6 @@ export default function DevicesPage() {
|
||||
description: "请使用手机扫描新的二维码添加设备",
|
||||
});
|
||||
} else {
|
||||
console.error("获取二维码失败:", result);
|
||||
toast({
|
||||
title: "获取二维码失败",
|
||||
description: result?.msg || "请稍后重试",
|
||||
@@ -315,7 +305,6 @@ export default function DevicesPage() {
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取二维码失败", error);
|
||||
toast({
|
||||
title: "获取二维码失败",
|
||||
description: "请检查网络连接后重试",
|
||||
@@ -347,7 +336,6 @@ export default function DevicesPage() {
|
||||
|
||||
try {
|
||||
setIsSubmittingImei(true);
|
||||
console.log("正在添加设备,IMEI:", deviceImei, "设备名称:", deviceName);
|
||||
|
||||
// 使用api.post发送请求到/v1/devices
|
||||
const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/devices`, {
|
||||
@@ -363,18 +351,14 @@ export default function DevicesPage() {
|
||||
})
|
||||
});
|
||||
|
||||
console.log("添加设备响应状态:", response.status);
|
||||
|
||||
// 保存原始响应文本以便调试
|
||||
const responseText = await response.text();
|
||||
console.log("原始响应内容:", responseText);
|
||||
|
||||
// 尝试将响应解析为JSON
|
||||
let result;
|
||||
try {
|
||||
result = JSON.parse(responseText);
|
||||
} catch (e) {
|
||||
console.error("响应不是有效的JSON:", e);
|
||||
toast({
|
||||
title: "添加设备失败",
|
||||
description: "服务器返回的数据格式无效",
|
||||
@@ -383,8 +367,6 @@ export default function DevicesPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("添加设备响应:", result);
|
||||
|
||||
if (result && result.code === 200) {
|
||||
toast({
|
||||
title: "设备添加成功",
|
||||
@@ -399,7 +381,6 @@ export default function DevicesPage() {
|
||||
// 刷新设备列表
|
||||
loadDevices(1, true);
|
||||
} else {
|
||||
console.error("添加设备失败:", result);
|
||||
toast({
|
||||
title: "添加设备失败",
|
||||
description: result?.msg || "请检查设备信息是否正确",
|
||||
@@ -407,7 +388,6 @@ export default function DevicesPage() {
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("添加设备请求失败:", error);
|
||||
toast({
|
||||
title: "请求失败",
|
||||
description: "网络错误,请稍后重试",
|
||||
@@ -499,6 +479,43 @@ export default function DevicesPage() {
|
||||
router.push(`/devices/${deviceId}`);
|
||||
}
|
||||
|
||||
// 处理添加设备
|
||||
const handleAddDevice = async () => {
|
||||
try {
|
||||
const s2_accountId = localStorage.getItem('s2_accountId');
|
||||
if (!s2_accountId) {
|
||||
toast.error('未获取到用户信息,请重新登录');
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch('/api/devices', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
imei: deviceImei,
|
||||
memo: deviceName,
|
||||
s2_accountId: s2_accountId,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.code === 200) {
|
||||
toast.success('添加设备成功');
|
||||
setIsAddDeviceOpen(false);
|
||||
// 刷新设备列表
|
||||
loadDevices(1, true);
|
||||
} else {
|
||||
toast.error(data.msg || '添加设备失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('添加设备失败:', error);
|
||||
toast.error('添加设备失败,请稍后重试');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex-1 bg-gray-50 min-h-screen">
|
||||
<header className="sticky top-0 z-10 bg-white border-b">
|
||||
@@ -642,7 +659,7 @@ export default function DevicesPage() {
|
||||
</DialogHeader>
|
||||
|
||||
<Tabs defaultValue="scan" value={activeTab} onValueChange={setActiveTab} className="mt-4">
|
||||
<TabsList className="grid grid-cols-2 w-full">
|
||||
{/* <TabsList className="grid grid-cols-2 w-full">
|
||||
<TabsTrigger value="scan" className="flex items-center">
|
||||
<QrCode className="h-4 w-4 mr-2" />
|
||||
扫码添加
|
||||
@@ -651,7 +668,7 @@ export default function DevicesPage() {
|
||||
<Smartphone className="h-4 w-4 mr-2" />
|
||||
手动添加
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</TabsList> */}
|
||||
|
||||
<TabsContent value="scan" className="space-y-4 py-4">
|
||||
<div className="flex flex-col items-center justify-center p-6 space-y-4">
|
||||
@@ -748,15 +765,10 @@ export default function DevicesPage() {
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleAddDeviceByImei}
|
||||
disabled={isSubmittingImei || !deviceImei.trim()}
|
||||
onClick={handleAddDevice}
|
||||
disabled={!deviceImei.trim() || !deviceName.trim()}
|
||||
>
|
||||
{isSubmittingImei ? (
|
||||
<>
|
||||
<div className="w-4 h-4 mr-2 rounded-full border-2 border-white border-t-transparent animate-spin"></div>
|
||||
提交中...
|
||||
</>
|
||||
) : "添加"}
|
||||
添加
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -98,17 +98,14 @@ export default function LoginPage() {
|
||||
const response = await loginApi.login(form.phone, form.password)
|
||||
|
||||
if (response.code === 200 && response.data) {
|
||||
// 获取用户信息和token
|
||||
const { token, token_expired, member } = response.data
|
||||
// 保存登录信息
|
||||
localStorage.setItem('token', response.data.token)
|
||||
localStorage.setItem('token_expired', response.data.token_expired)
|
||||
localStorage.setItem('s2_accountId', response.data.member.s2_accountId)
|
||||
|
||||
// 保存用户信息
|
||||
localStorage.setItem('userInfo', JSON.stringify(response.data.member))
|
||||
|
||||
// 保存token和用户信息
|
||||
login(token, {
|
||||
id: member.id,
|
||||
username: member.username || member.account || '',
|
||||
account: member.account,
|
||||
avatar: member.avatar
|
||||
})
|
||||
|
||||
// 显示成功提示
|
||||
toast({
|
||||
title: "登录成功",
|
||||
|
||||
@@ -20,18 +20,23 @@ const menuItems = [
|
||||
|
||||
export default function ProfilePage() {
|
||||
const router = useRouter()
|
||||
const { isAuthenticated, user, logout } = useAuth()
|
||||
const [showLogoutDialog, setShowLogoutDialog] = useState(false)
|
||||
const [userInfo, setUserInfo] = useState<any>(null)
|
||||
|
||||
// 处理身份验证状态,将路由重定向逻辑移至useEffect
|
||||
// 从localStorage获取用户信息
|
||||
useEffect(() => {
|
||||
if (!isAuthenticated) {
|
||||
router.push("/login")
|
||||
const userInfoStr = localStorage.getItem('userInfo')
|
||||
if (userInfoStr) {
|
||||
setUserInfo(JSON.parse(userInfoStr))
|
||||
}
|
||||
}, [isAuthenticated, router])
|
||||
}, [])
|
||||
|
||||
const handleLogout = () => {
|
||||
logout() // 使用AuthProvider中的logout方法删除本地保存的用户信息
|
||||
// 清除本地存储的用户信息
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('token_expired')
|
||||
localStorage.removeItem('s2_accountId')
|
||||
localStorage.removeItem('userInfo')
|
||||
setShowLogoutDialog(false)
|
||||
router.push("/login")
|
||||
}
|
||||
@@ -57,14 +62,14 @@ export default function ProfilePage() {
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center space-x-4">
|
||||
<Avatar className="w-20 h-20">
|
||||
<AvatarImage src={user?.avatar || ""} />
|
||||
<AvatarFallback>{user?.username ? user.username.slice(0, 2) : "用户"}</AvatarFallback>
|
||||
<AvatarImage src={userInfo?.avatar || ""} />
|
||||
<AvatarFallback>{userInfo?.username ? userInfo.username.slice(0, 2) : "用户"}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex-1">
|
||||
<h2 className="text-xl font-semibold text-blue-600">{user?.username || "用户"}</h2>
|
||||
<h2 className="text-xl font-semibold text-blue-600">{userInfo?.username || "用户"}</h2>
|
||||
<p className="text-gray-500">
|
||||
账号: <ClientOnly fallback="加载中...">
|
||||
{user?.account || Math.floor(10000000 + Math.random() * 90000000).toString()}
|
||||
{userInfo?.account || Math.floor(10000000 + Math.random() * 90000000).toString()}
|
||||
</ClientOnly>
|
||||
</p>
|
||||
<div className="mt-2">
|
||||
|
||||
@@ -9,6 +9,7 @@ import { BasicSettings } from "../components/basic-settings"
|
||||
import { GroupSelector } from "../components/group-selector"
|
||||
import { ContentSelector } from "../components/content-selector"
|
||||
import type { WechatGroup, ContentLibrary } from "@/types/group-sync"
|
||||
import { toast } from "@/components/ui/use-toast"
|
||||
|
||||
const steps = [
|
||||
{ id: 1, title: "步骤 1", subtitle: "基础设置" },
|
||||
@@ -46,11 +47,39 @@ export default function NewGroupSyncPage() {
|
||||
setFormData((prev) => ({ ...prev, contentLibraries }))
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
// 这里可以添加保存逻辑,例如API调用
|
||||
console.log("保存表单数据:", formData)
|
||||
router.push("/workspace/group-sync")
|
||||
}
|
||||
const handleSubmit = async (formData: any) => {
|
||||
try {
|
||||
const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/api/group-sync`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
},
|
||||
body: JSON.stringify(formData)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.code === 200) {
|
||||
toast({
|
||||
title: "创建成功",
|
||||
description: "群同步计划已创建",
|
||||
});
|
||||
router.push('/workspace/group-sync');
|
||||
} else {
|
||||
toast({
|
||||
title: "创建失败",
|
||||
description: data.msg || "请稍后重试",
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "创建失败",
|
||||
description: "网络错误,请稍后重试",
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
router.push("/workspace/group-sync")
|
||||
@@ -81,7 +110,7 @@ export default function NewGroupSyncPage() {
|
||||
isEnabled: formData.isEnabled,
|
||||
}}
|
||||
onNext={handleBasicSettingsNext}
|
||||
onSave={handleSave}
|
||||
onSave={handleSubmit}
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
)}
|
||||
@@ -92,7 +121,7 @@ export default function NewGroupSyncPage() {
|
||||
onGroupsChange={handleGroupsChange}
|
||||
onPrevious={() => setCurrentStep(1)}
|
||||
onNext={() => setCurrentStep(3)}
|
||||
onSave={handleSave}
|
||||
onSave={handleSubmit}
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
)}
|
||||
@@ -103,7 +132,7 @@ export default function NewGroupSyncPage() {
|
||||
onLibrariesChange={handleLibrariesChange}
|
||||
onPrevious={() => setCurrentStep(2)}
|
||||
onNext={() => setCurrentStep(4)}
|
||||
onSave={handleSave}
|
||||
onSave={handleSubmit}
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
)}
|
||||
@@ -118,7 +147,7 @@ export default function NewGroupSyncPage() {
|
||||
<Button type="button" variant="outline" onClick={() => setCurrentStep(3)}>
|
||||
上一步
|
||||
</Button>
|
||||
<Button type="button" onClick={handleSave}>
|
||||
<Button type="button" onClick={handleSubmit}>
|
||||
完成
|
||||
</Button>
|
||||
<Button type="button" variant="outline" onClick={handleCancel}>
|
||||
|
||||
@@ -15,6 +15,7 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
|
||||
import { Slider } from "@/components/ui/slider"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { TrafficPoolSelector } from "@/app/components/traffic-pool-selector"
|
||||
import { toast } from "@/components/ui/use-toast"
|
||||
|
||||
// 模拟数据
|
||||
const planDetails = {
|
||||
@@ -123,11 +124,39 @@ export default function EditTrafficDistributionPage({ params }: { params: { id:
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
// 这里处理表单提交逻辑
|
||||
console.log("提交表单数据:", formData)
|
||||
router.push(`/workspace/traffic-distribution/${params.id}`)
|
||||
}
|
||||
const handleSubmit = async (formData: any) => {
|
||||
try {
|
||||
const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/api/traffic-distribution/${params.id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
},
|
||||
body: JSON.stringify(formData)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.code === 200) {
|
||||
toast({
|
||||
title: "保存成功",
|
||||
description: "流量分配计划已更新",
|
||||
});
|
||||
router.push('/workspace/traffic-distribution');
|
||||
} else {
|
||||
toast({
|
||||
title: "保存失败",
|
||||
description: data.msg || "请稍后重试",
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "保存失败",
|
||||
description: "网络错误,请稍后重试",
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const isStep1Valid = formData.name && formData.source
|
||||
const isStep2Valid = formData.targetGroups.length > 0 || formData.targetDevices.length > 0
|
||||
@@ -463,7 +492,7 @@ export default function EditTrafficDistributionPage({ params }: { params: { id:
|
||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||
上一步
|
||||
</Button>
|
||||
<Button onClick={handleSubmit} disabled={!isStep3Valid}>
|
||||
<Button onClick={() => handleSubmit(formData)} disabled={!isStep3Valid}>
|
||||
保存修改
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -6,17 +6,17 @@ use think\facade\Route;
|
||||
// 定义RESTful风格的API路由 - 认证相关
|
||||
Route::group('v1/auth', function () {
|
||||
// 无需认证的接口
|
||||
Route::post('login', 'app\\common\\controller\\Auth@login'); // 账号密码登录
|
||||
Route::post('mobile-login', 'app\\common\\controller\\Auth@mobileLogin'); // 手机号验证码登录
|
||||
Route::post('code', 'app\\common\\controller\\Auth@sendCode'); // 发送验证码
|
||||
Route::post('login', 'app\common\controller\PasswordLoginController@index'); // 账号密码登录
|
||||
Route::post('mobile-login', 'app\common\controller\Auth@mobileLogin'); // 手机号验证码登录
|
||||
Route::post('code', 'app\common\controller\Auth@SendCodeController'); // 发送验证码
|
||||
|
||||
// 需要JWT认证的接口
|
||||
Route::get('info', 'app\\common\\controller\\Auth@info')->middleware(['jwt']); // 获取用户信息
|
||||
Route::post('refresh', 'app\\common\\controller\\Auth@refresh')->middleware(['jwt']); // 刷新令牌
|
||||
Route::get('info', 'app\common\controller\Auth@info')->middleware(['jwt']); // 获取用户信息
|
||||
Route::post('refresh', 'app\common\controller\Auth@refresh')->middleware(['jwt']); // 刷新令牌
|
||||
});
|
||||
|
||||
// 附件上传相关路由
|
||||
Route::group('v1/', function () {
|
||||
Route::post('attachment/upload', 'app\\common\\controller\\Attachment@upload'); // 上传附件
|
||||
Route::get('attachment/:id', 'app\\common\\controller\\Attachment@info'); // 获取附件信息
|
||||
Route::post('attachment/upload', 'app\common\controller\Attachment@upload'); // 上传附件
|
||||
Route::get('attachment/:id', 'app\common\controller\Attachment@info'); // 获取附件信息
|
||||
})->middleware(['jwt']);
|
||||
12
Server/application/common/controller/BaseController.php
Normal file
12
Server/application/common/controller/BaseController.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use think\Controller;
|
||||
|
||||
/**
|
||||
* 基础控制器
|
||||
*/
|
||||
class BaseController extends Controller
|
||||
{
|
||||
}
|
||||
136
Server/application/common/controller/PasswordLoginController.php
Normal file
136
Server/application/common/controller/PasswordLoginController.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\model\User as UserModel;
|
||||
use app\common\util\JwtUtil;
|
||||
use Exception;
|
||||
use library\ResponseHelper;
|
||||
use think\Validate;
|
||||
|
||||
/**
|
||||
* 认证控制器
|
||||
* 处理用户登录和身份验证
|
||||
*/
|
||||
class PasswordLoginController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取用户基本信息
|
||||
*
|
||||
* @param string $account
|
||||
* @param int $typeId
|
||||
* @return UserModel
|
||||
*/
|
||||
protected function getUserProfileWithAccountAndType(string $account, int $typeId): UserModel
|
||||
{
|
||||
$user = UserModel::where(
|
||||
function ($query) use ($account) {
|
||||
$query->where('phone', $account)->whereOr('account', $account);
|
||||
}
|
||||
)
|
||||
->where(
|
||||
function ($query) use ($typeId) {
|
||||
$query->where('status', 1)->where('typeId', $typeId);
|
||||
}
|
||||
)->find();
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*
|
||||
* @param string $account 账号(手机号)
|
||||
* @param string $password 密码(可能是加密后的)
|
||||
* @param int $typeId 身份信息
|
||||
* @return array|null
|
||||
*/
|
||||
protected function getUser(string $account, string $password, int $typeId): array
|
||||
{
|
||||
$user = $this->getUserProfileWithAccountAndType($account, $typeId);
|
||||
|
||||
if (!$user) {
|
||||
throw new \Exception('用户不存在或已禁用', 403);
|
||||
}
|
||||
|
||||
if ($user->passwordMd5 !== md5($password)) {
|
||||
throw new \Exception('账号或密码错误', 403);
|
||||
}
|
||||
|
||||
return array_merge($user->toArray(), [
|
||||
'lastLoginIp' => $this->request->ip(),
|
||||
'lastLoginTime' => time()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据验证
|
||||
*
|
||||
* @param array $params
|
||||
* @return $this
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function dataValidate(array $params): self
|
||||
{
|
||||
$validate = Validate::make([
|
||||
'account' => 'require',
|
||||
'password' => 'require|length:6,64',
|
||||
'typeId' => 'require|in:1,2',
|
||||
], [
|
||||
'account.require' => '账号不能为空',
|
||||
'password.require' => '密码不能为空',
|
||||
'password.length' => '密码长度必须在6-64个字符之间',
|
||||
'typeId.require' => '用户类型不能为空',
|
||||
'typeId.in' => '用户类型错误',
|
||||
]);
|
||||
|
||||
if (!$validate->check($params)) {
|
||||
throw new \Exception($validate->getError(), 400);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*
|
||||
* @param string $account 账号(手机号)
|
||||
* @param string $password 密码(可能是加密后的)
|
||||
* @param string $typeId 登录IP
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function doLogin(string $account, string $password, int $typeId): array
|
||||
{
|
||||
// 获取用户信息
|
||||
$member = $this->getUser($account, $password, $typeId);
|
||||
|
||||
// 生成JWT令牌
|
||||
$token = JwtUtil::createToken($member, 86400);
|
||||
$token_expired = time() + 86400;
|
||||
|
||||
return compact('member', 'token', 'token_expired');
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$params = $this->request->only(['account', 'password', 'typeId']);
|
||||
|
||||
try {
|
||||
$result = $this->dataValidate($params)->doLogin(
|
||||
$params['account'],
|
||||
$params['password'],
|
||||
$params['typeId']
|
||||
);
|
||||
|
||||
return ResponseHelper::success($result, '登录成功');
|
||||
} catch (Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
39
Server/application/common/controller/SendCodeController.php
Normal file
39
Server/application/common/controller/SendCodeController.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use library\ResponseHelper;
|
||||
use think\facade\Request;
|
||||
|
||||
/**
|
||||
* 认证控制器
|
||||
* 处理用户登录和身份验证
|
||||
*/
|
||||
class SendCodeController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 发送验证码
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$params = $this->request->only(['account', 'type']);
|
||||
|
||||
// 参数验证
|
||||
$validate = validate('common/Auth');
|
||||
if (!$validate->scene('send_code')->check($params)) {
|
||||
return ResponseHelper::error($validate->getError());
|
||||
}
|
||||
|
||||
try {
|
||||
// 调用发送验证码服务
|
||||
$result = $this->authService->sendLoginCode(
|
||||
$params['account'],
|
||||
$params['type']
|
||||
);
|
||||
return ResponseHelper::success($result, '验证码发送成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use think\Model;
|
||||
@@ -7,84 +8,23 @@ use think\model\concern\SoftDelete;
|
||||
class User extends Model
|
||||
{
|
||||
use SoftDelete;
|
||||
|
||||
|
||||
/**
|
||||
* 数据表名
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'ck_users';
|
||||
protected $name = 'users';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = 'updateTime';
|
||||
protected $defaultSoftDelete = 0;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
* @var string
|
||||
*/
|
||||
protected $pk = 'id';
|
||||
|
||||
/**
|
||||
* 软删除字段
|
||||
* @var string
|
||||
*/
|
||||
protected $deleteTime = 'deleteTime';
|
||||
protected $defaultSoftDelete = 0;
|
||||
|
||||
/**
|
||||
* 隐藏属性
|
||||
* @var array
|
||||
*/
|
||||
protected $hidden = ['passwordMd5', 'passwordLocal', 'deleteTime'];
|
||||
|
||||
/**
|
||||
* 字段类型
|
||||
* @var array
|
||||
*/
|
||||
protected $type = [
|
||||
'id' => 'integer',
|
||||
'isAdmin' => 'integer',
|
||||
'companyId' => 'integer',
|
||||
'typeId' => 'integer',
|
||||
'lastLoginTime' => 'integer',
|
||||
'status' => 'integer',
|
||||
'createTime' => 'integer',
|
||||
'updateTime' => 'integer',
|
||||
'deleteTime' => 'integer'
|
||||
];
|
||||
|
||||
/**
|
||||
* 通过手机号获取用户信息
|
||||
* @param string $account 手机号
|
||||
* @return array|null
|
||||
*/
|
||||
public static function getUserByMobile($account)
|
||||
{
|
||||
// 查询用户
|
||||
$user = self::where('account', $account)
|
||||
->where('status', 1)
|
||||
->find();
|
||||
|
||||
if (!$user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 用手机号当做默认用户名(如果没有设置用户名)
|
||||
$username = $user->username ?: $user->account;
|
||||
// 默认头像地址
|
||||
$avatar = $user->avatar ?: '';
|
||||
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'username' => $username,
|
||||
'account' => $user->account,
|
||||
'avatar' => $avatar,
|
||||
'isAdmin' => $user->isAdmin,
|
||||
'companyId' => $user->companyId,
|
||||
'typeId' => $user->typeId,
|
||||
'lastLoginIp' => $user->lastLoginIp,
|
||||
'lastLoginTime' => $user->lastLoginTime
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,85 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\model\User;
|
||||
use app\common\model\User as UserModel;
|
||||
use app\common\util\JwtUtil;
|
||||
use think\facade\Log;
|
||||
use think\facade\Cache;
|
||||
use think\facade\Config;
|
||||
use think\facade\Env;
|
||||
use think\facade\Log;
|
||||
|
||||
class AuthService
|
||||
{
|
||||
/**
|
||||
* 令牌有效期(秒)
|
||||
*/
|
||||
const TOKEN_EXPIRE = 7200;
|
||||
|
||||
/**
|
||||
* 短信服务实例
|
||||
* @var SmsService
|
||||
*/
|
||||
protected $smsService;
|
||||
|
||||
/**
|
||||
* 获取用户基本信息
|
||||
*
|
||||
* @param string $account
|
||||
* @param int $typeId
|
||||
* @return UserModel
|
||||
*/
|
||||
protected function getUserProfileWithAccountAndType(string $account, int $typeId): UserModel
|
||||
{
|
||||
$user = UserModel::where(function ($query) use ($account) {
|
||||
$query->where('phone', $account)->whereOr('account', $account);
|
||||
})
|
||||
->where(function ($query) use ($typeId) {
|
||||
$query->where('status', 1)->where('typeId', $typeId);
|
||||
})->find();
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*
|
||||
* @param string $account 账号(手机号)
|
||||
* @param string $password 密码(可能是加密后的)
|
||||
* @param int $typeId 身份信息
|
||||
* @return array|null
|
||||
*/
|
||||
protected function getUser($account, $password, $typeId)
|
||||
protected function getUser(string $account, string $password, int $typeId): array
|
||||
{
|
||||
// 查询用户
|
||||
$user = User::where('account', $account)
|
||||
->where('typeId', $typeId)
|
||||
->where('status', 1)
|
||||
->find();
|
||||
$user = $this->getUserProfileWithAccountAndType($account, $typeId);
|
||||
|
||||
if (!$user) {
|
||||
// 记录日志
|
||||
\think\facade\Log::info('用户不存在或已禁用', ['account' => $account]);
|
||||
return null;
|
||||
throw new \Exception('用户不存在或已禁用', 403);
|
||||
}
|
||||
|
||||
// 记录密码验证信息
|
||||
\think\facade\Log::info('密码验证', [
|
||||
'account' => $account,
|
||||
'input_password' => $password,
|
||||
'stored_hash' => $user->passwordMd5,
|
||||
]);
|
||||
|
||||
// 验证密码
|
||||
$isValid = ($user->passwordMd5 == md5($password));
|
||||
|
||||
\think\facade\Log::info('密码验证结果', [
|
||||
'account' => $account,
|
||||
'is_valid' => $isValid,
|
||||
]);
|
||||
|
||||
if (!$isValid) {
|
||||
return null;
|
||||
if ($user->passwordMd5 !== md5($password)) {
|
||||
throw new \Exception('账号或密码错误', 403);
|
||||
}
|
||||
|
||||
// 更新登录信息
|
||||
$user->lastLoginIp = request()->ip();
|
||||
$user->lastLoginTime = time();
|
||||
$user->save();
|
||||
|
||||
// 用手机号当做默认用户名(如果没有设置用户名)
|
||||
$username = $user->username ?: $user->account;
|
||||
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'username' => $username,
|
||||
'account' => $user->account,
|
||||
'avatar' => $user->avatar,
|
||||
'isAdmin' => $user->isAdmin,
|
||||
'companyId' => $user->companyId,
|
||||
'typeId' => $user->typeId,
|
||||
'lastLoginIp' => $user->lastLoginIp,
|
||||
'lastLoginTime' => $user->lastLoginTime
|
||||
];
|
||||
return $user->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,39 +66,28 @@ class AuthService
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*
|
||||
* @param string $account 账号(手机号)
|
||||
* @param string $password 密码(可能是加密后的)
|
||||
* @param string $ip 登录IP
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function login($account, $password, $typeId, $ip)
|
||||
public function login(string $account, string $password, int $typeId, string $ip)
|
||||
{
|
||||
// 获取用户信息
|
||||
$user = $this->getUser($account, $password, $typeId);
|
||||
$member = $this->getUser($account, $password, $typeId);
|
||||
|
||||
if (empty($user)) {
|
||||
// 记录登录失败
|
||||
Log::info('登录失败', ['account' => $account, 'ip' => $ip, 'is_encrypted' => true]);
|
||||
throw new \Exception('账号或密码错误');
|
||||
}
|
||||
|
||||
// 生成JWT令牌
|
||||
$token = JwtUtil::createToken($user, self::TOKEN_EXPIRE);
|
||||
$expireTime = time() + self::TOKEN_EXPIRE;
|
||||
|
||||
// 记录登录成功
|
||||
Log::info('登录成功', ['account' => $account, 'ip' => $ip]);
|
||||
|
||||
return [
|
||||
'token' => $token,
|
||||
'token_expired' => $expireTime,
|
||||
'member' => $user
|
||||
];
|
||||
$token_expired = time() + self::TOKEN_EXPIRE;
|
||||
|
||||
return compact('member', 'token', 'token_expired');
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机号验证码登录
|
||||
*
|
||||
* @param string $account 手机号
|
||||
* @param string $code 验证码(可能是加密后的)
|
||||
* @param string $ip 登录IP
|
||||
@@ -137,14 +100,14 @@ class AuthService
|
||||
// 验证验证码
|
||||
if (!$this->smsService->verifyCode($account, $code, 'login', $isEncrypted)) {
|
||||
Log::info('验证码验证失败', ['account' => $account, 'ip' => $ip, 'is_encrypted' => $isEncrypted]);
|
||||
throw new \Exception('验证码错误或已过期');
|
||||
throw new \Exception('验证码错误或已过期', 404);
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
$user = User::getUserByMobile($account);
|
||||
if (empty($user)) {
|
||||
Log::info('用户不存在', ['account' => $account, 'ip' => $ip]);
|
||||
throw new \Exception('用户不存在');
|
||||
throw new \Exception('用户不存在', 404);
|
||||
}
|
||||
|
||||
// 生成JWT令牌
|
||||
@@ -163,6 +126,7 @@ class AuthService
|
||||
|
||||
/**
|
||||
* 发送登录验证码
|
||||
*
|
||||
* @param string $account 手机号
|
||||
* @param string $type 验证码类型
|
||||
* @return array
|
||||
@@ -175,6 +139,7 @@ class AuthService
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*
|
||||
* @param array $userInfo JWT中的用户信息
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
@@ -184,16 +149,17 @@ class AuthService
|
||||
if (empty($userInfo)) {
|
||||
throw new \Exception('获取用户信息失败');
|
||||
}
|
||||
|
||||
|
||||
// 移除不需要返回的字段
|
||||
unset($userInfo['exp']);
|
||||
unset($userInfo['iat']);
|
||||
|
||||
|
||||
return $userInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新令牌
|
||||
*
|
||||
* @param array $userInfo JWT中的用户信息
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
@@ -203,15 +169,15 @@ class AuthService
|
||||
if (empty($userInfo)) {
|
||||
throw new \Exception('刷新令牌失败');
|
||||
}
|
||||
|
||||
|
||||
// 移除过期时间信息
|
||||
unset($userInfo['exp']);
|
||||
unset($userInfo['iat']);
|
||||
|
||||
|
||||
// 生成新令牌
|
||||
$token = JwtUtil::createToken($userInfo, self::TOKEN_EXPIRE);
|
||||
$expireTime = time() + self::TOKEN_EXPIRE;
|
||||
|
||||
|
||||
return [
|
||||
'token' => $token,
|
||||
'token_expired' => $expireTime
|
||||
@@ -220,52 +186,53 @@ class AuthService
|
||||
|
||||
/**
|
||||
* 获取系统授权信息,使用缓存存储10分钟
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getSystemAuthorization()
|
||||
{
|
||||
// 定义缓存键名
|
||||
$cacheKey = 'system_authorization_token';
|
||||
|
||||
|
||||
// 尝试从缓存获取授权信息
|
||||
$authorization = Cache::get($cacheKey);
|
||||
//$authorization = 'mYpVVhPY7PxctvYw1pn1VCTS2ck0yZG8q11gAiJrRN_D3q7KXXBPAfXoAmqs7kKHeaAx-h4GB7DiqVIQJ09HiXVhaQT6PtgLX3w8YV16erThC-lG1fyJB4DJxu-QxA3Q8ogSs1WFOa8aAXD1QQUZ7Kbjkw_VMLL4lrfe0Yjaqy3DnO7aL1xGnNjjX8P5uqCAZgHKlN8NjuDEGyYvXygW1YyoK9pNpwvq-6DYKjLWdmbHvFaAybHf-hU1XyrFavZqcZYxIoVXjfJ5ASp4XxeCWqMCzwtSoz9RAvwLAlNxGweowtuyX9389ZaXI-zbqb2T0S8llg';
|
||||
// 如果缓存中没有或已过期,则重新获取
|
||||
// 如果缓存中没有或已过期,则重新获取
|
||||
if (empty($authorization)) {
|
||||
try {
|
||||
// 从环境变量中获取API用户名和密码
|
||||
$username = Env::get('api.username', '');
|
||||
$password = Env::get('api.password', '');
|
||||
|
||||
|
||||
if (empty($username) || empty($password)) {
|
||||
Log::error('缺少API用户名或密码配置');
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
// 构建登录参数
|
||||
$params = [
|
||||
'grant_type' => 'password',
|
||||
'username' => $username,
|
||||
'password' => $password
|
||||
];
|
||||
|
||||
|
||||
// 获取API基础URL
|
||||
$baseUrl = Env::get('api.wechat_url', '');
|
||||
if (empty($baseUrl)) {
|
||||
Log::error('缺少API基础URL配置');
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
// 调用登录接口获取token
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, '', 'plain');
|
||||
$result = requestCurl($baseUrl . 'token', $params, 'POST',$header);
|
||||
$result = requestCurl($baseUrl . 'token', $params, 'POST', $header);
|
||||
$result_array = handleApiResponse($result);
|
||||
|
||||
if (isset($result_array['access_token']) && !empty($result_array['access_token'])) {
|
||||
$authorization = $result_array['access_token'];
|
||||
|
||||
|
||||
// 存入缓存,有效期10分钟(600秒)
|
||||
Cache::set($cacheKey, $authorization, 600);
|
||||
Cache::set('system_refresh_token', $result_array['refresh_token'], 600);
|
||||
@@ -281,7 +248,7 @@ class AuthService
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $authorization;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use think\facade\Cache;
|
||||
@@ -13,12 +14,12 @@ class SmsService
|
||||
* 验证码有效期(秒)
|
||||
*/
|
||||
const CODE_EXPIRE = 300;
|
||||
|
||||
|
||||
/**
|
||||
* 验证码长度
|
||||
*/
|
||||
const CODE_LENGTH = 4;
|
||||
|
||||
|
||||
/**
|
||||
* 发送验证码
|
||||
* @param string $mobile 手机号
|
||||
@@ -30,23 +31,23 @@ class SmsService
|
||||
{
|
||||
// 检查发送频率限制
|
||||
$this->checkSendLimit($mobile, $type);
|
||||
|
||||
|
||||
// 生成验证码
|
||||
$code = $this->generateCode();
|
||||
|
||||
|
||||
// 缓存验证码
|
||||
$this->saveCode($mobile, $code, $type);
|
||||
|
||||
|
||||
// 发送验证码(实际项目中对接短信平台)
|
||||
$this->doSend($mobile, $code, $type);
|
||||
|
||||
|
||||
// 记录日志
|
||||
Log::info('发送验证码', [
|
||||
'mobile' => $mobile,
|
||||
'type' => $type,
|
||||
'code' => $code
|
||||
]);
|
||||
|
||||
|
||||
return [
|
||||
'mobile' => $mobile,
|
||||
'expire' => self::CODE_EXPIRE,
|
||||
@@ -54,7 +55,7 @@ class SmsService
|
||||
'code' => $code
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 验证验证码
|
||||
* @param string $mobile 手机号
|
||||
@@ -67,7 +68,7 @@ class SmsService
|
||||
{
|
||||
$cacheKey = $this->getCodeCacheKey($mobile, $type);
|
||||
$cacheCode = Cache::get($cacheKey);
|
||||
|
||||
|
||||
if (!$cacheCode) {
|
||||
Log::info('验证码不存在或已过期', [
|
||||
'mobile' => $mobile,
|
||||
@@ -75,15 +76,15 @@ class SmsService
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// 验证码是否匹配
|
||||
$isValid = false;
|
||||
|
||||
|
||||
if ($isEncrypted) {
|
||||
// 前端已加密,需要对缓存中的验证码进行相同的加密处理
|
||||
$encryptedCacheCode = $this->encryptCode($cacheCode);
|
||||
$isValid = hash_equals($encryptedCacheCode, $code);
|
||||
|
||||
|
||||
// 记录日志
|
||||
Log::info('加密验证码验证', [
|
||||
'mobile' => $mobile,
|
||||
@@ -95,7 +96,7 @@ class SmsService
|
||||
} else {
|
||||
// 未加密,直接比较
|
||||
$isValid = ($cacheCode === $code);
|
||||
|
||||
|
||||
// 记录日志
|
||||
Log::info('明文验证码验证', [
|
||||
'mobile' => $mobile,
|
||||
@@ -104,15 +105,15 @@ class SmsService
|
||||
'is_valid' => $isValid
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
// 验证成功后删除缓存
|
||||
if ($isValid) {
|
||||
Cache::rm($cacheKey);
|
||||
}
|
||||
|
||||
|
||||
return $isValid;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 检查发送频率限制
|
||||
* @param string $mobile 手机号
|
||||
@@ -122,24 +123,24 @@ class SmsService
|
||||
protected function checkSendLimit($mobile, $type)
|
||||
{
|
||||
$cacheKey = $this->getCodeCacheKey($mobile, $type);
|
||||
|
||||
|
||||
// 检查是否存在未过期的验证码
|
||||
if (Cache::has($cacheKey)) {
|
||||
throw new \Exception('验证码已发送,请稍后再试');
|
||||
}
|
||||
|
||||
|
||||
// 检查当日发送次数限制
|
||||
$limitKey = "sms_limit:{$mobile}:" . date('Ymd');
|
||||
$sendCount = Cache::get($limitKey, 0);
|
||||
|
||||
|
||||
if ($sendCount >= 10) {
|
||||
throw new \Exception('今日发送次数已达上限');
|
||||
}
|
||||
|
||||
|
||||
// 更新发送次数
|
||||
Cache::set($limitKey, $sendCount + 1, 86400);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成随机验证码
|
||||
* @return string
|
||||
@@ -149,7 +150,7 @@ class SmsService
|
||||
// 生成4位数字验证码
|
||||
return sprintf("%0" . self::CODE_LENGTH . "d", mt_rand(0, pow(10, self::CODE_LENGTH) - 1));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 保存验证码到缓存
|
||||
* @param string $mobile 手机号
|
||||
@@ -161,7 +162,7 @@ class SmsService
|
||||
$cacheKey = $this->getCodeCacheKey($mobile, $type);
|
||||
Cache::set($cacheKey, $code, self::CODE_EXPIRE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 执行发送验证码
|
||||
* @param string $mobile 手机号
|
||||
@@ -175,7 +176,7 @@ class SmsService
|
||||
// 这里仅做模拟,返回成功
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取验证码缓存键名
|
||||
* @param string $mobile 手机号
|
||||
@@ -186,7 +187,7 @@ class SmsService
|
||||
{
|
||||
return "sms_code:{$mobile}:{$type}";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 加密验证码
|
||||
* 使用与前端相同的加密算法
|
||||
|
||||
@@ -10,72 +10,73 @@ Route::group('v1/', function () {
|
||||
|
||||
// 设备管理相关
|
||||
Route::group('devices', function () {
|
||||
Route::get(':id/related-accounts', 'app\\cunkebao\\controller\\device\\GetRelatedAccountsV1Controller@index'); // 设备关联微信账号路由
|
||||
Route::get(':id/handle-logs', 'app\\cunkebao\\controller\\device\\GetDeviceHandleLogsV1Controller@index'); // 获取设备操作记录
|
||||
Route::get('', 'app\\cunkebao\\controller\\device\\GetDeviceListV1Controller@index'); // 获取设备列表
|
||||
Route::get(':id', 'app\\cunkebao\\controller\\device\\GetDeviceDetailV1Controller@index'); // 获取设备详情
|
||||
Route::post('', 'app\\cunkebao\\controller\\device\\PostAddDeviceV1Controller@index'); // 添加设备
|
||||
Route::put('refresh', 'app\\cunkebao\\controller\\device\\RefreshDeviceDetailV1Controller@index'); // 刷新设备状态
|
||||
Route::delete(':id', 'app\\cunkebao\\controller\\Device@delete'); // 删除设备
|
||||
Route::post('task-config', 'app\\cunkebao\\controller\\device\\UpdateDeviceTaskConfigV1Controller@index'); // 更新设备任务配置
|
||||
Route::get(':id/related-accounts', 'app\cunkebao\controller\device\GetRelatedAccountsV1Controller@index'); // 设备关联微信账号路由
|
||||
Route::get(':id/handle-logs', 'app\cunkebao\controller\device\GetDeviceHandleLogsV1Controller@index'); // 获取设备操作记录
|
||||
Route::get('', 'app\cunkebao\controller\device\GetDeviceListV1Controller@index'); // 获取设备列表
|
||||
Route::get(':id', 'app\cunkebao\controller\device\GetDeviceDetailV1Controller@index'); // 获取设备详情
|
||||
Route::post('', 'app\cunkebao\controller\device\PostAddDeviceV1Controller@index'); // 添加设备
|
||||
Route::put('refresh', 'app\cunkebao\controller\device\RefreshDeviceDetailV1Controller@index'); // 刷新设备状态
|
||||
Route::delete(':id', 'app\cunkebao\controller\Device@delete'); // 删除设备
|
||||
Route::post('task-config', 'app\cunkebao\controller\device\UpdateDeviceTaskConfigV1Controller@index');
|
||||
Route::get('add-results', 'app\cunkebao\controller\device\GetAddResultedDevicesController@index'); // 更新设备任务配置
|
||||
});
|
||||
|
||||
// 设备微信相关
|
||||
Route::group('device/wechats', function () {
|
||||
Route::get('friends', 'app\\cunkebao\\controller\\DeviceWechat@getFriends'); // 获取微信好友列表
|
||||
Route::get('count', 'app\\cunkebao\\controller\\DeviceWechat@count'); // 获取在线微信账号数量
|
||||
Route::get('device-count', 'app\\cunkebao\\controller\\DeviceWechat@deviceCount'); // 获取有登录微信的设备数量
|
||||
Route::get('', 'app\\cunkebao\\controller\\DeviceWechat@index'); // 获取在线微信账号列表
|
||||
Route::get(':id', 'app\\cunkebao\\controller\\DeviceWechat@detail'); // 获取微信号详情
|
||||
Route::put('refresh', 'app\\cunkebao\\controller\\DeviceWechat@refresh'); // 刷新设备微信状态
|
||||
Route::post('transfer-friends', 'app\\cunkebao\\controller\\DeviceWechat@transferFriends'); // 微信好友转移
|
||||
Route::get('friends', 'app\cunkebao\controller\DeviceWechat@getFriends'); // 获取微信好友列表
|
||||
Route::get('count', 'app\cunkebao\controller\DeviceWechat@count'); // 获取在线微信账号数量
|
||||
Route::get('device-count', 'app\cunkebao\controller\DeviceWechat@deviceCount'); // 获取有登录微信的设备数量
|
||||
Route::get('', 'app\cunkebao\controller\DeviceWechat@index'); // 获取在线微信账号列表
|
||||
Route::get(':id', 'app\cunkebao\controller\DeviceWechat@detail'); // 获取微信号详情
|
||||
Route::put('refresh', 'app\cunkebao\controller\DeviceWechat@refresh'); // 刷新设备微信状态
|
||||
Route::post('transfer-friends', 'app\cunkebao\controller\DeviceWechat@transferFriends'); // 微信好友转移
|
||||
});
|
||||
|
||||
// 获客场景相关
|
||||
Route::group('plan/scenes', function () {
|
||||
Route::get('', 'app\\cunkebao\\controller\\Scene@index'); // 获取场景列表
|
||||
Route::get('', 'app\cunkebao\controller\Scene@index'); // 获取场景列表
|
||||
});
|
||||
|
||||
// 流量标签相关
|
||||
Route::group('traffic/tags', function () {
|
||||
Route::get('', 'app\\cunkebao\\controller\\TrafficTag@index'); // 获取标签列表
|
||||
Route::get('', 'app\cunkebao\controller\TrafficTag@index'); // 获取标签列表
|
||||
});
|
||||
|
||||
// 流量池相关
|
||||
Route::group('traffic/pool', function () {
|
||||
Route::post('import', 'app\\cunkebao\\controller\\TrafficPool@importOrders'); // 导入订单标签
|
||||
Route::post('import', 'app\cunkebao\controller\TrafficPool@importOrders'); // 导入订单标签
|
||||
});
|
||||
|
||||
// 工作台相关
|
||||
Route::group('workbench', function () {
|
||||
Route::post('create', 'app\\cunkebao\\controller\\WorkbenchController@create'); // 创建工作台
|
||||
Route::get('list', 'app\\cunkebao\\controller\\WorkbenchController@getList'); // 获取工作台列表
|
||||
Route::post('update-status', 'app\\cunkebao\\controller\\WorkbenchController@updateStatus'); // 更新工作台状态
|
||||
Route::delete('delete', 'app\\cunkebao\\controller\\WorkbenchController@delete'); // 删除工作台
|
||||
Route::post('copy', 'app\\cunkebao\\controller\\WorkbenchController@copy'); // 拷贝工作台
|
||||
Route::get('detail', 'app\\cunkebao\\controller\\WorkbenchController@detail'); // 获取工作台详情
|
||||
Route::post('update', 'app\\cunkebao\\controller\\WorkbenchController@update'); // 更新工作台
|
||||
Route::post('create', 'app\cunkebao\controller\WorkbenchController@create'); // 创建工作台
|
||||
Route::get('list', 'app\cunkebao\controller\WorkbenchController@getList'); // 获取工作台列表
|
||||
Route::post('update-status', 'app\cunkebao\controller\WorkbenchController@updateStatus'); // 更新工作台状态
|
||||
Route::delete('delete', 'app\cunkebao\controller\WorkbenchController@delete'); // 删除工作台
|
||||
Route::post('copy', 'app\cunkebao\controller\WorkbenchController@copy'); // 拷贝工作台
|
||||
Route::get('detail', 'app\cunkebao\controller\WorkbenchController@detail'); // 获取工作台详情
|
||||
Route::post('update', 'app\cunkebao\controller\WorkbenchController@update'); // 更新工作台
|
||||
});
|
||||
|
||||
// 内容库相关
|
||||
Route::group('content/library', function () {
|
||||
Route::post('create', 'app\\cunkebao\\controller\\ContentLibraryController@create'); // 创建内容库
|
||||
Route::get('list', 'app\\cunkebao\\controller\\ContentLibraryController@getList'); // 获取内容库列表
|
||||
Route::post('update', 'app\\cunkebao\\controller\\ContentLibraryController@update'); // 更新内容库
|
||||
Route::delete('delete', 'app\\cunkebao\\controller\\ContentLibraryController@delete'); // 删除内容库
|
||||
Route::get('detail', 'app\\cunkebao\\controller\\ContentLibraryController@detail'); // 获取内容库详情
|
||||
Route::get('collectMoments', 'app\\cunkebao\\controller\\ContentLibraryController@collectMoments'); // 采集朋友圈
|
||||
Route::post('create', 'app\cunkebao\controller\ContentLibraryController@create'); // 创建内容库
|
||||
Route::get('list', 'app\cunkebao\controller\ContentLibraryController@getList'); // 获取内容库列表
|
||||
Route::post('update', 'app\cunkebao\controller\ContentLibraryController@update'); // 更新内容库
|
||||
Route::delete('delete', 'app\cunkebao\controller\ContentLibraryController@delete'); // 删除内容库
|
||||
Route::get('detail', 'app\cunkebao\controller\ContentLibraryController@detail'); // 获取内容库详情
|
||||
Route::get('collectMoments', 'app\cunkebao\controller\ContentLibraryController@collectMoments'); // 采集朋友圈
|
||||
});
|
||||
|
||||
// 好友相关
|
||||
Route::group('friend', function () {
|
||||
Route::get('', 'app\\cunkebao\\controller\\friend\\GetFriendListV1Controller@index'); // 获取好友列表
|
||||
Route::get('', 'app\cunkebao\controller\friend\GetFriendListV1Controller@index'); // 获取好友列表
|
||||
});
|
||||
|
||||
//群相关
|
||||
Route::group('chatroom', function () {
|
||||
Route::get('', 'app\\cunkebao\\controller\\chatroom\\GetChatroomListV1Controller@index'); // 获取群列表
|
||||
Route::get('getMemberList', 'app\\cunkebao\\controller\\chatroom\\GetChatroomListV1Controller@getMemberList'); // 获取群详情
|
||||
Route::get('', 'app\cunkebao\controller\chatroom\GetChatroomListV1Controller@index'); // 获取群列表
|
||||
Route::get('getMemberList', 'app\cunkebao\controller\chatroom\GetChatroomListV1Controller@getMemberList'); // 获取群详情
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ class BaseController extends Controller
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getUserInfo(string $column = '')
|
||||
protected function getUserInfo(?string $column = null)
|
||||
{
|
||||
$user = $this->request->userInfo;
|
||||
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\device;
|
||||
|
||||
use app\api\controller\DeviceController as ApiDeviceController;
|
||||
use app\common\model\Device as DeviceModel;
|
||||
use app\common\model\User as UserModel;
|
||||
use library\ResponseHelper;
|
||||
use think\Controller;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 设备控制器
|
||||
*/
|
||||
class GetAddResultedDevicesController extends Controller
|
||||
{
|
||||
/**
|
||||
* 通过账号id 获取项目id。
|
||||
*
|
||||
* @param int $accountId
|
||||
* @return int
|
||||
*/
|
||||
protected function getCompanyIdByAccountId(int $accountId): int
|
||||
{
|
||||
return UserModel::where('s2_accountId', $accountId)->value('companyId');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目下的所有设备。
|
||||
*
|
||||
* @param int $companyId
|
||||
* @return array
|
||||
*/
|
||||
protected function getAllDevicesIdWithInCompany(int $companyId): array
|
||||
{
|
||||
return DeviceModel::where('companyId', $companyId)->column('id') ?: [0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行数据迁移。
|
||||
*
|
||||
* @param int $accountId
|
||||
* @return void
|
||||
*/
|
||||
protected function migrateData(int $accountId): void
|
||||
{
|
||||
$companyId = $this->getCompanyIdByAccountId($accountId);
|
||||
$deviceIds = $this->getAllDevicesIdWithInCompany($companyId) ?: [0];
|
||||
|
||||
// 从 s2_device 导入数据。
|
||||
$this->getNewDeviceFromS2_device($deviceIds, $companyId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 s2_device 导入数据。
|
||||
*
|
||||
* @param array $ids
|
||||
* @param int $companyId
|
||||
* @return void
|
||||
*/
|
||||
protected function getNewDeviceFromS2_device(array $ids, int $companyId): void
|
||||
{
|
||||
$ids = implode(',', $ids);
|
||||
|
||||
$sql = "insert into ck_device(`id`, `imei`, `model`, phone, operatingSystem,memo,alive,brand,rooted,xPosed,softwareVersion,extra,createTime,updateTime,deleteTime,companyId)
|
||||
select
|
||||
d.id,d.imei,d.model,d.phone,d.operatingSystem,d.memo,d.alive,d.brand,d.rooted,d.xPosed,d.softwareVersion,d.extra,d.createTime,d.lastUpdateTime,d.deleteTime,a.departmentId companyId
|
||||
from s2_device d
|
||||
join s2_company_account a on d.currentAccountId = a.id
|
||||
where isDeleted = 0 and deletedAndStop = 0 and d.id not in ({$ids}) and a.departmentId = {$companyId}
|
||||
";
|
||||
|
||||
Db::query($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取添加的关联设备结果。
|
||||
*
|
||||
* @param int $accountId
|
||||
* @return bool
|
||||
*/
|
||||
protected function getAddResulted(int $accountId): bool
|
||||
{
|
||||
$result = (new ApiDeviceController())->getlist(
|
||||
[
|
||||
'accountId' => $accountId,
|
||||
'pageIndex' => 0,
|
||||
'pageSize' => 1
|
||||
],
|
||||
true
|
||||
);
|
||||
|
||||
$result = json_decode($result, true);
|
||||
$result = $result['data']['results'][0] ?? false;
|
||||
|
||||
return $result ? (
|
||||
// 125是前端延迟5秒 + 轮询120次 1次/s
|
||||
time() - strtotime($result['lastUpdateTime']) <= 65
|
||||
) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取基础统计信息
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$accountId = $this->request->param('accountId/d');
|
||||
|
||||
$isAdded = $this->getAddResulted($accountId);
|
||||
$isAdded && $this->migrateData($accountId);
|
||||
|
||||
return ResponseHelper::success(
|
||||
[
|
||||
'added' => $isAdded
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user