一、闲谈
手机号码的格式千变万化,不同国家的手机号码格式有很大的差异,细细研究,会发现很多有意思的事情,比如: 你以为手机号码都是数字组成的?当然不, 在以色列,1-800-Flowers 也是一个合法的手机号, 而在埃及,手机号码可能是下面的象形文字哦 其实这些有趣的小故事都可以在 google 开源的库中找到:地址
所以,很显然,本文所提及的解决方案,其实就是 google 的这个类库的使用方法。
二、设计
关于手机号校验的设计,会涉及到几个点: ① 手机号码数据类型
上文也提过,手机号码也会出现字符哦,所以,肯定不能使用 int 等数字类型,况且手机号码也基本不会用来进行计算操作。 ② 长度
手机号码长度其实是依据各个国家的设计而不相同的, 比如大陆就是11位手机号,而瑞士,据说会有8位的号码。那么应该使用多长的字段来保存呢?其实手机号保留 20 位长度,目前是比较安全的,所以,数据库设计为 varchar 50 是妥妥没问题的。
③ 页面设计
其实最佳的体验是分为两个框,一个下拉框用来选择区号,另一个输入框用来输入手机号。 ④ 手机号校验
诚然,如果强制要求有验证码校验,那么是最佳的,但时如果没有的话,那么在此推荐 google 的手机号码校验类库:libphonenumber 比如亚马逊aws的注册页面,就没有进行号码的规制校验,但时强制使用了验证码校验。关于 libphonenumber,该类库提供了 Java、JS等实现,上手简单,功能强大。而且还提供了运营商查询、归属地查询等功能。
三、类库的使用
① jar包引入
如果是使用 maven 来构建地址,需要引入如下几个库<!--手机号解析--> <dependency> <groupId>com.googlecode.libphonenumber</groupId> <artifactId>libphonenumber</artifactId> <version>8.9.9</version> </dependency> <!--手机归属地定位相关--> <dependency> <groupId>com.googlecode.libphonenumber</groupId> <artifactId>geocoder</artifactId> <version>2.99</version> </dependency> <!-- 手机运营商相关 --> <dependency> <groupId>com.googlecode.libphonenumber</groupId> <artifactId>carrier</artifactId> <version>1.9</version> </dependency> <dependency> <groupId>com.googlecode.libphonenumber</groupId> <artifactId>prefixmapper</artifactId> <version>2.99</version> </dependency>
② 工具类代码
import com.google.i18n.phonenumbers.NumberParseException; import com.google.i18n.phonenumbers.PhoneNumberToCarrierMapper; import com.google.i18n.phonenumbers.PhoneNumberUtil; import com.google.i18n.phonenumbers.Phonenumber; import com.google.i18n.phonenumbers.geocoding.PhoneNumberOfflineGeocoder; import java.util.HashMap; import java.util.Locale; import java.util.Map; /** * @Author: Jet * @Description: 国际手机号校验 * @Date: 2018/5/9 9:20 */ public class LibphonenumberUtil { private static final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance(); private static PhoneNumberToCarrierMapper carrier = PhoneNumberToCarrierMapper.getInstance(); private static PhoneNumberOfflineGeocoder geocoder = PhoneNumberOfflineGeocoder.getInstance(); private static final String DEFAULT_COUNTRY = "CN"; private static final String[] phoneCases = new String[] { "8618611234515", //中国 true "00886912347718", //台湾 "006581234994", //新加坡 "15911234718", //中国 "008201234704546", //Korea "17091234155" //中国170 }; public static final Map<String, String> CHINESE_CARRIER_MAPPER = new HashMap<>(); static { CHINESE_CARRIER_MAPPER.put("China Mobile", "中国移动"); CHINESE_CARRIER_MAPPER.put("China Unicom", "中国联通"); CHINESE_CARRIER_MAPPER.put("China Telecom", "中国电信"); } public static void main(String[] args) { System.out.println(doGeo("17811981865", "86")); } /** * @Author: Jet * @Description ① 可以加区号,也可以不加,区号默认86 * ② 区号前面的“+”和“00”占位可加可不加 * ② 手机号中间可以增加“-” * @param phone “+8617717031234 +008617717031234 8617717031234 177-1703-1234” * @Date: 2018/5/9 9:21 */ public static boolean doValidUniversal(String phone) { Phonenumber.PhoneNumber phoneNumber = doParse(phone); return phoneNumber.hasNationalNumber() && doValid(phoneNumber.getNationalNumber() + "", phoneNumber.getCountryCode() + ""); } /** * @Author: Jet * @Description 电话解析逻辑 * @param phone “+8617717031234 +008617717031234 8617717031234 177-1703-1234”” * @return 电话实体类 Phonenumber.PhoneNumber * @Date: 2018/5/9 9:21 */ public static Phonenumber.PhoneNumber doParse(String phone) { try { return phoneNumberUtil.parse(phone, DEFAULT_COUNTRY); } catch (NumberParseException e) { throw new NumberFormatException("invalid phone number: " + phone); } } /** * @Author: Jet * @Description 手机校验逻辑 * @param phoneNumber 手机号 * @param countryCode 手机区号 * @Date: 2018/5/9 9:21 */ public static boolean doValid(String phoneNumber, String countryCode){ int ccode = Integer.parseInt(countryCode); long phone = Long.parseLong(phoneNumber); Phonenumber.PhoneNumber pn = new Phonenumber.PhoneNumber(); pn.setCountryCode(ccode); pn.setNationalNumber(phone); return phoneNumberUtil.isValidNumber(pn); } /** * @Author: Jet * @Description 手机运营商 * @param phoneNumber 手机号 * @param countryCode 手机区号 * @return 能转成中文则返回中文,否则返回英文的 * @Date: 2018/5/9 9:21 */ public static String doCarrier(String phoneNumber, String countryCode){ int ccode = Integer.parseInt(countryCode); long phone = Long.parseLong(phoneNumber); Phonenumber.PhoneNumber pn = new Phonenumber.PhoneNumber(); pn.setCountryCode(ccode); pn.setNationalNumber(phone); //返回结果只有英文,自己转成成中文 String carrierEn = carrier.getNameForNumber(pn, Locale.ENGLISH); return CHINESE_CARRIER_MAPPER.containsKey(carrierEn)?CHINESE_CARRIER_MAPPER.get(carrierEn):carrierEn; } /** * @Author: Jet * @Description 手机归属地 * @param phoneNumber 手机号 * @param countryCode 手机区号 * @Date: 2018/5/9 9:21 */ public static String doGeo(String phoneNumber, String countryCode){ int ccode = Integer.parseInt(countryCode); long phone = Long.parseLong(phoneNumber); Phonenumber.PhoneNumber pn = new Phonenumber.PhoneNumber(); pn.setCountryCode(ccode); pn.setNationalNumber(phone); return geocoder.getDescriptionForNumber(pn, Locale.CHINESE); } }
③ 工具类使用说明
结构很简单,见下图:四、总结
① 由于手机号码的规则一直是处于更新状态的,所以建议jar包的版本要常更新,可以常逛逛 maven 仓库:https://repo1.maven.org/maven2/com/googlecode/libphonenumber/ ② 手机号校验千万要加区号,否则会有歧义,如下 10 位长度手机号:
857 498 4492
10位手机号按理应该是国外的,但是你如果使用国内的区号来校验会发现,该号码是“贵州毕节”的,
至于该号码到底是什么,暂未摸清楚。
文章评论