修复支付不跳转

This commit is contained in:
2026-09-28 22:17:18 +08:00
parent 92403171d4
commit 7c75edcc61
8 changed files with 113 additions and 6 deletions
+8 -2
View File
@@ -301,8 +301,14 @@ const checkOrder = async (orderNo: string, silent = false) => {
ElMessage.success("购买成功,套餐已开通"); ElMessage.success("购买成功,套餐已开通");
payVisible.value = false; payVisible.value = false;
loadMyOrders(); loadMyOrders();
// 重建动态路由,把新开通模块的「功能未开通」菜单占位页切换为真实页面 // 重建动态路由,把新开通模块的「功能未开通」菜单占位页切换为真实页面。
reloadMenusAndDynamicRoutes().catch(() => {}); // 必须等重建完成后再跳首页,否则会按旧的菜单路由跳转、落到占位页
try {
await reloadMenusAndDynamicRoutes();
} catch {
/* 忽略:进入首页时路由守卫会再拉一次菜单 */
}
router.replace("/home");
return; return;
} }
if (status === "paid") { if (status === "paid") {
+8 -2
View File
@@ -312,8 +312,14 @@ const checkOrder = async (silent = false) => {
ElMessage.success("购买成功,功能已开通"); ElMessage.success("购买成功,功能已开通");
payVisible.value = false; payVisible.value = false;
loadMyOrders(); loadMyOrders();
// 重建动态路由,把刚开通模块的「功能未开通」菜单占位页切换为真实页面 // 重建动态路由,把刚开通模块的「功能未开通」菜单占位页切换为真实页面。
reloadMenusAndDynamicRoutes().catch(() => {}); // 必须等重建完成后再跳首页,否则会按旧的菜单路由跳转、落到占位页
try {
await reloadMenusAndDynamicRoutes();
} catch {
/* 忽略:进入首页时路由守卫会再拉一次菜单 */
}
router.replace("/home");
return; return;
} }
if (status === "paid") { if (status === "paid") {
+8
View File
@@ -93,3 +93,11 @@ payment_secret_key = f656fe85ed6a1caba9f19966d8cdde46114709969bea5875cde7664d4ad
# 要求:公网可访问、HTTPS、无鉴权无重定向;微信 JSAPI 还需在商户平台「支付授权目录」登记同一域名。 # 要求:公网可访问、HTTPS、无鉴权无重定向;微信 JSAPI 还需在商户平台「支付授权目录」登记同一域名。
# 本地开发收不到渠道回调,留空即可(回调地址退化为相对路径 /api/payment/callback/{渠道},由网关补齐域名)。 # 本地开发收不到渠道回调,留空即可(回调地址退化为相对路径 /api/payment/callback/{渠道},由网关补齐域名)。
payment_callback_base = https://api.yunzer.cn payment_callback_base = https://api.yunzer.cn
# 渠道证书落盘根目录(各渠道目录的父目录,实际路径 = {payment_cert_dir}/{渠道}/xxx.pem)。
# 留空时按进程工作目录下的 certs/payment 处理。
# 历史数据里 cert_json 存的是相对路径(相对「当初上传时」的进程工作目录),
# 若服务的工作目录与当初不一致(例如上传时是 /www/wwwroot/api.yunzer.cn,现在跑在 /app),
# 把这里配成当初上传用的绝对目录即可继续读到旧证书,无需重新上传:
payment_cert_dir = /www/wwwroot/api.yunzer.cn/certs/payment/wechat
# payment_cert_dir =
+3 -1
View File
@@ -384,7 +384,9 @@ func (c *PlatformPaymentController) UploadCertificate() {
} }
defer func() { _ = file.Close() }() defer func() { _ = file.Close() }()
dir := filepath.Join("certs", "payment", channel) // 落盘目录:payment_cert_dir 配置优先,未配置时用相对工作目录的 certs/payment/<渠道>。
// 入库保持相对路径——本地开发与线上可能共用同一个库,存绝对路径会互相覆盖对方环境。
dir := payment.CertDir(channel)
if err := os.MkdirAll(dir, 0o700); err != nil { if err := os.MkdirAll(dir, 0o700); err != nil {
c.jsonErr(500, 500, "创建证书目录失败: "+err.Error()) c.jsonErr(500, 500, "创建证书目录失败: "+err.Error())
return return
+81
View File
@@ -0,0 +1,81 @@
package payment
import (
"os"
"path/filepath"
"strings"
beego "github.com/beego/beego/v2/server/web"
)
// =============================================================
// 渠道证书落盘路径解析
//
// 历史实现把证书存成相对路径(certs/payment/<渠道>/xxx.pem),
// 该相对路径在「上传时」与「读取时」分别相对各自的进程工作目录解析。
// 一旦服务的工作目录发生变化(例如从 /www/wwwroot/api.yunzer.cn 换到 /app),
// 库里记的路径就指向不存在的文件,报 “The system cannot find the file specified”。
//
// 因此读取时统一做一次解析:
// - 落盘:写入 payment_cert_dir(未配置时退回工作目录下的 certs/payment),入库仍存相对路径
// (本地开发与线上可能共用同一个库,存绝对路径会互相覆盖对方环境);
// - 读取:先按当前工作目录解析,再按配置的 payment_cert_dir 根目录兜底解析。
// =============================================================
// legacyCertPrefix 历史相对路径前缀,用于把 certs/payment/<渠道>/xxx.pem 剥成 <渠道>/xxx.pem
const legacyCertPrefix = "certs/payment/"
// CertBaseDir 渠道证书落盘根目录(各渠道目录的父目录)
func CertBaseDir() string {
dir, _ := beego.AppConfig.String("payment_cert_dir")
dir = strings.TrimSpace(dir)
if dir == "" {
dir = filepath.Join("certs", "payment")
}
return dir
}
// CertDir 某渠道的证书目录
func CertDir(channel string) string {
return filepath.Join(CertBaseDir(), filepath.Base(channel))
}
// ResolveCertPath 把库中可能存的相对路径解析为当前可读的路径。
// 依次尝试:绝对路径 -> 相对当前工作目录 -> 相对 payment_cert_dir 根目录(兼容历史相对路径)。
// 都读不到时原样返回,让上层的 os.ReadFile 报出原始路径便于排查。
func ResolveCertPath(p string) string {
p = strings.TrimSpace(p)
if p == "" {
return ""
}
if filepath.IsAbs(p) {
return p
}
// 相对当前工作目录(历史行为,能读到就不动)
if certPathExists(p) {
return p
}
// 兼容历史相对路径:剥掉 certs/payment 前缀后挂到配置的根目录下
if cand := filepath.Join(CertBaseDir(), certRelative(p)); certPathExists(cand) {
return cand
}
// 兜底:按文件名在根目录下找
if cand := filepath.Join(CertBaseDir(), filepath.Base(p)); certPathExists(cand) {
return cand
}
return p
}
// certRelative 从历史相对路径中剥掉 certs/payment 前缀
func certRelative(p string) string {
norm := filepath.ToSlash(filepath.Clean(p))
if i := strings.Index(norm, legacyCertPrefix); i >= 0 {
norm = norm[i+len(legacyCertPrefix):]
}
return filepath.FromSlash(norm)
}
func certPathExists(path string) bool {
info, err := os.Stat(path)
return err == nil && !info.IsDir()
}
+4
View File
@@ -221,6 +221,10 @@ func LoadChannelConfig(channel string) (*ChannelConfig, error) {
} }
if row.CertJSON != nil && *row.CertJSON != "" { if row.CertJSON != nil && *row.CertJSON != "" {
_ = json.Unmarshal([]byte(*row.CertJSON), &cfg.CertPaths) _ = json.Unmarshal([]byte(*row.CertJSON), &cfg.CertPaths)
// 历史数据存的是相对路径,这里统一解析为当前可读路径,避免依赖进程工作目录
for key, val := range cfg.CertPaths {
cfg.CertPaths[key] = ResolveCertPath(val)
}
} }
return cfg, nil return cfg, nil
} }