使用Java编写Hive的UDF实现身份证号码校验及15位升级18位

news2024/12/21 19:17:15

使用Java编写Hive的UDF实现身份证号码校验及15位升级18位

背景

在数仓项目中,有时候会根据身份证信息做一些取数filter或者条件判断的相关运算进而获取到所需的信息。古人是用Oracle做数仓,理所当然是用SQL写UDF【虽然SQL写UDF给SQL用就像用鸡肉饲养肉鸡一样,令笔者感叹Oracle的神奇】。自从Oracle数据库开发岗位被取缔后,SQL Boy们就摇身一变,成为还是只会写Hive SQL脚本的大数据开发攻城狮。本来SQL Boy们力推“原则上不允许写Java和Scala,只允许写SQL”,但是纯SQL的方式写出来,case when里套case when,套了好几层,还没有写完,就已经有几百行了。由于HQL任务可能多次用到这个功能,会导致凡是用到这种判断算法的HQL脚本最终能有几千行、上w行,不忍直视。所以最迂腐的SQL Boy也终于还是想明白了原则上不允许代表着实际上可以这么做。笔者就写了这个UDF来大幅减少HQL任务的篇幅,提升美观性。日后更多的SQL Boy也可以直接套用这个方法提高开发效率。

原理

参考:https://lizhiyong.blog.csdn.net/article/details/126186377

众所周知,Hive不可以像Oracle那样用SQL写UDF,所以需要Java写,并且打Jar包注册运行。底层原理参照之前的这篇。简单起见,就不继承GenericUDF了,而是继承UDF。

具体的规则,需要参考国标:GB11643-1999。老的 GB11643-1989 已经淘汰了,这也是为神马要有15位升级18位的功能。国标中已经给出了具体的系数和校验位的算法,照猫画虎即可。

算法

先去除脏数据,如果满足15位长,就升级18位。如果是18位,就算出校验码判断是否和数据的校验码一致。为了让判断更严谨,当然还需要添加一些判断,例如上上世纪【1900年之前】出生的人一定不会用自己的身份证号买车了。。。这些都是业务代码,日后可以根据实际情况再做修正,比纯SQL方式的可维护性好太多了。

Java实现

pom

<properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <hive-exec.version>3.1.2</hive-exec.version>
        <encoding>UTF-8</encoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.hive</groupId>
            <artifactId>hive-exec</artifactId>
            <version>${hive-exec.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.glassfish</groupId>
                    <artifactId>javax.el</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

只需要这个 hive-exec 依赖即可。笔者当前版本的Apache Hive有依赖冲突,故手动排除了glassfish。

Java类

package com.zhiyong;

import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.hive.ql.exec.UDF;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @program: zhiyong_study
 * @description: HiveUDF用于检测是否为身份证号
 * @author: zhiyong
 * @create: 2023-02-23 21:27
 **/
public class IdCardCheck {
    //顺便用main方法做单元测试
    public static void main(String[] args) {
        String input1 = "142222190001011111";
        String input2 = "abCd1900ya101111x";
        String input3 = "#s12311111   11";
        String input4 = "###############";
        String input5 = "########### @";
        String input6 = "111112222233333";
        String input7 = "110 10  03x";
        String input8 = "11 946 9120 3";
        String input9 = "11 21951 2311 X";

        Udf_zhiyong_id_check udf_zhiyong_id_check = new Udf_zhiyong_id_check();

        System.out.println("result1 = " + udf_zhiyong_id_check.evauate(input1));
        System.out.println("result2 = " + udf_zhiyong_id_check.evauate(input2));
        System.out.println("result3 = " + udf_zhiyong_id_check.evauate(input3));
        System.out.println("result4 = " + udf_zhiyong_id_check.evauate(input4));
        System.out.println("result5 = " + udf_zhiyong_id_check.evauate(input5));
        System.out.println("result6 = " + udf_zhiyong_id_check.evauate(input6));
        System.out.println("result7 = " + udf_zhiyong_id_check.evauate(input7));
        System.out.println("result8 = " + udf_zhiyong_id_check.evauate(input8));
        System.out.println("result9 = " + udf_zhiyong_id_check.evauate(input9));

    }
}

/**
 * UDF用于判断是否为身份证号码,18位则返回,15位则升级18位,否则返回空串
 */
class Udf_zhiyong_id_check extends UDF {
    public String evauate(String input) {
        String result = "";
        if (StringUtils.isEmpty(input)) {
            return "";
        }

        result = input
                .replaceAll("x", "X")    //x->X
                .replaceAll("([a-z]+)", "")  //去除a-z
                .replaceAll("([A-W]+)", "")  //去除A-W
                .replaceAll("([Y-Z]+)", "")  //去除Y-Z
                .replaceAll("\\s*", "")  //去除所有空格
        ;

        int length = result.length();
        if (!(15 == length || 18 == length)) {//必须是15位或者18位
            return "";
        }

        for (int i = 0; i < length; i++) {
            try {
                int parseInt = Integer.parseInt(String.valueOf(result.charAt(i)));
            } catch (NumberFormatException e) {
                if (!(i == 17 && String.valueOf(result.charAt(i)).equals("X"))) {
                    return "";
                }
            }
        }

        //+一些判断让UDF更严谨
        //=====================================START================================
        Boolean flg = false;
        int[] provinceCode = {11, 12, 13, 14, 15,   //华北
                21, 22, 23,   //东北
                31, 32, 33, 34, 35, 36, 37,   //华东
                41, 42, 43,   //华中
                44, 45, 46,    //华南
                50, 51, 52, 53, 54, //西南
                61, 62, 63, 64, 65, //西北
                81, 82, 83    //港澳台
        };

        for (int i = 0; i < provinceCode.length; i++) {
            if (provinceCode[i] == Integer.parseInt(result.substring(0, 2))) {
                flg = true;
            }
        }

        if (!flg) {
            return "";
        }

        int year = 0;
        int month = 0;
        int day = 0;
        if (15 == length) {
            year = 1900 + Integer.parseInt(result.substring(6, 8));
            month = Integer.parseInt(result.substring(8, 10));
            day = Integer.parseInt(result.substring(10, 12));
        } else {
            year = Integer.parseInt(result.substring(6, 10));
            month = Integer.parseInt(result.substring(10, 12));
            day = Integer.parseInt(result.substring(12, 14));
        }

        if (year * 1000 + month * 100 + day >
                Integer.parseInt(new SimpleDateFormat("yyyyMMdd").format(new Date()))
                || year < 1900) {
            return "";
        }

        if (month > 12 || month < 1) {
            return "";
        }

        if (day > 31 || day < 1) {
            return "";
        }

        if (day == 31 && (month == 4 || month == 6 || month == 9 || month == 11)) {
            return "";
        }

        if (month == 2 && day > 28) {
            if (day > 29) {
                return "";
            }
            //29号只存在于闰年
            if (!(year % 100 == 0 || (year % 4 == 0 && year % 100 != 0))) {
                return "";
            }
        }

        //==============================END============================

        int[] tmp1 = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}; //存储系数
        String[] tmp2 = {"1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"};  //存储尾数
        int sum = 0;

        if (15 == length) {//身份证15位->18位
            String str_17 = result.substring(0, 6) + "19" + result.substring(6, 15);

            for (int i = 0; i < str_17.length(); i++) {//逐位相乘及sum
                sum += Integer.parseInt(String.valueOf(str_17.charAt(i))) * tmp1[i];
            }

            for (int i = 0; i < tmp2.length; i++) {
                if (i == sum % 11) {
                    return str_17 + tmp2[i];
                }
            }
        }

        //18位的需要校验尾数
        for (int i = 0; i < length - 1; i++) {
            sum += Integer.parseInt(String.valueOf(result.charAt(i))) * tmp1[i];
        }

        for (int i = 0; i < tmp2.length; i++) {
            if (i == sum % 11) {
                if (!(tmp2[i].equals(String.valueOf(result.charAt(17))))) {
                    return "";
                }
            }
        }

        return result;
    }

}

大数据行业,业务算法不值钱,值钱的是数据。。。笔者把sit验证用的数据脱敏了。。。读者可以自己搞一些验证。

结果

在这里插入图片描述

当使用正确的数据时,可以返回值。如果有多余的空格,也可以去除掉并且返回正确的值。如果是错误的数据,就返回空串。

打Jar包上传DataPhin和上传到Apache Hive操作略有不同,底层实现是一致的,如果是别的平台也是类似的做法,要触类旁通!!!

总结

大数据开发中,如果处理的是结构化的表,用功能极其简陋的SQL并没有什么问题,反倒脚本短小易于阅读还有开发运维容易的优势。涉及到略微不那么简单的递归迭代、循环遍历、多路分支,纯SQL硬写也写得出来并且语法没问题的话也可以凑合着跑起来。但是遇到处理的是文件、流、或者每一条数据的字段个数不一样多的log,数据不再是结构化的表时,SQL就完全没有用武之地。虽然不如Scala那么强大,但是Java做这些事情还是比SQL强很多。原则是死的,人是活的,要与时俱进学会变通!!!

总结

大数据开发中,如果处理的是结构化的表,用功能极其简陋的SQL并没有什么问题,反倒脚本短小易于阅读还有开发运维容易的优势。涉及到略微不那么简单的递归迭代、循环遍历、多路分支,纯SQL硬写也写得出来并且语法没问题的话也可以凑合着跑起来。但是遇到处理的是文件、流、或者每一条数据的字段个数不一样多的log,数据不再是结构化的表时,SQL就完全没有用武之地。虽然不如Scala那么强大,但是Java做这些事情还是比SQL强很多。原则是死的,人是活的,要与时俱进学会变通!!!

转载请注明出处:https://lizhiyong.blog.csdn.net/article/details/129220107
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/371174.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Gin获取Response Body引发的OOM

有轮子尽量用轮子 &#x1f62d; &#x1f62d; &#x1f62d; &#x1f62d; &#x1f62d; &#x1f62d; 我们在开发中基于Gin开发了一个Api网关&#xff0c;但上线后发现内存会在短时间内暴涨&#xff0c;然后被OOM kill掉。具体内存走势如下图&#xff1a; 放大其中一次 在…

OllyDbg

本文通过吾爱破解论坛上提供的OllyDbg版本为例&#xff0c;讲解该软件的使用方法 F2对鼠标所处的位置打下断点&#xff0c;一般表现为鼠标所属地址位置背景变红F3加载一个可执行程序&#xff0c;进行调试分析&#xff0c;表现为弹出打开文件框F4执行程序到光标处F5缩小还原当前…

EF 框架的简介、发展历史;ORM框架概念

一、EF 框架简介EF 全称是 EntityFramework 。Entity Framework是ADO.NET 中的一套支持开发面向数据的软件应用程序的技术,是微软的一个ORM框架。ORM框架&#xff08;Object Relational Mapping&#xff09; 翻译过来就是对象关系映射。如果不用ORM框架&#xff0c;我们一般这样…

考虑交叉耦合因素的IPMSM无传感器改进线性自抗扰控制策略

考虑交叉耦合因素的IPMSM无传感器改进线性自抗扰控制策略一级目录二级目录三级目录控制原理ELADRC信号提取龙格贝尔观测器方波注入simulink仿真给定转速&#xff1a;转速环&#xff1a;电流环&#xff1a;一级目录 二级目录 三级目录 首先声明一下&#xff0c;本篇博客是复现…

分析 HTTP,TCP 的长连接和短连接以及 socket

1、HTTP 协议与 TCP/IP 协议的关系 HTTP 的长连接和短连接本质上是 TCP 长连接和短连接。HTTP 属于应用层协议&#xff0c;在传输层使用 TCP 协议&#xff0c;在网络层使用 IP 协议。IP 协议主要解决网络路由和寻址问题&#xff0c;TCP 协议主要解决如何在 IP 层之上可靠的传递…

Apache Hadoop生态部署-Flume采集节点安装

目录 Apache Hadoop生态-目录汇总-持续更新 一&#xff1a;安装包准备 二&#xff1a;安装与常用配置 2.1&#xff1a;下载解压安装包 2.2&#xff1a;解决guava版本问题 2.3&#xff1a;修改配置 三&#xff1a;修复Taildir问题 3.1&#xff1a;Taildir Source能断点续…

SpringMVC请求转发和重定向

请求转发&#xff1a;forward:重定向&#xff1a;redirect转发&#xff1a;由服务器的页面进行跳转&#xff0c;不需要客户端重新发送请求&#xff1a;特点如下&#xff1a;1、地址栏的请求不会发生变化&#xff0c;显示的还是第一次请求的地址2、请求的次数&#xff0c;有且仅…

已解决kettle新建作业,点击保存抛出异常Invalid state, the Connection object is closed.

已解决kettle新建作业&#xff0c;点击保存进资源数据库抛出异常Invalid state, the Connection object is closed.的解决方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01; 文章目录报错问题报错翻译报错原因解决方法联系博主免费帮忙解决报错报错问题 一个小伙伴…

JS 执行机制 详解(附图)

一、JS是单线程JS语言的一大特点就是单线程&#xff0c;也就是说&#xff0c;同一个时间只能做一件事。这是JS这门脚本语言诞生的使命所致——用来处理页面中用户的交互&#xff0c;以及操作DOM而诞生的。单线程就意味着&#xff0c;所有任务需要排队&#xff0c;前一个任务结束…

C++014-C++字符串

文章目录C014-C字符串字符串目标char[]和stringchar[]char*string字符常量与字符串常量字符串的输入题目描述 字符串输出题目描述在线练习&#xff1a;总结C014-C字符串 在线练习&#xff1a; http://noi.openjudge.cn/ https://www.luogu.com.cn/ 字符串 目标 1、了解字符串…

OAuth2.0从入门到实战(附github地址)

OAuth2.0 文章目录OAuth2.0OAuth2.0的含义与思想[快递员的例子]([OAuth 2.0 的一个简单解释 - 阮一峰的网络日志 (ruanyifeng.com)](https://www.ruanyifeng.com/blog/2019/04/oauth_design.html))互联网的例子令牌与密码OAuth2.0的四种授权方式RFC 6749一、授权码(前后端分离)…

Vue3电商项目实战-商品详情模块6【17-商品详情-标签页组件、18-商品详情-热榜组件、19-商品详情-详情组件、20-商品详情-注意事项组件】

文章目录17-商品详情-标签页组件18-商品详情-热榜组件19-商品详情-详情组件20-商品详情-注意事项组件17-商品详情-标签页组件 目的&#xff1a;实现商品详情组件和商品评价组件的切换 大致步骤&#xff1a; 完成基础的tab的导航布局完成tab标签页的切换样式效果使用动态组件完…

【设计模式】 策略模式介绍及C代码实现

【设计模式】 策略模式介绍及C代码实现 背景 在软件构建过程中&#xff0c;某些对象使用的算法可能多种多样&#xff0c;经常改变&#xff0c;如果将这些算法都编码到对象中&#xff0c;将会使对象变得异常复杂&#xff0c;而且有时候支持不使用的算法也是一个性能负担。 如何…

go单元测试

接着上一篇中的go module创建项目calc为例&#xff0c;在simplemath包中&#xff0c;是使用在命令行中使用交互式的方式进行测试&#xff0c;现在可以为这几个函数实现单元测试&#xff0c; go test&#xff0c;这个测试工具来自于 Go 官方的 gc 工具链。 运行 go test 命令将执…

JVM本地方法接口和本地方法栈

1、本地方法概述简单地讲&#xff0c;一个Native Method是一个Java调用非Java代码的接囗。一个Native Method是的实现由非Java语言实现&#xff0c;比如C。这个特征并非Java所特有&#xff0c;很多其它的编程语言都有这一机制&#xff0c;比如在C中&#xff0c;你可以用extern …

openpnp - 零碎记录

文章目录openpnp - 零碎记录概述笔记配置文件保存无效ENDopenpnp - 零碎记录 概述 这段时间, 正在配置校准手头的openpnp设备, 用的官网最新的openpnp2.0. 由于openpnp的bug和自己的不细致, 导致多次校准失败. 现在从头校准时, 每进行一步, 就保存一下配置文件, 如果最终发现…

MySQL_主从复制读写分离

主从复制 概述 主从复制是指将主数据库的DDL和DML操作通过二进制日志传到从库服务器中&#xff0c;然后在从库上对这些日志重新执行&#xff08;也叫重做&#xff09;&#xff0c;从而使得从库和主库的数据保持同步。 MySQL支持一台主库同时向多台从库进行复制&#xff0c;从…

leetcode 31~40 学习经历

leetcode 31~40 学习经历31. 下一个排列32. 最长有效括号33. 搜索旋转排序数组34. 在排序数组中查找元素的第一个和最后一个位置35. 搜索插入位置36. 有效的数独37. 解数独38. 外观数列39. 组合总和40. 组合总和 II小结31. 下一个排列 整数数组的一个 排列 就是将其所有成员以序…

3.JVM内存分配机制详解【2023】

redis跳表 内容概要 内存分配 1.类加载检查 &#x1f60a;虚拟机遇到一条new指令时&#xff0c;首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用&#xff0c;并且检查这个 符号引用代表的类是否已被加载、解析和初始化过。如果没有&#xff0c;那必须先执…

MySQL/Oracle获取当前时间几天/分钟前的时间

获取当前时间 要想获取当前时间几天/分钟前的时间&#xff0c;首先要知道怎么获取当前时间&#xff1b; 对于MySQL和Oracle获取当前时间的方法是不一样的&#xff1b; MySQL&#xff1a; select NOW(); 示例&#xff1a; Oracle&#xff1a; select sysdate from dual; 示…