PHP项目数字吉尔吉斯斯坦索姆与eKGS

wen PHP项目 2

本文目录导读:

PHP项目数字吉尔吉斯斯坦索姆与eKGS

  1. 理解核心概念
  2. 数据结构设计(MySQL/PostgreSQL)
  3. PHP货币处理类(推荐使用bcmath)
  4. 数据库操作示例
  5. 汇率与转换逻辑(当eKGS与KGS不同价时)
  6. 前端显示最佳实践
  7. 测试示例(PHPUnit)
  8. 安全与性能建议
  9. 常见问题场景处理

针对PHP项目中处理“数字吉尔吉斯斯坦索姆”(KGS)与“eKGS”(可能指电子索姆或特定系统内的代币/积分)的需求,以下是个完整的开发指南,涵盖数据结构、货币格式化、汇率处理及安全最佳实践。

理解核心概念

  • KGS:吉尔吉斯斯坦索姆(ISO 4217: KGS),传统法币,1 KGS = 100 tyiyn(提因)。
  • eKGS:可能指:
    • 电子索姆(数字货币版本)
    • 系统内虚拟积分(如1 eKGS = 1 KGS,但不可提现)
    • 第三方支付系统代币(如Elcard、Megapay等电子钱包)

关键假设:本文假设eKGS是系统内部与KGS1:1锚定的电子形式,但在计算、显示和交易中需要明确区分。

数据结构设计(MySQL/PostgreSQL)

1 用户余额表

CREATE TABLE user_balances (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL UNIQUE,
    eKGS_balance DECIMAL(16,2) NOT NULL DEFAULT 0.00,  -- 电子索姆
    KGS_balance DECIMAL(16,2) NOT NULL DEFAULT 0.00,   -- 真实索姆
    tyiyn_balance INT NOT NULL DEFAULT 0,               -- 提因(分币)
    last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

2 交易日志表

CREATE TABLE transaction_log (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    amount DECIMAL(16,2) NOT NULL,
    currency_type ENUM('KGS', 'eKGS', 'tyiyn') NOT NULL,
    transaction_type ENUM('deposit', 'withdrawal', 'transfer', 'conversion', 'payment'),
    balance_before DECIMAL(16,2),
    balance_after DECIMAL(16,2),
    exchange_rate DECIMAL(10,6) DEFAULT NULL,  -- 当涉及KGS/eKGS互转时
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_user_currency (user_id, currency_type)
);

PHP货币处理类(推荐使用bcmath)

1 核心货币类 KyrgyzstaniSom.php

<?php
class KyrgyzstaniSom {
    private const DECIMAL_PLACES = 2;
    private const TYIYN_PER_SOM = 100;
    private string $amount; // 使用字符串精确存储
    /**
     * @param string|int|float $amount 索姆金额
     * @throws InvalidArgumentException
     */
    public function __construct($amount = '0.00') {
        $this->setAmount($amount);
    }
    public function setAmount($amount): void {
        if (is_numeric($amount)) {
            $amount = number_format((float)$amount, self::DECIMAL_PLACES, '.', '');
        }
        if (!preg_match('/^-?\d+\.\d{2}$/', $amount)) {
            throw new InvalidArgumentException("Invalid KGS amount: $amount");
        }
        $this->amount = $amount;
    }
    // 加法(保留精度)
    public function add(KyrgyzstaniSom $other): KyrgyzstaniSom {
        $result = bcadd($this->amount, $other->amount, self::DECIMAL_PLACES);
        return new self($result);
    }
    // 减法
    public function subtract(KyrgyzstaniSom $other): KyrgyzstaniSom {
        $result = bcsub($this->amount, $other->amount, self::DECIMAL_PLACES);
        return new self($result);
    }
    // 转换为tyiyn(整数)
    public function toTyiyn(): int {
        return (int)bcmul($this->amount, (string)self::TYIYN_PER_SOM, 0);
    }
    // 格式化显示
    public function format(string $locale = 'ky_KG'): string {
        $formatter = new NumberFormatter($locale, NumberFormatter::DECIMAL);
        $formatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, 2);
        $formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, 2);
        $formatter->setTextAttribute(NumberFormatter::CURRENCY_CODE, 'KGS');
        return $formatter->formatCurrency((float)$this->amount, 'KGS');
    }
    // JSON序列化
    public function __toString(): string {
        return $this->amount;
    }
}

2 eKGS专用处理扩展

class EKGS extends KyrgyzstaniSom {
    private string $type = 'eKGS';
    // 覆写格式化,添加前缀
    public function format(string $locale = 'ky_KG'): string {
        return 'e' . parent::format($locale);
    }
    // 转换到KGS(1:1,但可在此加入费率逻辑)
    public function toKGS(float $exchangeRate = 1.0): KyrgyzstaniSom {
        $kgsAmount = bcmul($this->amount, (string)$exchangeRate, 2);
        return new KyrgyzstaniSom($kgsAmount);
    }
}

数据库操作示例

1 查询用户余额

class BalanceService {
    private PDO $pdo;
    public function getUserBalances(int $userId): array {
        $stmt = $this->pdo->prepare(
            "SELECT eKGS_balance, KGS_balance FROM user_balances WHERE user_id = ?"
        );
        $stmt->execute([$userId]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        return [
            'eKGS' => new EKGS($row['eKGS_balance']),
            'KGS'  => new KyrgyzstaniSom($row['KGS_balance']),
        ];
    }
    // 转账逻辑(带原子操作)
    public function transfer(int $fromUserId, int $toUserId, KyrgyzstaniSom $amount, string $currency): bool {
        try {
            $this->pdo->beginTransaction();
            // 1. 检查发送方余额(使用数据库级锁)
            $stmt = $this->pdo->prepare(
                "SELECT {$currency}_balance FROM user_balances WHERE user_id = ? FOR UPDATE"
            );
            $stmt->execute([$fromUserId]);
            $fromBalance = $stmt->fetchColumn();
            if ($fromBalance < $amount->__toString()) {
                throw new \RuntimeException('余额不足');
            }
            // 2. 更新余额
            $this->pdo->prepare(
                "UPDATE user_balances SET {$currency}_balance = {$currency}_balance - ? WHERE user_id = ?"
            )->execute([$amount->__toString(), $fromUserId]);
            $this->pdo->prepare(
                "UPDATE user_balances SET {$currency}_balance = {$currency}_balance + ? WHERE user_id = ?"
            )->execute([$amount->__toString(), $toUserId]);
            // 3. 记录交易日志
            $this->logTransaction($fromUserId, $amount, $currency, 'transfer');
            $this->pdo->commit();
            return true;
        } catch (\Exception $e) {
            $this->pdo->rollBack();
            throw $e;
        }
    }
}

汇率与转换逻辑(当eKGS与KGS不同价时)

1 汇率表结构

CREATE TABLE exchange_rates (
    id INT PRIMARY KEY AUTO_INCREMENT,
    from_currency ENUM('KGS','eKGS','USD','RUB') NOT NULL,
    to_currency ENUM('KGS','eKGS','USD','RUB') NOT NULL,
    rate DECIMAL(12,6) NOT NULL,
    valid_from TIMESTAMP NOT NULL,
    valid_until TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_currency_pair (from_currency, to_currency, valid_from)
);

2 汇率转换服务

class CurrencyConverter {
    private PDO $pdo;
    public function convert(string $from, string $to, KyrgyzstaniSom $amount): KyrgyzstaniSom {
        if ($from === $to) return $amount;
        // 获取实时汇率
        $rate = $this->getCurrentRate($from, $to);
        $converted = bcmul($amount->__toString(), (string)$rate, 2);
        return new KyrgyzstaniSom($converted);
    }
    private function getCurrentRate(string $from, string $to): float {
        $stmt = $this->pdo->prepare(
            "SELECT rate FROM exchange_rates 
             WHERE from_currency = ? AND to_currency = ?
             AND valid_from <= NOW() 
             AND (valid_until IS NULL OR valid_until > NOW())
             ORDER BY valid_from DESC LIMIT 1"
        );
        $stmt->execute([$from, $to]);
        return (float)$stmt->fetchColumn();
    }
}

前端显示最佳实践

1 JavaScript辅助格式化

// 格式化KGS金额
const formatKGS = (amount) => {
    return new Intl.NumberFormat('ky-KG', {
        style: 'currency',
        currency: 'KGS',
        minimumFractionDigits: 2,
        maximumFractionDigits: 2
    }).format(amount);
};
// 格式化eKGS(添加电子标识)
const formatEKGS = (amount) => {
    return `e${formatKGS(amount)}`;
};
// 使用示例
console.log(formatKGS(1250.50));   // 1 250,50 сом
console.log(formatEKGS(500));      // e500,00 сом

2 国际化(ICU规则)

  • 吉尔吉斯斯坦语(ky_KG):
    • 分组分隔符:空格 (如 1 234,56 сом
    • 小数分隔符:逗号
    • 货币符号后置:сом(西里尔)或 KGS(拉丁)

测试示例(PHPUnit)

class KyrgyzstaniSomTest extends \PHPUnit\Framework\TestCase {
    public function testAddition(): void {
        $a = new KyrgyzstaniSom('100.50');
        $b = new KyrgyzstaniSom('200.75');
        $result = $a->add($b);
        $this->assertEquals('301.25', (string)$result);
    }
    public function testTyiynConversion(): void {
        $kgs = new KyrgyzstaniSom('150.25');
        $this->assertEquals(15025, $kgs->toTyiyn());
    }
    public function testEKGSFormat(): void {
        $eKGS = new EKGS('500.00');
        $this->assertStringContainsString('e', $eKGS->format());
        $this->assertStringContainsString('500', $eKGS->format());
    }
}

安全与性能建议

  1. 始终使用bcmath:避免浮点精度问题
  2. 数据库使用DECIMALDECIMAL(16,2) 支持最大10亿索姆
  3. 交易加锁:使用SELECT ... FOR UPDATE防止并发问题
  4. 审计日志:所有货币操作记录完整的before/after状态
  5. 汇率更新:通过cron定期更新,或使用实时API(如NBKR央行数据)

常见问题场景处理

  • 充值eKGS:生成唯一交易ID,调用支付网关回调
  • eKGS兑KGS:设置固定汇率或浮动汇率,限制每日兑换限额
  • 展示历史余额:快照表保存每日余额,避免长事务

如果你有更具体的业务场景(如eKGS是否支持提现、是否与特定支付系统对接),欢迎补充,我可以进一步优化方案。

抱歉,评论功能暂时关闭!