fix(upload): 支持管理后台管理员身份上传图片并补齐 a-upload 鉴权请求头
这个提交包含在:
@@ -167,6 +167,7 @@
|
||||
<a-upload
|
||||
action="/api/upload"
|
||||
name="file"
|
||||
:headers="uploadHeaders"
|
||||
:show-file-list="false"
|
||||
@success="handleUploadSuccess"
|
||||
@error="handleUploadError"
|
||||
@@ -250,9 +251,15 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { getToken } from '@/utils/auth';
|
||||
|
||||
const uploadHeaders = computed(() => {
|
||||
const token = getToken();
|
||||
return token ? { Authorization: `Bearer ${token}` } : {};
|
||||
});
|
||||
|
||||
interface BannerItem {
|
||||
id: number;
|
||||
@@ -332,7 +339,11 @@
|
||||
const handleUploadSuccess = (fileItem: any) => {
|
||||
const response = fileItem.response;
|
||||
if (response && response.code === 20000 && response.data?.url) {
|
||||
formData.value.image = response.data.url;
|
||||
let url = response.data.url;
|
||||
if (url.startsWith('/uploads/')) {
|
||||
url = `https://peiww.gynhc.com${url}`;
|
||||
}
|
||||
formData.value.image = url;
|
||||
Message.success('轮播图片上传成功并已持久化');
|
||||
} else {
|
||||
Message.warning('上传完成,但响应格式异常');
|
||||
|
||||
@@ -355,6 +355,7 @@
|
||||
<a-upload
|
||||
action="/api/upload"
|
||||
name="file"
|
||||
:headers="uploadHeaders"
|
||||
:show-file-list="false"
|
||||
@success="handlePopupUploadSuccess"
|
||||
@error="handlePopupUploadError"
|
||||
@@ -1085,6 +1086,12 @@
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import axios from 'axios';
|
||||
import { getToken } from '@/utils/auth';
|
||||
|
||||
const uploadHeaders = computed(() => {
|
||||
const token = getToken();
|
||||
return token ? { Authorization: `Bearer ${token}` } : {};
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
|
||||
@@ -349,8 +349,8 @@ function requireAdmin(req: Request, res: Response, next: NextFunction): void {
|
||||
// 仅依据服务端签名校验通过的 admin token 判定管理员请求;
|
||||
// 严禁信任任何客户端可伪造的角色字段(operatorRole / x-user-role)。
|
||||
function isVerifiedAdminRequest(req: Request): boolean {
|
||||
const auth = String(req.headers['authorization'] || '');
|
||||
const token = auth.startsWith('Bearer ') ? auth.slice(7).trim() : '';
|
||||
const auth = String(req.headers['authorization'] || req.headers['x-admin-token'] || '');
|
||||
const token = auth.startsWith('Bearer ') ? auth.slice(7).trim() : auth.trim();
|
||||
return Boolean(token) && verifyAdminToken(token);
|
||||
}
|
||||
|
||||
@@ -714,12 +714,16 @@ app.all('/api/user/certification', (_req: Request, res: Response) => {
|
||||
// 真实文件与图片对象存储上传接口 (支持单图上传并持久化至 public/uploads)
|
||||
const handleFileUpload = (req: Request, res: Response) => {
|
||||
// 上传要求登录,避免匿名上传被滥用(刷盘/托管非法内容)。
|
||||
// 兼容管理后台管理员身份与小程序客户端用户身份。
|
||||
// 不依赖 NODE_ENV:只有显式 ALLOW_INSECURE_DEV_AUTH=1 的本地联调才放开。
|
||||
if (SECURE_MODE) {
|
||||
try {
|
||||
resolveSecureUserId(req, 0, '上传文件');
|
||||
} catch (e: any) {
|
||||
return res.status(e.status || 401).json({ code: e.code || 40100, message: e.message || '请先登录后再上传' });
|
||||
const isAdmin = isVerifiedAdminRequest(req);
|
||||
if (!isAdmin) {
|
||||
try {
|
||||
resolveSecureUserId(req, 0, '上传文件');
|
||||
} catch (e: any) {
|
||||
return res.status(e.status || 401).json({ code: e.code || 40100, message: e.message || '请先登录后再上传' });
|
||||
}
|
||||
}
|
||||
}
|
||||
upload.single('file')(req, res, (err: any) => {
|
||||
|
||||
在新工单中引用
屏蔽一个用户