bluesbruce

bluesbruce 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

bluesbruce 收藏了文章 · 9月3日

自定义类加载器-从.class和.jar中读取

一. 类加载器

  1. JVM中的类加载器:在jvm中,存在两种类加载器,
    a) Boostrap ClassLoader:这个是由c++实现的,所以在方法区并没有Class对象的实例存在。用于加载JAVA_HOME/bin目录下的jar包

b) 其他类加载器:由java实现,可以在方法区找到其Class对象。这里又细分为几个加载器

  1. 扩展类加载器(Extension ClassLoader):它负责用于加载JAVA_HOME/lib/ext目录中的,或者被java.ext.dirs系统变量指定所指定的路径中所有类库,开发者可以直接使用扩展类加载器。java.ext.dirs系统变量所指定的路径的可以通过程序来查看。System.getProperty("java.ext.dirs")

  2. 应用程序类加载器(Application ClassLoader):负责加载用户类路径上指定的类库。开发者可以直接使用这个类加载器。ps:在没有指定自定义类加载器的情况下,这就是程序的默认加载器。

  3. 自定义类加载器(User ClassLoader):

  4. 双亲委派模型:避免由于Class字节码被多次加载。底层类加载器在收到一个类加载的请求的时候,都先把请求转发给其父加载器(并不是一个继承的关系),父类查找不到才会让子类去加载。如果强制只有双亲委派模型,那么,web服务器的隔离是无法实现的。

  由于最近想做一个类似tomcat一样的简易版web服务器来加深理解http请求的处理过程。我们都清楚,每个web应用在tomcat中都可以使用自己版本的jar。除了少量的包,如Servlet-api.jar,还有一些java原生的包之外,tomcat是会为每个不同的应用加载不同的jar包或者class,且彼此之间不会相互影响。这一步是通过自定义类加载器来实现的,在虚拟机层面,判断两个Class是否相等的前提是他们是同一个类加载器加载的,否则就没有意义了。这篇文章简单的实现一个自定义的加载过程,PS:这个例子并没有破坏双亲委派模型,因为例子中依然会查找父类,如果找不到再使用子类加载。接下来笔者会再更新破坏双亲委派模型的博客,这里挖个坑。
  首先自定义类加载器,最重要的就是先继承ClassLoader这个类。加载器的加载流程是,给出一个Class文件的全限定名,然后调用loadClass方法,这个方法每部会现在自己已经加载的类中查找,如果找到就返回。找不到则向父类查找,如果父类都找不到这才开始自己加载,调用findClass方法。所以我们只要覆盖findClass方法就可以实现自己定义的加载了。顺带一提,ClassLoader中有一个方法叫defineClass(String, byte[], int, int);这个方法通过传进去一个Class文件的字节数组,就可以方法区生成一个Class对象。所以要实现findClass的目标就很明确了,只要将Class文件读取进来,然后生成byte数组,调用defineClass方法就可以了。

@Override
    protected Class<?> findClass(String name){
        try {
            byte[] result = getClassFromFileOrMap(name);
            if(result == null){
                throw new FileNotFoundException();
            }else{
                return defineClass(name, result, 0, result.length);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

  那么如何找到Class文件呢?手动生成/网络下载我们就暂时不谈论。这里只说两种最常见的,一是直接.class文件中查找,二是从jar包中加载。
从class文件中加载非常简单。只要找到相应的文件,就可以通过字节流读取进来。代码如下:

input = new FileInputStream(file);
ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
int bufferSize = 4096; 
byte[] buffer = new byte[bufferSize]; 
int bytesNumRead = 0; 
while ((bytesNumRead = input.read(buffer)) != -1) { 
    baos.write(buffer, 0, bytesNumRead); 
}
return baos.toByteArray();

  从jar读取则相对麻烦一点,java给我们提供了一个专门用来读取jar包文件的类,抽象成一个JarFile的对象。通过调用这个对象的getInputStream方法,也是可以获取文件的输入流,从而读取字节数组。笔者做了一点相应的缓存,如果每次查找文件都要先读取jar文件,再遍历查找class文件是非常耗时的操作。于是,笔者选择再加载之前,把所有的jar包中的所有class读取到内存中,保存在一个map对象中。建立一个全限定名和字节数组的映射。这样在加载阶段,就能省下很多的时间了。全部的代码如下

package com.chasel.cloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 自定义的类加载器【子类优先】
 * @author hujiancai
 * @description 
 * @data 2017年3月11日
 * @version v_0.1
 */
public class MyWebAppLoader extends ClassLoader{
    /**
     * lib:表示加载的文件在jar包中
     * 类似tomcat就是{PROJECT}/WEB-INF/lib/
     */
    private String lib;
    /**
     * classes:表示加载的文件是单纯的class文件
     * 类似tomcat就是{PROJECT}/WEB-INF/classes/
     */
    private String classes;
    /**
     * 采取将所有的jar包中的class读取到内存中
     * 然后如果需要读取的时候,再从map中查找
     */
    private Map<String, byte[]> map;
    
    /**
     * 只需要指定项目路径就好
     * 默认jar加载路径是目录下{PROJECT}/WEB-INF/lib/
     * 默认class加载路径是目录下{PROJECT}/WEB-INF/classes/
     * @param webPath
     * @throws MalformedURLException 
     * @throws SecurityException 
     * @throws NoSuchMethodException 
     */
    public MyWebAppLoader(String webPath) throws NoSuchMethodException, SecurityException, MalformedURLException{
        lib = webPath + "WEB-INF/lib/";
        classes = webPath + "WEB-INF/classes/";
        map = new HashMap<String,byte[]>(64);
        
        preReadJarFile();
    }

    /**
     * 按照父类的机制,如果在父类中没有找到的类
     * 才会调用这个findClass来加载
     * 这样只会加载放在自己目录下的文件
     * 而系统自带需要的class并不是由这个加载
     */
    @Override
    protected Class<?> findClass(String name){
        try {
            byte[] result = getClassFromFileOrMap(name);
            if(result == null){
                throw new FileNotFoundException();
            }else{
                return defineClass(name, result, 0, result.length);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    
    /**
     * 从指定的classes文件夹下找到文件
     * @param name
     * @return
     */
    private byte[] getClassFromFileOrMap(String name){
        String classPath = classes + name.replace('.', File.separatorChar) + ".class";
        File file = new File(classPath);
        if(file.exists()){
            InputStream input = null;
            try {
                input = new FileInputStream(file);
                ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
                int bufferSize = 4096; 
                byte[] buffer = new byte[bufferSize]; 
                int bytesNumRead = 0; 
                while ((bytesNumRead = input.read(buffer)) != -1) { 
                    baos.write(buffer, 0, bytesNumRead); 
                }
                return baos.toByteArray();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally{
                if(input != null){
                    try {
                        input.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            
        }else{
            if(map.containsKey(name)) {
                //去除map中的引用,避免GC无法回收无用的class文件
                return map.remove(name);
            }
        }
        return null;
    }
    
    /**
     * 预读lib下面的包
     */
    private void preReadJarFile(){
        List<File> list = scanDir();
        for(File f : list){
            JarFile jar;
            try {
                jar = new JarFile(f);
                readJAR(jar);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    /**
     * 读取一个jar包内的class文件,并存在当前加载器的map中
     * @param jar
     * @throws IOException
     */
    private void readJAR(JarFile jar) throws IOException{
        Enumeration<JarEntry> en = jar.entries();
        while (en.hasMoreElements()){
            JarEntry je = en.nextElement();
            String name = je.getName();
            if (name.endsWith(".class")){
                String clss = name.replace(".class", "").replaceAll("/", ".");
                if(this.findLoadedClass(clss) != null) continue;
                
                InputStream input = jar.getInputStream(je);
                ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
                int bufferSize = 4096; 
                byte[] buffer = new byte[bufferSize]; 
                int bytesNumRead = 0; 
                while ((bytesNumRead = input.read(buffer)) != -1) { 
                    baos.write(buffer, 0, bytesNumRead); 
                }
                byte[] cc = baos.toByteArray();
                input.close();
                map.put(clss, cc);//暂时保存下来
            }
        }
    }
    
    /**
     * 扫描lib下面的所有jar包
     * @return
     */
    private List<File> scanDir() {
        List<File> list = new ArrayList<File>();
        File[] files = new File(lib).listFiles();
        for (File f : files) {
            if (f.isFile() && f.getName().endsWith(".jar"))
                list.add(f);
        }
        return list;
    }
    
    /**
     * 添加一个jar包到加载器中去。
     * @param jarPath
     * @throws IOException 
     */
    public void addJar(String jarPath) throws IOException{
        File file = new File(jarPath);
        if(file.exists()){
            JarFile jar = new JarFile(file);
            readJAR(jar);
        }
    }
}
查看原文

bluesbruce 发布了文章 · 8月24日

利用GeoTools判断坐标是否在多边形内

geotools

GeoTools是一个使用JAVA开发的开源GIS工具。可以开发符合标准的地理信息系统及相关应用。具体使用及详情可以百度一下。本文主要讲述如何利用GeoTools判断坐标是否在多边形内。

POM.xml

<dependency>
  <groupId>org.geotools</groupId>
  <artifactId>gt-jts-wrapper</artifactId>
  <version>23.2</version>
</dependency>

JAVA

import org.geotools.geometry.jts.JTSFactoryFinder;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKTReader;

/**
 * 利用GeoTools判断坐标是否在多边形内
 *
 * @author BBF
 */
public class GeoDemo {

  /**
   * 通用几何对象工厂构建器
   */
  private static final GeometryFactory FACTORY = JTSFactoryFinder.getGeometryFactory(null);
  private static final WKTReader WKT_READER = new WKTReader(FACTORY);


  /**
   * 判断点是否存在多边形内
   *
   * @param point   点
   * @param polygon 多边形
   * @return true - 在多边形内
   * @throws ParseException wkt转换异常
   * @see <a href="https://blog.csdn.net/lidejun152046/article/details/47128169">WKT格式</a>
   */
  public static boolean contains(String point, String polygon) throws ParseException {
    return WKT_READER.read(polygon).contains(WKT_READER.read(point));
  }
}

测试用例

@Test
public void testContains() throws ParseException {
  String wktPoint1 = "POINT (10 10)";
  String wktPoint2 = "POINT (20 20)";
  String wktPoly = "POLYGON ((0 0, 0 10, 10 20, 20 20, 20 0, 0 0))";
  boolean test1 = GeoDemo.contains(wktPoint1, wktPoly);
  boolean test2 = GeoDemo.contains(wktPoint2, wktPoly);
  System.out.println("point1位置:" + test1); // true
  System.out.println("point2位置:" + test2); // false
}

附录:geotools仓库引用

org.geotools的仓库地址:https://repo.osgeo.org/repository/release/
特别提醒
没有发布到中央库
没有发布到中央库
没有发布到中央库

无Nexus

如果本地没有搭建Nexus私服,需要配置外部私服(如阿里云、华为云等),切记要改一下mirrorOf

setting.xml

<mirrors>
  <mirror>
      <id>alimaven</id>
      <name>aliyun maven</name>
      <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
      <mirrorOf>*,!osgeo</mirrorOf>
    </mirror>
</mirrors>

pom.xml

<repositories>
  <!-- geotools仓库 -->
  <repository>
    <id>osgeo</id>
    <name>OSGeo Release Repository</name>
    <url>https://repo.osgeo.org/repository/release/</url>
    <snapshots>
      <enabled>false</enabled>
    </snapshots>
    <releases>
      <enabled>true</enabled>
    </releases>
  </repository>
</repositories>

有私服

Nexus创建maven2(proyx),镜像https://repo.osgeo.org/repository/release/。将仓库加入group

查看原文

赞 0 收藏 0 评论 0

bluesbruce 发布了文章 · 8月21日

关于雪花算法JS数值精度丢失的问题

在使用雪花算法(SnowFlake)的时候,产生18位的Long类型数值。
如: 668565260419989504,在JS中变为 668565260419989500

分析

JS的基础类型Number,遵循IEEE 754规范,采用双精度存储(double precision),具有53位有效数字精度,并总共占用64 bit。默认模式是最近舍入(Round to Nearest)。
不超过Math.pow(2, 53) = 9007199254740992(16位) 不会丢失精度。

处理

// Jackson
@JsonSerialize(using = com.fasterxml.jackson.databind.ser.std.ToStringSerializer.class)
private long id;

// FastJson
@JSONField(serializeUsing = com.alibaba.fastjson.serializer.ToStringSerializer.class)
private long id;
查看原文

赞 1 收藏 1 评论 0

bluesbruce 发布了文章 · 7月29日

监控Linux服务器CPU和内存

利用脚本获取Linux服务器的CPU和内存。

需要安装bc计算器

yum install -y bc

创建执行脚本

计算CPU利用率,配置了5秒采样。

执行脚本,5秒后输出采集日期|CPU负载|可用内存|总内存

#!/bin/sh
##echo user nice system idle iowait irq softirq
CPULOG_1=$(cat /proc/stat | grep 'cpu ' | awk '{print $2" "$3" "$4" "$5" "$6" "$7" "$8}')
SYS_IDLE_1=$(echo $CPULOG_1 | awk '{print $4}')
Total_1=$(echo $CPULOG_1 | awk '{print $1+$2+$3+$4+$5+$6+$7}')

# 休眠5秒,继续采样
sleep 5

CPULOG_2=$(cat /proc/stat | grep 'cpu ' | awk '{print $2" "$3" "$4" "$5" "$6" "$7" "$8}')
SYS_IDLE_2=$(echo $CPULOG_2 | awk '{print $4}')
Total_2=$(echo $CPULOG_2 | awk '{print $1+$2+$3+$4+$5+$6+$7}')

SYS_IDLE=`expr $SYS_IDLE_2 - $SYS_IDLE_1`

Total=`expr $Total_2 - $Total_1`
SYS_USAGE=`expr $SYS_IDLE/$Total*100 |bc -l`

SYS_Rate=`expr 100-$SYS_USAGE |bc -l`

Disp_SYS_Rate=`expr "scale=3; $SYS_Rate/1" |bc`
echo $Disp_SYS_Rate%

# 增加内存统计
Memory=`free -m | awk 'NR==2{print $4"|"$2}'`
# 采集日期 | CPU负载 | 可用内存 | 总内存
echo `date '+%Y-%m-%d %H:%M'`"|"$Disp_SYS_Rate%"|"$Memory
exit 0

在CentOS7上测试通过。

查看原文

赞 0 收藏 0 评论 0

bluesbruce 发布了文章 · 1月9日

SpringBoot下Druid密码回调配置

使用druid-spring-boot-starter,配置自定义的数据库密码加密回调。
重写com.alibaba.druid.util.DruidPasswordCallbacksetProperties方法。

配置的密码写入密文

特别注意spring.datasource.druid.password这个属性必须存在,值空或者随便写

# 这个属性必须存在,值空或者随便写
spring.datasource.druid.password=hello


# 自定义加密回调,特别注意,在AesDruidPasswordCallback中,读取这些属性值
spring.datasource.druid.password-callback-class-name=com.bbf.config.AesDruidPasswordCallback
# 在自定义的回调类中,需要读pwd和key的值
spring.datasource.druid.connect-properties.pwd=Icdcxq3YHY56bSgFZOeLHY9eGB75RfReA4EvFFyNxsY=
spring.datasource.druid.connect-properties.key=pXR9pD4KhMGDlmVOQmJDBSJT4iGkXOUso2Fslo2dv3x

解密算法

这里的AesUtil是自己定义的一种加密算法。实际中可以换成其他的算法。

import com.bbf.util.AesUtil;
import com.alibaba.druid.pool.DruidAbstractDataSource;
import com.alibaba.druid.util.DruidPasswordCallback;
import java.util.Properties;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 数据库回调密码解密
 * <p>在{@link DruidAbstractDataSource#createPhysicalConnection()}调用PasswordCallback方法时,
 * 会把getConnectProperties()的值放入,也就是spring.datasource.druid.connect-properties。
 * 不要与spring.datasource.druid.connection-properties混淆</p>
 *
 * @author BBF
 */
public class AesDruidPasswordCallback extends DruidPasswordCallback {

  private static final long serialVersionUID = 8636919602466752407L;
  private static final Logger LOGGER = LoggerFactory.getLogger(AesDruidPasswordCallback.class);

  @Override
  public void setProperties(Properties properties) {
    super.setProperties(properties);
    // 从druid的connectProperties中获取自定义的配置信息
    String pwd = properties.getProperty("pwd");
    String key = properties.getProperty("key");
    char[] p = null;
    try {
      if (StringUtils.isNoneBlank(pwd, key)) {
        // 解密密码
        String newPassword = AesUtil.cbcDecrypt(pwd, key);
        p = newPassword.toCharArray();
      }
    } catch (Exception ex) {
      LOGGER.error("[AesDruidPasswordCallback]解密失败:{}", ex.getMessage(), ex);
    }
    super.setPassword(p);
  }
}

密码加密测试类

/**
 * 数据库加密测试类
 *
 * @author BBF
 */
public class AesDruidPasswordCallbackTest {

  private static final String PWD = "htdd";
  private static final String KEY = "pXR9pD4KhMGDlmVOQmJDBSJT4iGkXOUso2Fslo2dv3x";

  @Test
  public void encryptPassword() {
    try {
      String pwd = AesUtil.cbcEncrypt(PWD, KEY);
      System.out.println("预期密文:Icdcxq3YHY56bSgFZOeLHY9eGB75RfReA4EvFFyNxsY=");
      System.out.printf("计算密文:%s\n", pwd);
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }
}
查看原文

赞 0 收藏 0 评论 0

bluesbruce 发布了文章 · 2019-10-23

Files.walkFileTree遍历目录文件

Files.walkFileTree遍历目录文件

java.nio.file.Files.walkFileTreeJDK7新增的静态工具方法。

Files.walkFileTree的原理介绍

static Path walkFileTree(Path start, Set<FileVisitOption> options, int maxDepth, FileVisitor<? super Path> visitor) throws IOException;

static Path walkFileTree(Path start, FileVisitor<? super Path> visitor) throws IOException;

参数列表:

  • java.nio.file.Path start 遍历的起始路径
  • Set<java.nio.file.FileVisitOption> options 遍历选项
  • int maxDepth 遍历深度
  • java.nio.file.FileVisitor<? super Path> visitor 遍历过程中的行为控制器

遍历行为控制器FileVisitor

接口java.nio.file.FileVisitor包含四个方法,涉及到遍历过程中的几个重要的步骤节点。一般实际中使用SimpleFileVisitor简化操作。

public interface FileVisitor<T> {

    FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs)
        throws IOException;

    FileVisitResult visitFile(T file, BasicFileAttributes attrs)
        throws IOException;

    FileVisitResult visitFileFailed(T file, IOException exc)
        throws IOException;

    FileVisitResult postVisitDirectory(T dir, IOException exc)
        throws IOException;
}
  • preVisitDirectory 访问一个目录,在进入之前调用。
  • postVisitDirectory 一个目录的所有节点都被访问后调用。遍历时跳过同级目录或有错误发生,Exception会传递给这个方法
  • visitFile 文件被访问时被调用。该文件的文件属性被传递给这个方法
  • visitFileFailed 当文件不能被访问时,此方法被调用。Exception被传递给这个方法。

遍历行为结果 FileVisitResult

public enum FileVisitResult {
    CONTINUE,
    TERMINATE,
    SKIP_SUBTREE,
    SKIP_SIBLINGS;
}
  • CONTINUE 继续遍历
  • SKIP_SIBLINGS 继续遍历,但忽略当前节点的所有兄弟节点直接返回上一层继续遍历
  • SKIP_SUBTREE 继续遍历,但是忽略子目录,但是子文件还是会访问
  • TERMINATE 终止遍历

查找指定文件

使用java.nio.file.Path提供的startsWithendsWith等方法,需要特别注意的是:匹配的是路径节点的完整内容,而不是字符串

例如: /usr/web/bbf.jar

Path path = Paths.get("/usr/web/bbf.jar");
path.endsWith("bbf.jar");  // true
path.endsWith(".jar");     // false

使用PathMatcher

@Test
public void visitFile2() throws IOException {
  // 查找java和txt文件
  String glob = "glob:**/*.{java,txt}";
  String path = "D:\\work_java\\bbf\\CORE";

  final PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher(glob);

  Files.walkFileTree(Paths.get(path), new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
        throws IOException {
      if (pathMatcher.matches(file)) {
        System.out.println(file);
      }
      return FileVisitResult.CONTINUE;
    }
  });
}

getPathMatcher方法的参数语法:规则:模式,其中规则支持两种模式globregex

全局规则glob

使用类似于正则表达式但语法更简单的模式,匹配路径的字符串。

  • glob:*.java 匹配以java结尾的文件
  • glob:*.* 匹配包含'.'的文件
  • glob:*.{java,class} 匹配以java或class结尾的文件
  • glob:foo.? 匹配以foo开头且一个字符扩展名的文件
  • glob:/home/*/* 在unix平台上匹配,例如/home/gus/data等
  • glob:/home/** 在unix平台上匹配,例如/home/gus,/home/gus/data
  • glob:c:\\\\* 在windows平台上匹配,例如c:foo,c:bar,注意字符串转义

规则说明

  • * 匹配零个或多个字符与名称组件,不跨越目录
  • ** 匹配零个或多个字符与名称组件,跨越目录(含子目录)
  • ? 匹配一个字符的字符与名称组件
  • \ 转义字符,例如\{表示匹配左花括号
  • [] 匹配方括号表达式中的范围,连字符(-)可指定范围。例如[ABC]匹配"A"、"B"和"C";[a-z]匹配从"a"到"z";[abce-g]匹配"a"、"b"、"c"、"e"、"f"、"g";
  • [!...]匹配范围之外的字符与名称组件,例如[!a-c]匹配除"a"、"b"、"c"之外的任意字符
  • {}匹配组中的任意子模式,多个子模式用","分隔,不能嵌套。

正则规则regex

使用java.util.regex.Pattern支持的正则表达式。

示例

获取指定扩展名的文件

以下测试用例,目的都是获取指定目录下的.properties和.html文件。

/**
 * 递归遍历,字符串判断
 *
 * @throws IOException IO异常
 */
@Test
public void visitFile1() throws IOException {
  String path = "D:\\work_java\\hty\\HTY_CORE";

  Files.walkFileTree(Paths.get(path), new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
        throws IOException {
      String pathStr = file.toString();
      if (pathStr.endsWith("properties") || pathStr.endsWith("html")) {
        System.out.println(file);
      }
      return FileVisitResult.CONTINUE;
    }
  });
}

/**
 * 递归遍历,glob模式
 *
 * @throws IOException IO异常
 */
@Test
public void visitFile2() throws IOException {
  String glob = "glob:**/*.{properties,html}";
  String path = "D:\\work_java\\hty\\HTY_CORE";

  final PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher(glob);

  Files.walkFileTree(Paths.get(path), new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
        throws IOException {
      if (pathMatcher.matches(file)) {
        System.out.println(file);
      }
      return FileVisitResult.CONTINUE;
    }
  });
}

/**
 * 递归遍历,正则模式
 *
 * @throws IOException IO异常
 */
@Test
public void visitFile3() throws IOException {
  // (?i)忽略大小写,(?:)标记该匹配组不应被捕获
  String reg = "regex:.*\\.(?i)(?:properties|html)";
  String path = "D:\\work_java\\hty\\HTY_CORE";

  final PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher(reg);

  Files.walkFileTree(Paths.get(path), new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
        throws IOException {
      if (pathMatcher.matches(file)) {
        System.out.println(file);
      }
      return FileVisitResult.CONTINUE;
    }
  });
}

查找指定文件

/**
 * 查找指定文件
 *
 * @throws IOException IO异常
 */
@Test
public void visitFile() throws IOException {
  String path = "D:\\work_java\\hty\\HTY_CORE\\src";

  Files.walkFileTree(Paths.get(path), new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
        throws IOException {
      // 使用endsWith,必须是路径中的一段,而不是几个字符
      if (file.endsWith("log.java")) {
        System.out.println(file);
        // 找到文件,终止操作
        return FileVisitResult.TERMINATE;
      }
      return FileVisitResult.CONTINUE;
    }
  });
}

遍历单层目录

使用DirectoryStream会获取指定目录下的目录和文件。可以使用newDirectoryStream的第二个参数进行筛选,glob语法。

/**
 * 遍历单层目录
 *
 * @throws IOException IO异常
 */
@Test
public void dir() throws IOException {
  Path source = Paths.get("D:\\work_java\\hty\\HTY_CORE\\src\\main\\resources");
  try (DirectoryStream<Path> stream = Files.newDirectoryStream(source, "*.xml")) {
    Iterator<Path> ite = stream.iterator();
    while (ite.hasNext()) {
      Path pp = ite.next();
      System.out.println(pp.getFileName());
    }
  }
}

复制文件到新目录

/**
 * 递归复制
 *
 * @throws IOException IO异常
 */
@Test
public void copyAll() throws IOException {
  Path source = Paths.get("D:\\work_java\\hty\\HTY_CORE\\src");
  Path target = Paths.get("D:\\temp\\core");
  // 源文件夹非目录
  if (!Files.isDirectory(source)) {
    throw new IllegalArgumentException("源文件夹错误");
  }
  // 源路径的层级数
  int sourcePart = source.getNameCount();
  Files.walkFileTree(source, new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
        throws IOException {
      // 在目标文件夹中创建dir对应的子文件夹
      Path subDir;
      if (dir.compareTo(source) == 0) {
        subDir = target;
      } else {
        // 获取相对原路径的路径名,然后组合到target上
        subDir = target.resolve(dir.subpath(sourcePart, dir.getNameCount()));
      }
      Files.createDirectories(subDir);
      return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
      Files.copy(file, target.resolve(file.subpath(sourcePart, file.getNameCount())),
          StandardCopyOption.REPLACE_EXISTING);
      return FileVisitResult.CONTINUE;
    }
  });
  System.out.println("复制完毕");
}

文件和流的复制

/**
 * 流复制到文件
 *
 * @throws IOException IO异常
 */
@Test
public void copy1() throws IOException {
  Path source = Paths.get("D:\\work_java\\hty\\HTY_CORE\\src\\main\\resources\\ehcache.xml");
  Path target = Paths.get("D:\\temp\\");
  if (!Files.exists(target)) {
    Files.createDirectories(target);
  }
  Path targetFile = target.resolve(source.getFileName());
  try (InputStream fs = FileUtils.openInputStream(source.toFile())) {
    Files.copy(fs, targetFile, StandardCopyOption.REPLACE_EXISTING);
  }
}

/**
 * 文件复制到流
 *
 * @throws IOException IO异常
 */
@Test
public void copy2() throws IOException {
  Path source = Paths.get("D:\\work_java\\hty\\HTY_CORE\\src\\main\\resources\\ehcache.xml");
  Path target = Paths.get("D:\\temp\\core");
  Path targetFile = target.resolve(source.getFileName());
  if (!Files.exists(target)) {
    Files.createDirectories(target);
  }
  try (OutputStream fs = FileUtils.openOutputStream(targetFile.toFile());
      OutputStream out = new BufferedOutputStream(fs)) {
    Files.copy(source, out);
  }
}

Path与File的转换

/**
 * Path与File的转换
 */
@Test
public void testPath() {
  File file = new File("D:\\work_java\\hty\\HTY_CORE");
  System.out.println(file.toURI());
  System.out.println(file.getAbsolutePath());
  System.out.println(file.getName());

  System.out.println("-------");
  Path path = Paths.get(file.toURI());
  System.out.println(path.toUri());
  System.out.println(path.toAbsolutePath());
  System.out.println(path.getFileName());

  System.out.println("-------");
  File f = path.toFile();
  System.out.println(f.getAbsolutePath());
}
查看原文

赞 1 收藏 0 评论 0

bluesbruce 发布了文章 · 2019-06-19

使用winsw部署Windows服务

仓库地址: https://github.com/kohsuke/winsw
下载地址: https://github.com/kohsuke/wi...

安装说明

https://github.com/kohsuke/wi...

  1. 下载winsw程序,选择对应版本。(WinSW.NET2.exe 或 WinSW.NET4.exe)
  2. 安装.NET Framework
  3. WinSW.exe复制到自定义的目录,并重命名为test.exe
  4. 同目录下创建test.xml特别注意,xml和exe必须同名
  5. 使用test.exe install安装服务。
  6. 使用test.exe uninstall卸载服务。

示例,配置springboot的服务

<configuration>
  <id>lance</id>
  <name>lance(winsw)</name>
  <description>java</description>
  <executable>java</executable>
  <arguments>-server -Xms400m -Xmx400m -Xmn100m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -Xverify:none -XX:+DisableExplicitGC -Djava.awt.headless=true -jar "lance-1.2.0.jar"</arguments>
  <log mode="reset"/>
</configuration>

命令说明

E:\service>test.exe /?
A wrapper binary that can be used to host executables as Windows services

Usage: winsw [/redirect file] <command> [<args>]
       Missing arguments trigger the service mode

Available commands:
- 'install'   - install the service to Windows Service Controller
- 'uninstall' - uninstall the service
- 'start'     - start the service (must be installed before)
- 'stop'      - stop the service
- 'restart'   - restart the service
- 'restart!'  - self-restart (can be called from child processes)
- 'status'    - check the current status of the service
- 'test'      - check if the service can be started and then stopped
- 'testwait'  - starts the service and waits until a key is pressed then stops the service
- 'version'   - print the version info
- 'help'      - print the help info (aliases: -h,--help,-?,/?)

Extra options:
- '/redirect' - redirect the wrapper's STDOUT and STDERR to the specified file

WinSW 2.2.0.0
More info: https://github.com/kohsuke/winsw
Bug tracker: https://github.com/kohsuke/winsw/issues

配置说明

https://github.com/kohsuke/wi...

<!--
 这是Windows服务包装器的一个配置示例。
 此配置文件应放在WinSW.exe同目录,名称应相同。
 例如,对于myapp.exe,配置文件名应为myapp.xml。

 更多信息:https://github.com/kohsuke/winsw/blob/master/doc/xmlconfigfile.md
-->
<configuration>
  
<!-- 
SECTION: 必填项
-->
  
  <!-- 服务ID,在windows系统中必须唯一 -->
  <id>myapp</id>
  <!-- 服务显示名称,只能英文和数字 -->
  <name>MyApp Service (powered by WinSW)</name>
  <!-- 服务描述,只能英文和数字 -->
  <description>This service is a service cratead from a sample configuration</description>
  
  <!-- 启动可执行文件的路径 -->
  <executable>%BASE%\myExecutable.exe</executable>

<!--
SECTION: 安装
这些选项仅在安装时有效。修改配置后,必须重新安装服务才能生效。
-->

  <!--
    OPTION: serviceaccount
    定义运行服务的账号。
  -->
  <!--
  <serviceaccount>
    <domain>YOURDOMAIN</domain>
    <user>useraccount</user>
    <password>Pa55w0rd</password>
    <allowservicelogon>true</allowservicelogon>
  </serviceaccount>
  -->
  
  <!--
    OPTION: onfailure
    定义一系列操作,如果托管的可执行文件失败,则顺序执行这些操作。
    支持的操作:restart,reboot,none
  -->
  <!--
  <onfailure action="restart" delay="10 sec"/>
  <onfailure action="restart" delay="20 sec"/>
  <onfailure action="reboot" />
  -->
  
  <!--
    OPTION: resetfailure
    windows服务重置故障状态的时间。
    默认值 1 day
  -->
  <!--
  <resetfailure>1 hour</resetfailure>
  -->

<!--
SECTION: 执行管理配置
-->

  <!-- 
    OPTION: arguments
    传递给可执行文件的参数。
  -->
  <!--
  <arguments>-classpath c:\cygwin\home\kohsuke\ws\hello-world\out\production\hello-world test.Main</arguments>
  -->

  <!-- 
    OPTION: startarguments
    可执行文件启动参数。
    如果配置,则覆盖"arguments"
  -->
  <!--
  <startarguments></startarguments>
  -->
  
  <!--
    OPTION: workingdirectory
    如果指定,则设置可执行文件的默认工作目录。
    默认值:服务包装的可执行文件的目录
  -->
  <!--
  <workingdirectory>C:\myApp\work</workingdirectory>
-->
  
  <!--
    OPTION: priority
    所需的进程优先级。
    可设置的值:Normal, Idle, High, RealTime, BelowNormal, AboveNormal
    默认值:Normal
  -->
  <priority>Normal</priority>
  
  <!-- 
    OPTION: stoptimeout
    强制终止可执行文件之前,尝试正常关闭的等待时间。
    默认值:15 seconds
  -->
  <stoptimeout>15 sec</stoptimeout>
    
  <!--
    OPTION: stopparentprocessfirst
    如果设置为true,在停止子进程之前终止父进程。
    默认值:false
  -->
  <stopparentprocessfirst>false</stopparentprocessfirst>

  <!-- 
    OPTION: stopexecutable
    执行关闭服务的可执行文件路径。
    只有配置了"stoparguments"时,才使用此配置。如果没有则用"executable"关闭服务
  -->
  <!--
  <stopexecutable>%BASE%\stop.exe</stopexecutable>
  -->

  <!-- 
    OPTION: stoparguments
    终止服务时,传递给"stopexecutable"的附加参数
    此选项还允许通过停止可执行文件终止可执行文件
  -->
  <!--
  <stoparguments>-stop true</stoparguments>
  -->
<!-- 
SECTION: 服务管理
-->
    <!--
      OPTION: startmode
      定义服务的启动模式
      支持的模式:Automatic, Manual, Boot, System (latter ones are supported for driver services only)
      默认模式:Automatic
    -->
    <startmode>Automatic</startmode>
    
    <!--
      OPTION: delayedAutoStart
      如果在"startmode"配置了"Automatic",则启用延迟自动启动。
      请参阅Winsw文档以获取有关支持的平台版本和限制的信息。
    -->
    <!--
        <delayedAutoStart/>
    -->
    
    <!-- 
      OPTION: depend
      在本服务启动之前启动的服务。
    -->
    <!--
    <depend>Eventlog</depend>
    <depend>W32Time</depend>
    -->
    
    <!--
      OPTION: waithint
      挂起停止操作所需的时间
      在指定的时间段过去之前,服务应该下次调用setServiceStatus函数。
      否则服务将被标记为无响应
      默认值:15 seconds
    -->
    <waithint>15 sec</waithint>
    
    <!--
      OPTION: sleeptime
      服务下次调用setServiceStatus函数之前的时间。
      不要超过"waithint"。建议间隔是"waithint"的十分之一,但不小于1秒,也不大于10秒。
      默认值:1 second
    -->
    <sleeptime>1 sec</sleeptime>
    
    <!--
      OPTION: interactive
      指示服务可以与桌面交互。
    -->
    <!--
    <interactive/>
    -->
    
<!-- 
SECTION: 日志记录 
-->

  <!--
    OPTION: logpath
    为服务包装程序生成的所有日志设置自定义日志目录。
    默认值:包含执行器的目录
  -->
  <!--
    <logpath>%BASE%\logs</logpath>
  -->
  
  <!--
    OPTION: log
    为可执行文件生成的日志定义日志记录模式。
    支持的模式:
      * append - 追加到现有日志
      * none - 不保存日志
      * reset - 启动时擦除日志
      * roll - 根据大小自动循环分割日志
      * roll-by-time - 根据日期循环分割日志
    默认模式: append
    
    每种模式都有不同的设置。https://github.com/kohsuke/winsw/blob/master/doc/loggingAndErrorReporting.md
  -->
  <log mode="append">
    <!--
    <setting1/>
    <setting2/>
  -->
  </log>
  
<!--
SECTION: 环境设置
-->
  <!--
    OPTION: env
    设置或重写环境变量。
    顶层可能配置了多个条目。
  -->
  <!--
  <env name="MY_TOOL_HOME" value="C:\etc\tools\myTool" />
  <env name="LM_LICENSE_FILE" value="host1;host2" />
  -->


  <!--
    OPTION: download
    启动前由包装器执行的下载列表。
  -->
  <!--
  <download from="http://www.google.com/" to="%BASE%\index.html" />
  
  下载并在出现错误时使服务启动失败:
  <download from="http://www.nosuchhostexists.com/" to="%BASE%\dummy.html" failOnError="true"/>

  由于连接未加密而导致基本身份验证不安全的示例:
  <download from="http://example.com/some.dat" to="%BASE%\some.dat"
            auth="basic" unsecureAuth=“true”
            username="aUser" password=“aPassw0rd" />

  通过HTTPS安全基本身份验证
  <download from="https://example.com/some.dat" to="%BASE%\some.dat"
            auth="basic" username="aUser" password="aPassw0rd" />

  当目标服务器和客户端是同一域的成员,并且客户端域属于具有信任的域时,安全身份验证:
  <download from="https://example.com/some.dat" to="%BASE%\some.dat" auth="sspi" />
  -->

<!-- 
SECTION: 其他选项
-->
  
  <!--
    OPTION: beeponshutdown
    指示服务在关闭时(如果操作系统支持)应发出嘟嘟声。
  -->
  <!--
  <beeponshutdown/> 
  -->
  
<!--
SECTION: 扩展
此配置部分允许指定自定义扩展。
更多信息请访问:https://github.com/kohsuke/winsw/blob/master/doc/extensions/extensions.md
-->

<!--
<extensions>
  Extension 1: id values must be unique
  <extension enabled="true" id="extension1" className="winsw.Plugins.SharedDirectoryMapper.SharedDirectoryMapper">
    <mapping>
      <map enabled="false" label="N:" uncpath="\\UNC"/>
      <map enabled="false" label="M:" uncpath="\\UNC2"/>
    </mapping>
  </extension>
  ...
</extensions>
-->

</configuration>
查看原文

赞 3 收藏 2 评论 0

bluesbruce 发布了文章 · 2019-06-11

CentOS7安装Elasticsearch7

CentOS7安装Elasticsearch7

下载地址:https://www.elastic.co/cn/dow...

使用YUM安装

# 下载并安装公共签名密钥
rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch
# 配置RPM仓库
vim /etc/yum.repos.d/elasticsearch.repo

[elasticsearch-7.x]
name=Elasticsearch repository for 7.x packages
baseurl=https://artifacts.elastic.co/packages/7.x/yum
# Apache 2.0 license
#baseurl=https://artifacts.elastic.co/packages/oss-7.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md
yum install -y elasticsearch

配置服务

启动服务之前一定要先配置/etc/elasticsearch/elasticsearch.ymlnetwork.hosthttp.portcluster.initial_master_nodes

firewall-cmd --zone=public --add-port=9200/tcp --permanent
firewall-cmd --reload

sudo /bin/systemctl daemon-reload
sudo /bin/systemctl enable elasticsearch.service

# 启动停止服务
sudo systemctl start elasticsearch
sudo systemctl stop elasticsearch

测试服务

[root@localhost ~]# curl "http://127.0.0.1:9200/"
{
  "name" : "localhost.localdomain",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "Pxdp0Z24SJ-MIBH_2oMe2A",
  "version" : {
    "number" : "7.1.1",
    "build_flavor" : "default",
    "build_type" : "rpm",
    "build_hash" : "7a013de",
    "build_date" : "2019-05-23T14:04:00.380842Z",
    "build_snapshot" : false,
    "lucene_version" : "8.0.0",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

常用文件

# 配置文件
vim /etc/elasticsearch/elasticsearch.yml

# JVM配置
vim /etc/elasticsearch/jvm.options

# 启动日志
tail -n 10 -f /var/log/elasticsearch/elasticsearch.log

问题处理

绑定IP和跨域

vim /etc/elasshellticsearch/elasticsearch.yml

# 允许任意IP访问
network.host: 0.0.0.0

# 修改开放的端口
http.port: 9200

# 最后添加跨域
http.cors.enabled: true
http.cors.allow-origin: "*"

启动失败

启动报错信息如下:
the default discovery settings are unsuitable for production use; at least one of [discovery.seed_hosts, discovery.seed_providers, cluster.initial_master_nodes] must be configured
vim /etc/elasticsearch/elasticsearch.yml

# 修改【#cluster.initial_master_nodes: ["node-1", "node-2"] 】
cluster.initial_master_nodes: ["node-1"]

进程虚拟内存不足

vim /etc/sysctl.conf

# 添加
vm.max_map_count=262144

# 保存后执行
sysctl -p

RPM目录

类型描述默认位置设置
homeElasticsearch主目录或 $ES_HOME/usr/share/elasticsearch
bin二进制脚本,包括elasticsearch启动节点和elasticsearch-plugin安装插件/usr/share/elasticsearch/bin
conf配置文件elasticsearch.yml/etc/elasticsearchES_PATH_CONF
conf环境变量,包括堆大小,文件描述符/etc/sysconfig/elasticsearc
data节点上分配的每个索引、分片的数据文件的位置。可以容纳多个位置。/var/lib/elasticsearchpath.data
logs日志文件位置/var/log/elasticsearchpath.logs
plugins插件文件位置。每个插件都将包含在一个子目录中。/usr/share/elasticsearch/plugins
repo共享文件系统存储库位置。可以容纳多个位置。文件系统存储库可以放在此处指定的任何目录的任何子目录中。Not configuredpath.repo

配置Elasticsearch

Elasticsearch默认使用/etc/elasticsearch运行时配置。此目录的所有权以及此目录中的所有文件在安装时都设置为root:elasticsearch,并且目录设置了setgid标志,以便在/etc/elasticsearch下创建的所有文件和子目录,例如使用密钥库创建密钥库工具等。

Elasticsearch默认读取配置文件/etc/elasticsearch/elasticsearch.yml详细说明

RPM还有一个系统配置文件(/etc/sysconfig/elasticsearch),允许设置以下参数。

参数说明
JAVA_HOME设置要使用的自定义Java路径。
MAX_OPEN_FILES最大打开文件数,默认为65535。
MAX_LOCKED_MEMORY最大锁定内存大小。如果需要通过elasticsearch.yml中的选项bootstrap.memory_lock来控制,就设置为unlimited
MAX_MAP_COUNT进程可能具有的最大内存映射区域数。如果您使用mmapfs 索引存储类型,请确保将其设置为较高的值。默认为262144。
ES_PATH_CONF配置文件目录(其中必须包括elasticsearch.ymljvm.optionslog4j2.properties); 默认目录 /etc/elasticsearch
ES_JAVA_OPTS其他JVM系统属性
RESTART_ON_UPGRADE在程序包升级时配置重新启动,默认为false。这意味着您必须在手动安装软件包后重新启动Elasticsearch实例。这样做的原因是为了确保群集中的升级不会导致连续的分片重新分配,从而导致高网络流量并缩短群集的响应时间。

附录:完整的elasticsearch.yml文件

vim /etc/elasticsearch/elasticsearch.yml

# ======================== Elasticsearch Configuration =========================
#
# NOTE: Elasticsearch comes with reasonable defaults for most settings.
#       Before you set out to tweak and tune the configuration, make sure you
#       understand what are you trying to accomplish and the consequences.
#
# The primary way of configuring a node is via this file. This template lists
# the most important settings you may want to configure for a production cluster.
#
# Please consult the documentation for further information on configuration options:
# https://www.elastic.co/guide/en/elasticsearch/reference/index.html
#
# ---------------------------------- Cluster -----------------------------------
#
# Use a descriptive name for your cluster:
#
#cluster.name: my-application
#
# ------------------------------------ Node ------------------------------------
#
# Use a descriptive name for the node:
#
node.name: node-1
#
# Add custom attributes to the node:
#
#node.attr.rack: r1
#
# ----------------------------------- Paths ------------------------------------
#
# Path to directory where to store the data (separate multiple locations by comma):
#
path.data: /var/lib/elasticsearch
#
# Path to log files:
#
path.logs: /var/log/elasticsearch
#
# ----------------------------------- Memory -----------------------------------
#
# Lock the memory on startup:
#
#bootstrap.memory_lock: true
#
# Make sure that the heap size is set to about half the memory available
# on the system and that the owner of the process is allowed to use this
# limit.
#
# Elasticsearch performs poorly when the system is swapping the memory.
#
# ---------------------------------- Network -----------------------------------
#
# Set the bind address to a specific IP (IPv4 or IPv6):
#
network.host: 0.0.0.0
#
# Set a custom port for HTTP:
#
http.port: 9200
#
# For more information, consult the network module documentation.
#
# --------------------------------- Discovery ----------------------------------
#
# Pass an initial list of hosts to perform discovery when this node is started:
# The default list of hosts is ["127.0.0.1", "[::1]"]
#
#discovery.seed_hosts: ["host1", "host2"]
#
# Bootstrap the cluster using an initial set of master-eligible nodes:
#
#cluster.initial_master_nodes: ["node-1", "node-2"]
cluster.initial_master_nodes: ["node-1"]
#
# For more information, consult the discovery and cluster formation module documentation.
#
# ---------------------------------- Gateway -----------------------------------
#
# Block initial recovery after a full cluster restart until N nodes are started:
#
#gateway.recover_after_nodes: 3
#
# For more information, consult the gateway module documentation.
#
# ---------------------------------- Various -----------------------------------
#
# Require explicit names when deleting indices:
#
#action.destructive_requires_name: true

http.cors.enabled: true
http.cors.allow-origin: "*"

附录:完整的elasticsearch.service文件

vim /lib/systemd/system/elasticsearch.service

[Unit]
Description=Elasticsearch
Documentation=http://www.elastic.co
Wants=network-online.target
After=network-online.target

[Service]
RuntimeDirectory=elasticsearch
PrivateTmp=true
Environment=ES_HOME=/usr/share/elasticsearch
Environment=ES_PATH_CONF=/etc/elasticsearch
Environment=PID_DIR=/var/run/elasticsearch
EnvironmentFile=-/etc/sysconfig/elasticsearch

WorkingDirectory=/usr/share/elasticsearch

User=elasticsearch
Group=elasticsearch

ExecStart=/usr/share/elasticsearch/bin/elasticsearch -p ${PID_DIR}/elasticsearch.pid --quiet

# StandardOutput is configured to redirect to journalctl since
# some error messages may be logged in standard output before
# elasticsearch logging system is initialized. Elasticsearch
# stores its logs in /var/log/elasticsearch and does not use
# journalctl by default. If you also want to enable journalctl
# logging, you can simply remove the "quiet" option from ExecStart.
StandardOutput=journal
StandardError=inherit

# Specifies the maximum file descriptor number that can be opened by this process
LimitNOFILE=65535

# Specifies the maximum number of processes
LimitNPROC=4096

# Specifies the maximum size of virtual memory
LimitAS=infinity

# Specifies the maximum file size
LimitFSIZE=infinity

# Disable timeout logic and wait until process is stopped
TimeoutStopSec=0

# SIGTERM signal is used to stop the Java process
KillSignal=SIGTERM

# Send the signal only to the JVM rather than its control group
KillMode=process

# Java process is never killed
SendSIGKILL=no

# When a JVM receives a SIGTERM signal it exits with code 143
SuccessExitStatus=143

[Install]
WantedBy=multi-user.target

# Built for packages-7.1.1 (packages)
查看原文

赞 1 收藏 1 评论 0

bluesbruce 发布了文章 · 2019-03-06

删除Druid下方的阿里云广告

目前druid-1.1.14的web控制台依然存在阿里云的广告,本文通过过滤器将广告文本拦截。

定位问题

产生广告的JS文件在 druid-1.1.14.jar/support/http/resources/js/common.js

查看源码可知是buildFooter方法进行植入,由init方法调用。

解决问题

/**
 * Druid的配置类
 *
 * @author BBF
 */
@Configuration
@AutoConfigureAfter(DruidDataSourceAutoConfigure.class)
public class DruidConfig {

  /**
   * 带有广告的common.js全路径,druid-1.1.14
   */
  private static final String FILE_PATH = "support/http/resources/js/common.js";
  /**
   * 原始脚本,触发构建广告的语句
   */
  private static final String ORIGIN_JS = "this.buildFooter();";
  /**
   * 替换后的脚本
   */
  private static final String NEW_JS = "//this.buildFooter();";

  /**
   * 去除Druid监控页面的广告
   *
   * @param properties DruidStatProperties属性集合
   * @return {@link org.springframework.boot.web.servlet.FilterRegistrationBean}
   */
  @Bean
  @ConditionalOnWebApplication
  @ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled", havingValue = "true")
  public FilterRegistrationBean<RemoveAdFilter> removeDruidAdFilter(
      DruidStatProperties properties) throws IOException {
    // 获取web监控页面的参数
    DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
    // 提取common.js的配置路径
    String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
    String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
    // 获取common.js
    String text = Utils.readFromResource(FILE_PATH);
    // 屏蔽 this.buildFooter(); 不构建广告
    final String newJs = text.replace(ORIGIN_JS, NEW_JS);
    FilterRegistrationBean<RemoveAdFilter> registration = new FilterRegistrationBean<>();
    registration.setFilter(new RemoveAdFilter(newJs));
    registration.addUrlPatterns(commonJsPattern);
    return registration;
  }

  /**
   * 删除druid的广告过滤器
   *
   * @author BBF
   */
  private class RemoveAdFilter implements Filter {

    private final String newJs;

    public RemoveAdFilter(String newJS) {
      this.newJs = newJS;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
      chain.doFilter(request, response);
      // 重置缓冲区,响应头不会被重置
      response.resetBuffer();
      response.getWriter().write(newJs);
    }
  }
}
查看原文

赞 1 收藏 0 评论 0

bluesbruce 发布了文章 · 2019-03-06

使用SpringSecurity处理CSRF攻击

CSRF漏洞现状

CSRF(Cross-site request forgery)跨站请求伪造,也被称为One Click Attack或者Session Riding,通常缩写为CSRFXSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,XSS利用站点内的信任用户,而CSRF则通过伪装成受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性。
CSRF是一种依赖web浏览器的、被混淆过的代理人攻击(deputy attack)。

POM依赖

<!-- 模板引擎 freemarker -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!-- Security (只使用CSRF部分) -->
<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-web</artifactId>
</dependency>

配置过滤器

@SpringBootApplication
public class Application {

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
  
  /**
   * 配置CSRF过滤器
   *
   * @return {@link org.springframework.boot.web.servlet.FilterRegistrationBean}
   */
  @Bean
  public FilterRegistrationBean<CsrfFilter> csrfFilter() {
    FilterRegistrationBean<CsrfFilter> registration = new FilterRegistrationBean<>();
    registration.setFilter(new CsrfFilter(new HttpSessionCsrfTokenRepository()));
    registration.addUrlPatterns("/*");
    registration.setName("csrfFilter");
    return registration;
  }
}

在form请求中添加CSRF的隐藏字段

<input name="${(_csrf.parameterName)!}" value="${(_csrf.token)!}" type="hidden" />

在AJAX请求中添加header头

xhr.setRequestHeader("${_csrf.headerName}", "${_csrf.token}");

jQuery的Ajax全局配置

jQuery.ajaxSetup({
  "beforeSend": function (request) {
    request.setRequestHeader("${_csrf.headerName}", "${_csrf.token}");
  }
});
查看原文

赞 0 收藏 0 评论 1

认证与成就

  • 获得 43 次点赞
  • 获得 4 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 4 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2016-04-15
个人主页被 742 人浏览