Skip to content

商业库存与按量履约事务 ​

java-db 的线程绑定事务适合将原操作、库存分配、权益扣量和累计交付金额一起提交。数据库负责并发互斥,服务端负责当前资格与计量证据,客户端展示已经确认的事实。

请求、预占与交付分开 ​

用户确认使用时,将选中批次的可用量转入预占量,不增加已用量。排队只决定等待顺序;只有独占位置真实可用,才形成开始含、结束不含的服务区间。多个窗口、重复列表请求和重复通知共享同一服务请求,不能再扣一次。

一种实用实现是采用短期服务租约:调度器持续确认资格和实际位置,随后追加已经确认的区间。调度器停顿超过租约时,未经证明的空档不补记服务时间,保留未交付量并展示暂停原因。租约不能取代实际服务可达性证据;真实环境应将流量入口健康与服务资格共同作为续租条件。

PostgreSQL 事务锁与累计金额 ​

以下独立示例展示服务进度行的锁定、重复截止点去重、区间追加与累计到分。confirmedUntil 必须来自服务端已经核实的连续可服务区间,不能直接采用浏览器传入的时间。

sql
create table demo_service_progress (
  id bigint primary key,
  promised_seconds bigint not null check(promised_seconds > 0),
  delivered_seconds bigint not null default 0,
  paid_fen bigint not null check(paid_fen >= 0),
  delivered_fen bigint not null default 0,
  confirmed_until timestamptz not null,
  check(delivered_seconds between 0 and promised_seconds)
);
create table demo_service_interval (
  id bigint primary key,
  service_id bigint not null references demo_service_progress(id),
  starts_at timestamptz not null,
  ends_at timestamptz not null,
  quantity bigint not null check(quantity > 0),
  unique(service_id, starts_at),
  check(ends_at > starts_at)
);
java
import java.math.BigInteger;
import java.sql.Connection;
import java.sql.Timestamp;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import nexus.io.db.activerecord.Db;
import nexus.io.db.activerecord.Row;
import nexus.io.tio.utils.snowflake.SnowflakeIdUtils;

public final class ConfirmedServiceLedger {
  public void append(long serviceId, Instant confirmedUntil) {
    Instant cutoff = confirmedUntil.truncatedTo(ChronoUnit.SECONDS);
    boolean committed = Db.tx(Connection.TRANSACTION_READ_COMMITTED, () -> {
      Row progress = Db.findFirst(
          "select * from demo_service_progress where id=? for update", serviceId);
      if (progress == null) {
        throw new IllegalArgumentException("Service does not exist");
      }
      Instant start = progress.getTimestamp("confirmed_until").toInstant();
      long promised = progress.getLong("promised_seconds");
      long delivered = progress.getLong("delivered_seconds");
      long seconds = Math.min(Duration.between(start, cutoff).getSeconds(), promised - delivered);
      if (seconds <= 0) {
        return true;
      }
      Instant end = start.plusSeconds(seconds);
      long cumulative = delivered + seconds;
      long amount = BigInteger.valueOf(progress.getLong("paid_fen"))
          .multiply(BigInteger.valueOf(cumulative))
          .divide(BigInteger.valueOf(promised)).longValueExact();
      Db.update("""
          insert into demo_service_interval(id, service_id, starts_at, ends_at, quantity)
          values(?,?,?,?,?)
          """, SnowflakeIdUtils.id(), serviceId, Timestamp.from(start), Timestamp.from(end), seconds);
      Db.update("""
          update demo_service_progress
          set delivered_seconds=?, delivered_fen=?, confirmed_until=? where id=?
          """, cumulative, amount, Timestamp.from(end), serviceId);
      return true;
    });
    if (!committed) {
      throw new IllegalStateException("Service transaction rolled back");
    }
  }
}

本例只演示已经确认的连续区间。发生中断后,应另建新区间起点并保留旧区间,不能直接把中断前的截止点推进到恢复后的时间。真实业务还需要在同一事务内移动批次数量,并关联原现金行。全部交付时累计金额等于原实付;例如300分对应1800秒,累计40秒交付6分,剩余责任294分。不要逐秒舍入后相加,也不要将40秒截成0分钟。

多个库存槽参与一次决策时,可以先用固定业务锁序列获取 PostgreSQL 事务级 advisory lock,再锁服务行和批次行。所有领域写入方必须遵守相同锁顺序。排期表还应约束同槽、同资源的半开区间不可重叠,预约和实际交付分别保存。

JSON 数量保留精度 ​

请求模型可用 Number 保留 JSON 原数量,在业务边界显式转换,避免小数转换成整数后丢失原始含义。

java
import java.math.BigDecimal;

public record ServiceQuantityRequest(Number quantity) {
  public long exactQuantity() {
    if (quantity == null) {
      throw new IllegalArgumentException("Quantity is required");
    }
    long exact = new BigDecimal(quantity.toString()).longValueExact();
    if (exact <= 0) {
      throw new IllegalArgumentException("Quantity must be positive");
    }
    return exact;
  }
}

曝光与未交付责任 ​

曝光使用独立观测事实:服务端匹配结果签发短期凭据,经核适配器确认前台可见比例、连续时长、可靠主体与测试排除后,才进入交付事务。同主体、同资源、同业务日的合格或待核事实通过唯一索引去重;待核不等失败,不能同时释放原量并发补偿。

服务到期后,原卡停止新使用,已经受理但未交付的责任单独保留。现金退款继续引用原本金范围;用户明确选择补交时,新批次关联原责任,原卡不复活。非现金补交保留来源和数量,不生成现金收入。

建议验证最后槽位争用、重复截止点、多窗口、40秒累计到分、短租约中断、待核观测跨期,以及退款与补交互斥。隔离合成测试证明工程行为,真实渠道可达与可信观测需要对应适配证据。