优化结构

This commit is contained in:
Yvan
2026-08-22 15:53:14 +08:00
parent ca52de5db0
commit 491ed21d48
72 changed files with 746 additions and 695 deletions
@@ -0,0 +1,417 @@
package integration
import (
"context"
"encoding/json"
"errors"
"fmt"
paymentutil "kra/pkg/paymentkit"
"sort"
"strconv"
"strings"
"time"
)
const (
IntegrationKindPayment = "payment"
IntegrationKindMQ = "mq"
IntegrationKindWebSocket = "websocket"
)
type IntegrationConfig struct {
Kind string
Provider string
Enabled bool
Values json.RawMessage
}
type IntegrationConfigField struct {
Key string `json:"key"`
Label string `json:"label"`
Type string `json:"type"`
Required bool `json:"required"`
Secret bool `json:"secret"`
Placeholder string `json:"placeholder,omitempty"`
Description string `json:"description,omitempty"`
Options []IntegrationConfigOption `json:"options,omitempty"`
}
type IntegrationConfigOption struct {
Label string `json:"label"`
Value any `json:"value"`
}
type IntegrationConfigDefinition struct {
Kind string `json:"kind"`
Provider string `json:"provider"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Fields []IntegrationConfigField `json:"fields"`
Defaults map[string]any `json:"defaults"`
}
type IntegrationConfigRepo interface {
ListIntegrationConfigs(context.Context, string) ([]*IntegrationConfig, error)
FindIntegrationConfig(context.Context, string, string) (*IntegrationConfig, error)
SaveIntegrationConfig(context.Context, *IntegrationConfig) error
DeleteIntegrationConfig(context.Context, string, string) error
}
type IntegrationConnectionTester interface {
TestIntegration(context.Context, *IntegrationConfig) error
}
type IntegrationConfigUsecase struct {
repo IntegrationConfigRepo
tester IntegrationConnectionTester
}
func NewIntegrationConfigUsecase(repo IntegrationConfigRepo, tester IntegrationConnectionTester) *IntegrationConfigUsecase {
return &IntegrationConfigUsecase{repo: repo, tester: tester}
}
func (uc *IntegrationConfigUsecase) List(ctx context.Context, kind string) ([]*IntegrationConfig, error) {
kind = normalizeIntegrationPart(kind)
if kind == "" {
return nil, errors.New("集成配置 kind 不能为空")
}
return uc.repo.ListIntegrationConfigs(ctx, kind)
}
func (uc *IntegrationConfigUsecase) Find(ctx context.Context, kind, provider string) (*IntegrationConfig, error) {
kind, provider = normalizeIntegrationPart(kind), normalizeIntegrationPart(provider)
if kind == "" || provider == "" {
return nil, errors.New("集成配置 kind 和 provider 不能为空")
}
return uc.repo.FindIntegrationConfig(ctx, kind, provider)
}
func (uc *IntegrationConfigUsecase) Save(ctx context.Context, config *IntegrationConfig) error {
if config == nil {
return errors.New("集成配置请求为空")
}
config.Kind = normalizeIntegrationPart(config.Kind)
config.Provider = normalizeIntegrationPart(config.Provider)
if config.Kind == "" || config.Provider == "" || len(config.Kind) > 32 || len(config.Provider) > 64 {
return errors.New("集成配置 kind 或 provider 无效")
}
if !json.Valid(config.Values) {
return errors.New("集成配置必须是合法 JSON")
}
values := map[string]any{}
if err := json.Unmarshal(config.Values, &values); err != nil {
return errors.New("集成配置必须是 JSON 对象")
}
if definition, ok := IntegrationDefinition(config.Kind, config.Provider); ok {
values = mergeIntegrationDefaults(definition.Defaults, values)
if config.Enabled {
if err := ValidateIntegrationConfig(config.Kind, config.Provider, values); err != nil {
return err
}
}
config.Values, _ = json.Marshal(values)
}
return uc.repo.SaveIntegrationConfig(ctx, config)
}
// Test validates and probes a candidate configuration without persisting it.
// The adapter may resolve masked secret values from the active runtime store.
func (uc *IntegrationConfigUsecase) Test(ctx context.Context, config *IntegrationConfig) error {
if config == nil {
return errors.New("集成配置请求为空")
}
config.Kind = normalizeIntegrationPart(config.Kind)
config.Provider = normalizeIntegrationPart(config.Provider)
if config.Kind != IntegrationKindMQ && config.Kind != IntegrationKindWebSocket {
return errors.New("仅支持测试消息队列和 WebSocket 集成")
}
if config.Kind == "" || config.Provider == "" || len(config.Kind) > 32 || len(config.Provider) > 64 {
return errors.New("集成配置 kind 或 provider 无效")
}
if !json.Valid(config.Values) {
return errors.New("集成配置必须是合法 JSON")
}
values := map[string]any{}
if err := json.Unmarshal(config.Values, &values); err != nil {
return errors.New("集成配置必须是 JSON 对象")
}
if definition, ok := IntegrationDefinition(config.Kind, config.Provider); ok {
values = mergeIntegrationDefaults(definition.Defaults, values)
}
if err := ValidateIntegrationConfig(config.Kind, config.Provider, values); err != nil {
return err
}
encoded, _ := json.Marshal(values)
config.Enabled = true
config.Values = encoded
if uc.tester == nil {
return errors.New("集成连接测试器未初始化")
}
return uc.tester.TestIntegration(ctx, config)
}
func (uc *IntegrationConfigUsecase) Delete(ctx context.Context, kind, provider string) error {
kind, provider = normalizeIntegrationPart(kind), normalizeIntegrationPart(provider)
if kind == "" || provider == "" {
return errors.New("集成配置 kind 和 provider 不能为空")
}
return uc.repo.DeleteIntegrationConfig(ctx, kind, provider)
}
func normalizeIntegrationPart(value string) string {
return strings.ToLower(strings.TrimSpace(value))
}
func IntegrationDefinitions(kind string) []IntegrationConfigDefinition {
kind = normalizeIntegrationPart(kind)
definitions := integrationDefinitions[kind]
out := append([]IntegrationConfigDefinition(nil), definitions...)
sort.Slice(out, func(i, j int) bool { return out[i].Provider < out[j].Provider })
return out
}
func IntegrationDefinition(kind, provider string) (IntegrationConfigDefinition, bool) {
for _, definition := range integrationDefinitions[normalizeIntegrationPart(kind)] {
if definition.Provider == normalizeIntegrationPart(provider) {
return definition, true
}
}
return IntegrationConfigDefinition{}, false
}
func DefaultIntegrationConfig(kind, provider string) map[string]any {
definition, ok := IntegrationDefinition(kind, provider)
if !ok {
return map[string]any{}
}
return mergeIntegrationDefaults(definition.Defaults, nil)
}
func mergeIntegrationDefaults(defaults, values map[string]any) map[string]any {
out := make(map[string]any, len(defaults)+len(values))
for key, value := range defaults {
out[key] = value
}
for key, value := range values {
out[key] = value
}
return out
}
func ValidateIntegrationConfig(kind, provider string, values map[string]any) error {
kind = normalizeIntegrationPart(kind)
provider = normalizeIntegrationPart(provider)
switch kind {
case IntegrationKindPayment:
return validatePaymentIntegrationConfig(provider, values)
case IntegrationKindMQ, IntegrationKindWebSocket:
return validateCommunicationIntegrationConfig(kind, provider, values)
default:
return nil
}
}
func validateCommunicationIntegrationConfig(kind, provider string, values map[string]any) error {
definition, ok := IntegrationDefinition(kind, provider)
if !ok {
return errors.New("不支持的通信集成")
}
for _, field := range definition.Fields {
if field.Required && integrationText(values, field.Key) == "" {
return fmt.Errorf("%s 缺少配置字段 %s", provider, field.Key)
}
}
switch kind + "/" + provider {
case IntegrationKindMQ + "/emqx":
broker := strings.ToLower(integrationText(values, "broker"))
if !strings.HasPrefix(broker, "tcp://") && !strings.HasPrefix(broker, "ssl://") && !strings.HasPrefix(broker, "ws://") && !strings.HasPrefix(broker, "wss://") && !strings.HasPrefix(broker, "mqtt://") {
return errors.New("emqx broker 必须使用 tcp、ssl、ws、wss 或 mqtt 协议")
}
if keepAlive := integrationInt64(values, "keep_alive", 0); keepAlive <= 0 {
return errors.New("emqx keep_alive 必须大于 0")
}
if timeout := integrationInt64(values, "connect_timeout", 0); timeout <= 0 {
return errors.New("emqx connect_timeout 必须大于 0")
}
if interval := integrationInt64(values, "reconnect_interval", 0); interval <= 0 {
return errors.New("emqx reconnect_interval 必须大于 0")
}
case IntegrationKindMQ + "/rabbitmq":
port := integrationInt64(values, "port", 0)
if port < 1 || port > 65535 {
return errors.New("rabbitmq port 必须在 1-65535 之间")
}
exchangeType := strings.ToLower(integrationText(values, "exchange_type"))
if exchangeType != "direct" && exchangeType != "fanout" && exchangeType != "topic" {
return errors.New("rabbitmq exchange_type 必须是 direct、fanout 或 topic")
}
if integrationInt64(values, "prefetch_count", -1) < 0 {
return errors.New("rabbitmq prefetch_count 不能小于 0")
}
if integrationInt64(values, "heartbeat", -1) < 0 {
return errors.New("rabbitmq heartbeat 不能小于 0")
}
if integrationInt64(values, "connect_timeout", 0) <= 0 {
return errors.New("rabbitmq connect_timeout 必须大于 0")
}
if integrationInt64(values, "reconnect_interval", 0) <= 0 {
return errors.New("rabbitmq reconnect_interval 必须大于 0")
}
case IntegrationKindWebSocket + "/melody":
path := integrationText(values, "path")
if !strings.HasPrefix(path, "/") {
return errors.New("websocket path 必须以 / 开头")
}
for _, key := range []string{"write_wait", "pong_wait", "ping_period"} {
value := integrationText(values, key)
if value == "" {
continue
}
duration, err := time.ParseDuration(value)
if err != nil || duration <= 0 {
return fmt.Errorf("websocket %s 必须是大于 0 的时长", key)
}
}
if integrationInt64(values, "max_message_size", -1) < 0 || integrationInt64(values, "message_buffer_size", -1) < 0 {
return errors.New("websocket 消息大小和缓冲区不能小于 0")
}
}
return nil
}
func validatePaymentIntegrationConfig(provider string, values map[string]any) error {
definition, ok := IntegrationDefinition(IntegrationKindPayment, provider)
if !ok {
return errors.New("不支持的支付渠道")
}
for _, field := range definition.Fields {
if field.Required && integrationText(values, field.Key) == "" {
return fmt.Errorf("%s 缺少配置字段 %s", provider, field.Key)
}
}
switch provider {
case paymentutil.ProviderAlipayV3:
for _, group := range []struct {
label string
keys []string
}{
{"private_key", []string{"private_key", "private_key_content", "private_key_path"}},
{"app_cert", []string{"app_cert", "app_cert_content", "app_cert_path", "app_public_cert", "app_public_cert_content", "app_public_cert_path"}},
{"root_cert", []string{"root_cert", "root_cert_content", "root_cert_path", "alipay_root_cert", "alipay_root_cert_content", "alipay_root_cert_path"}},
{"public_cert", []string{"public_cert", "public_cert_content", "public_cert_path", "alipay_public_cert", "alipay_public_cert_content", "alipay_public_cert_path"}},
} {
if integrationFirst(values, group.keys...) == "" {
return fmt.Errorf("%s 缺少配置字段 %s", provider, group.label)
}
}
case paymentutil.ProviderWechatV2:
if integrationFirst(values, "client_cert", "cert_pem", "apiclient_cert") == "" || integrationFirst(values, "client_key", "key_pem", "apiclient_key") == "" {
return fmt.Errorf("%s 退款要求同时配置 client_cert 和 client_key", provider)
}
case paymentutil.ProviderApple:
if integrationInt64(values, "price_divisor", 0) <= 0 {
if _, exists := values["price_divisors"].(map[string]any); !exists {
return fmt.Errorf("%s 缺少配置字段 price_divisor 或 price_divisors", provider)
}
}
case paymentutil.ProviderDouyin:
if integrationFirst(values, "platform_serial_no", "platform_cert_serial") == "" {
return fmt.Errorf("%s 缺少配置字段 platform_serial_no", provider)
}
case paymentutil.ProviderQQ:
if integrationFirst(values, "mch_id", "merchant_id") == "" {
return fmt.Errorf("%s 缺少配置字段 mch_id", provider)
}
if !hasQQIntegrationCertificate(values) {
return fmt.Errorf("%s 退款要求配置 cert_file + key_file、pkcs12_file 或对应的 *_content", provider)
}
signType := strings.ToUpper(integrationText(values, "sign_type"))
if signType != "" && signType != "MD5" && signType != "HMAC-SHA256" {
return fmt.Errorf("%s sign_type 必须是 MD5 或 HMAC-SHA256", provider)
}
case paymentutil.ProviderAllinPay:
orderType := strings.ToLower(integrationFirst(values, "query_order_type", "order_type"))
if orderType != "" && orderType != "reqsn" && orderType != "trxid" {
return fmt.Errorf("%s query_order_type 必须是 reqsn 或 trxid", provider)
}
case paymentutil.ProviderChinaums, paymentutil.ProviderSFT, paymentutil.ProviderSuperPay, paymentutil.ProviderWechatGame, paymentutil.ProviderDouyinGame:
if integrationFirst(values, "app_key", "merchant_key", "signing_secret", "token") == "" {
return fmt.Errorf("%s 缺少签名密钥", provider)
}
if !validIntegrationScale(integrationInt64(values, "query_amount_scale", 0)) {
return fmt.Errorf("%s query_amount_scale 必须是正的 10 的幂", provider)
}
}
if environment := strings.ToLower(integrationText(values, "environment")); environment != "" {
allowedSandbox := provider != paymentutil.ProviderQQ && provider != paymentutil.ProviderDouyin && provider != paymentutil.ProviderLakala
if environment != "production" && environment != "prod" && (!allowedSandbox || environment != "sandbox") {
return fmt.Errorf("%s environment 配置无效", provider)
}
}
return validateIntegrationCallbackAck(values)
}
func integrationText(values map[string]any, key string) string {
value, exists := values[key]
if !exists || value == nil {
return ""
}
return strings.TrimSpace(fmt.Sprint(value))
}
func integrationFirst(values map[string]any, keys ...string) string {
for _, key := range keys {
if value := integrationText(values, key); value != "" {
return value
}
}
return ""
}
func integrationInt64(values map[string]any, key string, fallback int64) int64 {
value := integrationText(values, key)
if value == "" {
return fallback
}
parsed, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return fallback
}
return parsed
}
func hasQQIntegrationCertificate(values map[string]any) bool {
if integrationFirst(values, "pkcs12_file", "p12_file", "pkcs12_content") != "" {
return true
}
if integrationFirst(values, "cert_file", "cert_path") != "" && integrationFirst(values, "key_file", "key_path") != "" {
return true
}
return integrationFirst(values, "cert_content") != "" && integrationFirst(values, "key_content") != ""
}
func validIntegrationScale(scale int64) bool {
if scale <= 0 {
return false
}
for scale%10 == 0 {
scale /= 10
}
return scale == 1
}
func validateIntegrationCallbackAck(values map[string]any) error {
for _, prefix := range []string{"callback_success_", "callback_failure_"} {
if value := integrationText(values, prefix+"status"); value != "" {
status, err := strconv.Atoi(value)
if err != nil || status < 200 || status > 599 {
return fmt.Errorf("%sstatus 必须是 200-599", prefix)
}
}
if contentType := integrationText(values, prefix+"content_type"); strings.ContainsAny(contentType, "\r\n") || len(contentType) > 200 {
return fmt.Errorf("%scontent_type 非法", prefix)
}
}
return nil
}
@@ -0,0 +1,98 @@
package integration
import (
"context"
"encoding/json"
"testing"
)
type integrationConfigRepoTestDouble struct {
saves int
}
func (*integrationConfigRepoTestDouble) ListIntegrationConfigs(context.Context, string) ([]*IntegrationConfig, error) {
return nil, nil
}
func (*integrationConfigRepoTestDouble) FindIntegrationConfig(context.Context, string, string) (*IntegrationConfig, error) {
return nil, nil
}
func (r *integrationConfigRepoTestDouble) SaveIntegrationConfig(context.Context, *IntegrationConfig) error {
r.saves++
return nil
}
func (*integrationConfigRepoTestDouble) DeleteIntegrationConfig(context.Context, string, string) error {
return nil
}
type integrationConnectionTesterDouble struct {
calls int
config *IntegrationConfig
}
func (t *integrationConnectionTesterDouble) TestIntegration(_ context.Context, config *IntegrationConfig) error {
t.calls++
t.config = config
return nil
}
func TestCommunicationIntegrationDefinitionsAndValidation(t *testing.T) {
for _, target := range []struct{ kind, provider string }{
{IntegrationKindMQ, "emqx"},
{IntegrationKindMQ, "rabbitmq"},
{IntegrationKindWebSocket, "melody"},
} {
values := DefaultIntegrationConfig(target.kind, target.provider)
if len(values) == 0 {
t.Fatalf("default config missing for %s/%s", target.kind, target.provider)
}
if err := ValidateIntegrationConfig(target.kind, target.provider, values); err != nil {
t.Fatalf("default config invalid for %s/%s: %v", target.kind, target.provider, err)
}
}
}
func TestCommunicationIntegrationValidationRejectsInvalidValues(t *testing.T) {
rabbit := DefaultIntegrationConfig(IntegrationKindMQ, "rabbitmq")
rabbit["port"] = 0
if err := ValidateIntegrationConfig(IntegrationKindMQ, "rabbitmq", rabbit); err == nil {
t.Fatal("invalid rabbitmq port was accepted")
}
websocket := DefaultIntegrationConfig(IntegrationKindWebSocket, "melody")
websocket["path"] = "ws"
if err := ValidateIntegrationConfig(IntegrationKindWebSocket, "melody", websocket); err == nil {
t.Fatal("invalid websocket path was accepted")
}
}
func TestIntegrationConfigTestDoesNotPersistCandidate(t *testing.T) {
repo := &integrationConfigRepoTestDouble{}
tester := &integrationConnectionTesterDouble{}
usecase := NewIntegrationConfigUsecase(repo, tester)
raw, _ := json.Marshal(map[string]any{"path": "/candidate"})
err := usecase.Test(context.Background(), &IntegrationConfig{
Kind: " WebSocket ",
Provider: " Melody ",
Enabled: false,
Values: raw,
})
if err != nil {
t.Fatal(err)
}
if repo.saves != 0 {
t.Fatalf("candidate config was persisted %d times", repo.saves)
}
if tester.calls != 1 || tester.config == nil {
t.Fatalf("connection tester calls = %d, config = %#v", tester.calls, tester.config)
}
if tester.config.Kind != IntegrationKindWebSocket || tester.config.Provider != "melody" || !tester.config.Enabled {
t.Fatalf("tested config = %#v", tester.config)
}
values := map[string]any{}
if err = json.Unmarshal(tester.config.Values, &values); err != nil {
t.Fatal(err)
}
if values["path"] != "/candidate" || values["write_wait"] != "10s" {
t.Fatalf("tested values = %#v", values)
}
}
@@ -0,0 +1,180 @@
package integration
import paymentutil "kra/pkg/paymentkit"
func integrationField(key, label string, required, secret bool, fieldType string) IntegrationConfigField {
if fieldType == "" {
fieldType = "text"
}
return IntegrationConfigField{Key: key, Label: label, Required: required, Secret: secret, Type: fieldType}
}
func integrationSelect(key, label string, required bool, values ...string) IntegrationConfigField {
options := make([]IntegrationConfigOption, 0, len(values))
for _, value := range values {
options = append(options, IntegrationConfigOption{Label: value, Value: value})
}
return IntegrationConfigField{Key: key, Label: label, Required: required, Type: "select", Options: options}
}
func paymentDefinition(provider, name, description string, defaults map[string]any, fields ...IntegrationConfigField) IntegrationConfigDefinition {
common := []IntegrationConfigField{
integrationField("notify_url", "支付回调地址", false, false, "url"),
integrationField("return_url", "同步跳转地址", false, false, "url"),
integrationField("test_amount", "测试金额(最小货币单位)", false, false, "number"),
integrationField("test_currency", "测试币种", false, false, "text"),
integrationField("test_extra", "测试下单扩展参数(JSON)", false, false, "textarea"),
integrationField("test_mode", "允许执行渠道测试", false, false, "switch"),
}
if _, exists := defaults["test_amount"]; !exists {
defaults["test_amount"] = 1
}
if _, exists := defaults["test_currency"]; !exists {
defaults["test_currency"] = "CNY"
}
if _, exists := defaults["test_extra"]; !exists {
defaults["test_extra"] = "{}"
}
if _, exists := defaults["test_mode"]; !exists {
defaults["test_mode"] = false
}
fields = append(fields, common...)
for _, field := range fields {
if _, exists := defaults[field.Key]; exists {
continue
}
switch field.Type {
case "number":
defaults[field.Key] = 0
case "switch":
defaults[field.Key] = false
default:
defaults[field.Key] = ""
}
}
return IntegrationConfigDefinition{Kind: IntegrationKindPayment, Provider: provider, Name: name, Description: description, Defaults: defaults, Fields: fields}
}
var genericPaymentFields = []IntegrationConfigField{
integrationField("protocol_version", "协议版本", true, false, "text"),
integrationField("app_id", "应用 ID", true, false, "text"),
integrationField("merchant_id", "商户 ID", true, false, "text"),
integrationField("create_url", "下单接口", true, false, "url"),
integrationField("query_url", "查单接口", true, false, "url"),
integrationField("refund_url", "退款接口", true, false, "url"),
integrationField("app_key", "签名密钥", true, true, "password"),
integrationField("query_status_field", "查单状态字段", true, false, "text"),
integrationField("query_success_values", "查单成功值", true, false, "text"),
integrationField("query_trade_no_field", "商户单号字段", true, false, "text"),
integrationField("query_provider_trade_no_field", "平台单号字段", true, false, "text"),
integrationField("query_amount_field", "金额字段", true, false, "text"),
integrationField("query_currency_field", "币种字段", true, false, "text"),
integrationField("query_amount_scale", "金额倍率", true, false, "number"),
integrationField("callback_status_field", "回调状态字段", true, false, "text"),
integrationField("callback_success_values", "回调成功值", true, false, "text"),
integrationField("callback_trade_no_field", "回调商户单号字段", true, false, "text"),
integrationField("callback_provider_trade_no_field", "回调平台单号字段", true, false, "text"),
}
var genericPaymentDefaults = map[string]any{
"protocol_version": "v1", "app_id": "", "merchant_id": "", "create_url": "", "query_url": "", "refund_url": "", "app_key": "",
"query_status_field": "", "query_success_values": "", "query_trade_no_field": "",
"query_provider_trade_no_field": "", "query_amount_field": "", "query_currency_field": "", "query_amount_scale": 1,
"callback_status_field": "", "callback_success_values": "", "callback_trade_no_field": "",
"callback_provider_trade_no_field": "", "notify_url": "", "return_url": "",
}
func genericPaymentDefinition(provider, name, description string) IntegrationConfigDefinition {
defaults := mergeIntegrationDefaults(genericPaymentDefaults, nil)
fields := append([]IntegrationConfigField(nil), genericPaymentFields...)
return paymentDefinition(provider, name, description, defaults, fields...)
}
var integrationDefinitions = map[string][]IntegrationConfigDefinition{
IntegrationKindMQ: {
{
Kind: IntegrationKindMQ, Provider: "emqx", Name: "EMQX", Description: "EMQX MQTT 消息服务",
Defaults: map[string]any{"broker": "tcp://127.0.0.1:1883", "client_id": "kra", "username": "", "password": "", "keep_alive": 30, "clean_session": true, "connect_timeout": 10, "reconnect_interval": 5},
Fields: []IntegrationConfigField{
{Key: "broker", Label: "Broker 地址", Type: "text", Required: true, Placeholder: "tcp://127.0.0.1:1883"},
{Key: "client_id", Label: "客户端 ID", Type: "text", Required: true, Placeholder: "kra"},
{Key: "username", Label: "用户名", Type: "text"},
{Key: "password", Label: "密码", Type: "password", Secret: true},
{Key: "keep_alive", Label: "心跳间隔(秒)", Type: "number", Required: true},
{Key: "clean_session", Label: "清理会话", Type: "switch", Description: "连接时不恢复 Broker 端保存的旧会话。"},
{Key: "connect_timeout", Label: "连接超时(秒)", Type: "number", Required: true},
{Key: "reconnect_interval", Label: "重连退避上限(秒)", Type: "number", Required: true, Description: "网络中断后自动重连的最大退避间隔。"},
},
},
{
Kind: IntegrationKindMQ, Provider: "rabbitmq", Name: "RabbitMQ", Description: "RabbitMQ AMQP 消息队列",
Defaults: map[string]any{"host": "127.0.0.1", "port": 5672, "username": "guest", "password": "guest", "vhost": "/", "exchange": "kra", "exchange_type": "topic", "queue": "kra", "routing_key": "#", "durable": true, "auto_delete": false, "prefetch_count": 10, "heartbeat": 10, "connect_timeout": 10, "reconnect_interval": 5, "tls": false},
Fields: []IntegrationConfigField{
{Key: "host", Label: "主机", Type: "text", Required: true, Placeholder: "127.0.0.1"},
{Key: "port", Label: "端口", Type: "number", Required: true},
{Key: "username", Label: "用户名", Type: "text", Required: true},
{Key: "password", Label: "密码", Type: "password", Required: true, Secret: true},
{Key: "vhost", Label: "Virtual Host", Type: "text", Required: true, Placeholder: "/"},
{Key: "exchange", Label: "Exchange", Type: "text", Required: true, Placeholder: "kra"},
{Key: "exchange_type", Label: "Exchange 类型", Type: "select", Required: true, Options: []IntegrationConfigOption{{Label: "topic", Value: "topic"}, {Label: "direct", Value: "direct"}, {Label: "fanout", Value: "fanout"}}},
{Key: "queue", Label: "Queue", Type: "text", Required: true, Placeholder: "kra"},
{Key: "routing_key", Label: "默认 Routing Key", Type: "text", Required: true, Placeholder: "#", Description: "业务未指定订阅键时使用;topic 类型支持 * 和 #。"},
{Key: "durable", Label: "持久化", Type: "switch"},
{Key: "auto_delete", Label: "自动删除", Type: "switch"},
{Key: "prefetch_count", Label: "预取数量", Type: "number"},
{Key: "heartbeat", Label: "心跳间隔(秒)", Type: "number"},
{Key: "connect_timeout", Label: "连接超时(秒)", Type: "number", Required: true},
{Key: "reconnect_interval", Label: "重连退避上限(秒)", Type: "number", Required: true, Description: "网络中断后自动重连的最大退避间隔。"},
{Key: "tls", Label: "启用 TLS", Type: "switch"},
},
},
},
IntegrationKindWebSocket: {
{
Kind: IntegrationKindWebSocket, Provider: "melody", Name: "WebSocket", Description: "WebSocket 实时连接服务",
Defaults: map[string]any{"path": "/ws", "allow_origins": []string{}, "max_message_size": 0, "write_wait": "10s", "pong_wait": "60s", "ping_period": "54s", "message_buffer_size": 0, "concurrent_message_handling": false},
Fields: []IntegrationConfigField{
{Key: "path", Label: "访问路径", Type: "text", Required: true, Placeholder: "/ws"},
{Key: "allow_origins", Label: "允许的来源", Type: "string-list", Placeholder: "https://admin.example.com", Description: "每行一个 Origin;留空时沿用 WebSocket 组件默认策略。"},
{Key: "max_message_size", Label: "最大消息字节数", Type: "number", Description: "0 表示使用组件默认值。"},
{Key: "write_wait", Label: "写入超时", Type: "text", Required: true, Placeholder: "10s"},
{Key: "pong_wait", Label: "Pong 等待时间", Type: "text", Required: true, Placeholder: "60s"},
{Key: "ping_period", Label: "Ping 间隔", Type: "text", Required: true, Placeholder: "54s"},
{Key: "message_buffer_size", Label: "消息缓冲区", Type: "number", Description: "0 表示不额外缓冲。"},
{Key: "concurrent_message_handling", Label: "并发处理消息", Type: "switch"},
},
},
},
IntegrationKindPayment: {
paymentDefinition(paymentutil.ProviderAlipay, "支付宝", "支付宝 OpenAPI RSA2 支付", map[string]any{"app_id": "", "private_key": "", "public_key": "", "environment": "production", "sign_type": "RSA2", "gateway_url": "https://openapi.alipay.com/gateway.do", "method": "alipay.trade.create"},
integrationField("app_id", "应用 ID", true, false, "text"), integrationField("private_key", "应用私钥", true, true, "textarea"), integrationField("public_key", "支付宝公钥", true, true, "textarea"),
integrationSelect("environment", "环境", false, "production", "sandbox"), integrationSelect("sign_type", "签名算法", false, "RSA2", "RSA"), integrationField("gateway_url", "网关地址", false, false, "url"),
integrationSelect("method", "默认支付方式", false, "alipay.trade.create", "alipay.trade.pay", "alipay.trade.precreate", "alipay.trade.app.pay", "alipay.trade.page.pay", "alipay.trade.wap.pay")),
paymentDefinition(paymentutil.ProviderAlipayV3, "支付宝 V3", "支付宝证书模式 V3 接口", map[string]any{"app_id": "", "private_key": "", "app_cert": "", "root_cert": "", "public_cert": "", "environment": "production", "api_base_url": "https://openapi.alipay.com", "gateway_url": "https://openapi.alipay.com/gateway.do", "method": "alipay.trade.create"},
integrationField("app_id", "应用 ID", true, false, "text"), integrationField("private_key", "应用私钥", false, true, "textarea"), integrationField("app_cert", "应用公钥证书", false, true, "textarea"), integrationField("root_cert", "支付宝根证书", false, true, "textarea"), integrationField("public_cert", "支付宝公钥证书", false, true, "textarea"),
integrationSelect("environment", "环境", false, "production", "sandbox"), integrationField("api_base_url", "API 地址", false, false, "url"), integrationField("gateway_url", "网关地址", false, false, "url"), integrationSelect("method", "默认支付方式", false, "alipay.trade.create", "alipay.trade.pay", "alipay.trade.precreate", "alipay.trade.app.pay", "alipay.trade.page.pay", "alipay.trade.wap.pay")),
paymentDefinition(paymentutil.ProviderWechatV2, "微信支付 V2", "微信支付 V2,含退款双向证书", map[string]any{"app_id": "", "merchant_id": "", "mch_key": "", "sign_type": "MD5", "trade_type": "NATIVE", "client_cert": "", "client_key": ""},
integrationField("app_id", "应用 ID", true, false, "text"), integrationField("merchant_id", "商户号", true, false, "text"), integrationField("mch_key", "API 密钥", true, true, "password"), integrationSelect("sign_type", "签名算法", false, "MD5", "HMAC-SHA256"), integrationSelect("trade_type", "默认交易类型", false, "JSAPI", "APP", "NATIVE", "MWEB", "MICROPAY"), integrationField("client_cert", "商户证书", false, true, "textarea"), integrationField("client_key", "证书私钥", false, true, "textarea")),
paymentDefinition(paymentutil.ProviderWechatV3, "微信支付 V3", "微信支付 API v3", map[string]any{"app_id": "", "merchant_id": "", "serial_no": "", "private_key": "", "api_v3_key": "", "platform_cert": "", "platform_serial_no": "", "trade_type": "jsapi"},
integrationField("app_id", "应用 ID", true, false, "text"), integrationField("merchant_id", "商户号", true, false, "text"), integrationField("serial_no", "商户证书序列号", true, false, "text"), integrationField("private_key", "商户私钥", true, true, "textarea"), integrationField("api_v3_key", "API v3 密钥", true, true, "password"), integrationField("platform_cert", "平台证书", true, true, "textarea"), integrationField("platform_serial_no", "平台证书序列号", false, false, "text"), integrationSelect("trade_type", "默认交易类型", false, "jsapi", "app", "native", "h5", "codepay")),
paymentDefinition(paymentutil.ProviderApple, "Apple IAP", "Apple App Store Server API", map[string]any{"issuer_id": "", "key_id": "", "bundle_id": "", "private_key": "", "price_divisor": 10, "environment": "production", "test_product_id": "", "test_transaction_id": ""},
integrationField("issuer_id", "Issuer ID", true, false, "text"), integrationField("key_id", "Key ID", true, false, "text"), integrationField("bundle_id", "Bundle ID", true, false, "text"), integrationField("private_key", "P8 私钥", true, true, "textarea"), integrationField("price_divisor", "价格除数", false, false, "number"), integrationSelect("environment", "环境", false, "production", "sandbox"), integrationField("test_product_id", "测试商品 ID", false, false, "text"), integrationField("test_transaction_id", "沙箱交易 ID", false, false, "text")),
paymentDefinition(paymentutil.ProviderDouyin, "抖音支付", "抖音开放平台支付", map[string]any{"app_id": "", "merchant_id": "", "serial_no": "", "api_key": "", "private_key": "", "platform_cert": "", "platform_serial_no": "", "trade_type": "jsapi", "environment": "production"},
integrationField("app_id", "应用 ID", true, false, "text"), integrationField("merchant_id", "商户号", true, false, "text"), integrationField("serial_no", "商户证书序列号", true, false, "text"), integrationField("api_key", "API 密钥", true, true, "password"), integrationField("private_key", "商户私钥", true, true, "textarea"), integrationField("platform_cert", "平台证书", true, true, "textarea"), integrationField("platform_serial_no", "平台证书序列号", false, false, "text"), integrationSelect("trade_type", "默认交易类型", false, "app", "jsapi", "h5", "native"), integrationSelect("environment", "环境", false, "production")),
paymentDefinition(paymentutil.ProviderQQ, "QQ 钱包", "QQ 钱包支付", map[string]any{"mch_id": "", "api_key": "", "sign_type": "MD5", "trade_type": "NATIVE", "cert_file": "", "key_file": "", "environment": "production"},
integrationField("mch_id", "商户号", true, false, "text"), integrationField("api_key", "API 密钥", true, true, "password"), integrationSelect("sign_type", "签名算法", false, "MD5", "HMAC-SHA256"), integrationSelect("trade_type", "默认交易类型", false, "JSAPI", "NATIVE", "APP", "MICROPAY"), integrationField("cert_file", "退款证书路径", false, false, "text"), integrationField("key_file", "退款私钥路径", false, true, "text"), integrationSelect("environment", "环境", false, "production")),
paymentDefinition(paymentutil.ProviderAllinPay, "通联支付", "通联收银宝支付", map[string]any{"cus_id": "", "app_id": "", "private_key": "", "public_key": "", "org_id": "", "pay_type": "W02", "query_order_type": "reqsn", "currency": "CNY", "environment": "production"},
integrationField("cus_id", "商户号", true, false, "text"), integrationField("app_id", "应用 ID", true, false, "text"), integrationField("private_key", "商户私钥", true, true, "textarea"), integrationField("public_key", "平台公钥", true, true, "textarea"), integrationField("org_id", "机构号", false, false, "text"), integrationField("pay_type", "支付类型", false, false, "text"), integrationSelect("query_order_type", "查单标识", false, "reqsn", "trxid"), integrationField("currency", "币种", false, false, "text"), integrationSelect("environment", "环境", false, "production", "sandbox")),
paymentDefinition(paymentutil.ProviderLakala, "拉卡拉", "拉卡拉聚合支付", map[string]any{"partner_code": "", "credential_code": "", "channel": "Wechat", "method": "jsapi", "currency": "CNY", "environment": "production"},
integrationField("partner_code", "合作方编号", true, false, "text"), integrationField("credential_code", "凭证码", true, true, "password"), integrationSelect("channel", "支付渠道", false, "Wechat", "Alipay", "UnionPay"), integrationSelect("method", "默认支付方式", false, "jsapi", "h5", "mini", "native", "qrcode", "native_jsapi", "sdk", "web", "retail", "retail_qrcode"), integrationField("currency", "币种", false, false, "text"), integrationSelect("environment", "环境", false, "production")),
paymentDefinition(paymentutil.ProviderPayPal, "PayPal", "PayPal Checkout", map[string]any{"client_id": "", "client_secret": "", "webhook_id": "", "environment": "sandbox", "return_url": "", "cancel_url": "", "auto_capture": true, "test_currency": "USD"},
integrationField("client_id", "Client ID", true, false, "text"), integrationField("client_secret", "Client Secret", true, true, "password"), integrationField("webhook_id", "Webhook ID", true, false, "text"), integrationSelect("environment", "环境", false, "sandbox", "production"), integrationField("cancel_url", "取消跳转地址", false, false, "url"), integrationField("auto_capture", "自动捕获", false, false, "switch")),
paymentDefinition(paymentutil.ProviderSaobei, "扫呗", "扫呗聚合支付", map[string]any{"inst_no": "", "key": "", "merchant_no": "", "terminal_id": "", "access_token": "", "pay_type": "010", "currency": "CNY", "environment": "production"},
integrationField("inst_no", "机构号", true, false, "text"), integrationField("key", "机构密钥", true, true, "password"), integrationField("merchant_no", "商户号", true, false, "text"), integrationField("terminal_id", "终端号", true, false, "text"), integrationField("access_token", "访问令牌", true, true, "password"), integrationField("pay_type", "支付类型", false, false, "text"), integrationField("currency", "币种", false, false, "text"), integrationSelect("environment", "环境", false, "production", "sandbox")),
genericPaymentDefinition(paymentutil.ProviderChinaums, "银联商务", "按商户协议配置的银联商务适配器"),
genericPaymentDefinition(paymentutil.ProviderSFT, "商福通", "按商户协议配置的商福通适配器"),
genericPaymentDefinition(paymentutil.ProviderSuperPay, "Supper Pay", "按商户协议配置的 Supper Pay 适配器"),
genericPaymentDefinition(paymentutil.ProviderWechatGame, "微信小游戏支付", "微信小游戏虚拟支付配置驱动适配器"),
genericPaymentDefinition(paymentutil.ProviderDouyinGame, "抖音小游戏支付", "抖音小游戏支付配置驱动适配器"),
},
}
+6
View File
@@ -0,0 +1,6 @@
package integration
import "github.com/google/wire"
// ProviderSet wires communication and integration-configuration usecases.
var ProviderSet = wire.NewSet(NewIntegrationConfigUsecase)