修复文件上传和下单支付流程
This commit is contained in:
@@ -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) {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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()],
|
||||||
}),
|
}),
|
||||||
|
|||||||
Reference in New Issue
Block a user