• Excel导出超过65535行报异常

    工程中在使用POI导出Excel数据,某次因为导出的数据量比较大就报了下面的错误: 查了下资料,了解到问题在于使用了HSSFWorkbook。HSSFWorkbook用来操作Excel2003以前(包括2003)的版本,限制了每个Sheet导出的行数至多为65535行。 要修复这个问题可以考虑使用XSSFWorkbook或者SXSSFWorkbook。如果要导出的数据量特别大,建议使用后者。因为XSSFWorkbook会将全部要写入Excel中的数据保存在内存中,数据量太大的话就会导致内存溢出错误。 SXSSFWorkbook是在POI3.8版本时出现的,对XSSFWorkbook做了一些优化,通过采用将一定数量的数据写入硬盘临时文件的机制减轻了内存的压力。 参考文档 HSSF XSSF SXSSF

    [阅读更多...]
  • SpringBoot读写xml上传到S3

    最近的工作涉及到了生成xml文件并上传到AWS存储服务S3这样的处理。期间遇到了两个问题,简单记录下: springboot读取xml模板异常 将生成的xml上传到S3的问题 springboot的版本是2.1.9.RELEASE,读写xml文件使用的是Dom4J,版本是2.1.3。逐个说明下遇到的这几个问题。 1.springboot读取xml模板异常 现阶段是将xml模板文件存储在springboot项目的resource目录下的。具体路径为 最初是通过类加载器获取文件路径后再尝试读取模板文件的: 通过类加器获取到的文件路径是: 不过我们都知道,springboot是将整个工程包括配置文件打成一个jar包后再直接运行。这样想在linux的服务器上通过文件路径找文件是注定找不到的。 后来改成直接通过SpringBoot提供的ClassResource类来获取resource路径下的配置文件: 这里直接使用InputStream读取的模板文件。注意不要再尝试通过调用ClassResource实例的getFile()方法来获取文件,不然会遇到和之前同样的问题。 额,期间还发生了无法将模板文件打进springboot项目运行时的jar文件这样的问题。因为是将模板文件存储在了resources的子目录下,需要调整下maven打包的配置: 下面这几行如果没有的话需要加上,不然会读取不到子目录中的配置文件: 2.将生成的xml上传到S3 AWS提供的最便捷的上传文件接口是这个: 这个接口通过File实例来执行上传。所以我一开始的想法是先生成一个临时文件保存在服务器本地,读取本地临时文件为File执行上传,最后再删掉本地的临时文件。这个思路是没问题的,在本地执行也OK。但是在生产环境,由于权限相关的问题,生成临时文件失败了。 不想再去折腾权限相关的事情,所以将出路寄托在了AWS提供的另一个接口上: 也就是说考虑将xml文件内容输出到InputStream,然后再将InputStream上传到S3。一切都在内存里执行,不依赖外部文件系统也就不会有文件权限的问题。 这个方案的问题在于ObjectMetaData这个类有点儿黑箱的意思。该怎么设置需要进行一些摸索。看了一遍这个类的接口文档,需要调用的也就这两个set方法: 其中后者(文件长度)是AWS建议设置的,不设置会在处理的时候给出WARN。根据方法文档也可以看到,如果不设置,在上传的时候就会在内存中缓存整个信息流来计算文件长度。 至于前者是上传到S3文件的缓存过期时间,酌情设置即可。 另一个需要解决的问题就是怎么将Dom4j生成的Document输出再读取到InputStream中。这里用到了XmlWritter类,具体实现如下: 验证了一下,这个方法是可行的。修改后生产环境没有再报错。 向AWS S3存储服务上传文件的实现代码在这篇文章里:Java实现上传文件到AWS S3 End!

    [阅读更多...]
  • Java SpringBoot实现上传文件到AWS S3

    简单记录一下在Springboot中上传文件到AWS S3存储服务的代码。 在application.xml中添加aws相关配置: 新建一个AwsS3Componment类来执行上传文件操作: 因为使用的服务有设置endpoint,所以这里需要使用下面这一行完成endpoint的设置: 如果不设置endpoint就会收到下面这样的报错: 异常信息中提示了AccessKey无效——虽然我的AccessKey是有效的。 在endpoint的这行配置中还设置了region信息。如果不需要设置endpoint,就得补上region的配置: 下面是执行上传的代码: 这里是通过File实例执行的上传。有时候会需要直接通过文件流执行上传,此时可以使用下面的代码: 注意这里的setContentLength()最好配置一下。不设置会在处理的时候给出WARN。根据方法文档也可以看到,如果不设置,在上传的时候就会先在内存中缓存整个信息流来计算文件长度。 大体上就是这样了。 End!

    [阅读更多...]
  • Jackson处理json中的反斜杠

    在使用jackson处理json时遇到了反斜杠相关的问题。比如我们从数据库或文件中读到了这样的一个json字符串: 可以看到在World前面有一个反斜杠。因为这个反斜杠,JetBrains IDEA会提示这是一个错误的字符串,使用jakcson处理这个json字符串时也会报错,错误信息如下: 针对这种问题,Jackson已经预设了配置项: ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER。 在Jackson早期的版本中,可以这样配置: 但是在最近的版本中,这个配置项被移到了JsonReadFeature类中。对此我是很惆怅的:因为没办法简简单单直接使用configure方法进行配置了。 Em,在最近的Jackson的版本中需要这样配置: 如果已经有一个ObjectMapper实例了,还可以考虑创建一个ObjectReader实例读取数据: 大体上就是这样了。 参考文档 How to use JsonReadFeature End!

    [阅读更多...]
  • springboot入门11 – MyBatis写入时null问题统一处理方案

    如果MySQL表的一些字段被设置为不允许为空,使用MyBatis写入的数据中相应字段的值是null就有可能会报类似下面的错误: 对于这个问题,我通常的做法是为对应字段的null值直接赋值一个空字符串(或其它默认值)。但是这次,不知道为什么那么多不必要的字段被设置为了不能为空:七八个字段,要判断是否为空,还要一个个赋默认值 —— 想想就头疼。一定要想办法优化掉这种啰嗦且难看的东西。 参考分页插件 PageHelper 的实现后找到了思路:添加一个拦截器,获取参数实例,为实例的值为null的Field设置一个默认值。 拦截器的实现如下: 在这个拦截器中通过反射的方式为参数实例值为null的字段完成了赋默认值。这里只为几个我常用的类型(字符串、整型和日期类型)设置了默认值,如果还会用到其他值请自行修改setDefaultValue方法。 代码中有一个重要的点就是拦截器类上的注解@Signature: 这个注解指明了具体要拦截的类和方法。在这里,数据库操作主要是通过Executor类完成;并且不论是新增或是更新都是通过update方法实现的,args属性则明确了方法参数只有一个Object类型的值。 然后是让这个拦截器生效。代码如下: 这里通过@Configuration完成的自定义拦截器的注入。 配置完成后,再次执行写入和更新的测试案例,一次通过,搞定! 示例代码:zhyea / spring-boot-database-pad

    [阅读更多...]
  • java唯一字符串ID生成方案

    工作中经常会有生成唯一字符串的需求。通常最容易想到的是UUID。UUID的唯一性毋庸置疑,但是32位的长度也容易让人退避三舍。也曾经想过参考《短网址生成方案》来生成一串ID,但是试验了一下发现唯一性不太好。 最终采用的方案是时钟方案,简单来说就是用当前时间戳做唯一ID。 采用时间戳做ID,秒或毫秒都容易产生重复,换成纳秒在单节点上就没问题了。参考百度百科关于纳秒的描述就能清楚为什么纳秒级别的时间戳不会产生重复: 光在真空中一纳秒仅传播0.3米。个人电脑的微处理器执行一道指令(如将两数相加)约需2至4纳秒。 我们生成一条唯一ID所需的CPU指令绝不止一道,因此用纳秒作单机唯一ID是绰绰有余的。在测试中发现,即使是千分之一纳秒也足够我们在PC机上生成唯一ID了。 至于长度:对原始值做一次Base62处理,长度就能缩减到令人满意的程度。 不多废话,直接上代码: 这里用千分之一纳秒做基数(经测试,基数在10w分之一纳秒内都是安全的),再加上1~99的顺序号来生成唯一ID。最终可以保证在大于10纳秒(近似)的时间区间内不会产生重复值。 为了缩减长度,对字符串做了Base62处理。在处理前又将纳秒数值做了一次翻转处理。不难想象,如果直接使用原始值来做Base62处理,因为时钟的特征,最终生成的值的前几位都是相同的。 来看一下这个程序生成的ID: 八位的长度,唯一且整齐。下面是一个单元测试: 这里只对10240的规模做了测试。因为唯一ID是基于时钟生成的,所以测试时整体规模的大小不影响ID的唯一性(和短链接方案不一样)。但是太小了也不行——顺序号会发挥作用。10240算是一个中庸的值,足够暴露问题,也不会有太多的冗余。 可以在github看到相关的代码 GitHub / UniqId 仍然需要强调一下:这个方案只能保证在(当前)单机上的唯一性,如果是集群范围内建议采用其他方案,或者加上一两位机器ID。

    [阅读更多...]
  • Java 抽象工具类

    在SpringBoot的源码中有看到使用abstract关键字定义的工具类,如: 使用abstract关键字的目的猜测应该是为了避免实例化。 同样为了避免实例化,在jdk中定义的工具类则通常是使用私有化构造器来实现的: 目的都是为了避免实例化,这两种方案无非是“茴”字的不同写法而已。 我自己比较偏好第二种写法,原因有二: abstract关键字在语义上的作用不是简单的为了避免实例化,更何况说是用来定义工具类了 阿里p3c的规范要求Java工具类使用第二种方案(安装了相关插件,每次都被提醒很头疼的) 但对于第二种方案我通常习惯更多做一些事情,比如下例: 在工具类定义时添加了final关键字避免被继承修改;在私有构造器里面抛出了异常以防止私有构造器被反射调用。当然这也有可能是我杞人忧天了。如果真的有心要做些什么,通常也是防不住的。 在StackOverflow上也有对这个问题的讨论:Should Helper/Utility Classes be abstract?。有趣的是乱入了一些C# Developer,让我们有机会一窥其他语言中的特色。 End!

    [阅读更多...]
  • Java AES加密

    做360广告的对接需要对密码进行AES加密,下面是点睛平台文档的描述: (AES模式为CBC,加密算法MCRYPT_RIJNDAEL_128)对MD5加密后的密码实现对称加密。秘钥是apiSecret 的前16位,向量是后16位,加密结果为64位数字和小写字母。 用Java实现AES需要依赖Java加密扩展(The Java Cryptography Extension,简称JCE)的支持——主要是在javax下面的一些包。根据描述需要使用的算法为“AES/CBC/NoPadding”,实现方案如下: 这里使用的SecretKeySpec、AlgorithmParameterSpec、IvParameterSpec等类都是JCE提供的,通常在JVM环境下可以直接使用。Hex.encodeHexString()方法则是由apache-commons-codec提供的。如果不想多引入一个依赖也可以使用下面的方法: 下面是为这个加密方法写的单元测试: 这里的代码大体上能够满足360广告的对接需求了。但是因为jdk11偶尔对一些javax扩展包的不支持,我有些不太喜欢这个方案。另外在一些资料中也了解到jdk对AES 256加密是有一些限制的,要响应相关限制需要引入一个授权文件或者更换jdk,这就有些难接受了。种种原因吧,我需要一个替换方案。 最开始我以为在apache-common-codec中会有相关方案,但是结果是让人失望的。不过还好,最终我找到了Bouncy Castle。以下是关于Bouncy Castle的一些描述: Bouncy Castle 是一种用于Java平台的开放源码的轻量级密码算法包。它支持大量的密码算法,并提供 JCE 1.2.1 的实现。Bouncy Castle是轻量级的,从J2SE 1.4到J2ME(包括MIDP)平台,它都可以运行。它是在MIDP上运行的唯一完整的密码术包。 使用Bouncy Castle提供的能力必然需要先引入相关的依赖。针对不同的jdk版本,Bouncy Castle都有提供对应的Cryptography Provider。比如我使用的是JDK1.8,对应的就是bcprov-jdk15to18,相关的依赖如下: 基于Bouncy Castle实现的360点睛平台AES加密处理如下: 因为360点睛平台要求使用的加密key没有超过256位,所以两个方案都是行得通的。 我比较喜欢Bouncy Castle这个方案,这个方案相对较轻量,并且不依赖JCE。但是这个方案的不足之处也恰恰在于此:Java中的SSL层,JSSE和XML加密库都依赖到JCE,而且AES Key长度的校验是在Cipher类中进行的,在这些场景下Bouncy Castle也很难起到作用。 参考文档 Why does Java allow AES-256 bit encryption on systems without JCE unlimited strength policies if using PBE? python实现AES对称加密 AES 256 without JCE Unlimited Strength Jurisdiction Policy Files Is AES256 encryption decryption possible in Java without unlimited strength JCE files? RIJNDAEL encryption with Java  

    [阅读更多...]
  • springboot入门10 – 修改banner

    这个内容有点儿水了。但是将springboot启动时的banner修改一下是个蛮好玩的事情。比如,不知道什么时候,我们组的springboot应用的banner就被改成了这个样子: 据说改了之后BUG真的少了耶!(*/ω\*) 修改方式也比较简单,创建一个名为banner.txt的文件,写入图标字符,然后将这个文件放到resource目录下。搞定了。就这样。 不过,springboot还是提供了一些配置信息的。下面是可以在banner.txt中使用的一些替换宏: ${AnsiColor.BRIGHT_RED}:设置控制台中输出内容的颜色 ${application.version}:用来获取MANIFEST.MF文件中的版本号 ${application.formatted-version}:格式化后的${application.version}版本信息 ${spring-boot.version}:Spring Boot的版本号 ${spring-boot.formatted-version}:格式化后的${spring-boot.version}版本信息 还有一些其它替换宏,在idea中编辑banner.txt文档的时候这些都有动态提示。 还有一些在application中使用的配置: 最后记录几个生成ascii字符图像的网站(话说这也是我写这篇文的初衷): 图像转字符图像:https://www.degraeve.com/img2txt.php 文字转字符图像:http://patorjk.com/software/taag End!

    [阅读更多...]
  • springboot入门09 – 实现伪静态

    最近想了下springboot前端路径的伪静态实现。 通过百度最容易找到的方案是使用urlrewritefilter这个依赖。不过一想到要为这么一件事情就添加一个依赖,还要再添加一个配置文件,还要挨个写一遍所有的路径映射就觉得头疼,所以pass。 跟踪了一下springboot WEB请求处理的过程,找到了一个关键类:UrlPathHelper。在获取Handler之前,由这个类负责解析请求路径。正好可以在WebMvcConfigurer中配置UrlPathHelper类的实例,这就给了我们动些手脚的空间。 更贴心的是,springboot获取请求路径的第一选择不是调用HttpServletRequest.getRequestURI()方法或者HttpServletRequest.getServletPath()方法,而是从HttpServletRequest的attribute中获取,这样能省下不少力气。 来看下实现方式。 1. 实现伪静态路径解析类 伪静态路径解析类是UrlPathHelper类的一个子类,通过重写getRequestUri()方法实现了伪静态路径的处理。 代码如下: 代码中的核心部分是下面两句: 在后面处理中,springboot会优先从Attribute中获取请求路径。 2. 添加WebMvcConfigurer配置类 然后需要在WebMvcConfigurer中配置PseudoStaticPathHelper实例。 实现方式如下: 然后——这样就可以了。 不写示例应用了。在 Calf 这个项目中有类似的用法,有需要就看看。 End!

    [阅读更多...]