ODPS/MaxComputer 自定义UDF、UDTF函数

发布时间 2023-09-13 16:07:44作者: zhuzhu&you
ODPS/MaxComputer 自定义UDF、UDTF函数
  • 前置条件:
    • 创建Maven工程导入jar包和打包工具:
<dependency>
            <groupId>com.aliyun.odps</groupId>
            <artifactId>odps-sdk-udf</artifactId>
            <version>0.29.10-public</version>
</dependency>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.xxxx.comment.ODPSDate_ForMat</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
  • ODPS 自定义UDF:
    • 实现MySQL  DATE_FORMAT()函数功能
public class ODPSDate_ForMat extends UDF {
    public static void main(String[] args) {
        System.out.println(new ODPSDate_ForMat().evaluate("2023-07", "yyyy-MM-dd hh:mm:ss", "false"));
    }

    public String evaluate(String timeStr,String outTimeType, String ifNullGetNow) {
        return dateformat(outTimeType,timeStr,Boolean.valueOf(ifNullGetNow));
    }


    /**
     * 传入日期格式和日期,返回目标日期String
     * @description 日期格式转换,从一种string日期格式转换成目标string日期格式
     * @param pattern 需要转换的日期格式
     * @param date 需要转换的日期 如"2005-11:07.11/22:55" "2005-11:07.11/22:55" "2005-11-07"
     * @param ifNullGetNow 如果为空或空串,true获取当前时间的需要格式,如果为false,返回date
     * @return String 目标字符串日期格式
     */
    public static String dateformat(String pattern,Object date, boolean ifNullGetNow) {
        if(date == null || "".equals(date)) {
            if (ifNullGetNow) {
                return new SimpleDateFormat(pattern).format(new Date());
            } else {
                return "";
            }
        }
        String dateStr = date.toString();
        //将非日期的分隔符去掉
        dateStr = dateStr.replace("-", "").replace(":", "").replace(" ", "").replace("/", "").replace(".", "");
        //yyyyMMddHHmmss 使用这个14位的作为标准格式
        int length = 14 - dateStr.length();
        // 不足14位补0
        if(length > 0) {
            dateStr = dateStr + String.format("%0" + length + "d", 0);
        }
        Date temp;
        try {
            temp = new SimpleDateFormat("yyyyMMddHHmmss").parse(dateStr);//转换成yyyyMMddHHmmss统一格式,拿到Date格式
            return new SimpleDateFormat(pattern).format(temp);//把Date格式再格式化为pattern格式的字符串日期
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }
}
  • ODPS自定义UDAF函数:
    • 实现需求:列转行
    • 需求描述:现有一个HTML富文本字段,需要提取HTML中的所有a标签中的href中的文件路径。
    • 实现方法:工具类
package com.cccc.tools;

import com.cccc.bean.HtmlAlabelBean;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public enum RegexAnalysis {
    /*
        解析字符串中所有的<a href="upload/...">测试</a>标签中的href,text
     */
    html_analysis("<\\s*a\\s+([\\w-\\.]+\\s*=\\s*[\"][:/\\.?'();,%&!#|=\\w-\\u4e00-\\u9fa5\\s]*[\"]\\s*)*\\s*href\\s*=\\s*[\"](.?.*upload.*)[\"]\\s*([\\w-\\.]+\\s*=\\s*[\"][:/\\.?'();,%&!#|=\\w-\\u4e00-\\u9fa5\\s]*[\"]\\s*)*\\s*>(.+?)</\\s*a\\s*>?");

    private String regex_string;

    public static  List<HtmlAlabelBean> printMatchInfo(String content, String regex) {
        //TODO: 存储正则取出来的数据对象
        List<HtmlAlabelBean> htmlAlabelBeans=new ArrayList<>();
        Pattern p = Pattern.compile(regex);
        Matcher m = p.matcher(content);
        HtmlAlabelBean htmlAlabelBean=null;
        while (m.find()) {
            //path
            String filePath=m.group(2);
            //filename
            String fileName=m.group(4);
            htmlAlabelBeans.add(new HtmlAlabelBean(fileName,filePath));

        }
        return htmlAlabelBeans;
    }

    RegexAnalysis(String name){
        regex_string = name;
    }

    public String getRegexString(){
        return regex_string;
    }
}
    •  Bean实体类:
package com.***.bean;

/**
 * @ClassName: HtmlAlableBean
 * @PageName: com.***.bean
 * @Author: 东霖
 * @CreateTime: 2023/9/13 15:39
 * @Description: TODO
 **/
public class HtmlAlabelBean {
    private String fileName;
    private String filePath;

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public String getFilePath() {
        return filePath;
    }

    public void setFilePath(String filePath) {
        this.filePath = filePath;
    }

    public HtmlAlabelBean(String fileName, String filePath) {
        this.fileName = fileName;
        this.filePath = filePath;
    }
}
    • UDF实现类:
package com.ccc.comment;

import com.aliyun.odps.udf.ExecutionContext;
import com.aliyun.odps.udf.UDFException;
import com.aliyun.odps.udf.UDTF;
import com.aliyun.odps.udf.annotation.Resolve;
import com.dtdream.bean.HtmlAlabelBean;
import com.dtdream.tools.RegexAnalysis;
import org.apache.log4j.Logger;

import java.io.IOException;
import java.util.List;

/**
 * @ClassName: AnalysisHtmlContent
 * @PageName: com.cccc.comment
 * @Author: 东霖
 * @CreateTime: 2023/9/12 17:46
 * @Description: TODO UDTF 一进多出UDF函数
 **/
// 入参类型->返回参数
@Resolve({"string->string,string"})
public class AnalysisHtmlContent extends UDTF {

    private static Logger logger = Logger.getLogger(AnalysisHtmlContent.class);

    /**
     * TODO:初始化方法,在UDTF处理输入数据前,调用用户自定义的初始化行为。在每个Worker内setup会被先调用一次。
     *
     * @param ctx
     * @throws UDFException
     */
    @Override
    public void setup(ExecutionContext ctx) throws UDFException {
    }

    /**
     * TODO:这个方法由框架调用,SQL中每一条记录都会对应调用一次process,process的参数为SQL语句中指定的UDTF输入参数。输入参数以Object[]的形式传入,输出结果通过forward函数输出。用户需要在process函数内自行调用forward,以决定输出数据。
     *
     * @param args
     * @throws UDFException
     * @throws IOException
     */
    @Override
    public void process(Object[] args) throws UDFException, IOException {
        logger.debug("start 进入数据处理......");
        logger.debug("入参2为:%s"+(String)args[1]);
        List<HtmlAlabelBean> htmlAlabelBeans = null;
        // TODO: 接受传入进来的html_content
        String html_content = (String) args[0];
        //TODO: 调用正则表达式取数据,默认取参数传入进来的正则如果不传入则传入默认值: false
        String regex_string = (String) args[1];
        //TODO: 判断是否传入正则
        if (regex_string.equals("false")) {
            //TODO: 如果没有传入则用自带模板正则
            String regexString = RegexAnalysis.html_analysis.getRegexString();
            htmlAlabelBeans = RegexAnalysis.printMatchInfo(html_content, regexString);
        }else{
            htmlAlabelBeans = RegexAnalysis.printMatchInfo(html_content, regex_string);
        }
        for (int i = 0; i < htmlAlabelBeans.size(); i++) {
            HtmlAlabelBean htmlAlabelBean = htmlAlabelBeans.get(i);
            // 返回的数据 注意输出字段类型
            forward(htmlAlabelBean.getFileName(),htmlAlabelBean.getFilePath());
        }
    }

    /**
     * TODO:UDTF的结束方法,此方法由框架调用,并且只会被调用一次,即在处理完最后一条记录之后。
     *
     * @throws UDFException
     */
    @Override
    public void close() throws UDFException {

    }
}

     打包上传至ODPS: