init project

This commit is contained in:
jaysunxiao committed 2021-05-20 14:17:21 +08:00
1 parent b7f28da485
commit bce060a28e
573 files changed
+84513

No files matched your search

+211
View File
@@ -0,0 +1,211 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zfoo</groupId>
<artifactId>util</artifactId>
<version>3.0</version>
<packaging>jar</packaging>
<properties>
<!-- 本项目的其它module版本号 -->
<zfoo.event.version>3.0</zfoo.event.version>
<zfoo.hotswap.version>3.0</zfoo.hotswap.version>
<zfoo.monitor.version>3.0</zfoo.monitor.version>
<zfoo.net.version>3.0</zfoo.net.version>
<zfoo.scheduler.version>3.0</zfoo.scheduler.version>
<zfoo.storage.version>3.0</zfoo.storage.version>
<zfoo.orm.version>3.0</zfoo.orm.version>
<zfoo.protocol.version>3.0</zfoo.protocol.version>
<zfoo.util.version>3.0</zfoo.util.version>
<!-- 核心spring框架 -->
<spring.version>5.3.4</spring.version>
<spring.boot.version>2.4.3</spring.boot.version>
<!-- 工具包 -->
<commons-codec.version>1.15</commons-codec.version>
<commons-io.version>2.8.0</commons-io.version>
<commons-collections.version>4.4</commons-collections.version>
<commons-lang.version>3.12.0</commons-lang.version>
<commons-fileupload.version>1.4</commons-fileupload.version>
<commons-logging.version>1.2</commons-logging.version>
<commons-log4j.version>2.14.0</commons-log4j.version>
<httpcomponents.version>4.5.13</httpcomponents.version>
<httpcore.version>4.4.14</httpcore.version>
<google.guava.version>30.1-jre</google.guava.version>
<google.protobuf.version>3.9.1</google.protobuf.version>
<google.gson.version>2.8.6</google.gson.version>
<kryo.version>5.0.3</kryo.version>
<caffeine.version>2.8.8</caffeine.version>
<jctools.version>3.2.0</jctools.version>
<hutool.version>5.5.9</hutool.version>
<oshi.version>5.7.0</oshi.version>
<snakeyaml.version>1.28</snakeyaml.version>
<!-- json和xml解析包 -->
<jackson.version>2.12.1</jackson.version>
<fastjson.version>1.2.51</fastjson.version>
<!-- office文档解析包 -->
<poi.version>4.1.2</poi.version>
<!-- 字节码增强 -->
<javassist.version>3.27.0-GA</javassist.version>
<bytebuddy.version>1.10.22</bytebuddy.version>
<!-- 网络通讯框架 -->
<netty.version>4.1.63.Final</netty.version>
<!-- 分布式zookeeper核心依赖包 -->
<zookeeper.version>3.6.1</zookeeper.version>
<curator.version>5.1.0</curator.version>
<!-- 数据库和缓存 -->
<mongodb-driver-sync.version>4.2.1</mongodb-driver-sync.version>
<jedis.version>3.3.0</jedis.version>
<!-- 消息队列中间件 -->
<rocketmq.version>4.5.2</rocketmq.version>
<!-- elastic search 中间件 -->
<elastic.search.version>7.9.3</elastic.search.version>
<elastic.search.spring.version>4.1.5</elastic.search.spring.version>
<lucene.version>8.6.2</lucene.version>
<slf4j.version>1.7.30</slf4j.version>
<logback.version>1.2.3</logback.version>
<junit.version>4.13.1</junit.version>
<!-- java版本和文件编码 -->
<java.version>11</java.version>
<file.encoding>UTF-8</file.encoding>
<jakarta.version>1.3.5</jakarta.version>
<!-- maven核心插件 -->
<maven-clean-plugin.version>3.1.0</maven-clean-plugin.version>
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
<maven-resources-plugin.version>3.2.0</maven-resources-plugin.version>
<maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version>
<maven-jar-plugin.version>3.2.0</maven-jar-plugin.version>
<maven-shade-plugin.version>3.2.4</maven-shade-plugin.version>
<versions-maven-plugin.version>2.8.1</versions-maven-plugin.version>
<project.build.sourceEncoding>${file.encoding}</project.build.sourceEncoding>
<maven.compiler.encoding>${file.encoding}</maven.compiler.encoding>
</properties>
<dependencies>
<dependency>
<groupId>com.zfoo</groupId>
<artifactId>protocol</artifactId>
<version>${zfoo.protocol.version}</version>
</dependency>
<!-- 依赖的Spring模块类库 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Spring动态表达式语言SpEL -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- 依赖的测试库 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<plugins>
<!-- 清理插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>${maven-clean-plugin.version}</version>
</plugin>
<!-- 编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${file.encoding}</encoding>
</configuration>
</plugin>
<!-- resource资源管理插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>${maven-resources-plugin.version}</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<encoding>${file.encoding}</encoding>
<outputDirectory>${project.build.directory}/resource</outputDirectory>
<resources>
<resource>
<directory>src/main/resources/</directory>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<!-- 测试插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<forkMode>once</forkMode>
<threadCount>10</threadCount>
<argLine>-Dfile.encoding=${file.encoding}</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-jar-plugin.version}</version>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,174 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.zfoo.protocol.exception.ExceptionUtils;
import com.zfoo.protocol.util.AssertionUtils;
import com.zfoo.protocol.util.StringUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* DOM(Document Object Model)文档对象模型
* <p>
* Convenience methods for working with the DOM API,in particular for working with DOM Nodes and DOM Elements.
* </p>
*
* @author jaysunxiao
* @version 3.0
*/
public abstract class DomUtils {
private static final XmlMapper MAPPER = XmlMapper.builder()
.defaultUseWrapper(false)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true) // 当反序列化有未知属性则抛异常,true打开这个设置
.build();
public static <T> T string2Object(String xml, Class<T> clazz) {
try {
return MAPPER.readValue(xml, clazz);
} catch (IOException e) {
throw new RuntimeException(StringUtils.format("将xml字符串[xml:{}]转换为对象[class:{}]异常[error:{}]", xml, clazz, ExceptionUtils.getStackTrace(e)));
}
}
public static <T> T file2Object(File xmlFile, Class<T> clazz) {
try {
XMLInputFactory f = XMLInputFactory.newFactory();
XMLStreamReader sr = f.createXMLStreamReader(new FileInputStream(xmlFile));
return MAPPER.readValue(sr, clazz);
} catch (Exception e) {
throw new RuntimeException(StringUtils.format("将xml文件[xml:{}]转换为对象[class:{}]异常[error:{}]", xmlFile, clazz, ExceptionUtils.getStackTrace(e)));
}
}
public static <T> T inputStream2Object(InputStream xmlInputStream, Class<T> clazz) {
try {
return MAPPER.readValue(xmlInputStream, clazz);
} catch (Exception e) {
throw new RuntimeException(StringUtils
.format("将xmlInputStream转换为对象[class:{}]异常[error:{}]", clazz, ExceptionUtils.getStackTrace(e)));
}
}
/**
* 只返回第一层的孩子节点,不返回第一层孩子节点的孩子节点
* <p>
* Retrieves all child elements of the given DOM element
* </p>
*
* @param element the DOM element to analyze
* @return a List of child {@code org.w3c.dom.Element} instances
*/
public static List<Element> getChildElements(Element element) {
AssertionUtils.notNull(element, "Element must not be null");
NodeList nodeList = element.getChildNodes();
List<Element> childEles = new ArrayList<Element>();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node instanceof Element) {
childEles.add((Element) node);
}
}
return childEles;
}
/**
* Retrieves all child elements of the given DOM element that match any of the given element names.
* Only looks at the direct child level of the given element; do not go into further depth
* (in contrast to the DOM API's {@code getElementsByTagName} method).
*
* @param element the DOM element to analyze
* @param childElementNames the child element names to look for
* @return a List of child {@code org.w3c.dom.Element} instances
* @see org.w3c.dom.Element
* @see org.w3c.dom.Element#getElementsByTagName
*/
public static List<Element> getChildElementsByTagName(Element element, String... childElementNames) {
AssertionUtils.notNull(element, "Element must not be null");
AssertionUtils.notNull(childElementNames, "Element names collection must not be null");
List<String> childEleNameList = Arrays.asList(childElementNames);
NodeList childNodes = element.getChildNodes();
List<Element> elements = new ArrayList<>();
for (int i = 0; i < childNodes.getLength(); i++) {
Node node = childNodes.item(i);
if (node instanceof Element && nodeNameMatch(node, childEleNameList)) {
elements.add((Element) node);
}
}
return elements;
}
public static List<Element> getChildElementsByTagName(Element ele, String childElementName) {
return getChildElementsByTagName(ele, new String[]{childElementName});
}
/**
* Utility method that returns the first child element identified by its name.
*
* @param ele the DOM element to analyze
* @param childEleName the child element name to look for
* @return the {@code org.w3c.dom.Element} instance, or {@code null} if none found
*/
public static Element getFirstChildElementByTagName(Element ele, String childEleName) {
AssertionUtils.notNull(ele, "Element must not be null");
AssertionUtils.notNull(childEleName, "Element name must not be null");
NodeList nodeList = ele.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node instanceof Element && nodeNameMatch(node, childEleName)) {
return (Element) node;
}
}
return null;
}
/*
Namespace-aware equals comparison.
*/
public static boolean nodeNameEquals(Node node, String desiredName) {
AssertionUtils.notNull(node, "Node must not be null");
AssertionUtils.notNull(desiredName, "Desired name must not be null");
return nodeNameMatch(node, desiredName);
}
/*
Matches the given node's name and local name against the given desired names.
*/
private static boolean nodeNameMatch(Node node, Collection<?> desiredNames) {
return (desiredNames.contains(node.getNodeName()) || desiredNames.contains(node.getLocalName()));
}
/*
Matches the given node's name and local name against the given desired name.
*/
private static boolean nodeNameMatch(Node node, String desiredName) {
return (desiredName.equals(node.getNodeName()) || desiredName.equals(node.getLocalName()));
}
}
@@ -0,0 +1,45 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util;
import com.zfoo.protocol.util.AssertionUtils;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
/**
* @author jaysunxiao
* @version 3.0
*/
public abstract class EnumUtils {
public static <E extends Enum<E>> boolean isInEnums(String targetEnumName, E[] sourceEnums) {
AssertionUtils.notNull(targetEnumName, sourceEnums);
return Arrays.stream(sourceEnums).anyMatch(sourceEnum -> sourceEnum.name().equals(targetEnumName));
}
public static <E> Set<E> enumerationToSet(Enumeration<E> enumeration) {
var set = new HashSet<E>();
if (enumeration != null) {
while (enumeration.hasMoreElements()) {
set.add(enumeration.nextElement());
}
}
return set;
}
}
@@ -0,0 +1,75 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
/**
* @author jaysunxiao
* @version 3.0
*/
public abstract class ThreadUtils {
private static final int WAIT_TIME = 10;
private static final TimeUnit TIME_UNIT = TimeUnit.SECONDS;
public static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public static void shutdown(ExecutorService executor) {
try {
if (!executor.isTerminated()) {
executor.shutdown();
if (!executor.awaitTermination(WAIT_TIME, TIME_UNIT)) {
executor.shutdownNow();
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void shutdownForkJoinPool() {
try {
ForkJoinPool.commonPool().shutdown();
if (ForkJoinPool.commonPool().awaitTermination(WAIT_TIME, TimeUnit.SECONDS)) {
ForkJoinPool.commonPool().shutdownNow();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
/**
* 使用kill退出的方式,不能调用这个停止方法
*/
public static void shutdownApplication() {
new Thread(() -> {
System.exit(0);
}).start();
}
}
@@ -0,0 +1,136 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.captcha;
import com.zfoo.protocol.util.IOUtils;
import com.zfoo.util.captcha.model.AbstractCaptcha;
import com.zfoo.util.math.RandomUtils;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.Operation;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
/**
* png格式验证码
*
* @author jaysunxiao
* @version 3.0
*/
public class ArithmeticCaptcha extends AbstractCaptcha {
/**
* 计算公式
*/
private String arithmeticString;
@Override
public void buildCaptcha() {
var sb = new StringBuilder();
for (var i = 1; i <= length; i++) {
sb.append(RandomUtils.randomInt(10));
if (i == length) {
break;
}
var operation = RandomUtils.randomEle(List.of(Operation.ADD, Operation.SUBTRACT, Operation.MULTIPLY));
switch (operation) {
case ADD:
sb.append("+");
break;
case SUBTRACT:
sb.append("-");
break;
case MULTIPLY:
sb.append("x");
break;
default:
}
}
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(sb.toString().replaceAll("x", "*"));
captcha = exp.getValue(String.class);
sb.append("=?");
arithmeticString = sb.toString();
}
/**
* 生成验证码
*/
@Override
public void drawImage(OutputStream out) {
var captchaChars = arithmeticString.toCharArray();
BufferedImage bi = null;
Graphics2D g2d = null;
try {
bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
g2d = (Graphics2D) bi.getGraphics();
// 填充背景
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, width, height);
// 抗锯齿
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 画干扰圆
drawOval(2, g2d);
// 画字符串
g2d.setFont(getCaptchaFont().getFont());
FontMetrics fontMetrics = g2d.getFontMetrics();
int fW = width / captchaChars.length; // 每一个字符所占的宽度
int fSp = (fW - (int) fontMetrics.getStringBounds("8", g2d).getWidth()) / 2; // 字符的左右边距
for (int i = 0; i < captchaChars.length; i++) {
g2d.setColor(color());
int fY = height - ((height - (int) fontMetrics.getStringBounds(String.valueOf(captchaChars[i]), g2d).getHeight()) >> 1); // 文字的纵坐标
g2d.drawString(String.valueOf(captchaChars[i]), i * fW + fSp + 3, fY - 3);
}
ImageIO.write(bi, "png", out);
out.flush();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
IOUtils.closeIO(out);
if (g2d != null) {
g2d.dispose();
}
if (bi != null) {
bi.getGraphics().dispose();
}
}
}
@Override
public String toBase64() {
return toBase64("data:image/png;base64,");
}
public String getArithmeticString() {
return arithmeticString;
}
public void setArithmeticString(String arithmeticString) {
this.arithmeticString = arithmeticString;
}
}
@@ -0,0 +1,148 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.captcha;
import com.zfoo.protocol.util.IOUtils;
import com.zfoo.util.captcha.gif.GifEncoder;
import com.zfoo.util.captcha.model.AbstractCaptcha;
import com.zfoo.util.math.RandomUtils;
import java.awt.*;
import java.awt.geom.CubicCurve2D;
import java.awt.image.BufferedImage;
import java.io.OutputStream;
/**
* Gif验证码类
*
* @author jaysunxiao
* @version 3.0
*/
public class GifCaptcha extends AbstractCaptcha {
@Override
public void drawImage(OutputStream out) {
try {
char[] captchaChars = captcha.toCharArray(); // 获取验证码数组
// 随机生成每个文字的颜色
Color fontColor[] = new Color[length];
for (int i = 0; i < length; i++) {
fontColor[i] = color();
}
// 随机生成贝塞尔曲线参数
int x1 = 5, y1 = RandomUtils.randomInt(5, height / 2);
int x2 = width - 5, y2 = RandomUtils.randomInt(height / 2, height - 5);
int ctrlx = RandomUtils.randomInt(width / 4, width / 4 * 3), ctrly = RandomUtils.randomInt(5, height - 5);
if (RandomUtils.randomInt(2) == 0) {
int ty = y1;
y1 = y2;
y2 = ty;
}
int ctrlx1 = RandomUtils.randomInt(width / 4, width / 4 * 3), ctrly1 = RandomUtils.randomInt(5, height - 5);
int[][] besselXY = new int[][]{{x1, y1}, {ctrlx, ctrly}, {ctrlx1, ctrly1}, {x2, y2}};
// 开始画gif每一帧
GifEncoder gifEncoder = new GifEncoder();
gifEncoder.setQuality(180);
gifEncoder.setDelay(100);
gifEncoder.setRepeat(0);
gifEncoder.start(out);
for (int i = 0; i < length; i++) {
BufferedImage frame = drawImageFrame(fontColor, captchaChars, i, besselXY);
gifEncoder.addFrame(frame);
frame.flush();
}
gifEncoder.finish();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
IOUtils.closeIO(out);
}
}
@Override
public String toBase64() {
return toBase64("data:image/gif;base64,");
}
/**
* 画随机码图
*
* @param fontColor 随机字体颜色
* @param strs 字符数组
* @param flag 透明度
* @param besselXY 干扰线参数
* @return BufferedImage
*/
private BufferedImage drawImageFrame(Color[] fontColor, char[] strs, int flag, int[][] besselXY) {
BufferedImage bi = null;
Graphics2D g2d = null;
try {
bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
g2d = (Graphics2D) bi.getGraphics();
// 填充背景颜色
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, width, height);
// 抗锯齿
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 画干扰圆圈
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.1f * RandomUtils.randomInt(10))); // 设置透明度
drawOval(2, g2d);
// 画干扰线
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f)); // 设置透明度
g2d.setStroke(new BasicStroke(1.2f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
g2d.setColor(fontColor[0]);
CubicCurve2D shape = new CubicCurve2D.Double(besselXY[0][0], besselXY[0][1], besselXY[1][0], besselXY[1][1], besselXY[2][0], besselXY[2][1], besselXY[3][0], besselXY[3][1]);
g2d.draw(shape);
// 画验证码
g2d.setFont(getCaptchaFont().getFont());
FontMetrics fontMetrics = g2d.getFontMetrics();
int fW = width / strs.length; // 每一个字符所占的宽度
int fSp = (fW - (int) fontMetrics.getStringBounds("W", g2d).getWidth()) / 2; // 字符的左右边距
for (int i = 0; i < strs.length; i++) {
// 设置透明度
AlphaComposite ac3 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, getAlpha(flag, i));
g2d.setComposite(ac3);
g2d.setColor(fontColor[i]);
int fY = height - ((height - (int) fontMetrics.getStringBounds(String.valueOf(strs[i]), g2d).getHeight()) >> 1); // 文字的纵坐标
g2d.drawString(String.valueOf(strs[i]), i * fW + fSp + 3, fY - 3);
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (g2d != null) {
g2d.dispose();
}
if (bi != null) {
bi.getGraphics().dispose();
}
}
return bi;
}
/**
* 获取透明度,从0到1,自动计算步长
*
* @param i
* @param j
* @return 透明度
*/
private float getAlpha(int i, int j) {
int num = i + j;
float r = (float) 1 / (length - 1);
float s = length * r;
return num >= length ? (num * r - s) : num * r;
}
}
@@ -0,0 +1,82 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.captcha;
import com.zfoo.protocol.util.IOUtils;
import com.zfoo.util.captcha.model.AbstractCaptcha;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
/**
* png格式验证码
*
* @author jaysunxiao
* @version 3.0
*/
public class PngCaptcha extends AbstractCaptcha {
@Override
public void drawImage(OutputStream out) {
var captchaChars = captcha.toCharArray();
BufferedImage bi = null;
Graphics2D g2d = null;
try {
bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
g2d = (Graphics2D) bi.getGraphics();
// 填充背景
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, width, height);
// 抗锯齿
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 画干扰圆
drawOval(2, g2d);
// 画干扰线
g2d.setStroke(new BasicStroke(2f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
drawBesselLine(1, g2d);
// 画字符串
g2d.setFont(getCaptchaFont().getFont());
FontMetrics fontMetrics = g2d.getFontMetrics();
int fW = width / captchaChars.length; // 每一个字符所占的宽度
int fSp = (fW - (int) fontMetrics.getStringBounds("W", g2d).getWidth()) / 2; // 字符的左右边距
for (int i = 0; i < captchaChars.length; i++) {
g2d.setColor(color());
int fY = height - ((height - (int) fontMetrics.getStringBounds(String.valueOf(captchaChars[i]), g2d).getHeight()) >> 1); // 文字的纵坐标
g2d.drawString(String.valueOf(captchaChars[i]), i * fW + fSp + 3, fY - 3);
}
ImageIO.write(bi, "png", out);
out.flush();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
IOUtils.closeIO(out);
if (g2d != null) {
g2d.dispose();
}
if (bi != null) {
bi.getGraphics().dispose();
}
}
}
@Override
public String toBase64() {
return toBase64("data:image/png;base64,");
}
}
@@ -0,0 +1,344 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.captcha.gif;
import java.io.IOException;
import java.io.OutputStream;
/**
* @author jaysunxiao
* @version 3.0
*/
public class Encoder {
static final int BITS = 12;
static final int HSIZE = 5003; // 80% 占用率
private static final int EOF = -1;
int n_bits; // number of bits/code
int maxbits = BITS; // user settable max # bits/code
int maxcode; // maximum code, given n_bits
int maxmaxcode = 1 << BITS; // should NEVER generate this code
int[] htab = new int[HSIZE];
int[] codetab = new int[HSIZE];
int hsize = HSIZE; // for dynamic table sizing
int free_ent = 0; // first unused entry
// block compression parameters -- after all codes are used up,
// and compression rate changes, start over.
boolean clear_flg = false;
int g_init_bits;
int ClearCode;
int EOFCode;
int cur_accum = 0;
int cur_bits = 0;
// Algorithm: use open addressing double hashing (no chaining) on the
// prefix code / next character combination. We do a variant of Knuth's
// algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
// secondary probe. Here, the modular division first probe is gives way
// to a faster exclusive-or manipulation. Also do block compression with
// an adaptive reset, whereby the code table is cleared when the compression
// ratio decreases, but after the table fills. The variable-length output
// codes are re-sized at this point, and a special CLEAR code is generated
// for the decompressor. Late addition: construct the table according to
// file size for noticeable speed improvement on small files. Please direct
// questions about this implementation to ames!jaw.
int masks[] =
{
0x0000,
0x0001,
0x0003,
0x0007,
0x000F,
0x001F,
0x003F,
0x007F,
0x00FF,
0x01FF,
0x03FF,
0x07FF,
0x0FFF,
0x1FFF,
0x3FFF,
0x7FFF,
0xFFFF};
// Number of characters so far in this 'packet'
int a_count;
// Define the storage for the packet accumulator
byte[] accum = new byte[256];
// output
//
// Output the given code.
// Inputs:
// code: A n_bits-bit integer. If == -1, then EOF. This assumes
// that n_bits =< wordsize - 1.
// Outputs:
// Outputs code to the file.
// Assumptions:
// Chars are 8 bits long.
// Algorithm:
// Maintain a BITS character long buffer (so that 8 codes will
// fit in it exactly). Use the VAX insv instruction to insert each
// code in turn. When the buffer fills up empty it and start over.
// 图片的宽高
private int imgW, imgH;
private byte[] pixAry;
private int initCodeSize; // 验证码位数
private int remaining; // 剩余数量
private int curPixel; // 像素
//----------------------------------------------------------------------------
/**
* @param width 宽度
* @param height 高度
* @param pixels 像素
* @param color_depth 颜色
*/
Encoder(int width, int height, byte[] pixels, int color_depth) {
imgW = width;
imgH = height;
pixAry = pixels;
initCodeSize = Math.max(2, color_depth);
}
// Add a character to the end of the current packet, and if it is 254
// characters, flush the packet to disk.
/**
* @param c 字节
* @param outs 输出流
* @throws IOException IO异常
*/
void char_out(byte c, OutputStream outs) throws IOException {
accum[a_count++] = c;
if (a_count >= 254) {
flush_char(outs);
}
}
// Clear out the hash table
// table clear for block compress
/**
* @param outs 输出流
* @throws IOException IO异常
*/
void cl_block(OutputStream outs) throws IOException {
cl_hash(hsize);
free_ent = ClearCode + 2;
clear_flg = true;
output(ClearCode, outs);
}
// reset code table
/**
* @param hsize int
*/
void cl_hash(int hsize) {
for (int i = 0; i < hsize; ++i) {
htab[i] = -1;
}
}
/**
* @param init_bits int
* @param outs 输出流
* @throws IOException IO异常
*/
void compress(int init_bits, OutputStream outs) throws IOException {
int fcode;
int i /* = 0 */;
int c;
int ent;
int disp;
int hsize_reg;
int hshift;
// Set up the globals: g_init_bits - initial number of bits
g_init_bits = init_bits;
// Set up the necessary values
clear_flg = false;
n_bits = g_init_bits;
maxcode = MAXCODE(n_bits);
ClearCode = 1 << (init_bits - 1);
EOFCode = ClearCode + 1;
free_ent = ClearCode + 2;
a_count = 0; // clear packet
ent = nextPixel();
hshift = 0;
for (fcode = hsize; fcode < 65536; fcode *= 2) {
++hshift;
}
hshift = 8 - hshift; // set hash code range bound
hsize_reg = hsize;
cl_hash(hsize_reg); // clear hash table
output(ClearCode, outs);
outer_loop:
while ((c = nextPixel()) != EOF) {
fcode = (c << maxbits) + ent;
i = (c << hshift) ^ ent; // xor hashing
if (htab[i] == fcode) {
ent = codetab[i];
continue;
} else if (htab[i] >= 0) // non-empty slot
{
disp = hsize_reg - i; // secondary hash (after G. Knott)
if (i == 0) {
disp = 1;
}
do {
if ((i -= disp) < 0) {
i += hsize_reg;
}
if (htab[i] == fcode) {
ent = codetab[i];
continue outer_loop;
}
} while (htab[i] >= 0);
}
output(ent, outs);
ent = c;
if (free_ent < maxmaxcode) {
codetab[i] = free_ent++; // code -> hashtable
htab[i] = fcode;
} else {
cl_block(outs);
}
}
// Put out the final code.
output(ent, outs);
output(EOFCode, outs);
}
//----------------------------------------------------------------------------
/**
* @param os 输出流
* @throws IOException IO异常
*/
void encode(OutputStream os) throws IOException {
os.write(initCodeSize); // write "initial code size" byte
remaining = imgW * imgH; // reset navigation variables
curPixel = 0;
compress(initCodeSize + 1, os); // compress and write the pixel data
os.write(0); // write block terminator
}
// Flush the packet to disk, and reset the accumulator
/**
* @param outs 输出流
* @throws IOException IO异常
*/
void flush_char(OutputStream outs) throws IOException {
if (a_count > 0) {
outs.write(a_count);
outs.write(accum, 0, a_count);
a_count = 0;
}
}
/**
* @param n_bits int
* @return int
*/
final int MAXCODE(int n_bits) {
return (1 << n_bits) - 1;
}
//----------------------------------------------------------------------------
// Return the next pixel from the image
//----------------------------------------------------------------------------
/**
* @return int
*/
private int nextPixel() {
if (remaining == 0) {
return EOF;
}
--remaining;
byte pix = pixAry[curPixel++];
return pix & 0xff;
}
/**
* @param code int
* @param outs 输出流
* @throws IOException IO异常
*/
void output(int code, OutputStream outs) throws IOException {
cur_accum &= masks[cur_bits];
if (cur_bits > 0) {
cur_accum |= (code << cur_bits);
} else {
cur_accum = code;
}
cur_bits += n_bits;
while (cur_bits >= 8) {
char_out((byte) (cur_accum & 0xff), outs);
cur_accum >>= 8;
cur_bits -= 8;
}
// If the next entry is going to be too big for the code size,
// then increase it, if possible.
if (free_ent > maxcode || clear_flg) {
if (clear_flg) {
maxcode = MAXCODE(n_bits = g_init_bits);
clear_flg = false;
} else {
++n_bits;
if (n_bits == maxbits) {
maxcode = maxmaxcode;
} else {
maxcode = MAXCODE(n_bits);
}
}
}
if (code == EOFCode) {
// At EOF, write the rest of the buffer.
while (cur_bits > 0) {
char_out((byte) (cur_accum & 0xff), outs);
cur_accum >>= 8;
cur_bits -= 8;
}
flush_char(outs);
}
}
}
@@ -0,0 +1,513 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.captcha.gif;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.*;
public class GifEncoder {
protected int width; // image size
protected int height;
protected Color transparent = null; // transparent color if given
protected int transIndex; // transparent index in color table
protected int repeat = -1; // no repeat
protected int delay = 0; // frame delay (hundredths)
protected boolean started = false; // ready to output frames
protected OutputStream out;
protected BufferedImage image; // current frame
protected byte[] pixels; // BGR byte array from frame
protected byte[] indexedPixels; // converted frame indexed to palette
protected int colorDepth; // number of bit planes
protected byte[] colorTab; // RGB palette
protected boolean[] usedEntry = new boolean[256]; // active palette entries
protected int palSize = 7; // color table size (bits-1)
protected int dispose = -1; // disposal code (-1 = use default)
protected boolean closeStream = false; // close stream when finished
protected boolean firstFrame = true;
protected boolean sizeSet = false; // if false, get size from first frame
protected int sample = 10; // default sample interval for quantizer
/**
* Sets the delay time between each frame, or changes it
* for subsequent frames (applies to last frame added).
*
* @param ms int delay time in milliseconds
*/
public void setDelay(int ms) {
delay = Math.round(ms / 10.0f);
}
/**
* Sets the GIF frame disposal code for the last added frame
* and any subsequent frames. Default is 0 if no transparent
* color has been set, otherwise 2.
*
* @param code int disposal code.
*/
public void setDispose(int code) {
if (code >= 0) {
dispose = code;
}
}
/**
* Sets the number of times the set of GIF frames
* should be played. Default is 1; 0 means play
* indefinitely. Must be invoked before the first
* image is added.
*
* @param iter int number of iterations.
*/
public void setRepeat(int iter) {
if (iter >= 0) {
repeat = iter;
}
}
/**
* Sets the transparent color for the last added frame
* and any subsequent frames.
* Since all colors are subject to modification
* in the quantization process, the color in the final
* palette for each frame closest to the given color
* becomes the transparent color for that frame.
* May be set to null to indicate no transparent color.
*
* @param c Color to be treated as transparent on display.
*/
public void setTransparent(Color c) {
transparent = c;
}
/**
* Adds next GIF frame. The frame is not written immediately, but is
* actually deferred until the next frame is received so that timing
* data can be inserted. Invoking <code>finish()</code> flushes all
* frames. If <code>setSize</code> was not invoked, the size of the
* first image is used for all subsequent frames.
*
* @param im BufferedImage containing frame to write.
* @return true if successful.
*/
public boolean addFrame(BufferedImage im) {
if ((im == null) || !started) {
return false;
}
boolean ok = true;
try {
if (!sizeSet) {
// use first frame's size
setSize(im.getWidth(), im.getHeight());
}
image = im;
getImagePixels(); // convert to correct format if necessary
analyzePixels(); // build color table & map pixels
if (firstFrame) {
writeLSD(); // logical screen descriptior
writePalette(); // global color table
if (repeat >= 0) {
// use NS app extension to indicate reps
writeNetscapeExt();
}
}
writeGraphicCtrlExt(); // write graphic control extension
writeImageDesc(); // image descriptor
if (!firstFrame) {
writePalette(); // local color table
}
writePixels(); // encode and write pixel data
firstFrame = false;
} catch (IOException e) {
ok = false;
}
return ok;
}
//added by alvaro
public boolean outFlush() {
boolean ok = true;
try {
out.flush();
return ok;
} catch (IOException e) {
ok = false;
}
return ok;
}
public byte[] getFrameByteArray() {
return ((ByteArrayOutputStream) out).toByteArray();
}
/**
* Flushes any pending data and closes output file.
* If writing to an OutputStream, the stream is not
* closed.
*
* @return boolean
*/
public boolean finish() {
if (!started) {
return false;
}
boolean ok = true;
started = false;
try {
out.write(0x3b); // gif trailer
out.flush();
if (closeStream) {
out.close();
}
} catch (IOException e) {
ok = false;
}
return ok;
}
public void reset() {
// reset for subsequent use
transIndex = 0;
out = null;
image = null;
pixels = null;
indexedPixels = null;
colorTab = null;
closeStream = false;
firstFrame = true;
}
/**
* Sets frame rate in frames per second. Equivalent to
* <code>setDelay(1000/fps)</code>.
*
* @param fps float frame rate (frames per second)
*/
public void setFrameRate(float fps) {
if (fps != 0f) {
delay = Math.round(100f / fps);
}
}
/**
* Sets quality of color quantization (conversion of images
* to the maximum 256 colors allowed by the GIF specification).
* Lower values (minimum = 1) produce better colors, but slow
* processing significantly. 10 is the default, and produces
* good color mapping at reasonable speeds. Values greater
* than 20 do not yield significant improvements in speed.
*
* @param quality int greater than 0.
*/
public void setQuality(int quality) {
if (quality < 1) {
quality = 1;
}
sample = quality;
}
/**
* Sets the GIF frame size. The default size is the
* size of the first frame added if this method is
* not invoked.
*
* @param w int frame width.
* @param h int frame width.
*/
public void setSize(int w, int h) {
if (started && !firstFrame) {
return;
}
width = w;
height = h;
if (width < 1) {
width = 320;
}
if (height < 1) {
height = 240;
}
sizeSet = true;
}
/**
* Initiates GIF file creation on the given stream. The stream
* is not closed automatically.
*
* @param os OutputStream on which GIF images are written.
* @return false if initial write failed.
*/
public boolean start(OutputStream os) {
if (os == null) {
return false;
}
boolean ok = true;
closeStream = false;
out = os;
try {
writeString("GIF89a"); // header
} catch (IOException e) {
ok = false;
}
return started = ok;
}
/**
* Initiates writing of a GIF file with the specified name.
*
* @param file String containing output file name.
* @return false if open or initial write failed.
*/
public boolean start(String file) {
boolean ok = true;
try {
out = new BufferedOutputStream(new FileOutputStream(file));
ok = start(out);
closeStream = true;
} catch (IOException e) {
ok = false;
}
return started = ok;
}
/**
* Analyzes image colors and creates color map.
*/
protected void analyzePixels() {
int len = pixels.length;
int nPix = len / 3;
indexedPixels = new byte[nPix];
Quant nq = new Quant(pixels, len, sample);
// initialize quantizer
colorTab = nq.process(); // create reduced palette
// convert map from BGR to RGB
for (int i = 0; i < colorTab.length; i += 3) {
byte temp = colorTab[i];
colorTab[i] = colorTab[i + 2];
colorTab[i + 2] = temp;
usedEntry[i / 3] = false;
}
// map image pixels to new palette
int k = 0;
for (int i = 0; i < nPix; i++) {
int index =
nq.map(pixels[k++] & 0xff,
pixels[k++] & 0xff,
pixels[k++] & 0xff);
usedEntry[index] = true;
indexedPixels[i] = (byte) index;
}
pixels = null;
colorDepth = 8;
palSize = 7;
// get closest match to transparent color if specified
if (transparent != null) {
transIndex = findClosest(transparent);
}
}
/**
* Returns index of palette color closest to c
*
* @param c color
* @return int
*/
protected int findClosest(Color c) {
if (colorTab == null) {
return -1;
}
int r = c.getRed();
int g = c.getGreen();
int b = c.getBlue();
int minpos = 0;
int dmin = 256 * 256 * 256;
int len = colorTab.length;
for (int i = 0; i < len; ) {
int dr = r - (colorTab[i++] & 0xff);
int dg = g - (colorTab[i++] & 0xff);
int db = b - (colorTab[i] & 0xff);
int d = dr * dr + dg * dg + db * db;
int index = i / 3;
if (usedEntry[index] && (d < dmin)) {
dmin = d;
minpos = index;
}
i++;
}
return minpos;
}
/**
* Extracts image pixels into byte array "pixels"
*/
protected void getImagePixels() {
int w = image.getWidth();
int h = image.getHeight();
int type = image.getType();
if ((w != width)
|| (h != height)
|| (type != BufferedImage.TYPE_3BYTE_BGR)) {
// create new image with right size/format
BufferedImage temp =
new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
Graphics2D g = temp.createGraphics();
g.drawImage(image, 0, 0, null);
image = temp;
}
pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
}
/**
* Writes Graphic Control Extension
*
* @throws IOException IO异常
*/
protected void writeGraphicCtrlExt() throws IOException {
out.write(0x21); // extension introducer
out.write(0xf9); // GCE label
out.write(4); // data block size
int transp, disp;
if (transparent == null) {
transp = 0;
disp = 0; // dispose = no action
} else {
transp = 1;
disp = 2; // force clear if using transparent color
}
if (dispose >= 0) {
disp = dispose & 7; // user override
}
disp <<= 2;
// packed fields
out.write(0 | // 1:3 reserved
disp | // 4:6 disposal
0 | // 7 user input - 0 = none
transp); // 8 transparency flag
writeShort(delay); // delay x 1/100 sec
out.write(transIndex); // transparent color index
out.write(0); // block terminator
}
/**
* Writes Image Descriptor
*
* @throws IOException IO异常
*/
protected void writeImageDesc() throws IOException {
out.write(0x2c); // image separator
writeShort(0); // image position x,y = 0,0
writeShort(0);
writeShort(width); // image size
writeShort(height);
// packed fields
if (firstFrame) {
// no LCT - GCT is used for first (or only) frame
out.write(0);
} else {
// specify normal LCT
out.write(0x80 | // 1 local color table 1=yes
0 | // 2 interlace - 0=no
0 | // 3 sorted - 0=no
0 | // 4-5 reserved
palSize); // 6-8 size of color table
}
}
/**
* Writes Logical Screen Descriptor
*
* @throws IOException IO异常
*/
protected void writeLSD() throws IOException {
// logical screen size
writeShort(width);
writeShort(height);
// packed fields
out.write((0x80 | // 1 : global color table flag = 1 (gct used)
0x70 | // 2-4 : color resolution = 7
0x00 | // 5 : gct sort flag = 0
palSize)); // 6-8 : gct size
out.write(0); // background color index
out.write(0); // pixel aspect ratio - assume 1:1
}
/**
* Writes Netscape application extension to define
* repeat count.
*
* @throws IOException IO异常
*/
protected void writeNetscapeExt() throws IOException {
out.write(0x21); // extension introducer
out.write(0xff); // app extension label
out.write(11); // block size
writeString("NETSCAPE" + "2.0"); // app id + auth code
out.write(3); // sub-block size
out.write(1); // loop sub-block id
writeShort(repeat); // loop count (extra iterations, 0=repeat forever)
out.write(0); // block terminator
}
/**
* Writes color table
*
* @throws IOException IO异常
*/
protected void writePalette() throws IOException {
out.write(colorTab, 0, colorTab.length);
int n = (3 * 256) - colorTab.length;
for (int i = 0; i < n; i++) {
out.write(0);
}
}
/**
* Encodes and writes pixel data
*
* @throws IOException IO异常
*/
protected void writePixels() throws IOException {
Encoder encoder = new Encoder(width, height, indexedPixels, colorDepth);
encoder.encode(out);
}
/**
* Write 16-bit value to output stream, LSB first
*
* @param value int
* @throws IOException IO异常
*/
protected void writeShort(int value) throws IOException {
out.write(value & 0xff);
out.write((value >> 8) & 0xff);
}
/**
* Writes string to output stream
*
* @param s string
* @throws IOException IO异常
*/
protected void writeString(String s) throws IOException {
for (int i = 0; i < s.length(); i++) {
out.write((byte) s.charAt(i));
}
}
}
@@ -0,0 +1,474 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.captcha.gif;
/**
* @author jaysunxiao
* @version 3.0
*/
public class Quant {
protected static final int netsize = 256; /* number of colours used */
/* four primes near 500 - assume no image has a length so large */
/* that it is divisible by all four primes */
protected static final int prime1 = 499;
protected static final int prime2 = 491;
protected static final int prime3 = 487;
protected static final int prime4 = 503;
protected static final int minpicturebytes = (3 * prime4);
/* minimum size for input image */
/* Program Skeleton
----------------
[select samplefac in range 1..30]
[read image from input file]
pic = (unsigned char*) malloc(3*width*height);
initnet(pic,3*width*height,samplefac);
learn();
unbiasnet();
[write output image header, using writecolourmap(f)]
inxbuild();
write output image using inxsearch(b,g,r) */
/* Network Definitions
------------------- */
protected static final int maxnetpos = (netsize - 1);
protected static final int netbiasshift = 4; /* bias for colour values */
protected static final int ncycles = 100; /* no. of learning cycles */
/* defs for freq and bias */
protected static final int intbiasshift = 16; /* bias for fractions */
protected static final int intbias = (((int) 1) << intbiasshift);
protected static final int gammashift = 10; /* gamma = 1024 */
protected static final int gamma = (((int) 1) << gammashift);
protected static final int betashift = 10;
protected static final int beta = (intbias >> betashift); /* beta = 1/1024 */
protected static final int betagamma =
(intbias << (gammashift - betashift));
/* defs for decreasing radius factor */
protected static final int initrad = (netsize >> 3); /* for 256 cols, radius starts */
protected static final int radiusbiasshift = 6; /* at 32.0 biased by 6 bits */
protected static final int radiusbias = (((int) 1) << radiusbiasshift);
protected static final int initradius = (initrad * radiusbias); /* and decreases by a */
protected static final int radiusdec = 30; /* factor of 1/30 each cycle */
/* defs for decreasing alpha factor */
protected static final int alphabiasshift = 10; /* alpha starts at 1.0 */
protected static final int initalpha = (((int) 1) << alphabiasshift);
/* radbias and alpharadbias used for radpower calculation */
protected static final int radbiasshift = 8;
protected static final int radbias = (((int) 1) << radbiasshift);
protected static final int alpharadbshift = (alphabiasshift + radbiasshift);
protected static final int alpharadbias = (((int) 1) << alpharadbshift);
protected int alphadec; /* biased by 10 bits */
/* Types and Global Variables
-------------------------- */
protected byte[] thepicture; /* the input image itself */
protected int lengthcount; /* lengthcount = H*W*3 */
protected int samplefac; /* sampling factor 1..30 */
// typedef int pixel[4]; /* BGRc */
protected int[][] network; /* the network itself - [netsize][4] */
protected int[] netindex = new int[256];
/* for network lookup - really 256 */
protected int[] bias = new int[netsize];
/* bias and freq arrays for learning */
protected int[] freq = new int[netsize];
protected int[] radpower = new int[initrad];
/* radpower for precomputation */
/* Initialise network in range (0,0,0) to (255,255,255) and set parameters
----------------------------------------------------------------------- */
public Quant(byte[] thepic, int len, int sample) {
int i;
int[] p;
thepicture = thepic;
lengthcount = len;
samplefac = sample;
network = new int[netsize][];
for (i = 0; i < netsize; i++) {
network[i] = new int[4];
p = network[i];
p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize;
freq[i] = intbias / netsize; /* 1/netsize */
bias[i] = 0;
}
}
public byte[] colorMap() {
byte[] map = new byte[3 * netsize];
int[] index = new int[netsize];
for (int i = 0; i < netsize; i++) {
index[network[i][3]] = i;
}
int k = 0;
for (int i = 0; i < netsize; i++) {
int j = index[i];
map[k++] = (byte) (network[j][0]);
map[k++] = (byte) (network[j][1]);
map[k++] = (byte) (network[j][2]);
}
return map;
}
/* Insertion sort of network and building of netindex[0..255] (to do after unbias)
------------------------------------------------------------------------------- */
public void inxbuild() {
int i, j, smallpos, smallval;
int[] p;
int[] q;
int previouscol, startpos;
previouscol = 0;
startpos = 0;
for (i = 0; i < netsize; i++) {
p = network[i];
smallpos = i;
smallval = p[1]; /* index on g */
/* find smallest in i..netsize-1 */
for (j = i + 1; j < netsize; j++) {
q = network[j];
if (q[1] < smallval) { /* index on g */
smallpos = j;
smallval = q[1]; /* index on g */
}
}
q = network[smallpos];
/* swap p (i) and q (smallpos) entries */
if (i != smallpos) {
j = q[0];
q[0] = p[0];
p[0] = j;
j = q[1];
q[1] = p[1];
p[1] = j;
j = q[2];
q[2] = p[2];
p[2] = j;
j = q[3];
q[3] = p[3];
p[3] = j;
}
/* smallval entry is now in position i */
if (smallval != previouscol) {
netindex[previouscol] = (startpos + i) >> 1;
for (j = previouscol + 1; j < smallval; j++) {
netindex[j] = i;
}
previouscol = smallval;
startpos = i;
}
}
netindex[previouscol] = (startpos + maxnetpos) >> 1;
for (j = previouscol + 1; j < 256; j++) {
netindex[j] = maxnetpos; /* really 256 */
}
}
/* Main Learning Loop
------------------ */
public void learn() {
int i, j, b, g, r;
int radius, rad, alpha, step, delta, samplepixels;
byte[] p;
int pix, lim;
if (lengthcount < minpicturebytes) {
samplefac = 1;
}
alphadec = 30 + ((samplefac - 1) / 3);
p = thepicture;
pix = 0;
lim = lengthcount;
samplepixels = lengthcount / (3 * samplefac);
delta = samplepixels / ncycles;
alpha = initalpha;
radius = initradius;
rad = radius >> radiusbiasshift;
if (rad <= 1) {
rad = 0;
}
for (i = 0; i < rad; i++) {
radpower[i] =
alpha * (((rad * rad - i * i) * radbias) / (rad * rad));
}
//fprintf(stderr,"beginning 1D learning: initial radius=%d\n", rad);
if (lengthcount < minpicturebytes) {
step = 3;
} else if ((lengthcount % prime1) != 0) {
step = 3 * prime1;
} else {
if ((lengthcount % prime2) != 0) {
step = 3 * prime2;
} else {
if ((lengthcount % prime3) != 0) {
step = 3 * prime3;
} else {
step = 3 * prime4;
}
}
}
i = 0;
while (i < samplepixels) {
b = (p[pix + 0] & 0xff) << netbiasshift;
g = (p[pix + 1] & 0xff) << netbiasshift;
r = (p[pix + 2] & 0xff) << netbiasshift;
j = contest(b, g, r);
altersingle(alpha, j, b, g, r);
if (rad != 0) {
alterneigh(rad, j, b, g, r); /* alter neighbours */
}
pix += step;
if (pix >= lim) {
pix -= lengthcount;
}
i++;
if (delta == 0) {
delta = 1;
}
if (i % delta == 0) {
alpha -= alpha / alphadec;
radius -= radius / radiusdec;
rad = radius >> radiusbiasshift;
if (rad <= 1) {
rad = 0;
}
for (j = 0; j < rad; j++) {
radpower[j] =
alpha * (((rad * rad - j * j) * radbias) / (rad * rad));
}
}
}
//fprintf(stderr,"finished 1D learning: final alpha=%f !\n",((float)alpha)/initalpha);
}
/* Search for BGR values 0..255 (after net is unbiased) and return colour index
---------------------------------------------------------------------------- */
public int map(int b, int g, int r) {
int i, j, dist, a, bestd;
int[] p;
int best;
bestd = 1000; /* biggest possible dist is 256*3 */
best = -1;
i = netindex[g]; /* index on g */
j = i - 1; /* start at netindex[g] and work outwards */
while ((i < netsize) || (j >= 0)) {
if (i < netsize) {
p = network[i];
dist = p[1] - g; /* inx key */
if (dist >= bestd) {
i = netsize; /* stop iter */
} else {
i++;
if (dist < 0) {
dist = -dist;
}
a = p[0] - b;
if (a < 0) {
a = -a;
}
dist += a;
if (dist < bestd) {
a = p[2] - r;
if (a < 0) {
a = -a;
}
dist += a;
if (dist < bestd) {
bestd = dist;
best = p[3];
}
}
}
}
if (j >= 0) {
p = network[j];
dist = g - p[1]; /* inx key - reverse dif */
if (dist >= bestd) {
j = -1; /* stop iter */
} else {
j--;
if (dist < 0) {
dist = -dist;
}
a = p[0] - b;
if (a < 0) {
a = -a;
}
dist += a;
if (dist < bestd) {
a = p[2] - r;
if (a < 0) {
a = -a;
}
dist += a;
if (dist < bestd) {
bestd = dist;
best = p[3];
}
}
}
}
}
return (best);
}
public byte[] process() {
learn();
unbiasnet();
inxbuild();
return colorMap();
}
/* Unbias network to give byte values 0..255 and record position i to prepare for sort
----------------------------------------------------------------------------------- */
public void unbiasnet() {
int i, j;
for (i = 0; i < netsize; i++) {
network[i][0] >>= netbiasshift;
network[i][1] >>= netbiasshift;
network[i][2] >>= netbiasshift;
network[i][3] = i; /* record colour no */
}
}
/* Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|]
--------------------------------------------------------------------------------- */
protected void alterneigh(int rad, int i, int b, int g, int r) {
int j, k, lo, hi, a, m;
int[] p;
lo = i - rad;
if (lo < -1) {
lo = -1;
}
hi = i + rad;
if (hi > netsize) {
hi = netsize;
}
j = i + 1;
k = i - 1;
m = 1;
while ((j < hi) || (k > lo)) {
a = radpower[m++];
if (j < hi) {
p = network[j++];
try {
p[0] -= (a * (p[0] - b)) / alpharadbias;
p[1] -= (a * (p[1] - g)) / alpharadbias;
p[2] -= (a * (p[2] - r)) / alpharadbias;
} catch (Exception e) {
} // prevents 1.3 miscompilation
}
if (k > lo) {
p = network[k--];
try {
p[0] -= (a * (p[0] - b)) / alpharadbias;
p[1] -= (a * (p[1] - g)) / alpharadbias;
p[2] -= (a * (p[2] - r)) / alpharadbias;
} catch (Exception e) {
}
}
}
}
/* Move neuron i towards biased (b,g,r) by factor alpha
---------------------------------------------------- */
protected void altersingle(int alpha, int i, int b, int g, int r) {
/* alter hit neuron */
int[] n = network[i];
n[0] -= (alpha * (n[0] - b)) / initalpha;
n[1] -= (alpha * (n[1] - g)) / initalpha;
n[2] -= (alpha * (n[2] - r)) / initalpha;
}
/* Search for biased BGR values
---------------------------- */
protected int contest(int b, int g, int r) {
/* finds closest neuron (min dist) and updates freq */
/* finds best neuron (min dist-bias) and returns position */
/* for frequently chosen neurons, freq[i] is high and bias[i] is negative */
/* bias[i] = gamma*((1/netsize)-freq[i]) */
int i, dist, a, biasdist, betafreq;
int bestpos, bestbiaspos, bestd, bestbiasd;
int[] n;
bestd = ~(((int) 1) << 31);
bestbiasd = bestd;
bestpos = -1;
bestbiaspos = bestpos;
for (i = 0; i < netsize; i++) {
n = network[i];
dist = n[0] - b;
if (dist < 0) {
dist = -dist;
}
a = n[1] - g;
if (a < 0) {
a = -a;
}
dist += a;
a = n[2] - r;
if (a < 0) {
a = -a;
}
dist += a;
if (dist < bestd) {
bestd = dist;
bestpos = i;
}
biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift));
if (biasdist < bestbiasd) {
bestbiasd = biasdist;
bestbiaspos = i;
}
betafreq = (freq[i] >> betashift);
freq[i] -= betafreq;
bias[i] += (betafreq << gammashift);
}
freq[bestpos] += beta;
bias[bestpos] -= betagamma;
return (bestbiaspos);
}
}
@@ -0,0 +1,285 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.captcha.model;
import com.zfoo.protocol.util.IOUtils;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.util.math.RandomUtils;
import java.awt.*;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.QuadCurve2D;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.util.Base64;
/**
* 验证码抽象类
*
* @author jaysunxiao
* @version 3.0
*/
public abstract class AbstractCaptcha {
/**
* 常用颜色
*/
private static final int[][] COLOR = new int[][]
{
{0, 135, 255},
{51, 153, 51},
{255, 102, 102},
{255, 153, 0},
{153, 102, 0},
{153, 102, 153},
{51, 153, 153},
{102, 102, 255},
{0, 102, 204},
{204, 51, 51},
{0, 153, 204},
{0, 51, 102}
};
/**
* 验证码随机字符长度
*/
protected int length = 5;
/**
* 验证码显示宽度
*/
protected int width = 130;
/**
* 验证码显示高度
*/
protected int height = 48;
/**
* 当前验证码
*/
protected String captcha;
private CaptchaCharEnum captchaChar = CaptchaCharEnum.ARAB_ENGLISH;
private CaptchaFontEnum captchaFont = CaptchaFontEnum.actionj;
public void buildCaptcha() {
switch (captchaChar) {
case ARAB_NUMBER:
captcha = RandomUtils.randomString(StringUtils.ARAB_NUMBER, length);
break;
case ENGLISH_CHAR:
captcha = RandomUtils.randomString(StringUtils.ENGLISH_CHAR, length);
break;
case ARAB_ENGLISH:
captcha = RandomUtils.randomString(length);
break;
default:
}
}
/**
* 给定范围获得随机颜色
*
* @param fc 0-255
* @param bc 0-255
* @return 随机颜色
*/
protected Color color(int fc, int bc) {
if (fc > 255) {
fc = 255;
}
if (bc > 255) {
bc = 255;
}
int r = fc + RandomUtils.randomInt(bc - fc);
int g = fc + RandomUtils.randomInt(bc - fc);
int b = fc + RandomUtils.randomInt(bc - fc);
return new Color(r, g, b);
}
/**
* 获取随机常用颜色
*
* @return 随机颜色
*/
protected Color color() {
int[] color = COLOR[RandomUtils.randomInt(COLOR.length)];
return new Color(color[0], color[1], color[2]);
}
/**
* 验证码输出,抽象方法,由子类实现
*/
public abstract void drawImage(OutputStream os);
/**
* 输出base64编码
*
* @return base64编码字符串
*/
public abstract String toBase64();
/**
* 输出base64编码
*
* @param type 编码头
* @return base64编码字符串
*/
public String toBase64(String type) {
var outputStream = new ByteArrayOutputStream();
drawImage(outputStream);
var base64 = type + Base64.getEncoder().encodeToString(outputStream.toByteArray());
IOUtils.closeIO(outputStream);
return base64;
}
/**
* 获取当前的验证码
*/
public String captcha() {
return captcha;
}
/**
* 随机画干扰线
*
* @param num 数量
* @param g Graphics2D
*/
public void drawLine(int num, Graphics2D g) {
drawLine(num, null, g);
}
/**
* 随机画干扰线
*
* @param num 数量
* @param color 颜色
* @param g Graphics2D
*/
public void drawLine(int num, Color color, Graphics2D g) {
for (int i = 0; i < num; i++) {
g.setColor(color == null ? color() : color);
int x1 = RandomUtils.randomInt(-10, width - 10);
int y1 = RandomUtils.randomInt(5, height - 5);
int x2 = RandomUtils.randomInt(10, width + 10);
int y2 = RandomUtils.randomInt(2, height - 2);
g.drawLine(x1, y1, x2, y2);
}
}
/**
* 随机画干扰圆
*
* @param num 数量
* @param g Graphics2D
*/
public void drawOval(int num, Graphics2D g) {
drawOval(num, null, g);
}
/**
* 随机画干扰圆
*
* @param num 数量
* @param color 颜色
* @param g Graphics2D
*/
public void drawOval(int num, Color color, Graphics2D g) {
for (int i = 0; i < num; i++) {
g.setColor(color == null ? color() : color);
int w = 5 + RandomUtils.randomInt(10);
g.drawOval(RandomUtils.randomInt(width - 25), RandomUtils.randomInt(height - 15), w, w);
}
}
/**
* 随机画贝塞尔曲线
*
* @param num 数量
* @param g Graphics2D
*/
public void drawBesselLine(int num, Graphics2D g) {
drawBesselLine(num, null, g);
}
/**
* 随机画贝塞尔曲线
*
* @param num 数量
* @param color 颜色
* @param g Graphics2D
*/
public void drawBesselLine(int num, Color color, Graphics2D g) {
for (var i = 0; i < num; i++) {
g.setColor(color == null ? color() : color);
int x1 = 5, y1 = RandomUtils.randomInt(5, height / 2);
int x2 = width - 5, y2 = RandomUtils.randomInt(height / 2, height - 5);
int ctrlx = RandomUtils.randomInt(width / 4, width / 4 * 3), ctrly = RandomUtils.randomInt(5, height - 5);
if (RandomUtils.randomInt(2) == 0) {
int ty = y1;
y1 = y2;
y2 = ty;
}
if (RandomUtils.randomInt(2) == 0) { // 二阶贝塞尔曲线
QuadCurve2D shape = new QuadCurve2D.Double();
shape.setCurve(x1, y1, ctrlx, ctrly, x2, y2);
g.draw(shape);
} else { // 三阶贝塞尔曲线
int ctrlx1 = RandomUtils.randomInt(width / 4, width / 4 * 3), ctrly1 = RandomUtils.randomInt(5, height - 5);
CubicCurve2D shape = new CubicCurve2D.Double(x1, y1, ctrlx, ctrly, ctrlx1, ctrly1, x2, y2);
g.draw(shape);
}
}
}
public CaptchaCharEnum getCaptchaChar() {
return captchaChar;
}
public void setCaptchaChar(CaptchaCharEnum captchaChar) {
this.captchaChar = captchaChar;
}
public CaptchaFontEnum getCaptchaFont() {
return captchaFont;
}
public void setCaptchaFont(CaptchaFontEnum captchaFont) {
this.captchaFont = captchaFont;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
}
@@ -0,0 +1,39 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.captcha.model;
/**
* @author jaysunxiao
* @version 3.0
*/
public enum CaptchaCharEnum {
/**
* 纯数字
*/
ARAB_NUMBER,
/**
* 纯英文字母
*/
ENGLISH_CHAR,
/**
* 数字和英文结合
*/
ARAB_ENGLISH,
;
}
@@ -0,0 +1,73 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.captcha.model;
import com.zfoo.protocol.util.ClassUtils;
import com.zfoo.protocol.util.IOUtils;
import com.zfoo.protocol.util.StringUtils;
import java.awt.*;
import java.io.InputStream;
/**
* @author jaysunxiao
* @version 3.0
*/
public enum CaptchaFontEnum {
/**
* 默认字体
*/
actionj,
epilog,
fresnel,
headache,
lexo,
prefix,
progbot,
ransom,
robot,
scandal,
;
private static final Font DEFAULT_FONT = new Font("Arial", Font.BOLD, 32);
private Font font;
CaptchaFontEnum() {
InputStream inputStream = null;
try {
inputStream = ClassUtils.getFileFromClassPath(StringUtils.format("captcha/{}.ttf", this.name()));
font = Font.createFont(Font.TRUETYPE_FONT, inputStream).deriveFont(Font.BOLD, 32f);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
IOUtils.closeIO(inputStream);
}
}
public Font getFont() {
return font;
}
}
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.math;
/**
* @author jaysunxiao
* @version 3.0
*/
public class Combinatorics {// |kɒmbinəˋtɒ:riks|n.组合学
//输出数组a的startIndex~endIndex(包括端点)的全排列
public void permutation(int[] a, int startIndex, int endIndex) {
if (startIndex == endIndex) {
for (int i = 0; i <= endIndex; i++) {
System.out.print(a[i]);
}
System.out.println();
}
for (int i = startIndex; i <= endIndex; i++) {
swap(a, i, startIndex);
permutation(a, startIndex + 1, endIndex);
swap(a, i, startIndex);
}
}
//输出数组a的全排列
public void permutation(int[] a) {
permutation(a, 0, a.length - 1);
}
//public void Combination(int[] a,int[] b,int)
//交换数组中的两个元素
public void swap(int[] a, int xIndex, int yIndex) {
int temp = a[xIndex];
a[xIndex] = a[yIndex];
a[yIndex] = temp;
}
}
@@ -0,0 +1,76 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.math;
import com.zfoo.protocol.model.Pair;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.TreeMap;
/**
* 带虚拟节点的一致性Hash算法,参考:http://www.zsythink.net/archives/1182
*
* @author jaysunxiao
* @version 3.0
*/
public class ConsistentHash<K, V> {
// 真实结点列表,考虑到服务器上线、下线的场景,即添加、删除的场景会比较频繁,这里使用LinkedList会更好
private List<Pair<K, V>> realNodes = new LinkedList<>();
// 虚拟节点,key表示虚拟节点的hash值,value表示虚拟节点的名称
private TreeMap<Integer, Pair<K, V>> virtualNodeTreeMap = new TreeMap<>();
// 虚拟节点的数目,数量越大约均匀,经验值150
private int virtualNodes = 0;
public ConsistentHash(List<Pair<K, V>> realNodes, int virtualNodes) {
// 先把原始的服务器添加到真实结点列表中
this.realNodes.addAll(realNodes);
this.virtualNodes = virtualNodes;
// 初始化
// 再添加虚拟节点,遍历LinkedList使用foreach循环效率会比较高
for (var realNode : realNodes) {
addNode(realNode);
}
}
public void addNode(Pair<K, V> realNode) {
for (var i = 0; i < this.virtualNodes; i++) {
var virtualNode = realNode.getKey().toString() + "&&VN" + i;
var hash = HashUtils.fnvHash(virtualNode);
virtualNodeTreeMap.put(hash, realNode);
}
}
// 得到应当路由到的结点
public Pair<K, V> getRealNode(Object key) {
// 得到该key的hash值
var hash = HashUtils.fnvHash(key);
// 第一个Key就是顺时针过去离node最近的那个结点
var entry = virtualNodeTreeMap.ceilingEntry(hash);
if (Objects.isNull(entry)) {
// 如果没有比该key的hash值大的,则从第一个node开始
return virtualNodeTreeMap.firstEntry().getValue();
}
return entry.getValue();
}
}
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.math;
/**
* 改进的hash算法虽然发布比较均匀,但是没有Java自带的hash算法速度快,所以需要知道自己需要什么
*
* @author jaysunxiao
* @version 3.0
*/
public abstract class HashUtils {
private static final int P = 16777619;
private static final int INIT_HASH = (int) 2166136261L;
/**
* 改进的32位FNV算法1
*
* @param data 数组
* @return hash结果
*/
public static int fnvHash(byte[] data) {
var hash = INIT_HASH;
for (byte b : data) {
hash = (hash ^ b) * P;
}
hash += hash << 13;
hash ^= hash >> 7;
hash += hash << 3;
hash ^= hash >> 17;
hash += hash << 5;
return Math.abs(hash);
}
/**
* 改进的32位FNV算法1
*
* @param object 计算hash的对象,会调用toString方法
* @return hash结果
*/
public static int fnvHash(Object object) {
var hash = object.toString().chars().reduce(INIT_HASH, (left, right) -> (left ^ right) * P);
hash += hash << 13;
hash ^= hash >> 7;
hash += hash << 3;
hash ^= hash >> 17;
hash += hash << 5;
return Math.abs(hash);
}
}
@@ -0,0 +1,998 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.math;
import com.zfoo.protocol.collection.CollectionUtils;
import com.zfoo.protocol.util.AssertionUtils;
import com.zfoo.protocol.util.StringUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Collection;
/**
* 数字工具类<br>
* BigDecimal(double val)构造方法的结果有一定的不可预知性,例如:
*
* <pre>
* new BigDecimal(0.1)
* </pre>
* <p>
* 表示的不是0.1而是0.1000000000000000055511151231257827021181583404541015625
* <p>
* 这是因为0.1无法准确的表示为double。因此应该使用new BigDecimal(String)。
*
* @author jaysunxiao
* @version 3.0
*/
public abstract class NumberUtils {
/**
* 默认除法运算精度
*/
private static final int DEFAULT_DIV_SCALE = 10;
/**
* 提供精确的加法运算<br>
* 如果传入多个值为null或者空,则返回0
*
* @param values 多个被加值
* @return 和
*/
public static BigDecimal add(Number... values) {
if (CollectionUtils.isEmpty(values)) {
return BigDecimal.ZERO;
}
Number value = values[0];
BigDecimal result = new BigDecimal(null == value ? "0" : value.toString());
for (int i = 1; i < values.length; i++) {
value = values[i];
if (null != value) {
result = result.add(new BigDecimal(value.toString()));
}
}
return result;
}
/**
* 提供精确的加法运算<br>
* 如果传入多个值为null或者空,则返回0
*
* @param values 多个被加值
* @return 求和
*/
public static BigDecimal add(String... values) {
if (CollectionUtils.isEmpty(values)) {
return BigDecimal.ZERO;
}
String value = values[0];
BigDecimal result = new BigDecimal(null == value ? "0" : value);
for (int i = 1; i < values.length; i++) {
value = values[i];
if (null != value) {
result = result.add(new BigDecimal(value));
}
}
return result;
}
/**
* 提供精确的加法运算<br>
* 如果传入多个值为null或者空,则返回0
*
* @param values 多个被加值
* @return 求和
*/
public static BigDecimal add(BigDecimal... values) {
if (CollectionUtils.isEmpty(values)) {
return BigDecimal.ZERO;
}
BigDecimal value = values[0];
BigDecimal result = null == value ? BigDecimal.ZERO : value;
for (int i = 1; i < values.length; i++) {
value = values[i];
if (null != value) {
result = result.add(value);
}
}
return result;
}
/**
* 提供精确的减法运算<br>
* 如果传入多个值为null或者空,则返回0
*
* @param values 多个被减值
* @return 差
*/
public static BigDecimal sub(Number... values) {
if (CollectionUtils.isEmpty(values)) {
return BigDecimal.ZERO;
}
Number value = values[0];
BigDecimal result = new BigDecimal(null == value ? "0" : value.toString());
for (int i = 1; i < values.length; i++) {
value = values[i];
if (null != value) {
result = result.subtract(new BigDecimal(value.toString()));
}
}
return result;
}
/**
* 提供精确的减法运算<br>
* 如果传入多个值为null或者空,则返回0
*
* @param values 多个被减值
* @return 差
*/
public static BigDecimal sub(String... values) {
if (CollectionUtils.isEmpty(values)) {
return BigDecimal.ZERO;
}
String value = values[0];
BigDecimal result = new BigDecimal(null == value ? "0" : value);
for (int i = 1; i < values.length; i++) {
value = values[i];
if (null != value) {
result = result.subtract(new BigDecimal(value));
}
}
return result;
}
/**
* 提供精确的减法运算<br>
* 如果传入多个值为null或者空,则返回0
*
* @param values 多个被减值
* @return 差
*/
public static BigDecimal sub(BigDecimal... values) {
if (CollectionUtils.isEmpty(values)) {
return BigDecimal.ZERO;
}
BigDecimal value = values[0];
BigDecimal result = null == value ? BigDecimal.ZERO : value;
for (int i = 1; i < values.length; i++) {
value = values[i];
if (null != value) {
result = result.subtract(value);
}
}
return result;
}
/**
* 提供精确的乘法运算<br>
* 如果传入多个值为null或者空,则返回0
*
* @param values 多个被乘值
* @return 积
*/
public static BigDecimal mul(Number... values) {
if (CollectionUtils.isEmpty(values)) {
return BigDecimal.ZERO;
}
Number value = values[0];
BigDecimal result = new BigDecimal(null == value ? "0" : value.toString());
for (int i = 1; i < values.length; i++) {
value = values[i];
if (null != value) {
result = result.multiply(new BigDecimal(value.toString()));
}
}
return result;
}
/**
* 提供精确的乘法运算
*
* @param a 被乘数
* @param b 乘数
* @return 积
*/
public static BigDecimal mul(String a, String b) {
return mul(new BigDecimal(a), new BigDecimal(b));
}
/**
* 提供精确的乘法运算<br>
* 如果传入多个值为null或者空,则返回0
*
* @param values 多个被乘值
* @return 积
*/
public static BigDecimal mul(String... values) {
if (CollectionUtils.isEmpty(values)) {
return BigDecimal.ZERO;
}
String value = values[0];
BigDecimal result = new BigDecimal(null == value ? "0" : value);
for (int i = 1; i < values.length; i++) {
value = values[i];
if (null != value) {
result = result.multiply(new BigDecimal(value));
}
}
return result;
}
/**
* 提供精确的乘法运算<br>
* 如果传入多个值为null或者空,则返回0
*
* @param values 多个被乘值
* @return 积
*/
public static BigDecimal mul(BigDecimal... values) {
if (CollectionUtils.isEmpty(values)) {
return BigDecimal.ZERO;
}
BigDecimal value = values[0];
BigDecimal result = null == value ? BigDecimal.ZERO : value;
for (int i = 1; i < values.length; i++) {
value = values[i];
if (null != value) {
result = result.multiply(value);
}
}
return result;
}
/**
* 提供(相对)精确的除法运算,当发生除不尽的情况的时候,精确到小数点后10位,后面的四舍五入
*
* @param a 被除数
* @param b 除数
* @return 两个参数的商
*/
public static BigDecimal div(Number a, Number b) {
return div(a, b, DEFAULT_DIV_SCALE);
}
/**
* 提供(相对)精确的除法运算,当发生除不尽的情况的时候,精确到小数点后10位,后面的四舍五入
*
* @param a 被除数
* @param b 除数
* @return 两个参数的商
*/
public static BigDecimal div(String a, String b) {
return div(a, b, DEFAULT_DIV_SCALE);
}
/**
* 提供(相对)精确的除法运算,当发生除不尽的情况时,由scale指定精确度,后面的四舍五入
*
* @param a 被除数
* @param b 除数
* @param scale 精确度,如果为负值,取绝对值
* @return 两个参数的商
*/
public static BigDecimal div(Number a, Number b, int scale) {
return div(a, b, scale, RoundingMode.HALF_UP);
}
/**
* 提供(相对)精确的除法运算,当发生除不尽的情况时,由scale指定精确度,后面的四舍五入
*
* @param a 被除数
* @param b 除数
* @param scale 精确度,如果为负值,取绝对值
* @return 两个参数的商
*/
public static BigDecimal div(String a, String b, int scale) {
return div(a, b, scale, RoundingMode.HALF_UP);
}
/**
* 提供(相对)精确的除法运算,当发生除不尽的情况时,由scale指定精确度
*
* @param a 被除数
* @param b 除数
* @param scale 精确度,如果为负值,取绝对值
* @param roundingMode 保留小数的模式 {@link RoundingMode}
* @return 两个参数的商
*/
public static BigDecimal div(Number a, Number b, int scale, RoundingMode roundingMode) {
return div(a.toString(), b.toString(), scale, roundingMode);
}
/**
* 提供(相对)精确的除法运算,当发生除不尽的情况时,由scale指定精确度
*
* @param a 被除数
* @param b 除数
* @param scale 精确度,如果为负值,取绝对值
* @param roundingMode 保留小数的模式 {@link RoundingMode}
* @return 两个参数的商
*/
public static BigDecimal div(String a, String b, int scale, RoundingMode roundingMode) {
return div(new BigDecimal(a), new BigDecimal(b), scale, roundingMode);
}
/**
* 提供(相对)精确的除法运算,当发生除不尽的情况时,由scale指定精确度
*
* @param a 被除数
* @param b 除数
* @param scale 精确度,如果为负值,取绝对值
* @param roundingMode 保留小数的模式 {@link RoundingMode}
* @return 两个参数的商
*/
public static BigDecimal div(BigDecimal a, BigDecimal b, int scale, RoundingMode roundingMode) {
AssertionUtils.notNull(b, "Divisor must be not null !");
if (null == a) {
return BigDecimal.ZERO;
}
if (scale < 0) {
scale = -scale;
}
return a.divide(b, scale, roundingMode);
}
/**
* 提供精确的乘法运算<br>
* 如果传入多个值为null或者空,则返回0
*
* @param values 多个被乘值
* @return 积
*/
public static BigDecimal div(BigDecimal... values) {
if (CollectionUtils.isEmpty(values)) {
return BigDecimal.ZERO;
}
BigDecimal value = values[0];
BigDecimal result = null == value ? BigDecimal.ZERO : value;
for (int i = 1; i < values.length; i++) {
value = values[i];
if (null != value) {
result = result.divide(value);
}
}
return result;
}
// ------------------------------------------------------------------------------------------- round
/**
* 保留固定位数小数<br>
* 采用四舍五入策略 {@link RoundingMode#HALF_UP}<br>
* 例如保留2位小数:123.456789 =》 123.46
*
* @param v 值
* @param scale 保留小数位数
* @return 新值
*/
public static BigDecimal round(double v, int scale) {
return round(v, scale, RoundingMode.HALF_UP);
}
/**
* 保留固定位数小数<br>
* 采用四舍五入策略 {@link RoundingMode#HALF_UP}<br>
* 例如保留2位小数:123.456789 =》 123.46
*
* @param numberStr 数字值的字符串表现形式
* @param scale 保留小数位数
* @return 新值
*/
public static BigDecimal round(String numberStr, int scale) {
return round(numberStr, scale, RoundingMode.HALF_UP);
}
/**
* 保留固定位数小数<br>
* 采用四舍五入策略 {@link RoundingMode#HALF_UP}<br>
* 例如保留2位小数:123.456789 =》 123.46
*
* @param number 数字值
* @param scale 保留小数位数
* @return 新值
*/
public static BigDecimal round(BigDecimal number, int scale) {
return round(number, scale, RoundingMode.HALF_UP);
}
/**
* 保留固定位数小数<br>
* 例如保留四位小数:123.456789 =》 123.4567
*
* @param v 值
* @param scale 保留小数位数
* @param roundingMode 保留小数的模式 {@link RoundingMode}
* @return 新值
*/
public static BigDecimal round(double v, int scale, RoundingMode roundingMode) {
return round(Double.toString(v), scale, roundingMode);
}
/**
* 保留固定位数小数<br>
* 例如保留四位小数:123.456789 =》 123.4567
*
* @param numberStr 数字值的字符串表现形式
* @param scale 保留小数位数,如果传入小于0,则默认0
* @param roundingMode 保留小数的模式 {@link RoundingMode},如果传入null则默认四舍五入
* @return 新值
*/
public static BigDecimal round(String numberStr, int scale, RoundingMode roundingMode) {
AssertionUtils.isTrue(!StringUtils.isBlank(numberStr));
if (scale < 0) {
scale = 0;
}
return round(toBigDecimal(numberStr), scale, roundingMode);
}
/**
* 保留固定位数小数<br>
* 例如保留四位小数:123.456789 =》 123.4567
*
* @param number 数字值
* @param scale 保留小数位数,如果传入小于0,则默认0
* @param roundingMode 保留小数的模式 {@link RoundingMode},如果传入null则默认四舍五入
* @return 新值
*/
public static BigDecimal round(BigDecimal number, int scale, RoundingMode roundingMode) {
if (null == number) {
number = BigDecimal.ZERO;
}
if (scale < 0) {
scale = 0;
}
if (null == roundingMode) {
roundingMode = RoundingMode.HALF_UP;
}
return number.setScale(scale, roundingMode);
}
/**
* 四舍六入五成双计算法
* <p>
* 四舍六入五成双是一种比较精确比较科学的计数保留法,是一种数字修约规则。
* </p>
*
* <pre>
* 算法规则:
* 四舍六入五考虑,
* 五后非零就进一,
* 五后皆零看奇偶,
* 五前为偶应舍去,
* 五前为奇要进一。
* </pre>
*
* @param number 需要科学计算的数据
* @param scale 保留的小数位
* @return 结果
*/
public static BigDecimal roundHalfEven(Number number, int scale) {
return roundHalfEven(toBigDecimal(number), scale);
}
/**
* 四舍六入五成双计算法
* <p>
* 四舍六入五成双是一种比较精确比较科学的计数保留法,是一种数字修约规则。
* </p>
*
* <pre>
* 算法规则:
* 四舍六入五考虑,
* 五后非零就进一,
* 五后皆零看奇偶,
* 五前为偶应舍去,
* 五前为奇要进一。
* </pre>
*
* @param value 需要科学计算的数据
* @param scale 保留的小数位
* @return 结果
*/
public static BigDecimal roundHalfEven(BigDecimal value, int scale) {
return round(value, scale, RoundingMode.HALF_EVEN);
}
/**
* 保留固定小数位数,舍去多余位数
*
* @param number 需要科学计算的数据
* @param scale 保留的小数位
* @return 结果
*/
public static BigDecimal roundDown(Number number, int scale) {
return roundDown(toBigDecimal(number), scale);
}
/**
* 保留固定小数位数,舍去多余位数
*
* @param value 需要科学计算的数据
* @param scale 保留的小数位
* @return 结果
*/
public static BigDecimal roundDown(BigDecimal value, int scale) {
return round(value, scale, RoundingMode.DOWN);
}
// ------------------------------------------------------------------------------------------- decimalFormat
/**
* 格式化double<br>
* 对 {@link DecimalFormat} 做封装<br>
*
* @param pattern 格式 格式中主要以 # 和 0 两种占位符号来指定数字长度。0 表示如果位数不足则以 0 填充,# 表示只要有可能就把数字拉上这个位置。<br>
* <ul>
* <li>0 =》 取一位整数</li>
* <li>0.00 =》 取一位整数和两位小数</li>
* <li>00.000 =》 取两位整数和三位小数</li>
* <li># =》 取所有整数部分</li>
* <li>#.##% =》 以百分比方式计数,并取两位小数</li>
* <li>#.#####E0 =》 显示为科学计数法,并取五位小数</li>
* <li>,### =》 每三位以逗号进行分隔,例如:299,792,458</li>
* <li>光速大小为每秒,###米 =》 将格式嵌入文本</li>
* </ul>
* @param value 值
* @return 格式化后的值
*/
public static String decimalFormat(String pattern, double value) {
return new DecimalFormat(pattern).format(value);
}
/**
* 格式化double<br>
* 对 {@link DecimalFormat} 做封装<br>
*
* @param pattern 格式 格式中主要以 # 和 0 两种占位符号来指定数字长度。0 表示如果位数不足则以 0 填充,# 表示只要有可能就把数字拉上这个位置。<br>
* <ul>
* <li>0 =》 取一位整数</li>
* <li>0.00 =》 取一位整数和两位小数</li>
* <li>00.000 =》 取两位整数和三位小数</li>
* <li># =》 取所有整数部分</li>
* <li>#.##% =》 以百分比方式计数,并取两位小数</li>
* <li>#.#####E0 =》 显示为科学计数法,并取五位小数</li>
* <li>,### =》 每三位以逗号进行分隔,例如:299,792,458</li>
* <li>光速大小为每秒,###米 =》 将格式嵌入文本</li>
* </ul>
* @param value 值
* @return 格式化后的值
*/
public static String decimalFormat(String pattern, long value) {
return new DecimalFormat(pattern).format(value);
}
/**
* 格式化金额输出,每三位用逗号分隔
*
* @param value 金额
* @return 格式化后的值
*/
public static String decimalFormatMoney(double value) {
return decimalFormat(",##0.00", value);
}
/**
* 格式化百分比,小数采用四舍五入方式
*
* @param number 值
* @param scale 保留小数位数
* @return 百分比
*/
public static String formatPercent(double number, int scale) {
final NumberFormat format = NumberFormat.getPercentInstance();
format.setMaximumFractionDigits(scale);
return format.format(number);
}
// ------------------------------------------------------------------------------------------- isXXX
/**
* 判断String是否是整数<br>
* 支持8、10、16进制
*
* @param s String
* @return 是否为整数
*/
public static boolean isInteger(String s) {
try {
Integer.parseInt(s);
} catch (NumberFormatException e) {
return false;
}
return true;
}
/**
* 判断字符串是否是Long类型<br>
* 支持8、10、16进制
*
* @param s String
* @return 是否为{@link Long}类型
*/
public static boolean isLong(String s) {
try {
Long.parseLong(s);
} catch (NumberFormatException e) {
return false;
}
return true;
}
/**
* 判断字符串是否是浮点数
*
* @param s String
* @return 是否为{@link Double}类型
*/
public static boolean isDouble(String s) {
try {
Double.parseDouble(s);
if (s.contains(".")) {
return true;
}
return false;
} catch (NumberFormatException e) {
return false;
}
}
/**
* 判断字符串是否全部都为数字
* <pre>
* StringUtils.isNumeric(null) = false
* StringUtils.isNumeric("") = false
* StringUtils.isNumeric(" ") = false
* StringUtils.isNumeric("123") = true
* StringUtils.isNumeric("\u0967\u0968\u0969") = true
* StringUtils.isNumeric("12 3") = false
* StringUtils.isNumeric("ab2c") = false
* StringUtils.isNumeric("12-3") = false
* StringUtils.isNumeric("12.3") = false
* StringUtils.isNumeric("-123") = false
* StringUtils.isNumeric("+123") = false
* </pre>
*
* @param cs the CharSequence to check, may be null
* @return {@code true} if only contains digits, and is non-null
*/
public static boolean isNumeric(final CharSequence cs) {
if (StringUtils.isEmpty(cs)) {
return false;
}
return cs.chars().allMatch(it -> Character.isDigit(it));
}
/**
* 是否是质数(素数)<br>
* 质数表的质数又称素数。指整数在一个大于1的自然数中,除了1和此整数自身外,没法被其他自然数整除的数。
*
* @param n 数字
* @return 是否是质数
*/
public static boolean isPrimes(int n) {
AssertionUtils.isTrue(n > 1, "The number must be > 1");
for (int i = 2; i <= Math.sqrt(n); i++) {
if (n % i == 0) {
return false;
}
}
return true;
}
// ------------------------------------------------------------------------------------------- range
/**
* 从0开始给定范围内的整数列表,步进为1
*
* @param stop 结束(包含)
* @return 整数列表
*/
public static int[] range(int stop) {
return range(0, stop);
}
/**
* 给定范围内的整数列表,步进为1
*
* @param start 开始(包含)
* @param stop 结束(包含)
* @return 整数列表
*/
public static int[] range(int start, int stop) {
return range(start, stop, 1);
}
/**
* 给定范围内的整数列表
*
* @param start 开始(包含)
* @param stop 结束(包含)
* @param step 步进
* @return 整数列表
*/
public static int[] range(int start, int stop, int step) {
if (start < stop) {
step = Math.abs(step);
} else if (start > stop) {
step = -Math.abs(step);
} else {// start == end
return new int[]{start};
}
int size = Math.abs((stop - start) / step) + 1;
int[] values = new int[size];
int index = 0;
for (int i = start; (step > 0) ? i <= stop : i >= stop; i += step) {
values[index] = i;
index++;
}
return values;
}
/**
* 将给定范围内的整数添加到已有集合中,步进为1
*
* @param start 开始(包含)
* @param stop 结束(包含)
* @param values 集合
* @return 集合
*/
public static Collection<Integer> appendRange(int start, int stop, Collection<Integer> values) {
return appendRange(start, stop, 1, values);
}
/**
* 将给定范围内的整数添加到已有集合中
*
* @param start 开始(包含)
* @param stop 结束(包含)
* @param step 步进
* @param values 集合
* @return 集合
*/
public static Collection<Integer> appendRange(int start, int stop, int step, Collection<Integer> values) {
if (start < stop) {
step = Math.abs(step);
} else if (start > stop) {
step = -Math.abs(step);
} else {// start == end
values.add(start);
return values;
}
for (int i = start; (step > 0) ? i <= stop : i >= stop; i += step) {
values.add(i);
}
return values;
}
// ------------------------------------------------------------------------------------------- others
/**
* 计算阶乘
* <p>
* n! = n * (n-1) * ... * end
* </p>
*
* @param start 阶乘起始
* @param end 阶乘结束
* @return 结果
*/
public static long factorial(long start, long end) {
if (start < end) {
return 0L;
}
if (start == end) {
return 1L;
}
return start * factorial(start - 1, end);
}
/**
* 计算阶乘
* <p>
* n! = n * (n-1) * ... * 2 * 1
* </p>
*
* @param n 阶乘起始
* @return 结果
*/
public static long factorial(long n) {
return factorial(n, 1);
}
/**
* 平方根算法<br>
* 推荐使用 {@link Math#sqrt(double)}
*
* @param x 值
* @return 平方根
*/
public static long sqrt(long x) {
long y = 0;
long b = (~Long.MAX_VALUE) >>> 1;
while (b > 0) {
if (x >= y + b) {
x -= y + b;
y >>= 1;
y += b;
} else {
y >>= 1;
}
b >>= 2;
}
return y;
}
/**
* 最大公约数
*
* @param m 第一个值
* @param n 第二个值
* @return 最大公约数
*/
public static int divisor(int m, int n) {
while (m % n != 0) {
int temp = m % n;
m = n;
n = temp;
}
return n;
}
/**
* 最小公倍数
*
* @param m 第一个值
* @param n 第二个值
* @return 最小公倍数
*/
public static int multiple(int m, int n) {
return m * n / divisor(m, n);
}
/**
* 获得数字对应的二进制字符串
*
* @param number 数字
* @return 二进制字符串
*/
public static String getBinaryStr(Number number) {
if (number instanceof Long) {
return Long.toBinaryString((Long) number);
} else if (number instanceof Integer) {
return Integer.toBinaryString((Integer) number);
} else {
return Long.toBinaryString(number.longValue());
}
}
/**
* 二进制转int
*
* @param binaryStr 二进制字符串
* @return int
*/
public static int binaryToInt(String binaryStr) {
return Integer.parseInt(binaryStr, 2);
}
/**
* 二进制转long
*
* @param binaryStr 二进制字符串
* @return long
*/
public static long binaryToLong(String binaryStr) {
return Long.parseLong(binaryStr, 2);
}
// ------------------------------------------------------------------------------------------- compare
/**
* 数字转{@link BigDecimal}
*
* @param number 数字
* @return {@link BigDecimal}
*/
public static BigDecimal toBigDecimal(Number number) {
if (null == number) {
return BigDecimal.ZERO;
}
return toBigDecimal(number.toString());
}
/**
* 数字转{@link BigDecimal}
*
* @param number 数字
* @return {@link BigDecimal}
*/
public static BigDecimal toBigDecimal(String number) {
return (null == number) ? BigDecimal.ZERO : new BigDecimal(number);
}
/**
* 判断两个数字是否相邻,例如1和2相邻,1和3不相邻<br>
* 判断方法为做差取绝对值判断是否为1
*
* @param number1 数字1
* @param number2 数字2
* @return 是否相邻
*/
public static boolean isBeside(long number1, long number2) {
return Math.abs(number1 - number2) == 1;
}
/**
* 判断两个数字是否相邻,例如1和2相邻,1和3不相邻<br>
* 判断方法为做差取绝对值判断是否为1
*
* @param number1 数字1
* @param number2 数字2
* @return 是否相邻
*/
public static boolean isBeside(int number1, int number2) {
return Math.abs(number1 - number2) == 1;
}
/**
* 提供精确的幂运算
*
* @param number 底数
* @param n 指数
* @return 幂的积
*/
public static BigDecimal pow(Number number, int n) {
return pow(toBigDecimal(number), n);
}
/**
* 提供精确的幂运算
*
* @param number 底数
* @param n 指数
* @return 幂的积
*/
public static BigDecimal pow(BigDecimal number, int n) {
return number.pow(n);
}
}
@@ -0,0 +1,70 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.math;
import org.springframework.lang.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
/**
* @author jaysunxiao
* @version 3.0
*/
public class RandomSelector<T> {
private int cursor = 0;
private TreeMap<Integer, T> elementMap = new TreeMap<>();
public void addElement(@Nullable T value, int weight) {
if (value == null || weight <= 0) {
return;
}
cursor += weight;
elementMap.put(cursor, value);
}
public void clear() {
elementMap.clear();
cursor = 0;
}
public int size() {
return elementMap.size();
}
public T select() {
if (cursor <= 0) {
throw new IllegalStateException("全部的权重是0");
}
if (elementMap.isEmpty()) {
throw new IllegalStateException("选择的元素为空");
}
var randomInt = RandomUtils.randomInt(cursor) + 1;
return elementMap.ceilingEntry(randomInt).getValue();
}
public List<T> select(int count) {
List<T> resultList = new ArrayList<>();
for (int i = 0; i < count; i++) {
resultList.add(select());
}
return resultList;
}
}
@@ -0,0 +1,421 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.math;
import com.zfoo.protocol.util.StringUtils;
import java.awt.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.List;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
/**
* @author jaysunxiao
* @version 3.0
*/
public abstract class RandomUtils {
/**
* 用于随机选的字符和数字
*/
public static final String BASE_CHAR_NUMBER = StringUtils.ENGLISH_CHAR + StringUtils.ARAB_NUMBER;
/**
* 使用ThreadLocalRandom产生随机数,能够解决多个线程发生的竞争争夺。
*/
public static ThreadLocalRandom getRandom() {
return ThreadLocalRandom.current();
}
/**
* 获取{@link SecureRandom},类提供加密的强随机数生成器 (RNG)
*/
public static SecureRandom getSecureRandom() {
try {
return SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
/**
* 获取随机数产生器
*
* @param isSecure 是否为强随机数生成器 (RNG)
* @return {@link Random}
*/
public static Random getRandom(boolean isSecure) {
return isSecure ? getSecureRandom() : getRandom();
}
/**
* 获得随机Boolean值
*
* @return true or false
*/
public static boolean randomBoolean() {
return 0 == randomInt(2);
}
/**
* 获得指定范围内的随机数
*
* @param min 最小数(包含)
* @param max 最大数(不包含)
* @return 随机数
*/
public static int randomInt(int min, int max) {
return getRandom().nextInt(min, max);
}
/**
* 获得随机数[-2^32, 2^32)
*
* @return 随机数
*/
public static int randomInt() {
return getRandom().nextInt();
}
/**
* 获得指定范围内的随机数 [0,limit)
*
* @param limit 限制随机数的范围,不包括这个数
* @return 随机数
*/
public static int randomInt(int limit) {
return getRandom().nextInt(limit);
}
/**
* 获得指定范围内的随机数[min, max)
*
* @param min 最小数(包含)
* @param max 最大数(不包含)
* @return 随机数
*/
public static long randomLong(long min, long max) {
return getRandom().nextLong(min, max);
}
/**
* 获得随机数
*
* @return 随机数
*/
public static long randomLong() {
return getRandom().nextLong();
}
/**
* 获得指定范围内的随机数 [0,limit)
*
* @param limit 限制随机数的范围,不包括这个数
* @return 随机数
*/
public static long randomLong(long limit) {
return getRandom().nextLong(limit);
}
/**
* 获得指定范围内的随机数
*
* @param min 最小数(包含)
* @param max 最大数(不包含)
* @return 随机数
*/
public static double randomDouble(double min, double max) {
return getRandom().nextDouble(min, max);
}
/**
* 获得指定范围内的随机数
*
* @param min 最小数(包含)
* @param max 最大数(不包含)
* @param scale 保留小数位数
* @param roundingMode 保留小数的模式 {@link RoundingMode}
* @return 随机数
*/
public static double randomDouble(double min, double max, int scale, RoundingMode roundingMode) {
return NumberUtils.round(randomDouble(min, max), scale, roundingMode).doubleValue();
}
/**
* 获得随机数[0, 1)
*
* @return 随机数
*/
public static double randomDouble() {
return getRandom().nextDouble();
}
/**
* 获得指定范围内的随机数
*
* @param scale 保留小数位数
* @param roundingMode 保留小数的模式 {@link RoundingMode}
* @return 随机数
*/
public static double randomDouble(int scale, RoundingMode roundingMode) {
return NumberUtils.round(randomDouble(), scale, roundingMode).doubleValue();
}
/**
* 获得指定范围内的随机数 [0,limit)
*
* @param limit 限制随机数的范围,不包括这个数
* @return 随机数
*/
public static double randomDouble(double limit) {
return getRandom().nextDouble(limit);
}
/**
* 获得指定范围内的随机数
*
* @param limit 限制随机数的范围,不包括这个数
* @param scale 保留小数位数
* @param roundingMode 保留小数的模式 {@link RoundingMode}
* @return 随机数
*/
public static double randomDouble(double limit, int scale, RoundingMode roundingMode) {
return NumberUtils.round(randomDouble(limit), scale, roundingMode).doubleValue();
}
/**
* 获得指定范围内的随机数[0, 1)
*
* @return 随机数
*/
public static BigDecimal randomBigDecimal() {
return NumberUtils.toBigDecimal(getRandom().nextDouble());
}
/**
* 获得指定范围内的随机数 [0,limit)
*
* @param limit 最大数(不包含)
* @return 随机数
*/
public static BigDecimal randomBigDecimal(BigDecimal limit) {
return NumberUtils.toBigDecimal(getRandom().nextDouble(limit.doubleValue()));
}
/**
* 获得指定范围内的随机数
*
* @param min 最小数(包含)
* @param max 最大数(不包含)
* @return 随机数
*/
public static BigDecimal randomBigDecimal(BigDecimal min, BigDecimal max) {
return NumberUtils.toBigDecimal(getRandom().nextDouble(min.doubleValue(), max.doubleValue()));
}
/**
* 随机bytes
*
* @param length 长度
* @return bytes
*/
public static byte[] randomBytes(int length) {
byte[] bytes = new byte[length];
getRandom().nextBytes(bytes);
return bytes;
}
/**
* 随机获得列表中的元素
*
* @param <T> 元素类型
* @param list 列表
* @return 随机元素
*/
public static <T> T randomEle(List<T> list) {
return randomEle(list, list.size());
}
/**
* 随机获得列表中的元素
*
* @param <T> 元素类型
* @param list 列表
* @param limit 限制列表的前N项
* @return 随机元素
*/
public static <T> T randomEle(List<T> list, int limit) {
return list.get(randomInt(limit));
}
/**
* 随机获得数组中的元素
*
* @param <T> 元素类型
* @param array 列表
* @return 随机元素
*/
public static <T> T randomEle(T[] array) {
return randomEle(array, array.length);
}
/**
* 随机获得数组中的元素
*
* @param <T> 元素类型
* @param array 列表
* @param limit 限制列表的前N项
* @return 随机元素
*/
public static <T> T randomEle(T[] array, int limit) {
return array[randomInt(limit)];
}
/**
* 随机获得列表中的一定量元素
*
* @param <T> 元素类型
* @param list 列表
* @param count 随机取出的个数
* @return 随机元素
*/
public static <T> List<T> randomEles(List<T> list, int count) {
final List<T> result = new ArrayList<T>(count);
int limit = list.size();
while (result.size() < count) {
result.add(randomEle(list, limit));
}
return result;
}
/**
* 随机获得列表中的一定量的不重复元素,返回Set
*
* @param <T> 元素类型
* @param collection 列表
* @param count 随机取出的个数
* @return 随机元素
* @throws IllegalArgumentException 需要的长度大于给定集合非重复总数
*/
public static <T> Set<T> randomEleSet(Collection<T> collection, int count) {
ArrayList<T> source = new ArrayList<>(new HashSet<>(collection));
if (count > source.size()) {
throw new IllegalArgumentException("Count is larger than collection distinct size !");
}
final HashSet<T> result = new HashSet<T>(count);
int limit = collection.size();
while (result.size() < count) {
result.add(randomEle(source, limit));
}
return result;
}
/**
* 获得一个随机的字符串(只包含数字和字符)
*
* @param length 字符串的长度
* @return 随机字符串
*/
public static String randomString(int length) {
return randomString(BASE_CHAR_NUMBER, length);
}
/**
* 获得一个随机的字符串(只包含数字和大写字符)
*
* @param length 字符串的长度
* @return 随机字符串
*/
public static String randomStringUpper(int length) {
return randomString(BASE_CHAR_NUMBER, length).toUpperCase();
}
/**
* 获得一个只包含数字的字符串
*
* @param length 字符串的长度
* @return 随机字符串
*/
public static String randomNumbers(int length) {
return randomString(StringUtils.ARAB_NUMBER, length);
}
/**
* 获得一个随机的字符串
*
* @param baseString 随机字符选取的样本
* @param length 字符串的长度
* @return 随机字符串
*/
public static String randomString(String baseString, int length) {
final StringBuilder sb = new StringBuilder();
if (length < 1) {
length = 1;
}
int baseLength = baseString.length();
for (int i = 0; i < length; i++) {
int number = getRandom().nextInt(baseLength);
sb.append(baseString.charAt(number));
}
return sb.toString();
}
/**
* 随机数字,数字为0~9单个数字
*
* @return 随机数字字符
*/
public static int randomNumber() {
return randomChar(StringUtils.ARAB_NUMBER);
}
/**
* 随机字母或数字,小写
*
* @return 随机字符
*/
public static char randomChar() {
return randomChar(BASE_CHAR_NUMBER);
}
/**
* 随机字符
*
* @param baseString 随机字符选取的样本
* @return 随机字符
*/
public static char randomChar(String baseString) {
return baseString.charAt(getRandom().nextInt(baseString.length()));
}
/**
* 生成随机颜色
*
* @return 随机颜色
*/
public static Color randomColor() {
final Random random = getRandom();
return new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255));
}
}
@@ -0,0 +1,220 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.math.dfa;
import com.zfoo.protocol.collection.CollectionUtils;
import com.zfoo.protocol.util.StringUtils;
import java.util.*;
/**
* DFA(Deterministic Finite Automaton 确定有穷自动机)
* DFA单词树(以下简称单词树),常用于在某大段文字中快速查找某几个关键词是否存在。<br>
* 单词树使用group区分不同的关键字集合,不同的分组可以共享树枝,避免重复建树。<br>
* 单词树使用树状结构表示一组单词。<br>
* 例如:红领巾,红河构建树后为:<br>
* <br> 红
* <br> / \
* <br> 领 河
* <br> /
* <br> 巾
* 其中每个节点都是一个WordTree对象,查找时从上向下查找。<br>
*/
public class WordTree extends HashMap<Character, WordTree> {
/**
* 敏感词字符末尾标识,用于标识单词末尾字符
*/
private Set<Character> endCharacterSet = new HashSet<>();
/**
* 默认构造
*/
public WordTree() {
}
/**
* 增加一组单词
*
* @param words 单词集合
*/
public void addWords(Collection<String> words) {
var wordSet = new HashSet<>(words);
for (var word : wordSet) {
addWord(word);
}
}
/**
* 添加单词,使用默认类型
*
* @param word 单词
*/
public void addWord(String word) {
WordTree parent = null;
WordTree current = this;
WordTree child;
char currentChar = 0;
int length = word.length();
for (var i = 0; i < length; i++) {
currentChar = word.charAt(i);
// 只处理合法字符
if (!StringUtils.isStopChar(currentChar)) {
child = current.get(currentChar);
if (child == null) {
// 无子类,新建一个子节点后存放下一个字符
child = new WordTree();
current.put(currentChar, child);
}
parent = current;
current = child;
}
}
if (null != parent && parent != this) {
parent.setEnd(currentChar);
}
}
//------------------------------------------------------------------------------- match
/**
* 指定文本是否包含树中的词
*
* @param text 被检查的文本
* @return 是否包含
*/
public boolean isMatch(String text) {
if (StringUtils.isBlank(text)) {
return false;
}
var matchAll = matchAll(text, 1);
return CollectionUtils.isNotEmpty(matchAll);
}
//------------------------------------------------------------------------------- match all
/**
* 找出所有匹配的关键字
*
* @param text 被检查的文本
* @return 匹配的词列表
*/
public List<String> matchAll(String text) {
return matchAll(text, -1);
}
/**
* 找出所有匹配的关键字
*
* @param text 被检查的文本
* @param limit 限制匹配个数
* @return 匹配的词列表
*/
public List<String> matchAll(String text, int limit) {
return matchAll(text, limit, false, false);
}
/**
* 找出所有匹配的关键字<br>
* 密集匹配原则:假如关键词有 ab,b,文本是abab,将匹配 [ab,b,ab]<br>
* 贪婪匹配(最长匹配)原则:假如关键字a,ab,最长匹配将匹配[a, ab]
*
* @param text 被检查的文本
* @param limit 限制匹配个数
* @param isDensityMatch 是否使用密集匹配原则
* @param isGreedMatch 是否使用贪婪匹配(最长匹配)原则
* @return 匹配的词列表
*/
public List<String> matchAll(String text, int limit, boolean isDensityMatch, boolean isGreedMatch) {
if (StringUtils.isBlank(text)) {
return Collections.emptyList();
}
var findWords = new ArrayList<String>();
WordTree current = this;
int length = text.length();
// 存放查找到的字符缓存。完整出现一个词时加到findedWords中,否则清空
StringBuilder wordBuffer;
char currentChar;
for (int i = 0; i < length; i++) {
wordBuffer = new StringBuilder();
for (int j = i; j < length; j++) {
currentChar = text.charAt(j);
if (StringUtils.isStopChar(currentChar)) {
if (wordBuffer.length() > 0) {
// 做为关键词中间的停顿词被当作关键词的一部分被返回
wordBuffer.append(currentChar);
} else {
// 停顿词做为关键词的第一个字符时需要跳过
i++;
}
continue;
} else if (!current.containsKey(currentChar)) {
// 非关键字符被整体略过,重新以下个字符开始检查
break;
}
wordBuffer.append(currentChar);
if (current.isEnd(currentChar)) {
// 到达单词末尾,关键词成立,从此词的下一个位置开始查找
findWords.add(wordBuffer.toString());
if (limit > 0 && findWords.size() >= limit) {
// 超过匹配限制个数,直接返回
return findWords;
}
if (!isDensityMatch) {
// 如果非密度匹配,跳过匹配到的词
i = j;
}
if (!isGreedMatch) {
// 如果懒惰匹配(非贪婪匹配)。当遇到第一个结尾标记就结束本轮匹配
break;
}
}
current = current.get(currentChar);
if (null == current) {
break;
}
}
current = this;
}
return findWords;
}
/**
* 是否末尾
*
* @param c 检查的字符
* @return 是否末尾
*/
private boolean isEnd(Character c) {
return this.endCharacterSet.contains(c);
}
/**
* 设置是否到达末尾
*
* @param c 设置结尾的字符
*/
private void setEnd(Character c) {
if (null != c) {
this.endCharacterSet.add(c);
}
}
}
@@ -0,0 +1,173 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.math.lexer;
import com.zfoo.protocol.util.StringUtils;
import java.util.Set;
/**
* 词法分析器
*
* @author jaysunxiao
* @version 3.0
*/
public class LexicalAnalysis {
private static final Set<String> KEY_WORDS = Set.of("break", "include", "begin", "end", "if", "else", "while", "switch");
//判断是否是关键字
private boolean isKey(String str) {
return KEY_WORDS.contains(str);
}
//判断是否是字母
boolean isLetter(char letter) {
return StringUtils.isEnglishChar(letter);
}
//判断是否是数字
boolean isDigit(char digit) {
return Character.isDigit(digit);
}
//词法分析
void analyze(char[] chars) {
for (var i = 0; i < chars.length; i++) {
var ch = chars[i];
if (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r') {
continue;
}
var arr = "";
if (isLetter(ch)) {
while (isLetter(ch) || isDigit(ch)) {
arr += ch;
ch = chars[++i];
}
//回退一个字符
i--;
if (isKey(arr)) {
//关键字
System.out.println(arr + "\t4" + "\t关键字");
} else {
//标识符
System.out.println(arr + "\t4" + "\t标识符");
}
} else if (isDigit(ch) || (ch == '.')) {
while (isDigit(ch) || (ch == '.' && isDigit(chars[++i]))) {
if (ch == '.') {
i--;
}
arr = arr + ch;
ch = chars[++i];
}
//属于无符号常数
System.out.println(arr + "\t5" + "\t常数");
} else {
switch (ch) {
//运算符
case '+':
System.out.println(ch + "\t2" + "\t运算符");
break;
case '-':
System.out.println(ch + "\t2" + "\t运算符");
break;
case '*':
System.out.println(ch + "\t2" + "\t运算符");
break;
case '/':
System.out.println(ch + "\t2" + "\t运算符");
break;
//分界符
case '(':
System.out.println(ch + "\t3" + "\t分界符");
break;
case ')':
System.out.println(ch + "\t3" + "\t分界符");
break;
case '[':
System.out.println(ch + "\t3" + "\t分界符");
break;
case ']':
System.out.println(ch + "\t3" + "\t分界符");
break;
case ';':
System.out.println(ch + "\t3" + "\t分界符");
break;
case '{':
System.out.println(ch + "\t3" + "\t分界符");
break;
case '}':
System.out.println(ch + "\t3" + "\t分界符");
break;
// 字符串
case '"':
do {
arr += ch;
ch = chars[++i];
} while (ch != '"');
arr += ch;
System.out.println(arr + "\t3" + "\t字符串");
break;
//运算符
case '=': {
ch = chars[++i];
if (ch == '=') {
System.out.println("==" + "\t2" + "\t运算符");
} else {
System.out.println("=" + "\t2" + "\t运算符");
i--;
}
}
break;
case ':': {
ch = chars[++i];
if (ch == '=') {
System.out.println(":=" + "\t2" + "\t运算符");
} else {
System.out.println(":" + "\t2" + "\t运算符");
i--;
}
}
break;
case '>': {
ch = chars[++i];
if (ch == '=') {
System.out.println(">=" + "\t2" + "\t运算符");
} else {
System.out.println(">" + "\t2" + "\t运算符");
i--;
}
}
break;
case '<': {
ch = chars[++i];
if (ch == '=') {
System.out.println("<=" + "\t2" + "\t运算符");
} else {
System.out.println("<" + "\t2" + "\t运算符");
i--;
}
}
break;
//无识别
default:
System.out.println(ch + "\t6" + "\t无识别符");
}
}
}
}
}
@@ -0,0 +1,120 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.net;
import com.zfoo.protocol.collection.CollectionUtils;
import com.zfoo.protocol.util.StringUtils;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author jaysunxiao
* @version 3.0
*/
public class HostAndPort {
private String host;
private int port;
public static HostAndPort valueOf(String host, int port) {
HostAndPort hostAndPort = new HostAndPort();
hostAndPort.host = host;
hostAndPort.port = port;
return hostAndPort;
}
/**
* @param hostAndPort example -> localhost:port
*/
public static HostAndPort valueOf(String hostAndPort) {
var split = hostAndPort.trim().split(StringUtils.COLON_REGEX);
return valueOf(split[0].trim(), Integer.parseInt(split[1].trim()));
}
/**
* @param hostAndPorts example -> localhost:port,localhost:port,localhost:port
*/
public static List<HostAndPort> toHostAndPortList(String hostAndPorts) {
if (StringUtils.isEmpty(hostAndPorts)) {
return Collections.emptyList();
}
var hostAndPortSplits = hostAndPorts.split(StringUtils.COMMA_REGEX);
var hostAndPortList = new ArrayList<HostAndPort>();
for (var hostAndPort : hostAndPortSplits) {
hostAndPortList.add(valueOf(hostAndPort));
}
return hostAndPortList;
}
public static List<HostAndPort> toHostAndPortList(Collection<String> list) {
if (CollectionUtils.isEmpty(list)) {
return Collections.emptyList();
}
var hostAndPortList = new ArrayList<HostAndPort>();
list.forEach(it -> hostAndPortList.addAll(toHostAndPortList(it)));
return hostAndPortList;
}
public static String toHostAndPortListStr(Collection<HostAndPort> list) {
var urlList = list.stream()
.map(it -> it.toHostAndPortStr())
.collect(Collectors.toList());
return StringUtils.joinWith(StringUtils.COMMA, urlList.toArray());
}
public String toHostAndPortStr() {
return StringUtils.format("{}:{}", this.host.trim(), this.port);
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
HostAndPort that = (HostAndPort) o;
return port == that.port && Objects.equals(host, that.host);
}
@Override
public int hashCode() {
return Objects.hash(host, port);
}
@Override
public String toString() {
return StringUtils.format("[{}]", toHostAndPortStr());
}
}
@@ -0,0 +1,440 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.net;
import com.zfoo.protocol.util.AssertionUtils;
import com.zfoo.protocol.util.IOUtils;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.util.EnumUtils;
import org.springframework.lang.Nullable;
import java.io.IOException;
import java.io.OutputStream;
import java.net.*;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* 网络相关工具
*
* @author jaysunxiao
* @version 3.0
*/
public class NetUtils {
public final static String LOCAL_LOOPBACK_IP = "127.0.0.1";
/**
* 默认最小端口,1024
*/
public static final int PORT_RANGE_MIN = 1024;
/**
* 默认最大端口,65535
*/
public static final int PORT_RANGE_MAX = 0xFFFF;
/**
* IP v4
*/
public final static Pattern IPV4 = Pattern.compile("\\b((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\b");
/**
* IP v6
*/
public final static Pattern IPV6 = Pattern.compile("(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))");
/**
* 根据long值获取ip v4地址
*
* @param longIP IP的long表示形式
* @return IP V4 地址
*/
public static String longToIpv4(long longIP) {
final StringBuilder sb = new StringBuilder();
// 直接右移24位
sb.append((longIP / 1_000_000_000));
sb.append(".");
// 将高8位置0,然后右移16位
sb.append(longIP / 1_000_000 % 1_000);
sb.append(".");
sb.append(longIP / 1_000 % 1_000);
sb.append(".");
sb.append(longIP % 1_000);
return sb.toString();
}
/**
* 根据ip地址计算出long型的数据
*
* @param strIP IP V4 地址
* @return long值
*/
public static long ipv4ToLong(String strIP) {
if (isValidAddress(strIP)) {
long[] ip = new long[4];
// 先找到IP地址字符串中.的位置
int position1 = strIP.indexOf(".");
int position2 = strIP.indexOf(".", position1 + 1);
int position3 = strIP.indexOf(".", position2 + 1);
// 将每个.之间的字符串转换成整型
ip[0] = Long.parseLong(strIP.substring(0, position1));
ip[1] = Long.parseLong(strIP.substring(position1 + 1, position2));
ip[2] = Long.parseLong(strIP.substring(position2 + 1, position3));
ip[3] = Long.parseLong(strIP.substring(position3 + 1));
return (ip[0] * 1_000_000_000) + (ip[1] * 1_000_000) + (ip[2] * 1_000) + ip[3];
}
return 0;
}
public static boolean isValidAddress(String address) {
return IPV4.matcher(address).matches() || IPV6.matcher(address).matches();
}
/**
* 检测本地端口可用性<br>
* 来自org.springframework.util.SocketUtils
*
* @param port 被检测的端口
* @return 是否可用
*/
public static boolean isAvailablePort(int port) {
if (!isValidPort(port)) {
// 给定的IP未在指定端口范围中
return false;
}
try (ServerSocket ss = new ServerSocket(port)) {
return true;
} catch (IOException e) {
return false;
}
}
/**
* 是否为有效的端口<br>
* 此方法并不检查端口是否被占用
*
* @param port 端口号
* @return 是否有效
*/
public static boolean isValidPort(int port) {
// 有效端口是0~65535
return port >= 0 && port <= PORT_RANGE_MAX;
}
/**
* 查找1024~65535范围内的可用端口<br>
* 此方法只检测给定范围内的随机一个端口,检测65535-1024次<br>
*
* @return 可用的端口
*/
public static int getAvailablePort() {
return getAvailablePort(PORT_RANGE_MIN);
}
/**
* 查找指定范围内的可用端口,最大值为65535<br>
* 此方法只检测给定范围内的随机一个端口,检测65535-minPort次<br>
*
* @param minPort 端口最小值(包含)
* @return 可用的端口
*/
public static int getAvailablePort(int minPort) {
return getAvailablePort(minPort, PORT_RANGE_MAX);
}
/**
* 查找指定范围内的可用端口<br>
* 此方法只检测给定范围内的随机一个端口,检测maxPort-minPort次<br>
*
* @param minPort 端口最小值(包含)
* @param maxPort 端口最大值(包含)
* @return 可用的端口
*/
public static int getAvailablePort(int minPort, int maxPort) {
for (int i = minPort; i <= maxPort; i++) {
if (isAvailablePort(i)) {
return i;
}
}
throw new IllegalArgumentException(StringUtils
.format("Could not find an available port in the range [{}, {}] after {} attempts", minPort, maxPort, maxPort - minPort));
}
/**
* 获取多个本地可用端口<br>
*
* @param minPort 端口最小值(包含)
* @param maxPort 端口最大值(包含)
* @return 可用的端口
*/
public static TreeSet<Integer> getAvailablePorts(int numRequested, int minPort, int maxPort) {
final TreeSet<Integer> availablePorts = new TreeSet<>();
int attemptCount = 0;
while ((++attemptCount <= numRequested + 100) && availablePorts.size() < numRequested) {
availablePorts.add(getAvailablePort(minPort, maxPort));
}
if (availablePorts.size() != numRequested) {
throw new IllegalArgumentException(StringUtils
.format("Could not find {} available ports in the range [{}, {}]", numRequested, minPort, maxPort));
}
return availablePorts;
}
/**
* 判定是否为内网IP<br>
* 私有IP:A类 10.0.0.0-10.255.255.255 B类 172.16.0.0-172.31.255.255 C类 192.168.0.0-192.168.255.255 当然,还有127这个网段是环回地址
*
* @param ipAddress IP地址
* @return 是否为内网IP
*/
public static boolean isInnerIP(String ipAddress) {
long ipNum = NetUtils.ipv4ToLong(ipAddress);
long aBegin = NetUtils.ipv4ToLong("10.0.0.0");
long aEnd = NetUtils.ipv4ToLong("10.255.255.255");
long bBegin = NetUtils.ipv4ToLong("172.16.0.0");
long bEnd = NetUtils.ipv4ToLong("172.31.255.255");
long cBegin = NetUtils.ipv4ToLong("192.168.0.0");
long cEnd = NetUtils.ipv4ToLong("192.168.255.255");
boolean isInnerIp = isInner(ipNum, aBegin, aEnd) || isInner(ipNum, bBegin, bEnd) || isInner(ipNum, cBegin, cEnd) || ipAddress.equals(LOCAL_LOOPBACK_IP);
return isInnerIp;
}
/**
* 指定IP的long是否在指定范围内
*
* @param userIp 用户IP
* @param begin 开始IP
* @param end 结束IP
* @return 是否在范围内
*/
private static boolean isInner(long userIp, long begin, long end) {
return (userIp >= begin) && (userIp <= end);
}
/**
* 通过域名得到IP
*
* @param hostName HOST
* @return ip address or hostName if UnknownHostException
*/
public static String getIpByHost(String hostName) {
try {
return InetAddress.getByName(hostName).getHostAddress();
} catch (UnknownHostException e) {
return hostName;
}
}
/**
* 获取本机所有网卡
*/
public static Set<NetworkInterface> getAllNetworkInterface() {
try {
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
return EnumUtils.enumerationToSet(networkInterfaces);
} catch (SocketException e) {
return Collections.emptySet();
}
}
/**
* 获取所有满足过滤条件的本地IP地址对象
*
* @return 过滤后的地址对象列表
*/
public static Set<InetAddress> getAllAddress() {
var networkInterfaces = getAllNetworkInterface();
var ipSet = new LinkedHashSet<InetAddress>();
networkInterfaces.forEach(it -> {
final Enumeration<InetAddress> inetAddresses = it.getInetAddresses();
while (inetAddresses.hasMoreElements()) {
final InetAddress inetAddress = inetAddresses.nextElement();
if (Objects.nonNull(inetAddress)) {
ipSet.add(inetAddress);
}
}
});
return ipSet;
}
/**
* 获得本机的IPv4地址列表<br>
* 返回的IP列表有序,按照系统设备顺序
*/
public static Set<String> localIpv4s() {
var localAddressList = getAllAddress().stream()
.filter(it -> it instanceof Inet4Address).collect(Collectors.toSet());
return toIpList(localAddressList);
}
/**
* 获得本机的IPv6地址列表<br>
* 返回的IP列表有序,按照系统设备顺序
*/
public static Set<String> localIpv6s() {
var localAddressList = getAllAddress().stream()
.filter(it -> it instanceof Inet6Address).collect(Collectors.toSet());
return toIpList(localAddressList);
}
/**
* 地址列表转换为IP地址列表
*/
public static Set<String> toIpList(Set<InetAddress> addressList) {
var ipSet = new LinkedHashSet<String>();
for (InetAddress address : addressList) {
ipSet.add(address.getHostAddress());
}
return ipSet;
}
/**
* 获得本机的IP地址列表(包括Ipv4和Ipv6)<br>
*/
public static Set<String> localIps() {
var localAddressList = getAllAddress();
return toIpList(localAddressList);
}
/**
* 获取本机网卡IP地址,这个地址为所有网卡中非回路地址的第一个<br>
* 如果获取失败调用 {@link InetAddress#getLocalHost()}方法获取。<br>
* 此方法不会抛出异常,获取失败将返回<code>null</code><br>
* <p>
* 参考:http://stackoverflow.com/questions/9481865/getting-the-ip-address-of-the-current-machine-using-java
*
* @return 本机网卡IP地址,获取失败返回空字符串
*/
public static String getLocalhostStr() {
InetAddress localhost = getLocalhost();
if (null != localhost) {
return localhost.getHostAddress();
}
return StringUtils.EMPTY;
}
/**
* 获取本机网卡IP地址,规则如下:
*
* <pre>
* 1. 查找所有网卡地址,必须非回路(loopback)地址、非局域网地址(siteLocal)、IPv4地址
* 2. 如果无满足要求的地址,调用 {@link InetAddress#getLocalHost()} 获取地址
* </pre>
*
* @return 本机网卡IP地址,获取失败返回<code>null</code>
*/
@Nullable
public static InetAddress getLocalhost() {
var address = getAllAddress().stream()
.filter(it -> !it.isLoopbackAddress()
// 非地区本地地址,指10.0.0.0 ~ 10.255.255.255、172.16.0.0 ~ 172.31.255.255、192.168.0.0 ~ 192.168.255.255
&& it.isSiteLocalAddress()
// 需为IPV4地址
&& it instanceof Inet4Address
&& !it.getHostAddress().contains(":"))
.findFirst();
if (address.isPresent()) {
return address.get();
}
try {
return InetAddress.getLocalHost();
} catch (UnknownHostException e) {
return null;
}
}
/**
* 获得本机MAC地址
*
* @return 本机MAC地址
*/
public static String getLocalMacAddress() {
return getMacAddress(getLocalhost());
}
/**
* 获得指定地址信息中的MAC地址,使用分隔符“-”
*
* @param inetAddress {@link InetAddress}
* @return MAC地址,用-分隔
*/
public static String getMacAddress(InetAddress inetAddress) {
return getMacAddress(inetAddress, "-");
}
/**
* 获得指定地址信息中的MAC地址
*
* @param inetAddress {@link InetAddress}
* @param separator 分隔符,推荐使用“-”或者“:”
* @return MAC地址,用-分隔
*/
public static String getMacAddress(InetAddress inetAddress, String separator) {
AssertionUtils.notNull(inetAddress);
byte[] mac;
try {
mac = NetworkInterface.getByInetAddress(inetAddress).getHardwareAddress();
} catch (SocketException e) {
throw new IllegalArgumentException(e);
}
if (null != mac) {
final StringBuilder sb = new StringBuilder();
String s;
for (int i = 0; i < mac.length; i++) {
if (i != 0) {
sb.append(separator);
}
// 字节转换为整数
s = Integer.toHexString(mac[i] & 0xFF);
sb.append(s.length() == 1 ? 0 + s : s);
}
return sb.toString();
}
return null;
}
/**
* 使用普通Socket发送数据
*
* @param host Server主机
* @param port Server端口
* @param data 数据
* @throws IOException IO异常
*/
public static void netCat(String host, int port, byte[] data) throws IOException {
OutputStream out = null;
try {
Socket socket = new Socket(host, port);
out = socket.getOutputStream();
out.write(data);
out.flush();
} finally {
IOUtils.closeIO(out);
}
}
}
@@ -0,0 +1,93 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.security;
import com.zfoo.protocol.util.StringUtils;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.util.Base64;
/**
* AES加密和解密
* <p>
* 默认AES/ECB/PKCS5Padding
*
* @author jaysunxiao
* @version 3.0
*/
public abstract class AesUtils {
private static final Key KEY;
private static final String KEY_STR = "=jE[`B],YO24Vt+Akh&}D7@s9l1uLKP)";
/**
* 密钥算法
*/
private static final String ALGORITHM = "AES";
/**
* 加解密算法/工作模式/填充方式
*/
private static final String ALGORITHM_STR = "AES/ECB/PKCS5Padding";
static {
try {
KEY = new SecretKeySpec(KEY_STR.getBytes(StringUtils.DEFAULT_CHARSET_NAME), ALGORITHM);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 对str进行AES加密
*
* @param str 需要加密的字符串
* @return AES加密后的字符串
*/
public static String getEncryptString(String str) {
try {
var base64Encoder = Base64.getEncoder();
var strBytes = str.getBytes(StringUtils.DEFAULT_CHARSET_NAME);
var cipher = Cipher.getInstance(ALGORITHM_STR);
cipher.init(Cipher.ENCRYPT_MODE, KEY);
var encryptStrBytes = cipher.doFinal(strBytes);
return base64Encoder.encodeToString(encryptStrBytes);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 对str进行AES解密
*
* @param str 需要解密的字符串
* @return AES解密后的字符串
*/
public static String getDecryptString(String str) {
try {
var base64Decoder = Base64.getDecoder();
var strBytes = base64Decoder.decode(str);
var cipher = Cipher.getInstance(ALGORITHM_STR);
cipher.init(Cipher.DECRYPT_MODE, KEY);
var decryptStrBytes = cipher.doFinal(strBytes);
return new String(decryptStrBytes, StringUtils.DEFAULT_CHARSET_NAME);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
@@ -0,0 +1,58 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.security;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author jaysunxiao
* @version 3.0
*/
public abstract class IdUtils {
private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(0);
/**
* 本地int的id,如果达到最大值则重新从最小值重新计算
*/
public static int getLocalIntId() {
return ATOMIC_INTEGER.incrementAndGet();
}
/**
* 获得分布式环境下唯一id
*
* @return String UUID
*/
public static String getUUID() {
String uuid = UUID.randomUUID().toString();
//去掉“-”符号
return uuid.replaceAll("-", "");
}
/**
* 小的id在前 - 大的id在后
*
* @param a 第一个数字
* @param b 第二个数字
* @return 生成的id
*/
public static String generateStringId(long a, long b) {
return Math.min(a, b) + "-" + Math.max(a, b);
}
}
@@ -0,0 +1,88 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.security;
import com.zfoo.protocol.util.StringUtils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* MD5的全称是Message-Digest Algorithm 5,在90年代初由MIT的计算机科学实验室和RSA Data Security Inc发明,经MD2、MD3和MD4发展而来。
* <p>
* MD5将任意长度的“字节串”变换成一个128bit的大整数,并且它是一个不可逆的字符串变换算法。
* <p>
* 换句话说就是,即使你看到源程序和算法描述,也无法将一个MD5的值变换回原始的字符串。
* <p>
* 从数学原理上说,是因为原始的字符串有无穷多个,这有点象不存在反函数的数学函数。
* <p>
* MD5的典型应用是对一段Message(字节串)产生fingerprint(指纹),以防止被“篡改”。
* <p>
* 举个例子,你将一段话写在一个叫 readme.txt文件中,并对这个readme.txt产生一个MD5的值并记录在案,然后你可以传播这个文件给别人,
* 别人如果修改了文件中的任何内容,你对这个文件重新计算MD5时就会发现。如果再有一个第三方的认证机构,用MD5还可以防止文件作者的“抵赖”,
* 这就是所谓的数字签名应用。MD5还广泛用于加密和解密技术上,在很多操作系统中,用户的密码是以MD5值(或类似的其它算法)的方式保存的,
* 用户Login的时候,系统是把用户输入的密码计算成MD5值,然后再去和系统中保存的MD5值进行比较,而系统并不“知道”用户的密码是什么。
*
* @author jaysunxiao
* @version 3.0
*/
public abstract class MD5Utils {
private static final String MD5_ALGORITHM = "MD5";
/**
* 16进制字符
*/
private static final char[] HEX_CHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
public static String strToMD5(String str) {
if (StringUtils.isBlank(str)) {
throw new NullPointerException();
}
return bytesToMD5(StringUtils.bytes(str));
}
// MD5将任意长度的“字节串”变换成一个128bit的大整数
public static String bytesToMD5(byte[] bytes) {
try {
var messageDigest = MessageDigest.getInstance(MD5_ALGORITHM);
// inputByteArray是输入字符串转换得到的字节数组
messageDigest.update(bytes);
// 转换并返回结果,也是字节数组,包含16个元素
// 字符数组转换成字符串返回,MD5将任意长度的字节数组变换成一个16个字节,128bit的大整数
return byteArrayToHex(messageDigest.digest());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("MD加密出现未知异常", e);
}
}
//下面这个函数用于将字节数组换成成16进制的字符串
private static String byteArrayToHex(byte[] bytes) {
// new一个字符数组,这个就是用来组成结果字符串的(解释一下:一个byte是八位二进制,也就是2位十六进制字符(2的8次方等于16的2次方))
var resultCharArray = new char[bytes.length * 2];
// 遍历字节数组,通过位运算(位运算效率高),转换成字符放到字符数组中去
var index = 0;
for (var b : bytes) {
resultCharArray[index++] = HEX_CHARS[b >>> 4 & 0xf];
resultCharArray[index++] = HEX_CHARS[b & 0xf];
}
// 字符数组组合成字符串返回
return new String(resultCharArray);
}
}
@@ -0,0 +1,72 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.security;
import com.zfoo.protocol.util.IOUtils;
import java.io.ByteArrayOutputStream;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
/**
* @author jaysunxiao
* @version 3.0
*/
public abstract class ZipUtils {
private static final int BUFFER_SIZE = IOUtils.ONE_BYTE;
// compression level (0-9),只能是0-9
private static final int COMPRESS_LEVEL = 5;
public static byte[] zip(byte[] bytes) {
// deflate [dɪ'fleɪt] v.抽出空气; 缩小
Deflater deflater = new Deflater(COMPRESS_LEVEL);
deflater.setInput(bytes);
deflater.finish();
ByteArrayOutputStream baos = new ByteArrayOutputStream(BUFFER_SIZE);
byte[] buffer = new byte[BUFFER_SIZE];
while (!deflater.finished()) {
int count = deflater.deflate(buffer);
baos.write(buffer, 0, count);
}
deflater.end();
IOUtils.closeIO(baos);
return baos.toByteArray();
}
public static byte[] unZip(byte[] bytes) {
Inflater inflater = new Inflater();
inflater.setInput(bytes);
ByteArrayOutputStream baos = new ByteArrayOutputStream(BUFFER_SIZE);
try {
byte[] buffer = new byte[BUFFER_SIZE];
while (!inflater.finished()) {
int count = inflater.inflate(buffer);
baos.write(buffer, 0, count);
}
} catch (DataFormatException e) {
throw new RuntimeException(e);
} finally {
IOUtils.closeIO(baos);
}
return baos.toByteArray();
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+33
View File
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
import com.zfoo.protocol.util.StringUtils;
import org.junit.Ignore;
import org.junit.Test;
/**
* @author jaysunxiao
* @version 3.0
*/
@Ignore
public class MainTest {
@Test
public void testTemplate() {
System.out.println(StringUtils.MULTIPLE_HYPHENS);
System.out.println("XX测试:");
System.out.println("**********");
System.out.println(StringUtils.MULTIPLE_HYPHENS);
}
}
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util;
import org.junit.Test;
/**
* @author jaysunxiao
* @version 3.0
*/
public class AssertionUtilsTest {
@Test
public void classLocation() {
}
}
@@ -0,0 +1,62 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util;
import com.zfoo.protocol.util.ClassUtils;
import com.zfoo.protocol.util.IOUtils;
import com.zfoo.protocol.util.StringUtils;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import java.io.IOException;
import java.util.Set;
/**
* @author jaysunxiao
* @version 3.0
*/
@Ignore
public class ClassUtilTest {
// ClassUtilTest
@Test
public void classLocation() {
String str = ClassUtils.classLocation(Integer.class);
Assert.assertEquals("jrt:/java.base/java/lang/Integer.class", str);
}
@Test
public void getAllClasses() throws Exception {
System.out.println(StringUtils.MULTIPLE_HYPHENS);
System.out.println("某个包下的所有类查找测试:");
Set<Class<?>> set = ClassUtils.getAllClasses("com.zfoo");
for (Class<?> clazz : set) {
System.out.println(clazz.getName());
}
System.out.println(StringUtils.MULTIPLE_HYPHENS);
}
@Test
public void getClassPath() {
System.out.println(ClassUtils.getClassAbsPath(User.class));
}
@Test
public void getClassFromClassPath() throws IOException {
System.out.println(new String(IOUtils.toByteArray(ClassUtils.getFileFromClassPath("com.zfoo.util.ClassUtilsTest"))));
}
}
@@ -0,0 +1,103 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import org.junit.Assert;
import org.junit.Test;
import java.util.List;
public class DomUtilsTest {
private static final String XML_WITH_HEAD = "" +
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n" +
"\n" +
"<protocols version=\"1.0\" author=\"jaysunxiao\">\n" +
"\n" +
" <protocol id=\"0\" location=\"com.zfoo.test.CM_Int\"/>\n" +
" <protocol id=\"1\" location=\"com.zfoo.test.SM_Int\"/>\n" +
" <protocol id=\"2\" location=\"com.zfoo.test.CM_Float\"/>\n" +
"\n" +
"</protocols>";
private static final String XML_OF_STANDARD_TEXT =
"<protocols version=\"1.0\" author=\"jaysunxiao\">\n" +
"\n" +
" <protocol id=\"0\" location=\"com.zfoo.test.CM_Int\"/>\n" +
" <protocol id=\"1\" location=\"com.zfoo.test.SM_Int\"/>\n" +
" <protocol id=\"2\" location=\"com.zfoo.test.CM_Float\"/>\n" +
"\n" +
"</protocols>";
@Test
public void testXmlWithHead() {
Protos protos = DomUtils.string2Object(XML_WITH_HEAD, Protos.class);
Assert.assertEquals("jaysunxiao", protos.getAuthor());
Assert.assertEquals("1.0", protos.getVersion());
}
@Test
public void testXmlOfStandardText() {
Protos protos = DomUtils.string2Object(XML_OF_STANDARD_TEXT, Protos.class);
Assert.assertEquals("jaysunxiao", protos.getAuthor());
Assert.assertEquals("1.0", protos.getVersion());
}
}
@JsonPropertyOrder({"version", "author", "list"})
@JacksonXmlRootElement(localName = "protocols")
class Protos {
@JacksonXmlProperty(isAttribute = true, localName = "version")
private String version;
@JacksonXmlProperty(isAttribute = true, localName = "author")
private String author;
@JacksonXmlProperty(localName = "protocol")
@JacksonXmlElementWrapper(useWrapping = false)
private List<Proto> protos;
public String getVersion() {
return version;
}
public String getAuthor() {
return author;
}
public List<Proto> getProtos() {
return protos;
}
}
@JsonPropertyOrder({"id", "location"})
class Proto {
@JacksonXmlProperty(isAttribute = true, localName = "id")
private int id;
@JacksonXmlProperty(isAttribute = true, localName = "location")
private String location;
public int getId() {
return id;
}
public String getLocation() {
return location;
}
}
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util;
import org.junit.Assert;
import org.junit.Test;
/**
* @author jaysunxiao
* @version 3.0
*/
enum Num {
ONE,
TWO,
THREE;
}
public class EnumUtilsTest {
@Test
public void isInEnumsTest() {
Assert.assertTrue(EnumUtils.isInEnums("ONE", new Num[]{Num.ONE, Num.TWO, Num.THREE}));
}
}
@@ -0,0 +1,84 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util;
import com.zfoo.protocol.util.FileUtils;
import org.junit.Ignore;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.util.List;
/**
* @author jaysunxiao
* @version 3.0
*/
@Ignore
public class FileUtilTest {
@Test
public void absPathTest() {
var absPath = FileUtils.getProAbsPath();
System.out.println(absPath);
}
@Test
public void createFile() throws IOException {
FileUtils.createFile(FileUtils.getProAbsPath() + File.separator + "hello", "hhh");
}
@Test
public void deleteFile() {
FileUtils.deleteFile(new File(FileUtils.getProAbsPath() + File.separator + "hello"));
}
@Test
public void writeFile() {
FileUtils.writeStringToFile(new File(FileUtils.getProAbsPath() + File.separator + "test.txt"), "hello world!");
}
@Test
public void readFile() {
String str = FileUtils.readFileToString(new File(FileUtils.getProAbsPath() + File.separator + "test.txt"));
System.out.println(str);
}
@Test
public void getProjectPath() {
System.out.println(FileUtils.getProAbsPath());
}
@Test
public void searchFile() {
FileUtils.searchFileInProject(new File(FileUtils.getProAbsPath()));
}
@Test
public void getAllFiles() {
List<File> list = FileUtils.getAllReadableFiles(new File(FileUtils.getProAbsPath()));
for (File file : list) {
System.out.println(file.getName());
}
}
@Test
public void searchFileInProject() {
System.out.println(FileUtils.searchFileInProject("User"));
}
}
@@ -0,0 +1,190 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util;
import com.zfoo.protocol.model.Triple;
import com.zfoo.protocol.util.JsonUtils;
import org.junit.Assert;
import org.junit.Test;
import java.util.*;
/**
* @author jaysunxiao
* @version 3.0
*/
public class JsonUtilTest {
public static String id = "\"id\":\"1000\"";
public static String name = "\"name\":\"jaysunxiao\"";
public static String sex = "\"sex\":\"man\"";
public static String age = "\"age\":22";
public static String list = "\"list\":[1,2,3]";
public static String map = "\"map\":{\"1\":1,\"2\":2,\"3\":3}";
public static String userJson = "{" + id + "," + name + "," + sex + ","
+ age + "," + list + "," + map + "}";
@Test
public void string2Object() {
User user = JsonUtils.string2Object(userJson, User.class);
Assert.assertEquals(user.getId(), "1000");
Assert.assertEquals(user.getName(), "jaysunxiao");
Assert.assertEquals(user.getSex(), "man");
Assert.assertEquals(user.getList().size(), 3);
Assert.assertEquals(user.getMap().size(), 3);
}
@Test
public void object2String() {
User user = new User();
user.setId("1000");
user.setName("jaysunxiao");
user.setSex("man");
user.setAge(22);
//数组,链表,list
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
user.setList(list);
//map
Map<Integer, Integer> map = new HashMap<>();
map.put(1, 1);
map.put(2, 2);
map.put(3, 3);
user.setMap(map);
Assert.assertEquals(JsonUtils.object2String(user), userJson);
}
@Test
public void string2List() {
String str = "[1,2,3]";
List<Integer> list = JsonUtils.string2List(str, Integer.class);
Assert.assertEquals(list.size(), 3);
}
@Test
public void string2Set() {
String str = "[1,2,3]";
Set<Integer> set = JsonUtils.string2Set(str, Integer.class);
Assert.assertEquals(set.size(), 3);
}
@Test
public void string2Map() {
String str = "{\"1\":1,\"2\":2,\"3\":3}";
Map<Integer, Integer> map = JsonUtils.string2Map(str, Integer.class, Integer.class);
Assert.assertEquals(map.size(), 3);
}
@Test
public void string2Array() {
String str = "[1,2,3]";
Integer[] list = JsonUtils.string2Array(str, Integer.class);
Assert.assertEquals(list.length, 3);
}
@Test
public void getNodeTest() {
Assert.assertEquals(JsonUtils.getNode(userJson, "id").asText(), "1000");
}
@Test
public void tripleTest() {
var triple = new Triple<String, String, String>("a", "b", "c");
var tripleStr = JsonUtils.object2String(triple);
var temp = JsonUtils.string2Object(tripleStr, Triple.class);
}
}
//@JsonIgnoreProperties({"name", "age"})//可以将它看做是@JsonIgnore的批量操作
class User {
private String id;
//@JsonIgnore//作用在字段或方法上,用来完全忽略被注解的字段和方法对应的属性
//@JsonProperty//注意这里必须得有该注解,因为没有提供对应的getId和setId函数,而是其他的getter和setter,防止遗漏该属性
private String name;
private String sex;
private int age;
private List<Integer> list;
private Map<Integer, Integer> map;
@Override
public String toString() {
return "User{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
", list=" + list +
", map=" + map +
'}';
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public List<Integer> getList() {
return list;
}
public void setList(List<Integer> list) {
this.list = list;
}
public Map<Integer, Integer> getMap() {
return map;
}
public void setMap(Map<Integer, Integer> map) {
this.map = map;
}
}
@@ -0,0 +1,82 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util;
import com.zfoo.util.net.NetUtils;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import java.io.IOException;
/**
* @author jaysunxiao
* @version 3.0
*/
public class NetUtilsTest {
@Test
public void localhostTest() {
var localHostStr = NetUtils.getLocalhostStr();
var localHost = NetUtils.getLocalhost();
Assert.assertEquals(localHostStr, localHost.getHostAddress());
}
@Test
public void ipv4Test() {
var ipLong = NetUtils.ipv4ToLong(NetUtils.LOCAL_LOOPBACK_IP);
var ipStr = NetUtils.longToIpv4(ipLong);
Assert.assertEquals(ipStr, NetUtils.LOCAL_LOOPBACK_IP);
}
@Test
public void isInnerIpTest() {
Assert.assertTrue(NetUtils.isInnerIP(NetUtils.LOCAL_LOOPBACK_IP));
}
@Ignore
@Test
public void isNetCatTest() throws IOException {
NetUtils.netCat(NetUtils.LOCAL_LOOPBACK_IP, 9000, new byte[]{1, 2, 3});
}
@Ignore
@Test
public void isUsableLocalPortTest() {
System.out.println(NetUtils.isAvailablePort(2181));
System.out.println(NetUtils.getAvailablePort(2181));
}
@Ignore
@Test
public void getAllNetworkInterfaceTest() {
var set = NetUtils.getAllNetworkInterface();
set.stream().forEach(it -> System.out.println(it));
}
@Ignore
@Test
public void getAllAddressTest() {
var set = NetUtils.getAllAddress();
set.stream().forEach(it -> System.out.println(it));
}
@Ignore
@Test
public void localIpv4sTest() {
var set = NetUtils.localIpv4s();
set.stream().forEach(it -> System.out.println(it));
}
}
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util;
import com.zfoo.protocol.util.ReflectionUtils;
import org.junit.Ignore;
import org.junit.Test;
import java.lang.reflect.Field;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* @author jaysunxiao
* @version 3.0
*/
@interface Id {
}
@Ignore
public class ReflectUtilTest {
@Test
public void testGetFieldsByAnnotation() {
Field[] fields = ReflectionUtils.getFieldsByAnnoInPOJOClass(User.class, Id.class);
System.out.println(fields.length);
}
@Test
public void testFilterFieldsInClass() {
ReflectionUtils.filterFieldsInClass(User.class, new Predicate<Field>() {
@Override
public boolean test(Field field) {
return field != null;
}
}, new Consumer<Field>() {
@Override
public void accept(Field field) {
System.out.println(field.getName());
}
});
}
}
@@ -0,0 +1,49 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util;
import com.zfoo.protocol.util.StringUtils;
import org.junit.Assert;
import org.junit.Test;
/**
* @author jaysunxiao
* @version 3.0
*/
public class StringUtilTest {
@Test
public void formatTest() {
String str = StringUtils.format("this is {} for {}", "a", "b");
Assert.assertEquals("this is a for b", str);
}
@Test
public void isEmpty() {
Assert.assertFalse(StringUtils.isEmpty(" "));
}
@Test
public void capitalize() {
String str = "hello world!";
Assert.assertEquals(StringUtils.capitalize(str), "Hello world!");
}
@Test
public void unCapitalize() {
String str = "Hello world!";
Assert.assertEquals(StringUtils.uncapitalize(str), "hello world!");
}
}
@@ -0,0 +1,84 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.captcha;
import com.zfoo.util.captcha.model.CaptchaFontEnum;
import org.junit.Ignore;
import org.junit.Test;
import java.io.File;
import java.io.FileOutputStream;
/**
* @author jaysunxiao
* @version 3.0
*/
@Ignore
public class CaptchaTest {
@Test
public void pngTest() throws Exception {
for (var font : CaptchaFontEnum.values()) {
PngCaptcha pngCaptcha = new PngCaptcha();
pngCaptcha.setLength(4);
pngCaptcha.setCaptchaFont(font);
pngCaptcha.buildCaptcha();
System.out.println(pngCaptcha.captcha());
pngCaptcha.drawImage(new FileOutputStream(new File(font.name() + ".png")));
}
}
@Test
public void gifTest() throws Exception {
for (var font : CaptchaFontEnum.values()) {
GifCaptcha gifCaptcha = new GifCaptcha();
gifCaptcha.setLength(5);
gifCaptcha.setCaptchaFont(font);
gifCaptcha.buildCaptcha();
System.out.println(gifCaptcha.captcha());
gifCaptcha.drawImage(new FileOutputStream(new File(font.name() + ".gif")));
}
}
@Test
public void arithmeticTest() throws Exception {
for (var font : CaptchaFontEnum.values()) {
ArithmeticCaptcha specCaptcha = new ArithmeticCaptcha();
specCaptcha.setLength(3);
specCaptcha.setCaptchaFont(font);
specCaptcha.buildCaptcha();
System.out.println(specCaptcha.getArithmeticString() + " " + specCaptcha.captcha());
specCaptcha.drawImage(new FileOutputStream(new File(font.name() + ".png")));
}
}
@Test
public void base64Test() throws Exception {
for (var i = 0; i < Long.MAX_VALUE; i++) {
GifCaptcha gifCaptcha = new GifCaptcha();
gifCaptcha.buildCaptcha();
gifCaptcha.toBase64();
PngCaptcha pngCaptcha = new PngCaptcha();
pngCaptcha.buildCaptcha();
pngCaptcha.toBase64();
ArithmeticCaptcha arithmeticCaptcha = new ArithmeticCaptcha();
arithmeticCaptcha.buildCaptcha();
arithmeticCaptcha.toBase64();
}
}
}
@@ -0,0 +1,76 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.collection;
import com.zfoo.protocol.collection.CollectionUtils;
import com.zfoo.protocol.model.Pair;
import org.junit.Assert;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author jaysunxiao
* @version 3.0
*/
public class CollectionUtilsTest {
@Test
public void mapRemoveTest() {
Map<String, String> map = new ConcurrentHashMap<>();
map.put("a", "a");
Assert.assertEquals(map.remove("a"), "a");
}
@Test
public void collateTest() {
var a = new ArrayList<>(List.of(1, 2, 3, 9));
var b = new ArrayList<>(List.of(2, 4, 6, 7, 8));
var c = CollectionUtils.collate(a, b);
var d = List.of(1, 2, 2, 3, 4, 6, 7, 8, 9);
Assert.assertArrayEquals(c.toArray(), d.toArray());
a = new ArrayList<>(List.of(1, 2, 3));
b = new ArrayList<>();
c = CollectionUtils.collate(a, b);
Assert.assertArrayEquals(c.toArray(), a.toArray());
}
@Test
public void listJoinListTest() {
var a = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
var b = List.of(11, 22, 33, 44, 55);
var c = List.of(1, 2, 3, 4, 5, 6);
var d = CollectionUtils.listJoinList(true, new Pair<>(2, a), new Pair<>(2, b), new Pair<>(2, c));
Assert.assertArrayEquals(d.toArray(), List.of(1, 2, 11, 22, 3, 4, 5, 6, 33, 44, 7, 8, 55, 9).toArray());
var e = CollectionUtils.listJoinList(false, new Pair<>(2, a), new Pair<>(2, b), new Pair<>(2, c));
Assert.assertArrayEquals(e.toArray(), List.of(1, 2, 11, 22, 1, 2, 3, 4, 33, 44, 3, 4, 5, 6, 55, 5, 6, 7, 8, 9).toArray());
}
@Test
public void subListLastTest() {
var list = List.of(1, 2, 3, 4, 5);
Assert.assertArrayEquals(CollectionUtils.subListLast(list, 1).toArray(), List.of(5).toArray());
Assert.assertArrayEquals(CollectionUtils.subListLast(list, 4).toArray(), List.of(2, 3, 4, 5).toArray());
Assert.assertArrayEquals(CollectionUtils.subListLast(list, 5).toArray(), list.toArray());
Assert.assertArrayEquals(CollectionUtils.subListLast(list, 6).toArray(), list.toArray());
Assert.assertArrayEquals(CollectionUtils.subListLast(list, 100).toArray(), list.toArray());
}
}
@@ -0,0 +1,39 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.collection;
import com.zfoo.protocol.collection.tree.GeneralTree;
import org.junit.Test;
/**
* @author jaysunxiao
* @version 3.0
*/
public class GeneralTreeTest {
@Test
public void test() {
var generalTree = new GeneralTree<String>();
generalTree.addNode("a", "hi");
generalTree.addNode("a.b", "hello");
generalTree.addNode("a.b.c", "world1");
generalTree.addNode("a.b.d", "world2");
generalTree.addNode("a.b.e", "world3");
System.out.println(generalTree.getNodeByPath("a.b").getData());
System.out.println(generalTree.getNodeByPath("a.b").getChildren());
}
}
@@ -0,0 +1,92 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.math;
import com.zfoo.protocol.model.Pair;
import com.zfoo.protocol.util.JsonUtils;
import com.zfoo.protocol.util.StringUtils;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;
import java.util.TreeMap;
/**
* @author jaysunxiao
* @version 3.0
*/
@Ignore
public class ConsistentHashTest {
//待添加入Hash环的服务器列表
private static List<Pair<String, String>> servers = List.of(new Pair<>("192.168.0.0:111", "192.168.0.0:111")
, new Pair<>("192.168.0.1:111", "192.168.0.1:111"), new Pair<>("192.168.0.2:111", "192.168.0.2:111"));
private static List<Pair<String, String>> nums = List.of(new Pair<>("1", "1"), new Pair<>("2", "2"), new Pair<>("3", "3"));
private static List<Pair<String, String>> chars = List.of(new Pair<>("a", "a"), new Pair<>("b", "b"), new Pair<>("c", "c"));
@Test
public void consistentHashTest() {
test(servers);
System.out.println(StringUtils.MULTIPLE_HYPHENS);
test(nums);
System.out.println(StringUtils.MULTIPLE_HYPHENS);
test(chars);
System.out.println(StringUtils.MULTIPLE_HYPHENS);
}
public void test(List<Pair<String, String>> list) {
var realNodeMap = new HashMap<String, Integer>();
var hitNodeMap = new HashMap<Integer, String>();
list.forEach(it -> realNodeMap.put(it.getKey(), 0));
var consistentHash = new ConsistentHash<>(list, 300);
for (int i = 0; i < 100000; i++) {
var key = String.valueOf(i);
var realNode = consistentHash.getRealNode(key).getKey();
int nums = realNodeMap.get(realNode);
realNodeMap.put(realNode, ++nums);
hitNodeMap.put(i, realNode);
}
for (int i = 0; i < 1000; i++) {
for (int j = 0; j < 100000; j++) {
var key = String.valueOf(j);
var realNode = consistentHash.getRealNode(key).getKey();
Assert.assertEquals(realNode, hitNodeMap.get(j));
}
}
System.out.println(JsonUtils.object2String(realNodeMap));
}
@Test
public void testTreeMap() {
var treeMap = new TreeMap<Integer, String>();
treeMap.put(1, "a");
treeMap.put(100, "b");
treeMap.put(200, "c");
System.out.println(treeMap.ceilingEntry(8));
System.out.println(treeMap.ceilingEntry(450));
}
}
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.math.dfa;
import org.junit.Ignore;
import org.junit.Test;
import java.util.List;
/**
* @author jaysunxiao
* @version 3.0
*/
@Ignore
public class WordTreeTest {
@Test
public void test() {
WordTree tree = new WordTree();
tree.addWord("大");
tree.addWord("大土豆");
tree.addWord("土豆");
tree.addWord("刚出锅");
tree.addWord("出锅");
tree.addWord("fuck");
// 正文
String text = "text asdff asdf afucksdf ";
List<String> matchAll = tree.matchAll(text, -1, true, false);
System.out.println(matchAll);
}
}
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.math.lexer;
import org.junit.Test;
/**
* @author jaysunxiao
* @version 3.0
*/
public class LexicalAnalysisTest {
private static final String str = " void main() {\n" +
" if (a == 8) {\n" +
" c = c +2;\n" +
" b = 1.2;\n" +
" c = c + 3;\n" +
" d = \"hello world\";\n" +
" }\n" +
" }";
@Test
public void test() {
var lexical = new LexicalAnalysis();
lexical.analyze(str.toCharArray());
}
}
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2020 The zfoo Authors
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.security;
import org.junit.Assert;
import org.junit.Test;
/**
* @author jaysunxiao
* @version 3.0
*/
public class AesUtilsTest {
@Test
public void test() {
String passWord = "hello world";
String encodePassWorld = AesUtils.getEncryptString(passWord);
Assert.assertEquals(passWord, AesUtils.getDecryptString(encodePassWorld));
}
}
@@ -0,0 +1,34 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.security;
import org.junit.Assert;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author jaysunxiao
* @version 3.0
*/
public class IdUtilsTest {
@Test
public void test() {
var atomicInteger = new AtomicInteger(Integer.MAX_VALUE);
atomicInteger.getAndIncrement();
Assert.assertEquals(atomicInteger.get(), Integer.MIN_VALUE);
}
}
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.security;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.util.DigestUtils;
/**
* @author jaysunxiao
* @version 3.0
*/
public class MD5UtilsTest {
@Test
public void md5Test() {
var str = "qwerasdfzxcv1234;:'";
Assert.assertEquals(MD5Utils.bytesToMD5(str.getBytes()).toLowerCase(), DigestUtils.md5DigestAsHex(str.getBytes()));
}
}
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.util.security;
import org.junit.Assert;
import org.junit.Test;
/**
* @author jaysunxiao
* @version 3.0
*/
public class ZipTest {
// Zip算法压缩测试
@Test
public void test() {
String str = "ZIP,是一个文件的压缩的算法,原名Deflate(真空),发明者为菲利普·卡兹(Phil Katz))," +
"他于1989年1月公布了该格式的资料。ZIP通常使用后缀名“.zip”,它的MIME格式为 application/zip 。" +
"目前,ZIP格式属于几种主流的压缩格式之一,其竞争者包括RAR格式以及开放源码的7-Zip格式。" +
"从性能上比较,RAR格式较ZIP格式压缩率较高,但是它的压缩时间远远高于Zip。" +
"而7-Zip(7z)由于提供了免费的压缩工具而逐渐在更多的领域得到应用。";
byte[] bytes = str.getBytes();
// 压缩前数组长度
Assert.assertEquals(bytes.length, 555);
bytes = ZipUtils.zip(bytes);
// 压缩后数组长度
Assert.assertEquals(bytes.length, 438);
bytes = ZipUtils.unZip(bytes);
Assert.assertEquals(new String(bytes), str);
}
}