OpenPyXL爬坑_第二弹

这是一段平平无奇的代码,用于获取冻结单元格

1
2
3
4
5
6
7
8
9
import shutil
import openpyxl

path = "/Users/xxx/Documents/tmp/openPyXLTest.xlsx"

wb = openpyxl.load_workbook(path)
sheet = wb["Sheet"]
print(sheet.freeze_panes)
wb.close()

文件内容如下

执行效果如下

看上去一切正常

但是当文件滚动条变更之后,如图

它的执行结果就变成了

没错,只要滚动条滚动了,取出来的值就是不正确的。如同源码展示的,它只会取 topLeftCell ,而这个值只会在加载的时候初始化,所以冻结的单元格行数,并不能准确取到。

已提交issue,期待回复

https://foss.heptapod.net/openpyxl/openpyxl/-/issues/2119

回复结果:

应该使用sheet.sheet_view.pane.ySplit获取冻结行数

输出结果 3.0

OpenPyXL爬坑

背景

接到需求,批量修改式样书的表纸页和履历页。本以为是简单的改动,但是深深踩了OpenPyXL的坑。

测试代码

1
2
3
4
5
6
7
8
9
10
11
import shutil
from openpyxl import load_workbook

path = "/Users/xxx/Documents/tmp/openPyXLTest.xlsx"
copyFile = "/Users/xxx/Documents/tmp/openPyXLTest2.xlsx"

shutil.copy(path, copyFile)

wb = load_workbook(copyFile)
wb.save(copyFile)
wb.close()

运行结果

图形问题解决

根据运行结果可以看出,只是简单的打开、关闭一个 excel 文件,会导致图片以及图形的丢失。

针对图形丢失在网上有很明确的说明,openpyxl 使用 Pillow 处理图片,所以需要安装 Pillow

pip install Pillow

安装 Pillow 之后,运行结果如下

图形依然丢失

去 OpenPyXL 仓库上翻了翻,这居然是一个陈年老bug https://foss.heptapod.net/openpyxl/openpyxl/-/issues/1268

彻底解决

使用 xlwings 库或者直接使用 VBA

xlwings: https://docs.xlwings.org/en/latest/quickstart.html#

xlwings示例

1
2
3
4
5
6
7
8
9
10
11
import shutil
import xlwings as xw

path = "/Users/xxx/Documents/tmp/openPyXLTest.xlsx"
copyFile = "/Users/xxx/Documents/tmp/openPyXLTest3.xlsx"

shutil.copy(path, copyFile)
wb = xw.Book(copyFile)
wb.save(copyFile)
wb.close()
print("done")

Junit测试通过,MavenTest测试未通过

Run Unit Test和Maven test的区别

差异1:

在IDE中通过选中单元测试路径,点击右键选择run test和点击maven中的test是有区别的。在Maven执行测试的过程中,是不允许测试cases访问其他项目的测试类和其他项目的resources下文件的。也就是说,在a/src/test/java下的测试用例,是不能引用b/src/test/java中的类的,同时也不允许访问b/src/test/resources下的资源的。但是在IDE中的Run Unit Test几乎是没有这样的限制的。

差异2:

Maven强制要求src/test/java下不能存在resource的文件,必须放到src/test/reources文件夹下,但是IDE却很少有对应的约束。

解决方法

在maven插件配置:(surefire2.14以下版本)

1
2
3
4
5
6
7
8
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12</version>
<configuration>
<forkMode>always</forkMode>
</configuration>
</plugin>

重点加入configureation的配置部分

在maven插件配置:(surefire2.14及其以上版本)

1
2
3
4
5
6
7
8
9
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<configuration>
<reuseForks>false</reuseForks>
<forkCount>1</forkCount>
</configuration>
</plugin>

在2.14以上的版本中,forkMode配置项已经废弃了。

java外字处理爬坑

背景

日语内外字不在常规Unicode编码集内,需要手动更换成编码集内的文字。

坑点

外字在本地文档/服务器文档编码集符合文档提供的规则,但是在Stream流内会自动解析成\\uFFFD\\uxxxx。例如SJIS编码下F141的文字,在UTF8下编码为E08D,但是进入Stream流后变成了\\uFFFD\\u0041,所以要在代码内实际确认一下对应的编码。同时因为外字转成了\\uFFFD\\uxxxx,所以位数变成了2位,而且外字变换常规字符后可能会由一个字符变成多个字符,对应Byte的切分要注意位数变更。

相关代码段

打印字符串unicode编码

1
2
3
4
5
6
7
public static void printUnicode(String str) {
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
System.out.printf(String.format("\\u%04X ", (int) c));
}
System.out.println();
}

变换外字

1
2
3
4
5
6
7
8
9
10
Map<String, String> convertMap = new HashMap<>(); 
convertMap.put("\\uFFFD\\u0041", "さい");
convertMap.put("\\uFFFD\\u0042", "そね");

for (Map.Entry<String, String> entry : convertMap.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
// repaceAll支持直接变换Unicode
String info = info.replaceAll(key, value);
}

时间戳四舍五入

因为项目原因,api返回的结果是6位毫秒(2023-04-12 20:56:31.589183),而存储到db然后导出是3位数毫秒(2023-04-12 20:56:31.589)。

这里牵扯到四舍五入,所以不能单纯截取,需要加500毫秒。

1
2
3
4
5
6
from datetime import datetime, timedelta
time_str = "2023-04-07 18:14:45.589183"
dt = datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S.%f")
new_dt = dt + timedelta(microseconds=500)
new_time_str = new_dt.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
print(new_time_str)

ConfigParser读取properties

ConfigParser读取properties文件时,properties文件必须有默认的头,例如[default],如果没有会报错。

因为ConfigParser默认是读取ini格式文件,ini文件必须有section header。properties虽然也是key=value格式,但是不强制section header。

解决方式是读取内容后手动加上header,然后交给ConfigParser解析。

1
2
3
4
content = "[default]\n" + open(bathPath + "\\" + file).read()
config = ConfigParser(allow_no_value=True)
config.read_string(content)
value = config.get('default', key)

final无法阻止数组值变换

final是引用不可变,值还是可以改变的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class App {

public static final String[] array = {"1", "2"};

public static void main(String[] args) throws Exception {

for (int i = 0; i < array.length; i ++) {
System.out.print(array[i] + " ");
}
System.out.print("\n");
array[0] = "3";
for (int i = 0; i < array.length; i ++) {
System.out.print(array[i] + " ");
}

}
}
// 输出结果
// 1 2
// 3 2

Python报错UnicodeDecodeError

背景

Python IO读取文件时报错UnicodeDecodeError: ‘gbk’ codec can’t decode byte...

错误原因

如同报错信息,Unicode解码失败。根本原因是文件中有汉字/日文等其他文字不能用gbk打开。

解决方法

利用utf-8格式打开

1
file = open(filename, encoding="utf8")

MySQL执行SQL脚本报错 ERROR 1231 (42000)

错误场景

登录MySQL,利用source命令导入sql文件时报错。

错误截图

解决方法

在sql文件前加入下面注释:

1
2
3
4
5
6
7
8
9
10
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

示例

执行yum提示错误 thread died in berkeley db library

问题描述

在执行yum安装或者其他命令时,有如下提示:

解决方法

1
2
3
4
5
6
[root@VM-0-14-centos /]# cd /var/lib/rpm
[root@VM-0-14-centos rpm]# ls
Basenames __db.001 __db.003 Group Name Packages Requirename Sigmd5
Conflictname __db.002 Dirnames Installtid Obsoletename Providename Sha1header Triggername
[root@VM-0-14-centos rpm]# rm -rf __db*
[root@VM-0-14-centos rpm]# rpm --rebuilddb

SpringBoot解决跨域问题

跨域问题

前后端分离的时候出现了跨域问题。。。。虽然可以用Jsonp的方式解决,但是axios推荐利用CORS方式解决。

解决方法

添加一个拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.wordcard.filter;

import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* @author 19745
*/
@Component
public class CORSFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE,PUT");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "accept,x-requested-with,Content-Type");
response.setHeader("Access-Control-Allow-Credentials", "true");
chain.doFilter(req, res);
}

@Override
public void destroy() {

}

}