修复文件上传和下单支付流程

This commit is contained in:
yml2213
2026-06-12 19:42:24 +08:00
parent 6ada7393ef
commit a615100730
7 changed files with 61 additions and 11 deletions
+22 -2
View File
@@ -10,6 +10,7 @@ import (
"net/url" "net/url"
"path" "path"
"strings" "strings"
"sync"
"time" "time"
"hfb_sys/backend/internal/config" "hfb_sys/backend/internal/config"
@@ -21,6 +22,8 @@ import (
type Storage struct { type Storage struct {
client *minio.Client client *minio.Client
bucket string bucket string
bucketMu sync.Mutex
bucketReady bool
} }
type Object struct { type Object struct {
@@ -43,7 +46,7 @@ func NewStorage(cfg config.StorageConfig) (*Storage, error) {
} }
storage := &Storage{client: client, bucket: cfg.Bucket} storage := &Storage{client: client, bucket: cfg.Bucket}
if err := storage.ensureBucket(context.Background()); err != nil { if err := storage.ensureBucket(context.Background()); err != nil {
return nil, err return storage, err
} }
return storage, nil return storage, nil
} }
@@ -62,6 +65,9 @@ func (s *Storage) Put(ctx context.Context, scene string, header *multipart.FileH
} }
func (s *Storage) PutObject(ctx context.Context, key string, reader io.Reader, size int64, contentType string, metadata map[string]string) error { func (s *Storage) PutObject(ctx context.Context, key string, reader io.Reader, size int64, contentType string, metadata map[string]string) error {
if err := s.ensureBucketReady(ctx); err != nil {
return err
}
_, err := s.client.PutObject(ctx, s.bucket, key, reader, size, minio.PutObjectOptions{ _, err := s.client.PutObject(ctx, s.bucket, key, reader, size, minio.PutObjectOptions{
ContentType: contentType, ContentType: contentType,
UserMetadata: metadata, UserMetadata: metadata,
@@ -95,9 +101,23 @@ func (s *Storage) ensureBucket(ctx context.Context) error {
} }
} }
if exists { if exists {
s.bucketReady = true
return nil return nil
} }
return s.client.MakeBucket(ctx, s.bucket, minio.MakeBucketOptions{}) if err := s.client.MakeBucket(ctx, s.bucket, minio.MakeBucketOptions{}); err != nil {
return err
}
s.bucketReady = true
return nil
}
func (s *Storage) ensureBucketReady(ctx context.Context) error {
s.bucketMu.Lock()
defer s.bucketMu.Unlock()
if s.bucketReady {
return nil
}
return s.ensureBucket(ctx)
} }
func normalizeEndpoint(raw string) (string, bool, error) { func normalizeEndpoint(raw string) (string, bool, error) {
+1 -1
View File
@@ -242,7 +242,7 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
var err error var err error
fileStorage, err = filemodule.NewStorage(cfg.Storage) fileStorage, err = filemodule.NewStorage(cfg.Storage)
if err != nil { if err != nil {
logger.Warn("file storage unavailable; file APIs will return 503", zap.Error(err)) logger.Warn("file storage bucket is not ready; file APIs will retry on request", zap.Error(err))
} }
} }
fileService := filemodule.NewService(fileStorage) fileService := filemodule.NewService(fileStorage)
+1 -1
View File
@@ -17,7 +17,7 @@
X-Frame-Options "DENY" X-Frame-Options "DENY"
Referrer-Policy "strict-origin-when-cross-origin" Referrer-Policy "strict-origin-when-cross-origin"
Permissions-Policy "camera=(), microphone=(), geolocation=()" Permissions-Policy "camera=(), microphone=(), geolocation=()"
Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'" Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data: blob:; font-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'"
} }
# 聊天 SSE 长连接:关闭缓冲,逐条推送。 # 聊天 SSE 长连接:关闭缓冲,逐条推送。
@@ -241,8 +241,8 @@ async function submitOrder() {
ordering.value = true ordering.value = true
try { try {
const order = await createOrder(listing.value.id) const order = await createOrder(listing.value.id)
ElMessage.success('订单已创建,请完成支付') ElMessage.success('订单已创建,正在打开支付')
await router.push(`/orders/${order.id}`) await router.push({ path: `/orders/${order.id}`, query: { pay: '1' } })
} catch (error) { } catch (error) {
ElMessage.error(readError(error, '下单失败')) ElMessage.error(readError(error, '下单失败'))
} finally { } finally {
@@ -231,9 +231,9 @@ async function submitOrder() {
if (!listing.value) return if (!listing.value) return
ordering.value = true ordering.value = true
try { try {
await createOrder(listing.value.id) const order = await createOrder(listing.value.id)
showToast({ message: '订单已创建,请完成支付', icon: 'passed' }) showToast({ message: '订单已创建,正在打开支付', icon: 'passed' })
await router.push(`/m/orders`) await router.push({ path: `/m/orders/${order.id}`, query: { pay: '1' } })
} catch (error) { } catch (error) {
showToast({ message: readError(error, '下单失败'), icon: 'cross' }) showToast({ message: readError(error, '下单失败'), icon: 'cross' })
} finally { } finally {
@@ -90,6 +90,7 @@ const activeNames = ref<string[]>([])
const showDisputePopup = ref(false) const showDisputePopup = ref(false)
const showCounterPopup = ref(false) const showCounterPopup = ref(false)
const showRejectPopup = ref(false) const showRejectPopup = ref(false)
let autoPayHandled = false
const isOwner = computed(() => order.value?.owner_id === session.userId) const isOwner = computed(() => order.value?.owner_id === session.userId)
const isRenter = computed(() => order.value?.renter_id === session.userId) const isRenter = computed(() => order.value?.renter_id === session.userId)
@@ -139,6 +140,16 @@ async function loadOrder() {
handoffRecords.value = await fetchHandoffRecords(String(route.params.id)) handoffRecords.value = await fetchHandoffRecords(String(route.params.id))
hydrateResourceUsage() hydrateResourceUsage()
hydrateCounterForm() hydrateCounterForm()
if (
!autoPayHandled &&
route.query.pay === '1' &&
order.value?.status === 'pending_payment' &&
order.value?.renter_id === session.userId
) {
autoPayHandled = true
void router.replace({ path: route.path })
void handlePay()
}
} catch { } catch {
showToast({ message: '加载订单失败', icon: 'cross' }) showToast({ message: '加载订单失败', icon: 'cross' })
} finally { } finally {
+19
View File
@@ -33,9 +33,28 @@ function elementPlusChunk(id: string) {
return undefined return undefined
} }
function stripVantRemoteIconFont() {
return {
name: 'strip-vant-remote-icon-font',
generateBundle(_options, bundle) {
for (const chunk of Object.values(bundle)) {
if (chunk.type !== 'asset' || typeof chunk.source !== 'string') continue
if (!chunk.fileName.endsWith('.css')) continue
// Vant 图标字体已经内联了 woff2,这里去掉远程 woff 备用地址,避免触发 CSP 外链字体拦截。
chunk.source = chunk.source.replace(
/,url\(\/\/at\.alicdn\.com\/t\/c\/font_2553510_ciljc7axaw7\.woff\?t=1705587463221\) format\("woff"\)/g,
''
)
}
},
}
}
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
vue(), vue(),
stripVantRemoteIconFont(),
AutoImport({ AutoImport({
resolvers: [ElementPlusResolver()], resolvers: [ElementPlusResolver()],
}), }),