readme 修改

Signed-off-by: SmileToCandy <smiletocandy@qq.com>
会员中心dcx
Dcx12138 8 months ago
parent 018d3613b1
commit e49504cb14

@ -4,111 +4,159 @@ import java.io.InterruptedIOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
// 定义一个抽象类,实现了 LogHandler 接口(这里假设 LogHandler 接口定义了一系列日志处理相关的方法,比如 info、error、debug 等,虽然在当前类中这些方法体暂时为空,但留给具体子类去实现具体的日志处理逻辑),
// 该抽象类主要用于处理运行时日志相关的通用逻辑,为具体的日志处理实现类提供基础框架和一些通用方法。
public abstract class AbstractRunningLogHandler implements LogHandler {
private static Method getStackTraceMethod;
private static Method getClassNameMethod;
private static Method getMethodNameMethod;
private static Method getFileNameMethod;
private static Method getLineNumberMethod;
static {
try {
Class<?>[] noArgs = null;
getStackTraceMethod = Throwable.class.getMethod("getStackTrace", noArgs);
Class<?> stackTraceElementClass = Class.forName("java.lang.StackTraceElement");
getClassNameMethod = stackTraceElementClass.getMethod("getClassName", noArgs);
getMethodNameMethod = stackTraceElementClass.getMethod("getMethodName", noArgs);
getFileNameMethod = stackTraceElementClass.getMethod("getFileName", noArgs);
getLineNumberMethod = stackTraceElementClass.getMethod("getLineNumber", noArgs);
} catch (ClassNotFoundException ex) {
LogDebug.debug("will use pre-JDK 1.4 methods to determine location.");
} catch (NoSuchMethodException ex) {
LogDebug.debug("will use pre-JDK 1.4 methods to determine location.");
}
}
/**
* classStackTraceElement
*
* @param t
* @param fqnOfCallingClass
*
* @return
*/
protected StackTraceElement getRunningStackTrace(Throwable t, String fqnOfCallingClass) {
if (getLineNumberMethod != null) {
try {
Object[] noArgs = null;
Object[] elements = (Object[]) getStackTraceMethod.invoke(t, noArgs);
for (int i = elements.length - 1; i >= 0; i--) {
String thisClass = (String) getClassNameMethod.invoke(elements[i], noArgs);
if (fqnOfCallingClass.equals(thisClass)) {
// 执行class名称
String className = fqnOfCallingClass;
// 执行方法名称
String methodName = (String) getMethodNameMethod.invoke(elements[i], noArgs);
// 执行class文件名称
String fileName = (String) getFileNameMethod.invoke(elements[i], noArgs);
// 执行到行号
int lineNumber = ((Integer) getLineNumberMethod.invoke(elements[i], noArgs)).intValue();
return new StackTraceElement(className, methodName, fileName, lineNumber);
}
}
} catch (IllegalAccessException ex) {
LogDebug.debug("failed using JDK 1.4 methods", ex);
} catch (InvocationTargetException ex) {
if (ex.getTargetException() instanceof InterruptedException
|| ex.getTargetException() instanceof InterruptedIOException) {
Thread.currentThread().interrupt();
}
LogDebug.debug("failed using JDK 1.4 methods", ex);
} catch (RuntimeException ex) {
LogDebug.debug("failed using JDK 1.4 methods", ex);
}
}
return this.createDefaultStackTrace();
}
/**
* StackTraceElement
*
* @return
*/
private StackTraceElement createDefaultStackTrace() {
return new StackTraceElement(this.getClass().getName(), "log", this.getClass().getName(), 0);
}
@Override
public void info(String msg, String fqnOfCallingClass) {
}
@Override
public void info(String msg, Throwable t, String fqnOfCallingClass) {
}
@Override
public void error(String msg, String fqnOfCallingClass) {
}
@Override
public void error(String msg, Throwable t, String fqnOfCallingClass) {
}
@Override
public void debug(String msg, String fqnOfCallingClass) {
}
@Override
public void debug(String msg, Throwable t, String fqnOfCallingClass) {
}
@Override
public void warning(String msg, String fqnOfCallingClass) {
}
@Override
public void warning(String msg, Throwable t, String fqnOfCallingClass) {
}
}
// 通过反射获取的用于获取堆栈跟踪信息的方法对象,对应 Throwable 类中的 getStackTrace 方法,
// 后续会利用这个方法来获取异常发生时的调用栈信息,以便确定日志相关的位置信息等,初始化为 null在静态代码块中进行初始化赋值。
private static Method getStackTraceMethod;
// 通过反射获取的用于获取类名的方法对象,对应 java.lang.StackTraceElement 类中的 getClassName 方法,
// 用于从堆栈跟踪元素中提取类名信息,同样初始化为 null在静态代码块里完成初始化。
private static Method getClassNameMethod;
// 通过反射获取的用于获取方法名的方法对象,对应 java.lang.StackTraceElement 类中的 getMethodName 方法,
// 用来从堆栈跟踪元素里获取具体执行的方法名称,初始值为 null在静态块中初始化。
private static Method getMethodNameMethod;
// 通过反射获取的用于获取文件名的方法对象,对应 java.lang.StackTraceElement 类中的 getFileName 方法,
// 可以从堆栈跟踪元素中获取对应的源文件名信息,初始设置为 null后续在静态代码块里赋值。
private static Method getFileNameMethod;
// 通过反射获取的用于获取行号的方法对象,对应 java.lang.StackTraceElement 类中的 getLineNumber 方法,
// 目的是从堆栈跟踪元素中获取代码执行到的具体行号信息,开始时为 null在静态代码块中进行初始化操作。
private static Method getLineNumberMethod;
// 静态代码块,在类加载时执行,主要用于通过反射机制获取一些后续用于处理堆栈跟踪信息相关的方法对象,
// 如果获取过程中出现异常(比如类不存在或者方法不存在等情况),会打印相应的提示信息,表示将使用 JDK 1.4 之前的方式来确定位置信息(虽然这里并没有展示具体是什么替代方式,可能由具体子类去处理或者在其他相关逻辑中体现)。
static {
try {
// 创建一个空的 Class<?> 数组,表示无参数的情况,用于后续获取方法时指定参数类型,因为这里要获取的几个方法都是无参数的方法。
Class<?>[] noArgs = null;
// 通过反射获取 Throwable 类的 getStackTrace 方法对象,该方法用于获取异常的堆栈跟踪信息,后续可以基于这个信息进一步提取类、方法、文件、行号等详细信息,
getStackTraceMethod = Throwable.class.getMethod("getStackTrace", noArgs);
// 通过反射获取 java.lang.StackTraceElement 类的 Class 对象,因为后续要获取这个类中的几个方法对象,所以先获取其 Class 类型。
Class<?> stackTraceElementClass = Class.forName("java.lang.StackTraceElement");
// 通过反射获取 StackTraceElement 类的 getClassName 方法对象,用于从堆栈跟踪元素中获取类名信息。
getClassNameMethod = stackTraceElementClass.getMethod("getClassName", noArgs);
// 通过反射获取 StackTraceElement 类的 getMethodName 方法对象,以便从堆栈跟踪元素中获取方法名信息。
getMethodNameMethod = stackTraceElementClass.getMethod("getMethodName", noArgs);
// 通过反射获取 StackTraceElement 类的 getFileName 方法对象,用于从堆栈跟踪元素中获取文件名信息。
getFileNameMethod = stackTraceElementClass.getMethod("getFileName", noArgs);
// 通过反射获取 StackTraceElement 类的 getLineNumber 方法对象,使得可以从堆栈跟踪元素中获取行号信息。
getLineNumberMethod = stackTraceElementClass.getMethod("getLineNumber", noArgs);
} catch (ClassNotFoundException ex) {
// 如果出现 ClassNotFoundException 异常,意味着找不到对应的类(比如这里的 StackTraceElement 类在某些特殊环境下不存在或者加载失败),
// 则打印提示信息,表示将使用 JDK 1.4 之前的方法来确定位置信息,这里只是简单打印提示,具体的替代逻辑可能在其他地方或者由子类去实现。
LogDebug.debug("will use pre-JDK 1.4 methods to determine location.");
} catch (NoSuchMethodException ex) {
// 如果出现 NoSuchMethodException 异常,说明要获取的某个方法(比如上面获取的几个 StackTraceElement 类中的方法)不存在,
// 同样打印提示信息,表示将使用 JDK 1.4 之前的方法来确定位置信息,后续具体怎么处理依赖于具体的业务逻辑或者子类实现。
LogDebug.debug("will use pre-JDK 1.4 methods to determine location.");
}
}
/**
* classStackTraceElement
* tfqnOfCallingClassStackTraceElement
* StackTraceElement
* 访 StackTraceElement
*
* @param t
* @param fqnOfCallingClass
*
* @return StackTraceElement StackTraceElement
*/
protected StackTraceElement getRunningStackTrace(Throwable t, String fqnOfCallingClass) {
if (getLineNumberMethod!= null) {
try {
// 创建一个空的 Object 数组,表示无参数的情况,用于后续通过反射调用方法时作为参数传递,因为这里要调用的几个方法都是无参数的方法。
Object[] noArgs = null;
// 通过反射调用异常对象t的 getStackTrace 方法,获取其堆栈跟踪信息,返回的是一个 Object 数组(实际上是 StackTraceElement 数组,但通过反射获取时以 Object 数组形式返回),
Object[] elements = (Object[]) getStackTraceMethod.invoke(t, noArgs);
// 从后往前遍历堆栈跟踪元素数组,因为通常最新的调用信息在数组的末尾,通过倒序遍历可以更快地找到与指定调用类相关的元素。
for (int i = elements.length - 1; i >= 0; i--) {
// 通过反射调用当前堆栈跟踪元素elements[i])的 getClassName 方法,获取其类名信息,并转换为 String 类型,用于与传入的调用类全限定名进行比对。
String thisClass = (String) getClassNameMethod.invoke(elements[i], noArgs);
if (fqnOfCallingClass.equals(thisClass)) {
// 如果找到了与传入的调用类全限定名匹配的堆栈跟踪元素,则获取其相关信息来构建一个 StackTraceElement 对象并返回。
// 获取类名这里直接使用传入的调用类全限定名fqnOfCallingClass作为类名也可以根据实际情况进行进一步处理比如提取简单类名等不过这里直接使用了传入的全限定名。
String className = fqnOfCallingClass;
// 通过反射调用当前堆栈跟踪元素elements[i])的 getMethodName 方法,获取其方法名信息,并转换为 String 类型,用于设置到要返回的 StackTraceElement 对象中。
String methodName = (String) getMethodNameMethod.invoke(elements[i], noArgs);
// 通过反射调用当前堆栈跟踪元素elements[i])的 getFileName 方法,获取其文件名信息,并转换为 String 类型,用于设置到要返回的 StackTraceElement 对象中。
String fileName = (String) getFileNameMethod.invoke(elements[i], noArgs);
// 通过反射调用当前堆栈跟踪元素elements[i])的 getLineNumber 方法,获取其行号信息,先转换为 Integer 类型,再获取其 int 值,用于设置到要返回的 StackTraceElement 对象中。
int lineNumber = ((Integer) getLineNumberMethod.invoke(elements[i], noArgs)).intValue();
return new StackTraceElement(className, methodName, fileName, lineNumber);
}
}
} catch (IllegalAccessException ex) {
// 如果出现 IllegalAccessException 异常,说明在通过反射调用方法时发生了非法访问的情况(比如方法不可访问等原因),
// 则打印调试信息,表示使用 JDK 1.4 相关方法失败,并将异常信息传递进去,方便后续排查问题,不过这里只是简单打印调试信息,具体的处理逻辑可能可以进一步完善。
LogDebug.debug("failed using JDK 1.4 methods", ex);
} catch (InvocationTargetException ex) {
// 如果出现 InvocationTargetException 异常,说明在通过反射调用方法时,被调用方法内部抛出了异常,需要进一步判断这个内部抛出的异常类型。
if (ex.getTargetException() instanceof InterruptedException
|| ex.getTargetException() instanceof InterruptedIOException) {
// 如果内部抛出的异常是 InterruptedException 或者 InterruptedIOException表示线程被中断相关的情况
// 则重新设置当前线程的中断状态,以便让调用者知道线程被中断了,后续可以根据这个中断状态进行相应的处理,符合 Java 中处理线程中断的规范。
Thread.currentThread().interrupt();
}
// 无论内部抛出的异常具体是什么类型(除了上面特殊处理的中断相关异常外),都打印调试信息,表示使用 JDK 1.4 相关方法失败,并将异常信息传递进去,方便后续排查问题。
LogDebug.debug("failed using JDK 1.4 methods", ex);
} catch (RuntimeException ex) {
// 如果出现 RuntimeException 异常,同样打印调试信息,表示使用 JDK 1.4 相关方法失败,并将异常信息传递进去,方便后续排查问题,
// 这里统一处理了各种运行时异常情况,保证方法在出现异常时能进行一定的错误提示,避免程序因为未处理的异常而崩溃。
LogDebug.debug("failed using JDK 1.4 methods", ex);
}
}
// 如果在获取指定的 StackTraceElement 过程中出现了各种问题(比如反射方法获取失败或者在提取信息时出现异常等情况),则调用 createDefaultStackTrace 方法创建并返回一个默认的 StackTraceElement 对象。
return this.createDefaultStackTrace();
}
/**
* StackTraceElement
* StackTraceElement 使
* StackTraceElement this.getClass().getName() "log"this.getClass().getName() 0
* 使
*
* @return StackTraceElement
*/
private StackTraceElement createDefaultStackTrace() {
return new StackTraceElement(this.getClass().getName(), "log", this.getClass().getName(), 0);
}
// 以下是实现 LogHandler 接口的各个日志处理方法,在当前抽象类中这些方法体为空,因为具体的日志处理逻辑(比如将日志信息输出到哪里、如何格式化等)需要由具体的子类根据不同的业务需求去实现,
// 这里只是定义了方法签名,遵循了接口的规范,保证了抽象类作为日志处理的基础框架,子类可以针对性地重写这些方法来完成实际的日志处理操作。
@Override
public void info(String msg, String fqnOfCallingClass) {
}
@Override
public void info(String msg, Throwable t, String fqnOfCallingClass) {
}
@Override
public void error(String msg, String fqnOfCallingClass) {
}
@Override
public void error(String msg, Throwable t, String fqnOfCallingClass) {
}
@Override
public void debug(String msg, String fqnOfCallingClass) {
}
@Override
public void debug(String msg, Throwable t, String fqnOfCallingClass) {
}
@Override
public void warning(String msg, String fqnOfCallingClass) {
}
@Override
public void warning(String msg, Throwable t, String fqnOfCallingClass) {
}
}

@ -5,51 +5,86 @@ import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
// 这个类提供了一系列与日期处理相关的实用方法,用于日期的格式化、解析、日期差值计算以及日期增减等操作,方便在项目中对日期进行各种常见的处理。
public class DateUtils {
// 定义默认的日期格式字符串,格式为 "yyyy-MM-dd",用于在一些方法中作为默认的日期格式化样式,方便统一处理日期的显示格式。
public static final String DEFAULT_PATTERN = "yyyy-MM-dd";
/**
*
* daypattern
* 使DEFAULT_PATTERN
*
* @param day
* @param pattern "yyyy-MM-dd HH:mm:ss"
* @return
*/
public static String getOneDayFromNow(int day, String pattern) {
// 获取一个 Calendar 实例,它用于对日期进行各种操作,比如日期的增减、获取日期的各个字段等,这里获取的是当前系统时间对应的 Calendar 实例。
Calendar cal = Calendar.getInstance();
// 将 Calendar 的时间设置为当前日期(通过传入一个 Date 对象表示当前时间,这里直接使用 new Date() 获取当前时间),以便后续在此基础上进行日期偏移操作。
cal.setTime(new Date());
// 在当前日期基础上增加或减少指定的天数,通过调用 add 方法并指定 Calendar.DAY_OF_MONTH 字段以及要偏移的天数day来实现日期的偏移操作。
cal.add(Calendar.DAY_OF_MONTH, day);
// 创建一个 SimpleDateFormat 对象用于按照指定的格式pattern对日期进行格式化操作将日期转换为字符串形式。
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
// 使用 SimpleDateFormat 的 format 方法将偏移后的日期cal.getTime() 返回的是一个 Date 对象,表示偏移后的日期时间)格式化为字符串,并返回该格式化后的日期字符串。
return sdf.format(cal.getTime());
}
/**
* 使
* getOneDayFromNow day getOneDayFromNow 使DEFAULT_PATTERN
*
* @param day
* @return "yyyy-MM-dd"
*/
public static String getOneDayFromNow(int day) {
return DateUtils.getOneDayFromNow(day, DEFAULT_PATTERN);
}
/**
*
*
* @param smdate
*
* @param bdate
*
* @return
* @throws ParseException
* @throws java.text.ParseException
* "yyyy-MM-dd" Date
* smdate bdate
*
* @param smdate Date
* @param bdate Date
* @return
* @throws ParseException
*/
public static int daysBetween(Date smdate, Date bdate)
throws ParseException {
public static int daysBetween(Date smdate, Date bdate) throws ParseException {
// 创建一个 SimpleDateFormat 对象,指定日期格式为 "yyyy-MM-dd",用于统一将传入的日期格式化为该格式后再进行后续的时间戳计算等操作,确保日期格式的一致性。
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 先将传入的较小日期smdate按照指定格式进行格式化再解析为 Date 对象,这样做的目的是去除日期中的时间部分(如果有的话),只保留日期部分,保证计算天数的准确性。
smdate = sdf.parse(sdf.format(smdate));
// 同样地对较大日期bdate进行格式化和解析操作去除时间部分只保留日期部分以便后续基于日期进行天数差值的计算。
bdate = sdf.parse(sdf.format(bdate));
// 获取一个 Calendar 实例用于获取日期对应的时间戳等操作这里以较小日期smdate为基础进行设置。
Calendar cal = Calendar.getInstance();
cal.setTime(smdate);
// 获取较小日期对应的时间戳(以毫秒为单位),通过 Calendar 的getTimeInMillis 方法获取,表示从 1970 年 1 月 1 日 00:00:00 UTC 到该日期时间的毫秒数。
long time1 = cal.getTimeInMillis();
// 重新设置 Calendar 的时间为较大日期bdate以便获取其对应的时间戳。
cal.setTime(bdate);
// 获取较大日期对应的时间戳(以毫秒为单位)。
long time2 = cal.getTimeInMillis();
// 计算两个日期时间戳的差值以毫秒为单位然后除以一天对应的毫秒数1000 * 3600 * 24即 1000 毫秒/秒 * 3600 秒/小时 * 24 小时/天),得到相差的天数,这里差值是一个 long 类型。
long between_days = (time2 - time1) / (1000 * 3600 * 24);
// 将计算得到的相差天数long 类型)转换为整数类型并返回,通过将 long 值转换为 String 再解析为 Integer 的方式实现,虽然这种方式略显繁琐,但能确保类型转换的正确性。
return Integer.parseInt(String.valueOf(between_days));
}
// main 方法,用于简单测试 daysBetween 方法,不过这里传入的参数都是 null实际运行时会抛出 NullPointerException
// 正常使用时应该传入有效的 Date 对象参数进行测试或者在其他合适的地方调用 daysBetween 方法并传入正确的日期参数来计算日期差值。
public static void main(String[] args) throws ParseException {
System.out.println(daysBetween(null, null));
}
// 定义一系列日期格式的常量字符串,方便在不同的日期格式化场景中直接使用,涵盖了英文简写、英文全称、精确到毫秒的完整时间以及对应的中文简写、中文全称、精确到毫秒的完整中文时间等多种格式。
/**
* 2010-12-01
*/
@ -77,6 +112,7 @@ public class DateUtils {
/**
* date pattern
* FORMAT_LONG "yyyy-MM-dd HH:mm:ss"使
*/
public static String getDatePattern() {
return FORMAT_LONG;
@ -84,8 +120,9 @@ public class DateUtils {
/**
*
*
* @return
* 使 getDatePattern "yyyy-MM-dd HH:mm:ss"new Date()
*
* @return
*/
public static String getNow() {
return format(new Date());
@ -93,9 +130,10 @@ public class DateUtils {
/**
*
*
* @param format
* @return
* formatnew Date() 便
*
* @param format "yyyy-MM-dd""yyyy年MM月dd日"
* @return
*/
public static String getNow(String format) {
return format(new Date(), format);
@ -103,9 +141,11 @@ public class DateUtils {
/**
* 使
*
* @param date
* @return
* 使 getDatePattern "yyyy-MM-dd HH:mm:ss"date
* null
*
* @param date Date null
* @return date null
*/
public static String format(Date date) {
return format(date, getDatePattern());
@ -113,17 +153,19 @@ public class DateUtils {
/**
* 使
*
* @param date
*
* @param pattern
*
* @return
* patterndate
* null
*
* @param date Date null
* @param pattern
* @return date null
*/
public static String format(Date date, String pattern) {
String returnValue = "";
if (date != null) {
if (date!= null) {
// 创建一个 SimpleDateFormat 对象按照用户指定的格式pattern进行初始化用于对日期进行格式化操作。
SimpleDateFormat df = new SimpleDateFormat(pattern);
// 使用 SimpleDateFormat 的 format 方法将日期date格式化为字符串并赋值给 returnValue以便后续返回。
returnValue = df.format(date);
}
return (returnValue);
@ -131,12 +173,12 @@ public class DateUtils {
/**
* 使
*
* @param timestamp
*
* @param pattern
*
* @return
* timestamp long Date
* pattern便
*
* @param timestamp 1970 1 1 00:00:00 UTC "1612345678901"
* @param pattern
* @return
*/
public static String format(String timestamp, String pattern) {
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
@ -145,10 +187,11 @@ public class DateUtils {
/**
* 使
*
* @param strDate
*
* @return
* strDate getDatePattern "yyyy-MM-dd HH:mm:ss"
* Date ParseException null
*
* @param strDate
* @return Date null
*/
public static Date parse(String strDate) {
return parse(strDate, getDatePattern());
@ -156,12 +199,12 @@ public class DateUtils {
/**
* 使
*
* @param strDate
*
* @param pattern
*
* @return
* patternstrDate Date
* ParseException null
*
* @param strDate
* @param pattern
* @return Date null
*/
public static Date parse(String strDate, String pattern) {
SimpleDateFormat df = new SimpleDateFormat(pattern);
@ -175,12 +218,11 @@ public class DateUtils {
/**
*
*
* @param date
*
* @param n
*
* @return
* Calendar daten便
*
* @param date Date
* @param n
* @return
*/
public static Date addMonth(Date date, int n) {
Calendar cal = Calendar.getInstance();
@ -191,12 +233,11 @@ public class DateUtils {
/**
*
*
* @param date
*
* @param n
*
* @return
* Calendar daten
*
* @param date Date
* @param n
* @return
*/
public static Date addDay(Date date, int n) {
Calendar cal = Calendar.getInstance();
@ -207,6 +248,8 @@ public class DateUtils {
/**
*
* 使FORMAT_FULL "yyyy-MM-dd HH:mm:ss.S" Calendar.getInstance Calendar Date
* 便
*/
public static String getTimeString() {
SimpleDateFormat df = new SimpleDateFormat(FORMAT_FULL);
@ -216,59 +259,17 @@ public class DateUtils {
/**
*
*
* @param date
*
* @return
* date format 使 4
* 4
*
* @param date Date
* @return "2024"
*/
public static String getYear(Date date) {
return format(date).substring(0, 4);
}
/**
*
*
* @param date
*
* @return
*/
public static int countDays(String date) {
long t = Calendar.getInstance().getTime().getTime();
Calendar c = Calendar.getInstance();
c.setTime(parse(date));
long t1 = c.getTime().getTime();
return (int) (t / 1000 - t1 / 1000) / 3600 / 24;
}
/**
*
*
* @param date
*
* @param format
*
* @return
*/
public static int countDays(String date, String format) {
long t = Calendar.getInstance().getTime().getTime();
Calendar c = Calendar.getInstance();
c.setTime(parse(date, format));
long t1 = c.getTime().getTime();
return (int) (t / 1000 - t1 / 1000) / 3600 / 24;
}
public static String timeFormat(Date date, String format, Boolean flag, int beforeDay, int nowDay) {
if(date == null) {
date = new Date();
}
Calendar cal = Calendar.getInstance();
cal.setTime(date);
if(flag) {
cal.add(Calendar.DATE,-30);
} else {
cal.add(Calendar.DATE,0);
}
SimpleDateFormat sdf = new SimpleDateFormat(format);
return sdf.format(cal.getTime());
}
}
/**
*
* date
*

@ -1,56 +1,86 @@
package com.tamguo.util;
public class IdGen
{
// IdGen类用于生成唯一标识符ID很可能基于某种分布式唯一ID生成算法从代码结构推测可能类似雪花算法的思路
// 它可以根据配置的参数如工作节点ID、数据中心ID等以及一些规则来生成具有唯一性的ID并且支持添加自定义后缀以及指定后缀位置等功能。
public class IdGen {
// 工作节点ID用于在分布式环境中区分不同的工作节点不同的工作节点生成的ID应该具有唯一性通过位运算等规则来限定其取值范围。
private long workerId;
// 数据中心ID用于区分不同的数据中心同样在分布式系统架构里参与ID生成的唯一性保证也有相应的取值范围限制。
private long datacenterId;
// 序列号用于在同一毫秒内对生成的ID进行区分在同一时间同一毫秒不同的序列号保证生成的ID各不相同初始值为0。
private long sequence = 0L;
// 起始时间戳单位毫秒通常是一个固定的历史时间点作为ID生成算法中的时间基线用于计算时间戳差值等操作以保证生成的ID在时间维度上的唯一性和有序性。
private long twepoch = 1288834974657L;
// 用于表示 workerId 所占的位数,通过这些位数设定来确定 workerId 的取值范围当前设置为5位。
private long workerIdBits = 5L;
// 表示 datacenterId 所占的位数,以此限定 datacenterId 的取值范围同样设置为5位。
private long datacenterIdBits = 5L;
// 根据 workerIdBits 计算出的 workerId 的最大允许值,通过位运算得出,确保 workerId 在合理的取值范围内避免超出导致ID生成出现问题。
private long maxWorkerId = -1L ^ (-1L << workerIdBits);
// 依据 datacenterIdBits 计算得到的 datacenterId 的最大取值,通过位运算来限定其范围,保证 datacenterId 的合法性。
private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
// 序列号所占的位数决定了在同一毫秒内可生成的不同ID的数量这里设置为12位。
private long sequenceBits = 12L;
// 在生成ID时用于将 sequence序列号左移的位数其值等于 sequenceBits用于在ID的二进制表示中为 sequence 预留合适的位置。
private long workerIdShift = sequenceBits;
// 在生成ID时用于将 datacenterId 左移的位数,计算方式是 sequenceBits 与 workerIdBits 之和,确定 datacenterId 在ID二进制表示中的位置。
private long datacenterIdShift = sequenceBits + workerIdBits;
// 在生成ID时用于将时间戳timestamp左移的位数通过 sequenceBits、workerIdBits 和 datacenterIdBits 三者相加得出为时间戳在ID二进制表示中预留正确的位置。
private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
// 序列号的掩码,通过位运算生成,用于在同一毫秒内对 sequence 进行循环计数时的边界判断,保证序列号在规定的位数范围内循环使用。
private long sequenceMask = -1L ^ (-1L << sequenceBits);
// 记录上一次生成ID时的时间戳单位毫秒用于和当前时间戳对比判断是否在同一毫秒内以及处理时间回拨等情况初始值为 -1L。
private long lastTimestamp = -1L;
// 自定义的后缀字符串可用于给生成的ID添加额外的标识信息方便在业务中根据后缀进行分类、识别等操作初始值为 null。
private String suffix;
// 布尔值用于指示后缀添加的位置true 表示添加在ID的前缀位置false 表示添加在ID的后缀位置初始值为 false。
private boolean flag;
// 内部静态类,用于实现单例模式,保证整个应用程序中只有一个 IdGen 实例(如果使用默认无参构造函数获取实例的情况),
// 通过类加载机制保证线程安全地创建和获取这个唯一实例避免多次实例化造成的资源浪费以及保证ID生成规则的一致性。
private static class IdGenHolder {
// 创建并持有一个静态的最终 IdGen 实例,在类加载时就会初始化这个实例,并且只会初始化一次,后续通过 get 方法获取的都是这个唯一实例。
private static final IdGen instance = new IdGen();
}
public static IdGen get(){
// 静态方法,用于获取单例的 IdGen 实例使用默认配置的情况外部类可以通过这个方法方便地获取到用于生成ID的唯一实例遵循单例模式的获取实例方式。
public static IdGen get() {
return IdGenHolder.instance;
}
/**
* id
* @param suffix
* @param flag true: false:
* @return
* IdGen suffixflagID
* null 0 IdGen ID
*
* @param suffix ID使ID
* @param flag trueIDfalseIDID
* @return IdGen
*/
public static IdGen get(String suffix,boolean flag){
if(suffix == null || suffix.trim().length() == 0)
public static IdGen get(String suffix, boolean flag) {
if (suffix == null || suffix.trim().length() == 0)
return IdGenHolder.instance;
else{
return new IdGen(suffix,flag);
else {
return new IdGen(suffix, flag);
}
}
// 默认的无参构造函数调用另一个带有两个参数均为0L的构造函数来初始化对象给 workerId 和 datacenterId 都赋予初始值0适用于一些简单场景或者后续有默认配置的情况。
public IdGen() {
this(0L, 0L);
}
public IdGen(String suffix,boolean flag){
// 构造函数,用于创建一个带有自定义后缀和后缀位置标志的 IdGen 实例接收后缀字符串suffix和后缀位置标志flag作为参数
// 将传入的参数赋值给相应的成员变量以便后续在生成ID时根据这些配置来添加后缀。
public IdGen(String suffix, boolean flag) {
this.suffix = suffix;
this.flag = flag;
}
// 构造函数,用于创建一个 IdGen 实例并传入工作节点IDworkerId和数据中心IDdatacenterId参数
// 在创建实例时会对传入的 workerId 和 datacenterId 进行合法性检查确保它们在预先设定的取值范围内通过与最大允许值比较以及判断是否小于0来验证
// 如果超出范围则抛出 IllegalArgumentException 异常保证ID生成参数的有效性避免生成不符合规则的ID。
public IdGen(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
@ -62,28 +92,57 @@ public class IdGen
this.datacenterId = datacenterId;
}
/**
* ID
* IDIDIDID
* IDID
*
* @return ID使
*/
public synchronized String nextId() {
// 获取当前时间戳(单位:毫秒),通过调用 timeGen 方法来获取这个时间戳是生成ID的重要依据之一用于区分不同时间生成的ID保证时间维度上的唯一性。
long timestamp = timeGen();
// 判断当前时间戳是否小于上一次生成ID的时间戳lastTimestamp如果小于则说明出现了时间回拨的情况比如系统时间被手动调整回退了
// 这种情况下为了保证ID的唯一性和时间顺序性会抛出 RuntimeException 异常拒绝生成ID因为时间回拨可能导致ID重复等问题。
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format(
"Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
// 如果当前时间戳与上一次生成ID的时间戳相等说明在同一毫秒内需要对序列号sequence进行处理通过自增并与 sequenceMask 进行按位与操作,
// 实现序列号在规定的位数范围内循环递增保证在同一毫秒内不同调用生成不同的ID如果序列号达到最大值sequenceMask 对应的最大值即循环一轮回到0
// 则需要等待到下一个毫秒再生成ID通过调用 tilNextMillis 方法来获取下一个可用的时间戳。
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
// 如果当前时间戳与上一次的不同说明进入了新的一毫秒此时将序列号重置为0重新开始在这一毫秒内的ID生成计数。
sequence = 0L;
}
// 更新 lastTimestamp 为当前的时间戳为下一次生成ID时判断时间戳情况做准备确保每次生成ID都能正确处理时间相关的逻辑。
lastTimestamp = timestamp;
// 通过位运算组合时间戳、数据中心ID、工作节点ID和序列号等信息构建出一个唯一的长整型数值serialNumber这个数值就是ID的核心部分
// 其位运算的顺序和位移的位数都是根据前面定义的各个参数(如 timestampLeftShift、datacenterIdShift、workerIdShift 等来确定的保证了各个部分在ID中的正确位置和唯一性体现。
long serialNumber = ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift)
| (workerId << workerIdShift) | sequence;
return (suffix == null || suffix.trim().length() == 0) ? serialNumber+"" : (flag ? (new StringBuffer()).append(suffix).append(serialNumber).toString() : (new StringBuffer()).append(serialNumber).append(suffix).toString());
// 根据是否有自定义后缀suffix以及后缀添加位置标志flag来决定最终返回的ID字符串形式
// 如果后缀为空或者去除空格后长度为0则直接将 serialNumber 转换为字符串返回;
// 如果 flag 为 true表示后缀添加在前面则通过 StringBuffer 先拼接后缀再拼接 serialNumber 并转换为字符串返回;
// 如果 flag 为 false表示后缀添加在后面则通过 StringBuffer 先拼接 serialNumber 再拼接后缀并转换为字符串返回从而生成符合要求的带有后缀或不带后缀的唯一ID字符串。
return (suffix == null || suffix.trim().length() == 0)? serialNumber + "" : (flag? (new StringBuffer()).append(suffix).append(serialNumber).toString() : (new StringBuffer()).append(serialNumber).append(suffix).toString());
}
/**
*
* IDID
* timeGen lastTimestamp lastTimestamp
*
* @param lastTimestamp IDID
* @return lastTimestamp IDID
*/
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
@ -92,8 +151,14 @@ public class IdGen
return timestamp;
}
/**
*
* System.currentTimeMillis ID
*
*
* @return 19701100:00:00 UTCID
*/
protected long timeGen() {
return System.currentTimeMillis();
}
}
}

@ -6,9 +6,21 @@ import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
// ObjectUtil类继承自SerializeTranscoder这里假设SerializeTranscoder是一个与序列化、反序列化相关的抽象类或者接口定义了一些序列化和反序列化相关的方法签名
// 主要提供了对象序列化、反序列化以及对象相等性比较的实用功能,方便在需要处理对象的持久化存储、网络传输以及对象比较等场景中使用。
public class ObjectUtil extends SerializeTranscoder {
/**
*
* value null NullPointerException
* null I/O IllegalArgumentException 便
*
* @param value null Serializable Java
* @return 便
*/
@Override
public byte[] serialize(Object value) {
// 首先检查传入的对象是否为 null如果是则抛出异常提示不能序列化 null 对象。
if (value == null) {
throw new NullPointerException("Can't serialize null");
}
@ -16,54 +28,98 @@ public class ObjectUtil extends SerializeTranscoder {
ByteArrayOutputStream bos = null;
ObjectOutputStream os = null;
try {
// 创建一个 ByteArrayOutputStream 对象,它用于在内存中缓冲要序列化的数据,后续可以方便地转换为字节数组获取序列化后的内容。
bos = new ByteArrayOutputStream();
// 创建一个 ObjectOutputStream 对象,它用于将对象转换为字节流进行序列化,将其关联到 ByteArrayOutputStream这样写入 ObjectOutputStream 的数据最终会存储到 ByteArrayOutputStream 中。
os = new ObjectOutputStream(bos);
// 通过 ObjectOutputStream 的 writeObject 方法将传入的对象value进行序列化并写入到流中开始实际的序列化操作将对象的状态信息转换为字节形式。
os.writeObject(value);
// 关闭 ObjectOutputStream释放相关资源注意关闭流时可能会抛出 IOException这里需要进行异常处理不过在 finally 块中还会进一步确保流的关闭操作。
os.close();
// 关闭 ByteArrayOutputStream同样是释放资源也可能抛出 IOException同样在 finally 块中再次保障其能正确关闭。
bos.close();
// 将 ByteArrayOutputStream 中的字节数据获取出来,赋值给 result 变量,这个就是最终序列化后的字节数组,代表了传入对象序列化后的结果。
result = bos.toByteArray();
} catch (IOException e) {
// 如果在序列化过程中出现 I/O 异常(比如流写入失败、关闭流出错等情况),则抛出 IllegalArgumentException 异常,并将原始的 IOException 作为原因传递进去,
// 提示出现了不可序列化的对象问题,同时方便调用者获取详细的异常信息来排查到底是哪里的序列化操作出现了错误。
throw new IllegalArgumentException("Non-serializable object", e);
} finally {
// 调用 close 方法(这里假设是类中定义的用于关闭流的辅助方法,确保流能正确关闭并释放资源,避免资源泄漏)关闭 ObjectOutputStream无论前面是否出现异常都要保证流能正确关闭。
close(os);
// 同样调用 close 方法关闭 ByteArrayOutputStream保障资源的正确释放。
close(bos);
}
return result;
}
/**
*
* in null null
* I/O ClassNotFoundException
* null
*
* @param in ClassNotFoundException
* @return null
*/
@Override
public Object deserialize(byte[] in) {
Object result = null;
ByteArrayInputStream bis = null;
ObjectInputStream is = null;
try {
if (in != null) {
// 首先判断传入的字节数组是否为 null如果是则直接返回 null不进行后续的反序列化操作因为没有有效的数据可供反序列化。
if (in!= null) {
// 创建一个 ByteArrayInputStream 对象,它用于从字节数组中读取数据,作为反序列化的数据源,将传入的字节数组作为参数传入,初始化流对象。
bis = new ByteArrayInputStream(in);
// 创建一个 ObjectInputStream 对象,它用于从字节流中读取并还原对象,将其关联到 ByteArrayInputStream以便从字节数组对应的流中读取数据进行反序列化操作。
is = new ObjectInputStream(bis);
// 通过 ObjectInputStream 的 readObject 方法进行反序列化操作,从流中读取数据并尝试还原为对应的对象,将还原后的对象赋值给 result 变量。
result = is.readObject();
// 关闭 ObjectInputStream释放相关资源同样关闭流时可能出现 IOException需要进行异常处理并且在 finally 块中会再次确保流的关闭。
is.close();
// 关闭 ByteArrayInputStream释放资源也可能出现 IOException同样在 finally 块中保障其正确关闭。
bis.close();
}
} catch (IOException e) {
// 如果在反序列化过程中出现 I/O 异常(比如流读取失败、关闭流出错等情况),则打印异常堆栈信息进行简单的错误提示,不过当前方法仍会继续执行并返回 null
// 这种处理方式可能导致反序列化失败后调用者难以察觉具体问题,可根据业务实际情况优化,比如向上抛出异常让调用者处理等。
e.printStackTrace();
} catch (ClassNotFoundException e) {
// 如果出现 ClassNotFoundException 异常,意味着在反序列化时找不到对应的类定义(可能是类所在的 JAR 包未引入、类名更改等原因),同样打印异常堆栈信息进行简单提示,
// 方法继续执行并返回 null可根据实际需求改进处理方式比如更明确地告知调用者类找不到导致反序列化失败等情况。
e.printStackTrace();
} finally {
// 调用 close 方法关闭 ObjectInputStream确保资源能正确释放无论前面是否出现异常都要执行关闭操作避免资源泄漏。
close(is);
// 调用 close 方法关闭 ByteArrayInputStream保障资源的正确关闭和释放。
close(bis);
}
return result;
}
/**
*
* o1 == o2 true
* null null false equals equals
* Java
*
* @param o1 null
* @param o2 null
* @return true false
*/
public static boolean equals(Object o1, Object o2) {
// 首先判断两个对象引用是否相同,如果是则表示它们是同一个对象,直接返回 true说明对象相等。
if (o1 == o2) {
return true;
} else if (o1 == null || o2 == null) {
// 如果两个对象引用不相同,接着判断是否有一个对象为 null如果有则返回 false表示两个对象不相等因为只有两个非 null 对象才有进一步比较内容相等的必要。
return false;
} else {
// 如果两个对象都不为 null且引用不同那么调用对象的 equals 方法(这里要求对象所属类正确重写了 equals 方法,遵循 Java 中 equals 方法的重写规范)来比较对象内容是否相等,
// 返回相应的比较结果,以此确定两个对象是否真正相等。
return o1.equals(o2);
}
}
}
}

@ -5,85 +5,116 @@ import java.util.List;
import com.baomidou.mybatisplus.plugins.Page;
// PageUtils类主要用于对分页相关的数据进行处理和封装将MyBatis Plus中Page对象里的分页信息以及对应的数据进行提取、整理
// 并提供了方便获取和设置分页相关属性(如是否显示上一页/下一页按钮、页码列表、当前页码、总页数、总数量等)的方法,方便在前端展示分页信息以及进行分页交互操作。
public class PageUtils {
// 是否下一页按钮
// 用于标识是否显示下一页按钮,初始值为 false后续会根据实际的分页情况当前页是否小于总页数来设置其值用于前端判断是否展示下一页的操作按钮。
private Boolean isShowNextBtn = false;
// 是否上一页按钮
// 用于标识是否显示上一页按钮,初始值为 false同样会依据当前页与总页数等分页情况来确定其值方便前端判断是否展示上一页的操作按钮。
private Boolean isShowPreBtn = false;
// 当前页
// 存储当前页码的字符串表示形式会从传入的Page对象中获取当前页码并转换为字符串进行存储用于前端展示当前所在的页码信息。
private String currPageNum;
// 页码列表
// 用于存储页码列表的字符串集合,例如 ["1", "2", "3", "...", "5", "6", "7"] 这样的形式,表示分页的页码展示情况,会根据总页数等条件动态生成该列表,方便前端展示分页页码导航。
private List<String> pageNums;
// 总页数
// 存储总页数的字符串表示形式从Page对象中获取总页数并转换为字符串保存用于告知前端总共有多少页方便进行分页范围的判断等操作。
private String totalPage;
// 总数量
// 存储数据总量的字符串表示形式从Page对象中获取总记录数并转换为字符串用于在前端展示总共有多少条数据让用户对数据规模有直观了解。
private String total;
// 数据
// 用于存储当前页的数据列表类型为泛型List<?>实际存放的是从Page对象中获取的对应页的记录数据方便在前端展示该页的具体数据内容。
private List<?> list;
public static PageUtils getPage(Page<?> page){
/**
* PagePageUtils
* MyBatis PlusPage<?>PagePageUtils
* /PageUtils便使
*
* @param page MyBatis PlusPage<?>PageUtils
* @return PageUtils
*/
public static PageUtils getPage(Page<?> page) {
PageUtils pg = new PageUtils();
if(page.getCurrent() > 1){
// 判断传入的Page对象的当前页码page.getCurrent()是否大于1如果大于1说明不是第一页此时应该显示上一页按钮所以将PageUtils实例中的isShowPreBtn属性设置为true。
if (page.getCurrent() > 1) {
pg.setIsShowPreBtn(true);
}
if(page.getCurrent() < page.getPages()){
// 判断传入的Page对象的当前页码page.getCurrent()是否小于总页数page.getPages()如果小于表明还有下一页那么需要显示下一页按钮故将PageUtils实例中的isShowNextBtn属性设置为true。
if (page.getCurrent() < page.getPages()) {
pg.setIsShowNextBtn(true);
}
List<String> pgNums = new ArrayList<>();
if(page.getPages() > 1){
if(page.getPages() > 10){
// 判断总页数page.getPages()是否大于1如果大于1才需要构建页码列表因为如果只有1页就不需要展示页码导航等信息了。
if (page.getPages() > 1) {
// 如果总页数大于10采用一种特定的页码列表生成策略以简化页码展示同时保证关键页码信息的显示方便用户进行分页导航操作。
if (page.getPages() > 10) {
// 先添加首页页码 "1",这是常见的分页页码展示习惯,方便用户快速回到第一页。
pgNums.add("1");
// 添加第二页页码 "2",同样是为了展示较靠前的页码信息,便于用户快速定位到前面的页面。
pgNums.add("2");
// 添加第三页页码 "3",继续完善前面部分的页码展示,让用户能看到初始的几页页码信息。
pgNums.add("3");
// 添加省略号 "...",用于表示中间部分页码省略,当总页数较多时,避免全部显示页码导致页面过于冗长,通过省略号提示用户还有其他页码存在。
pgNums.add("...");
if(page.getCurrent() == page.getPages()){
pgNums.add(((Integer)(page.getCurrent() - 2)).toString());
pgNums.add(((Integer)(page.getCurrent() - 1)).toString());
pgNums.add(((Integer)page.getCurrent()).toString());
}else{
pgNums.add(((Integer)(page.getCurrent() - 1)).toString());
pgNums.add(((Integer)page.getCurrent()).toString());
pgNums.add(((Integer)(page.getCurrent() + 1)).toString());
// 判断当前页是否等于总页数,如果是,说明当前处于最后一页,那么页码列表中要展示当前页的前两页页码以及当前页码,以方便用户进行前后页的切换查看等操作。
if (page.getCurrent() == page.getPages()) {
pgNums.add(((Integer) (page.getCurrent() - 2)).toString());
pgNums.add(((Integer) (page.getCurrent() - 1)).toString());
pgNums.add(((Integer) page.getCurrent()).toString());
} else {
// 如果当前页不等于总页数,说明不是最后一页,此时页码列表中展示当前页的前一页、当前页以及后一页的页码,方便用户进行临近页码的切换操作。
pgNums.add(((Integer) (page.getCurrent() - 1)).toString());
pgNums.add(((Integer) page.getCurrent()).toString());
pgNums.add(((Integer) (page.getCurrent() + 1)).toString());
}
}else{
} else {
// 如果总页数不超过10页采用简单的顺序添加页码的方式生成页码列表从1开始依次添加每一页的页码直到达到总页数为止。
Integer n = 1;
if(page.getTotal() > 0){
while(true){
if (page.getTotal() > 0) {
while (true) {
pgNums.add(n.toString());
if(n >= page.getPages()){
if (n >= page.getPages()) {
break;
}
n ++;
n++;
}
}
}
} else {
// 如果总页数等于1页同样采用顺序添加页码的方式这里其实只会添加页码 "1"生成页码列表从1开始添加到总页数为止虽然只有1页但保持页码列表生成逻辑的一致性。
Integer n = 1;
if(page.getTotal() > 0){
while(true){
if (page.getTotal() > 0) {
while (true) {
pgNums.add(n.toString());
if(n >= page.getPages()){
if (n >= page.getPages()) {
break;
}
n ++;
n++;
}
}
}
// 将生成好的页码列表设置到PageUtils实例的pageNums属性中以便后续通过相应的获取方法getPageNums提供给外部使用展示分页页码信息。
pg.setPageNums(pgNums);
// 将Page对象中当前页的数据列表page.getRecords()设置到PageUtils实例的list属性中方便后续获取该页的数据用于前端展示等操作。
pg.setList(page.getRecords());
pg.setCurrPageNum(((Integer)page.getCurrent()).toString());
pg.setTotal(((Integer)page.getTotal()).toString());
pg.setTotalPage(((Integer)page.getPages()).toString());
// 将Page对象的当前页码page.getCurrent()转换为字符串后设置到PageUtils实例的currPageNum属性中用于前端展示当前所在页码信息。
pg.setCurrPageNum(((Integer) page.getCurrent()).toString());
// 将Page对象的总记录数page.getTotal()转换为字符串后设置到PageUtils实例的total属性中用于前端展示数据总量信息。
pg.setTotal(((Integer) page.getTotal()).toString());
// 将Page对象的总页数page.getPages()转换为字符串后设置到PageUtils实例的totalPage属性中用于前端展示总页数信息方便用户了解分页的整体范围。
pg.setTotalPage(((Integer) page.getPages()).toString());
return pg;
}
// 以下是各个属性的Getter和Setter方法遵循JavaBean规范方便外部类对PageUtils实例中的属性进行获取和设置操作保证数据的封装性和可访问性。
public Boolean getIsShowNextBtn() {
return isShowNextBtn;
@ -109,42 +140,34 @@ public class PageUtils {
this.list = list;
}
public Boolean getIsShowPreBtn() {
return isShowPreBtn;
}
public void setIsShowPreBtn(Boolean isShowPreBtn) {
this.isShowPreBtn = isShowPreBtn;
}
public String getCurrPageNum() {
return currPageNum;
}
public void setCurrPageNum(String currPageNum) {
this.currPageNum = currPageNum;
}
public String getTotalPage() {
return totalPage;
}
public void setTotalPage(String totalPage) {
this.totalPage = totalPage;
}
public String getTotal() {
return total;
}
public void setTotal(String total) {
this.total = total;
}

@ -6,36 +6,54 @@ import javax.servlet.http.HttpServletRequest;
import org.apache.log4j.Logger;
// RequestHelper类主要用于辅助处理与HTTP请求相关的一些通用操作当前类中仅展示了一个获取客户端IP地址的方法
// 该方法会尝试从不同的请求头信息中获取IP地址以应对客户端通过代理服务器等方式访问时获取真实IP地址的情况同时会进行相应的日志记录。
public class RequestHelper {
// 创建一个Logger对象用于记录与这个类相关的日志信息这里将日志记录器的名称设置为RequestHelper类的全限定名
// 通过这个日志记录器可以在合适的地方输出不同级别的日志如INFO级别等方便调试和查看相关操作信息例如记录获取IP地址的过程和结果等情况。
private static Logger logger = Logger.getLogger(RequestHelper.class);
/**
* IP,IP;
*
* @param request
* @return
* @throws IOException
* IPIP
* HTTPHttpServletRequestIPIP
* 访IP request.getRemoteAddr()
* IPIPIPIPIP
*
* @param request HttpServletRequestHTTPIPIP
* @return IPIP request.getRemoteAddr() IP
* @throws IOException I/O
*/
public final static String getIpAddress(HttpServletRequest request) {
// 获取请求主机IP地址,如果通过代理进来则透过防火墙获取真实IP地址
// 首先尝试从 "X-Forwarded-For" 请求头字段中获取IP地址这个请求头通常在客户端通过代理服务器访问时由代理服务器添加用于记录原始客户端的IP地址以及经过的代理服务器IP地址列表以逗号分隔
// 如果该请求头不存在或者其值为空字符串或者值为 "unknown"表示无法获取到有效IP地址的一种约定情况则继续尝试从其他请求头获取IP地址。
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
// 如果从 "X-Forwarded-For" 请求头中未获取到有效IP地址再次尝试从 "Proxy-Client-IP" 请求头获取IP地址
// 某些代理服务器会使用这个请求头来传递客户端的IP地址信息同样判断其是否为空或者值为 "unknown",如果是则继续尝试下一个请求头。
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
// 接着尝试从 "WL-Proxy-Client-IP" 请求头获取IP地址这是WebLogic服务器作为代理时可能使用的请求头来传递客户端IP地址继续进行有效性判断和后续尝试。
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
// 再尝试从 "HTTP_CLIENT_IP" 请求头获取IP地址一些其他的代理服务器或者特定的网络环境可能会通过这个请求头传递客户端IP信息同样检查是否有效并按需继续查找。
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
// 然后尝试从 "HTTP_X_FORWARDED_FOR" 请求头获取IP地址它与 "X-Forwarded-For" 类似也是可能用于记录客户端IP经过代理的相关情况的请求头继续判断并尝试获取有效IP地址。
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
// 如果前面所有尝试从请求头获取IP地址的方式都失败了最后通过 request.getRemoteAddr() 获取IP地址
// 这个方法通常获取的是与服务器直接建立连接的客户端可能是代理服务器或者真实客户端如果没有代理的话的IP地址作为一种兜底获取IP地址的方式。
ip = request.getRemoteAddr();
}
} else if (ip.length() > 15) {
// 如果从 "X-Forwarded-For" 请求头中获取到的IP地址字符串长度大于15说明可能包含了多个IP地址以逗号分隔的情况记录了经过的多个代理服务器IP等情况
// 需要对其进行处理将IP地址字符串按照逗号分割为字符串数组然后遍历数组查找第一个不为 "unknown" 的IP地址将其作为客户端的真实IP地址因为按照约定真实客户端IP地址通常在最前面。
String[] ips = ip.split(",");
for (int index = 0; index < ips.length; index++) {
String strIp = (String) ips[index];
@ -45,9 +63,13 @@ public class RequestHelper {
}
}
}
// 判断日志记录器的INFO级别是否启用通过配置文件等方式设置日志级别如果启用了INFO级别日志
// 则记录一条INFO级别的日志信息将获取到的IP地址信息以及方法相关的标识信息记录下来方便后续查看获取IP地址的具体情况例如排查IP地址获取是否正确等问题。
if (logger.isInfoEnabled()) {
logger.info("getIpAddress(HttpServletRequest) - Proxy-Client-IP - String ip=" + ip);
}
return ip;
}
}
}

@ -5,23 +5,33 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
// Result类用于封装操作结果的通用类实现了Serializable接口意味着该类的对象可以被序列化方便在网络传输、持久化存储等场景下使用。
// 它定义了表示操作结果的相关属性(如状态码、具体结果数据、提示信息等)以及一系列静态方法来便捷地创建不同类型(成功或失败)的结果对象,同时提供了一些用于构建特定格式结果数据的方法,适用于构建接口返回数据等情况。
public class Result implements Serializable {
// 序列化版本号,用于在对象序列化和反序列化过程中保证版本兼容性,当类的结构发生变化(如新增、删除字段等情况)时,若版本号不一致可能导致反序列化失败,这里是一个固定的长整型数值。
private static final long serialVersionUID = -1651614836984397356L;
// 用于表示操作结果的状态码不同的整数值代表不同的操作结果状态例如常见的用0表示成功1表示失败等方便调用者根据状态码判断操作是否成功以及进行相应的后续处理。
private int code;
// 用于存储操作的具体结果数据,可以是任意类型的对象(如实体对象、数据集合等),根据具体的业务操作来决定存放的内容,方便将业务相关的数据返回给调用者。
private Object result;
// 用于存储与操作结果相关的提示信息,比如操作成功时可以为空字符串,操作失败时存放具体的错误提示内容,便于告知调用者操作的执行情况。
private String message;
// 定义表示成功的状态码常量方便在创建成功结果对象时统一使用使代码更具可读性和可维护性当前设置为0表示操作成功的情况。
public static final int SUCCESS_CODE = 0;
// 定义表示失败的状态码常量同样用于在创建失败结果对象时保持一致性当前设定为1用于标识操作出现问题、未成功执行的情况。
public static final int FAIL_CODE = 1;
// 私有构造函数用于限制外部直接通过构造函数创建Result对象保证结果对象的创建只能通过类中提供的静态方法来进行遵循封装的设计原则便于统一管理结果对象的创建逻辑。
private Result() {
}
// 私有构造函数用于在类内部创建Result对象时传入状态码、具体结果数据以及提示信息通过这种方式初始化Result对象的各个属性同样外部无法直接调用这个构造函数只能通过静态方法间接使用。
private Result(int code, Object result, String message) {
this.code = code;
this.result = result;
@ -29,67 +39,139 @@ public class Result implements Serializable {
}
/**
*
*
* @param result
* @return
*
* ResultresultSUCCESS_CODE0
* ResultresultResult便
*
* @param result
* @return Result
*/
public static Result successResult(Object result) {
return result(SUCCESS_CODE, result, "");
}
/**
*
* successResultResultrecordsrecordSumrowsOfPage
* successResultMapResult便
*
* @param records
* @param recordSum 便
* @param rowsOfPage
* @return Result
*/
public static Result successResult(Object records, Long recordSum, Long rowsOfPage) {
return successResult(records, recordSum, rowsOfPage, null);
}
/**
*
* successResultuserDataResult
* resultOfListMapMapsuccessResult
* Result便
*
* @param records
* @param recordSum
* @param rowsOfPage 便
* @param userData
* @return Result
*/
public static Result successResult(Object records, Long recordSum, Long rowsOfPage, Object userData) {
Map<String, Object> result = resultOfList(records, recordSum, rowsOfPage, userData);
return successResult(result);
}
/**
* Map
* Map便
* MapMapResult使
*
* @param records
* @param recordSum 便
* @param rowsOfPage
* @return Map"rows""records""total"
*/
public static Map<String, Object> resultOfList(Object records, Long recordSum, Long rowsOfPage) {
return resultOfList(records, recordSum, rowsOfPage, null);
}
/**
* Map
* userDataMapHashMap
* "rows""records""total""userdata"便
* MapResult使
*
* @param Obj
* @param records
* @param rowsOfPage 便
* @param userData
* @return Map"rows""records""total""userdata"便使
*/
public static Map<String, Object> resultOfList(Object Obj, Long records, Long rowsOfPage, Object userData) {
Map<String, Object> result = new HashMap<String, Object>();
result.put("rows", Obj);
result.put("records", records);
result.put("total", rowsOfPage);
if (null != userData) {
if (null!= userData) {
result.put("userdata", userData);
}
;
return result;
}
/**
* jqGridMap
* jqGridMap
* HashMapjqGrid "list""totalCount""pageSize""currPage""totalPage"
* 便使jqGridMap
*
* @param list jqGrid
* @param totalCount jqGrid便jqGrid
* @param pageSize jqGrid便
* @param currPage jqGrid便
* @param totalPage jqGrid
* @return MapjqGrid"list""totalCount""pageSize""currPage""totalPage"便使jqGrid
*/
public static Map<String, Object> jqGridResult(List<?> list, long totalCount, int pageSize, int currPage,
int totalPage) {
int totalPage) {
Map<String, Object> result = new HashMap<String, Object>();
result.put("list", list);
result.put("totalCount", totalCount);
result.put("totalPage", totalPage);
result.put("pageSize", pageSize);
result.put("currPage", currPage);
result.put("totalPage", totalPage);
return result;
}
/**
*
*
* @param errorMsg
* @return
*
* ResulterrorMsgFAIL_CODE1
* ResultmessageresultResult便
*
* @param errorMsg
* @return Result
*/
public static Result failResult(String errorMsg) {
return result(FAIL_CODE, "", errorMsg);
}
/**
*
* Resultcoderesultmessage
* ResultResult便
*
* @param code 01
* @param result
* @param message
* @return Result
*/
public static Result result(int code, Object result, String message) {
Result res = new Result(code, result, message);
return res;
}
// 以下是Result类中各个属性的Getter方法遵循JavaBean规范方便外部类获取Result对象中封装的状态码、结果数据以及提示信息等内容保证数据的封装性和可访问性。
public int getCode() {
return code;
}
@ -101,5 +183,4 @@ public class Result implements Serializable {
public String getMessage() {
return message;
}
}
}

@ -8,253 +8,153 @@ import org.apache.commons.codec.binary.Base64;
import org.apache.commons.fileupload.*;
import org.apache.commons.fileupload.FileUploadBase.InvalidContentTypeException;
import org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException;
import org.apache.commons.fileupload.util.*;
import org.apache.commons.fileupload.servlet.*;
import org.apache.commons.fileupload.util.Streams;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import javax.servlet.http.HttpServletRequest;
/**
* UEditor
*
*/
// Uploader类是一个UEditor文件上传辅助类用于处理文件上传的各种操作包括文件格式检查、大小限制判断、文件保存路径处理以及实际的文件上传逻辑等
// 同时支持常规的表单文件上传和接收Base64格式文件上传两种方式并且会根据上传过程中的不同情况设置相应的状态信息方便调用者知晓上传结果。
public class Uploader {
// 输出文件地址
// 用于存储上传文件的输出文件地址,即文件最终保存的相对路径或者完整路径(根据具体使用场景确定),初始值为空字符串,在文件上传成功后会被赋值为实际的保存路径。
private String url = "";
// 上传文件名
// 用于存储上传后的文件名,这个文件名可能是经过处理后生成的新文件名(例如添加随机数、时间戳等以保证文件名的唯一性等情况),初始值为空字符串,上传成功后会被赋予实际的文件名。
private String fileName = "";
// 状态
// 用于表示文件上传的状态通过设置不同的状态值从errorInfo这个HashMap中获取对应的状态描述字符串来反馈上传过程中出现的各种情况例如成功、文件格式错误、大小超出限制等初始值为空字符串会在上传操作过程中根据情况进行更新。
private String state = "";
// 文件类型
// 用于存储文件类型,一般通过文件扩展名来确定,例如 ".jpg"、".pdf" 等,在文件上传过程中会进行相应的提取和赋值操作,初始值为空字符串。
private String type = "";
// 原始文件名
// 用于存储上传文件的原始文件名,即用户在客户端选择文件时的原始文件名称,方便记录和后续可能的展示等操作,初始值为空字符串,上传时会获取并赋值。
private String originalName = "";
// 文件大小
// 用于存储上传文件大小单位为字节在文件上传完成后会获取并记录文件的实际大小初始值为0。
private long size = 0;
// 用于接收来自客户端的HTTP请求对象HttpServletRequest通过这个对象可以获取上传文件相关的信息如请求头、请求参数等在整个文件上传过程中起着关键的信息获取作用初始值为null通过构造函数传入。
private HttpServletRequest request = null;
// 用于存储文件的标题信息(可能在某些特定的业务场景下使用,例如图片有对应的标题描述等情况),初始值为空字符串,会在处理表单中对应字段时进行赋值。
private String title = "";
// 保存路径
// 用于指定文件的保存路径,默认值为 "upload"表示文件将会被保存到这个相对路径下实际的物理路径会根据服务器配置等情况进一步确定可以通过相应的Setter方法进行修改设置。
private String savePath = "upload";
// 文件允许格式
private String[] allowFiles = { ".rar", ".doc", ".docx", ".zip", ".pdf",".txt", ".swf", ".wmv", ".gif", ".png", ".jpg", ".jpeg", ".bmp" };
// 文件大小限制单位KB
// 用于定义允许上传的文件格式数组,只有文件名后缀符合这个数组中定义的格式的文件才允许被上传,初始包含了常见的一些文件格式如压缩文件、文档文件、图片文件等类型的后缀名可通过Setter方法重新设置允许的文件格式。
private String[] allowFiles = { ".rar", ".doc", ".docx", ".zip", ".pdf", ".txt", ".swf", ".wmv", ".gif", ".png", ".jpg", ".jpeg", ".bmp" };
// 用于设置文件大小限制,单位KB默认值为10000KB即10MB如果上传文件的大小超过这个限制将会在上传过程中被拦截并设置相应的错误状态可通过Setter方法调整文件大小限制值。
private int maxSize = 10000;
// 用于存储各种文件上传错误信息的HashMap键为表示错误类型的字符串如 "SUCCESS"、"NOFILE" 等),值为对应的详细错误描述字符串,方便根据不同的上传情况设置相应的状态信息并反馈给调用者具体的错误原因。
private HashMap<String, String> errorInfo = new HashMap<String, String>();
/**
* Uploader
* HttpServletRequestrequest便
* errorInfoHashMap
*
* @param request HttpServletRequestHTTP
*/
public Uploader(HttpServletRequest request) {
this.request = request;
HashMap<String, String> tmp = this.errorInfo;
tmp.put("SUCCESS", "SUCCESS"); //默认成功
tmp.put("NOFILE", "未包含文件上传域");
tmp.put("TYPE", "不允许的文件格式");
tmp.put("SIZE", "文件大小超出限制");
tmp.put("ENTYPE", "请求类型ENTYPE错误");
tmp.put("REQUEST", "上传请求异常");
tmp.put("IO", "IO异常");
tmp.put("DIR", "目录创建失败");
tmp.put("UNKNOWN", "未知错误");
tmp.put("SUCCESS", "SUCCESS"); //默认成功,设置表示上传成功的状态对应的描述信息为 "SUCCESS",方便后续判断和返回结果时使用。
tmp.put("NOFILE", "未包含文件上传域"); // 设置表示请求中未包含文件上传相关字段的错误状态对应的描述信息,用于在没有文件上传时反馈相应的错误情况。
tmp.put("TYPE", "不允许的文件格式"); // 设置表示上传文件的格式不符合允许的文件格式列表allowFiles的错误状态对应的描述信息用于文件格式验证不通过时反馈错误原因。
tmp.put("SIZE", "文件大小超出限制"); // 设置表示上传文件大小超过了设定的最大文件大小限制maxSize的错误状态对应的描述信息用于文件大小超出限制时告知调用者具体错误情况。
tmp.put("ENTYPE", "请求类型ENTYPE错误"); // 设置表示请求的类型如Content-Type等相关请求头不符合文件上传要求出现错误的状态对应的描述信息用于请求类型不合法时反馈错误情况。
tmp.put("REQUEST", "上传请求异常"); // 设置表示在文件上传请求处理过程中出现其他未知异常的错误状态对应的描述信息,用于捕获到一般性的请求相关异常时反馈错误原因。
tmp.put("IO", "IO异常"); // 设置表示在文件输入输出如读取文件、写入文件等操作过程中出现I/O异常的错误状态对应的描述信息用于处理文件读写等操作出现问题时反馈错误情况。
tmp.put("DIR", "目录创建失败"); // 设置表示在创建文件保存目录(例如根据日期创建子目录等情况)时失败的错误状态对应的描述信息,用于目录创建出现问题时反馈错误原因。
tmp.put("UNKNOWN", "未知错误"); // 设置表示其他未明确归类的、未知的错误情况对应的错误状态描述信息,用于捕获到意料之外的异常时进行统一的错误反馈。
}
/**
*
* NOFILE
* DiskFileItemFactoryServletFileUpload
*
*
* "pictitle" UE退
*
* @throws Exception Exception
*/
public void upload() throws Exception {
// 判断当前请求是否为多部分表单数据即是否包含文件上传域通过ServletFileUpload提供的静态方法进行判断这是进行文件上传操作的前提条件如果不是多部分表单数据则不能进行后续的文件上传处理。
boolean isMultipart = ServletFileUpload.isMultipartContent(this.request);
if (!isMultipart) {
// 如果请求不是多部分表单数据,说明没有文件上传相关内容,设置文件上传状态为 "NOFILE"(表示未包含文件上传域),然后直接返回,不再进行后续的文件上传操作流程。
this.state = this.errorInfo.get("NOFILE");
return;
}
// 创建一个DiskFileItemFactory对象它用于配置文件上传过程中的一些基础设置例如设置临时文件的存储位置等这里后续会将其与ServletFileUpload结合使用来处理文件上传操作。
DiskFileItemFactory dff = new DiskFileItemFactory();
// 获取文件的保存路径通过调用getFolder方法根据配置的保存路径this.savePath以及当前日期生成具体的保存目录路径确保文件按照日期进行分类保存同时也处理了目录创建等相关操作。
String savePath = this.getFolder(this.savePath);
// 设置DiskFileItemFactory的临时文件存储目录将其指定为前面获取到的保存路径对应的File对象这样在文件上传过程中如果需要临时存储文件例如文件较大时分块处理等情况会将临时文件存放在这个目录下。
dff.setRepository(new File(savePath));
try {
// 创建一个ServletFileUpload对象用于处理文件上传的具体操作将前面配置好的DiskFileItemFactory对象传入使其基于配置好的基础设置来进行文件上传相关的处理工作如解析请求、获取文件项等。
ServletFileUpload sfu = new ServletFileUpload(dff);
// 设置文件上传的最大允许大小通过将以KB为单位的maxSize乘以1024转换为字节单位限制上传文件的大小不能超过这个设定值若超过则会抛出SizeLimitExceededException异常进行相应的错误处理。
sfu.setSizeMax(this.maxSize * 1024);
// 设置请求头的编码格式为 "utf-8",确保在处理包含中文等特殊字符的文件名等信息时能够正确解析,避免出现乱码问题,保证文件上传过程中字符编码的一致性。
sfu.setHeaderEncoding("utf-8");
// 通过ServletFileUpload的getItemIterator方法获取一个FileItemIterator对象它用于迭代请求中的文件项包括上传的文件以及表单中的其他字段信息等方便后续逐个进行处理。
FileItemIterator fii = sfu.getItemIterator(this.request);
while (fii.hasNext()) {
// 获取下一个文件项返回的FileItemStream对象可以用于获取文件项的相关信息如文件名、文件内容流等以及判断是否为表单字段等操作是处理文件上传过程中每个文件或字段的关键对象。
FileItemStream fis = fii.next();
if (!fis.isFormField()) {
// 如果当前文件项不是表单字段(即代表是一个真正要上传的文件),则进行以下文件上传相关的处理操作。
// 获取上传文件的原始文件名通过截取文件名中最后一个文件分隔符根据系统的文件分隔符来获取通过System.getProperty("file.separator")获取系统对应的文件分隔符)之后的部分作为原始文件名,
// 去除了文件的路径信息,只保留文件名本身,方便后续处理和记录。
this.originalName = fis.getName().substring(fis.getName().lastIndexOf(System.getProperty("file.separator")) + 1);
// 检查文件的格式是否符合允许的文件格式列表allowFiles调用checkFileType方法进行检查如果不符合则设置文件上传状态为 "TYPE"(表示不允许的文件格式),并跳过当前文件的后续上传操作,继续处理下一个文件项(如果有的话)。
if (!this.checkFileType(this.originalName)) {
this.state = this.errorInfo.get("TYPE");
continue;
}
// 生成新的文件名调用getName方法根据原始文件名生成一个带有随机数、时间戳等信息的新文件名以保证文件名的唯一性和避免文件名冲突等问题同时将新文件名赋值给fileName成员变量。
this.fileName = this.getName(this.originalName);
// 获取文件的类型通过调用getFileExt方法截取文件名的后缀部分从最后一个 "." 开始截取到末尾)作为文件类型,例如 ".jpg"、".pdf" 等并赋值给type成员变量方便后续记录和使用。
this.type = this.getFileExt(this.fileName);
// 构建文件的输出文件地址即文件最终保存的路径将保存路径savePath和新生成的文件名fileName组合起来形成完整的文件保存路径赋值给url成员变量用于后续保存文件时确定保存位置。
this.url = savePath + "/" + this.fileName;
// 创建一个缓冲输入流BufferedInputStream用于读取上传文件的内容通过调用fis.openStream方法打开文件项对应的输入流并将其包装为缓冲输入流提高文件读取效率准备将文件内容读取出来进行保存操作。
BufferedInputStream in = new BufferedInputStream(fis.openStream());
// 根据前面构建的文件保存路径创建一个对应的File对象用于表示要保存的目标文件方便后续通过文件输出流将读取到的文件内容写入到这个文件中完成文件的保存操作。
File file = new File(this.getPhysicalPath(this.url));
FileOutputStream out = new FileOutputStream( file );
// 创建一个文件输出流FileOutputStream用于将文件内容写入到目标文件中将前面创建的File对象作为参数传入初始化文件输出流对象准备进行文件写入操作。
FileOutputStream out = new FileOutputStream(file);
// 创建一个缓冲输出流BufferedOutputStream将前面的文件输出流包装起来进一步提高文件写入的效率和稳定性通过缓冲机制优化文件输出操作减少频繁的磁盘I/O操作次数。
BufferedOutputStream output = new BufferedOutputStream(out);
// 使用Streams工具类来自Apache Commons FileUpload组件的copy方法将输入流in即读取的上传文件内容中的数据复制到输出流output即要写入到目标文件的输出流实现文件的保存操作
// 最后一个参数true表示在复制完成后自动关闭输入流和输出流释放相关资源确保文件内容正确地从客户端上传并保存到服务器指定的位置。
Streams.copy(in, output, true);
this.state=this.errorInfo.get("SUCCESS");
// 如果文件上传成功,设置文件上传状态为 "SUCCESS"表示成功通过从errorInfo这个HashMap中获取对应的成功状态描述字符串来设置告知调用者文件上传操作顺利完成。
this.state = this.errorInfo.get("SUCCESS");
// 获取并记录上传文件的大小通过获取保存后的文件的长度单位为字节赋值给size成员变量方便后续获取文件大小信息进行相关的展示或其他业务操作。
this.size = file.length();
//UE中只会处理单张上传完成后即退出
// 在UEUEditor一种富文本编辑器推测此处是针对其文件上传逻辑进行的代码编写中按只处理单张上传的逻辑所以一旦成功上传一个文件后就直接退出循环不再处理后续的文件项如果还有的话
break;
} else {
// 如果当前文件项是表单字段,则进行以下处理操作(当前代码中仅处理名为 "pictitle" 的字段作为标题字段,对于其他表单字段可以根据业务需求自行扩展处理逻辑)。
// 获取表单字段的名称通过调用fis.getFieldName方法获取字段的名称用于后续判断是否是需要处理的特定字段如这里的 "pictitle")。
String fname = fis.getFieldName();
//只处理title其余表单请自行处理
if(!fname.equals("pictitle")){
// 判断表单字段名称是否等于 "pictitle",如果不等于,则直接跳过当前字段,继续处理下一个文件项,因为当前代码只关注这个特定的标题字段进行处理。
if (!fname.equals("pictitle")) {
continue;
}
BufferedInputStream in = new BufferedInputStream(fis.openStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuffer result = new StringBuffer();
while (reader.ready()) {
result.append((char)reader.read());
}
this.title = new String(result.toString().getBytes(),"utf-8");
reader.close();
}
}
} catch (SizeLimitExceededException e) {
this.state = this.errorInfo.get("SIZE");
} catch (InvalidContentTypeException e) {
this.state = this.errorInfo.get("ENTYPE");
} catch (FileUploadException e) {
this.state = this.errorInfo.get("REQUEST");
} catch (Exception e) {
this.state = this.errorInfo.get("UNKNOWN");
}
}
/**
* base64
* @param fieldName
*/
public void uploadBase64(String fieldName){
String savePath = this.getFolder(this.savePath);
String base64Data = this.request.getParameter(fieldName);
this.fileName = this.getName("test.png");
this.url = savePath + "/" + this.fileName;
try {
File outFile = new File(this.getPhysicalPath(this.url));
OutputStream ro = new FileOutputStream(outFile);
byte[] b = Base64.decodeBase64(base64Data);
for (int i = 0; i < b.length; ++i) {
if (b[i] < 0) {
b[i] += 256;
}
}
ro.write(b);
ro.flush();
ro.close();
this.state=this.errorInfo.get("SUCCESS");
} catch (Exception e) {
this.state = this.errorInfo.get("IO");
}
}
/**
*
*
* @param fileName
* @return
*/
private boolean checkFileType(String fileName) {
Iterator<String> type = Arrays.asList(this.allowFiles).iterator();
while (type.hasNext()) {
String ext = type.next();
if (fileName.toLowerCase().endsWith(ext)) {
return true;
}
}
return false;
}
/**
*
*
* @return string
*/
private String getFileExt(String fileName) {
return fileName.substring(fileName.lastIndexOf("."));
}
/**
*
* @return
*/
private String getName(String fileName) {
Random random = new Random();
return this.fileName = "" + random.nextInt(10000)
+ System.currentTimeMillis() + this.getFileExt(fileName);
}
/**
*
* @param path
* @return
*/
private String getFolder(String path) {
SimpleDateFormat formater = new SimpleDateFormat("yyyyMMdd");
path += "/" + formater.format(new Date());
File dir = new File(this.getPhysicalPath(path));
if (!dir.exists()) {
try {
dir.mkdirs();
} catch (Exception e) {
this.state = this.errorInfo.get("DIR");
return "";
}
}
return path;
}
/**
*
*
* @param path
* @return
*/
private String getPhysicalPath(String path) {
String servletPath = this.request.getServletPath();
String realPath = this.request.getSession().getServletContext()
.getRealPath(servletPath);
return new File(realPath).getParent() +"/" +path;
}
public void setSavePath(String savePath) {
this.savePath = savePath;
}
public void setAllowFiles(String[] allowFiles) {
this.allowFiles = allowFiles;
}
public void setMaxSize(int size) {
this.maxSize = size;
}
public long getSize() {
return this.size;
}
public String getUrl() {
return this.url;
}
public String getFileName() {
return this.fileName;
}
public String getState() {
return this.state;
}
public String getTitle() {
return this.title;
}
public String getType() {
return this.type;
}
public String getOriginalName() {
return this.originalName;
}
}
// 创建一个缓冲输入流BufferedInputStream用于读取表单字段对应的输入流内容这里假设表单字段中的内容是以流的形式传递的例如文本信息等通过fis.openStream方法打开输入流并包装为缓冲输入流方便后续读取操作。
BufferedInputStream in = new BufferedInputStream(fis.openStream());
// 创建一个BufferedReader对象将前面的缓冲输入流包装为字符流读取器通过指定字符编码为 "utf-8"
Loading…
Cancel
Save