434 lines
17 KiB
Go
434 lines
17 KiB
Go
package application
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"path"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/assets"
|
|
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/billing"
|
|
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/httpapi"
|
|
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/jobs"
|
|
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/logging"
|
|
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/postgres"
|
|
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/providers"
|
|
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/settings"
|
|
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/webhook"
|
|
)
|
|
|
|
func unavailableHandler(status int) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(status) })
|
|
}
|
|
|
|
func firstNonEmpty(values ...string) string {
|
|
for _, value := range values {
|
|
if value = strings.TrimSpace(value); value != "" {
|
|
return value
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func positiveInt64Env(getenv postgres.Getenv, name string, fallback int64) int64 {
|
|
value, err := strconv.ParseInt(strings.TrimSpace(getenv(name)), 10, 64)
|
|
if err != nil || value <= 0 {
|
|
return fallback
|
|
}
|
|
return value
|
|
}
|
|
|
|
func durationEnv(getenv postgres.Getenv, name string, fallback time.Duration) time.Duration {
|
|
milliseconds := positiveInt64Env(getenv, name, fallback.Milliseconds())
|
|
return time.Duration(milliseconds) * time.Millisecond
|
|
}
|
|
|
|
func parseBool(value string) bool {
|
|
switch strings.ToLower(strings.TrimSpace(value)) {
|
|
case "1", "true", "yes", "on":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func applicationJobID() string {
|
|
return applicationID("job")
|
|
}
|
|
|
|
func applicationID(prefix string) string {
|
|
raw := make([]byte, 12)
|
|
if _, err := rand.Read(raw); err == nil {
|
|
return prefix + "-" + hex.EncodeToString(raw)
|
|
}
|
|
return prefix + "-" + strconv.FormatInt(time.Now().UnixNano(), 36)
|
|
}
|
|
|
|
func imageEngine(getenv postgres.Getenv) string {
|
|
engine := strings.ToLower(firstNonEmpty(getenv("IMAGE_GENERATE_ENGINE"), getenv("IMAGE_CREATION_ENGINE"), getenv("IMAGE_PROVIDER"), "jimeng"))
|
|
if engine != "evolink" && engine != "bailian" {
|
|
return "jimeng"
|
|
}
|
|
return engine
|
|
}
|
|
|
|
func videoEngine(getenv postgres.Getenv) string {
|
|
if strings.EqualFold(strings.TrimSpace(getenv("VIDEO_GENERATE_ENGINE")), "seedance") {
|
|
return "seedance"
|
|
}
|
|
return "bailian"
|
|
}
|
|
|
|
func mockEnabled(getenv postgres.Getenv, flag string, configured bool) bool {
|
|
switch strings.ToLower(strings.TrimSpace(getenv(flag))) {
|
|
case "1", "true":
|
|
return true
|
|
case "0", "false":
|
|
return false
|
|
default:
|
|
return !configured
|
|
}
|
|
}
|
|
|
|
func imageProvider(getenv postgres.Getenv) string {
|
|
switch imageEngine(getenv) {
|
|
case "evolink":
|
|
if mockEnabled(getenv, "EVOLINK_MOCK", strings.TrimSpace(getenv("EVOLINK_API_KEY")) != "") {
|
|
return "mock"
|
|
}
|
|
return "evolink"
|
|
case "bailian":
|
|
if mockEnabled(getenv, "BAILIAN_MOCK", bailianAPIKey(getenv) != "") {
|
|
return "mock"
|
|
}
|
|
return "bailian"
|
|
default:
|
|
configured := strings.TrimSpace(getenv("VOLCENGINE_ACCESS_KEY_ID")) != "" && strings.TrimSpace(getenv("VOLCENGINE_SECRET_ACCESS_KEY")) != ""
|
|
if mockEnabled(getenv, "JIMENG_VISUAL_MOCK", configured) {
|
|
return "mock"
|
|
}
|
|
return "volcengine-visual"
|
|
}
|
|
}
|
|
|
|
func videoProvider(getenv postgres.Getenv) string {
|
|
if videoEngine(getenv) == "seedance" {
|
|
if mockEnabled(getenv, "SEEDANCE_MOCK", strings.TrimSpace(getenv("SEEDANCE_API_KEY")) != "") {
|
|
return "mock"
|
|
}
|
|
return "seedance"
|
|
}
|
|
if mockEnabled(getenv, "BAILIAN_MOCK", bailianAPIKey(getenv) != "") {
|
|
return "mock"
|
|
}
|
|
return "bailian"
|
|
}
|
|
|
|
func imageModel(getenv postgres.Getenv) string {
|
|
switch imageEngine(getenv) {
|
|
case "evolink":
|
|
return firstNonEmpty(getenv("EVOLINK_IMAGE_MODEL"), "gpt-image-2")
|
|
case "bailian":
|
|
return firstNonEmpty(getenv("BAILIAN_IMAGE_MODEL"), "wan2.7-image-pro")
|
|
default:
|
|
return firstNonEmpty(getenv("JIMENG_IMAGE_GENERATE_46_REQ_KEY"), "jimeng_seedream46_cvtob")
|
|
}
|
|
}
|
|
|
|
func videoModel(getenv postgres.Getenv) string {
|
|
if videoEngine(getenv) == "seedance" {
|
|
return firstNonEmpty(getenv("SEEDANCE_MODEL"), "doubao-seedance-2-0-260128")
|
|
}
|
|
return firstNonEmpty(getenv("BAILIAN_VIDEO_MODEL"), "wan2.7-i2v-2026-04-25")
|
|
}
|
|
|
|
func bailianAPIKey(getenv postgres.Getenv) string {
|
|
return firstNonEmpty(getenv("BAILIAN_API_KEY"), getenv("DASHSCOPE_API_KEY"))
|
|
}
|
|
|
|
func bailianNativeBaseURL(getenv postgres.Getenv) string {
|
|
base := strings.TrimRight(firstNonEmpty(getenv("BAILIAN_BASE_URL"), "https://llm-126wneubbdo6dbr5.cn-beijing.maas.aliyuncs.com/compatible-mode/v1"), "/")
|
|
lower := strings.ToLower(base)
|
|
const suffix = "/compatible-mode/v1"
|
|
if strings.HasSuffix(lower, suffix) {
|
|
return base[:len(base)-len(suffix)]
|
|
}
|
|
return base
|
|
}
|
|
|
|
func buildProviderRegistry(getenv postgres.Getenv) jobs.ProviderRegistry {
|
|
client := &http.Client{Timeout: durationEnv(getenv, "ZHINIAN_PROVIDER_TIMEOUT_MS", 30*time.Second)}
|
|
maximum := positiveInt64Env(getenv, "ZHINIAN_PROVIDER_MAX_RESPONSE_BYTES", 2<<20)
|
|
return jobs.ProviderRegistry{
|
|
"mock": providers.NewMock(firstNonEmpty(getenv("ZHINIAN_MOCK_SEED"), "zhinian")),
|
|
"volcengine-visual": providers.NewVolcengine(providers.Config{
|
|
BaseURL: firstNonEmpty(getenv("VOLCENGINE_VISUAL_ENDPOINT"), "https://visual.volcengineapi.com"),
|
|
Model: imageModel(getenv), AccessKeyID: getenv("VOLCENGINE_ACCESS_KEY_ID"), SecretAccessKey: getenv("VOLCENGINE_SECRET_ACCESS_KEY"),
|
|
Region: firstNonEmpty(getenv("VOLCENGINE_REGION"), "cn-north-1"), Service: firstNonEmpty(getenv("VOLCENGINE_SERVICE"), "cv"), MaxResponseBytes: maximum,
|
|
}, client, nil),
|
|
"evolink": providers.NewEvoLink(providers.Config{BaseURL: firstNonEmpty(getenv("EVOLINK_BASE_URL"), "https://api.evolink.ai"), APIKey: getenv("EVOLINK_API_KEY"), Model: firstNonEmpty(getenv("EVOLINK_IMAGE_MODEL"), "gpt-image-2"), MaxResponseBytes: maximum}, client),
|
|
"bailian": providers.NewBailian(providers.Config{BaseURL: bailianNativeBaseURL(getenv), APIKey: bailianAPIKey(getenv), Model: firstNonEmpty(getenv("BAILIAN_IMAGE_MODEL"), "wan2.7-image-pro"), MaxResponseBytes: maximum}, client),
|
|
"seedance": providers.NewSeedance(providers.Config{BaseURL: firstNonEmpty(getenv("SEEDANCE_BASE_URL"), "https://ark.cn-beijing.volces.com/api/v3"), APIKey: getenv("SEEDANCE_API_KEY"), Model: firstNonEmpty(getenv("SEEDANCE_MODEL"), "doubao-seedance-2-0-260128"), MaxResponseBytes: maximum}, client),
|
|
}
|
|
}
|
|
|
|
func providerImageTargets(getenv postgres.Getenv) map[string]jobs.ProviderTarget {
|
|
jimengConfigured := strings.TrimSpace(getenv("VOLCENGINE_ACCESS_KEY_ID")) != "" && strings.TrimSpace(getenv("VOLCENGINE_SECRET_ACCESS_KEY")) != ""
|
|
return map[string]jobs.ProviderTarget{
|
|
"jimeng": {
|
|
Provider: providerOrMock("volcengine-visual", mockEnabled(getenv, "JIMENG_VISUAL_MOCK", jimengConfigured)),
|
|
Model: firstNonEmpty(getenv("JIMENG_IMAGE_GENERATE_46_REQ_KEY"), "jimeng_seedream46_cvtob"),
|
|
},
|
|
"evolink": {
|
|
Provider: providerOrMock("evolink", mockEnabled(getenv, "EVOLINK_MOCK", strings.TrimSpace(getenv("EVOLINK_API_KEY")) != "")),
|
|
Model: firstNonEmpty(getenv("EVOLINK_IMAGE_MODEL"), "gpt-image-2"),
|
|
},
|
|
"bailian": {
|
|
Provider: providerOrMock("bailian", mockEnabled(getenv, "BAILIAN_MOCK", bailianAPIKey(getenv) != "")),
|
|
Model: firstNonEmpty(getenv("BAILIAN_IMAGE_MODEL"), "wan2.7-image-pro"),
|
|
},
|
|
}
|
|
}
|
|
|
|
func providerVideoTargets(getenv postgres.Getenv) map[string]jobs.ProviderTarget {
|
|
return map[string]jobs.ProviderTarget{
|
|
"seedance": {
|
|
Provider: providerOrMock("seedance", mockEnabled(getenv, "SEEDANCE_MOCK", strings.TrimSpace(getenv("SEEDANCE_API_KEY")) != "")),
|
|
Model: firstNonEmpty(getenv("SEEDANCE_MODEL"), "doubao-seedance-2-0-260128"),
|
|
Settings: map[string]any{
|
|
"ratio": firstNonEmpty(getenv("SEEDANCE_DEFAULT_RATIO"), "9:16"),
|
|
"duration": float64(positiveInt64Env(getenv, "SEEDANCE_DEFAULT_DURATION", 5)),
|
|
"resolution": firstNonEmpty(getenv("SEEDANCE_DEFAULT_RESOLUTION"), "720p"),
|
|
},
|
|
},
|
|
"bailian": {
|
|
Provider: providerOrMock("bailian", mockEnabled(getenv, "BAILIAN_MOCK", bailianAPIKey(getenv) != "")),
|
|
Model: firstNonEmpty(getenv("BAILIAN_VIDEO_MODEL"), "wan2.7-i2v-2026-04-25"),
|
|
},
|
|
}
|
|
}
|
|
|
|
func providerOrMock(provider string, mocked bool) string {
|
|
if mocked {
|
|
return "mock"
|
|
}
|
|
return provider
|
|
}
|
|
|
|
func defaultWebhookSender(getenv postgres.Getenv) (*webhook.HTTPSender, error) {
|
|
return webhook.NewPublicHTTPSender(
|
|
durationEnv(getenv, "ZHINIAN_WEBHOOK_TIMEOUT_MS", 10*time.Second),
|
|
webhook.NewPublicDestinationPolicy(nil, nil),
|
|
)
|
|
}
|
|
|
|
func capabilitySummary(getenv postgres.Getenv) func(context.Context) (any, error) {
|
|
return func(context.Context) (any, error) {
|
|
engine := imageEngine(getenv)
|
|
provider := map[string]string{"jimeng": "volcengine-visual", "evolink": "evolink", "bailian": "bailian"}[engine]
|
|
primaryVideoEngine := videoEngine(getenv)
|
|
primaryVideo := map[string]any{
|
|
"id": "video.generate", "kind": "video", "engine": primaryVideoEngine,
|
|
"provider": primaryVideoEngine, "reqKey": videoModel(getenv),
|
|
}
|
|
if primaryVideoEngine == "seedance" {
|
|
primaryVideo["label"] = "Seedance 视频生成"
|
|
primaryVideo["limits"] = seedanceCapabilityLimits()
|
|
} else {
|
|
primaryVideo["label"] = "百炼图生视频"
|
|
primaryVideo["limits"] = bailianVideoCapabilityLimits()
|
|
}
|
|
return []any{
|
|
map[string]any{"id": "image.generate", "label": "图片生成 4.6", "kind": "image", "engine": engine, "provider": provider, "reqKey": imageModel(getenv)},
|
|
primaryVideo,
|
|
map[string]any{"id": "video.generate.bailian", "label": "百炼图生视频", "kind": "video", "engine": "bailian", "provider": "bailian", "reqKey": firstNonEmpty(getenv("BAILIAN_VIDEO_MODEL"), "wan2.7-i2v-2026-04-25"), "limits": bailianVideoCapabilityLimits()},
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
func seedanceCapabilityLimits() map[string]any {
|
|
return map[string]any{"durationSeconds": map[string]int{"min": 4, "max": 15}, "ratios": []string{"16:9", "4:3", "1:1", "3:4", "9:16", "21:9", "adaptive"}, "resolutions": []string{"480p", "720p", "1080p"}}
|
|
}
|
|
|
|
func bailianVideoCapabilityLimits() map[string]any {
|
|
return map[string]any{"inputImages": map[string]int{"min": 1, "max": 2}, "durationSeconds": map[string]int{"min": 2, "max": 15}, "resolutions": []string{"720P", "1080P"}}
|
|
}
|
|
|
|
func runtimeHealthDetails(getenv postgres.Getenv) httpapi.HealthDetails {
|
|
image := imageEngine(getenv)
|
|
imageLabel := map[string]string{"jimeng": "即梦", "evolink": "EvoLink", "bailian": "阿里云百炼"}[image]
|
|
visualConfigured := strings.TrimSpace(getenv("VOLCENGINE_ACCESS_KEY_ID")) != "" && strings.TrimSpace(getenv("VOLCENGINE_SECRET_ACCESS_KEY")) != ""
|
|
auth, _ := ParseAuthConfig(getenv)
|
|
bailianKey := bailianAPIKey(getenv)
|
|
bailianMode := "missing"
|
|
if mockFlagEnabled(getenv("BAILIAN_MOCK")) {
|
|
bailianMode = "mock"
|
|
} else if bailianKey != "" {
|
|
bailianMode = "bailian"
|
|
}
|
|
authMode := "disabled"
|
|
if auth.Required {
|
|
authMode = "missing"
|
|
if auth.Configured {
|
|
authMode = "configured"
|
|
}
|
|
}
|
|
return httpapi.HealthDetails{
|
|
VisualAPIMode: providerMode(mockEnabled(getenv, "JIMENG_VISUAL_MOCK", visualConfigured), "volcengine"),
|
|
EvolinkMode: providerMode(mockEnabled(getenv, "EVOLINK_MOCK", strings.TrimSpace(getenv("EVOLINK_API_KEY")) != ""), "evolink"),
|
|
SeedanceMode: providerMode(mockEnabled(getenv, "SEEDANCE_MOCK", strings.TrimSpace(getenv("SEEDANCE_API_KEY")) != ""), "seedance"),
|
|
BailianMode: bailianMode,
|
|
AuthMode: authMode,
|
|
Capabilities: []any{
|
|
map[string]any{"id": "image.generate", "label": "图片生成 4.6", "engine": image, "engineLabel": imageLabel, "reqKey": imageModel(getenv)},
|
|
map[string]any{"id": "video.generate", "label": "Seedance 视频生成", "engine": "seedance", "engineLabel": "Seedance", "reqKey": firstNonEmpty(getenv("SEEDANCE_MODEL"), "doubao-seedance-2-0-260128")},
|
|
},
|
|
}
|
|
}
|
|
|
|
func providerMode(mock bool, live string) string {
|
|
if mock {
|
|
return "mock"
|
|
}
|
|
return live
|
|
}
|
|
|
|
func mockFlagEnabled(value string) bool {
|
|
switch strings.ToLower(strings.TrimSpace(value)) {
|
|
case "1", "true":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func remoteAssetMaxBytes(getenv postgres.Getenv) int64 {
|
|
return positiveInt64Env(getenv, "ZHINIAN_REMOTE_ASSET_MAX_BYTES", 20<<20)
|
|
}
|
|
|
|
var runtimeSettingKeys = []string{
|
|
"ALI_OSS_ACCESS_KEY_ID", "ALI_OSS_ACCESS_KEY_SECRET", "ALI_OSS_BUCKET", "ALI_OSS_ENDPOINT", "ALI_OSS_PUBLIC_BASE_URL",
|
|
"BAILIAN_API_KEY", "BAILIAN_BASE_URL", "BAILIAN_IMAGE_MODEL", "BAILIAN_VIDEO_MODEL", "DATABASE_URL", "DASHSCOPE_API_KEY",
|
|
"EVOLINK_API_KEY", "EVOLINK_BASE_URL", "EVOLINK_IMAGE_MODEL", "EVOLINK_IMAGE_QUALITY", "IMAGE_GENERATE_ENGINE",
|
|
"SEEDANCE_API_KEY", "SEEDANCE_MODEL", "VIDEO_GENERATE_ENGINE", "VOLCENGINE_ACCESS_KEY_ID", "VOLCENGINE_SECRET_ACCESS_KEY",
|
|
"ZHINIAN_AUTH_REQUIRED", "ZHINIAN_AUTH_SESSION_SECRET", "ZHINIAN_BILLING_ACCOUNT_BANK", "ZHINIAN_BILLING_ACCOUNT_NAME",
|
|
"ZHINIAN_BILLING_ACCOUNT_NUMBER", "ZHINIAN_BILLING_CONTACT", "ZHINIAN_BILLING_REQUIRED",
|
|
}
|
|
|
|
func runtimeEnvironment(getenv postgres.Getenv) map[string]string {
|
|
values := make(map[string]string, len(runtimeSettingKeys))
|
|
for _, key := range runtimeSettingKeys {
|
|
if value := getenv(key); value != "" {
|
|
values[key] = value
|
|
}
|
|
}
|
|
return values
|
|
}
|
|
|
|
func defaultSettingsService(getenv postgres.Getenv) *settings.Service {
|
|
path := firstNonEmpty(getenv("ZHINIAN_SETTINGS_FILE"), ".env.local")
|
|
return settings.New(path, runtimeEnvironment(getenv), nil)
|
|
}
|
|
|
|
type settingsBillingAccountStore struct{ service *settings.Service }
|
|
|
|
func (store settingsBillingAccountStore) Load(ctx context.Context) (billing.AccountConfig, error) {
|
|
value, err := store.service.Get(ctx)
|
|
if err != nil {
|
|
return billing.AccountConfig{}, err
|
|
}
|
|
payload, ok := value.(settings.Payload)
|
|
if !ok {
|
|
return billing.AccountConfig{}, fmt.Errorf("load billing account settings: unexpected settings payload")
|
|
}
|
|
fields := map[string]string{}
|
|
for _, group := range payload.Groups {
|
|
for _, field := range group.Fields {
|
|
fields[field.Key] = field.Value
|
|
}
|
|
}
|
|
return billing.AccountConfig{
|
|
AccountName: fields["ZHINIAN_BILLING_ACCOUNT_NAME"], BankName: fields["ZHINIAN_BILLING_ACCOUNT_BANK"],
|
|
AccountNumber: fields["ZHINIAN_BILLING_ACCOUNT_NUMBER"], Contact: fields["ZHINIAN_BILLING_CONTACT"],
|
|
}, nil
|
|
}
|
|
|
|
func (store settingsBillingAccountStore) Save(ctx context.Context, value billing.AccountConfig) error {
|
|
_, err := store.service.Save(ctx, map[string]any{
|
|
"ZHINIAN_BILLING_ACCOUNT_NAME": value.AccountName, "ZHINIAN_BILLING_ACCOUNT_BANK": value.BankName,
|
|
"ZHINIAN_BILLING_ACCOUNT_NUMBER": value.AccountNumber, "ZHINIAN_BILLING_CONTACT": value.Contact,
|
|
})
|
|
return err
|
|
}
|
|
|
|
type logServiceAdapter struct{ service *logging.Service }
|
|
|
|
func (adapter logServiceAdapter) List(ctx context.Context, filters httpapi.LogFilters) (any, error) {
|
|
return adapter.service.List(ctx, logging.Filters{Level: filters.Level, Q: filters.Q, Source: filters.Source, Limit: filters.Limit})
|
|
}
|
|
|
|
func (adapter logServiceAdapter) Clear(ctx context.Context) error { return adapter.service.Clear(ctx) }
|
|
|
|
func (adapter logServiceAdapter) Append(ctx context.Context, input logging.Input) (logging.Entry, error) {
|
|
return adapter.service.Append(ctx, input)
|
|
}
|
|
|
|
func defaultLogService(getenv postgres.Getenv) httpapi.LogService {
|
|
runtimeDirectory := firstNonEmpty(getenv("ZHINIAN_RUNTIME_DIR"), ".runtime")
|
|
logDirectory := firstNonEmpty(getenv("ZHINIAN_LOG_DIR"), filepath.Join(runtimeDirectory, "logs"))
|
|
return logServiceAdapter{service: logging.New(filepath.Join(logDirectory, "server-events.jsonl"), positiveInt64Env(getenv, "ZHINIAN_LOG_MAX_BYTES", 5<<20), nil, nil)}
|
|
}
|
|
|
|
type prefixedBlobStore struct {
|
|
prefix string
|
|
store assets.BlobStore
|
|
}
|
|
|
|
func (store prefixedBlobStore) Put(ctx context.Context, key string, body io.Reader, size int64, contentType string) (assets.StoredObject, error) {
|
|
stored, err := store.store.Put(ctx, path.Join(store.prefix, key), body, size, contentType)
|
|
if err != nil {
|
|
return assets.StoredObject{}, err
|
|
}
|
|
// StoragePath is an application key used by /uploads and
|
|
// /generated-results. The OSS namespace prefix is private to this adapter.
|
|
stored.Key = key
|
|
return stored, nil
|
|
}
|
|
|
|
func (store prefixedBlobStore) Read(ctx context.Context, key string) (assets.Blob, error) {
|
|
return store.store.Read(ctx, path.Join(store.prefix, key))
|
|
}
|
|
|
|
func (store prefixedBlobStore) Delete(ctx context.Context, key string) error {
|
|
return store.store.Delete(ctx, path.Join(store.prefix, key))
|
|
}
|
|
|
|
func configuredOSSBlobStore(getenv postgres.Getenv) (assets.BlobStore, bool, error) {
|
|
endpoint, bucket := strings.TrimSpace(getenv("ALI_OSS_ENDPOINT")), strings.TrimSpace(getenv("ALI_OSS_BUCKET"))
|
|
accessKeyID, secret := strings.TrimSpace(getenv("ALI_OSS_ACCESS_KEY_ID")), strings.TrimSpace(getenv("ALI_OSS_ACCESS_KEY_SECRET"))
|
|
publicURL := strings.TrimSpace(getenv("ALI_OSS_PUBLIC_BASE_URL"))
|
|
if endpoint == "" || bucket == "" || accessKeyID == "" || secret == "" || publicURL == "" {
|
|
return nil, false, nil
|
|
}
|
|
client, err := assets.NewOSSHTTPClient(accessKeyID, secret, &http.Client{Timeout: durationEnv(getenv, "ZHINIAN_OSS_TIMEOUT_MS", 30*time.Second)}, nil)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
store, err := assets.NewOSS(assets.OSSConfig{Endpoint: endpoint, Bucket: bucket, PublicBaseURL: publicURL, PublicRead: true}, client)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
prefix := strings.Trim(strings.TrimSpace(getenv("ALI_OSS_PREFIX")), "/")
|
|
if prefix == "" {
|
|
prefix = "zhinian"
|
|
}
|
|
return prefixedBlobStore{prefix: prefix, store: store}, true, nil
|
|
}
|