From f3ac67686f4663af4ba8a1ef893c9180ad5b84d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A9=E7=88=B1=E6=9C=89=E6=83=85?= Date: Thu, 17 Feb 2022 15:03:31 +0800 Subject: [PATCH] =?UTF-8?q?U=20=E5=8A=A0=E5=85=A5=E8=A1=8C=E4=B8=BA?= =?UTF-8?q?=E8=BD=A8=E8=BF=B9=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../template/slider/SliderCaptchaInfo.java | 30 +- .../slider/StandardSliderCaptchaTemplate.java | 12 +- .../template/slider/util/CollectionUtils.java | 393 ++++++++ .../template/slider/util/ObjectUtils.java | 909 ++++++++++++++++++ .../validator/BasicCaptchaTrackValidator.java | 126 +++ .../SimpleSliderCaptchaValidator.java | 65 ++ .../slider/validator/SliderCaptchaTrack.java | 41 + .../validator/SliderCaptchaValidator.java | 46 + 8 files changed, 1609 insertions(+), 13 deletions(-) create mode 100644 src/main/java/cloud/tianai/captcha/template/slider/util/CollectionUtils.java create mode 100644 src/main/java/cloud/tianai/captcha/template/slider/util/ObjectUtils.java create mode 100644 src/main/java/cloud/tianai/captcha/template/slider/validator/BasicCaptchaTrackValidator.java create mode 100644 src/main/java/cloud/tianai/captcha/template/slider/validator/SimpleSliderCaptchaValidator.java create mode 100644 src/main/java/cloud/tianai/captcha/template/slider/validator/SliderCaptchaTrack.java create mode 100644 src/main/java/cloud/tianai/captcha/template/slider/validator/SliderCaptchaValidator.java diff --git a/src/main/java/cloud/tianai/captcha/template/slider/SliderCaptchaInfo.java b/src/main/java/cloud/tianai/captcha/template/slider/SliderCaptchaInfo.java index 992234d..612a12a 100644 --- a/src/main/java/cloud/tianai/captcha/template/slider/SliderCaptchaInfo.java +++ b/src/main/java/cloud/tianai/captcha/template/slider/SliderCaptchaInfo.java @@ -16,10 +16,6 @@ public class SliderCaptchaInfo { * x轴 */ private Integer x; - /** - * x轴百分比 - */ - private Float xPercent; /** * y轴 */ @@ -32,21 +28,39 @@ public class SliderCaptchaInfo { * 移动图 */ private String sliderImage; + /** 背景图片宽度. */ + private Integer bgImageWidth; + /** 背景图片高度. */ + private Integer bgImageHeight; + /** 滑块图片宽度. */ + private Integer sliderImageWidth; + /** 滑块图片高度. */ + private Integer sliderImageHeight; /** * 扩展字段 */ public Object expand; - public SliderCaptchaInfo(Integer x, Float xPercent, Integer y, String backgroundImage, String sliderImage) { + public SliderCaptchaInfo(Integer x, Integer y, String backgroundImage, String sliderImage, Integer bgImageWidth, Integer bgImageHeight, Integer sliderImageWidth, Integer sliderImageHeight) { this.x = x; - this.xPercent = xPercent; this.y = y; this.backgroundImage = backgroundImage; this.sliderImage = sliderImage; + this.bgImageWidth = bgImageWidth; + this.bgImageHeight = bgImageHeight; + this.sliderImageWidth = sliderImageWidth; + this.sliderImageHeight = sliderImageHeight; } - public static SliderCaptchaInfo of(Integer x, Float xPercent, Integer y, String backgroundImage, String sliderImage) { - return new SliderCaptchaInfo(x, xPercent, y, backgroundImage, sliderImage); + public static SliderCaptchaInfo of(Integer x, + Integer y, + String backgroundImage, + String sliderImage, + Integer bgImageWidth, + Integer bgImageHeight, + Integer sliderImageWidth, + Integer sliderImageHeight) { + return new SliderCaptchaInfo(x, y, backgroundImage, sliderImage, bgImageWidth, bgImageHeight, sliderImageWidth, sliderImageHeight); } } diff --git a/src/main/java/cloud/tianai/captcha/template/slider/StandardSliderCaptchaTemplate.java b/src/main/java/cloud/tianai/captcha/template/slider/StandardSliderCaptchaTemplate.java index 0654469..5a98ed5 100644 --- a/src/main/java/cloud/tianai/captcha/template/slider/StandardSliderCaptchaTemplate.java +++ b/src/main/java/cloud/tianai/captcha/template/slider/StandardSliderCaptchaTemplate.java @@ -151,19 +151,21 @@ public class StandardSliderCaptchaTemplate implements SliderCaptchaTemplate { GenerateParam param) { String backgroundFormatName = param.getBackgroundFormatName(); String sliderFormatName = param.getSliderFormatName(); - // 计算滑块百分比 - float xPercent = (float) randomX / backgroundImage.getWidth(); String backGroundImageBase64 = transform(backgroundImage, backgroundFormatName); String sliderImageBase64 = transform(sliderImage, sliderFormatName); - return SliderCaptchaInfo.of(randomX, xPercent, randomY, + return SliderCaptchaInfo.of(randomX, randomY, backGroundImageBase64, - sliderImageBase64); + sliderImageBase64, + backgroundImage.getWidth(), backgroundImage.getHeight(), + sliderImage.getWidth(), sliderImage.getHeight() + ); } /** * 将图片转换成字符串格式 + * * @param bufferedImage 图片 - * @param formatType 格式化类型 + * @param formatType 格式化类型 * @return String * @throws IOException */ diff --git a/src/main/java/cloud/tianai/captcha/template/slider/util/CollectionUtils.java b/src/main/java/cloud/tianai/captcha/template/slider/util/CollectionUtils.java new file mode 100644 index 0000000..4005af9 --- /dev/null +++ b/src/main/java/cloud/tianai/captcha/template/slider/util/CollectionUtils.java @@ -0,0 +1,393 @@ +/* + * Copyright 2002-2019 the original author or 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 + * + * https://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 cloud.tianai.captcha.template.slider.util; + +import java.util.*; + +/** + * + * 拷贝spring + * Miscellaneous collection utility methods. + * Mainly for internal use within the framework. + * + * @author Juergen Hoeller + * @author Rob Harrop + * @author Arjen Poutsma + * @since 1.1.3 + */ +public abstract class CollectionUtils { + + /** + * Return {@code true} if the supplied Collection is {@code null} or empty. + * Otherwise, return {@code false}. + * @param collection the Collection to check + * @return whether the given Collection is empty + */ + public static boolean isEmpty(Collection collection) { + return (collection == null || collection.isEmpty()); + } + + /** + * Return {@code true} if the supplied Map is {@code null} or empty. + * Otherwise, return {@code false}. + * @param map the Map to check + * @return whether the given Map is empty + */ + public static boolean isEmpty(Map map) { + return (map == null || map.isEmpty()); + } + + + + /** + * Merge the given Properties instance into the given Map, + * copying all properties (key-value pairs) over. + *

Uses {@code Properties.propertyNames()} to even catch + * default properties linked into the original Properties instance. + * @param props the Properties instance to merge (may be {@code null}) + * @param map the target Map to merge the properties into + */ + @SuppressWarnings("unchecked") + public static void mergePropertiesIntoMap(Properties props, Map map) { + if (props != null) { + for (Enumeration en = props.propertyNames(); en.hasMoreElements();) { + String key = (String) en.nextElement(); + Object value = props.get(key); + if (value == null) { + // Allow for defaults fallback or potentially overridden accessor... + value = props.getProperty(key); + } + map.put((K) key, (V) value); + } + } + } + + + /** + * Check whether the given Iterator contains the given element. + * @param iterator the Iterator to check + * @param element the element to look for + * @return {@code true} if found, {@code false} otherwise + */ + public static boolean contains(Iterator iterator, Object element) { + if (iterator != null) { + while (iterator.hasNext()) { + Object candidate = iterator.next(); + if (ObjectUtils.nullSafeEquals(candidate, element)) { + return true; + } + } + } + return false; + } + + /** + * Check whether the given Enumeration contains the given element. + * @param enumeration the Enumeration to check + * @param element the element to look for + * @return {@code true} if found, {@code false} otherwise + */ + public static boolean contains(Enumeration enumeration, Object element) { + if (enumeration != null) { + while (enumeration.hasMoreElements()) { + Object candidate = enumeration.nextElement(); + if (ObjectUtils.nullSafeEquals(candidate, element)) { + return true; + } + } + } + return false; + } + + /** + * Check whether the given Collection contains the given element instance. + *

Enforces the given instance to be present, rather than returning + * {@code true} for an equal element as well. + * @param collection the Collection to check + * @param element the element to look for + * @return {@code true} if found, {@code false} otherwise + */ + public static boolean containsInstance(Collection collection, Object element) { + if (collection != null) { + for (Object candidate : collection) { + if (candidate == element) { + return true; + } + } + } + return false; + } + + /** + * Return {@code true} if any element in '{@code candidates}' is + * contained in '{@code source}'; otherwise returns {@code false}. + * @param source the source Collection + * @param candidates the candidates to search for + * @return whether any of the candidates has been found + */ + public static boolean containsAny(Collection source, Collection candidates) { + return findFirstMatch(source, candidates) != null; + } + + /** + * Return the first element in '{@code candidates}' that is contained in + * '{@code source}'. If no element in '{@code candidates}' is present in + * '{@code source}' returns {@code null}. Iteration order is + * {@link Collection} implementation specific. + * @param source the source Collection + * @param candidates the candidates to search for + * @return the first present object, or {@code null} if not found + */ + @SuppressWarnings("unchecked") + public static E findFirstMatch(Collection source, Collection candidates) { + if (isEmpty(source) || isEmpty(candidates)) { + return null; + } + for (Object candidate : candidates) { + if (source.contains(candidate)) { + return (E) candidate; + } + } + return null; + } + + /** + * Find a single value of the given type in the given Collection. + * @param collection the Collection to search + * @param type the type to look for + * @return a value of the given type found if there is a clear match, + * or {@code null} if none or more than one such value found + */ + @SuppressWarnings("unchecked") + public static T findValueOfType(Collection collection, Class type) { + if (isEmpty(collection)) { + return null; + } + T value = null; + for (Object element : collection) { + if (type == null || type.isInstance(element)) { + if (value != null) { + // More than one value found... no clear single value. + return null; + } + value = (T) element; + } + } + return value; + } + + /** + * Find a single value of one of the given types in the given Collection: + * searching the Collection for a value of the first type, then + * searching for a value of the second type, etc. + * @param collection the collection to search + * @param types the types to look for, in prioritized order + * @return a value of one of the given types found if there is a clear match, + * or {@code null} if none or more than one such value found + */ + public static Object findValueOfType(Collection collection, Class[] types) { + if (isEmpty(collection) || ObjectUtils.isEmpty(types)) { + return null; + } + for (Class type : types) { + Object value = findValueOfType(collection, type); + if (value != null) { + return value; + } + } + return null; + } + + /** + * Determine whether the given Collection only contains a single unique object. + * @param collection the Collection to check + * @return {@code true} if the collection contains a single reference or + * multiple references to the same instance, {@code false} otherwise + */ + public static boolean hasUniqueObject(Collection collection) { + if (isEmpty(collection)) { + return false; + } + boolean hasCandidate = false; + Object candidate = null; + for (Object elem : collection) { + if (!hasCandidate) { + hasCandidate = true; + candidate = elem; + } + else if (candidate != elem) { + return false; + } + } + return true; + } + + /** + * Find the common element type of the given Collection, if any. + * @param collection the Collection to check + * @return the common element type, or {@code null} if no clear + * common type has been found (or the collection was empty) + */ + public static Class findCommonElementType(Collection collection) { + if (isEmpty(collection)) { + return null; + } + Class candidate = null; + for (Object val : collection) { + if (val != null) { + if (candidate == null) { + candidate = val.getClass(); + } + else if (candidate != val.getClass()) { + return null; + } + } + } + return candidate; + } + + /** + * Retrieve the first element of the given Set, using {@link SortedSet#first()} + * or otherwise using the iterator. + * @param set the Set to check (may be {@code null} or empty) + * @return the first element, or {@code null} if none + * @since 5.2.3 + * @see SortedSet + * @see LinkedHashMap#keySet() + * @see java.util.LinkedHashSet + */ + public static T firstElement(Set set) { + if (isEmpty(set)) { + return null; + } + if (set instanceof SortedSet) { + return ((SortedSet) set).first(); + } + + Iterator it = set.iterator(); + T first = null; + if (it.hasNext()) { + first = it.next(); + } + return first; + } + + /** + * Retrieve the first element of the given List, accessing the zero index. + * @param list the List to check (may be {@code null} or empty) + * @return the first element, or {@code null} if none + * @since 5.2.3 + */ + public static T firstElement(List list) { + if (isEmpty(list)) { + return null; + } + return list.get(0); + } + + /** + * Retrieve the last element of the given Set, using {@link SortedSet#last()} + * or otherwise iterating over all elements (assuming a linked set). + * @param set the Set to check (may be {@code null} or empty) + * @return the last element, or {@code null} if none + * @since 5.0.3 + * @see SortedSet + * @see LinkedHashMap#keySet() + * @see java.util.LinkedHashSet + */ + public static T lastElement(Set set) { + if (isEmpty(set)) { + return null; + } + if (set instanceof SortedSet) { + return ((SortedSet) set).last(); + } + + // Full iteration necessary... + Iterator it = set.iterator(); + T last = null; + while (it.hasNext()) { + last = it.next(); + } + return last; + } + + /** + * Retrieve the last element of the given List, accessing the highest index. + * @param list the List to check (may be {@code null} or empty) + * @return the last element, or {@code null} if none + * @since 5.0.3 + */ + public static T lastElement(List list) { + if (isEmpty(list)) { + return null; + } + return list.get(list.size() - 1); + } + + /** + * Marshal the elements from the given enumeration into an array of the given type. + * Enumeration elements must be assignable to the type of the given array. The array + * returned will be a different instance than the array given. + */ + public static A[] toArray(Enumeration enumeration, A[] array) { + ArrayList elements = new ArrayList<>(); + while (enumeration.hasMoreElements()) { + elements.add(enumeration.nextElement()); + } + return elements.toArray(array); + } + + /** + * Adapt an {@link Enumeration} to an {@link Iterator}. + * @param enumeration the original {@code Enumeration} + * @return the adapted {@code Iterator} + */ + public static Iterator toIterator(Enumeration enumeration) { + return (enumeration != null ? new EnumerationIterator<>(enumeration) : Collections.emptyIterator()); + } + + /** + * Iterator wrapping an Enumeration. + */ + private static class EnumerationIterator implements Iterator { + + private final Enumeration enumeration; + + public EnumerationIterator(Enumeration enumeration) { + this.enumeration = enumeration; + } + + @Override + public boolean hasNext() { + return this.enumeration.hasMoreElements(); + } + + @Override + public E next() { + return this.enumeration.nextElement(); + } + + @Override + public void remove() throws UnsupportedOperationException { + throw new UnsupportedOperationException("Not supported"); + } + } + + + +} diff --git a/src/main/java/cloud/tianai/captcha/template/slider/util/ObjectUtils.java b/src/main/java/cloud/tianai/captcha/template/slider/util/ObjectUtils.java new file mode 100644 index 0000000..858afe0 --- /dev/null +++ b/src/main/java/cloud/tianai/captcha/template/slider/util/ObjectUtils.java @@ -0,0 +1,909 @@ +/* + * Copyright 2002-2019 the original author or 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 + * + * https://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 cloud.tianai.captcha.template.slider.util; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.Optional; +import java.util.StringJoiner; + +/** + * 拷贝spring + * + * Miscellaneous object utility methods. + * + *

Mainly for internal use within the framework. + * + *

Thanks to Alex Ruiz for contributing several enhancements to this class! + * + * @author Juergen Hoeller + * @author Keith Donald + * @author Rod Johnson + * @author Rob Harrop + * @author Chris Beams + * @author Sam Brannen + * @since 19.03.2004 + * @see CollectionUtils + */ +public abstract class ObjectUtils { + + private static final int INITIAL_HASH = 7; + private static final int MULTIPLIER = 31; + + private static final String EMPTY_STRING = ""; + private static final String NULL_STRING = "null"; + private static final String ARRAY_START = "{"; + private static final String ARRAY_END = "}"; + private static final String EMPTY_ARRAY = ARRAY_START + ARRAY_END; + private static final String ARRAY_ELEMENT_SEPARATOR = ", "; + private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; + + + /** + * Return whether the given throwable is a checked exception: + * that is, neither a RuntimeException nor an Error. + * @param ex the throwable to check + * @return whether the throwable is a checked exception + * @see java.lang.Exception + * @see java.lang.RuntimeException + * @see java.lang.Error + */ + public static boolean isCheckedException(Throwable ex) { + return !(ex instanceof RuntimeException || ex instanceof Error); + } + + /** + * Check whether the given exception is compatible with the specified + * exception types, as declared in a throws clause. + * @param ex the exception to check + * @param declaredExceptions the exception types declared in the throws clause + * @return whether the given exception is compatible + */ + public static boolean isCompatibleWithThrowsClause(Throwable ex, Class... declaredExceptions) { + if (!isCheckedException(ex)) { + return true; + } + if (declaredExceptions != null) { + for (Class declaredException : declaredExceptions) { + if (declaredException.isInstance(ex)) { + return true; + } + } + } + return false; + } + + /** + * Determine whether the given object is an array: + * either an Object array or a primitive array. + * @param obj the object to check + */ + public static boolean isArray(Object obj) { + return (obj != null && obj.getClass().isArray()); + } + + /** + * Determine whether the given array is empty: + * i.e. {@code null} or of zero length. + * @param array the array to check + * @see #isEmpty(Object) + */ + public static boolean isEmpty(Object[] array) { + return (array == null || array.length == 0); + } + + /** + * Determine whether the given object is empty. + *

This method supports the following object types. + *

+ *

If the given object is non-null and not one of the aforementioned + * supported types, this method returns {@code false}. + * @param obj the object to check + * @return {@code true} if the object is {@code null} or empty + * @since 4.2 + * @see Optional#isPresent() + * @see ObjectUtils#isEmpty(Object[]) + * @see CollectionUtils#isEmpty(java.util.Collection) + * @see CollectionUtils#isEmpty(java.util.Map) + */ + @SuppressWarnings("rawtypes") + public static boolean isEmpty(Object obj) { + if (obj == null) { + return true; + } + + if (obj instanceof Optional) { + return !((Optional) obj).isPresent(); + } + if (obj instanceof CharSequence) { + return ((CharSequence) obj).length() == 0; + } + if (obj.getClass().isArray()) { + return Array.getLength(obj) == 0; + } + if (obj instanceof Collection) { + return ((Collection) obj).isEmpty(); + } + if (obj instanceof Map) { + return ((Map) obj).isEmpty(); + } + + // else + return false; + } + + /** + * Unwrap the given object which is potentially a {@link java.util.Optional}. + * @param obj the candidate object + * @return either the value held within the {@code Optional}, {@code null} + * if the {@code Optional} is empty, or simply the given object as-is + * @since 5.0 + */ + public static Object unwrapOptional(Object obj) { + if (obj instanceof Optional) { + Optional optional = (Optional) obj; + if (!optional.isPresent()) { + return null; + } + Object result = optional.get(); + return result; + } + return obj; + } + + /** + * Check whether the given array contains the given element. + * @param array the array to check (may be {@code null}, + * in which case the return value will always be {@code false}) + * @param element the element to check for + * @return whether the element has been found in the given array + */ + public static boolean containsElement(Object[] array, Object element) { + if (array == null) { + return false; + } + for (Object arrayEle : array) { + if (nullSafeEquals(arrayEle, element)) { + return true; + } + } + return false; + } + + /** + * Check whether the given array of enum constants contains a constant with the given name, + * ignoring case when determining a match. + * @param enumValues the enum values to check, typically obtained via {@code MyEnum.values()} + * @param constant the constant name to find (must not be null or empty string) + * @return whether the constant has been found in the given array + */ + public static boolean containsConstant(Enum[] enumValues, String constant) { + return containsConstant(enumValues, constant, false); + } + + /** + * Check whether the given array of enum constants contains a constant with the given name. + * @param enumValues the enum values to check, typically obtained via {@code MyEnum.values()} + * @param constant the constant name to find (must not be null or empty string) + * @param caseSensitive whether case is significant in determining a match + * @return whether the constant has been found in the given array + */ + public static boolean containsConstant(Enum[] enumValues, String constant, boolean caseSensitive) { + for (Enum candidate : enumValues) { + if (caseSensitive ? candidate.toString().equals(constant) : + candidate.toString().equalsIgnoreCase(constant)) { + return true; + } + } + return false; + } + + /** + * Case insensitive alternative to {@link Enum#valueOf(Class, String)}. + * @param the concrete Enum type + * @param enumValues the array of all Enum constants in question, usually per {@code Enum.values()} + * @param constant the constant to get the enum value of + * @throws IllegalArgumentException if the given constant is not found in the given array + * of enum values. Use {@link #containsConstant(Enum[], String)} as a guard to avoid this exception. + */ + public static > E caseInsensitiveValueOf(E[] enumValues, String constant) { + for (E candidate : enumValues) { + if (candidate.toString().equalsIgnoreCase(constant)) { + return candidate; + } + } + throw new IllegalArgumentException("Constant [" + constant + "] does not exist in enum type " + + enumValues.getClass().getComponentType().getName()); + } + + /** + * Append the given object to the given array, returning a new array + * consisting of the input array contents plus the given object. + * @param array the array to append to (can be {@code null}) + * @param obj the object to append + * @return the new array (of the same component type; never {@code null}) + */ + public static A[] addObjectToArray(A[] array, O obj) { + Class compType = Object.class; + if (array != null) { + compType = array.getClass().getComponentType(); + } + else if (obj != null) { + compType = obj.getClass(); + } + int newArrLength = (array != null ? array.length + 1 : 1); + @SuppressWarnings("unchecked") + A[] newArr = (A[]) Array.newInstance(compType, newArrLength); + if (array != null) { + System.arraycopy(array, 0, newArr, 0, array.length); + } + newArr[newArr.length - 1] = obj; + return newArr; + } + + /** + * Convert the given array (which may be a primitive array) to an + * object array (if necessary of primitive wrapper objects). + *

A {@code null} source value will be converted to an + * empty Object array. + * @param source the (potentially primitive) array + * @return the corresponding object array (never {@code null}) + * @throws IllegalArgumentException if the parameter is not an array + */ + public static Object[] toObjectArray(Object source) { + if (source instanceof Object[]) { + return (Object[]) source; + } + if (source == null) { + return EMPTY_OBJECT_ARRAY; + } + if (!source.getClass().isArray()) { + throw new IllegalArgumentException("Source is not an array: " + source); + } + int length = Array.getLength(source); + if (length == 0) { + return EMPTY_OBJECT_ARRAY; + } + Class wrapperType = Array.get(source, 0).getClass(); + Object[] newArray = (Object[]) Array.newInstance(wrapperType, length); + for (int i = 0; i < length; i++) { + newArray[i] = Array.get(source, i); + } + return newArray; + } + + + //--------------------------------------------------------------------- + // Convenience methods for content-based equality/hash-code handling + //--------------------------------------------------------------------- + + /** + * Determine if the given objects are equal, returning {@code true} if + * both are {@code null} or {@code false} if only one is {@code null}. + *

Compares arrays with {@code Arrays.equals}, performing an equality + * check based on the array elements rather than the array reference. + * @param o1 first Object to compare + * @param o2 second Object to compare + * @return whether the given objects are equal + * @see Object#equals(Object) + * @see java.util.Arrays#equals + */ + public static boolean nullSafeEquals(Object o1, Object o2) { + if (o1 == o2) { + return true; + } + if (o1 == null || o2 == null) { + return false; + } + if (o1.equals(o2)) { + return true; + } + if (o1.getClass().isArray() && o2.getClass().isArray()) { + return arrayEquals(o1, o2); + } + return false; + } + + /** + * Compare the given arrays with {@code Arrays.equals}, performing an equality + * check based on the array elements rather than the array reference. + * @param o1 first array to compare + * @param o2 second array to compare + * @return whether the given objects are equal + * @see #nullSafeEquals(Object, Object) + * @see java.util.Arrays#equals + */ + private static boolean arrayEquals(Object o1, Object o2) { + if (o1 instanceof Object[] && o2 instanceof Object[]) { + return Arrays.equals((Object[]) o1, (Object[]) o2); + } + if (o1 instanceof boolean[] && o2 instanceof boolean[]) { + return Arrays.equals((boolean[]) o1, (boolean[]) o2); + } + if (o1 instanceof byte[] && o2 instanceof byte[]) { + return Arrays.equals((byte[]) o1, (byte[]) o2); + } + if (o1 instanceof char[] && o2 instanceof char[]) { + return Arrays.equals((char[]) o1, (char[]) o2); + } + if (o1 instanceof double[] && o2 instanceof double[]) { + return Arrays.equals((double[]) o1, (double[]) o2); + } + if (o1 instanceof float[] && o2 instanceof float[]) { + return Arrays.equals((float[]) o1, (float[]) o2); + } + if (o1 instanceof int[] && o2 instanceof int[]) { + return Arrays.equals((int[]) o1, (int[]) o2); + } + if (o1 instanceof long[] && o2 instanceof long[]) { + return Arrays.equals((long[]) o1, (long[]) o2); + } + if (o1 instanceof short[] && o2 instanceof short[]) { + return Arrays.equals((short[]) o1, (short[]) o2); + } + return false; + } + + /** + * Return as hash code for the given object; typically the value of + * {@code Object#hashCode()}}. If the object is an array, + * this method will delegate to any of the {@code nullSafeHashCode} + * methods for arrays in this class. If the object is {@code null}, + * this method returns 0. + * @see Object#hashCode() + * @see #nullSafeHashCode(Object[]) + * @see #nullSafeHashCode(boolean[]) + * @see #nullSafeHashCode(byte[]) + * @see #nullSafeHashCode(char[]) + * @see #nullSafeHashCode(double[]) + * @see #nullSafeHashCode(float[]) + * @see #nullSafeHashCode(int[]) + * @see #nullSafeHashCode(long[]) + * @see #nullSafeHashCode(short[]) + */ + public static int nullSafeHashCode(Object obj) { + if (obj == null) { + return 0; + } + if (obj.getClass().isArray()) { + if (obj instanceof Object[]) { + return nullSafeHashCode((Object[]) obj); + } + if (obj instanceof boolean[]) { + return nullSafeHashCode((boolean[]) obj); + } + if (obj instanceof byte[]) { + return nullSafeHashCode((byte[]) obj); + } + if (obj instanceof char[]) { + return nullSafeHashCode((char[]) obj); + } + if (obj instanceof double[]) { + return nullSafeHashCode((double[]) obj); + } + if (obj instanceof float[]) { + return nullSafeHashCode((float[]) obj); + } + if (obj instanceof int[]) { + return nullSafeHashCode((int[]) obj); + } + if (obj instanceof long[]) { + return nullSafeHashCode((long[]) obj); + } + if (obj instanceof short[]) { + return nullSafeHashCode((short[]) obj); + } + } + return obj.hashCode(); + } + + /** + * Return a hash code based on the contents of the specified array. + * If {@code array} is {@code null}, this method returns 0. + */ + public static int nullSafeHashCode(Object[] array) { + if (array == null) { + return 0; + } + int hash = INITIAL_HASH; + for (Object element : array) { + hash = MULTIPLIER * hash + nullSafeHashCode(element); + } + return hash; + } + + /** + * Return a hash code based on the contents of the specified array. + * If {@code array} is {@code null}, this method returns 0. + */ + public static int nullSafeHashCode(boolean[] array) { + if (array == null) { + return 0; + } + int hash = INITIAL_HASH; + for (boolean element : array) { + hash = MULTIPLIER * hash + Boolean.hashCode(element); + } + return hash; + } + + /** + * Return a hash code based on the contents of the specified array. + * If {@code array} is {@code null}, this method returns 0. + */ + public static int nullSafeHashCode(byte[] array) { + if (array == null) { + return 0; + } + int hash = INITIAL_HASH; + for (byte element : array) { + hash = MULTIPLIER * hash + element; + } + return hash; + } + + /** + * Return a hash code based on the contents of the specified array. + * If {@code array} is {@code null}, this method returns 0. + */ + public static int nullSafeHashCode(char[] array) { + if (array == null) { + return 0; + } + int hash = INITIAL_HASH; + for (char element : array) { + hash = MULTIPLIER * hash + element; + } + return hash; + } + + /** + * Return a hash code based on the contents of the specified array. + * If {@code array} is {@code null}, this method returns 0. + */ + public static int nullSafeHashCode(double[] array) { + if (array == null) { + return 0; + } + int hash = INITIAL_HASH; + for (double element : array) { + hash = MULTIPLIER * hash + Double.hashCode(element); + } + return hash; + } + + /** + * Return a hash code based on the contents of the specified array. + * If {@code array} is {@code null}, this method returns 0. + */ + public static int nullSafeHashCode(float[] array) { + if (array == null) { + return 0; + } + int hash = INITIAL_HASH; + for (float element : array) { + hash = MULTIPLIER * hash + Float.hashCode(element); + } + return hash; + } + + /** + * Return a hash code based on the contents of the specified array. + * If {@code array} is {@code null}, this method returns 0. + */ + public static int nullSafeHashCode(int[] array) { + if (array == null) { + return 0; + } + int hash = INITIAL_HASH; + for (int element : array) { + hash = MULTIPLIER * hash + element; + } + return hash; + } + + /** + * Return a hash code based on the contents of the specified array. + * If {@code array} is {@code null}, this method returns 0. + */ + public static int nullSafeHashCode(long[] array) { + if (array == null) { + return 0; + } + int hash = INITIAL_HASH; + for (long element : array) { + hash = MULTIPLIER * hash + Long.hashCode(element); + } + return hash; + } + + /** + * Return a hash code based on the contents of the specified array. + * If {@code array} is {@code null}, this method returns 0. + */ + public static int nullSafeHashCode(short[] array) { + if (array == null) { + return 0; + } + int hash = INITIAL_HASH; + for (short element : array) { + hash = MULTIPLIER * hash + element; + } + return hash; + } + + /** + * Return the same value as {@link Boolean#hashCode(boolean)}}. + * @deprecated as of Spring Framework 5.0, in favor of the native JDK 8 variant + */ + @Deprecated + public static int hashCode(boolean bool) { + return Boolean.hashCode(bool); + } + + /** + * Return the same value as {@link Double#hashCode(double)}}. + * @deprecated as of Spring Framework 5.0, in favor of the native JDK 8 variant + */ + @Deprecated + public static int hashCode(double dbl) { + return Double.hashCode(dbl); + } + + /** + * Return the same value as {@link Float#hashCode(float)}}. + * @deprecated as of Spring Framework 5.0, in favor of the native JDK 8 variant + */ + @Deprecated + public static int hashCode(float flt) { + return Float.hashCode(flt); + } + + /** + * Return the same value as {@link Long#hashCode(long)}}. + * @deprecated as of Spring Framework 5.0, in favor of the native JDK 8 variant + */ + @Deprecated + public static int hashCode(long lng) { + return Long.hashCode(lng); + } + + + //--------------------------------------------------------------------- + // Convenience methods for toString output + //--------------------------------------------------------------------- + + /** + * Return a String representation of an object's overall identity. + * @param obj the object (may be {@code null}) + * @return the object's identity as String representation, + * or an empty String if the object was {@code null} + */ + public static String identityToString(Object obj) { + if (obj == null) { + return EMPTY_STRING; + } + String className = obj.getClass().getName(); + String identityHexString = getIdentityHexString(obj); + return className + '@' + identityHexString; + } + + /** + * Return a hex String form of an object's identity hash code. + * @param obj the object + * @return the object's identity code in hex notation + */ + public static String getIdentityHexString(Object obj) { + return Integer.toHexString(System.identityHashCode(obj)); + } + + /** + * Return a content-based String representation if {@code obj} is + * not {@code null}; otherwise returns an empty String. + *

Differs from {@link #nullSafeToString(Object)} in that it returns + * an empty String rather than "null" for a {@code null} value. + * @param obj the object to build a display String for + * @return a display String representation of {@code obj} + * @see #nullSafeToString(Object) + */ + public static String getDisplayString(Object obj) { + if (obj == null) { + return EMPTY_STRING; + } + return nullSafeToString(obj); + } + + /** + * Determine the class name for the given object. + *

Returns a {@code "null"} String if {@code obj} is {@code null}. + * @param obj the object to introspect (may be {@code null}) + * @return the corresponding class name + */ + public static String nullSafeClassName(Object obj) { + return (obj != null ? obj.getClass().getName() : NULL_STRING); + } + + /** + * Return a String representation of the specified Object. + *

Builds a String representation of the contents in case of an array. + * Returns a {@code "null"} String if {@code obj} is {@code null}. + * @param obj the object to build a String representation for + * @return a String representation of {@code obj} + */ + public static String nullSafeToString(Object obj) { + if (obj == null) { + return NULL_STRING; + } + if (obj instanceof String) { + return (String) obj; + } + if (obj instanceof Object[]) { + return nullSafeToString((Object[]) obj); + } + if (obj instanceof boolean[]) { + return nullSafeToString((boolean[]) obj); + } + if (obj instanceof byte[]) { + return nullSafeToString((byte[]) obj); + } + if (obj instanceof char[]) { + return nullSafeToString((char[]) obj); + } + if (obj instanceof double[]) { + return nullSafeToString((double[]) obj); + } + if (obj instanceof float[]) { + return nullSafeToString((float[]) obj); + } + if (obj instanceof int[]) { + return nullSafeToString((int[]) obj); + } + if (obj instanceof long[]) { + return nullSafeToString((long[]) obj); + } + if (obj instanceof short[]) { + return nullSafeToString((short[]) obj); + } + String str = obj.toString(); + return (str != null ? str : EMPTY_STRING); + } + + /** + * Return a String representation of the contents of the specified array. + *

The String representation consists of a list of the array's elements, + * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated + * by the characters {@code ", "} (a comma followed by a space). + * Returns a {@code "null"} String if {@code array} is {@code null}. + * @param array the array to build a String representation for + * @return a String representation of {@code array} + */ + public static String nullSafeToString(Object[] array) { + if (array == null) { + return NULL_STRING; + } + int length = array.length; + if (length == 0) { + return EMPTY_ARRAY; + } + StringJoiner stringJoiner = new StringJoiner(ARRAY_ELEMENT_SEPARATOR, ARRAY_START, ARRAY_END); + for (Object o : array) { + stringJoiner.add(String.valueOf(o)); + } + return stringJoiner.toString(); + } + + /** + * Return a String representation of the contents of the specified array. + *

The String representation consists of a list of the array's elements, + * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated + * by the characters {@code ", "} (a comma followed by a space). + * Returns a {@code "null"} String if {@code array} is {@code null}. + * @param array the array to build a String representation for + * @return a String representation of {@code array} + */ + public static String nullSafeToString(boolean[] array) { + if (array == null) { + return NULL_STRING; + } + int length = array.length; + if (length == 0) { + return EMPTY_ARRAY; + } + StringJoiner stringJoiner = new StringJoiner(ARRAY_ELEMENT_SEPARATOR, ARRAY_START, ARRAY_END); + for (boolean b : array) { + stringJoiner.add(String.valueOf(b)); + } + return stringJoiner.toString(); + } + + /** + * Return a String representation of the contents of the specified array. + *

The String representation consists of a list of the array's elements, + * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated + * by the characters {@code ", "} (a comma followed by a space). + * Returns a {@code "null"} String if {@code array} is {@code null}. + * @param array the array to build a String representation for + * @return a String representation of {@code array} + */ + public static String nullSafeToString(byte[] array) { + if (array == null) { + return NULL_STRING; + } + int length = array.length; + if (length == 0) { + return EMPTY_ARRAY; + } + StringJoiner stringJoiner = new StringJoiner(ARRAY_ELEMENT_SEPARATOR, ARRAY_START, ARRAY_END); + for (byte b : array) { + stringJoiner.add(String.valueOf(b)); + } + return stringJoiner.toString(); + } + + /** + * Return a String representation of the contents of the specified array. + *

The String representation consists of a list of the array's elements, + * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated + * by the characters {@code ", "} (a comma followed by a space). + * Returns a {@code "null"} String if {@code array} is {@code null}. + * @param array the array to build a String representation for + * @return a String representation of {@code array} + */ + public static String nullSafeToString(char[] array) { + if (array == null) { + return NULL_STRING; + } + int length = array.length; + if (length == 0) { + return EMPTY_ARRAY; + } + StringJoiner stringJoiner = new StringJoiner(ARRAY_ELEMENT_SEPARATOR, ARRAY_START, ARRAY_END); + for (char c : array) { + stringJoiner.add('\'' + String.valueOf(c) + '\''); + } + return stringJoiner.toString(); + } + + /** + * Return a String representation of the contents of the specified array. + *

The String representation consists of a list of the array's elements, + * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated + * by the characters {@code ", "} (a comma followed by a space). + * Returns a {@code "null"} String if {@code array} is {@code null}. + * @param array the array to build a String representation for + * @return a String representation of {@code array} + */ + public static String nullSafeToString(double[] array) { + if (array == null) { + return NULL_STRING; + } + int length = array.length; + if (length == 0) { + return EMPTY_ARRAY; + } + StringJoiner stringJoiner = new StringJoiner(ARRAY_ELEMENT_SEPARATOR, ARRAY_START, ARRAY_END); + for (double d : array) { + stringJoiner.add(String.valueOf(d)); + } + return stringJoiner.toString(); + } + + /** + * Return a String representation of the contents of the specified array. + *

The String representation consists of a list of the array's elements, + * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated + * by the characters {@code ", "} (a comma followed by a space). + * Returns a {@code "null"} String if {@code array} is {@code null}. + * @param array the array to build a String representation for + * @return a String representation of {@code array} + */ + public static String nullSafeToString(float[] array) { + if (array == null) { + return NULL_STRING; + } + int length = array.length; + if (length == 0) { + return EMPTY_ARRAY; + } + StringJoiner stringJoiner = new StringJoiner(ARRAY_ELEMENT_SEPARATOR, ARRAY_START, ARRAY_END); + for (float f : array) { + stringJoiner.add(String.valueOf(f)); + } + return stringJoiner.toString(); + } + + /** + * Return a String representation of the contents of the specified array. + *

The String representation consists of a list of the array's elements, + * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated + * by the characters {@code ", "} (a comma followed by a space). + * Returns a {@code "null"} String if {@code array} is {@code null}. + * @param array the array to build a String representation for + * @return a String representation of {@code array} + */ + public static String nullSafeToString(int[] array) { + if (array == null) { + return NULL_STRING; + } + int length = array.length; + if (length == 0) { + return EMPTY_ARRAY; + } + StringJoiner stringJoiner = new StringJoiner(ARRAY_ELEMENT_SEPARATOR, ARRAY_START, ARRAY_END); + for (int i : array) { + stringJoiner.add(String.valueOf(i)); + } + return stringJoiner.toString(); + } + + /** + * Return a String representation of the contents of the specified array. + *

The String representation consists of a list of the array's elements, + * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated + * by the characters {@code ", "} (a comma followed by a space). + * Returns a {@code "null"} String if {@code array} is {@code null}. + * @param array the array to build a String representation for + * @return a String representation of {@code array} + */ + public static String nullSafeToString(long[] array) { + if (array == null) { + return NULL_STRING; + } + int length = array.length; + if (length == 0) { + return EMPTY_ARRAY; + } + StringJoiner stringJoiner = new StringJoiner(ARRAY_ELEMENT_SEPARATOR, ARRAY_START, ARRAY_END); + for (long l : array) { + stringJoiner.add(String.valueOf(l)); + } + return stringJoiner.toString(); + } + + /** + * Return a String representation of the contents of the specified array. + *

The String representation consists of a list of the array's elements, + * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated + * by the characters {@code ", "} (a comma followed by a space). + * Returns a {@code "null"} String if {@code array} is {@code null}. + * @param array the array to build a String representation for + * @return a String representation of {@code array} + */ + public static String nullSafeToString(short[] array) { + if (array == null) { + return NULL_STRING; + } + int length = array.length; + if (length == 0) { + return EMPTY_ARRAY; + } + StringJoiner stringJoiner = new StringJoiner(ARRAY_ELEMENT_SEPARATOR, ARRAY_START, ARRAY_END); + for (short s : array) { + stringJoiner.add(String.valueOf(s)); + } + return stringJoiner.toString(); + } + +} diff --git a/src/main/java/cloud/tianai/captcha/template/slider/validator/BasicCaptchaTrackValidator.java b/src/main/java/cloud/tianai/captcha/template/slider/validator/BasicCaptchaTrackValidator.java new file mode 100644 index 0000000..e9d3dc1 --- /dev/null +++ b/src/main/java/cloud/tianai/captcha/template/slider/validator/BasicCaptchaTrackValidator.java @@ -0,0 +1,126 @@ +package cloud.tianai.captcha.template.slider.validator; + +import cloud.tianai.captcha.template.slider.util.CollectionUtils; +import cloud.tianai.captcha.template.slider.util.ObjectUtils; + +import java.util.List; + +/** + * @Author: 天爱有情 + * @date 2022/2/17 11:01 + * @Description 基本的行为轨迹校验 + */ +public class BasicCaptchaTrackValidator extends SimpleSliderCaptchaValidator { + + public BasicCaptchaTrackValidator() { + } + + public BasicCaptchaTrackValidator(float defaultTolerant) { + super(defaultTolerant); + } + + @Override + public boolean valid(SliderCaptchaTrack sliderCaptchaTrack, Float oriPercentage) { + // 校验参数 + checkParam(sliderCaptchaTrack); + // 基础校验 + boolean superValid = super.valid(sliderCaptchaTrack, oriPercentage); + if (!superValid) { + return false; + } + // 进行行为轨迹检测 + long startSlidingTime = sliderCaptchaTrack.getStartSlidingTime().getTime(); + long endSlidingTime = sliderCaptchaTrack.getEntSlidingTime().getTime(); + Integer bgImageWidth = sliderCaptchaTrack.getBgImageWidth(); + List trackList = sliderCaptchaTrack.getTrackList(); + // 这里只进行基本检测, 用一些简单算法进行校验,如有需要可扩展 + // 检测1: 滑动时间如果小于300毫秒 返回false + // 检测2: 轨迹数据要是少于背景宽度的五分之一,或者大于背景宽度的五分之一 返回false + // 检测3: x轴和y轴应该是从0开始的,要是一开始x轴和y轴乱跑,返回false + // 检测4: 如果y轴是相同的,必然是机器操作,直接返回false + // 检测5: x轴或者y轴直接的区间跳跃过大的话返回 false + // 检测6: x轴应该是由快到慢的, 要是速率一致,返回false + // 检测7: 如果x轴超过图片宽度的频率过高,返回false + + // 检测1 + if (startSlidingTime + 300 > endSlidingTime) { + return false; + } + // 检测2 + if (trackList.size() < bgImageWidth / 5 || trackList.size() > bgImageWidth * 5) { + return false; + } + // 检测3 + SliderCaptchaTrack.Track firstTrack = trackList.get(0); + if (firstTrack.getX() > 1 || firstTrack.getX() < -2 || firstTrack.getY() > 1 || firstTrack.getY() < -2) { + return false; + } + int check4 = 0; + int check7 = 0; + for (int i = 1; i < trackList.size(); i++) { + SliderCaptchaTrack.Track track = trackList.get(i); + int x = track.getX(); + int y = track.getY(); + // check4 + if (firstTrack.getY() == y) { + check4++; + } + // check7 + if (x >= bgImageWidth) { + check7++; + } + // check5 + SliderCaptchaTrack.Track preTrack = trackList.get(i - 1); + if ((track.getX() - preTrack.getX()) > 5 || (track.getY() - preTrack.getY()) > 5) { + return false; + } + } + if (check4 > trackList.size() * 0.7 || check7 > 200) { + return false; + } + + // check6 + int splitPos = (int) (trackList.size() * 0.7); + SliderCaptchaTrack.Track splitPostTrack = trackList.get(splitPos - 1); + int posTime = splitPostTrack.getT(); + float startAvgPosTime = posTime / (float) splitPos; + + SliderCaptchaTrack.Track lastTrack = trackList.get(trackList.size() - 1); + float endAvgPosTime = lastTrack.getT() / (float) (trackList.size() - splitPos); + + return endAvgPosTime > startAvgPosTime; + } + + + public void checkParam(SliderCaptchaTrack sliderCaptchaTrack) { + if (ObjectUtils.isEmpty(sliderCaptchaTrack.getBgImageWidth())) { + throw new IllegalArgumentException("bgImageWidth must not be null"); + } + if (ObjectUtils.isEmpty(sliderCaptchaTrack.getBgImageHeight())) { + throw new IllegalArgumentException("bgImageHeight must not be null"); + } + if (ObjectUtils.isEmpty(sliderCaptchaTrack.getSliderImageWidth())) { + throw new IllegalArgumentException("sliderImageWidth must not be null"); + } + if (ObjectUtils.isEmpty(sliderCaptchaTrack.getSliderImageHeight())) { + throw new IllegalArgumentException("sliderImageHeight must not be null"); + } + if (ObjectUtils.isEmpty(sliderCaptchaTrack.getStartSlidingTime())) { + throw new IllegalArgumentException("startSlidingTime must not be null"); + } + if (ObjectUtils.isEmpty(sliderCaptchaTrack.getEntSlidingTime())) { + throw new IllegalArgumentException("entSlidingTime must not be null"); + } + if (CollectionUtils.isEmpty(sliderCaptchaTrack.getTrackList())) { + throw new IllegalArgumentException("trackList must not be null"); + } + for (SliderCaptchaTrack.Track track : sliderCaptchaTrack.getTrackList()) { + Integer x = track.getX(); + Integer y = track.getY(); + Integer t = track.getT(); + if (x == null || y == null || t == null) { + throw new IllegalArgumentException("track[x,y,t] must not be null"); + } + } + } +} diff --git a/src/main/java/cloud/tianai/captcha/template/slider/validator/SimpleSliderCaptchaValidator.java b/src/main/java/cloud/tianai/captcha/template/slider/validator/SimpleSliderCaptchaValidator.java new file mode 100644 index 0000000..6cda1f9 --- /dev/null +++ b/src/main/java/cloud/tianai/captcha/template/slider/validator/SimpleSliderCaptchaValidator.java @@ -0,0 +1,65 @@ +package cloud.tianai.captcha.template.slider.validator; + +import cloud.tianai.captcha.template.slider.util.CollectionUtils; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +public class SimpleSliderCaptchaValidator implements SliderCaptchaValidator { + + public static float DEFAULT_TOLERANT = 0.02f; + /** 容错值. */ + @Getter + @Setter + public float defaultTolerant = DEFAULT_TOLERANT; + + public SimpleSliderCaptchaValidator() { + } + + public SimpleSliderCaptchaValidator(float defaultTolerant) { + this.defaultTolerant = defaultTolerant; + } + + @Override + public float calcPercentage(int x, int bgImageWidth) { + return (float) x / bgImageWidth; + } + + @Override + public boolean checkPercentage(Float newPercentage, Float oriPercentage) { + return checkPercentage(newPercentage, oriPercentage, defaultTolerant); + } + + @Override + public boolean checkPercentage(Float newPercentage, Float oriPercentage, float tolerant) { + if (newPercentage == null || Float.isNaN(newPercentage) || Float.isInfinite(newPercentage) + || oriPercentage == null || Float.isNaN(oriPercentage) || Float.isInfinite(oriPercentage)) { + return false; + } + // 容错值 + float maxTolerant = oriPercentage + tolerant; + float minTolerant = oriPercentage - tolerant; + return newPercentage >= minTolerant && newPercentage <= maxTolerant; + } + + @Override + public boolean valid(SliderCaptchaTrack sliderCaptchaTrack, Float oriPercentage) { + Integer bgImageWidth = sliderCaptchaTrack.getBgImageWidth(); + if (bgImageWidth == null || bgImageWidth < 1) { + // 没有背景图片宽度 + return false; + } + List trackList = sliderCaptchaTrack.getTrackList(); + if (CollectionUtils.isEmpty(trackList)) { + // 没有滑动轨迹 + return false; + } + // 取最后一个滑动轨迹 + SliderCaptchaTrack.Track lastTrack = trackList.get(trackList.size() - 1); + // 计算百分比 + float calcPercentage = calcPercentage(lastTrack.getX(), bgImageWidth); + // 校验百分比 + return checkPercentage(calcPercentage, oriPercentage); + } +} diff --git a/src/main/java/cloud/tianai/captcha/template/slider/validator/SliderCaptchaTrack.java b/src/main/java/cloud/tianai/captcha/template/slider/validator/SliderCaptchaTrack.java new file mode 100644 index 0000000..01df11f --- /dev/null +++ b/src/main/java/cloud/tianai/captcha/template/slider/validator/SliderCaptchaTrack.java @@ -0,0 +1,41 @@ +package cloud.tianai.captcha.template.slider.validator; + +import cloud.tianai.captcha.template.slider.util.CollectionUtils; +import cloud.tianai.captcha.template.slider.util.ObjectUtils; +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +/** + * @Author: 天爱有情 + * @date 2022/2/17 9:23 + * @Description 滑块验证码滑动轨迹 + */ +@Data +public class SliderCaptchaTrack { + + /** 背景图片宽度. */ + private Integer bgImageWidth; + /** 背景图片高度. */ + private Integer bgImageHeight; + /** 滑块图片宽度. */ + private Integer sliderImageWidth; + /** 滑块图片高度. */ + private Integer sliderImageHeight; + /** 滑动开始时间. */ + private Date startSlidingTime; + /** 滑动结束时间. */ + private Date entSlidingTime; + /** 滑动的轨迹. */ + private List trackList; + + @Data + @AllArgsConstructor + public static class Track { + private Integer x; + private Integer y; + private Integer t; + } +} diff --git a/src/main/java/cloud/tianai/captcha/template/slider/validator/SliderCaptchaValidator.java b/src/main/java/cloud/tianai/captcha/template/slider/validator/SliderCaptchaValidator.java new file mode 100644 index 0000000..dea7696 --- /dev/null +++ b/src/main/java/cloud/tianai/captcha/template/slider/validator/SliderCaptchaValidator.java @@ -0,0 +1,46 @@ +package cloud.tianai.captcha.template.slider.validator; + +/** + * @Author: 天爱有情 + * @date 2022/2/17 10:54 + * @Description 滑块验证码校验器 + */ +public interface SliderCaptchaValidator { + + /** + * 计算滑块要背景图的百分比,基本校验 + * + * @param x 凹槽的x轴 + * @param bgImageWidth 背景图片的宽度 + * @return float + */ + float calcPercentage(int x, int bgImageWidth); + + /** + * 校验滑块百分比 + * + * @param newPercentage 用户滑动的百分比 + * @param oriPercentage 正确的滑块百分比 + * @return boolean + */ + boolean checkPercentage(Float newPercentage, Float oriPercentage); + + /** + * 校验滑块百分比 + * + * @param newPercentage 用户滑动的百分比 + * @param oriPercentage 正确的滑块百分比 + * @param tolerant 容错值 + * @return boolean + */ + boolean checkPercentage(Float newPercentage, Float oriPercentage, float tolerant); + + /** + * 校验用户滑动滑块是否正确 + * + * @param sliderCaptchaTrack 包含了滑动轨迹,展示的图片宽高,滑动时间等参数 + * @param oriPercentage 正确的滑块百分比,用作基础校验 + * @return boolean + */ + boolean valid(SliderCaptchaTrack sliderCaptchaTrack, Float oriPercentage); +}