POI读取excel百万级-SAX方式解析

Myth丶恋晨 2022-04-03 09:13 287阅读 0赞

一. 简介

  1. excel解析的时候,采用SAX方方式会将excel转换为xml进行解析避免了内存溢出。
  2. 速度在31W的数据写入,100W条记录,大概50M的数据,耗时大概4分半(如果不需要校验,可能会更快);
  3. 暂时先直接将项目中的拷贝出来,使用的时候直接调工具类即可。目前正在搞自己的一个项目,后期会将导入,导出都弄上去,再优化下,放到git上。
  4. 另外,膜拜下原生jdbc, 昨天问了下,听说可以达到110W数量级的写入;
  5. 是在网上找的一个,然后自己封装了下, 加了一个委托,目前存在一个BUG 就是如果excel中没有数据,会自动跳过该空格。目前解决方案是:excel中为空的使用“-”来标识,后期解决。
  6. 关于去重的,请看最下面的案例说明。
  7. 注: 这个中间变量处理的并不完善 可以参考博主的另一篇博客
  8. https://blog.csdn.net/qq\_35206261/article/details/88579151
  9. 里面的变量边界处理的比较完善, 这个暂时没有处理,因为有点缺陷,准备换为阿里的easyExcel的导入方式。待处理

二. 代码DEMO

2.1 POM依赖

  1. <!-- poi -->
  2. <dependency>
  3. <groupId>org.apache.poi</groupId>
  4. <artifactId>poi</artifactId>
  5. <version>3.17</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.apache.poi</groupId>
  9. <artifactId>poi-ooxml</artifactId>
  10. <version>3.17</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.apache.poi</groupId>
  14. <artifactId>poi-ooxml-schemas</artifactId>
  15. <version>3.17</version>
  16. </dependency>
  17. <!-- sax -->
  18. <dependency>
  19. <groupId>sax</groupId>
  20. <artifactId>sax</artifactId>
  21. <version>2.0.1</version>
  22. </dependency>
  23. <dependency>
  24. <groupId>xml-apis</groupId>
  25. <artifactId>xml-apis</artifactId>
  26. <version>1.4.01</version>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.apache.xmlbeans</groupId>
  30. <artifactId>xmlbeans</artifactId>
  31. <version>2.6.0</version>
  32. </dependency>
  33. <dependency>
  34. <groupId>xerces</groupId>
  35. <artifactId>xercesImpl</artifactId>
  36. <version>2.11.0</version>
  37. </dependency>

2.2 EXCEL常量类

  1. package com.yzx.osp.common.constant;
  2. /**
  3. * @author qjwyss
  4. * @date 2018/9/19
  5. * @description EXCEL常量类
  6. */
  7. public class ExcelConstant {
  8. /**
  9. * excel2007扩展名
  10. */
  11. public static final String EXCEL07_EXTENSION = ".xlsx";
  12. /**
  13. * 每个sheet存储的记录数 100W
  14. */
  15. public static final Integer PER_SHEET_ROW_COUNT = 1000000;
  16. /**
  17. * 每次向EXCEL写入的记录数(查询每页数据大小) 20W
  18. */
  19. public static final Integer PER_WRITE_ROW_COUNT = 200000;
  20. /**
  21. * 每个sheet的写入次数 5
  22. */
  23. public static final Integer PER_SHEET_WRITE_COUNT = PER_SHEET_ROW_COUNT / PER_WRITE_ROW_COUNT;
  24. /**
  25. * 读取excel的时候每次批量插入数据库记录数
  26. */
  27. public static final Integer PER_READ_INSERT_BATCH_COUNT = 10000;
  28. }

2.3 读取EXCEL辅助类

  1. package com.yzx.osp.common.util;
  2. import org.apache.poi.openxml4j.opc.OPCPackage;
  3. import org.apache.poi.ss.usermodel.BuiltinFormats;
  4. import org.apache.poi.ss.usermodel.DataFormatter;
  5. import org.apache.poi.xssf.eventusermodel.XSSFReader;
  6. import org.apache.poi.xssf.model.SharedStringsTable;
  7. import org.apache.poi.xssf.model.StylesTable;
  8. import org.apache.poi.xssf.usermodel.XSSFCellStyle;
  9. import org.apache.poi.xssf.usermodel.XSSFRichTextString;
  10. import org.xml.sax.Attributes;
  11. import org.xml.sax.InputSource;
  12. import org.xml.sax.SAXException;
  13. import org.xml.sax.XMLReader;
  14. import org.xml.sax.helpers.DefaultHandler;
  15. import org.xml.sax.helpers.XMLReaderFactory;
  16. import java.io.InputStream;
  17. import java.util.ArrayList;
  18. import java.util.List;
  19. /**
  20. * @author qjwyss
  21. * @date 2018/12/19
  22. * @description 读取EXCEL辅助类
  23. */
  24. public class ExcelXlsxReaderWithDefaultHandler extends DefaultHandler {
  25. private ExcelReadDataDelegated excelReadDataDelegated;
  26. public ExcelReadDataDelegated getExcelReadDataDelegated() {
  27. return excelReadDataDelegated;
  28. }
  29. public void setExcelReadDataDelegated(ExcelReadDataDelegated excelReadDataDelegated) {
  30. this.excelReadDataDelegated = excelReadDataDelegated;
  31. }
  32. public ExcelXlsxReaderWithDefaultHandler(ExcelReadDataDelegated excelReadDataDelegated) {
  33. this.excelReadDataDelegated = excelReadDataDelegated;
  34. }
  35. /**
  36. * 单元格中的数据可能的数据类型
  37. */
  38. enum CellDataType {
  39. BOOL, ERROR, FORMULA, INLINESTR, SSTINDEX, NUMBER, DATE, NULL
  40. }
  41. /**
  42. * 共享字符串表
  43. */
  44. private SharedStringsTable sst;
  45. /**
  46. * 上一次的索引值
  47. */
  48. private String lastIndex;
  49. /**
  50. * 文件的绝对路径
  51. */
  52. private String filePath = "";
  53. /**
  54. * 工作表索引
  55. */
  56. private int sheetIndex = 0;
  57. /**
  58. * sheet名
  59. */
  60. private String sheetName = "";
  61. /**
  62. * 总行数
  63. */
  64. private int totalRows = 0;
  65. /**
  66. * 一行内cell集合
  67. */
  68. private List<String> cellList = new ArrayList<String>();
  69. /**
  70. * 判断整行是否为空行的标记
  71. */
  72. private boolean flag = false;
  73. /**
  74. * 当前行
  75. */
  76. private int curRow = 1;
  77. /**
  78. * 当前列
  79. */
  80. private int curCol = 0;
  81. /**
  82. * T元素标识
  83. */
  84. private boolean isTElement;
  85. /**
  86. * 异常信息,如果为空则表示没有异常
  87. */
  88. private String exceptionMessage;
  89. /**
  90. * 单元格数据类型,默认为字符串类型
  91. */
  92. private CellDataType nextDataType = CellDataType.SSTINDEX;
  93. private final DataFormatter formatter = new DataFormatter();
  94. /**
  95. * 单元格日期格式的索引
  96. */
  97. private short formatIndex;
  98. /**
  99. * 日期格式字符串
  100. */
  101. private String formatString;
  102. //定义前一个元素和当前元素的位置,用来计算其中空的单元格数量,如A6和A8等
  103. private String preRef = null, ref = null;
  104. //定义该文档一行最大的单元格数,用来补全一行最后可能缺失的单元格
  105. private String maxRef = null;
  106. /**
  107. * 单元格
  108. */
  109. private StylesTable stylesTable;
  110. /**
  111. * 总行号
  112. */
  113. private Integer totalRowCount;
  114. /**
  115. * 遍历工作簿中所有的电子表格
  116. * 并缓存在mySheetList中
  117. *
  118. * @param filename
  119. * @throws Exception
  120. */
  121. public int process(String filename) throws Exception {
  122. filePath = filename;
  123. OPCPackage pkg = OPCPackage.open(filename);
  124. XSSFReader xssfReader = new XSSFReader(pkg);
  125. stylesTable = xssfReader.getStylesTable();
  126. SharedStringsTable sst = xssfReader.getSharedStringsTable();
  127. XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
  128. this.sst = sst;
  129. parser.setContentHandler(this);
  130. XSSFReader.SheetIterator sheets = (XSSFReader.SheetIterator) xssfReader.getSheetsData();
  131. while (sheets.hasNext()) { //遍历sheet
  132. curRow = 1; //标记初始行为第一行
  133. sheetIndex++;
  134. InputStream sheet = sheets.next(); //sheets.next()和sheets.getSheetName()不能换位置,否则sheetName报错
  135. sheetName = sheets.getSheetName();
  136. InputSource sheetSource = new InputSource(sheet);
  137. parser.parse(sheetSource); //解析excel的每条记录,在这个过程中startElement()、characters()、endElement()这三个函数会依次执行
  138. sheet.close();
  139. }
  140. return totalRows; //返回该excel文件的总行数,不包括首列和空行
  141. }
  142. /**
  143. * 第一个执行
  144. *
  145. * @param uri
  146. * @param localName
  147. * @param name
  148. * @param attributes
  149. * @throws SAXException
  150. */
  151. @Override
  152. public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
  153. // 获取总行号 格式: A1:B5 取最后一个值即可
  154. if("dimension".equals(name)) {
  155. String dimensionStr = attributes.getValue("ref");
  156. totalRowCount = Integer.parseInt(dimensionStr.substring(dimensionStr.indexOf(":") + 2)) - 1;
  157. }
  158. //c => 单元格
  159. if ("c".equals(name)) {
  160. //前一个单元格的位置
  161. if (preRef == null) {
  162. preRef = attributes.getValue("r");
  163. } else {
  164. preRef = ref;
  165. }
  166. //当前单元格的位置
  167. ref = attributes.getValue("r");
  168. //设定单元格类型
  169. this.setNextDataType(attributes);
  170. }
  171. //当元素为t时
  172. if ("t".equals(name)) {
  173. isTElement = true;
  174. } else {
  175. isTElement = false;
  176. }
  177. //置空
  178. lastIndex = "";
  179. }
  180. /**
  181. * 第二个执行
  182. * 得到单元格对应的索引值或是内容值
  183. * 如果单元格类型是字符串、INLINESTR、数字、日期,lastIndex则是索引值
  184. * 如果单元格类型是布尔值、错误、公式,lastIndex则是内容值
  185. *
  186. * @param ch
  187. * @param start
  188. * @param length
  189. * @throws SAXException
  190. */
  191. @Override
  192. public void characters(char[] ch, int start, int length) throws SAXException {
  193. lastIndex += new String(ch, start, length);
  194. }
  195. /**
  196. * 第三个执行
  197. *
  198. * @param uri
  199. * @param localName
  200. * @param name
  201. * @throws SAXException
  202. */
  203. @Override
  204. public void endElement(String uri, String localName, String name) throws SAXException {
  205. //t元素也包含字符串
  206. if (isTElement) {//这个程序没经过
  207. //将单元格内容加入rowlist中,在这之前先去掉字符串前后的空白符
  208. String value = lastIndex.trim();
  209. cellList.add(curCol, value);
  210. curCol++;
  211. isTElement = false;
  212. //如果里面某个单元格含有值,则标识该行不为空行
  213. if (value != null && !"".equals(value)) {
  214. flag = true;
  215. }
  216. } else if ("v".equals(name)) {
  217. //v => 单元格的值,如果单元格是字符串,则v标签的值为该字符串在SST中的索引
  218. String value = this.getDataValue(lastIndex.trim(), "");//根据索引值获取对应的单元格值
  219. //补全单元格之间的空单元格
  220. if (!ref.equals(preRef)) {
  221. int len = countNullCell(ref, preRef);
  222. for (int i = 0; i < len; i++) {
  223. cellList.add(curCol, "");
  224. curCol++;
  225. }
  226. }
  227. cellList.add(curCol, value);
  228. curCol++;
  229. //如果里面某个单元格含有值,则标识该行不为空行
  230. if (value != null && !"".equals(value)) {
  231. flag = true;
  232. }
  233. } else {
  234. //如果标签名称为row,这说明已到行尾,调用optRows()方法
  235. if ("row".equals(name)) {
  236. //默认第一行为表头,以该行单元格数目为最大数目
  237. if (curRow == 1) {
  238. maxRef = ref;
  239. }
  240. //补全一行尾部可能缺失的单元格
  241. if (maxRef != null) {
  242. int len = countNullCell(maxRef, ref);
  243. for (int i = 0; i <= len; i++) {
  244. cellList.add(curCol, "");
  245. curCol++;
  246. }
  247. }
  248. if (flag && curRow != 1) { //该行不为空行且该行不是第一行,则发送(第一行为列名,不需要)
  249. // 调用excel读数据委托类进行读取插入操作
  250. excelReadDataDelegated.readExcelDate(sheetIndex, totalRowCount, curRow, cellList);
  251. totalRows++;
  252. }
  253. cellList.clear();
  254. curRow++;
  255. curCol = 0;
  256. preRef = null;
  257. ref = null;
  258. flag = false;
  259. }
  260. }
  261. }
  262. /**
  263. * 处理数据类型
  264. *
  265. * @param attributes
  266. */
  267. public void setNextDataType(Attributes attributes) {
  268. nextDataType = CellDataType.NUMBER; //cellType为空,则表示该单元格类型为数字
  269. formatIndex = -1;
  270. formatString = null;
  271. String cellType = attributes.getValue("t"); //单元格类型
  272. String cellStyleStr = attributes.getValue("s"); //
  273. String columnData = attributes.getValue("r"); //获取单元格的位置,如A1,B1
  274. if ("b".equals(cellType)) { //处理布尔值
  275. nextDataType = CellDataType.BOOL;
  276. } else if ("e".equals(cellType)) { //处理错误
  277. nextDataType = CellDataType.ERROR;
  278. } else if ("inlineStr".equals(cellType)) {
  279. nextDataType = CellDataType.INLINESTR;
  280. } else if ("s".equals(cellType)) { //处理字符串
  281. nextDataType = CellDataType.SSTINDEX;
  282. } else if ("str".equals(cellType)) {
  283. nextDataType = CellDataType.FORMULA;
  284. }
  285. if (cellStyleStr != null) { //处理日期
  286. int styleIndex = Integer.parseInt(cellStyleStr);
  287. XSSFCellStyle style = stylesTable.getStyleAt(styleIndex);
  288. formatIndex = style.getDataFormat();
  289. formatString = style.getDataFormatString();
  290. if (formatString.contains("m/d/yy") || formatString.contains("yyyy/mm/dd") || formatString.contains("yyyy/m/d")) {
  291. nextDataType = CellDataType.DATE;
  292. formatString = "yyyy-MM-dd hh:mm:ss";
  293. }
  294. if (formatString == null) {
  295. nextDataType = CellDataType.NULL;
  296. formatString = BuiltinFormats.getBuiltinFormat(formatIndex);
  297. }
  298. }
  299. }
  300. /**
  301. * 对解析出来的数据进行类型处理
  302. *
  303. * @param value 单元格的值,
  304. * value代表解析:BOOL的为0或1, ERROR的为内容值,FORMULA的为内容值,INLINESTR的为索引值需转换为内容值,
  305. * SSTINDEX的为索引值需转换为内容值, NUMBER为内容值,DATE为内容值
  306. * @param thisStr 一个空字符串
  307. * @return
  308. */
  309. @SuppressWarnings("deprecation")
  310. public String getDataValue(String value, String thisStr) {
  311. switch (nextDataType) {
  312. // 这几个的顺序不能随便交换,交换了很可能会导致数据错误
  313. case BOOL: //布尔值
  314. char first = value.charAt(0);
  315. thisStr = first == '0' ? "FALSE" : "TRUE";
  316. break;
  317. case ERROR: //错误
  318. thisStr = "\"ERROR:" + value.toString() + '"';
  319. break;
  320. case FORMULA: //公式
  321. thisStr = '"' + value.toString() + '"';
  322. break;
  323. case INLINESTR:
  324. XSSFRichTextString rtsi = new XSSFRichTextString(value.toString());
  325. thisStr = rtsi.toString();
  326. rtsi = null;
  327. break;
  328. case SSTINDEX: //字符串
  329. String sstIndex = value.toString();
  330. try {
  331. int idx = Integer.parseInt(sstIndex);
  332. XSSFRichTextString rtss = new XSSFRichTextString(sst.getEntryAt(idx));//根据idx索引值获取内容值
  333. thisStr = rtss.toString();
  334. rtss = null;
  335. } catch (NumberFormatException ex) {
  336. thisStr = value.toString();
  337. }
  338. break;
  339. case NUMBER: //数字
  340. if (formatString != null) {
  341. thisStr = formatter.formatRawCellContents(Double.parseDouble(value), formatIndex, formatString).trim();
  342. } else {
  343. thisStr = value;
  344. }
  345. thisStr = thisStr.replace("_", "").trim();
  346. break;
  347. case DATE: //日期
  348. thisStr = formatter.formatRawCellContents(Double.parseDouble(value), formatIndex, formatString);
  349. // 对日期字符串作特殊处理,去掉T
  350. thisStr = thisStr.replace("T", " ");
  351. break;
  352. default:
  353. thisStr = " ";
  354. break;
  355. }
  356. return thisStr;
  357. }
  358. public int countNullCell(String ref, String preRef) {
  359. //excel2007最大行数是1048576,最大列数是16384,最后一列列名是XFD
  360. String xfd = ref.replaceAll("\\d+", "");
  361. String xfd_1 = preRef.replaceAll("\\d+", "");
  362. xfd = fillChar(xfd, 3, '@', true);
  363. xfd_1 = fillChar(xfd_1, 3, '@', true);
  364. char[] letter = xfd.toCharArray();
  365. char[] letter_1 = xfd_1.toCharArray();
  366. int res = (letter[0] - letter_1[0]) * 26 * 26 + (letter[1] - letter_1[1]) * 26 + (letter[2] - letter_1[2]);
  367. return res - 1;
  368. }
  369. public String fillChar(String str, int len, char let, boolean isPre) {
  370. int len_1 = str.length();
  371. if (len_1 < len) {
  372. if (isPre) {
  373. for (int i = 0; i < (len - len_1); i++) {
  374. str = let + str;
  375. }
  376. } else {
  377. for (int i = 0; i < (len - len_1); i++) {
  378. str = str + let;
  379. }
  380. }
  381. }
  382. return str;
  383. }
  384. }

注: 此处使用了setter、getter、构造函数的方式将写数据委托接口注入进去,想了半天。

2.4 写数据委托接口

  1. package com.yzx.osp.common.util;
  2. import java.util.List;
  3. /**
  4. * @author qjwyss
  5. * @date 2018/12/19
  6. * @description 读取excel数据委托接口
  7. */
  8. public interface ExcelReadDataDelegated {
  9. /**
  10. * 每获取一条记录,即写数据
  11. * 在flume里每获取一条记录即写,而不必缓存起来,可以大大减少内存的消耗,这里主要是针对flume读取大数据量excel来说的
  12. *
  13. * @param sheetIndex sheet位置
  14. * @param totalRowCount 该sheet总行数
  15. * @param curRow 行号
  16. * @param cellList 行数据
  17. */
  18. public abstract void readExcelDate(int sheetIndex, int totalRowCount, int curRow, List<String> cellList);
  19. }

2.5 读取工具类

  1. package com.yzx.osp.common.util;
  2. import com.yzx.osp.common.constant.ExcelConstant;
  3. import java.util.List;
  4. /**
  5. * @author qjwyss
  6. * @date 2018/12/19
  7. * @description 读取EXCEL工具类
  8. */
  9. public class ExcelReaderUtil {
  10. public static void readExcel(String filePath, ExcelReadDataDelegated excelReadDataDelegated) throws Exception {
  11. int totalRows = 0;
  12. if (filePath.endsWith(ExcelConstant.EXCEL07_EXTENSION)) {
  13. ExcelXlsxReaderWithDefaultHandler excelXlsxReader = new ExcelXlsxReaderWithDefaultHandler(excelReadDataDelegated);
  14. totalRows = excelXlsxReader.process(filePath);
  15. } else {
  16. throw new Exception("文件格式错误,fileName的扩展名只能是xlsx!");
  17. }
  18. System.out.println("读取的数据总行数:" + totalRows);
  19. }
  20. public static void main(String[] args) throws Exception {
  21. String path = "E:\\temp\\5.xlsx";
  22. ExcelReaderUtil.readExcel(path, new ExcelReadDataDelegated() {
  23. @Override
  24. public void readExcelDate(int sheetIndex, int totalRowCount, int curRow, List<String> cellList) {
  25. System.out.println("总行数为:" + totalRowCount + " 行号为:" + curRow + " 数据:" + cellList);
  26. }
  27. });
  28. }
  29. }

注: 此处可以直接使用,将sheet索引、总行数、当前行号、当前行记录打印出来,结果如下:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1MjA2MjYx_size_16_color_FFFFFF_t_70

三. 使用案例

3.1 特殊说明

  1. 刚开始进行导入的 时候,因为每条记录要进行三个字段的校验(是否存在)和转换(名称要转换为对应的数据库ID),刚开始的做法是 读取一条记录,然后发3次查询sql进行校验,然后再发一次插入sql进行保存。然后悲剧了,测试导入1W条数据要好几分钟。 第二天早上想来突然想到优化方案, 瓶颈在于频繁发送sql语句。然后就在开始导入之前,将需要校验的记录先查询出来放到map中,然后每次遍历的时候直接从map中取出来进行校验,不必发sql了; 还有就是批量保存;

3.2 使用案例

  1. 忙,没时间修改,直接将代码拷贝上去了,自己用的时候直接调用工具类即可。
  2. @Override
  3. public ResultVO<Void> importMobileManagerList(String filePath) throws Exception {
  4. logger.info("开始导入号码列表:" + DateUtil.formatDate(new Date(), DateUtil.YYYY_MM_DD_HH_MM_SS));
  5. List<Integer> errorRowNumber = new ArrayList<>();
  6. List<MobileManager> mobileManagerList = new ArrayList<>();
  7. MobileManagerVO mobileManagerVO = new MobileManagerVO();
  8. List<String> mobileList = this.mobileManagerMapper.selectMobileList(mobileManagerVO);
  9. SysAreaVO sysAreaVO = new SysAreaVO();
  10. List<SysAreaVO> sysAreaVOList = this.sysAreaMapper.selectSysAreaVOList(sysAreaVO);
  11. CustomerVO customerVO = new CustomerVO();
  12. List<String> customerIdList = this.customerDao.selectLineOrBusinessCustomerIdList(customerVO);
  13. List<CustomerVO> customerVOList = this.customerDao.selectCustomerIdAndAccountIdList(customerVO);
  14. // 用来存储EXCEL中的号码集合 用来去重
  15. List<String> excelMobileList = new ArrayList<>();
  16. ExcelReaderUtil.readExcel(filePath, new ExcelReadDataDelegated() {
  17. @Override
  18. public void readExcelDate(int sheetIndex, int totalRowCount, int curRow, List<String> cellList) {
  19. // 校验数据合法性
  20. Boolean legalFlag = true;
  21. Integer provinceId = null;
  22. Integer cityId = null;
  23. List<String> accountIdList = null;
  24. // 号码、成本号码费、成本低消费、客户号码费、客户低消费不能为空
  25. if (CommonUtil.checkStringIsNullOrLine(cellList.get(0)) || CommonUtil.checkStringIsNullOrLine(cellList.get(12))
  26. || CommonUtil.checkStringIsNullOrLine(cellList.get(13)) || CommonUtil.checkStringIsNullOrLine(cellList.get(14))
  27. || CommonUtil.checkStringIsNullOrLine(cellList.get(15))) {
  28. legalFlag = false;
  29. }
  30. // 校验EXCEL中号码不能重复号码不能重复
  31. if(excelMobileList.contains(cellList.get(0).trim())) {
  32. legalFlag = false;
  33. } else {
  34. excelMobileList.add(cellList.get(0).trim());
  35. }
  36. // 客户类型为VBOSS并且分配了客户时,账户不能为空
  37. if (!CommonUtil.checkStringIsNullOrLine(cellList.get(7)) && !CommonUtil.checkStringIsNullOrLine(cellList.get(8))) {
  38. if (cellList.get(7).trim().equals("VBOSS") && CommonUtil.checkStringIsNullOrLine(cellList.get(8))) {
  39. legalFlag = false;
  40. }
  41. }
  42. // 客户类型为空的时候客户账户不能有值
  43. if (CommonUtil.checkStringIsNullOrLine(cellList.get(7))) {
  44. if (!CommonUtil.checkStringIsNullOrLine(cellList.get(8)) || !CommonUtil.checkStringIsNullOrLine(cellList.get(9))) {
  45. legalFlag = false;
  46. }
  47. }
  48. // 客户类型为bss的时候账户不能有值
  49. if (CommonUtil.checkStringIsNullOrLine(cellList.get(7))) {
  50. if (cellList.get(7).trim().equals("BSS")) {
  51. if (!CommonUtil.checkStringIsNullOrLine(cellList.get(9))) {
  52. legalFlag = false;
  53. }
  54. }
  55. }
  56. // 号码、区号必须为数字
  57. if (!CommonUtil.checkStringIsNullOrLine(cellList.get(0))) {
  58. if (!CommonUtil.checkIsInteger(cellList.get(0).trim())) {
  59. legalFlag = false;
  60. }
  61. }
  62. if (!CommonUtil.checkStringIsNullOrLine(cellList.get(1))) {
  63. if (!CommonUtil.checkIsInteger(cellList.get(1).trim())) {
  64. legalFlag = false;
  65. }
  66. }
  67. // 运营商只能为 移动、联通、电信、铁通、其它之一
  68. if (!CommonUtil.checkStringIsNullOrLine(cellList.get(5))) {
  69. if (!cellList.get(5).trim().equals("移动") && !cellList.get(5).trim().equals("联通")
  70. && !cellList.get(5).trim().equals("电信") && !cellList.get(5).trim().equals("铁通")
  71. && !cellList.get(5).trim().equals("其它")) {
  72. legalFlag = false;
  73. }
  74. }
  75. // 客户类型只能是 VBOSS或BSS之一
  76. if (!CommonUtil.checkStringIsNullOrLine(cellList.get(7))) {
  77. if (!cellList.get(7).trim().equalsIgnoreCase("VBOSS") && !cellList.get(7).trim().equalsIgnoreCase("BSS")) {
  78. legalFlag = false;
  79. }
  80. }
  81. // 成本号码费、成本低消费、客户号码费、客户低消费只能为小数
  82. if (!CommonUtil.checkStringIsNullOrLine(cellList.get(12))) {
  83. if (!CommonUtil.checkIsSmallNumber(cellList.get(12).trim())) {
  84. legalFlag = false;
  85. }
  86. }
  87. if (!CommonUtil.checkStringIsNullOrLine(cellList.get(13))) {
  88. if (!CommonUtil.checkIsSmallNumber(cellList.get(13).trim())) {
  89. legalFlag = false;
  90. }
  91. }
  92. if (!CommonUtil.checkStringIsNullOrLine(cellList.get(14))) {
  93. if (!CommonUtil.checkIsSmallNumber(cellList.get(14).trim())) {
  94. legalFlag = false;
  95. }
  96. }
  97. if (!CommonUtil.checkStringIsNullOrLine(cellList.get(15))) {
  98. if (!CommonUtil.checkIsSmallNumber(cellList.get(15).trim())) {
  99. legalFlag = false;
  100. }
  101. }
  102. // 数据库校验
  103. // 校验号码是否存在
  104. if (!CollectionUtils.isEmpty(mobileList)) {
  105. if (mobileList.contains(cellList.get(0).trim())) {
  106. legalFlag = false;
  107. }
  108. }
  109. // 校验省是否存在
  110. if (!CommonUtil.checkStringIsNullOrLine(cellList.get(2))) {
  111. if (CollectionUtils.isEmpty(sysAreaVOList)) {
  112. legalFlag = false;
  113. } else {
  114. Boolean hasFlag = false;
  115. for (SysAreaVO eachSysAreaVO : sysAreaVOList) {
  116. if (eachSysAreaVO.getAreaName().equals(cellList.get(2).trim())) {
  117. hasFlag = true;
  118. provinceId = eachSysAreaVO.getSaid();
  119. break;
  120. }
  121. }
  122. if (!hasFlag) {
  123. legalFlag = false;
  124. }
  125. }
  126. }
  127. // 校验市是否存在
  128. if (!CommonUtil.checkStringIsNullOrLine(cellList.get(3))) {
  129. if (CollectionUtils.isEmpty(sysAreaVOList)) {
  130. legalFlag = false;
  131. } else {
  132. Boolean hasFlag = false;
  133. for (SysAreaVO eachSysAreaVO : sysAreaVOList) {
  134. if (eachSysAreaVO.getAreaName().equals(cellList.get(3).trim())) {
  135. hasFlag = true;
  136. cityId = eachSysAreaVO.getSaid();
  137. break;
  138. }
  139. }
  140. if (!hasFlag) {
  141. legalFlag = false;
  142. }
  143. }
  144. }
  145. // 如果选择了客户类型并且分配了客户,则需要校验客户ID是否存在
  146. if(!CommonUtil.checkStringIsNullOrLine(cellList.get(7)) && !CommonUtil.checkStringIsNullOrLine(cellList.get(8))) {
  147. // 校验客户ID是否存在
  148. Boolean hasCustomerIdFlag = true;
  149. if(CollectionUtils.isEmpty(customerIdList)) {
  150. hasCustomerIdFlag = false;
  151. legalFlag = false;
  152. } else {
  153. if(!customerIdList.contains(cellList.get(8).trim())) {
  154. hasCustomerIdFlag = false;
  155. legalFlag = false;
  156. }
  157. }
  158. // 如果该客户ID存在,并且选中的客户类型是VBOSS,则需要校验账户和客户是否匹配
  159. if(hasCustomerIdFlag) {
  160. if(cellList.get(7).equals("VBOSS") && !CommonUtil.checkStringIsNullOrLine(cellList.get(9))) {
  161. if(CollectionUtils.isEmpty(customerVOList)) {
  162. legalFlag = false;
  163. } else {
  164. for (CustomerVO eachCustomerVO: customerVOList) {
  165. if(eachCustomerVO.getCustomerId().equals(cellList.get(8).trim())) {
  166. accountIdList = eachCustomerVO.getAccountIdList();
  167. break;
  168. }
  169. }
  170. if(CollectionUtils.isEmpty(accountIdList)) {
  171. legalFlag = false;
  172. } else {
  173. if(!accountIdList.contains(cellList.get(9).trim())) {
  174. legalFlag = false;
  175. }
  176. }
  177. }
  178. }
  179. }
  180. }
  181. // 如果数据合法,则封装号码对象
  182. if (!legalFlag) {
  183. if (!errorRowNumber.contains(curRow)) {
  184. errorRowNumber.add(curRow);
  185. }
  186. } else {
  187. try {
  188. MobileManager mobileManager = new MobileManager();
  189. mobileManager.setMobile(cellList.get(0).trim());
  190. mobileManager.setAreaCode(CommonUtil.checkStringIsNullOrLine(cellList.get(1)) ? null : cellList.get(1));
  191. mobileManager.setProvinceId(provinceId == null ? null : provinceId);
  192. mobileManager.setCityId(cityId == null ? null : cityId);
  193. mobileManager.setType(CommonUtil.checkStringIsNullOrLine(cellList.get(4)) ? null : cellList.get(4));
  194. if (!CommonUtil.checkStringIsNullOrLine(cellList.get(5))) {
  195. Integer operator = null;
  196. if (cellList.get(5).trim().equals("移动")) {
  197. operator = MobileManagerConstant.OPERATOR_YIDONG;
  198. } else if (cellList.get(5).trim().equals("联通")) {
  199. operator = MobileManagerConstant.OPERATOR_LIANTONG;
  200. } else if (cellList.get(5).trim().equals("电信")) {
  201. operator = MobileManagerConstant.OPERATOR_DIANXIN;
  202. } else if (cellList.get(5).trim().equals("铁通")) {
  203. operator = MobileManagerConstant.OPERATOR_TIETONG;
  204. } else if (cellList.get(5).trim().equals("其它")) {
  205. operator = MobileManagerConstant.OPERATOR_ELSE;
  206. }
  207. mobileManager.setOperator(operator);
  208. }
  209. mobileManager.setSupplierName(CommonUtil.checkStringIsNullOrLine(cellList.get(6)) ? null : cellList.get(6));
  210. if (!CommonUtil.checkStringIsNullOrLine(cellList.get(7))) {
  211. Integer customerType = null;
  212. if (cellList.get(7).trim().equalsIgnoreCase("VBOSS")) {
  213. customerType = MobileManagerConstant.CUSTOMER_TYPE_VBOSS;
  214. mobileManager.setAccountId(CommonUtil.checkStringIsNullOrLine(cellList.get(9)) ? null : cellList.get(9));
  215. } else if (cellList.get(7).trim().equalsIgnoreCase("BSS")) {
  216. customerType = MobileManagerConstant.CUSTOMER_TYPE_BSS;
  217. }
  218. mobileManager.setCustomerType(customerType);
  219. mobileManager.setCustomerId(CommonUtil.checkStringIsNullOrLine(cellList.get(8)) ? null : cellList.get(8));
  220. mobileManager.setState(CommonUtil.checkStringIsNullOrLine(cellList.get(8)) ?
  221. MobileManagerConstant.STATE_USEING : MobileManagerConstant.STATE_USEING);
  222. mobileManager.setDistributeTime(CommonUtil.checkStringIsNullOrLine(cellList.get(8)) ? null : new Date());
  223. } else {
  224. mobileManager.setState(MobileManagerConstant.STATE_UNUSE);
  225. }
  226. mobileManager.setTerminalUser(CommonUtil.checkStringIsNullOrLine(cellList.get(10)) ? null : cellList.get(10));
  227. if (!CommonUtil.checkStringIsNullOrLine(cellList.get(11))) {
  228. mobileManager.setPlanRecoveryDate(DateUtil.formatDateStrWithLine2Date(cellList.get(11).trim()));
  229. }
  230. mobileManager.setCostMobileFee(CommonUtil.checkStringIsNullOrLine(cellList.get(12)) ? null : Double.parseDouble(cellList.get(12).trim()));
  231. mobileManager.setCostLowFee(CommonUtil.checkStringIsNullOrLine(cellList.get(13)) ? null : Double.parseDouble(cellList.get(13).trim()));
  232. mobileManager.setCustomerMobileFee(CommonUtil.checkStringIsNullOrLine(cellList.get(14)) ? null : Double.parseDouble(cellList.get(14).trim()));
  233. mobileManager.setCustomerLowFee(CommonUtil.checkStringIsNullOrLine(cellList.get(15)) ? null : Double.parseDouble(cellList.get(15).trim()));
  234. mobileManager.setRemark(CommonUtil.checkStringIsNullOrLine(cellList.get(16)) ? null : cellList.get(16).trim());
  235. mobileManager.setCreateTime(new Date());
  236. mobileManagerList.add(mobileManager);
  237. } catch (Exception e) {
  238. e.printStackTrace();
  239. if (!errorRowNumber.contains(curRow)) {
  240. errorRowNumber.add(curRow);
  241. }
  242. }
  243. }
  244. // 批量保存
  245. try {
  246. if (mobileManagerList.size() == ExcelConstant.PER_READ_INSERT_BATCH_COUNT) {
  247. mobileManagerMapper.saveMobileManagerBatch(mobileManagerList);
  248. mobileManagerList.clear();
  249. } else if (mobileManagerList.size() < ExcelConstant.PER_READ_INSERT_BATCH_COUNT) {
  250. int lastInsertBatchCount = totalRowCount % ExcelConstant.PER_READ_INSERT_BATCH_COUNT == 0 ?
  251. totalRowCount / ExcelConstant.PER_READ_INSERT_BATCH_COUNT :
  252. totalRowCount / ExcelConstant.PER_READ_INSERT_BATCH_COUNT + 1;
  253. if ((curRow - 1) >= ((lastInsertBatchCount - 1) * ExcelConstant.PER_READ_INSERT_BATCH_COUNT + 1)
  254. && (curRow - 1) < lastInsertBatchCount * ExcelConstant.PER_READ_INSERT_BATCH_COUNT) {
  255. if (curRow - 1 == totalRowCount) {
  256. if(!CollectionUtils.isEmpty(mobileManagerList)) {
  257. mobileManagerMapper.saveMobileManagerBatch(mobileManagerList);
  258. }
  259. }
  260. }
  261. }
  262. } catch (Exception e) {
  263. e.printStackTrace();
  264. if (!errorRowNumber.contains(curRow)) {
  265. errorRowNumber.add(curRow);
  266. }
  267. }
  268. }
  269. });
  270. logger.info("导入号码列表完成:" + DateUtil.formatDate(new Date(), DateUtil.YYYY_MM_DD_HH_MM_SS));
  271. return ResultVO.getSuccess("批量导入VOIP用户完成, 共有" + errorRowNumber.size() + "条记录存在问题,失败行号为:" + errorRowNumber);
  272. }
  273. 有一点要说明的是关于去重的: 之前准备直接在数据库采用唯一索引来保证不重复,但是有BUG:因为是批量分批保存,如果保存的这一批中有重复的数据,则会导致该批数据都保存不了。
  274. 解决方案:1. excel数据去重: 定义一个全局变量集合用来存储可能重复的字段,如上面的mobileList, 每读取到excel一条记录,就判断该条记录是否在改全局变量集合中已经存在,如果存在,则将标志设置为false, 就不再放到批量存储对象的集合中,如果不存在,则放入; 2. 数据库校验是否存在: 这个就是在前面一次性查出来,每读取一条excel记录查看是否存在,如果存在,则将标志设置为false, 就不再放到批量存储对象的集合中,如果不存在,则放入;

发表评论

表情:
评论列表 (有 0 条评论,287人围观)

还没有评论,来说两句吧...

相关阅读