ElasticSearch8重置密码es8的高版本中，安装完之后默认会开启xpack.security.enabled安全项，即默认需要密码第一种方式是rpm安装之后，会提示设置密码，按照给出的提示执行即可第二种方式为正常的重置密码需要注意，两种方式都需要确保elasticsearch处于运行状态（service elasticsearch start）编辑elasticsearch.ymlrpm安装后的路径在/etc/elasticsearch/elasticsearch.yml正常来说把xpack.security.enabled设置为true，xpack.security.http.ssl.enabled设置为false即可# 安全总开关 xpack.security.enabled: true # 是否开启https，如果是内网环境的话一般把他置为false关掉也没事，开启的话就需要使用https来访问REST API # Enable encryption for HTTP API client connections, such as Kibana, Logstash, and Agents xpack.security.http.ssl: enabled: false keystore.path: certs/http.p12重置密码（第一种）rpm安装之后的提示如下，如果不需要kibana等服务，只需要输入以下指令即可：/usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic执行之后，输入y继续，elasticsearch就会输出用户elastic的一个随机密码，记住即可重置密码（第二种）找到elasticsearch的安装目录（rpm安装的默认在/usr/share/elasticsearch），进入到bin中，执行：./elasticsearch-setup-passwords interactive ****************************************************************************** Note: The 'elasticsearch-setup-passwords' tool has been deprecated. This command will be removed in a future release. ****************************************************************************** Initiating the setup of passwords for reserved users elastic,apm_system,kibana,kibana_system,logstash_system,beats_system,remote_monitoring_user. You will be prompted to enter passwords as the process progresses. Please confirm that you would like to continue [y/N]y # 这个就是elasticsearch的elastic用户的密码，如果不使用kibana等服务，改这个就行，当然全部写一样也无所谓 Enter password for [elastic]: Reenter password for [elastic]: Enter password for [apm_system]: Reenter password for [apm_system]: Enter password for [kibana_system]: Reenter password for [kibana_system]: Enter password for [logstash_system]: Reenter password for [logstash_system]: Enter password for [beats_system]: Reenter password for [beats_system]: Enter password for [remote_monitoring_user]: Reenter password for [remote_monitoring_user]: Changed password for user [apm_system] Changed password for user [kibana_system] Changed password for user [kibana] Changed password for user [logstash_system] Changed password for user [beats_system] Changed password for user [remote_monitoring_user] Changed password for user [elastic]完事之后重启elasticsearchservice elasticsearch restart之后使用elastic账户和自己设置的密码成功登录，注意如果提示EOF什么的，可能是因为你开启了ssl，需要使用https方式进行访问附上Java客户端使用方式@Data @ToString @ConfigurationProperties(prefix = "elasticsearch") public class ElasticsearchConfig { private String host = "localhost"; private Integer port = 9200; private String apiKey; private String user; private String password; }/** * 注册低级客户端 */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public RestClient getClient() { RestClientBuilder builder = RestClient.builder( new HttpHost(elasticsearchConfig.getHost(), elasticsearchConfig.getPort())); // apiKey鉴权方式 if (StrUtil.isNotBlank(elasticsearchConfig.getApiKey())) { builder.setDefaultHeaders(new Header[]{ new BasicHeader("Authorization", "ApiKey " + elasticsearchConfig.getApiKey()) }); } // 用户密码鉴权方式 if (StrUtil.isNotBlank(elasticsearchConfig.getUser()) && StrUtil.isNotBlank(elasticsearchConfig.getPassword())) { CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(elasticsearchConfig.getUser(), elasticsearchConfig.getPassword())); builder.setHttpClientConfigCallback(httpClientBuilder -> { httpClientBuilder.disableAuthCaching(); return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); }); } RestClient restClient = builder.build(); this.restClient = restClient; return restClient; }
使用MemoryAnalyzer来排查Java OutOfMemory问题工具arthasMemoryAnalyzerJDK17准备新版MemoryAnalyzer需要JDK17环境，手动下载JDK17并解压，然后在MemoryAnalyzer.exe执行程序目录下，找到MemoryAnalyzer.ini并编辑，在前面加上（第二行换成jdk17的地址）：-vm C:\Users\Administrator\.jdks\corretto-17.0.7\bin\javaw.exe完事保存即可步骤使用获取jvm heap dump文件，注意：主动获取操作会使服务暂时卡住，非必要时慎用使用arthas获取java -jar arthas-boot.jar # 选择进程后输入下面语句，后面为dump输出路径 heapdump /tmp/dump.hprof使用java自带的jmap获取# 最后跟的是jvm进程号 jmap -dump:file=/tmp/dump.hprof 需要DUMP的jvm进程号 # format=b指定为二进制格式文件 jmap -dump:format=b,file=/tmp/dump.hprof 需要DUMP的jvm进程号注意heap文件大小会等于jvm的heap大小，需要保证磁盘空间足够打开MemoryAnalyzer，注意MemoryAnalyzer的内存配置需要大于这个dump文件的大小，否则可能会卡死，内存配置在ini文件中改，最后一行-Xmx4096m点击File-Open Heap Dump，打开dump文件，之后程序会加载并分析heap文件，耐心等待即可List Object查看所有对象点击一下Retained Heap即可按照对象占用内存大小进行排序最后发现是spire.pdf中内存没有释放，回去查找对应代码修改即可
Java判断word文件版本业务中有调用一个office转换服务，转换服务是基于word转换的，但是有时候出现文件后缀名不匹配的情况时，word无法识别，就比如，这个文件实际上是word 2003文件，但是后缀名是docx，word就会打不开，从而转换失败解决办法转换之前判断文件头/** * 判断版本 */ public int checkDocx(String filePath) { //获取文件头判断版本 try (FileInputStream fileInputStream = new FileInputStream(filePath)) { byte[] b = new byte[4]; fileInputStream.read(b, 0, b.length); //Hutool的hexutil String value = HexUtil.encodeHexStr(b); logger.info("检测word文件头：" + value); //2003版本的word if ("d0cf11e0".equals(value)) { return 2003; } else { return 2007; } } catch (Exception e) { return -1; } }完事之后重命名一下文件再丢到转换服务就好了if (i == 2003 && "docx".equals(FileUtil.getExtensionName(file))) { //重命名文件为doc File file1 = new File(file); File file2 = new File(cn.hutool.core.io.FileUtil.getParent(file1, 1) + File.separator + FileUtil.getFileNameNoEx(file1.getName()) + ".doc"); File targetFile = cn.hutool.core.io.FileUtil.copy(file1, file2, true); }Fileutil/** * 获取文件扩展名，不带 . */ public static String getExtensionName(String filename) { if ((filename != null) && (filename.length() > 0)) { int dot = filename.lastIndexOf('.'); if ((dot > -1) && (dot < (filename.length() - 1))) { return filename.substring(dot + 1); } } return filename; } /** * Java文件操作 获取不带扩展名的文件名 */ public static String getFileNameNoEx(String filename) { if ((filename != null) && (filename.length() > 0)) { int dot = filename.lastIndexOf('.'); if ((dot > -1) && (dot < (filename.length()))) { return filename.substring(0, dot); } } return filename; }
Fastjson解析出来的JSONObject(map)没有按照原json排序的解决办法问题一个api返回了这样一个数据形式，请求这个api后，会用到其中的排序信息{ "state": "0", "success": true, "data": { "1": 55, "2": 81, "3": 39, "4": 45, "5": 54, "6": 24, "7": 70, "8": 106, "9": 72, "10": 32 } }使用如下代码，得到一个无序的的Map集合JSONObject res = JSON.parseObject(json); JSONObject data = res.getJSONObject("data");解决JSONObject res = JSON.parseObject(json, Feature.OrderedField); LinkedHashMap data = JSON.parseObject(res.getString("data"), LinkedHashMap.class);这样就得到一个有序Map集合
使用FastJson转将对象转为字符串时出现"$ref"的情况解决转Json发现业务执行有问题，打断点一看，转出来的json是这样的"files":[{"$ref":"$.formData.canWriteFiles.handwriting"},{"$ref":"$.formData.canWriteFiles.mainfile"}]主要是因为引用的内存对象重复才会出现这种情况FastJson对重复/循环引用的处理首先，fastjson作为一款序列化引擎，不可避免的会遇到循环引用的问题，为了避免StackOverflowError异常，fastjson会对引用进行检测。如果检测到存在重复/循环引用的情况，fastjson默认会以“引用标识”代替同一对象，而非继续循环解析导致StackOverflowError。解决办法避免重复引用内存对象，这当然是废话，直接看2吧局部关闭FastJson的引用检测JSON.toJSONString(object, SerializerFeature.DisableCircularReferenceDetect);springboot全局关闭FastJson引用检测 public class FastJsonHttpMessageConverterEx extends FastJsonHttpMessageConverter{ public FastJsonHttpMessageConverterEx(){ //在这里配置fastjson特性(全局设置的) FastJsonConfig fastJsonConfig = new FastJsonConfig(); //fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss"); //自定义时间格式 //fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteMapNullValue); //正常转换null值 //fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect); //关闭循环引用 this.setFastJsonConfig(fastJsonConfig); } @Override protected boolean supports(Class<?> clazz) { return super.supports(clazz); } } @Configuration public class WebMvcConfigurer extends WebMvcConfigurerAdapter { ..... @Bean public FastJsonHttpMessageConverterEx fastJsonHttpMessageConverterEx(){ return new FastJsonHttpMessageConverterEx(); } }创建新的对象，避免内存地址相同// 创建新的对象 Object newObj = new Object(); // 使用org.springframework.beans.BeansUtils复制属性值 BeansUtils.copyProperties(obj, newObj);https://www.jianshu.com/p/6041242405e8
使用JDBC获取Mysql数据库中符合条件的备份表并清理n天之前的表
使用JDBC获取Mysql数据库中符合条件的备份表并清理n天之前的表在执行同步操作之后会备份原来的表，导致线上数据库表越来越多，需要写一个定时清理这边定时清理使用xxl-job作为执行器，当然也可以当成正常方法来手动触发获取当前数据库名称select database() as dbname获取这个数据库中存在的符合条件的表我这边获取BACKUP_开头的表select table_name as name from information_schema.tables where table_schema= '" + dbname + "' and table_name like '" + "BACKUP_ + "%' order by table_name至于为什么不直接用show tables，这玩意貌似没法加like条件或者as xxx字段，有可能是我没找到方法完整代码如下/** * 定时清理备份的表 * @param needClear 需要清理的原始表名，多个表以逗号分隔 * @param clearDay 清理期限 * @throws Exception exception */ @XxlJob("定时清理备份的表") public void clearExpireBackupTable(String needClear, int clearDay) throws Exception { String jobParam = XxlJobHelper.getJobParam(); if (StringUtils.isNotBlank(jobParam)) { //使用xxl-job传递的参数：system_user,system_dept,system_user_dept|20 String[] split = jobParam.split("\\|"); needClear = split[0]; clearDay = Integer.parseInt(split[1]); } //清理的表只能以BACKUP为前缀 String[] split = needClear.split(","); //首先拿到所有backup开头的表 //获取主数据库名称 Map<String, Object> database = baseDao.findOneJdbc("select database() as name"); String dbname = (String) database.get("name"); //获取所有前缀匹配的表 String sql = "select table_name as name from information_schema.tables where table_schema= '" + dbname + "' and table_name like '" + BACKUP_PREFIX + "%' order by table_name"; List<Map<String, Object>> backupTableList = baseDao.findListJdbc(sql); for (Map<String, Object> stringObjectMap : backupTableList) { String tableName = (String) stringObjectMap.get("name"); //获取到改备份表的原始表名，例如：BACKUP_system_dept_2022_06_13_11_43_25 String regx = BACKUP_PREFIX + "([\\s\\S]+)_([\\d]{4}_[\\d]{2}_[\\d]{2}_[\\d]{2}_[\\d]{2}_[\\d]{2})"; String tableRealName = ReUtil.get(regx, tableName, 1); if (StringUtils.isNotBlank(tableRealName) && StringUtils.equalsAny(tableRealName, split)) { String tableCreateTimeStr = ReUtil.get(regx, tableName, 2); DateTime tableCreateTime = DateUtil.parse(tableCreateTimeStr, "yyyy_MM_dd_HH_mm_ss"); //判断这个表的创建时间是否超过设定期限 if (DateUtil.betweenDay(tableCreateTime, new Date(), false) > clearDay) { //清理掉这个表 XxlJobHelper.log("清理表：" + tableName); logger.info("清理表：" + tableName); baseDao.saveOrUpdateJdbc("drop table " + tableName); } } } XxlJobHelper.log("清理备份表任务正常结束！"); }
spring实现自定义注解切面编程例如要实现在某几个类之后或之前调用一些操作，就可以用到这个1. 创建自定义注解import java.lang.annotation.*; //注解放置的位置，METHOD是放在方法上 @Target(ElementType.METHOD) //注解在哪个阶段执行 @Retention(RetentionPolicy.RUNTIME) //生成文档 @Documented public @interface ElaSearchRefresh { String id() default ""; String className() default ""; }类中只需要写上自己需要的字段即可，后面可以作为传参使用2. 引入注解变量解析util照抄就完了import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.reflect.MethodSignature; import java.lang.reflect.Method; import java.util.Map; /** * 该类的作⽤可以把⽅法上的参数绑定到注解的变量中 * 注解的语法 * 变量名 * #{} * 能解析类似 * "#{sysOperateLogDto.operateType}" */ public class AnnotationResolver { private static AnnotationResolver resolver; public static AnnotationResolver newInstance() { if (resolver == null) { return resolver = new AnnotationResolver(); } else { return resolver; } } /** * 解析注解上的值 * * @param joinPoint 需要解析的字符串 * @param str * @return */ public Object resolver(JoinPoint joinPoint, String str) { if (str == null) return null; Object value = null; //如果匹配上了#则把内容当作变量 if (str.matches("#\\{\\D*\\}")) { String newStr = str.replaceAll("#\\{", "").replaceAll("\\}", ""); if (newStr.contains(".")) {//复杂类型 try { value = complexResolver(joinPoint, newStr); } catch (Exception e) { e.printStackTrace(); } } else { value = simpleResolver(joinPoint, newStr); } } else {//⾮变量 value = str; } return value; } private Object complexResolver(JoinPoint joinPoint, String str) throws Exception { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); String[] names = methodSignature.getParameterNames(); Object[] args = joinPoint.getArgs(); boolean jsonParseFlag = false; if (str.startsWith("$JSON$")){ jsonParseFlag = true; str = str.replace("$JSON$",""); } String[] strs = str.split("\\."); for (int i = 0; i < names.length; i++) { if (strs[0].equals(names[i])) { Object obj = args[i]; if (obj instanceof Map){ return ((Map<?, ?>) obj).get(strs[1]); } //支持json解析 if (obj instanceof String && jsonParseFlag){ JSONObject jsonObject = JSON.parseObject(obj.toString()); return jsonObject.get(strs[1]); } Method dmethod = obj.getClass().getDeclaredMethod(getMethodName(strs[1])); Object value = dmethod.invoke(args[i]); return getValue(value, 1, strs); } } return null; } private Object getValue(Object obj, int index, String[] strs) { try { if (obj != null && index < strs.length - 1) { Method method = obj.getClass().getDeclaredMethod(getMethodName(strs[index + 1])); obj = method.invoke(obj); getValue(obj, index + 1, strs); } return obj; } catch (Exception e) { e.printStackTrace(); return null; } } private String getMethodName(String name) { return "get" + name.replaceFirst(name.substring(0, 1), name.substring(0, 1).toUpperCase()); } private Object simpleResolver(JoinPoint joinPoint, String str) { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); String[] names = methodSignature.getParameterNames(); Object[] args = joinPoint.getArgs(); for (int i = 0; i < names.length; i++) { if (str.equals(names[i])) { return args[i]; } } return null; } } 3. 创建切面类import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.*; @Aspect @Component public class ElaSearchAspect { @Autowired private IBaseDao baseDao; //定义切点 @Pointcut(value = "@annotation(net.zjsos.business.common.business.filebase.aspect.ElaSearchRefresh)") public void elaRefreshPointCut() { } //定义切面 @After("elaRefreshPointCut()") //around需要使用ProceedingJoinPoint point public void after(JoinPoint point) throws Throwable { MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); ElaSearchRefresh annotation = method.getAnnotation(ElaSearchRefresh.class); //利用util来反射解析动态变量属性 String id = AnnotationResolver.newInstance().resolver(point, annotation.id()).toString(); //如果是写死的变量，直接使用annotation.id()获取即可 } //定义切面 @Around("elaRefreshPointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { //去执⾏被访问的切⾯ Object result = point.proceed(); //从切⾯织⼊点处通过反射机制获取织⼊点处 MethodSignature signature = (MethodSignature) point.getSignature(); //获取切⼊点所在的方法 Method method = signature.getMethod(); ElaSearchRefresh annotation = method.getAnnotation(ElaSearchRefresh.class); //利用util来反射解析动态变量属性 String id = AnnotationResolver.newInstance().resolver(point, annotation.id()).toString(); //如果是写死的变量，直接使用annotation.id()获取即可 } }4. 使用注解@RequestMapping("/updateAccessByApplyId") //获取动态变量需要加"#{}"，写死的变量直接"xxx"就行了 @ElaSearchRefresh(id = "#{dto.id}") public ResultDto updateAccessByApplyId(@RequestBody @Valid FileBaseUpdateAccessDto dto){ xxxxxxxxxxxxxxxxxxxxxx }
关于inputStream在第二次使用时无内容的问题使用IOUtils对inputStream进行操作之后，再次调用inputStream发现其length变为了0IOUtils.toByteArray(inputStream)解决办法：先将inputStream流读到byte中缓存住，然后需要使用时每次都重新生成流//读取inputStream流到byte数组 byte [] content = IOUtils.toByteArray(inputStream); //需要使用时都需要新建一个流 IOUtils.copy(new ByteArrayInputStream(content), myOutputStream); doSomethingElse(new ByteArrayInputStream(content));
因为需要将应用服务器之外的另一台文件服务器的ip地址隐藏，并且需要记录文件访问记录，以及需要对request的请求做一些处理，nginx实现这些相当麻烦，因此直接用Java写一个反向代理，简单明了。utils相当于使用流拷贝的方式，将获取到的newUrl的response，拷贝到客户请求的response中，这样就实现了转发 import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.util.StreamUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; public class ProxyUtil { public static void proxy(URI newUri, HttpServletRequest request, HttpServletResponse response) throws IOException { String methodName = request.getMethod(); HttpMethod httpMethod = HttpMethod.resolve(methodName); if(httpMethod == null) { return; } ClientHttpRequest delegate = new SimpleClientHttpRequestFactory().createRequest(newUri, httpMethod); Enumeration<String> headerNames = request.getHeaderNames(); // 设置请求头 while (headerNames.hasMoreElements()) { String headerName = headerNames.nextElement(); Enumeration<String> v = request.getHeaders(headerName); List<String> arr = new ArrayList<>(); while (v.hasMoreElements()) { arr.add(v.nextElement()); } delegate.getHeaders().addAll(headerName, arr); } StreamUtils.copy(request.getInputStream(), delegate.getBody()); // 执行远程调用 ClientHttpResponse clientHttpResponse = delegate.execute(); response.setStatus(clientHttpResponse.getStatusCode().value()); // 设置响应头 clientHttpResponse.getHeaders().forEach((key, value) -> value.forEach(it -> { response.setHeader(key, it); })); StreamUtils.copy(clientHttpResponse.getBody(), response.getOutputStream()); } }controller//经过/proxy/**的地址都会返回192.168.1.1的结果，相当于nginx反向代理的效果 @RequestMapping(value = "/proxy/**") public void proxy(HttpServletRequest request, HttpServletResponse response){ String path = uri.getPath(); URI newUri = new URI("http://192.168.1.1" + path.replace("/proxy","")) ProxyUtil.proxy(newUri,request,response); }这样加上后，所有经过/proxy/**的地址都会走一遍Java代码，你可以在代码中增加登录验证等功能
下载soapUI选择File-New SOAP project然后填入wsdl文件路径或者url地址并创建工程点击OK后，程序会自动获取wsdl文件中的接口信息并展示在列表中测试接口双击生成的request即可进入测试请求窗口，程序已经自动生成好了参数，仅需将?改为你想要的传参即可生成java代码按图中操作，如果是第一次生成需要点击右下角的tools，配置axis路径，如果没有axis，还需要下载axis配置完毕之后选择输出路径，程序会自动输出java文件。Java代码用法第一次看见这个代码说实在也有点懵，不知道怎么用生成的文件中可能会有实体类，这个应该能一眼看出来，别的就是service和实现impl类了生成的时候已经定义了默认请求网址，当然你也可以传入URL来定义你的自定义请求网址：public ExOaWebserviceNewMng getExService() throws MalformedURLException, ServiceException { URL url = new URL(urlStr); ExOaWebserviceNewMngImplService exOaWebserviceNewMngImplService = new ExOaWebserviceNewMngImplServiceLocator(); return exOaWebserviceNewMngImplService.getExOaWebserviceNewMngImplPort(url); }利用该方法返回的对象即可调用自动生成的方法，虽然http返回结果为xml形式，但是内部已经封装好了，因此调用的实际结果就是返回的值，无需再做处理，可以直接拿来用