*/ public static function parseFromConfigValue($raw): array { if (!is_string($raw) || trim($raw) === '') { return []; } $decoded = json_decode($raw, true); if (!is_array($decoded)) { return []; } if (isset($decoded['tiers']) && is_array($decoded['tiers'])) { $list = $decoded['tiers']; } else { $list = $decoded; } return self::normalizeList($list); } /** * @param list $items */ public static function normalizeList(array $items): array { $out = []; foreach ($items as $row) { if (!is_array($row)) { continue; } $id = isset($row['id']) && is_string($row['id']) ? trim($row['id']) : ''; if ($id === '') { $id = self::generateId(); } $title = self::stringField($row, 'title'); if ($title === '') { // 兼容历史:字段名 name 或更老的 account_name $title = self::stringField($row, 'name'); if ($title === '') { $title = self::stringField($row, 'account_name'); } } $titleEn = self::stringField($row, 'title_en'); $amount = self::normalizeAmount($row['amount'] ?? ''); $bonus = self::normalizeAmount($row['bonus_amount'] ?? '0'); $desc = self::stringField($row, 'desc'); if ($desc === '') { $desc = self::stringField($row, 'remark'); } $descEn = self::stringField($row, 'desc_en'); $sort = isset($row['sort']) && is_numeric($row['sort']) ? intval($row['sort']) : 0; $status = isset($row['status']) && is_numeric($row['status']) ? intval($row['status']) : 1; $status = $status === 1 ? 1 : 0; $out[] = [ 'id' => $id, 'title' => $title, 'title_en' => $titleEn, 'amount' => $amount, 'bonus_amount' => $bonus, 'desc' => $desc, 'desc_en' => $descEn, 'sort' => $sort, 'status' => $status, ]; } usort($out, static function (array $a, array $b): int { if ($a['sort'] !== $b['sort']) { return $a['sort'] <=> $b['sort']; } $ida = is_string($a['id']) ? $a['id'] : ''; $idb = is_string($b['id']) ? $b['id'] : ''; return strcmp($ida, $idb); }); return $out; } /** * 校验 POST 数据并输出用于入库的清洁数据 * * @param list> $items * * @throws InvalidArgumentException */ public static function prepareItemsForSave(array $items): array { $seenId = []; $out = []; foreach ($items as $idx => $row) { $no = $idx + 1; if (!is_array($row)) { throw new InvalidArgumentException('第 ' . $no . ' 行格式错误'); } $id = isset($row['id']) && is_string($row['id']) ? trim($row['id']) : ''; if ($id === '') { $id = self::generateId(); } if (!preg_match('/^[a-zA-Z0-9_\-]{1,32}$/', $id)) { throw new InvalidArgumentException('第 ' . $no . ' 行 ID 非法'); } if (isset($seenId[$id])) { throw new InvalidArgumentException('档位 ID 重复:' . $id); } $seenId[$id] = true; $title = self::stringField($row, 'title'); if ($title === '') { // 兼容上游(例如自动迁移脚本)传递历史 name 字段 $title = self::stringField($row, 'name'); } if ($title === '') { throw new InvalidArgumentException('第 ' . $no . ' 行中文充值名称不能为空'); } if (mb_strlen($title) > 64) { throw new InvalidArgumentException('第 ' . $no . ' 行中文充值名称过长'); } $titleEn = self::stringField($row, 'title_en'); if (mb_strlen($titleEn) > 64) { throw new InvalidArgumentException('第 ' . $no . ' 行英文充值名称过长'); } $amount = self::normalizeAmount($row['amount'] ?? ''); if (bccomp($amount, '0', 4) <= 0) { throw new InvalidArgumentException('第 ' . $no . ' 行充值金额必须大于 0'); } $bonus = self::normalizeAmount($row['bonus_amount'] ?? '0'); if (bccomp($bonus, '0', 4) < 0) { throw new InvalidArgumentException('第 ' . $no . ' 行赠送金额不能为负数'); } $desc = self::stringField($row, 'desc'); if (mb_strlen($desc) > 255) { throw new InvalidArgumentException('第 ' . $no . ' 行中文描述过长'); } $descEn = self::stringField($row, 'desc_en'); if (mb_strlen($descEn) > 255) { throw new InvalidArgumentException('第 ' . $no . ' 行英文描述过长'); } $sort = isset($row['sort']) && is_numeric($row['sort']) ? intval($row['sort']) : 0; $statusRaw = isset($row['status']) && is_numeric($row['status']) ? intval($row['status']) : 1; $status = $statusRaw === 1 ? 1 : 0; $out[] = [ 'id' => $id, 'title' => $title, 'title_en' => $titleEn, 'amount' => $amount, 'bonus_amount' => $bonus, 'desc' => $desc, 'desc_en' => $descEn, 'sort' => $sort, 'status' => $status, ]; } usort($out, static function (array $a, array $b): int { if ($a['sort'] !== $b['sort']) { return $a['sort'] <=> $b['sort']; } $ida = is_string($a['id']) ? $a['id'] : ''; $idb = is_string($b['id']) ? $b['id'] : ''; return strcmp($ida, $idb); }); return $out; } /** * @param list> $items */ public static function encodeForDb(array $items): string { $encoded = json_encode($items, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); if ($encoded === false) { throw new InvalidArgumentException('JSON 编码失败'); } return $encoded; } /** * 过滤出启用档位并按 sort 升序,供移动端选择 */ public static function publicList(array $items): array { $enabled = array_values(array_filter($items, static function (array $row): bool { if (!isset($row['status'])) { return false; } $val = is_numeric($row['status']) ? intval($row['status']) : 0; return $val === 1; })); usort($enabled, static function (array $a, array $b): int { $sa = isset($a['sort']) && is_numeric($a['sort']) ? intval($a['sort']) : 0; $sb = isset($b['sort']) && is_numeric($b['sort']) ? intval($b['sort']) : 0; if ($sa !== $sb) { return $sa <=> $sb; } $ida = isset($a['id']) && is_string($a['id']) ? $a['id'] : ''; $idb = isset($b['id']) && is_string($b['id']) ? $b['id'] : ''; return strcmp($ida, $idb); }); return $enabled; } /** * 按 ID 从档位列表中取出指定档位;未找到返回 null */ public static function findById(array $items, string $id): ?array { foreach ($items as $row) { if (!is_array($row)) { continue; } $rid = $row['id'] ?? ''; if (is_string($rid) && $rid === $id) { return $row; } } return null; } /** * 根据语言选择档位对外展示的 title/desc。 * * @param array $item * @return array{title: string, desc: string} */ public static function localize(array $item, string $lang): array { $title = self::stringField($item, 'title'); $titleEn = self::stringField($item, 'title_en'); $desc = self::stringField($item, 'desc'); $descEn = self::stringField($item, 'desc_en'); $isEn = self::isEnglishLang($lang); $pickedTitle = $isEn ? ($titleEn !== '' ? $titleEn : $title) : ($title !== '' ? $title : $titleEn); $pickedDesc = $isEn ? ($descEn !== '' ? $descEn : $desc) : ($desc !== '' ? $desc : $descEn); return [ 'title' => $pickedTitle, 'desc' => $pickedDesc, ]; } /** * 生成 10 位稳定 ID(t_ + 8 位随机 base32) */ public static function generateId(): string { $chars = 'abcdefghijkmnpqrstuvwxyz23456789'; $len = strlen($chars); $id = 't_'; for ($i = 0; $i < 8; $i++) { $id .= $chars[random_int(0, $len - 1)]; } return $id; } /** * 将金额归一化为 4 位小数字符串;非法输入返回 '0.0000' */ public static function normalizeAmount($raw): string { if ($raw === null || $raw === '') { return '0.0000'; } if (is_string($raw)) { $s = trim($raw); } elseif (is_int($raw) || is_float($raw)) { $s = strval($raw); } else { return '0.0000'; } $s = str_replace(',', '.', $s); if (!is_numeric($s)) { return '0.0000'; } return bcadd($s, '0', 4); } /** * 从数组取字符串字段并 trim,非字符串返回空串 * * @param array $row */ private static function stringField(array $row, string $key): string { if (!isset($row[$key])) { return ''; } $v = $row[$key]; return is_string($v) ? trim($v) : ''; } private static function isEnglishLang(string $lang): bool { $normalized = strtolower(str_replace('_', '-', trim($lang))); if ($normalized === '') { return false; } return $normalized === 'en' || str_starts_with($normalized, 'en-'); } }