feat:修改docekt配置添加ossCROS的配置

This commit is contained in:
zerosaturation 2026-06-24 16:30:00 +08:00
parent a96c9cf04f
commit 64b501102b
15 changed files with 165 additions and 341 deletions

View File

@ -118,4 +118,4 @@ OPENAI_API_KEY=sk-eIOujD5rUugIRIPecFi3I2rFr6Bhxx1jsRzRm6phyNeeKrCI
# 微达API BaseURL必须含 /v1 后缀,代码会拼成 /v1/images/edits
OPENAI_BASE_URL=https://api.weda.cc/v1
# 中转站实际暴露的 image 模型
OPENAI_MODEL=gpt-image-2
OPENAI_MODEL=gpt-image-2

View File

@ -113,3 +113,7 @@ require (
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
// backend/scripts/set-oss-cors import gateway/config( stdlib,)
// Dockerfile.services module , go.work, replace
replace github.com/topfans/backend/gateway => ./gateway

View File

@ -0,0 +1,100 @@
// set-oss-cors/main.go
// 给 OSS bucket 设置 CORS 规则,允许浏览器跨域读取预签名 URL。
// 背景:
// - 前端 useLaserSegment.downloadToLocal 通过 fetch 直连 OSS 拉取 cutout/png
// - OSS bucket 未配 CORS 时,浏览器在 CORS 预检后中断响应,
// 表现为 net::ERR_CONTENT_LENGTH_MISMATCH 200 (OK) 或 TypeError: Failed to fetch
//
// 用法:
//
// go run scripts/set-oss-cors/main.go
//
// (会自动从 backend/.env / docker/.env.prod 读取 OSS_REGION / OSS_BUCKET_NAME /
// OSS_ACCESS_KEY_ID / OSS_ACCESS_KEY_SECRET)
//
// 注意:aliyun-oss-go-sdk v3.0.2 的 CORS 方法挂在 *Client 上,不是 *Bucket。
package main
import (
"fmt"
"os"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"github.com/joho/godotenv"
"github.com/topfans/backend/gateway/config"
)
func main() {
// 多路径 fallback:本地 .env / docker/.env.prod / docker/.env / .env
for _, p := range []string{
"backend/.env",
"../.env",
".env",
"docker/.env.prod",
"docker/.env",
"../docker/.env.prod",
"../docker/.env",
} {
if _, err := os.Stat(p); err == nil {
_ = godotenv.Load(p)
}
}
cfg := config.Load()
fmt.Println("=== SetBucketCORS ===")
fmt.Printf("Bucket: %s Region: %s\n", cfg.OSS.BucketName, cfg.OSS.Region)
fmt.Printf("AK: %s\n", mask(cfg.OSS.AccessKeyID))
endpoint := fmt.Sprintf("https://oss-%s.aliyuncs.com", cfg.OSS.Region)
client, err := oss.New(endpoint, cfg.OSS.AccessKeyID, cfg.OSS.AccessKeySecret)
if err != nil {
fmt.Printf("FAIL 创建 OSS 客户端: %v\n", err)
os.Exit(1)
}
// 全域名 * 放行 (用户确认: top-fans-test 为测试桶, 预签名 URL 仍需签名才能访问)
//
// ⚠️ 重要: 阿里云 OSS 的 AllowedMethod 不支持 OPTIONS。
// 浏览器 CORS 预检的 OPTIONS 请求由 OSS 隐式处理(无需显式列出)。
// 加上 OPTIONS 会触发 400 InvalidArgument: "CORS Rule AllowedMethod Format Error"。
//
// Method 集合:
// GET - H5 fetch 拉 cutout / variant PNG (浏览器读图)
// HEAD - 上传后取文件 size
// POST - H5 PostObject 直传(uni-app H5 在浏览器跑时必须走 POST)
rule := oss.CORSRule{
AllowedOrigin: []string{"*"},
AllowedMethod: []string{"GET", "HEAD", "POST"},
AllowedHeader: []string{"*"},
ExposeHeader: []string{"ETag", "Content-Length", "Content-Type", "x-oss-request-id"},
MaxAgeSeconds: 3600,
}
// SDK v3.0.2: CORS 方法在 *Client 上
if err := client.SetBucketCORS(cfg.OSS.BucketName, []oss.CORSRule{rule}); err != nil {
fmt.Printf("FAIL SetBucketCORS: %v\n", err)
os.Exit(1)
}
// 立即读回验证
got, gErr := client.GetBucketCORS(cfg.OSS.BucketName)
if gErr != nil {
fmt.Printf("WARN GetBucketCORS 回读失败: %v\n", gErr)
} else {
fmt.Printf("OK 当前 bucket CORS 规则数: %d\n", len(got.CORSRules))
for i, r := range got.CORSRules {
fmt.Printf(" [%d] Origin=%v Method=%v Header=%v MaxAge=%d\n",
i, r.AllowedOrigin, r.AllowedMethod, r.AllowedHeader, r.MaxAgeSeconds)
}
}
fmt.Println("✅ CORS 配置已生效(可能有几秒传播延迟)")
}
func mask(s string) string {
if len(s) <= 8 {
return "***"
}
return s[:4] + "****" + s[len(s)-4:]
}

View File

@ -1,48 +0,0 @@
# ===================================================================
# TopFans Docker - 本机开发环境真实凭据untracked不进 git
# 由 docker-compose --env-file .env.local --env-file .env.local.dev 叠加读入
# ===================================================================
# 宿主机手起的 topfans-postgres
DB_USER=haihuizhu
DB_PASSWORD=admin
DB_NAME=top-fans
DB_PORT=5432
DB_HOST=host.docker.internal
# 宿主机手起的 topfans-redis重建后通过容器名 topfans-redis:6379 直连)
REDIS_HOST=topfans-redis
REDIS_PORT=6379
REDIS_PASSWORD=123456
# ==================== 镭射卡 OpenAI 中转站路径(覆盖 .env.local 的默认值)====================
# 前端 VITE_API_BASE_URL=http://localhost:18090 → 打到 docker gateway (18090)
# 所以 LASER_GEN_PROVIDER 必须在这里改,docker-compose.override.yml 第 92 行
# LASER_GEN_PROVIDER: ${LASER_GEN_PROVIDER:-minimax}
# 会把这里注入到容器,覆盖 .env.local 的 minimax 默认值
LASER_GEN_PROVIDER=openai
# 微达API BaseURL必须含 /v1 后缀,代码会拼成 /v1/images/edits
# 直连 OpenAI 官方会被墙,这里走微达API中转
OPENAI_BASE_URL=https://api.weda.cc/v1
# openai_client.go 的 buildEditFields() 根据 model 名前缀自动选参数集
# - gpt-image-* → 完整参数(transparent + 1024x1536 竖版)
# - 其他 → 基础参数(1024x1024 square,无 transparent,保守路径)
OPENAI_MODEL=gpt-image-2
# ⚠️ 微达API key —— 务必先在 https://api.weda.cc 撤销旧 key 再填新的
OPENAI_API_KEY=sk-eIOujD5rUugIRIPecFi3I2rFr6Bhxx1jsRzRm6phyNeeKrCI
# ==================== Dify 配置保留(暂未切回 dify,留着方便回滚)====================
# gateway 容器的 DIFY_API_BASE 默认是 https://api.dify.ai/v1生产 SaaS
# 本机要走自起的 Difyproject=difynginx 暴露在 host:80 → /v1 路由到 api:5001
#
# ⚠️ 不能用 host.docker.internalDocker Desktop 的 com.docker.backend.exe
# 抢占了 host 的 0.0.0.0:80host.docker.internal (192.168.65.254) 走不通。
# 用 host 真实 IP 172.23.0.1 直连 host:80Dify nginx bind 在这)
DIFY_API_BASE=http://172.23.0.1/v1
# 从 Dify 数据库里 laser_card_variants_v1 这个 app 的 api_tokens 表里取出来的
DIFY_API_KEY=app-Ibs7reARanyuYGZ7zrLyiM6e
# ==================== JWT_SECRET ====================
# 本机用生产同款 secret让生产签发的 token 在本机 gateway 也能验签通过
# ⚠️ 本地开发用,绝对不要把生产 secret 提交到 git
JWT_SECRET=topfans-secret-key-please-change-in-production

View File

@ -60,7 +60,10 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" \
echo "Built notificationservice" && \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" \
-o /tmp/moderationservice services/moderationService/main.go && \
echo "Built moderationservice"
echo "Built moderationservice" && \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" \
-o /tmp/set-oss-cors scripts/set-oss-cors/main.go && \
echo "Built set-oss-cors"
# ---- Runtime Stage: Gateway ----
FROM --platform=linux/amd64 alpine:3.19 AS gateway
@ -245,3 +248,13 @@ HEALTHCHECK --interval=10s --timeout=5s --start-period=15s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:20011 || exit 1
ENTRYPOINT ["/app/moderationservice"]
# ---- Runtime Stage: OSS CORS Init (一次性,跑完即退,设置 bucket CORS) ----
FROM --platform=linux/amd64 alpine:3.19 AS oss-cors-init
RUN apk add --no-cache ca-certificates tzdata
WORKDIR /app
COPY --from=builder /tmp/set-oss-cors /app/set-oss-cors
ENTRYPOINT ["/app/set-oss-cors"]

View File

@ -78,7 +78,7 @@ while [[ $# -gt 0 ]]; do
echo "服务名 (可选):"
echo " gateway, userService, socialService, assetService,"
echo " galleryService, activityService, taskService, starbookService, aiChatService,"
echo " laserCompositor, statisticService"
echo " statisticService"
echo " notificationService, moderationService"
echo ""
echo "示例:"
@ -104,6 +104,7 @@ while [[ $# -gt 0 ]]; do
statistic|statisticService|statisticservice) SERVICES+=("statisticservice") ;;
notification|notificationService|notificationservice) SERVICES+=("notificationservice") ;;
moderation|moderationService|moderationservice) SERVICES+=("moderationservice") ;;
oss-cors-init|osS-cors-init|ossCorsInit) SERVICES+=("oss-cors-init") ;;
all)
# all 关键字,构建所有服务
SERVICES=()
@ -122,7 +123,7 @@ done
# ==================== 服务列表 ====================
# 所有可用服务及其配置(使用小写 target 名)
ALL_SERVICES_NAME=("gateway" "userservice" "socialservice" "assetservice" "galleryservice" "activityservice" "taskservice" "starbookservice" "aichatservice" "lasercompositor" "statisticservice" "notificationservice" "moderationservice")
ALL_SERVICES_NAME=("gateway" "userservice" "socialservice" "assetservice" "galleryservice" "activityservice" "taskservice" "starbookservice" "aichatservice" "statisticservice" "notificationservice" "moderationservice" "oss-cors-init")
# 确定要构建的服务
if [ ${#SERVICES[@]} -eq 0 ]; then
@ -212,10 +213,10 @@ main() {
taskservice) docker_target="taskservice" ;;
starbookservice) docker_target="starbookservice" ;;
aichatservice) docker_target="aichatservice" ;;
lasercompositor) docker_target="lasercompositor" ;;
statisticservice) docker_target="statisticservice" ;;
notificationservice) docker_target="notificationservice" ;;
moderationservice) docker_target="moderationservice" ;;
oss-cors-init) docker_target="oss-cors-init" ;;
# 兼容旧的大写服务名
userService) docker_target="userservice" ;;
socialService) docker_target="socialservice" ;;
@ -228,6 +229,7 @@ main() {
statisticservice) docker_target="statisticservice" ;;
notificationService) docker_target="notificationservice" ;;
moderationService) docker_target="moderationservice" ;;
ossCorsInit) docker_target="oss-cors-init" ;;
*) docker_target="$service" ;;
esac

View File

@ -79,10 +79,10 @@ SERVICES=(
"taskservice"
"starbookservice"
"aichatservice"
"lasercompositor"
"statisticservice"
"notificationservice"
"moderationservice"
"oss-cors-init"
)
# ==================== 服务器配置 ====================

View File

@ -1,74 +0,0 @@
# ===================================================================
# TopFans Docker - 本机基础设施 (postgres + redis)
# untracked不进 git。和 docker-compose.local.yml 一起用:
# docker compose \
# -f docker-compose.local.yml \
# -f docker-compose.override.yml \
# -f docker-compose.infra.yml \
# --env-file .env --env-file .env.local --env-file .env.local.dev \
# up -d
# ===================================================================
# 用 named volumetopfans-postgres-data / topfans-redis-data
# 启动前需要从匿名 volume 迁移数据:
# docker volume create topfans-postgres-data
# docker run --rm -v 9d506bae...:/from -v topfans-postgres-data:/to \
# alpine sh -c "cp -a /from/. /to/ && rm -f /to/postmaster.pid"
# docker volume create topfans-redis-data
# docker run --rm -v 06828e1c...:/from -v topfans-redis-data:/to \
# alpine sh -c "cp -a /from/. /to/"
volumes:
# external: true 因为这两个 volume 是手动创建并迁移数据的
# 后续如果想让 compose 管理,可以去掉 external 并 docker volume rm 后重启
topfans-postgres-data:
name: topfans-postgres-data
external: true
topfans-redis-data:
name: topfans-redis-data
external: true
networks:
topfans-net:
name: docker_topfans-net # 跟 business compose 共享同一个网络
services:
postgres:
image: postgres:16
container_name: topfans-postgres
restart: unless-stopped
environment:
POSTGRES_USER: haihuizhu
POSTGRES_PASSWORD: admin
POSTGRES_DB: top-fans
ports:
# 保留 host port 5432 映射,兼容 .env.local.dev 里 DB_HOST=host.docker.internal:5432
- "5432:5432"
volumes:
- topfans-postgres-data:/var/lib/postgresql/data
networks:
- topfans-net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U haihuizhu -d top-fans"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
redis:
image: redis:7.4.9
container_name: topfans-redis
restart: unless-stopped
command: ["redis-server", "--requirepass", "123456"]
# 注意:不映射 host port 6379被 txw-cloud-redis 占了)
# 业务容器通过 docker_topfans-net 网络用容器名 topfans-redis:6379 直连
volumes:
- topfans-redis-data:/data
networks:
- topfans-net
healthcheck:
test: ["CMD", "redis-cli", "-a", "123456", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 5s

View File

@ -1,96 +0,0 @@
# ===================================================================
# TopFans Docker - 本机开发环境 service 级 overrideuntracked不进 git
# 与 docker-compose.local.yml 合并;只覆盖本机需要的字段
# 用法:
# docker compose -f docker-compose.local.yml -f docker-compose.override.yml \
# --env-file .env --env-file .env.local --env-file .env.local.dev up -d
# ===================================================================
# 覆盖所有业务的 DB/REDIS 凭据(指向本机手起的容器)
# 注意:这里只覆盖 environment,不重建容器,其它字段都继承自 compose
x-override-env: &override-env
DB_HOST: ${DB_HOST}
DB_PORT: ${DB_PORT}
DB_USER: ${DB_USER}
DB_PASSWORD: ${DB_PASSWORD}
DB_NAME: ${DB_NAME}
REDIS_HOST: ${REDIS_HOST}
REDIS_PORT: ${REDIS_PORT}
REDIS_PASSWORD: ${REDIS_PASSWORD}
# SMS dummy (本机不发短信,绕过启动期 FATAL)
SMS_ACCESS_KEY_ID: ${SMS_ACCESS_KEY_ID:-dummy}
SMS_ACCESS_KEY_SECRET: ${SMS_ACCESS_KEY_SECRET:-dummy}
# JWT_SECRET让所有 Dubbo serviceuserservice 等签 token 的)
# 跟 gateway 验签用同一个 secret否则会 401
# ⚠️ 必须跟 .env.local.dev 里 JWT_SECRET 的值匹配
JWT_SECRET: ${JWT_SECRET}
services:
userservice:
environment:
<<: *override-env
assetservice:
environment:
<<: *override-env
# assetService 读 USER_SERVICE_URL不是 DUBBO_USER_SERVICE_URL
# compose 原件设的是 DUBBO_USER_SERVICE_URL导致 fallback 到
# tri://localhost:20000 连不上 userservice → crystal_balance 返回 0
USER_SERVICE_URL: tri://userservice:20000
socialservice:
environment:
<<: *override-env
galleryservice:
environment:
<<: *override-env
activityservice:
environment:
<<: *override-env
taskservice:
environment:
<<: *override-env
starbookservice:
environment:
<<: *override-env
aichatservice:
environment:
<<: *override-env
statisticservice:
# statisticservice 用自己一套 STATISTIC_* 变量,跟 x-common-env 不同
environment:
STATISTIC_DB_HOST: ${DB_HOST}
STATISTIC_DB_PORT: ${DB_PORT}
STATISTIC_DB_USER: ${DB_USER}
STATISTIC_DB_PASSWORD: ${DB_PASSWORD}
STATISTIC_DB_NAME: ${DB_NAME}
STATISTIC_REDIS_HOST: ${REDIS_HOST}
STATISTIC_REDIS_PORT: ${REDIS_PORT}
STATISTIC_REDIS_PASSWORD: ${REDIS_PASSWORD}
gateway:
environment:
<<: *override-env
# gateway 决定镭射卡走哪条路径。compose 原件没引用这个变量,
# 这里补上,让 .env.local.dev 里的 LASER_GEN_PROVIDER 真正进容器
# (LASER_GEN_PROVIDER 的值由 .env.local.dev 控制,默认 openai)
LASER_GEN_PROVIDER: ${LASER_GEN_PROVIDER:-openai}
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
OPENAI_BASE_URL: ${OPENAI_BASE_URL:-https://api.weda.cc/v1}
OPENAI_MODEL: ${OPENAI_MODEL:-gpt-image-2}
# 本机用生产同款 JWT_SECRET生产 token 能直接用本机 gateway 验签)
# ⚠️ 仅本地开发用,生产不要用这个 secret
JWT_SECRET: ${JWT_SECRET:-topfans-secret-key-local-dev-only}
# 覆盖 host port 映射:宿主 8080/8090 被 Windows/Docker Desktop 锁
# PowerShell/netstat 都看不到进程,但 docker run 也 bind 失败),
# 改用 18090:8080。18080 被 txw-cloud-nacos 占了18090 跟它成一对好记。
# compose 默认会 merge list用 !override 强制替换 base 的 ["8080:8080"]
ports: !override
- "18090:8080"

View File

@ -564,6 +564,24 @@ services:
memory: 128M
cpus: '0.25'
# ==================== OSS CORS Init (一次性,跑完即退) ====================
# 部署时自动调阿里云 OSS API 给 bucket 推 CORS 规则(POST 直传必需)。
# restart: "no" 保证只跑一次,失败会显式退出非 0,deploy.sh 能看到错误。
# 复用 .env.prod 里现有的 OSS_REGION / OSS_BUCKET_NAME / OSS_ACCESS_KEY_*,
# 无需新增任何环境变量。
oss-cors-init:
image: topfans/oss-cors-init:latest
build:
context: ..
dockerfile: docker/Dockerfile.services
target: oss-cors-init
container_name: topfans-oss-cors-init
restart: "no"
env_file:
- .env.prod
networks:
- topfans-net
# ==================== API Gateway ====================
gateway:
image: topfans/gateway:latest
@ -589,11 +607,12 @@ services:
DUBBO_STATISTIC_SERVICE_URL: tri://statisticservice:20009
DUBBO_NOTIFICATION_SERVICE_URL: tri://notificationservice:20010
DUBBO_MODERATION_SERVICE_URL: tri://moderationservice:20011
LASER_COMPOSITOR_URL: http://lasercompositor:7002
# 镭射卡 AI 生成OpenAI 中转站 — 微达API通过 .env.prod 注入 API Key
# 注意:不能在 environment: 块里用 ${OPENAI_API_KEY:-} 这种 shell 变量展开语法,
# 因为这会读取 HOST shell 的环境变量,而不是 .env.prod 里的值,且会覆盖 env_file。
# OPENAI_API_KEY 必须且只能从 env_file: .env.prod 读取。
LASER_GEN_PROVIDER: ${LASER_GEN_PROVIDER:-openai}
OPENAI_BASE_URL: ${OPENAI_BASE_URL:-https://api.weda.cc/v1}
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
OPENAI_MODEL: ${OPENAI_MODEL:-gpt-image-2}
# 抠图人像扣底、OSS、Dify、JWT、Redis 全部走 env_file: .env.prod
REDIS_HOST: topfans-redis

View File

@ -1,11 +1,11 @@
# 开发环境配置
# HBuilderX「运行」时自动加载;CLI 用 --mode development
VITE_API_BASE_URL=http://192.168.110.60:8080
# VITE_API_BASE_URL=https://api.topfans.online
# VITE_API_BASE_URL=http://192.168.110.60:8080
VITE_API_BASE_URL=https://api.topfans.online
# WebSocket 地址:如与 API 同源可省略(自动从 VITE_API_BASE_URL 推导 http→ws、https→wss
# 独立部署时直接覆盖例如ws://192.168.110.60:8081
VITE_WS_BASE_URL=ws://192.168.110.60:8080
# VITE_WS_BASE_URL=wss://api.topfans.online
# VITE_WS_BASE_URL=ws://192.168.110.60:8080
VITE_WS_BASE_URL=wss://api.topfans.online
# WebSocket 路径:用于 Nginx 反向代理(前端连接的完整 URL = VITE_WS_BASE_URL + VITE_WS_AI_CHAT_PATH
# 需与后端 backend/.env 的 WS_AI_CHAT_PATH 保持一致
# Nginx 示例location /ai-chat { proxy_pass http://gateway:8080; ... }

View File

@ -1,9 +1,9 @@
{
"name" : "TopFans",
"appid" : "__UNI__8CBE431",
"appid" : "__UNI__F199FF4",
"description" : "",
"versionName" : "1.0.5",
"versionCode" : 115,
"versionCode" : 117,
"transformPx" : false,
/* 5+App */
"app-plus" : {

View File

@ -738,7 +738,11 @@ function originalFileName(filePath) {
if (filePath.startsWith('data:')) {
return 'image.jpg'
}
return filePath.split('/').pop() || 'image.jpg'
// 先剥掉 query string 和 hash 片段,避免传入 signed URL 时
// (?Expires=&Signature=&security-token=...) 把 original_name 撑爆
// 触发 materials.original_name VARCHAR(255) 越界 (SQLSTATE 22001)
const cleaned = filePath.split('?')[0].split('#')[0]
return cleaned.split('/').pop() || 'image.jpg'
} catch {
return 'image.jpg'
}

View File

@ -1,26 +1,16 @@
/**
* H5 本地开发 OSS Post 地址换为同源 /dev-oss-proxy vite.config.js 中中间件转发到 OSS 桶根 POST /
* 避免浏览器从 localhost / 局域网端口直连 *.aliyuncs.com 触发 CORS
* H5 OSS PostObject 直传 URL 解析
*
* 正式 H5 部署到业务域名时仍直连 ossData.host若遇 CORS请在 OSS 控制台配置 CORS 或使用与站点同域的反代
* 项目为 uni-app 移动端,本就走 OSS 直传(uni.uploadFile / fetch 直连 OSS )
* dev 模式下的 /dev-oss-proxy Vite 中间件转发方案已废弃( vite.config.js),
* 统一在 OSS 桶侧配 CORS 放行 POST 即可(Origin=*, Method=GET/HEAD/POST, Header=*)
*
* 保留本文件作为单一改写入口,后续如需按环境切换 URL 在这里集中处理
*
* @param {string} ossHost 签名接口返回的 host https://bucket.oss-cn-shanghai.aliyuncs.com
* @returns {string} 实际用于 fetch POST URL开发态为同源代理前缀
* @returns {string} 实际用于 POST URL(目前恒等于 ossHost)
*/
export function resolveH5OssPostUrl(ossHost) {
if (!ossHost || typeof location === 'undefined') {
return ossHost
}
const port = String(location.port || '')
const hn = location.hostname || ''
const devPortOk = ['5173', '5174', '8080', '9528'].includes(port)
const devHostOk =
hn === 'localhost' ||
hn === '127.0.0.1' ||
/^192\.168\./.test(hn) ||
/^10\./.test(hn)
if (devPortOk && devHostOk) {
return `${location.protocol}//${location.host}/dev-oss-proxy`
}
if (!ossHost) return ossHost
return ossHost
}

View File

@ -1,90 +0,0 @@
import { defineConfig } from 'vite'
import https from 'node:https'
import { createRequire } from 'node:module'
// @dcloudio/vite-plugin-uni v3 alpha 的 dist 是 CJS
// 且 __esModule 标志为非枚举Node 的 CJS-to-ESM 互操作
// 不会把它当 TS 编译产物,于是 `import uni from ...` 拿到的
// 是整个 module.exports 对象而不是函数,调用就报
// "uni is not a function"。用 createRequire 直走 CJS 拿 default。
const require = createRequire(import.meta.url)
const uni = require('@dcloudio/vite-plugin-uni').default
/** 与 upload-signature 返回的 OSS 虚拟域名一致;换桶时请同步 */
const OSS_DEV_HOST = 'top-fans-test.oss-cn-shanghai.aliyuncs.com'
/**
* 部分环境下 Vite 内置 server.proxy rewrite POST 未生效OSS 仍收到路径
* /dev-oss-proxy从而 405ResourceType: Object。此处用中间件固定转发到桶根 POST /
*/
function ossDevPostProxyPlugin() {
return {
name: 'oss-dev-post-proxy',
configureServer(server) {
server.middlewares.use((req, res, next) => {
const raw = req.url || ''
if (!raw.startsWith('/dev-oss-proxy')) {
return next()
}
if (req.method === 'OPTIONS') {
res.statusCode = 204
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS')
res.setHeader(
'Access-Control-Allow-Headers',
String(req.headers['access-control-request-headers'] || '*')
)
res.end()
return
}
if (req.method !== 'POST') {
res.statusCode = 405
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
res.end('OSS dev proxy only allows POST')
return
}
const hop = new Set([
'connection',
'keep-alive',
'proxy-authenticate',
'proxy-authorization',
'te',
'trailers',
'transfer-encoding',
'upgrade'
])
/** @type {import('node:http').OutgoingHttpHeaders} */
const headers = {}
for (const [k, v] of Object.entries(req.headers)) {
if (v === undefined || hop.has(k.toLowerCase())) continue
headers[k] = v
}
headers.host = OSS_DEV_HOST
const proxyReq = https.request(
{
hostname: OSS_DEV_HOST,
port: 443,
method: 'POST',
path: '/',
headers
},
(proxyRes) => {
res.writeHead(proxyRes.statusCode || 502, proxyRes.headers)
proxyRes.pipe(res)
}
)
proxyReq.on('error', (err) => {
if (!res.headersSent) {
res.statusCode = 502
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
}
res.end(err.message)
})
req.pipe(proxyReq)
})
}
}
}
export default defineConfig({
plugins: [ossDevPostProxyPlugin(), uni()]
})