Merge branch 'develop' of https://gitee.com/cunkebao/cunkebao_v3 into develop
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,9 +5,11 @@ Store_vue/node_modules/
|
|||||||
Cunkebao/.specstory/
|
Cunkebao/.specstory/
|
||||||
*.cursorindexingignore
|
*.cursorindexingignore
|
||||||
Server/.specstory/
|
Server/.specstory/
|
||||||
|
Server/thinkphp/
|
||||||
Store_vue/.specstory/
|
Store_vue/.specstory/
|
||||||
Store_vue/unpackage/
|
Store_vue/unpackage/
|
||||||
Store_vue/.vscode/
|
Store_vue/.vscode/
|
||||||
SuperAdmin/.specstory/
|
SuperAdmin/.specstory/
|
||||||
Cunkebao/dist
|
Cunkebao/dist
|
||||||
Touchkebao/.specstory/
|
Touchkebao/.specstory/
|
||||||
|
Serverruntime/
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import {
|
|||||||
GetContentItemListParams,
|
GetContentItemListParams,
|
||||||
CreateContentItemParams,
|
CreateContentItemParams,
|
||||||
UpdateContentItemParams,
|
UpdateContentItemParams,
|
||||||
|
AIRewriteParams,
|
||||||
|
ReplaceContentParams,
|
||||||
} from "./data";
|
} from "./data";
|
||||||
|
|
||||||
// 获取素材列表
|
// 获取素材列表
|
||||||
@@ -35,3 +37,13 @@ export function deleteContentItem(id: string) {
|
|||||||
export function getContentLibraryDetail(id: string) {
|
export function getContentLibraryDetail(id: string) {
|
||||||
return request("/v1/content/library/detail", { id }, "GET");
|
return request("/v1/content/library/detail", { id }, "GET");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AI改写内容
|
||||||
|
export function aiRewriteContent(params: AIRewriteParams) {
|
||||||
|
return request("/v1/content/library/aiEditContent", params, "GET");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 替换原内容
|
||||||
|
export function replaceContent(params: ReplaceContentParams) {
|
||||||
|
return request("/v1/content/library/aiEditContent", params, "POST");
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export interface ContentItem {
|
|||||||
delTime: number;
|
delTime: number;
|
||||||
wechatChatroomId?: string | null;
|
wechatChatroomId?: string | null;
|
||||||
senderNickname: string;
|
senderNickname: string;
|
||||||
|
senderAvatar?: string | null;
|
||||||
createMessageTime?: string | null;
|
createMessageTime?: string | null;
|
||||||
comment: string;
|
comment: string;
|
||||||
sendTime: number;
|
sendTime: number;
|
||||||
@@ -104,3 +105,15 @@ export interface UpdateContentItemParams
|
|||||||
extends Partial<CreateContentItemParams> {
|
extends Partial<CreateContentItemParams> {
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AI改写参数
|
||||||
|
export interface AIRewriteParams {
|
||||||
|
id: string;
|
||||||
|
aiPrompt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 替换内容参数
|
||||||
|
export interface ReplaceContentParams {
|
||||||
|
id: string;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -131,6 +131,25 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-icon-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-icon {
|
.avatar-icon {
|
||||||
@@ -613,3 +632,147 @@
|
|||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AI改写弹框样式
|
||||||
|
.ai-popup-content {
|
||||||
|
padding: 20px;
|
||||||
|
max-height: 100vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: #f9fbfd;
|
||||||
|
|
||||||
|
.ai-popup-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
border-bottom: 1px solid #e8f4ff;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1677ff;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 4px;
|
||||||
|
height: 18px;
|
||||||
|
background: #1677ff;
|
||||||
|
margin-right: 8px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-form {
|
||||||
|
.ai-form-item {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.ai-form-label {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 3px;
|
||||||
|
height: 14px;
|
||||||
|
background: #1677ff;
|
||||||
|
margin-right: 6px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-result-description {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-submit {
|
||||||
|
margin: 24px 0;
|
||||||
|
|
||||||
|
button {
|
||||||
|
height: 44px;
|
||||||
|
font-size: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 6px rgba(22, 119, 255, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-result-box {
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #e0f0ff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
min-height: 100px;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.ai-loading {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-result-content {
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.8;
|
||||||
|
color: #333;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-result-placeholder {
|
||||||
|
color: #999;
|
||||||
|
text-align: center;
|
||||||
|
padding: 30px 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.placeholder-icon {
|
||||||
|
font-size: 28px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-replace-action {
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
height: 44px;
|
||||||
|
font-size: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #1677ff;
|
||||||
|
border-color: #1677ff;
|
||||||
|
box-shadow: 0 2px 6px rgba(22, 119, 255, 0.2);
|
||||||
|
color: #ffffff;
|
||||||
|
|
||||||
|
&:hover, &:focus {
|
||||||
|
background: #4096ff;
|
||||||
|
border-color: #4096ff;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useEffect, useCallback } from "react";
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { Toast, SpinLoading, Dialog, Card } from "antd-mobile";
|
import { Toast, SpinLoading, Dialog, Card, Popup, TextArea } from "antd-mobile";
|
||||||
import { Input, Pagination, Button } from "antd";
|
import { Input, Pagination, Button, Spin } from "antd";
|
||||||
import {
|
import {
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
SearchOutlined,
|
SearchOutlined,
|
||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import Layout from "@/components/Layout/Layout";
|
import Layout from "@/components/Layout/Layout";
|
||||||
import NavCommon from "@/components/NavCommon";
|
import NavCommon from "@/components/NavCommon";
|
||||||
import { getContentItemList, deleteContentItem } from "./api";
|
import { getContentItemList, deleteContentItem, aiRewriteContent, replaceContent } from "./api";
|
||||||
import { ContentItem } from "./data";
|
import { ContentItem } from "./data";
|
||||||
import style from "./index.module.scss";
|
import style from "./index.module.scss";
|
||||||
|
|
||||||
@@ -42,6 +42,14 @@ const MaterialsList: React.FC = () => {
|
|||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
const pageSize = 20;
|
const pageSize = 20;
|
||||||
|
|
||||||
|
// AI改写相关状态
|
||||||
|
const [showAIRewritePopup, setShowAIRewritePopup] = useState(false);
|
||||||
|
const [currentMaterial, setCurrentMaterial] = useState<ContentItem | null>(null);
|
||||||
|
const [aiPrompt, setAiPrompt] = useState("");
|
||||||
|
const [aiResult, setAiResult] = useState("");
|
||||||
|
const [aiLoading, setAiLoading] = useState(false);
|
||||||
|
const [replaceLoading, setReplaceLoading] = useState(false);
|
||||||
|
|
||||||
// 获取素材列表
|
// 获取素材列表
|
||||||
const fetchMaterials = useCallback(async () => {
|
const fetchMaterials = useCallback(async () => {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
@@ -104,9 +112,75 @@ const MaterialsList: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleView = (materialId: number) => {
|
const handleAIRewrite = (material: ContentItem) => {
|
||||||
// 可以跳转到素材详情页面或显示弹窗
|
setCurrentMaterial(material);
|
||||||
console.log("查看素材:", materialId);
|
setAiPrompt("重写这条朋友圈 要求: 1、原本的字数和意思不要修改超过10% 2、出现品牌名或个人名字就去除 3、适当的换行及加些表情点缀");
|
||||||
|
setAiResult("");
|
||||||
|
setShowAIRewritePopup(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmitAIRewrite = async () => {
|
||||||
|
if (!currentMaterial) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setAiLoading(true);
|
||||||
|
const response = await aiRewriteContent({
|
||||||
|
id: currentMaterial.id.toString(),
|
||||||
|
aiPrompt: aiPrompt
|
||||||
|
});
|
||||||
|
|
||||||
|
setAiResult(response.contentAfter || "暂无改写结果");
|
||||||
|
|
||||||
|
// 可以在这里显示原内容和改写后内容的对比
|
||||||
|
console.log("原内容:", response.contentFront);
|
||||||
|
console.log("改写后内容:", response.contentAfter);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("AI改写失败:", error);
|
||||||
|
Toast.show({
|
||||||
|
content: "AI改写失败,请重试",
|
||||||
|
position: "top",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setAiLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReplaceContent = async () => {
|
||||||
|
if (!currentMaterial || !aiResult) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setReplaceLoading(true);
|
||||||
|
await replaceContent({
|
||||||
|
id: currentMaterial.id.toString(),
|
||||||
|
content: aiResult
|
||||||
|
});
|
||||||
|
|
||||||
|
Toast.show({
|
||||||
|
content: "内容已成功替换",
|
||||||
|
position: "top",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 刷新素材列表
|
||||||
|
fetchMaterials();
|
||||||
|
|
||||||
|
// 关闭弹窗
|
||||||
|
closeAIRewritePopup();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("替换内容失败:", error);
|
||||||
|
Toast.show({
|
||||||
|
content: "替换内容失败,请重试",
|
||||||
|
position: "top",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setReplaceLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeAIRewritePopup = () => {
|
||||||
|
setShowAIRewritePopup(false);
|
||||||
|
setCurrentMaterial(null);
|
||||||
|
setAiPrompt("");
|
||||||
|
setAiResult("");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRefresh = () => {
|
const handleRefresh = () => {
|
||||||
@@ -348,7 +422,23 @@ const MaterialsList: React.FC = () => {
|
|||||||
<div className={style["card-header"]}>
|
<div className={style["card-header"]}>
|
||||||
<div className={style["avatar-section"]}>
|
<div className={style["avatar-section"]}>
|
||||||
<div className={style["avatar"]}>
|
<div className={style["avatar"]}>
|
||||||
<UserOutlined className={style["avatar-icon"]} />
|
{material.senderAvatar ? (
|
||||||
|
<img
|
||||||
|
src={material.senderAvatar}
|
||||||
|
alt="头像"
|
||||||
|
className={style["avatar-img"]}
|
||||||
|
onError={(e) => {
|
||||||
|
e.currentTarget.style.display = 'none';
|
||||||
|
const nextElement = e.currentTarget.nextSibling as HTMLElement;
|
||||||
|
if (nextElement) {
|
||||||
|
nextElement.style.display = 'flex';
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<div className={style["avatar-icon-wrapper"]} style={{display: material.senderAvatar ? 'none' : 'flex'}}>
|
||||||
|
<UserOutlined className={style["avatar-icon"]}/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={style["header-info"]}>
|
<div className={style["header-info"]}>
|
||||||
<span className={style["creator-name"]}>
|
<span className={style["creator-name"]}>
|
||||||
@@ -381,7 +471,7 @@ const MaterialsList: React.FC = () => {
|
|||||||
编辑
|
编辑
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleView(material.id)}
|
onClick={() => handleAIRewrite(material)}
|
||||||
className={style["action-btn"]}
|
className={style["action-btn"]}
|
||||||
>
|
>
|
||||||
<BarChartOutlined />
|
<BarChartOutlined />
|
||||||
@@ -402,6 +492,100 @@ const MaterialsList: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/* AI改写弹框 */}
|
||||||
|
<Popup
|
||||||
|
visible={showAIRewritePopup}
|
||||||
|
onMaskClick={closeAIRewritePopup}
|
||||||
|
bodyStyle={{
|
||||||
|
borderRadius: "16px 16px 0 0",
|
||||||
|
maxHeight: "90vh",
|
||||||
|
boxShadow: "0 -4px 12px rgba(0, 0, 0, 0.1)"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className={style["ai-popup-content"]}>
|
||||||
|
<div className={style["ai-popup-header"]}>
|
||||||
|
<h3>AI内容改写</h3>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={closeAIRewritePopup}
|
||||||
|
>
|
||||||
|
关闭
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={style["ai-form"]}>
|
||||||
|
{/* 提示词输入区 */}
|
||||||
|
<div className={style["ai-form-item"]}>
|
||||||
|
<div className={style["ai-form-label"]}>提示词</div>
|
||||||
|
<div className={style["ai-form-control"]}>
|
||||||
|
<TextArea
|
||||||
|
placeholder="请输入提示词指导AI如何改写内容"
|
||||||
|
value={aiPrompt}
|
||||||
|
onChange={val => setAiPrompt(val)}
|
||||||
|
rows={4}
|
||||||
|
showCount
|
||||||
|
maxLength={500}
|
||||||
|
style={{
|
||||||
|
border: "1px solid #d9e8ff",
|
||||||
|
borderRadius: "8px",
|
||||||
|
padding: "12px",
|
||||||
|
fontSize: "14px"
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={style["ai-submit"]}>
|
||||||
|
<Button
|
||||||
|
block
|
||||||
|
color="primary"
|
||||||
|
onClick={handleSubmitAIRewrite}
|
||||||
|
loading={aiLoading}
|
||||||
|
disabled={aiLoading || !aiPrompt.trim()}
|
||||||
|
>
|
||||||
|
{aiLoading ? "生成中..." : "生成内容"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 改写结果区 */}
|
||||||
|
<div className={style["ai-form-item"]}>
|
||||||
|
<div className={style["ai-form-label"]}>改写结果</div>
|
||||||
|
<div className={style["ai-result-description"]}>AI生成的内容将显示在下方区域</div>
|
||||||
|
<div className={style["ai-result-box"]}>
|
||||||
|
{aiLoading ? (
|
||||||
|
<div className={style["ai-loading"]}>
|
||||||
|
<Spin tip="AI正在思考中..." />
|
||||||
|
</div>
|
||||||
|
) : aiResult ? (
|
||||||
|
<div className={style["ai-result-content"]}>
|
||||||
|
{aiResult}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className={style["ai-result-placeholder"]}>
|
||||||
|
<div className={style["placeholder-icon"]}>✨</div>
|
||||||
|
<div className={style["placeholder-text"]}>点击"生成内容"按钮获取AI改写结果</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 替换按钮 */}
|
||||||
|
{aiResult && (
|
||||||
|
<div className={style["ai-replace-action"]}>
|
||||||
|
<Button
|
||||||
|
block
|
||||||
|
color="primary"
|
||||||
|
onClick={handleReplaceContent}
|
||||||
|
loading={replaceLoading}
|
||||||
|
disabled={replaceLoading || !aiResult}
|
||||||
|
>
|
||||||
|
{replaceLoading ? "替换中..." : "替换原内容"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Popup>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -75,7 +75,18 @@ if (!function_exists('requestCurl')) {
|
|||||||
if (!function_exists('dataBuild')) {
|
if (!function_exists('dataBuild')) {
|
||||||
function dataBuild($array)
|
function dataBuild($array)
|
||||||
{
|
{
|
||||||
return is_array($array) ? http_build_query($array) : $array;
|
if (!is_array($array)) {
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理嵌套数组
|
||||||
|
foreach ($array as $key => $value) {
|
||||||
|
if (is_array($value)) {
|
||||||
|
$array[$key] = json_encode($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return http_build_query($array);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -550,14 +561,15 @@ if (!function_exists('exit_data')) {
|
|||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!function_exists('dump')) {
|
||||||
/**
|
/**
|
||||||
* 调试打印变量但不终止程序
|
* 调试打印变量但不终止程序
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
function dump()
|
function dump()
|
||||||
{
|
{
|
||||||
call_user_func_array(['app\\common\\helper\\Debug', 'dump'], func_get_args());
|
call_user_func_array(['app\\common\\helper\\Debug', 'dump'], func_get_args());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!function_exists('artificialAllotWechatFriend')) {
|
if (!function_exists('artificialAllotWechatFriend')) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
49
Server/application/http/middleware/Jwt.php
Normal file
49
Server/application/http/middleware/Jwt.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
namespace app\http\middleware;
|
||||||
|
|
||||||
|
use app\common\util\JwtUtil;
|
||||||
|
use think\facade\Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT认证中间件
|
||||||
|
*/
|
||||||
|
class Jwt
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 处理请求
|
||||||
|
* @param \think\Request $request
|
||||||
|
* @param \Closure $next
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function handle($request, \Closure $next)
|
||||||
|
{
|
||||||
|
// 获取Token
|
||||||
|
$token = JwtUtil::getRequestToken();
|
||||||
|
|
||||||
|
// 验证Token
|
||||||
|
if (!$token) {
|
||||||
|
return json([
|
||||||
|
'code' => 401,
|
||||||
|
'msg' => '未授权访问,缺少有效的身份凭证',
|
||||||
|
'data' => null
|
||||||
|
])->header(['Content-Type' => 'application/json; charset=utf-8']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload = JwtUtil::verifyToken($token);
|
||||||
|
if (!$payload) {
|
||||||
|
return json([
|
||||||
|
'code' => 401,
|
||||||
|
'msg' => '授权已过期或无效',
|
||||||
|
'data' => null
|
||||||
|
])->header(['Content-Type' => 'application/json; charset=utf-8']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将用户信息附加到请求中
|
||||||
|
$request->userInfo = $payload;
|
||||||
|
|
||||||
|
// 写入日志
|
||||||
|
Log::info('JWT认证通过', ['user_id' => $payload['id'] ?? 0, 'username' => $payload['username'] ?? '']);
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,21 +13,58 @@
|
|||||||
{
|
{
|
||||||
"name": "liu21st",
|
"name": "liu21st",
|
||||||
"email": "liu21st@gmail.com"
|
"email": "liu21st@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "yunwuxin",
|
||||||
|
"email": "448901948@qq.com"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.4.0",
|
"php": ">=5.6.0",
|
||||||
"topthink/framework": "5.0.*"
|
"topthink/framework": "5.1.*",
|
||||||
|
"topthink/think-installer": "~1.0",
|
||||||
|
"topthink/think-captcha": "^2.0",
|
||||||
|
"topthink/think-helper": "^3.0",
|
||||||
|
"topthink/think-image": "^1.0",
|
||||||
|
"topthink/think-queue": "^2.0",
|
||||||
|
"topthink/think-worker": "^2.0",
|
||||||
|
"textalk/websocket": "^1.2",
|
||||||
|
"aliyuncs/oss-sdk-php": "^2.3",
|
||||||
|
"monolog/monolog": "^1.24",
|
||||||
|
"guzzlehttp/guzzle": "^6.3",
|
||||||
|
"overtrue/wechat": "~4.0",
|
||||||
|
"endroid/qr-code": "^3.5",
|
||||||
|
"phpoffice/phpspreadsheet": "^1.8",
|
||||||
|
"workerman/workerman": "^3.5",
|
||||||
|
"workerman/gateway-worker": "^3.0",
|
||||||
|
"hashids/hashids": "^2.0",
|
||||||
|
"khanamiryan/qrcode-detector-decoder": "^1.0",
|
||||||
|
"lizhichao/word": "^2.0",
|
||||||
|
"adbario/php-dot-notation": "^2.2"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/var-dumper": "^3.4",
|
||||||
|
"topthink/think-migration": "^2.0"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"app\\": "application"
|
"app\\": "application"
|
||||||
}
|
},
|
||||||
|
"files": [
|
||||||
|
"application/common.php"
|
||||||
|
],
|
||||||
|
"classmap": []
|
||||||
},
|
},
|
||||||
"extra": {
|
"extra": {
|
||||||
"think-path": "thinkphp"
|
"think-path": "thinkphp"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"preferred-install": "dist"
|
"preferred-install": "dist",
|
||||||
}
|
"allow-plugins": {
|
||||||
|
"topthink/think-installer": true,
|
||||||
|
"easywechat-composer/easywechat-composer": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimum-stability": "dev",
|
||||||
|
"prefer-stable": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ return [
|
|||||||
|
|
||||||
// 全局中间件
|
// 全局中间件
|
||||||
'alias' => [
|
'alias' => [
|
||||||
'cors' => 'app\\common\\middleware\\AllowCrossDomain'
|
'cors' => 'app\\common\\middleware\\AllowCrossDomain',
|
||||||
|
'jwt' => 'app\\http\\middleware\\Jwt'
|
||||||
],
|
],
|
||||||
|
|
||||||
// 应用中间件
|
// 应用中间件
|
||||||
|
|||||||
Reference in New Issue
Block a user