
# 配置CDM作业字段映射
#### 操作场景
- 作业参数配置完成后，将进行字段映射的配置，您可以通过字段映射界面的![](https://support.huaweicloud.com/usermanual-dataartsstudio/zh-cn_image_0000001513959606.png)可自定义新增字段，也可单击操作列下![](https://support.huaweicloud.com/usermanual-dataartsstudio/zh-cn_image_0000001565398977.png)创建字段转换器。
- 如果是文件类数据源（FTP/SFTP/HDFS/OBS）之间相互迁移数据，且源端"文件格式"配置为"二进制格式"（即不解析文件内容直接传输），则没有字段映射这一步骤。
- 其他场景下，CDM会自动匹配源端和目的端数据表字段，需用户检查字段映射关系和时间格式是否正确，例如：源字段类型是否可以转换为目的字段类型。
- 自动创表场景下，需在目的端表中提前手动新增字段，再在字段映射里新增字段。
 
#### 约束限制
- 作业源端开启"使用SQL语句"参数时不支持配置转换器。

- 如果在字段映射界面，CDM通过获取样值的方式无法获得所有列（例如从HBase/CloudTable/MongoDB导出数据时，CDM有较大概率无法获得所有列），则可以单击![](https://support.huaweicloud.com/usermanual-dataartsstudio/zh-cn_image_0000001570750941.png)后选择"添加新字段"来手动增加，确保导入到目的端的数据完整。
- 关系数据库、Hive、MRS Hudi及DLI做源端时，不支持获取样值功能。
- SQLServer作为目的端数据源时，不支持timestamp类型字段的写入，需修改为其他时间类型字段写入（如datetime）。
- 当作业源端为OBS、迁移CSV文件时，并且配置"解析首行为列名"参数的场景下显示列名。
- 当使用二进制格式进行文件到文件的迁移时，没有字段映射这一步。
- 自动创表场景下，需在目的端表中提前手动新增字段，再在字段映射里新增字段。
- 添加完字段后，新增的字段在界面不显示样值，不会影响字段值的传输，CDM会将字段值直接写入目的端。
- 如果字段映射关系不正确，您可以通过拖拽字段、单击![](https://support.huaweicloud.com/usermanual-dataartsstudio/zh-cn_image_0000001524443730.png)对字段批量映射两种方式来调整字段映射关系。
- 如果是导入到数据仓库服务（DWS），则还需在目的字段中选择分布列，建议按如下顺序选取分布列：
  1. 有主键可以使用主键作为分布列。
  
  2. 多个数据字段联合做主键的场景，建议设置所有主键作为分布列。
  
  3. 在没有主键的场景下，如果没有选择分布列，DWS会默认第一列作为分布列，可能会有数据倾斜风险。
   
- 如CDM不支持源端迁移字段类型，请参见[不支持数据类型转换规避指导](https://support.huaweicloud.com/usermanual-dataartsstudio/dataartsstudio_01_0209.html#dataartsstudio_01_0209)将字段类型转换为CDM支持的类型。

- Doris作为目的端时， 使用JDBC模式不支持Map、Array字段类型的写入。
 
#### 新增字段
您可以单击字段映射界面的![](https://support.huaweicloud.com/usermanual-dataartsstudio/zh-cn_image_0000001574803625.png)选择"添加新字段"自定义新增字段，通常用于标记数据库来源，以确保导入到目的端数据的完整性。
图1字段映射   
![](https://support.huaweicloud.com/usermanual-dataartsstudio/zh-cn_image_0000001526386420.png "点击放大")
目前支持以下类型自定义字段：
- **常量**
  常量参数即参数值是固定的参数，不需要重新配置值。例如"label"="friends"用来标识常量值。
  
- **变量**
  您可以使用时间宏、表名宏、版本宏等变量来标记数据库来源信息。变量的语法：${variable}，其中"variable"指的是变量。例如"input_time"="${timestamp()}"用来标识当前时间的时间戳。
  
- **表达式**
  您可以使用表达式语言根据运行环境动态生成参数值。表达式的语法：#{expr}，其中"expr"指的是表达式。例如"time"="#{DateUtil.now()}"用来标识当前日期字符串。
  
 
#### 新建转换器
CDM支持字段内容转换，如果需要可单击操作列下![](https://support.huaweicloud.com/usermanual-dataartsstudio/zh-cn_image_0000001570544877.png)，进入转换器列表界面，再单击"新建转换器"。
图2新建转换器   
![](https://support.huaweicloud.com/usermanual-dataartsstudio/zh-cn_image_0000001868589638.png "点击放大")
CDM可以在迁移过程中对字段进行转换，目前支持以下字段转换器：
- **脱敏**
  隐藏字符串中的关键信息，例如要将"12345678910"转换为"123\*\*\*\*8910"，则配置如下：
  - "起始保留长度"为"3"。
  
  - "结尾保留长度"为"4"。
  
  - "替换字符"为"\*"。
    
- **去前后空格**
  自动去字符串前后的空格，不需要配置参数。
  
- **字符串反转**
  自动反转字符串，例如将"ABC"转换为"CBA"，不需要配置参数。
  
- **字符串替换**
  替换字符串，需要用户配置被替换的对象，以及替换后的值。
  
- **去换行**
  将字段中的换行符（\\n、\\r、\\r\\n）删除。
  
- **表达式转换**
  数据进行转换过程中，替换内容包含特殊字符时，需要先使用\\将该字符转义成普通字符。
  - 表达式支持以下两个环境变量：
    - value：当前字段值。
    
    - row：当前行，数组类型。
     
  
  - 表达式支持的工具类用法罗列如下，未列出即表示不支持：
    1. 如果当前字段为字符串类型，将字符串全部转换为小写，例如将"aBC"转换为"abc"。 表达式：StringUtils.lowerCase(value)
       
    
    2. 将当前字段的字符串全部转为大写。 表达式：StringUtils.upperCase(value)
       
    
    3. 如果想将第1个日期字段格式从"2018-01-05 15:15:05"转换为"20180105"。 表达式：DateUtils.format(DateUtils.parseDate(row\[0\],"yyyy-MM-dd HH:mm:ss"),"yyyyMMdd")
       
    
    4. 如果想将**毫秒级** 时间戳转换成"yyyy-MM-dd HH:mm:ss"格式的日期字符串的类型，例如字段值为"1701312046588"，转换后为"2023-11-30 10:40:46"。
       当前字段为字符串类型，表达式：DateUtils.format(NumberUtils.toLong(value),"yyyy-MM-dd HH:mm:ss")
       当前字段为数值类型，表达式：DateUtils.format(value,"yyyy-MM-dd HH:mm:ss")
       
    
    5. 如果想将**秒级** 时间戳转换成"yyyy-MM-dd HH:mm:ss"格式的日期字符串的类型，例如字段值为"1701312046"，转换后为"2023-11-30 10:40:46"。
       当前字段为字符串类型，表达式：DateUtils.format(NumberUtils.toLong(value+"000"),"yyyy-MM-dd HH:mm:ss")
       当前字段为数值类型，表达式：DateUtils.format(value\*1000,"yyyy-MM-dd HH:mm:ss")
       
    
    6. 如果想将"yyyy-MM-dd HH:mm:ss"格式的日期字符串转换为毫秒时间戳，需先用DateUtils.parseDate(value, format)将字符串解析为Date对象，再用DateUtils.getTime()提取时间戳。 如果源端字段本身就是TIMESTAMP/DATE类型，直接使用DateUtils.getTime(value)即可。
       表达式：DateUtils.getTime(DateUtils.parseDate(value,"yyyy-MM-dd HH:mm:ss"))
       
    
    7. 如果当前字段值为"yyyy-MM-dd"格式的日期字符串，需要截取年，例如字段值为"2017-12-01"，转换后为"2017"。 表达式：StringUtils.substringBefore(value,"-")
       
    
    8. 如果当前字段值为数值类型，转换后值为当前值的两倍。 表达式：value\*2
       
    
    9. 如果当前字段值为"true"，转换后为"Y"，其它值则转换后为"N"。 表达式：value=="true"?"Y":"N"
       
    
    10. 如果当前字段值为字符串类型，当为空时，转换为"Default"，否则不转换。 表达式：empty value? "Default":value
        
    
    11. 如果想将日期字段格式从"2018/01/05 15:15:05"转换为"2018-01-05 15:15:05"。 表达式：DateUtils.format(DateUtils.parseDate(value,"yyyy/MM/dd HH:mm:ss"),"yyyy-MM-dd HH:mm:ss")
        
    
    12. 获取一个36位的UUID（Universally Unique Identifier，通用唯一识别码）。 表达式：CommonUtils.randomUUID()
        
    
    13. 如果当前字段值为字符串类型，将首字母转换为大写，例如将"cat"转换为"Cat"。 表达式：StringUtils.capitalize(value)
        
    
    14. 如果当前字段值为字符串类型，将首字母转换为小写，例如将"Cat"转换为"cat"。 表达式：StringUtils.uncapitalize(value)
        
    
    15. 如果当前字段值为字符串类型，使用空格填充为指定长度，并且将字符串居中，当字符串长度不小于指定长度时不转换，例如将"ab"转换为长度为4的"ab"。 表达式：StringUtils.center(value,*4*)
        
    
    16. 删除字符串末尾的一个换行符（包括"\\n"、"\\r"或者"\\r\\n"），例如将"abc\\r\\n\\r\\n"转换为"abc\\r\\n"。 表达式：StringUtils.chomp(value)
        
    
    17. 如果字符串中包含指定的字符串，则返回布尔值true，否则返回false。例如"abc"中包含"a"，则返回true。 表达式：StringUtils.contains(value,"*a*")
        
    
    18. 如果字符串中包含指定字符串的任一字符，则返回布尔值true，否则返回false。例如"zzabyycdxx"中包含"z"或"a"任意一个，则返回true。 表达式：StringUtils.containsAny(value,"*za*")
        
    
    19. 如果字符串中不包含指定的所有字符，则返回布尔值true，包含任意一个字符则返回false。例如"abz"中包含"xyz"里的任意一个字符，则返回false。 表达式：StringUtils.containsNone(value,"*xyz*")
        
    
    20. 如果当前字符串只包含指定字符串中的字符，则返回布尔值true，包含任意一个其它字符则返回false。例如"abab"只包含"abc"中的字符，则返回true。 表达式：StringUtils.containsOnly(value,"*abc*")
        
    
    21. 如果字符串为空或null，则转换为指定的字符串，否则不转换。例如将空字符转换为null。 表达式：StringUtils.defaultIfEmpty(value,*null*)
        
    
    22. 如果字符串以指定的后缀结尾（包括大小写），则返回布尔值true，否则返回false。例如"abcdef"后缀不为null，则返回false。 表达式：StringUtils.endsWith(value,*null*)
        
    
    23. 如果字符串和指定的字符串完全一样（包括大小写），则返回布尔值true，否则返回false。例如比较字符串"abc"和"ABC"，则返回false。 表达式：StringUtils.equals(value,"*ABC*")
        
    
    24. 从字符串中获取指定字符串的第一个索引，没有则返回整数-1。例如从"aabaabaa"中获取"ab"的第一个索引1。 表达式：StringUtils.indexOf(value,"*ab*")
        
    
    25. 从字符串中获取指定字符串的最后一个索引，没有则返回整数-1。例如从"aFkyk"中获取"k"的最后一个索引4。 表达式：StringUtils.lastIndexOf(value,"*k*")
        
    
    26. 从字符串中指定的位置往后查找，获取指定字符串的第一个索引，没有则转换为"-1"。例如"aabaabaa"中索引3的后面，第一个"b"的索引是5。 表达式：StringUtils.indexOf(value,"*b* ",*3*)
        
    
    27. 从字符串获取指定字符串中任一字符的第一个索引，没有则返回整数-1。例如从"zzabyycdxx"中获取"z"或"a"的第一个索引0。 表达式：StringUtils.indexOfAny(value,"*za*")
        
    
    28. 如果字符串仅包含Unicode字符，返回布尔值true，否则返回false。例如"ab2c"中包含非Unicode字符，返回false。 表达式：StringUtils.isAlpha(value)
        
    
    29. 如果字符串仅包含Unicode字符或数字，返回布尔值true，否则返回false。例如"ab2c"中仅包含Unicode字符和数字，返回true。 表达式：StringUtils.isAlphanumeric(value)
        
    
    30. 如果字符串仅包含Unicode字符、数字或空格，返回布尔值true，否则返回false。例如"ab2c"中仅包含Unicode字符和数字，返回true。 表达式：StringUtils.isAlphanumericSpace(value)
        
    
    31. 如果字符串仅包含Unicode字符或空格，返回布尔值true，否则返回false。例如"ab2c"中包含Unicode字符和数字，返回false。 表达式：StringUtils.isAlphaSpace(value)
        
    
    32. 如果字符串仅包含ASCII可打印字符，返回布尔值true，否则返回false。例如"!ab-c\~"返回true。 表达式：StringUtils.isAsciiPrintable(value)
        
    
    33. 如果字符串为空或null，返回布尔值true，否则返回false。 表达式：StringUtils.isEmpty(value)
        
    
    34. 如果字符串中仅包含Unicode数字，返回布尔值true，否则返回false。 表达式：StringUtils.isNumeric(value)
        
    
    35. 获取字符串最左端的指定长度的字符，例如获取"abc"最左端的2位字符"ab"。 表达式：StringUtils.left(value,*2*)
        
    
    36. 获取字符串最右端的指定长度的字符，例如获取"abc"最右端的2位字符"bc"。 表达式：StringUtils.right(value,*2*)
        
    
    37. 将指定字符串拼接至当前字符串的左侧，需同时指定拼接后的字符串长度，如果当前字符串长度不小于指定长度，则不转换。例如将"yz"拼接到"bat"左侧，拼接后长度为8，则转换后为"yzyzybat"。 表达式：StringUtils.leftPad(value,*8* ,"*yz*")
        
    
    38. 将指定字符串拼接至当前字符串的右侧，需同时指定拼接后的字符串长度，如果当前字符串长度不小于指定长度，则不转换。例如将"yz"拼接到"bat"右侧，拼接后长度为8，则转换后为"batyzyzy"。 表达式：StringUtils.rightPad(value,*8* ,"*yz*")
        
    
    39. 如果当前字段为字符串类型，获取当前字符串的长度，如果该字符串为null，则返回0。 表达式：StringUtils.length(value)
        
    
    40. 如果当前字段为字符串类型，删除其中所有的指定字符串，例如从"queued"中删除"ue"，转换后为"qd"。 表达式：StringUtils.remove(value,"*ue*")
        
    
    41. 如果当前字段为字符串类型，移除当前字段末尾指定的子字符串。指定的子字符串若不在当前字段的末尾，则不转换，例如移除当前字段"www.domain.com"后的".com"。 表达式：StringUtils.removeEnd(value,"*.com*")
        
    
    42. 如果当前字段为字符串类型，移除当前字段开头指定的子字符串。指定的子字符串若不在当前字段的开头，则不转换，例如移除当前字段"www.domain.com"前的"www."。 表达式：StringUtils.removeStart(value,"*www* *.*")
        
    
    43. 如果当前字段为字符串类型，替换当前字段中所有的指定字符串，例如将"aba"中的"a"用"z"替换，转换后为"zbz"。 表达式：StringUtils.replace(value,"*a* ","*z*")
        替换内容包含特殊字符时，需要先把该字符转义成普通字符，例如，客户想通过该表达式把字符串中 \\t 去掉时，需要配置为： StringUtils.replace(value,"\\\\t","")（即把 \\ 再次转义）。
        
    
    44. 如果当前字段为字符串类型，一次替换字符串中的多个字符，例如将字符串"hello"中的"h"用"j"替换，"o"用"y"替换，转换后为"jelly"。 表达式：StringUtils.replaceChars(value,"*ho* ","*jy*")
        
    
    45. 如果字符串以指定的前缀开头（区分大小写），则返回布尔值true，否则返回false，例如当前字符串"abcdef"以"abc"开头，则返回true。 表达式：StringUtils.startsWith(value,"*abc*")
        
    
    46. 如果当前字段为字符串类型，去除字段中首、尾处所有指定的字符，例如去除"abcyx"中首尾所有的"x"、"y"、"z"和"b"，转换后为"abc"。 表达式：StringUtils.strip(value,"*xyz*b")
        
    
    47. 如果当前字段为字符串类型，去除字段末尾所有指定的字符，例如去除当前字段末尾的"abc"字符串。 表达式：StringUtils.stripEnd(value,*"abc"*)
        
    
    48. 如果当前字段为字符串类型，去除字段开头所有指定的字符，例如去除当前字段开头的所有空格。 表达式：StringUtils.stripStart(value,*null*)
        
    
    49. 如果当前字段为字符串类型，获取字符串指定位置后（索引从0开始，包括指定位置的字符）的子字符串，指定位置如果为负数，则从末尾往前计算位置，末尾第一位为-1。例如获取"abcde"索引为2的字符（即c）及之后的字符串，则转换后为"cde"。 表达式：StringUtils.substring(value,*2*)
        
    
    50. 如果当前字段为字符串类型，获取字符串指定区间（索引从0开始，区间起点包括指定位置的字符，区间终点不包含指定位置的字符）的子字符串，区间位置如果为负数，则从末尾往前计算位置，末尾第一位为-1。例如获取"abcde"第2个字符（即c）及之后、第4个字符（即e）之前的字符串，则转换后为"cd"。 表达式：StringUtils.substring(value,*2*,4)
        
    
    51. 如果当前字段为字符串类型，获取当前字段里第一个指定字符后的子字符串。例如获取"abcba"中第一个"b"之后的子字符串，转换后为"cba"。 表达式：StringUtils.substringAfter(value,"*b*")
        
    
    52. 如果当前字段为字符串类型，获取当前字段里最后一个指定字符后的子字符串。例如获取"abcba"中最后一个"b"之后的子字符串，转换后为"a"。 表达式：StringUtils.substringAfterLast(value,"*b*")
        
    
    53. 如果当前字段为字符串类型，获取当前字段里第一个指定字符前的子字符串。例如获取"abcba"中第一个"b"之前的子字符串，转换后为"a"。 表达式：StringUtils.substringBefore(value,"*b*")
        
    
    54. 如果当前字段为字符串类型，获取当前字段里最后一个指定字符前的子字符串。例如获取"abcba"中最后一个"b"之前的子字符串，转换后为"abc"。 表达式：StringUtils.substringBeforeLast(value,"*b*")
        
    
    55. 如果当前字段为字符串类型，获取嵌套在指定字符串之间的子字符串，没有匹配的则返回null。例如获取"tagabctag"中"tag"之间的子字符串，转换后为"abc"。 表达式：StringUtils.substringBetween(value,"*tag*")
        
    
    56. 如果当前字段为字符串类型，删除当前字符串两端的控制字符（char≤32），例如删除字符串前后的空格。 表达式：StringUtils.trim(value)
        
    
    57. 将当前字符串转换为字节，如果转换失败，则返回0。 表达式：NumberUtils.toByte(value)
        
    
    58. 将当前字符串转换为字节，如果转换失败，则返回指定值，例如指定值配置为1。 表达式：NumberUtils.toByte(value,*1*)
        
    
    59. 将当前字符串转换为Double数值，如果转换失败，则返回0.0d。 表达式：NumberUtils.toDouble(value)
        
    
    60. 将当前字符串转换为Double数值，如果转换失败，则返回指定值，例如指定值配置为1.1d。 表达式：NumberUtils.toDouble(value,*1.1d*)
        
    
    61. 将当前字符串转换为Float数值，如果转换失败，则返回0.0f。 表达式：NumberUtils.toFloat(value)
        
    
    62. 将当前字符串转换为Float数值，如果转换失败，则返回指定值，例如配置指定值为1.1f。 表达式：NumberUtils.toFloat(value,*1.1f*)
        
    
    63. 将当前字符串转换为Int数值，如果转换失败，则返回0。 表达式：NumberUtils.toInt(value)
        
    
    64. 将当前字符串转换为Int数值，如果转换失败，则返回指定值，例如配置指定值为1。 表达式：NumberUtils.toInt(value,*1*)
        
    
    65. 将字符串转换为Long数值，如果转换失败，则返回0。 表达式：NumberUtils.toLong(value)
        
    
    66. 将当前字符串转换为Long数值，如果转换失败，则返回指定值，例如配置指定值为1L。 表达式：NumberUtils.toLong(value,*1L*)
        
    
    67. 将字符串转换为Short数值，如果转换失败，则返回0。 表达式：NumberUtils.toShort(value)
        
    
    68. 将当前字符串转换为Short数值，如果转换失败，则返回指定值，例如配置指定值为1。 表达式：NumberUtils.toShort(value,*1*)
        
    
    69. 将当前IP字符串转换为Long数值，例如将"10.78.124.0"转换为Long数值是"172915712"。 表达式：CommonUtils.ipToLong(value)
        
    
    70. 从网络读取一个IP与物理地址映射文件，并存放到Map集合，这里的URL是IP与地址映射文件存放地址，例如"http://10.114.205.45:21203/sqoop/IpList.csv"。 表达式：HttpsUtils.downloadMap("*url*")
        
    
    71. 将IP与地址映射对象缓存起来并指定一个key值用于检索，例如"ipList"。 表达式：CommonUtils.setCache("*ipList* ",HttpsUtils.downloadMap("*url*"))
        
    
    72. 取出缓存的IP与地址映射对象。 表达式：CommonUtils.getCache("*ipList*")
        
    
    73. 判断是否有IP与地址映射缓存。 表达式：CommonUtils.cacheExists("*ipList*")
        
    
    74. 根据IP取出对应的详细地址：国家_省份_城市_运营商，例如"1xx.78.124.0"对应的地址为"中国_广东_深圳_电信"，取不到对应地址则默认"\*\*_\*\*_\*\*_\*\*"。如果需要，可通过StringUtil类表达式对地址进行进一步拆分。 表达式：CommonUtils.getMapValue(CommonUtils.ipToLong(value),CommonUtils.cacheExists("*ipLis* t")?CommonUtils.getCache("*ipList* "):CommonUtils.setCache("*ipLis* t",HttpsUtils.downloadMap("*url*")))
        
    
    75. 根据指定的偏移类型（month/day/hour/minute/second）及偏移量（正数表示增加，负数表示减少），将指定格式的时间转换为一个新时间，例如将"2019-05-21 12:00:00"增加8个小时。 表达式：DateUtils.getCurrentTimeByZone("*yyyy-MM-dd HH:mm:ss* ",value, "*hour* ", *8*)
        
    
    76. 如果value值为空或者null时，则返回字符串"aaa"，否则返回value。 表达式：StringUtils.defaultIfEmpty(value,"aaa")
        
     
   
 
#### 特殊链路说明
- 当源端为DLI，目的端为DWS时，DLI的tinyint类型字段映射为DWS的smallint类型字段。
- 当源端为Hudi，目的端为DWS时，Hudi的Double类型字段映射为DWS的Float类型字段。
 
