实现多商户履约平台基础

This commit is contained in:
yml2213
2026-07-30 12:02:32 +08:00
parent dde6f2c269
commit 83429456a6
41 changed files with 5487 additions and 504 deletions
@@ -0,0 +1,349 @@
CREATE TABLE IF NOT EXISTS users (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ,
deleted_at TIMESTAMPTZ,
username VARCHAR(64) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
nickname VARCHAR(64),
role VARCHAR(32) NOT NULL DEFAULT 'distributor',
status BIGINT DEFAULT 1,
invite_code VARCHAR(32),
parent_id BIGINT
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_users_username ON users (username);
CREATE UNIQUE INDEX IF NOT EXISTS idx_users_invite_code ON users (invite_code);
CREATE INDEX IF NOT EXISTS idx_users_deleted_at ON users (deleted_at);
CREATE INDEX IF NOT EXISTS idx_users_parent_id ON users (parent_id);
CREATE TABLE IF NOT EXISTS merchants (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ,
deleted_at TIMESTAMPTZ,
code VARCHAR(64) NOT NULL,
name VARCHAR(128) NOT NULL,
status VARCHAR(16) NOT NULL DEFAULT 'active',
contact_name VARCHAR(64),
contact_info VARCHAR(128)
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_merchants_code ON merchants (code);
CREATE INDEX IF NOT EXISTS idx_merchants_deleted_at ON merchants (deleted_at);
CREATE INDEX IF NOT EXISTS idx_merchants_status ON merchants (status);
CREATE TABLE IF NOT EXISTS merchant_members (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ,
merchant_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
role VARCHAR(32) NOT NULL DEFAULT 'operator',
status BIGINT NOT NULL DEFAULT 1,
is_default BOOLEAN NOT NULL DEFAULT FALSE
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_merchant_member ON merchant_members (merchant_id, user_id);
CREATE INDEX IF NOT EXISTS idx_merchant_members_merchant_id ON merchant_members (merchant_id);
CREATE INDEX IF NOT EXISTS idx_merchant_members_user_id ON merchant_members (user_id);
CREATE TABLE IF NOT EXISTS skins (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ,
deleted_at TIMESTAMPTZ,
merchant_id BIGINT NOT NULL DEFAULT 0,
name VARCHAR(128) NOT NULL,
sku VARCHAR(64) NOT NULL,
game VARCHAR(64),
category VARCHAR(64),
cover_url VARCHAR(512),
price DOUBLE PRECISION NOT NULL DEFAULT 0,
cost_price DOUBLE PRECISION DEFAULT 0,
commission DOUBLE PRECISION DEFAULT 0,
stock BIGINT DEFAULT 0,
status BIGINT DEFAULT 1,
description TEXT
);
DROP INDEX IF EXISTS idx_skins_sku;
CREATE UNIQUE INDEX IF NOT EXISTS idx_skins_merchant_sku ON skins (merchant_id, sku);
CREATE INDEX IF NOT EXISTS idx_skins_deleted_at ON skins (deleted_at);
CREATE INDEX IF NOT EXISTS idx_skins_merchant_id ON skins (merchant_id);
CREATE INDEX IF NOT EXISTS idx_skins_game ON skins (game);
CREATE INDEX IF NOT EXISTS idx_skins_category ON skins (category);
CREATE TABLE IF NOT EXISTS orders (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ,
deleted_at TIMESTAMPTZ,
merchant_id BIGINT NOT NULL DEFAULT 0,
order_no VARCHAR(64) NOT NULL,
skin_id BIGINT NOT NULL,
distributor_id BIGINT NOT NULL,
buyer_name VARCHAR(64),
amount DOUBLE PRECISION NOT NULL,
commission_amt DOUBLE PRECISION DEFAULT 0,
status VARCHAR(32) DEFAULT 'pending',
remark VARCHAR(255),
provider_order_no VARCHAR(64),
shipped_at TIMESTAMPTZ,
ship_fail_reason VARCHAR(512),
game_channel VARCHAR(64),
game_uid VARCHAR(128),
role_name VARCHAR(64),
pay_score BIGINT DEFAULT 0
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_orders_order_no ON orders (order_no);
CREATE INDEX IF NOT EXISTS idx_orders_deleted_at ON orders (deleted_at);
CREATE INDEX IF NOT EXISTS idx_orders_merchant_id ON orders (merchant_id);
CREATE INDEX IF NOT EXISTS idx_orders_skin_id ON orders (skin_id);
CREATE INDEX IF NOT EXISTS idx_orders_distributor_id ON orders (distributor_id);
CREATE INDEX IF NOT EXISTS idx_orders_status ON orders (status);
CREATE INDEX IF NOT EXISTS idx_orders_provider_order_no ON orders (provider_order_no);
CREATE TABLE IF NOT EXISTS ship_logs (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMPTZ,
merchant_id BIGINT NOT NULL DEFAULT 0,
order_no VARCHAR(64) NOT NULL,
order_id BIGINT,
ship_status VARCHAR(32) NOT NULL,
provider_order_no VARCHAR(64),
fail_reason VARCHAR(512),
payload TEXT,
result_status VARCHAR(32),
message VARCHAR(255)
);
CREATE INDEX IF NOT EXISTS idx_ship_logs_merchant_id ON ship_logs (merchant_id);
CREATE INDEX IF NOT EXISTS idx_ship_logs_order_no ON ship_logs (order_no);
CREATE INDEX IF NOT EXISTS idx_ship_logs_order_id ON ship_logs (order_id);
CREATE TABLE IF NOT EXISTS api_clients (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ,
deleted_at TIMESTAMPTZ,
merchant_id BIGINT NOT NULL,
name VARCHAR(128) NOT NULL,
app_key VARCHAR(96) NOT NULL,
secret_ciphertext TEXT NOT NULL,
signature_version VARCHAR(16) NOT NULL DEFAULT 'v1',
scopes TEXT NOT NULL,
status VARCHAR(16) NOT NULL DEFAULT 'active',
expires_at TIMESTAMPTZ,
last_used_at TIMESTAMPTZ
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_api_clients_app_key ON api_clients (app_key);
CREATE INDEX IF NOT EXISTS idx_api_clients_deleted_at ON api_clients (deleted_at);
CREATE INDEX IF NOT EXISTS idx_api_clients_merchant_id ON api_clients (merchant_id);
CREATE INDEX IF NOT EXISTS idx_api_clients_status ON api_clients (status);
CREATE TABLE IF NOT EXISTS products (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ,
deleted_at TIMESTAMPTZ,
code VARCHAR(96) NOT NULL,
name VARCHAR(160) NOT NULL,
category VARCHAR(64),
description TEXT,
attributes TEXT,
status VARCHAR(16) NOT NULL DEFAULT 'active'
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_products_code ON products (code);
CREATE INDEX IF NOT EXISTS idx_products_deleted_at ON products (deleted_at);
CREATE INDEX IF NOT EXISTS idx_products_category ON products (category);
CREATE INDEX IF NOT EXISTS idx_products_status ON products (status);
CREATE TABLE IF NOT EXISTS merchant_products (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ,
deleted_at TIMESTAMPTZ,
merchant_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
sku VARCHAR(96) NOT NULL,
display_name VARCHAR(160),
price_amount BIGINT NOT NULL DEFAULT 0,
cost_amount BIGINT NOT NULL DEFAULT 0,
currency VARCHAR(12) NOT NULL DEFAULT 'CNY',
stock BIGINT NOT NULL DEFAULT -1,
status VARCHAR(16) NOT NULL DEFAULT 'active',
fulfillment_config TEXT
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_merchant_product_sku ON merchant_products (merchant_id, sku);
CREATE INDEX IF NOT EXISTS idx_merchant_products_deleted_at ON merchant_products (deleted_at);
CREATE INDEX IF NOT EXISTS idx_merchant_products_merchant_id ON merchant_products (merchant_id);
CREATE INDEX IF NOT EXISTS idx_merchant_products_product_id ON merchant_products (product_id);
CREATE INDEX IF NOT EXISTS idx_merchant_products_status ON merchant_products (status);
CREATE TABLE IF NOT EXISTS wallet_accounts (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ,
merchant_id BIGINT NOT NULL,
currency VARCHAR(12) NOT NULL DEFAULT 'CNY',
available_balance BIGINT NOT NULL DEFAULT 0,
frozen_balance BIGINT NOT NULL DEFAULT 0
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_wallet_accounts_merchant_id ON wallet_accounts (merchant_id);
CREATE TABLE IF NOT EXISTS wallet_ledger_entries (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMPTZ,
merchant_id BIGINT NOT NULL,
wallet_account_id BIGINT NOT NULL,
entry_no VARCHAR(64) NOT NULL,
type VARCHAR(24) NOT NULL,
amount BIGINT NOT NULL,
balance_after BIGINT NOT NULL,
reference_type VARCHAR(32) NOT NULL,
reference_no VARCHAR(96) NOT NULL,
idempotency_key VARCHAR(128),
note VARCHAR(255)
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_wallet_ledger_entries_entry_no ON wallet_ledger_entries (entry_no);
CREATE UNIQUE INDEX IF NOT EXISTS idx_wallet_ledger_idempotency ON wallet_ledger_entries (merchant_id, idempotency_key);
CREATE INDEX IF NOT EXISTS idx_wallet_ledger_entries_merchant_id ON wallet_ledger_entries (merchant_id);
CREATE INDEX IF NOT EXISTS idx_wallet_ledger_entries_wallet_account_id ON wallet_ledger_entries (wallet_account_id);
CREATE INDEX IF NOT EXISTS idx_wallet_ledger_entries_type ON wallet_ledger_entries (type);
CREATE INDEX IF NOT EXISTS idx_wallet_ledger_entries_reference_no ON wallet_ledger_entries (reference_no);
CREATE TABLE IF NOT EXISTS fulfillment_orders (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ,
deleted_at TIMESTAMPTZ,
merchant_id BIGINT NOT NULL,
order_no VARCHAR(64) NOT NULL,
client_order_no VARCHAR(96) NOT NULL,
merchant_product_id BIGINT NOT NULL,
product_sku VARCHAR(96) NOT NULL,
product_name VARCHAR(160) NOT NULL,
quantity BIGINT NOT NULL DEFAULT 1,
amount BIGINT NOT NULL,
currency VARCHAR(12) NOT NULL DEFAULT 'CNY',
payment_status VARCHAR(16) NOT NULL DEFAULT 'pending',
fulfillment_status VARCHAR(16) NOT NULL DEFAULT 'pending',
buyer_reference VARCHAR(128),
request_data TEXT,
result_data TEXT,
provider_order_no VARCHAR(96),
failure_reason VARCHAR(512),
cancelled_at TIMESTAMPTZ,
delivered_at TIMESTAMPTZ
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_fulfillment_orders_order_no ON fulfillment_orders (order_no);
CREATE UNIQUE INDEX IF NOT EXISTS idx_order_client_no ON fulfillment_orders (merchant_id, client_order_no);
CREATE INDEX IF NOT EXISTS idx_fulfillment_orders_deleted_at ON fulfillment_orders (deleted_at);
CREATE INDEX IF NOT EXISTS idx_fulfillment_orders_merchant_id ON fulfillment_orders (merchant_id);
CREATE INDEX IF NOT EXISTS idx_fulfillment_orders_merchant_product_id ON fulfillment_orders (merchant_product_id);
CREATE INDEX IF NOT EXISTS idx_fulfillment_orders_payment_status ON fulfillment_orders (payment_status);
CREATE INDEX IF NOT EXISTS idx_fulfillment_orders_fulfillment_status ON fulfillment_orders (fulfillment_status);
CREATE INDEX IF NOT EXISTS idx_fulfillment_orders_provider_order_no ON fulfillment_orders (provider_order_no);
CREATE TABLE IF NOT EXISTS fulfillment_jobs (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ,
merchant_id BIGINT NOT NULL,
order_id BIGINT NOT NULL,
status VARCHAR(16) NOT NULL DEFAULT 'pending',
attempts BIGINT NOT NULL DEFAULT 0,
next_run_at TIMESTAMPTZ NOT NULL,
provider_order_no VARCHAR(96),
request_payload TEXT,
result_payload TEXT,
last_error VARCHAR(512)
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_fulfillment_jobs_order_id ON fulfillment_jobs (order_id);
CREATE INDEX IF NOT EXISTS idx_fulfillment_jobs_merchant_id ON fulfillment_jobs (merchant_id);
CREATE INDEX IF NOT EXISTS idx_fulfillment_jobs_status ON fulfillment_jobs (status);
CREATE INDEX IF NOT EXISTS idx_fulfillment_jobs_next_run_at ON fulfillment_jobs (next_run_at);
CREATE INDEX IF NOT EXISTS idx_fulfillment_jobs_provider_order_no ON fulfillment_jobs (provider_order_no);
CREATE TABLE IF NOT EXISTS callback_subscriptions (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ,
deleted_at TIMESTAMPTZ,
merchant_id BIGINT NOT NULL,
name VARCHAR(128) NOT NULL,
url VARCHAR(1024) NOT NULL,
events TEXT NOT NULL,
secret_ciphertext TEXT NOT NULL,
status VARCHAR(16) NOT NULL DEFAULT 'active'
);
CREATE INDEX IF NOT EXISTS idx_callback_subscriptions_deleted_at ON callback_subscriptions (deleted_at);
CREATE INDEX IF NOT EXISTS idx_callback_subscriptions_merchant_id ON callback_subscriptions (merchant_id);
CREATE INDEX IF NOT EXISTS idx_callback_subscriptions_status ON callback_subscriptions (status);
CREATE TABLE IF NOT EXISTS callback_deliveries (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ,
merchant_id BIGINT NOT NULL,
callback_subscription_id BIGINT NOT NULL,
event_id VARCHAR(64) NOT NULL,
event VARCHAR(64) NOT NULL,
payload TEXT NOT NULL,
status VARCHAR(16) NOT NULL DEFAULT 'pending',
attempts BIGINT NOT NULL DEFAULT 0,
next_attempt_at TIMESTAMPTZ NOT NULL,
last_status_code BIGINT NOT NULL DEFAULT 0,
last_response TEXT,
delivered_at TIMESTAMPTZ
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_callback_deliveries_event_id ON callback_deliveries (event_id);
CREATE INDEX IF NOT EXISTS idx_callback_deliveries_merchant_id ON callback_deliveries (merchant_id);
CREATE INDEX IF NOT EXISTS idx_callback_deliveries_callback_subscription_id ON callback_deliveries (callback_subscription_id);
CREATE INDEX IF NOT EXISTS idx_callback_deliveries_event ON callback_deliveries (event);
CREATE INDEX IF NOT EXISTS idx_callback_deliveries_status ON callback_deliveries (status);
CREATE INDEX IF NOT EXISTS idx_callback_deliveries_next_attempt_at ON callback_deliveries (next_attempt_at);
CREATE TABLE IF NOT EXISTS api_request_nonces (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMPTZ,
api_client_id BIGINT NOT NULL,
nonce VARCHAR(96) NOT NULL,
expires_at TIMESTAMPTZ NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_api_nonce ON api_request_nonces (api_client_id, nonce);
CREATE INDEX IF NOT EXISTS idx_api_request_nonces_api_client_id ON api_request_nonces (api_client_id);
CREATE INDEX IF NOT EXISTS idx_api_request_nonces_expires_at ON api_request_nonces (expires_at);
CREATE TABLE IF NOT EXISTS audit_logs (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMPTZ,
merchant_id BIGINT,
actor_user_id BIGINT,
api_client_id BIGINT,
request_id VARCHAR(64),
action VARCHAR(96) NOT NULL,
entity_type VARCHAR(64) NOT NULL,
entity_id VARCHAR(96) NOT NULL,
metadata TEXT
);
CREATE INDEX IF NOT EXISTS idx_audit_logs_merchant_id ON audit_logs (merchant_id);
CREATE INDEX IF NOT EXISTS idx_audit_logs_actor_user_id ON audit_logs (actor_user_id);
CREATE INDEX IF NOT EXISTS idx_audit_logs_api_client_id ON audit_logs (api_client_id);
CREATE INDEX IF NOT EXISTS idx_audit_logs_request_id ON audit_logs (request_id);
CREATE INDEX IF NOT EXISTS idx_audit_logs_action ON audit_logs (action);
CREATE INDEX IF NOT EXISTS idx_audit_logs_entity_type ON audit_logs (entity_type);
CREATE INDEX IF NOT EXISTS idx_audit_logs_entity_id ON audit_logs (entity_id);