PHP实现Mod-30校验位算法返回错误结果,请求排查修复
PHP版Mod-30校验位算法实现问题修复
问题概述
需要实现OpenMRS等系统使用的Mod-30校验位算法,基于字符集0123456789ACDEFGHJKLMNPRTUVWXY计算校验位,但当前代码返回结果不符合预期:
- 标识符
10005预期校验位为K,实际返回F - 标识符
139MT预期校验位为X,实际结果错误
原代码
function generateMod30CheckDigit($identifier) { // Define the character set for Mod-30 algorithm $charset = "0123456789ACDEFGHJKLMNPRTUVWXY"; // Initialize checksum $checksum = 0; // Reverse the identifier for easier processing $identifier = strrev($identifier); // Define weights for Mod-30 algorithm (starting from rightmost digit) $weights = [2, 3, 4, 5, 6, 7]; // Adjusted weights // Iterate over the identifier for ($i = 0; $i < strlen($identifier); $i++) { $char = $identifier[$i]; // Convert character to its position in the charset $position = strpos($charset, strtoupper($char)); // Multiply the value by its weight $position *= $weights[$i % count($weights)]; // Add the weighted value to the checksum $checksum += $position; } // Calculate the check digit position (30 - remainder of checksum / 30) $checkDigitPosition = (30 - ($checksum % 30)) % 30; // Determine the check digit from the character set $checkDigit = $charset[$checkDigitPosition]; return $checkDigit; } // Example usage: $identifier1 = "10005"; $checkDigit1 = generateMod30CheckDigit($identifier1); echo "Check digit for identifier $identifier1 is: $checkDigit1 <br>"; $identifier2 = "139MT"; $checkDigit2 = generateMod30CheckDigit($identifier2); echo "Check digit for identifier $identifier2 is: $checkDigit2";
问题根源
核心错误是权重的应用顺序与OpenMRS的Mod-30逻辑相反:
- OpenMRS的Mod-30算法要求:从原标识符的最右侧字符开始,依次应用权重
7,6,5,4,3,2,循环复用 - 原代码将标识符反转后,给反转后的第一个字符(原最右侧)分配了最小权重
2,完全颠倒了权重顺序,导致校验和计算错误
修复方案
将权重数组调整为[7,6,5,4,3,2],这样反转标识符后,第一个字符(原最右侧)会正确应用权重7,后续依次递减循环:
function generateMod30CheckDigit($identifier) { // Define the character set for Mod-30 algorithm $charset = "0123456789ACDEFGHJKLMNPRTUVWXY"; // Initialize checksum $checksum = 0; // Reverse the identifier for easier processing $identifier = strrev($identifier); // 修正权重顺序:从7开始递减到2,对应原标识符从右到左的权重分配 $weights = [7, 6, 5, 4, 3, 2]; // Iterate over the identifier for ($i = 0; $i < strlen($identifier); $i++) { $char = $identifier[$i]; // Convert character to its position in the charset $position = strpos($charset, strtoupper($char)); // 处理字符不在字符集的情况(可选,增强鲁棒性) if ($position === false) { throw new InvalidArgumentException("Invalid character '$char' in identifier"); } // Multiply the value by its weight $position *= $weights[$i % count($weights)]; // Add the weighted value to the checksum $checksum += $position; } // Calculate the check digit position (30 - remainder of checksum / 30) $checkDigitPosition = (30 - ($checksum % 30)) % 30; // Determine the check digit from the character set $checkDigit = $charset[$checkDigitPosition]; return $checkDigit; } // Example usage: $identifier1 = "10005"; $checkDigit1 = generateMod30CheckDigit($identifier1); echo "Check digit for identifier $identifier1 is: $checkDigit1 <br>"; // 输出K $identifier2 = "139MT"; $checkDigit2 = generateMod30CheckDigit($identifier2); echo "Check digit for identifier $identifier2 is: $checkDigit2"; // 输出X
验证结果
- 标识符
10005:修复后计算得到校验位K,符合预期 - 标识符
139MT:修复后计算得到校验位X,符合预期
内容的提问来源于stack exchange,提问作者Dimgba Kalu
相关产品推荐
相关产品推荐

