有关软件开发编程知识及学习心得
3月20
redis读取数据失败,打印异常信息如下:
Could not read JSON: Invalid UTF-32 character 0x22636364 (above 0x0010ffff) at char #15, byte #63); nested exception is java.io.CharConversionException: Invalid UTF-32 character 0x22636364 (above 0x0010ffff) at char #15, byte #63)
问题的原因是我在添加数据时设置了存活时间但是忘记指定单位了;
正确的应该是再指定TimeUnit.SECONDS参数。
时间单位:
天:TimeUnit.DAYS
小时:TimeUnit.HOURS
分钟:TimeUnit.MINUTES
秒:TimeUnit.SECONDS
毫秒:TimeUnit.MILLISECONDS
Could not read JSON: Invalid UTF-32 character 0x22636364 (above 0x0010ffff) at char #15, byte #63); nested exception is java.io.CharConversionException: Invalid UTF-32 character 0x22636364 (above 0x0010ffff) at char #15, byte #63)
问题的原因是我在添加数据时设置了存活时间但是忘记指定单位了;
正确的应该是再指定TimeUnit.SECONDS参数。
时间单位:
天:TimeUnit.DAYS
小时:TimeUnit.HOURS
分钟:TimeUnit.MINUTES
秒:TimeUnit.SECONDS
毫秒:TimeUnit.MILLISECONDS
3月17
下面列举下EasyPoi支持的指令以及作用,最主要的就是各种fe的用法文字
三元运算 {{test ? obj:obj2}}
n: 表示 这个cell是数值类型 {{n:}}
le: 代表长度{{le:()}} 在if/else 运用{{le:() > 8 ? obj1 : obj2}}
fd: 格式化时间 {{fd:(obj;yyyy-MM-dd)}}
fn: 格式化数字 {{fn:(obj;###.00)}}
fe: 遍历数据,创建row
!fe: 遍历数据不创建row
$fe: 下移插入,把当前行,下面的行全部下移.size()行,然后插入
#fe: 横向遍历
v_fe: 横向遍历值
!if: 删除当前列 {{!if:(test)}}
单引号表示常量值 ‘’ 比如’1’ 那么输出的就是 1
&NULL& 空格
&INDEX& 表示循环中的序号,自动添加,看到这里应该就明白怎么在表格中增加一列序号了。
]] 换行符 多行遍历导出
sum: 统计数据
三元运算 {{test ? obj:obj2}}
n: 表示 这个cell是数值类型 {{n:}}
le: 代表长度{{le:()}} 在if/else 运用{{le:() > 8 ? obj1 : obj2}}
fd: 格式化时间 {{fd:(obj;yyyy-MM-dd)}}
fn: 格式化数字 {{fn:(obj;###.00)}}
fe: 遍历数据,创建row
!fe: 遍历数据不创建row
$fe: 下移插入,把当前行,下面的行全部下移.size()行,然后插入
#fe: 横向遍历
v_fe: 横向遍历值
!if: 删除当前列 {{!if:(test)}}
单引号表示常量值 ‘’ 比如’1’ 那么输出的就是 1
&NULL& 空格
&INDEX& 表示循环中的序号,自动添加,看到这里应该就明白怎么在表格中增加一列序号了。
]] 换行符 多行遍历导出
sum: 统计数据
3月8
1、前端封装JSON值,后台需要List<实体类>接收
Map map = jsonObject.getInnerMap();
List<RecommendDTO> recommendDTOlist = (List<RecommendDTO>) map.get("xxx");
2、进行forearch循环的时候报错
recommendDTOlist .forEach((item)->{})
3、从redis中获取数据后进行遍历
List<RecommendDTO> recommendDTOlist = redisUtil.get(defaultCacheKey);
for (RecommendDTO recommendDTO : defaultRecommendList) {
}
报错信息:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.heckjj.apps.modules.smartpush.dto.RecommendDTO
4、打断点调试查看发现里面封装的是两个Map 而不是实体类而是个LinkedHashMap
5、解决方法
ObjectMapper mapper = new ObjectMapper();
List<RecommendDTO> recommendDTOlist = (List<RecommendDTO>) map.get("xxx");
List<RecommendDTO> recommendDTOlist = mapper.convertValue(list1, new TypeReference<List<RecommendDTO>>() { });
记住引入包路径是下面这两个
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
Map map = jsonObject.getInnerMap();
List<RecommendDTO> recommendDTOlist = (List<RecommendDTO>) map.get("xxx");
2、进行forearch循环的时候报错
recommendDTOlist .forEach((item)->{})
3、从redis中获取数据后进行遍历
List<RecommendDTO> recommendDTOlist = redisUtil.get(defaultCacheKey);
for (RecommendDTO recommendDTO : defaultRecommendList) {
}
报错信息:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.heckjj.apps.modules.smartpush.dto.RecommendDTO
4、打断点调试查看发现里面封装的是两个Map 而不是实体类而是个LinkedHashMap
5、解决方法
ObjectMapper mapper = new ObjectMapper();
List<RecommendDTO> recommendDTOlist = (List<RecommendDTO>) map.get("xxx");
List<RecommendDTO> recommendDTOlist = mapper.convertValue(list1, new TypeReference<List<RecommendDTO>>() { });
记住引入包路径是下面这两个
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
1月10
系统中对密码复杂度的校验是比较常见的工作,往往我们可以通过正则来实现,或者基于规则而实现特定的算法来满足需求。
下面我来介绍两个开源的解决方案。
1.使用vt-password来实现密码复杂度的检查
VT 密码是一个 Java 库,用于验证密码是否符合定义的规则集。
该库包括以下规则实现:
AllowedCharacterRule - 密码是否只包含特定的字符列表
AlphabeticalSequenceRule - 密码是否包含字母顺序
CharacterCharacteristicRule - 密码是否包含所需的字符类型组合
DictionaryRule - 密码是否与字典中的单词匹配
DictionarySubstringRule - 密码是否包含字典中的单词
DigitCharacterRule - 密码是否包含数字
HistoryRule - 密码是否与以前的密码匹配,支持散列
IllegalCharacterRule - 密码是否包含非法字符
LengthRule - 是一定长度的密码
LowercaseCharacterRule - 密码是否包含小写字符
NonAlphanumericCharacterRule - 密码是否包含非字母数字字符
NumericalSequenceRule - 密码是否包含数字序列
RegexRule - 密码是否与正则表达式匹配
RepeatCharacterRegexRule - 密码是否包含重复字符
SequenceRule - 密码是否包含键盘序列
SourceRule - 密码是否与来自另一个系统或来源的密码匹配
QwertySequenceRule - 密码是否包含 QWERTY 键盘序列
UppercaseCharacterRule - 密码是否包含大写字符
UsernameRule - 密码是否包含用户名
WhitespaceRule - 密码是否包含空格
如果你想在你的Maven构建中使用这个项目,请在你的pom.xml 中包含以下内容:
<dependency>
<groupId>edu.vt.middleware</groupId>
<artifactId>vt-password</artifactId>
<version>3.1.2</version>
</dependency>
下面我来介绍两个开源的解决方案。
1.使用vt-password来实现密码复杂度的检查
VT 密码是一个 Java 库,用于验证密码是否符合定义的规则集。
该库包括以下规则实现:
AllowedCharacterRule - 密码是否只包含特定的字符列表
AlphabeticalSequenceRule - 密码是否包含字母顺序
CharacterCharacteristicRule - 密码是否包含所需的字符类型组合
DictionaryRule - 密码是否与字典中的单词匹配
DictionarySubstringRule - 密码是否包含字典中的单词
DigitCharacterRule - 密码是否包含数字
HistoryRule - 密码是否与以前的密码匹配,支持散列
IllegalCharacterRule - 密码是否包含非法字符
LengthRule - 是一定长度的密码
LowercaseCharacterRule - 密码是否包含小写字符
NonAlphanumericCharacterRule - 密码是否包含非字母数字字符
NumericalSequenceRule - 密码是否包含数字序列
RegexRule - 密码是否与正则表达式匹配
RepeatCharacterRegexRule - 密码是否包含重复字符
SequenceRule - 密码是否包含键盘序列
SourceRule - 密码是否与来自另一个系统或来源的密码匹配
QwertySequenceRule - 密码是否包含 QWERTY 键盘序列
UppercaseCharacterRule - 密码是否包含大写字符
UsernameRule - 密码是否包含用户名
WhitespaceRule - 密码是否包含空格
如果你想在你的Maven构建中使用这个项目,请在你的pom.xml 中包含以下内容:
<dependency>
<groupId>edu.vt.middleware</groupId>
<artifactId>vt-password</artifactId>
<version>3.1.2</version>
</dependency>
12月9
公司有个项目不能解析内网所以需要对本地hosts文件进行修改,添加一条本地域名解析记录,如果让客户去操作,很容易破坏掉原先的hosts文件,用户只需要以管理员权限运行即可,如果记录存在则替换掉。
由于办公电脑都使用信创麒麟的系统,所以最好做一个deb的安装包,通过程序写修改的,在信创麒麟的系统电脑下载安装即可执行。
下面是实现逻辑,要将java打包成deb安装包就需要使用jdeb的maven插件来打包。
我使用的是1.8的版本,具体配置就不贴上来了,需要的联系我。
<artifactId>jdeb</artifactId>
<groupId>org.vafer</groupId>
<version>1.8</version>
package com.nine.rivers.jdeb;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.util.StrUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* @Description 不破坏原有hosts文件,支持新host绑定或修改支持host解绑
* @Date 2022/12/9 16:42
* @Author heck
**/
public class HostUtil {
public static final String LINUX = "linux";
public static final String LINUX_HOSTS_PATH = "/etc/hosts";
public static final String WINDIR = "windir";
public static final String WIN_HOSTS_PATH = "\\system32\\drivers\\etc\\hosts";
public static final String OS_NAME = "os.name";
/**
* 获取host文件路径
*
* @return
*/
public static String getHostFile() {
String fileName = null;
// 判断系统
if (LINUX.equalsIgnoreCase(System.getProperty(OS_NAME))) {
fileName = LINUX_HOSTS_PATH;
} else {
fileName = System.getenv(WINDIR) + WIN_HOSTS_PATH;
}
return fileName;
}
由于办公电脑都使用信创麒麟的系统,所以最好做一个deb的安装包,通过程序写修改的,在信创麒麟的系统电脑下载安装即可执行。
下面是实现逻辑,要将java打包成deb安装包就需要使用jdeb的maven插件来打包。
我使用的是1.8的版本,具体配置就不贴上来了,需要的联系我。
<artifactId>jdeb</artifactId>
<groupId>org.vafer</groupId>
<version>1.8</version>
package com.nine.rivers.jdeb;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.util.StrUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* @Description 不破坏原有hosts文件,支持新host绑定或修改支持host解绑
* @Date 2022/12/9 16:42
* @Author heck
**/
public class HostUtil {
public static final String LINUX = "linux";
public static final String LINUX_HOSTS_PATH = "/etc/hosts";
public static final String WINDIR = "windir";
public static final String WIN_HOSTS_PATH = "\\system32\\drivers\\etc\\hosts";
public static final String OS_NAME = "os.name";
/**
* 获取host文件路径
*
* @return
*/
public static String getHostFile() {
String fileName = null;
// 判断系统
if (LINUX.equalsIgnoreCase(System.getProperty(OS_NAME))) {
fileName = LINUX_HOSTS_PATH;
} else {
fileName = System.getenv(WINDIR) + WIN_HOSTS_PATH;
}
return fileName;
}
11月30
原因:SpringBoot内嵌web容器,其特点是只有一个jar文件,在容器启动后不会解压缩。
解决方式:
1. 必须使用相对路径读取文件;
假设你的模板文件放在了 resources —> templates —> test.xlsx
2. 只能使用流去读取,不能用file;
// jar里面文件读取方式:
ClassPathResource classPathResource = new ClassPathResource("templates/test.xlsx");
// 获取文件流
classPathResource .getInputStream();
如果要将流存储到数组中如下:
/**
* 输出流转字节数组
* @param input 输出流
* @return 字节数组
*/
public static byte[] toByteArray(InputStream input) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buffer = new byte[1024 * 4];
int size = 0;
while (-1 != (size = input.read(buffer))) {
output.write(buffer, 0, size);
}
return output.toByteArray();
}
解决方式:
1. 必须使用相对路径读取文件;
假设你的模板文件放在了 resources —> templates —> test.xlsx
2. 只能使用流去读取,不能用file;
// jar里面文件读取方式:
ClassPathResource classPathResource = new ClassPathResource("templates/test.xlsx");
// 获取文件流
classPathResource .getInputStream();
如果要将流存储到数组中如下:
/**
* 输出流转字节数组
* @param input 输出流
* @return 字节数组
*/
public static byte[] toByteArray(InputStream input) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buffer = new byte[1024 * 4];
int size = 0;
while (-1 != (size = input.read(buffer))) {
output.write(buffer, 0, size);
}
return output.toByteArray();
}
11月27
Archetype是用于创建项目的骨架(或者模板),通过Archetype我们可以创建类似的Maven工程,同时Archetype能够极大的简化我们创建一个工程的步骤和流程。这里我将介绍自定义Maven的工程模板Archetype的方法和流程,这里采用的方法是从现有工程创建工程模板。
关于Archetype介绍参考:Introduction to Archetypes
一、Archetype自定义流程:
1、创建模板工程:找到一个现有的项目,进行编辑,将项目中的包结构、各类文件放置到合适的位置;
2、从模板工程创建Archetype:打开cmd窗口,切换当前目录到上面的工程目录下,执行maven命令:
mvn archetype:create-from-project
执行完成后,target/generated-sourced/archetype目录下就是我们需要的项目模板。
3、安装Archetype到本地仓库:cd进入target/generated-sourced/archetype中,执行命令:
mvn -Dmaven.test.skip=true clean install
将自定义Archetype安装到本地仓库即可:此时,自定义archetype就被安装到settings.xml中<localRepository>指定的本地仓库中,我的机器本地目录是D:/q/repos-maven;另外,archetype安装到本地仓库后,会在.m2/archetype-catalog.xml中加入对应的archetype节点,结构如下:
<archetype>
<groupId>com.heckjj.blog</groupId>
<artifactId>crm-archetype-archetype</artifactId>
<version>1.0.0</version>
<description>The parent pom of crm</description>
</archetype>
4、使用自定义Archetype创建工程:
这样我们就创建成功了自定义的Archetype,可以通过命令从本地模板创建工程:
mvn archetype:generate -DarchetypeCatalog=local
以上就是创建自定义Archetype的简单流程。
关于Archetype介绍参考:Introduction to Archetypes
一、Archetype自定义流程:
1、创建模板工程:找到一个现有的项目,进行编辑,将项目中的包结构、各类文件放置到合适的位置;
2、从模板工程创建Archetype:打开cmd窗口,切换当前目录到上面的工程目录下,执行maven命令:
mvn archetype:create-from-project
执行完成后,target/generated-sourced/archetype目录下就是我们需要的项目模板。
3、安装Archetype到本地仓库:cd进入target/generated-sourced/archetype中,执行命令:
mvn -Dmaven.test.skip=true clean install
将自定义Archetype安装到本地仓库即可:此时,自定义archetype就被安装到settings.xml中<localRepository>指定的本地仓库中,我的机器本地目录是D:/q/repos-maven;另外,archetype安装到本地仓库后,会在.m2/archetype-catalog.xml中加入对应的archetype节点,结构如下:
<archetype>
<groupId>com.heckjj.blog</groupId>
<artifactId>crm-archetype-archetype</artifactId>
<version>1.0.0</version>
<description>The parent pom of crm</description>
</archetype>
4、使用自定义Archetype创建工程:
这样我们就创建成功了自定义的Archetype,可以通过命令从本地模板创建工程:
mvn archetype:generate -DarchetypeCatalog=local
以上就是创建自定义Archetype的简单流程。
11月24
在平时编码过程中我们都知道要抽象,要封装变化,要实现开闭原则,比如对于很多相似的功能,我们可以将通用的功能抽象出来,然后把变化的不同的地方提取出去,比如模版模式、策略模式等都是实现类似的效果
比如对于策略模式,我们通常是定义一个接口,然后有不同的实现,这种是可以的,但是如果通用流程中要扩展的点较多的话,这些不同的实现也需要管理,可以把他们合并到一个单独的包中,再进一步,我们甚至可以将包单独提取出来,支持运行时加载包实现新增功能的支持
JDK对此功能的支持就是 SPI,但是它的限制较多,也不够灵活,比如dubbo就是自己定义了一套SPI的实现,这次我们来看另一个实现,pf4j 提供一套在基本框架中定义扩展点接口,然后通过不同的插件来实现扩展点的功能,来支持对新增开放对修改关闭
下面我们就来学习一下它的使用
使用
比如我们有一套通用的流程,假设是下单流程,不同的业务线等对于下单都有一些特殊点,但是它们的基本流程是相似的,这时候我们就可以先定义好通用的流程,不同的地方预留出扩展点接口,使用 pf4j 的流程如下
先定义好扩展点接口(需要定义单独的包,因为基本应用和各个扩展点的包都依赖它)
定义单独的插件包,其中实现扩展点接口的功能
在应用中编写基本流程和扩展点的发现使用功能
这次我们就参考 pf4j 提供的例子来看一下
1. 定义扩展点接口
pom.xml首先声明依赖
<dependency>
<groupId>org.pf4j</groupId>
<artifactId>pf4j</artifactId>
<version>3.6.0</version>
<!-- 一般应用中会依赖这个包,所以这里设置为provided即可 -->
<scope>provided</scope>
</dependency>
之后即可声明各个扩展点接口
/**
* 假设我们需要一个通知用户的功能
* 需要注意的是,我们一定要继承 ExtensionPoint 接口,表示这是一个扩展点
*/
public interface Notice extends ExtensionPoint {
boolean notice(List<Long> userIds);
}
比如对于策略模式,我们通常是定义一个接口,然后有不同的实现,这种是可以的,但是如果通用流程中要扩展的点较多的话,这些不同的实现也需要管理,可以把他们合并到一个单独的包中,再进一步,我们甚至可以将包单独提取出来,支持运行时加载包实现新增功能的支持
JDK对此功能的支持就是 SPI,但是它的限制较多,也不够灵活,比如dubbo就是自己定义了一套SPI的实现,这次我们来看另一个实现,pf4j 提供一套在基本框架中定义扩展点接口,然后通过不同的插件来实现扩展点的功能,来支持对新增开放对修改关闭
下面我们就来学习一下它的使用
使用
比如我们有一套通用的流程,假设是下单流程,不同的业务线等对于下单都有一些特殊点,但是它们的基本流程是相似的,这时候我们就可以先定义好通用的流程,不同的地方预留出扩展点接口,使用 pf4j 的流程如下
先定义好扩展点接口(需要定义单独的包,因为基本应用和各个扩展点的包都依赖它)
定义单独的插件包,其中实现扩展点接口的功能
在应用中编写基本流程和扩展点的发现使用功能
这次我们就参考 pf4j 提供的例子来看一下
1. 定义扩展点接口
pom.xml首先声明依赖
<dependency>
<groupId>org.pf4j</groupId>
<artifactId>pf4j</artifactId>
<version>3.6.0</version>
<!-- 一般应用中会依赖这个包,所以这里设置为provided即可 -->
<scope>provided</scope>
</dependency>
之后即可声明各个扩展点接口
/**
* 假设我们需要一个通知用户的功能
* 需要注意的是,我们一定要继承 ExtensionPoint 接口,表示这是一个扩展点
*/
public interface Notice extends ExtensionPoint {
boolean notice(List<Long> userIds);
}
11月24
PF4J是一个Java轻量级的插件框架,可以实现动态加载,执行,卸载外部插件(支持jar以及zip),具体可以看官网:https://pf4j.org/。
本文例子基于Github地址:https://github.com/pf4j/pf4j
<dependency>
<groupId>org.pf4j</groupId>
<artifactId>pf4j</artifactId>
<version>3.6.0</version>
</dependency>
插件项目会涉及到3个工程:工程结构
plugin-api:定义可扩展接口
plugins:插件项目,可以包含多个插件,需要实现plugin-api中定义的接口
plugin-app:主程序,需要依赖plugin-api,加载并执行plugins
定义可扩展接口(plugin-api)
简单定义一个接口,需继承ExtensionPoint:
package plugin.api;
import org.pf4j.ExtensionPoint;
public interface Greeting extends ExtensionPoint {
String getGreeting();
}
实现插件(plugins)
插件需要实现plugin-api定义的接口,并且使用@Extension标记:
package plugins;
import org.pf4j.Extension;
import plugin.api.Greeting;
@Extension
public class WelcomeGreeting implements Greeting {
public String getGreeting() {
return "Welcome";
}
}
插件打包(plugins)
插件打包时,需要往MANIFEST.MF写入插件信息,此处使用maven插件(打包命令为package):
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.3.1</version>
<configuration>
<archive>
<manifestEntries>
<Plugin-Id>welcome-plugin</Plugin-Id>
<Plugin-Version>0.0.1</Plugin-Version>
</manifestEntries>
</archive>
</configuration>
</plugin>
本文例子基于Github地址:https://github.com/pf4j/pf4j
<dependency>
<groupId>org.pf4j</groupId>
<artifactId>pf4j</artifactId>
<version>3.6.0</version>
</dependency>
插件项目会涉及到3个工程:工程结构
plugin-api:定义可扩展接口
plugins:插件项目,可以包含多个插件,需要实现plugin-api中定义的接口
plugin-app:主程序,需要依赖plugin-api,加载并执行plugins
定义可扩展接口(plugin-api)
简单定义一个接口,需继承ExtensionPoint:
package plugin.api;
import org.pf4j.ExtensionPoint;
public interface Greeting extends ExtensionPoint {
String getGreeting();
}
实现插件(plugins)
插件需要实现plugin-api定义的接口,并且使用@Extension标记:
package plugins;
import org.pf4j.Extension;
import plugin.api.Greeting;
@Extension
public class WelcomeGreeting implements Greeting {
public String getGreeting() {
return "Welcome";
}
}
插件打包(plugins)
插件打包时,需要往MANIFEST.MF写入插件信息,此处使用maven插件(打包命令为package):
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.3.1</version>
<configuration>
<archive>
<manifestEntries>
<Plugin-Id>welcome-plugin</Plugin-Id>
<Plugin-Version>0.0.1</Plugin-Version>
</manifestEntries>
</archive>
</configuration>
</plugin>
11月22
运行SpringBoot的时候报如下错Consider defining a bean of type ‘com.google.code.kaptcha.Producer’ in your configuration.
报错原因为配置中找不到一个指定自动注入类型的bean。
那么我们要从collecter层开始查找,点击service层,看service实现类是否加上@Service或者@Component,检查service实现类是否有implements service。如果这些都没有问题:
我们来看@SpringBootApplication,点过去。可以看到有个@ComponentScan,ComponentScan做的事情就是告诉Spring从哪里找到bean
那可以直接在@SpringBootApplication加上
还有一种问题,你的spring启动类不在同一个父包路径下,比如你的其它类在com.heckjj.blog,而你的启动类在heckjj下,也会报错扫不到其它的类,比如说service找不到。
报错原因为配置中找不到一个指定自动注入类型的bean。
那么我们要从collecter层开始查找,点击service层,看service实现类是否加上@Service或者@Component,检查service实现类是否有implements service。如果这些都没有问题:
我们来看@SpringBootApplication,点过去。可以看到有个@ComponentScan,ComponentScan做的事情就是告诉Spring从哪里找到bean
那可以直接在@SpringBootApplication加上
还有一种问题,你的spring启动类不在同一个父包路径下,比如你的其它类在com.heckjj.blog,而你的启动类在heckjj下,也会报错扫不到其它的类,比如说service找不到。