实习工作难点记录

前言

不知不觉已经实习两个月了,记录一下 ‘美好’ 的实习生活



难点1. 在已有的excel表内追加数据不发生栈溢出(10w条数据,关键字:‘已有’,‘追加’)

之前没接触过excel,一上手就来个有意思的(公司用的poi)

在网上找了半圈只找到,读取大数据量的api(Stream流)和往空文件写入大量数据的api(SXSSFWorkbook)

他们俩的共同特点都是将内存里的数据用一段扔一段,于是用了一个很笨的办法实现了需求。

大概思路就是用Stream流读取数据,中间再对数据进行判断,如果符合新增数据的条件,就把要增加的数据添加进读取的数据中,最后再用 SXSSFWorkbook 向空文件写入。

缺点就是效率低,excel的样式丢失(读取的样式有问题)

贴一下代码

 /**
     * 根据map 对大批量数据的EXCEL 进行写操作
     * @param map
     * @param file
     * @param sheetIndex
     */
    public static void inserLargeExcelByMap(Map<String, Object> map, File file, int sheetIndex) {
        //输出流
        BufferedOutputStream bos = null;
       try (
               FileInputStream fis = new FileInputStream(file);
               //原excel
               Workbook workbook = StreamingReader.builder()
                       .rowCacheSize(100)
                       .bufferSize(4096)
                       .open(fis);
               //新excel
               SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook();
       ) {
           //1.获取 sheet
           Sheet oldSheet = workbook.getSheetAt(sheetIndex);
           SXSSFSheet newSheet = sxssfWorkbook.createSheet(oldSheet.getSheetName());
           //用于标记新增数据是否插入
           boolean flag = false;

           for (String key : map.keySet()) {
               long columnIndex = transLetter2Num(key.replaceAll("[0-9]", ""));
               long rowIndex = Long.parseLong(key.replaceAll("[a-zA-Z]", ""));

               //2.1遍历原excel 填充数据到新 excel
               for (Row oldRow : oldSheet) {
                   //2.2 创建新row
                   SXSSFRow newRow = newSheet.createRow(oldRow.getRowNum());
                   //2.3 判断插入数据的行 是否在所在行
                   if (rowIndex == oldRow.getRowNum()) {
                       for (Cell oldCell : oldRow) {
                           if (columnIndex == oldCell.getColumnIndex()) {
                               throw new RuntimeException("要插入数据的行列在excel已存在");
                           } else {
                               //2.4 填充原数据到新excel
                               newRow.createCell(oldCell.getColumnIndex()).setCellValue(oldCell.getStringCellValue());
                           }
                       }
                       //2.5 插入新增数据到新excel
                       newRow.createCell((int) columnIndex).setCellValue(map.get(key).toString());
                       //2.6 设置插入标记
                       flag = true;
                   } else {
                       // 3.1 新数据的行不在原excel 已有行时
                       for (Cell oldCell : oldRow) {
                           //3.2 填充原数据到新excel
                           newRow.createCell(oldCell.getColumnIndex()).setCellValue(oldCell.getStringCellValue());
                       }
                   }
               }

               //4 遍历完所有数据,新数据还没插入时,插入新数据
               if (!flag) {
                   newSheet.createRow((int) rowIndex).createCell((int) columnIndex).setCellValue(map.get(key).toString());
               }

           }

           //遍历完之后 将原excel删除
           String filePath = file.getAbsolutePath();
           file.delete();

           bos  = new BufferedOutputStream(new FileOutputStream(filePath));
           sxssfWorkbook.write(bos);
           sxssfWorkbook.dispose();
       } catch (FileNotFoundException e) {
           e.printStackTrace();
       } catch (IOException e) {
           e.printStackTrace();
       } finally {
           if (bos != null) {
               try {
                   bos.close();
               } catch (IOException e) {
                   e.printStackTrace();
               }
           }
       }

    }


    /**
     * 将 excel 中的字母列索引转换为数字列索引
     *
     * @param letterColIdx 字母列索引
     * @return rlt 返回字母列索引对应的数字索引 A -> 0  BA -> 52 CA -> 78
     * */
    public static long transLetter2Num(String letterColIdx) {
        long rlt = 0;
        letterColIdx = letterColIdx.toUpperCase();

        String[] sts = letterColIdx.split("");

        int length = sts.length;
        for (int i = 0; i < length; i++) {
            String st = sts[i];
            char[] cs = st.toCharArray();

            int idx = (int)cs[0];

            // 如果字符不是 A-Z 之间的字符, 则抛错
            if (idx < 65 || idx > 90) {
                throw new ExcelAnalysisException("Characters["+ st +"] not between A and Z.");
            }

            idx -= 64;
            long num = (long) (idx * Math.pow(26, length - i - 1));
            rlt  += num;
        }

        return --rlt;
    }



难点2. 优化某个代码片段的执行速度

拿到代码跑了一下,好家伙,跑了半个小时抛出死锁异常(人生第一次遇到死锁…),仔细看了看代码逻辑,发现以下几个问题。


问题一: 有一条查询所有数据的sql大致是
select * from table where column = '某1' order by '某2'

查看设计表,只有一个 id 主键索引,这条sql大概运行了接近10s(公司电脑比较烂),查出来10w条多点的数据

然后我把 sql 改为下面的语句,用时4s,sql优化知识之前几乎是空白,只在B站上看过一次图灵学院的免费公开课。

select * from table a, (select id from table where column = '某1') b where a.id = b.id order by '某2'

最后采用的是这条语句,并给 ‘某一,某二’增加了联合索引,用时1.9s

select 需要的字段 from table where column = '某1' order by '某2'

本来想试试这条语句,结果报错了,没搞懂为啥

select 需要的字段 from table a, (select id from table where column = '某1') b where a.id = b.id order by '某2'

问题二: 代码中有一个逻辑是将上述sql查询的10w条数据遍历,并根据条件进行更新,每一次循环更新的语句大概有8条…遍历完对数据库进行的IO操作至少以万为单位,这代码能运行快那才见鬼了

解决思路:想起来Mybatis-Plus中有条批量删除的语句,于是百度搜索关键字 mysql 批量更新
找到这条语句

UPDATE mytable SET
    myfield = CASE id
        WHEN 1 THEN 'value'
        WHEN 2 THEN 'value'
        WHEN 3 THEN 'value'
    END
WHERE id IN (1,2,3)

接下来就好办了,再循环的时候 加上拼接 sql 字符串的逻辑,最后跑代码只用了 10s不到的样子



难点3. 生成 PDF 报销信息

一开始是打算先手动生成一个pdf模板然后用 pdfbox 替换关键字,结果遇到了中文乱码的问题,试了好几个编码都不行,进官网只找到 api 文档 (弱鸡看不懂),最后百度找到了一个很好用的 jar 包 Spire文档地址

用段落➕表格 生成了 需要的PDF

过程中遇到的问题:

  1. 中文乱码,需要使用支持中文的字体
  2. 使用了宋体但是 linux环境 好像没有这个字体,导致报错,于是使用了从网上下载了免费商用的字体



难点4. 将 2.5w 个 word合同关键信息 提取,并添加进excel进行汇总

这一下 Office 三件套一样没落下,难点在于各个 word 里面的信息有差异,关键字也不一样,目前没找到啥好的解决办法



难点5. 会议室预约的前端实现

技术栈是vue + ant deign of vue,这算是我真正意义上使用 vue 开发项目,之前在学校有开发过一款跑腿小程序(行云跑腿,服务器到期了…),算是写着练手玩的。

需求:

  1. 响应式布局,支持 手机 和 PC
  2. 外观样式,自己看着办…

之前开发的小程序就是仿的 UU跑腿界面,有经验… ,最后决定模仿挺好看的一个日历界面

接手的当天晚上就回家恶补 vue vuex vue router的知识(之前有学过,但是很久没用忘的差不多了)

难点:

  1. 根据会议时常,鼠标移动时,对应时间段的div背景色改变。解决思路:给每个div加上 开始时间和结束时间的属性
  2. 切换会议室,如何刷新页面并且保留 data 数据。解决思路:将关键数据和 父组件同步,切换会议室时,通知父组件 配合 v-if 销毁重建 子组建
  3. 切换会议室,点击上一页,下一页增加过渡动画,模仿的那个日历做的动画很自然,我做的动画幅度太大,之前一直想通过修改动画间隔粒度来让动画变得自然。最后通过修改 移动的距离,完成了动画的复刻,模仿的挺像~

后端主要就是同时抢占会议室锁的问题,由于只是公司的几个领导会使用这个功能,并且代码应该是部署到一台服务器的,所以就使用了 synchronized 代码块

如果部署到多台服务器,可以用redis 实现一个简单的分布式锁
思路:通过 redis 的原子性操作,给 redis增加键值对。

  1. 只有当键不存在时,才增加
  2. 为防止应用宕机,需设置过期时间,自动将锁归还
  3. 为防止应用任务堵塞后(超过key过期时间),归还锁时,将别的任务加的锁给删除,解决办法可以再开一个线程定时重置锁的过期时间。



分享一个优雅的参数校验方法:validation

maven坐标:

        <!--参数检验-->
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
        </dependency>

简单示例:

最后加一个异常拦截配置

具体 API 参考



记录一次奇怪的BUG

bug问题:提交数据时,数据库插入了两条一样的数据

debug调试时,发现运行一条sql语句的代码,打印出一堆sql日志,一开始以为是通用mapper的问题,
后来发现是前端循环发送了四条异步请求,造成的并发问题。

加锁后发现问题依然存在,看了很久的代码,觉得逻辑没问题(第一次请求新增,后三次请求更新)
最后发现@Transactional注解 会使 synchronized关键字失效 ( synchronized代码块里执行的内容是在事务里面,在事务commit前可能有多个线程进入代码块),@Transactional注解又加在了整个service类上,给controller加锁,又会导致锁的粒度太大,最后交给了一个的前端实习妹子,让她用promise发同步请求



总结

  1. 由于大部分的任务都是对老项目(OA项目)进行维护和升级,深刻的明白了项目分层的意义,老项目没有service层
    一个Controller 快一万行代码,每个方法都看的头皮发麻
  2. 变量命名要规范!! 老项目前端代码甚至出现 a1,a2,a3 … a21 这样的神奇变量命名
  3. 参数校验!! 打开项目 总是点这点着就报错,点开控制台 熟悉的 java.lang.NullPointerException,不知道使用的员工是怎么受得了的…
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值