ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

PHP实现生辰八字排盘系统:从农历转换到四柱计算详解

PHP实现生辰八字排盘系统:从农历转换到四柱计算详解 简介这是一套基于PHP开发语言实现的生辰八字排盘系统完整源码适合PHP初学者、Web开发者以及对中国传统命理文化感兴趣的爱好者学习与二次开发。源码包共64个文件涵盖CSS样式、JavaScript脚本、动态链接库DLL及图片素材等主要类型压缩包大小仅207KB目录结构清晰包含主页面、接口调用与配置文件便于快速部署和阅读。已有654人前来学习下载。项目围绕公历转农历、天干地支推算、排盘界面呈现等核心模块展开利用DateTime处理日期通过Ajax异步交互提升体验并配合数据库实现查询记录管理。通过研读这份代码开发者既能掌握PHP与前端配合的完整项目流程也可深入理解八字排盘算法在Web中的落地思路是一份可运行、可扩展的实战参考资料。1. 为什么一个生辰八字排盘系统值得拆开看把一套生辰八字排盘源码从压缩包里解压出来第一眼看到的往往不是算法而是一堆 CSS、图片和 JS 文件。很多人以为这类项目的难点在界面样式真正动手改过才发现最折腾的是公历转农历、年柱以立春为界还是以春节为界、23 点之后算哪一天。这套 PHP 实例开发源码正好把这些问题都摆在了桌面上它用 PHP 接收用户的出生日期通过历法换算和干支推算生成八字四柱再配合 Ajax 把结果渲染到页面。对 PHP 开发者来说这是一个练习日期处理、数组操作、前后端交互和参数校验都足够典型的完整案例对刚入行的人来说能从这里看到一套 Web 小应用从表单到结果展示的完整链路。下文按计算模型、查询流程、安全边界和工程化扩展的顺序拆开讲。2. 天干地支与四柱排盘的计算模型2.1 四柱八字节盘的基本构成生辰八字由年柱、月柱、日柱、时柱组成每柱一个天干加一个地支四柱合计八个字。天干共 10 个甲、乙、丙、丁、戊、己、庚、辛、壬、癸地支共 12 个子、丑、寅、卯、辰、巳、午、未、申、酉、戌、亥。年柱和月柱的划分不是按农历正月初一而是按二十四节气中的立春和交节时刻这是初改排盘代码时最容易踩的坑。日柱的计算相对独立可以直接从某个已知基准日推算时柱则依赖日柱天干和出生时辰且存在 23 点换日的问题。这套 PHP 源码里排盘的核心逻辑可以拆成三个阶段公历转农历、按节气定年柱月柱、按时辰定日柱时柱。三个阶段的输入都是同一个公历日期时间输出却是两套相互影响的干支因此代码里需要同时维护农历信息、节气偏移和干支索引表。2.2 公历转农历的查表实现PHP 没有内置的公历转农历函数常见做法是查表法预先保存从 1900 年到 2100 年的农历数据每年用一个 32 位整数表示闰月、大小月和农历年首对应的公历偏移量。下面是一个简化版的农历数据解析函数。?php // 农历数据表每年一个整型低位到高位分别表示月份大小第16位起表示闰月 $lunarInfo [ 0x04bd8, 0x04ae0, 0x0a570, 0x054d5, // 1900-1903示例数据 // 实际项目里会填充到2100年 ]; function getLunarYearData(int $year): array { global $lunarInfo; $data $lunarInfo[$year - 1900]; $leapMonth $data 0xf; // 第0-3位闰月月份0表示无闰月 $leapDays ($data 16) 0x1; // 闰月是否是大月 $monthDays []; for ($i 1; $i 12; $i) { $monthDays[$i] (($data (16 $i)) 0x1) ? 30 : 29; } return [$leapMonth, $leapDays, $monthDays]; }这个函数的逻辑是把每年的农历数据当成一个二进制位图月大 30 天用位值 1 表示月小 29 天用位值 0 表示闰月信息单独用低 4 位存放。这样做的好处是内存占用极小200 年的农历数据只需 800 字节非常适合放进 PHP 数组或缓存。拿到每年的农历月大小和闰月信息后再用一个循环累加天数就能算出给定公历日期对应的农历年月日。实际项目里为了处理节气还会在同样的数据表里追加每个节气的公历偏移量。注意这里的基准必须统一比如以 1900 年 1 月 31 日为农历 1900 年正月初一后面的天数累加才不会漂移。2.3 PHP 推算年柱、月柱、日柱、时柱年柱以立春为界所以不能直接取公历年份。常见做法是先判断公历日期是否在立春之前如果是年柱按上一年算。月柱则以冬至为子月起点或用节气月份索引。下面是一段可运行的 PHP 实现。?php function buildYearPillar(int $year, int $month, int $day): string { $gan [甲,乙,丙,丁,戊,己,庚,辛,壬,癸]; $zhi [子,丑,寅,卯,辰,巳,午,未,申,酉,戌,亥]; // 立春一般在2月4日前后这里以2月4日为简化分界 $liChun strtotime(sprintf(%d-02-04 00:00:00, $year)); $current strtotime(sprintf(%d-%02d-%02d 00:00:00, $year, $month, $day)); if ($current $liChun) { $year--; } $yearGanIndex ($year - 4) % 10; $yearZhiIndex ($year - 4) % 12; return $gan[$yearGanIndex] . $zhi[$yearZhiIndex]; } function buildDayPillar(int $year, int $month, int $day): string { $gan [甲,乙,丙,丁,戊,己,庚,辛,壬,癸]; $zhi [子,丑,寅,卯,辰,巳,午,未,申,酉,戌,亥]; $baseDate strtotime(1900-01-01); $targetDate strtotime(sprintf(%d-%02d-%02d, $year, $month, $day)); $offset intdiv($targetDate - $baseDate, 86400) 10; // 100年修正偏移 $dayGanIndex ($offset) % 10; $dayZhiIndex ($offset) % 12; return $gan[$dayGanIndex] . $zhi[$dayZhiIndex]; }年柱计算方法里($year - 4) % 10是因为公元 4 年是甲子年用取模把年份映射到天干和地支索引。日柱计算中用 1900-01-01 作为已知基准日通过秒数差算出偏移天数然后对 10 和 12 取模。这里的10是基准日修正值具体值取决于你选择的基准点千万不要照抄否则日柱会整体错位。时柱需要先知道日柱天干的索引然后按五鼠遁口诀推算时柱天干。比如甲己日从甲子时开始乙庚日从丙子时开始。这个映射逻辑可以放进一个查找表避免写一堆 if。3. 用 PHP 搭建排盘查询与展示流程3.1 表单提交与日期参数校验前端提供一个出生日期表单包含年、月、日、时、分以及性别字段。PHP 后端拿到参数后第一步不是计算而是校验日期合法性。PHP 自带的checkdate函数可以判断公历日期是否存在但还要手动检查年份范围因为农历数据表只覆盖 1900 到 2100 年。?php $year (int)($_POST[year] ?? 0); $month (int)($_POST[month] ?? 0); $day (int)($_POST[day] ?? 0); $hour (int)($_POST[hour] ?? 0); $minute (int)($_POST[minute] ?? 0); if ($year 1900 || $year 2100) { exit(json_encode([error 年份超出排盘范围])); } if (!checkdate($month, $day, $year)) { exit(json_encode([error 公历日期不合法])); } if ($hour 0 || $hour 23 || $minute 0 || $minute 59) { exit(json_encode([error 时间不合法])); }这里把小时和分钟分开校验是因为后续计算时柱需要精确到小时分钟主要影响真太阳时的修正而不是直接参与时辰划分。checkdate能自动处理闰年的 2 月 29 日比自己写正则判断日期格式可靠得多。注意返回 JSON 时一定要先设置Content-Type: application/json头否则前端fetch拿到的字符串可能被误判为纯文本。3.2 Ajax 异步获取排盘结果前端页面用 jQuery 或原生 fetch 发起异步请求提交表单后不刷新页面直接展示结果。这里有一个容易忽略的细节PHP 脚本里的session_start()如果开启了会影响响应头在 Ajax 场景下建议把排盘接口单独抽成一个无会话脚本减少不必要的 Cookie 传输。async function getBazi() { const params new URLSearchParams({ year: document.getElementById(year).value, month: document.getElementById(month).value, day: document.getElementById(day).value, hour: document.getElementById(hour).value, minute: document.getElementById(minute).value, gender: document.querySelector(input[namegender]:checked).value }); const resp await fetch(/paipan_api.php, { method: POST, headers: { Content-Type: application/x-www-form-urlencoded }, body: params.toString() }); const data await resp.json(); renderBazi(data); }后端paipan_api.php接收这些字段调用排盘类返回 JSON 结构。推荐返回字段包含yearPillar、monthPillar、dayPillar、hourPillar、lunarDate、fiveElements等而不是直接把拼好的 HTML 返回。这样前端可以灵活控制展示后续如果要加分享图、小程序适配数据层不用动。如果前端和接口不在同一个域名还需要处理跨域。PHP 端最简单的方式是判断Origin头后输出允许跨域的响应头或者改用 JSONP 方式。初学时容易直接header(Access-Control-Allow-Origin: *)这在生产环境会有安全隐患建议只允许固定的几个来源域名。3.3 前端渲染八字节盘与五行统计拿到四柱数据后前端需要把八个字对应的五行统计出来。这段逻辑放在前端还是后端取决于项目定位如果只做展示前端写更简单如果后续要基于五行做命理分析后端统计算法更可控。下面是一个用 PHP 返回五行统计的片段。?php $ganZhi5 [ 甲 木, 乙 木, 丙 火, 丁 火, 戊 土, 己 土, 庚 金, 辛 金, 壬 水, 癸 水, 子 水, 丑 土, 寅 木, 卯 木, 辰 土, 巳 火, 午 火, 未 土, 申 金, 酉 金, 戌 土, 亥 水, ]; $pillars [ $data[yearPillar], $data[monthPillar], $data[dayPillar], $data[hourPillar] ]; $counts [金 0, 木 0, 水 0, 火 0, 土 0]; foreach ($pillars as $pillar) { for ($i 0; $i 2; $i) { $char mb_substr($pillar, $i, 1, UTF-8); if (isset($ganZhi5[$char])) { $counts[$ganZhi5[$char]]; } } } $data[fiveElements] $counts; echo json_encode($data, JSON_UNESCAPED_UNICODE);由于星座和地支字符都是 UTF-8使用mb_substr而不是substr才能正确取到单个汉字。五行统计时包括地支的藏干才是完整算法但初版项目里只按显式干支表统计也能让整体结构跑通。前端拿到fiveElements后可以把统计值渲染成横条或饼图没有额外复杂度。这个系统的效果如何很大程度上取决于你在这里多大程度保留地支藏干逻辑。4. 排盘数据的安全性、缓存与常见易错点4.1 防止 SQL 注入与 XSS排盘接口本身可能不需要数据库但如果要保存用户查询历史就必须处理 SQL 注入问题。PHP 中推荐使用 PDO 预处理语句而不是拼接字符串。?php $pdo new PDO(mysql:host127.0.0.1;dbnamebazi;charsetutf8mb4, user, pass); $stmt $pdo-prepare(INSERT INTO query_log (year, month, day, hour, result) VALUES (?, ?, ?, ?, ?)); $stmt-execute([$year, $month, $day, $hour, json_encode($result, JSON_UNESCAPED_UNICODE)]);这里?占位符让 PDO 自动处理转义可以避免经典的单引号绕过。另一个容易忽视的问题是 XSS排盘结果中包含用户输入的年月日如果直接把用户原样输入回显到网页可能被嵌入恶意脚本。任何要输出到 HTML 的字符串都要经过htmlspecialchars($str, ENT_QUOTES, UTF-8)处理尤其是你从数据库读出历史记录再回显的时候。不少 PHP 项目正是因为只在写入时做了过滤读取时大意导致存储型 XSS。4.2 农历转换数据的缓存设计农历数据表本身很小但节气信息数组和干支表如果每次请求都重新计算会消耗不必要的 CPU。常见做法是用 APCu 或文件缓存把解析后的农历年数据缓存起来。?php function getLunarDataCached(int $year): array { $cacheKey lunar_data_ . $year; if (function_exists(apcu_fetch)) { $cached apcu_fetch($cacheKey); if ($cached ! false) { return $cached; } } $data buildLunarYearData($year); if (function_exists(apcu_store)) { apcu_store($cacheKey, $data, 86400); } return $data; }如果你用的是文件缓存要注意 PHP-FPM 进程的用户权限确保缓存目录可写。APCu 缓存的过期时间可以设置成 86400 秒因为农历数据表一年内不会变。这里有一个坑不要把整个公历转农历结果缓存 10 年因为节气和闰月的数据一旦更新旧缓存会给出错误结果建议每次发布新数据表时手动清一次缓存。4.3 时柱边界23 点换日问题排盘系统最隐蔽的 bug 是 23 点以后出生的日期处理。传统命理中有早子时和晚子时的说法晚上 23:00 到次日 00:59 属于晚子时时柱虽然还按当天的干支但有些流派主张日柱要按第二天算。如果代码不做特殊处理直接把 23 点算到当天日柱和时柱会整体错位。?php if ($hour 23) { // 23点后按次日计算日柱 $dayOffset 1; $hourIndex 0; // 子时对应地支索引0 } else { $dayOffset 0; $hourIndex intdiv($hour 1, 2) % 12; } $targetDate mktime(0, 0, 0, $month, $day $dayOffset, $year); $dayPillar buildDayPillarFromTimestamp($targetDate);这段代码把 23 点单独拎出来统一按次日日柱处理同时时柱地支固定为子时。intdiv($hour 1, 2) % 12可以把 0 点到 23 点映射到 12 个时辰其中 0-1 点子时2-3 点丑时以此类推。这里涉及流派差异不建议在代码注释里写死对错但一定要留一个配置项比如lateZiShiAsNextDay true方便以后根据需要切换。4.4 节气边界立春与月令除了时柱边界月柱和年柱还依赖节气。年柱以立春为界月柱以十二节令为界比如正月从立春开始二月从惊蛰开始。很多实现把节气日期硬编码成固定日期比如立春一定是 2 月 4 日这在大多数年份没问题但遇到节气时间落在 2 月 3 日或 2 月 5 日的年份就会出错。严谨的做法是从天文台发布的节气表读取数据或者在一开始就内置一份精确到小时的节气数组然后按时间戳比较。?php $jieqi [ 2024-02-04 16:26 立春, 2024-03-05 10:22 惊蛰, // ... ]; function getCurrentJieqi(int $timestamp): ?string { global $jieqi; foreach ($jieqi as $time $name) { if (strtotime($time) $timestamp) { return $name; } } return null; }节气数组是排盘系统里最容易变动的部分建议把它放在单独的配置文件中方便每年更新。任何时候都要记住你处理的日期时间不是用户输入的字符串而是代表真实时间线的数字时间戳所有比较都应该基于时间戳而不是格式化字符串。5. 扩展把排盘系统做成可配置的 PHP 服务5.1 封装排盘核心类把散落的函数整理成一个可复用的类是工程化的第一步。下面的BaziEngine类包含年份范围配置和流派配置避免在控制器里堆逻辑。?php class BaziEngine { private array $config; public function __construct(array $config []) { $this-config array_merge([ minYear 1900, maxYear 2100, lateZiShiAsNextDay true, jieqiFile __DIR__ . /jieqi.php, ], $config); } public function calculate(int $year, int $month, int $day, int $hour, int $minute 0): array { if ($year $this-config[minYear] || $year $this-config[maxYear]) { throw new InvalidArgumentException(年份超出排盘范围); } $lunar $this-convertSolarToLunar($year, $month, $day); $yearPillar $this-computeYearPillar($year, $month, $day); $dayPillar $this-computeDayPillar($year, $month, $day, $hour); $hourPillar $this-computeHourPillar($dayPillar, $hour); $monthPillar $this-computeMonthPillar($year, $month, $day); return [ lunarDate $lunar, yearPillar $yearPillar, monthPillar $monthPillar, dayPillar $dayPillar, hourPillar $hourPillar, ]; } private function convertSolarToLunar(int $year, int $month, int $day): string { // 调用上一章的查表函数 } private function computeYearPillar(int $year, int $month, int $day): string { // 立春判断后调用干支计算 } private function computeDayPillar(int $year, int $month, int $day, int $hour): string { // 处理23点换日 } }类内部把年柱、月柱、日柱、时柱拆成独立私有方法每个方法只做一件事。这样写单元测试时会非常舒服因为可以直接用反射或公开方法逐一验证每个环节。config数组给了使用者灵活的入口比如切换早子时晚子时规则只需传入不同配置。5.2 命令行验证与单元测试排盘算法的正确性可以用 PHP 命令行快速验证不需要启动 Web 服务器。写一个简单的测试脚本把已知的排盘结果作为断言。php -r require BaziEngine.php; \$engine new BaziEngine(); \$result \$engine-calculate(1990, 5, 15, 10, 30); var_export(\$result); 如果你知道某个日期的准确四柱比如 1990 年 5 月 15 日 10:30 的排盘结果可以把它写成断言。这样每次修改农历数据表或时柱逻辑后只需要跑一条命令就能知道有没有引入回归 bug。没有框架也能做因为 PHP 内置了assert函数进阶可以引入 PHPUnit但这不是必需的。5.3 给前端增加简易分享图排盘结果生成后用户往往希望生成一张长图分享。PHP 的 GD 库可以做这件事创建画布写入四柱和五行统计输出 PNG。下面是一个最小示例。?php $im imagecreatetruecolor(600, 400); $bgColor imagecolorallocate($im, 251, 247, 240); $textColor imagecolorallocate($im, 60, 60, 60); imagefill($im, 0, 0, $bgColor); $font __DIR__ . /simhei.ttf; // 需要中文字体文件 $text 年柱: {$result[yearPillar]} 月柱: {$result[monthPillar]}; imagettftext($im, 20, 0, 60, 80, $textColor, $font, $text); header(Content-Type: image/png); imagepng($im); imagedestroy($im);GD 生成图片时最常遇到的问题是缺少中文字体英语字体画不出汉字。生产环境建议把字体文件放在项目目录内并在imagettftext里使用绝对路径。另一个性能考虑图片生成是 CPU 密集型操作建议加上Cache-Control响应头让浏览器缓存生成结果避免同一用户多次刷新反复执行 GD 函数。到这里一套 PHP 生辰八字排盘系统就从一个纯 Web 查询工具变成了带缓存、可测试、可分享图片的完整服务。本文还有配套的精品资源点击获取
返回列表