Enhance avatar URL handling by adding normalization logic to ensure consistent URL formats across user and admin profiles. Update environment template with detailed comments on BASE_URL and FORCE_HTTPS settings. Improve upload route to utilize normalized avatar URLs.
This commit is contained in:
parent
15413c85cc
commit
90505cba5f
@ -16,15 +16,29 @@ NODE_ENV=development
|
||||
# 服务端口(默认3000)
|
||||
PORT=3000
|
||||
|
||||
# 基础URL(用于生成完整的文件URL,支持HTTPS)
|
||||
# 基础URL(用于生成完整的文件URL和头像URL,支持HTTPS)
|
||||
# 示例:https://api.example.com 或 http://localhost:3000
|
||||
# 如果设置了此项,上传接口返回的URL将使用此值
|
||||
# 如果不设置,将根据请求自动检测协议(支持反向代理)
|
||||
#
|
||||
# 作用:
|
||||
# 1. 如果设置了此项,上传接口返回的URL将使用此值
|
||||
# 2. 头像URL存入数据库时会使用此值(如果是相对路径)
|
||||
# 3. 如果不设置,将根据请求自动检测协议(支持反向代理)
|
||||
#
|
||||
# 推荐设置场景:
|
||||
# - 生产环境:设置为完整的 HTTPS URL,如 https://api.example.com
|
||||
# - 开发环境:可以留空,系统会自动检测
|
||||
BASE_URL=
|
||||
|
||||
# 强制使用HTTPS(true/false)
|
||||
# 如果设置为 true,所有返回的URL将使用 https://
|
||||
# 适用于部署在反向代理(如nginx)后面的情况
|
||||
#
|
||||
# 作用:
|
||||
# 1. 如果设置为 true,所有返回的URL(包括头像URL)将使用 https://
|
||||
# 2. 适用于部署在反向代理(如nginx)后面的情况
|
||||
# 3. 当反向代理未正确设置 X-Forwarded-Proto 头时,可以使用此选项强制使用 HTTPS
|
||||
#
|
||||
# 注意:
|
||||
# - 如果设置了 BASE_URL 且 BASE_URL 是 https://,此选项会被忽略
|
||||
# - 如果传入的头像URL已经是 https://,会保持原样,不会被此选项影响
|
||||
FORCE_HTTPS=false
|
||||
|
||||
# ------------------------------------------
|
||||
@ -78,6 +92,26 @@ UPLOAD_MAX_SIZE=10
|
||||
# 上传文件存储路径
|
||||
UPLOAD_PATH=uploads
|
||||
|
||||
# ------------------------------------------
|
||||
# 头像URL处理说明
|
||||
# ------------------------------------------
|
||||
# 头像URL存入数据库时的处理规则:
|
||||
#
|
||||
# 1. 如果传入的URL是 https:// 开头,会保持 https://
|
||||
# 2. 如果传入的URL是 http:// 开头,会保持 http://
|
||||
# 3. 如果是相对路径(如 /uploads/xxx.jpg),会根据以下优先级处理:
|
||||
# a) 如果设置了 BASE_URL,使用 BASE_URL + 相对路径
|
||||
# b) 如果设置了 FORCE_HTTPS=true,使用 https:// + 主机地址 + 相对路径
|
||||
# c) 检测 X-Forwarded-Proto 头(反向代理设置),如果为 https 则使用 https
|
||||
# d) 使用 req.protocol(Express 自动检测)
|
||||
# e) 默认使用 http://
|
||||
#
|
||||
# 反向代理配置建议(nginx):
|
||||
# proxy_set_header X-Forwarded-Proto $scheme;
|
||||
# proxy_set_header X-Forwarded-Host $host;
|
||||
#
|
||||
# 这样系统可以自动识别 HTTPS 请求,无需设置 FORCE_HTTPS
|
||||
|
||||
# ------------------------------------------
|
||||
# 其他配置(可选)
|
||||
# ------------------------------------------
|
||||
|
||||
@ -468,7 +468,13 @@ class AdminController {
|
||||
async updateProfile(req, res) {
|
||||
try {
|
||||
const { real_name, phone, avatar } = req.body;
|
||||
await req.admin.update({ real_name, phone, avatar });
|
||||
const updateData = { real_name, phone };
|
||||
// 规范化头像URL:如果是 https 则保持 https,否则是 http
|
||||
if (avatar) {
|
||||
const { normalizeAvatarUrl } = require('../utils/helper');
|
||||
updateData.avatar = normalizeAvatarUrl(avatar, req);
|
||||
}
|
||||
await req.admin.update(updateData);
|
||||
res.json(success(null, '更新成功'));
|
||||
} catch (err) {
|
||||
console.error('更新个人信息失败:', err);
|
||||
|
||||
@ -3,7 +3,7 @@ const axios = require('axios');
|
||||
const crypto = require('crypto');
|
||||
const QRCode = require('qrcode');
|
||||
const { User, LadderUser, Store, Match, MatchGame } = require('../models');
|
||||
const { generateMemberCode, success, error, calculateDistance, getFullUrl } = require('../utils/helper');
|
||||
const { generateMemberCode, success, error, calculateDistance, getFullUrl, normalizeAvatarUrl } = require('../utils/helper');
|
||||
const { LADDER_LEVEL_NAMES } = require('../config/constants');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
@ -123,13 +123,15 @@ class UserController {
|
||||
|
||||
if (!user) {
|
||||
// 新用户注册
|
||||
// 规范化头像URL:如果是 https 则保持 https,否则是 http
|
||||
const normalizedAvatar = avatar ? normalizeAvatarUrl(avatar, req) : '';
|
||||
user = await User.create({
|
||||
openid,
|
||||
unionid,
|
||||
phone,
|
||||
member_code: generateMemberCode(),
|
||||
nickname: nickname || '新用户',
|
||||
avatar: avatar || '',
|
||||
avatar: normalizedAvatar,
|
||||
gender: gender || 0,
|
||||
status: 1
|
||||
});
|
||||
@ -140,7 +142,8 @@ class UserController {
|
||||
// 更新用户信息
|
||||
const updateData = { phone };
|
||||
if (nickname) updateData.nickname = nickname;
|
||||
if (avatar) updateData.avatar = avatar;
|
||||
// 规范化头像URL:如果是 https 则保持 https,否则是 http
|
||||
if (avatar) updateData.avatar = normalizeAvatarUrl(avatar, req);
|
||||
if (gender !== undefined) updateData.gender = gender;
|
||||
|
||||
await user.update(updateData);
|
||||
@ -234,7 +237,8 @@ class UserController {
|
||||
|
||||
const updateData = {};
|
||||
if (nickname) updateData.nickname = nickname;
|
||||
if (avatar) updateData.avatar = avatar;
|
||||
// 规范化头像URL:如果是 https 则保持 https,否则是 http
|
||||
if (avatar) updateData.avatar = normalizeAvatarUrl(avatar, req);
|
||||
if (gender !== undefined) updateData.gender = gender;
|
||||
|
||||
await user.update(updateData);
|
||||
|
||||
@ -4,7 +4,7 @@ const multer = require('multer');
|
||||
const path = require('path');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const { authAdmin, authUser } = require('../middlewares/auth');
|
||||
const { success, error, getFullUrl } = require('../utils/helper');
|
||||
const { success, error, getFullUrl, normalizeAvatarUrl } = require('../utils/helper');
|
||||
|
||||
// 配置文件存储
|
||||
const storage = multer.diskStorage({
|
||||
@ -66,7 +66,8 @@ router.post('/avatar', authUser, upload.single('file'), (req, res) => {
|
||||
}
|
||||
|
||||
const relativePath = `/uploads/${req.file.filename}`;
|
||||
const fullUrl = getFullUrl(relativePath, req);
|
||||
// 使用 normalizeAvatarUrl 确保 https 保持 https,http 保持 http
|
||||
const fullUrl = normalizeAvatarUrl(relativePath, req);
|
||||
res.json(success({ url: fullUrl }, '上传成功'));
|
||||
});
|
||||
|
||||
|
||||
@ -91,6 +91,65 @@ function pageResult(list, total, page, pageSize) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化头像URL(存入数据库时使用)
|
||||
* 如果是 https 则保持 https,否则是 http
|
||||
* @param {string} url - 头像URL(可能是完整URL或相对路径)
|
||||
* @param {object} req - Express request对象(用于获取host)
|
||||
* @returns {string} 规范化后的URL
|
||||
*/
|
||||
function normalizeAvatarUrl(url, req) {
|
||||
if (!url) return '';
|
||||
|
||||
// 如果已经是完整URL,保持原样(https保持https,http保持http)
|
||||
if (url.startsWith('https://')) {
|
||||
return url;
|
||||
}
|
||||
if (url.startsWith('http://')) {
|
||||
return url;
|
||||
}
|
||||
|
||||
// 相对路径,根据请求判断协议
|
||||
// 支持反向代理(nginx等)的 HTTPS 检测
|
||||
let protocol = 'http';
|
||||
if (req) {
|
||||
// 优先使用 X-Forwarded-Proto 头(反向代理设置)
|
||||
const forwardedProto = req.get('X-Forwarded-Proto');
|
||||
if (forwardedProto) {
|
||||
protocol = forwardedProto;
|
||||
} else {
|
||||
// 使用 req.protocol(Express 会自动处理)
|
||||
protocol = req.protocol;
|
||||
}
|
||||
|
||||
// 如果设置了环境变量强制使用 HTTPS
|
||||
if (process.env.FORCE_HTTPS === 'true') {
|
||||
protocol = 'https';
|
||||
}
|
||||
}
|
||||
|
||||
// 获取主机地址
|
||||
let host = `localhost:${process.env.PORT || 3000}`;
|
||||
if (req) {
|
||||
// 优先使用 X-Forwarded-Host(反向代理设置)
|
||||
const forwardedHost = req.get('X-Forwarded-Host');
|
||||
if (forwardedHost) {
|
||||
host = forwardedHost;
|
||||
} else {
|
||||
host = req.get('host') || host;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果设置了 BASE_URL 环境变量,直接使用
|
||||
if (process.env.BASE_URL) {
|
||||
const baseUrl = process.env.BASE_URL.replace(/\/$/, ''); // 移除末尾斜杠
|
||||
const pathWithoutLeadingSlash = url.startsWith('/') ? url : `/${url}`;
|
||||
return `${baseUrl}${pathWithoutLeadingSlash}`;
|
||||
}
|
||||
|
||||
return `${protocol}://${host}${url}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取完整URL(处理头像等资源路径)
|
||||
* @param {string} path - 相对路径或完整URL
|
||||
@ -155,5 +214,6 @@ module.exports = {
|
||||
success,
|
||||
error,
|
||||
pageResult,
|
||||
getFullUrl
|
||||
getFullUrl,
|
||||
normalizeAvatarUrl
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user