Ver Fonte

fix: 添加apikey回调地址

gatsby há 3 semanas atrás
pai
commit
1fb6a5ba44

+ 2 - 0
backend/app/plugin/module_payment/apikey/controller.py

@@ -36,6 +36,7 @@ async def create_api_key_controller(
         tenant_id=tenant_id,
         expired_days=data.expired_days,
         description=data.description,
+        return_url=data.return_url,
     )
     log.info(f"创建API Key成功: 租户ID={tenant_id}")
     return SuccessResponse(
@@ -46,6 +47,7 @@ async def create_api_key_controller(
             status=api_key_obj.status,
             expired_at=api_key_obj.expired_at,
             created_time=api_key_obj.created_time,
+            return_url=api_key_obj.return_url,
         ),
         msg="创建APIKey成功"
     )

+ 2 - 0
backend/app/plugin/module_payment/apikey/crud.py

@@ -38,6 +38,7 @@ class TenantApiKeyCRUD(CRUDBase[TenantApiKeyModel, TenantApiKeyCreate, TenantApi
         api_secret: str,
         tenant_id: int,
         expired_at: datetime,
+        return_url: str | None = None,
         description: str | None = None,
     ) -> TenantApiKeyModel:
         data = {
@@ -46,6 +47,7 @@ class TenantApiKeyCRUD(CRUDBase[TenantApiKeyModel, TenantApiKeyCreate, TenantApi
             "api_key": api_key,
             "api_secret": api_secret,
             "expired_at": expired_at,
+            "return_url": return_url,
         }
         obj = await self.create(data=data, skip_tenant_id=True)
         return obj

+ 3 - 0
backend/app/plugin/module_payment/apikey/model.py

@@ -50,6 +50,9 @@ class TenantApiKeyModel(ModelMixin, TenantMixin):
         index=True,
         comment="租户ID",
     )
+    return_url: Mapped[str] = mapped_column(
+        String(512), nullable=True, comment="同步返回地址"
+    )
 
 
 class TenantApiLogModel(ModelMixin, TenantMixin):

+ 3 - 0
backend/app/plugin/module_payment/apikey/schema.py

@@ -13,6 +13,7 @@ class TenantApiKeyCreate(BaseModel):
     tenant_id: Optional[int] = Field(None, description="租户ID,默认使用当前用户的租户ID")
     description: Optional[str] = Field(None, description="备注")
     expired_days: Optional[int] = Field(365, description="过期天数,默认365天")
+    return_url: Optional[str] = Field(default=None, description="同步返回地址")
 
 
 class TenantApiKeyResponse(BaseModel):
@@ -23,6 +24,7 @@ class TenantApiKeyResponse(BaseModel):
     status: str
     expired_at: Optional[datetime]
     created_time: datetime
+    return_url: Optional[str]
 
     class Config:
         from_attributes = True
@@ -37,6 +39,7 @@ class TenantApiKeyListResponse(BaseModel):
     last_used_at: Optional[datetime]
     created_time: datetime
     description: Optional[str]
+    return_url: Optional[str]
 
     class Config:
         from_attributes = True

+ 6 - 0
backend/app/plugin/module_payment/apikey/service.py

@@ -101,6 +101,7 @@ class TenantApiKeyService:
         auth: AuthSchema,
         tenant_id: int,
         expired_days: Optional[int] = 365,
+        return_url: Optional[str] = None,
         description: Optional[str] = None,
     ) -> TenantApiKeyModel:
         api_key, api_secret = TenantApiKeyService.generate_api_key(tenant_id)
@@ -110,8 +111,13 @@ class TenantApiKeyService:
             tenant_id=tenant_id,
             expired_at=datetime.now() + timedelta(days=expired_days or 365),
             description=description,
+            return_url=return_url,
         )
 
+    @staticmethod
+    async def get_apikey_service(auth: AuthSchema, api_key: str) -> Optional[TenantApiKeyModel]:
+        return await TenantApiKeyCRUD(auth).get(api_key=api_key)
+
     @staticmethod
     async def validate_api_key(auth: AuthSchema, api_key: str) -> Optional[TenantApiKeyModel]:
         return await TenantApiKeyCRUD(auth).get_by_api_key(api_key)

+ 6 - 0
backend/app/plugin/module_payment/openapi/model.py

@@ -26,6 +26,12 @@ class OpenTransferModel(PaymentModelMixin, TenantMixin, EnterpriseMixin):
         comment="平台订单号",
     )
 
+    api_key: Mapped[str] = mapped_column(
+        String(256),
+        comment="API Key",
+        index=True,
+    )
+
 
 class OpenConfModel(PaymentModelMixin, TenantMixin):
     __tablename__ = "open_conf"

+ 1 - 0
backend/app/plugin/module_payment/openapi/schema.py

@@ -7,6 +7,7 @@ from app.plugin.module_payment.account.schema import AccountTransferSchema
 
 class OpenTransferSchema(AccountTransferSchema):
     third_biz_no: Optional[str] = Field(default=None, description="三方订单号")
+    api_key: Optional[str] = Field(default=None, description="API Key")
 
 
 class OpenConfOutSchema(BaseModel):

+ 23 - 12
backend/app/plugin/module_payment/openapi/service.py

@@ -13,14 +13,15 @@ from app.plugin.module_payment.account import AccountService, TransferCRUD, Tran
 import aiohttp
 import asyncio
 
+from ..apikey.service import TenantApiKeyService
 
-async def fetch_manual_retry(session, url, notify_id, app_id, timestamp, content, max_retries=2):
+
+async def fetch_manual_retry(session, url, notify_id, timestamp, content, max_retries=2):
     for attempt in range(max_retries):
         try:
             log.debug("第 {} 次尝试: {}", attempt + 1, url)
             form_data = aiohttp.FormData()
             form_data.add_field('notify_id', notify_id)
-            form_data.add_field('app_id', app_id)
             form_data.add_field('timestamp', timestamp)
             form_data.add_field('content', content)
             async with session.post(url=url, data=form_data) as response:
@@ -80,15 +81,23 @@ class OpenTransferService:
 
             auth.tenant_id = open_data.tenant_id
             auth.check_data_scope = True
-            conf = await OpenConfService.get_conf_service(auth)
-            if not conf:
-                log.info("回调通知: 开放转账配置不存在, tenant_id={}", auth.tenant_id)
-                return False
-            
-            log.info("回调通知: 开放转账配置 app_id={}, return_url={}, tenant_id={}", conf.app_id, conf.return_url, auth.tenant_id)
 
-            if not conf.return_url:
-                log.info("回调通知: 未配置回调地址, tenant_id={}", auth.tenant_id)
+            apikey_data = None
+
+            # 先从apikey那return_url,否则使用conf
+            if open_data.api_key:
+                apikey_data = await TenantApiKeyService.get_apikey_service(auth=auth, api_key=open_data.api_key)
+            if apikey_data:
+                return_url = apikey_data.return_url
+            else:
+                conf = await OpenConfService.get_conf_service(auth)
+                if not conf:
+                    log.info("回调通知: 开放转账配置不存在, tenant_id={}", auth.tenant_id)
+                    return False
+                return_url = conf.return_url
+
+            if not return_url:
+                log.info("回调通知: 回调地址不存在")
                 return False
 
             result = TransferOutSchema.model_validate(transfer)
@@ -100,10 +109,11 @@ class OpenTransferService:
 
             timeout = aiohttp.ClientTimeout(total=30)
             async with aiohttp.ClientSession(timeout=timeout) as session:
-                log.info("回调通知: order_no={}, url={}, notify_id={}", order_no, conf.return_url, notify_id)
+                log.info("回调通知: order_no={}, url={}, notify_id={}", order_no, return_url, notify_id)
                 await fetch_manual_retry(
-                    session, conf.return_url, notify_id, conf.app_id, timestamp, content
+                    session, return_url, notify_id, timestamp, content
                 )
+            return True
 
         except Exception as e:
             log.error("回调通知异常: order_no={}, error={}", order_no, e, exc_info=True)
@@ -151,6 +161,7 @@ class OpenTransferService:
         create_data = {
             "third_biz_no": third_biz_no,
             "out_biz_no": result.out_biz_no,
+            "api_key": data.api_key,
         }
         await crud.create(create_data)
 

+ 1 - 0
frontend/package.json

@@ -136,6 +136,7 @@
     "postcss-html": "^1.8.0",
     "postcss-scss": "^4.0.9",
     "prettier": "^3.6.2",
+    "rollup-plugin-visualizer": "^7.0.1",
     "sass": "^1.89.2",
     "stylelint": "^16.25.0",
     "stylelint-config-html": "^1.1.0",

+ 3 - 0
frontend/src/api/module_payment/apikey/index.ts

@@ -15,6 +15,7 @@ export interface ApiKeyCreateForm {
   tenant_id?: number;
   expired_days: number;
   description?: string;
+  return_url?: string;
 }
 
 export interface ApiKeyUpdateForm {
@@ -35,6 +36,7 @@ export interface ApiKeyResponse {
   created_time: string;
   last_used_at?: string;
   description?: string;
+  return_url?: string;
 }
 
 export interface ApiKeyTable {
@@ -45,6 +47,7 @@ export interface ApiKeyTable {
   last_used_at?: string;
   created_time: string;
   description?: string;
+  return_url?: string;
 }
 
 export interface ApiKeyListResponse {

+ 25 - 2
frontend/src/views/module_payment/apikey/index.vue

@@ -109,6 +109,14 @@
                   min-width="150"
                   show-overflow-tooltip
                 />
+                <el-table-column
+                  v-if="contentCols.find((col) => col.prop === 'return_url')?.show"
+                  key="return_url"
+                  label="回调地址"
+                  prop="return_url"
+                  min-width="200"
+                  show-overflow-tooltip
+                />
                 <el-table-column
                   v-if="contentCols.find((col) => col.prop === 'created_time')?.show"
                   key="created_time"
@@ -175,7 +183,16 @@
                 v-model="formData.description"
                 type="textarea"
                 :rows="3"
-                placeholder="请输入描述"
+                placeholder="请输入描述(可选)"
+                :maxlength="255"
+              />
+            </el-form-item>
+            <el-form-item label="回调地址" prop="return_url">
+              <el-input
+                v-model="formData.return_url"
+                type="textarea"
+                :rows="2"
+                placeholder="请输入回调地址(可选)"
                 :maxlength="255"
               />
             </el-form-item>
@@ -242,6 +259,9 @@
             <el-descriptions-item label="描述">
               {{ apiKeyDetail.description || "无" }}
             </el-descriptions-item>
+            <el-descriptions-item label="回调地址">
+              {{ apiKeyDetail.return_url || "无" }}
+            </el-descriptions-item>
           </el-descriptions>
 
           <template #footer>
@@ -1039,6 +1059,7 @@ const apiKeyDetail = reactive<ApiKeyResponse>({
   status: "0",
   expired_at: "",
   created_time: "",
+  return_url: "",
 });
 
 const searchConfig = reactive<ISearchConfig>({
@@ -1079,6 +1100,7 @@ const contentCols = reactive<
   { prop: "expired_at", label: "过期时间", show: true },
   { prop: "last_used_at", label: "最后使用时间", show: true },
   { prop: "description", label: "描述", show: true },
+  { prop: "return_url", label: "回调地址", show: true },
   { prop: "created_time", label: "创建时间", show: true },
   { prop: "operation", label: "操作", show: true },
 ]);
@@ -1122,6 +1144,7 @@ const formData = reactive<ApiKeyCreateForm>({
   tenant_id: undefined,
   expired_days: 365,
   description: "",
+  return_url: "",
 });
 
 const dialogVisible = reactive({
@@ -1213,7 +1236,7 @@ function handleConfirmApiKeyDetail() {
 }
 
 function downloadApiKeyCsv() {
-  const csvContent = `API Key,API Secret,状态,过期时间,创建时间,描述\n"${apiKeyDetail.api_key}","${apiKeyDetail.api_secret}","${apiKeyDetail.status === '0' ? '正常' : '禁用'}","${apiKeyDetail.expired_at}","${apiKeyDetail.created_time}","${apiKeyDetail.description || ''}"`;
+  const csvContent = `API Key,API Secret,状态,过期时间,创建时间,描述,回调地址\n"${apiKeyDetail.api_key}","${apiKeyDetail.api_secret}","${apiKeyDetail.status === '0' ? '正常' : '禁用'}","${apiKeyDetail.expired_at}","${apiKeyDetail.created_time}","${apiKeyDetail.description || ''}","${apiKeyDetail.return_url || ''}"`;
   const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
   const link = document.createElement('a');
   const url = URL.createObjectURL(blob);

+ 15 - 4
frontend/vite.config.ts

@@ -8,6 +8,7 @@ import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
 import UnoCSS from "unocss/vite";
 import { resolve } from "path";
 import { name, version, engines, dependencies, devDependencies } from "./package.json";
+import { visualizer } from "rollup-plugin-visualizer";
 
 // 平台的名称、版本、运行所需的 node 版本、依赖、构建时间的类型提示
 const __APP_INFO__ = {
@@ -54,6 +55,11 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
     },
     plugins: [
       vue(),
+      visualizer({
+        open: true,
+        gzipSize: true,
+        brotliSize: true,
+      }),
       UnoCSS(),
       // API 自动导入
       AutoImport({
@@ -218,9 +224,15 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
           // manualChunks: {
           //   "vue-i18n": ["vue-i18n"],
           // },
+          experimentalMinChunkSize: 20 * 1024,
           manualChunks(id) {
+            // 1. 排除 .pnpm 目录
+            if (id.includes(".pnpm/")) {
+              return undefined;
+            }
+
+            // 2. 大型库单独拆分
             if (id.includes("node_modules")) {
-              // 针对大型库进行单独拆分
               if (id.includes("echarts")) {
                 return "echarts";
               }
@@ -237,10 +249,9 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
                 return "exceljs";
               }
 
-              // 其他模块保持当前拆分方式
+              // 3. 小型依赖合并到 vendor(避免过度拆分)
               const module = id.toString().split("node_modules/")[1].split("/")[0];
-              if (["birpc", "hookable"].includes(module)) return;
-              return module;
+              return module; // 其他包单独拆分
             }
           },
           // 用于从入口点创建的块的打包输出格式[name]表示文件名,[hash]表示该文件内容hash值