简介:本资源是一套基于Java实现的六爻起卦排盘小程序完整源码,面向对周易文化与编程实践交叉领域感兴趣的开发者、传统文化爱好者及高校计算机相关专业学生,旨在以现代编程方式还原传统六爻占卜的核心逻辑,解决手工起卦繁琐、解读门槛高、学习成本大的问题。压缩包共17个文件,含3个核心Java源文件(实现起卦、爻位生成与卦象解析)、5个编译后class文件、7个XML配置与构建文件(含pom.xml及IDEA工程配置),以及2个.gitignore文件,整体仅28KB,轻量易读,结构清晰体现面向对象设计思想。已有263人学习下载,代码严格遵循六爻规则模拟硬币起卦、纳甲装干支、世应定位、动爻判断等关键步骤,并封装为可扩展模块,附带完整Maven项目结构与标准目录组织,便于快速理解算法流程、调试卦例或二次开发个性化解读功能。 做这个小程序之前,我翻了不少开源项目,结果发现网上流传的六爻起卦排盘源码大多有几个通病:要么只处理了“起卦”这一步,排盘全靠前端写死;要么后端用脚本语言写得飞快,但碰到干支历法、纳甲这些规则就全靠硬编码;更常见的是代码根本没有工程结构,拿到手根本跑不起来。所以这次我干脆用 Java 完整实现了一套六爻起卦排盘的后端逻辑,再配合微信小程序做交互界面,把起卦、装卦、纳甲、排六亲六神、定世应这一整条链路打通,源码整理出来分享给大家。
先说清楚这个项目解决什么问题:它不是一个只用于演示的“静态卦表”,而是一个正经可运行的规则引擎。用户在小程序里选择起卦方式,输入时间或数字,或者直接手动摇卦,后端拿到请求后按传统六爻规则完成排盘,返回结构化的卦象数据,前端负责展示和分享。项目的定位是传统文化数字化工具,适合对周易民俗文化感兴趣的 Java 开发者、小程序开发者,以及想研究古典术数规则如何落地为代码的人参考学习。
1. 项目概述与整体设计
1.1 起卦排盘到底在做什么
很多人第一次接触六爻代码项目时,容易把“起卦”和“排盘”混为一谈。其实从计算机角度拆开看,它们是完全不同层次的工作。
起卦负责解决“输入怎么变成六个爻”的问题,常见的输入来源有三种:时间、数字、人为随机。时间起卦按农历年、月、日、时推算数字,再通过取模运算得到上卦、下卦和动爻;数字起卦则直接用用户输入的数字做同样的映射;手动摇卦是让用户模拟抛六次铜钱,每次产生一个阴爻或阳爻。起卦的输出是一组爻序列,比如从初爻到上爻分别是“少阳、少阴、老阳、少阴、少阴、少阳”。
排盘则是把这一组爻序列展开成一张完整的盘面,它要做的事包括:根据本卦确定所属八宫,给六个爻配上地支和天干,标出世爻和应爻,根据宫位五行和卦宫五行的生克关系排出六亲,再根据起卦日的天干排出六神。这些规则环环相扣,一个地支记错就会引发连锁错误,所以代码实现里最关键的是把规则表沉淀成可校验的数据结构,而不是散落在一堆 if else 里。
1.2 技术选型:Java 后端加小程序前端
既然要处理复杂的卦象规则,后端语言我毫不犹豫选了 Java。原因很朴素:第一,Java 的工程化生态成熟,Spring Boot 一键启动,接口调试、日志、异常处理都有现成方案;第二,它处理日期和数学运算非常稳定,尤其适合干支历法这类需要大量日期偏移计算的场景;第三,面向对象建模可以把“卦”“爻”“盘面”这些概念映射得很自然。
前端为什么选微信小程序?核心是因为分发方便,用户不用安装 App,分享一个卡片就能打开,非常契合这类轻量工具型应用。小程序端使用原生框架开发,组件丰富,radio 单选框、button、canvas 绘制这类需求都有成熟方案,不需要引入额外 UI 库。
完整源码分为两个目录:backend 是 Java 后端工程,frontend 是小程序工程。我用 Spring Boot 提供 REST 接口,用 Hutool 的日期工具辅助农历计算,小程序端则全部使用原生组件,没有额外依赖。整体架构前后端分离,接口只返回 JSON 数据,以后如果想把算法迁移到其他前端,后端代码完全不用改动。
1.3 项目目录结构与源码模块划分
拿到源码后建议先看目录结构,整个项目的模块划分是这样的:
liuyao-app/ ├── backend/ │ ├── pom.xml │ └── src/main/java/com/liuyao/ │ ├── LiuYaoApplication.java │ ├── controller/ │ │ └── GuaController.java │ ├── service/ │ │ ├── QiGuaService.java │ │ ├── PaiPanService.java │ │ └── LiuYaoService.java │ ├── model/ │ │ ├── Gua.java │ │ ├── Yao.java │ │ └── PaiPanResult.java │ ├── enums/ │ │ ├── YinYangEnum.java │ │ ├── WuQinEnum.java │ │ └── LiuShenEnum.java │ ├── constant/ │ │ └── GuaConstant.java │ └── util/ │ ├── CalendarUtil.java │ └── GanZhiUtil.java └── frontend/ ├── app.json ├── pages/ │ ├── index/ │ │ ├── index.wxml │ │ ├── index.wxss │ │ ├── index.js │ │ └── index.json │ └── result/ │ ├── result.wxml │ ├── result.wxss │ ├── result.js │ └── result.json └── utils/ └── request.js这个结构是我在实际迭代中逐步调整出来的。最初我把所有规则都写在 Controller 里,结果发现一个接口上千行根本没法维护。后来拆成了 service 层负责业务流程,util 层负责纯算法,model 层定义数据模型,枚举类维护固定规则,代码才变得清爽。建议你在二次开发时不要破坏这个分层,尤其是“算法工具类”和“业务编排”一定要分离,排盘规则后续大概率要调整,分开了改起来才不伤筋动骨。
2. 起卦排盘核心算法解析
2.1 三种起卦方式的规则拆解
时间起卦是最常用也最稳定的方式。传统规则是这样的:先算出当前的农历年、月、日、时,以农历年支的地支序数作为年数,农历月数作为月数,农历日数作为日数,时辰的地支序数作为时数。上卦取“年数 + 月数 + 日数”除以 8 的余数,下卦取“年数 + 月数 + 日数 + 时数”除以 8 的余数,动爻取“年数 + 月数 + 日数 + 时数”除以 6 的余数。除尽时按余数 8 或 6 处理,这个细节特别容易写错。
数字起卦的逻辑和它类似,区别在于数字来源。通常支持两种输入方式:输入一个数字或者是三个数字。一个数字时,直接按 8 取上卦,按 6 取动爻,下卦则需要再补充一个数字;三个数字时,第一个数取上卦,第二个数取下卦,第三个数取动爻。为了统一,我的实现里支持任意个数的数字输入,通过参数动态截取,前两个参与卦象计算,最后一个参与动爻计算。
手动摇卦是最有仪式感的方式,小程序端模拟六次掷币,每次生成一个爻。传统规则里,铜钱正面记为“阳”,反面记为“阴”,三枚铜钱组合出四种结果:三个正面为老阳,三个反面为老阴,两正一反为少阳,两反一正为少阴。其中老阳和老阴属于动爻,会有变卦的后续逻辑。后端设计上,手动摇卦与前两种方式的差异在于“输入源不同”,一旦拿到六个爻的阴阳属性,后面的排盘流程完全一致,所以代码里我把“生成爻序列”和“排盘”拆分成了两个服务,方便复用。
2.2 装卦阶段:纳甲、世应、六亲、六神
拿到本卦的六个爻之后,排盘就开始了。第一步是确定卦宫。六十四卦被分为八宫,每宫八个卦,由本宫卦(八纯卦)逐爻变化衍生出来。先通过本卦的上下卦组合查表,得到它属于哪一宫,这是后续所有排盘信息的基础。我在 GuaConstant 里用了一张静态映射表,把六十四卦的上下卦组合映射到宫位和世应位置,这张表是整个排盘规则的核心,建议你调试时多花时间核对。
第二步是纳甲,也就是给每个爻配上地支。纳甲规则按卦宫分阴阳,乾卦内卦三爻配子、寅、辰,外卦三爻配午、申、戌;坤卦内卦配未、巳、卯,外卦配丑、亥、酉。其他六卦的纳甲规则也都有章可循。我实现时没有把六十四卦的地支全写死,而是按“内卦三爻和外卦三爻分别查表”的方式动态拼接,这样既减少出错,也方便调试。
第三步定世应。世爻表示卦主,应爻表示对方,它们的固定位置由卦在八宫中的排列顺序决定。例如八纯卦世爻在第六爻,一世卦在初爻,二世卦在二爻,依此类推。我把每个卦的世应位置直接放在常量表里,排盘时按宫位查表得到位置下标。
第四步排六亲。六亲的依据是卦宫五行和每一爻地支五行的生克关系,生我者为父母,我生者为子孙,克我者为官鬼,我克者为妻财,比和者为兄弟。这里宫位五行的“我”加上爻地支的五行关系,用五行生克表就能推导出。
第五步排六神。六神按起卦日的天干确定顺序,甲乙日起青龙,丙丁日起朱雀,戊日起勾陈,己日起腾蛇,庚辛日起白虎,壬癸日起玄武。然后从初爻到上爻按固定顺序排列,分别是青龙、朱雀、勾陈、腾蛇、白虎、玄武。
2.3 排盘数据流与结果输出模型
整条数据流可以概括为:输入源 -> 爻序列 -> 本卦 -> 变卦 -> 宫位 -> 纳甲 -> 世应 -> 六亲 -> 六神 -> 结构化结果。
我用 Gua 对象表示一个卦,里面包含六个 Yao 对象,每个 Yao 对象记录:阴阳属性、是否为动爻、对应地支、五行、六亲、六神、世应标记。排盘结果对象 PaiPanResult 则持有本卦、变卦、起卦时间、起卦方式、世应位置等元信息,最终序列化为 JSON 返回前端。前端只需要拿到这份结构化的 JSON,就可以按自己的设计渲染任意风格,不用关心计算逻辑。
这里有一个关键点:动爻。如果起卦结果里有动爻,就需要额外生成一个变卦。变卦规则很简单,动爻的阴阳属性翻转即可:老阳变阴,老阴变阳。如果六个爻全部是静爻,则没有变卦。实际项目中,动爻数量直接影响排盘结果的复杂程度,所以后端返回数据时,我会把“本卦”“变卦”“动爻列表”分开字段,方便前端做差异化展示。
3. 关键代码实现与细节拆解
3.1 干支历法与农历日期的计算
六爻排盘离不开干支,尤其是日干支,因为六神排列以日干为基准。Java 标准库没有直接提供农历和干支计算能力,所以我封装了一个 CalendarUtil,核心思路是用“基准日期偏移”的方式计算。
实现上,我以某个已知干支的日期为基准点,计算目标日期与基准点的天数差,然后对 60 取模,得到目标日期的干支序号。需要注意的是,时间的获取必须统一使用 Asia/Shanghai 时区,否则不同的服务器时区会导致结果偏差。
农历部分的处理相对麻烦,因为有闰月概念。Hutool 的 ChineseDate 组件支持农历和公历互转,在实际开发中我直接复用它。如果你不想引入额外依赖,也可以把农历全年数据表硬编码进常量类,但那样代码量会膨胀,而且容易出错。我在项目里用的是亨特尔算法配合 ChineseDate,既保证准确度,也降低了维护成本。
3.2 时间起卦的实现代码
时间起卦的入口在 QiGuaService,核心逻辑可以浓缩成下面这段代码。注意这里的重点是取模运算的细节处理,尤其是“除尽按 8 或按 6”的问题。
public Gua qiguaByTime(LocalDateTime dateTime, ZoneId zone) { // 1. 转农历,获取农历年支、月、日、时 ChineseDate chineseDate = CalendarUtil.toChineseDate(dateTime, zone); int yearZhi = GanZhiUtil.getYearZhiIndex(chineseDate.getYear()); int month = chineseDate.getMonth(); int day = chineseDate.getDay(); int hourZhi = GanZhiUtil.getHourZhiIndex(dateTime.getHour()); // 2. 计算上卦、下卦、动爻 int upGua = (yearZhi + month + day) % 8; if (upGua == 0) { upGua = 8; } int downGua = (yearZhi + month + day + hourZhi) % 8; if (downGua == 0) { downGua = 8; } int dongYao = (yearZhi + month + day + hourZhi) % 6; if (dongYao == 0) { dongYao = 6; } // 3. 根据上下卦数字得到本卦六爻,再翻转动爻得到变卦 Gua benGua = GuaConstant.buildGuaByNumber(upGua, downGua, dongYao); benGua.setDongYaoIndex(dongYao); Gua bianGua = benGua.copy(); bianGua.flipYao(dongYao - 1); // 4. 后续由 PaiPanService 继续完成装卦 return benGua; }这段代码看起来简单,但有几个容易踩的坑。第一,年月日时数字的来源必须统一,比如年必须用“年支的序数”而不是农历年份本身,否则结果完全不同。第二,上卦和下卦的计算公式不一样,别把时数加到上卦里。第三,动爻取 6 时代表第六爻动,数组下标要减 1,别越界。
3.3 摇卦随机数与并发处理
手动摇卦的随机性处理,我一开始用的是 Math.random(),后来在压测时发现并发请求下容易出现“连续摇出同样六个爻”的诡异情况。原因是 Math.random() 底层用的 Random 并不是强随机,而且多线程共享同一个 Random 实例时会出现竞争问题。
换成 SecureRandom 之后问题明显改善。SecureRandom 属于密码学安全的随机数生成器,生成质量更高,但调用开销也更大。考虑到摇卦操作的并发量并不会特别高,直接使用 SecureRandom 是划算的。如果后续要支撑高并发,可以在项目里用 ThreadLocal 持有 SecureRandom 实例,避免每次请求都重新初始化。
摇卦的映射逻辑是这样的:每次生成一个 0 到 3 的随机整数,0 代表三个反面(老阴),1 代表两正一反(少阳),2 代表两反一正(少阴),3 代表三个正面(老阳)。这个映射顺序要和小程序端的动画效果保持一致,否则会出现“前端显示正面,后端却记为反面”的错位。
3.4 排盘数据结构设计与前端对接
我把排盘结果的数据模型设计成了三层嵌套结构,第一层是本次排盘的整体信息,第二层是本卦和变卦,第三层是每个爻的细节。这样设计的好处是前端拿到一份 JSON 就能一次性渲染完整盘面,不需要多次请求。
public class PaiPanResult { private Gua benGua; private Gua bianGua; private String qiGuaType; // 起卦方式 private String dateTime; // 起卦时间 private Integer shiYaoIndex; // 世爻位置 private Integer yingYaoIndex; // 应爻位置 } public class Gua { private String guaName; // 卦名 private String guaGong; // 所属宫 private List<Yao> yaos; // 六个爻 private Integer dongYaoIndex; // 动爻位置,无则为 null } public class Yao { private Integer index; // 爻位置,0 到 5 private YinYang yinYang; // 阴阳 private String diZhi; // 地支 private String wuXing; // 五行 private String wuQin; // 六亲 private String liuShen; // 六神 private Boolean isDong; // 是否动爻 private Boolean isShi; // 是否世爻 private Boolean isYing; // 是否应爻 }这样设计后,后端代码的职责非常清晰:QiGuaService 只负责产出本卦和变卦,PaiPanService 负责给卦里的每个爻补齐纳甲、六亲、六神、世应信息,Controller 则负责拼装响应。小程序端拿到这份 JSON 后,直接循环渲染六个爻的卡片,就能得到一个完整的盘面。
4. 小程序前端的交互与展示实现
4.1 起卦交互页面与单选框使用
小程序首页的设计很直白:顶部标题,中间一个 radio-group 让用户选择起卦方式,下方根据选择动态显示不同的输入区,底部是“开始排盘”按钮。
radio-group 的具体用法是:在 radio-group 里放多个 radio 组件,每个 radio 的 value 是字符串,通过 bindchange 事件取到用户选中的值。实际开发中有一个容易忽略的细节:radio 的 value 是字符串类型,如果你在后端接口里期望接收整数,前端必须先用 parseInt 转换,否则很容易出现类型不匹配的问题。
三种起卦方式的输入区是动态切换的。时间起卦方式不需要输入任何内容,直接读取手机本地时间,由后端计算农历和干支;数字起卦方式需要用户输入一个或三个数字;手动摇卦方式则显示“开始摇卦”按钮,点击一次生成一爻,一共点六次。动态切换用 wx:if 和 wx:elif 控制,注意每个输入区都要有独立的 data 绑定,避免切换时残留上一次的数据。
4.2 后端请求封装与状态管理
小程序请求后端统一走 utils/request.js 封装函数,内部使用 wx.request 发送请求,并统一处理 loading、错误提示和超时。封装的核心是返回一个 Promise,这样页面代码可以用 async/await 风格编写,逻辑会更清爽。
接口地址配置在小程序后台的“开发设置”里,需要加入 request 合法域名。本地开发时可以在开发者工具里勾选“不校验合法域名”,但上线前一定要换成 HTTPS 域名。我在项目里用了一个最小代码片段来展示请求封装逻辑:
function post(url, data) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + url, method: 'POST', data: data, timeout: 10000, success: (res) => { if (res.data.code === 0) { resolve(res.data.data); } else { wx.showToast({ title: res.data.msg, icon: 'none' }); reject(res.data); } }, fail: (err) => reject(err) }); }); }这里有个交互细节要提醒:时间起卦时前端拿到的本地时间可能和服务器时间有偏差,严谨的做法是前端请求一个时间同步接口,用服务器时间作为起卦时间,否则用户手机时间不准会直接影响排盘结果。我在实际开发中增加了这个时间校准逻辑,虽然多了一次请求,但结果准确性提升很多。
4.3 卦象渲染与分享输出
结果页的盘面展示是整个小程序的门面。我采用了列表卡片形式:每一行代表一个爻,显示六神、六亲、地支、世应标记,并用不同的背景色区分阳爻和阴爻。动爻则用特殊边框高亮,让用户一眼就能看出变卦的位置。
六爻的卦象符号我用简单字符来绘制,阳爻显示为一条实线,阴爻显示为两条短线,在 canvas 里绘制会更加美观,不过普通场景下 view 组件加上 CSS 也能实现不错的效果。考虑到小程序的包体积和渲染性能,我没有引入图表库,全部用基础组件实现。
分享功能利用小程序的 button open-type="share" 即可,onShareAppMessage 里拼接当前页面的参数。为了让分享出去的卡片包含盘面信息,我会在分享路径中带上排盘结果的 id,接收方打开时通过 id 请求后端重新拉取数据。这里一定要注意:不要在分享路径里塞太多参数,否则 URL 长度超限会报错。
5. 常见问题与排查实录
5.1 时间计算中的时区与闰月坑
我调试时遇到最诡异的问题是:本地运行正常,部署到云服务器后,时间起卦的结果和本地差了整整一个时辰。排查了大半天,最终发现是服务器默认时区是 UTC,而中国用的东八区,LocalDateTime 转 Date 时偏移了 8 小时。解决办法是启动类里显式指定时区,或者统一使用带时区的 ZonedDateTime。
第二个坑是闰月。农历闰月会导致月份数字不连续,直接拿 ChineseDate.getMonth() 在闰月年份会得到特殊标记。Hutool 的 ChineseDate 在处理闰月时会返回闰月标记,你必须判断当前是否处于闰月,否则“闰四月”会被当成“五月”计算,起卦结果全错。建议给 CalendarUtil 增加一个返回值对象,既包含月序数也包含是否闰月标记,方便上层做判断。
5.2 并发场景下的随机数与限流
时间起卦接口本身是无状态的,但手动摇卦因为涉及随机数生成器,并发时会概率性出现重复随机序列。前面提到换用 SecureRandom 后问题减少,但还有一个隐患:如果同一用户在一秒内连续点击“开始排盘”,后端拿到的时间完全相同,时间起卦的结果也一样,这是符合传统规则的,但从产品体验看,用户会觉得异常。
我的处理方式是接口幂等:同一用户在同一分钟内的重复请求直接返回最近一次的结果,而不是重新计算。实现上用一个 Caffeine 本地缓存,key 是用户 id 加分钟时间戳,value 是上一次排盘结果。这个方法也顺带降低了后端压力,一举两得。
5.3 小程序审核与渲染性能优化
小程序上线时,审核最关注的是类目和描述。我给这个工具设的类目是“工具 > 信息查询”,描述里明确写出“传统文化排盘演示,结果仅供学习交流”,避免使用“占卜预测”这类敏感词,这样能减少审核被驳回的概率。
渲染性能上也有一个经验要分享:六十四卦的卦名数据在结果页高频使用,如果每次加载都请求后端,用户流量消耗大而且白屏时间长。我后来把卦名、卦宫、纳甲表这些稳定的静态数据做成了本地 JSON,直接随小程序包加载,启动时读到全局变量里。只有真正的排盘请求才走后端接口,这样体验会流畅很多。
如果你后续想把项目扩展到更多平台,可以把前端改成 uni-app 或 Taro,后端完全不用动。因为接口层已经足够通用,任何能发 HTTP 请求的客户端都能接入。这也是我在设计初期坚持前后端分离的原因。
这个项目做完之后,我最大的感受是,传统文化里的很多规则模型,一旦拆解成清晰的映射关系,落地的难度其实比想象中要低。真正的坑往往藏在细节里,比如时区、闰月、取模边界、随机数并发,这些才是考验工程经验的地方。后续我还打算把排盘结果的解释库补上,做成一个完整的文化学习工具。源码里的目录结构、枚举设计和常量表,都是我踩过坑之后沉淀下来的,希望能帮你少走一点弯路。
本文还有配套的精品资源,点击获取