From 7e90c26b6880a1baa3280b441e0aa7d4fe7c1cc8 Mon Sep 17 00:00:00 2001 From: SmileToCandy Date: Fri, 17 Mar 2023 10:36:16 +0000 Subject: [PATCH] =?UTF-8?q?readme=20=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: SmileToCandy --- .idea/.gitignore | 8 + .idea/misc.xml | 6 + .idea/modules.xml | 8 + .idea/tamguo.iml | 9 + .idea/vcs.xml | 6 + README.md | 3 +- .../tamguo/common/encryption/ShaEncrypt.java | 58 ++-- .../main/java/com/tamguo/common/id/IdGen.java | 63 +++-- .../com/tamguo/common/image/CaptchaUtils.java | 254 ++++++++++-------- .../tamguo/common/serialize/ObjectUtil.java | 53 +++- .../common/serialize/SerializeTranscoder.java | 30 ++- .../utils/AbstractRunningLogHandler.java | 75 ++++-- 12 files changed, 400 insertions(+), 173 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/tamguo.iml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..694f698 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..a9ea907 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/tamguo.iml b/.idea/tamguo.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/.idea/tamguo.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index c2178b0..bdadb08 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,8 @@ - 管理员账号:system 密码:123456 **因为线上数据和测试数据没有做到隔离,作者已经把密码修改,可用.sql在本地运行看后台效果。** -加QQ群:937899574 可免费获取SQL基本 +现在作者组建一个团队来对这个项目进行迭代升级,需要前端、后端、设计人员 +有兴趣加入的小伙伴,可以加作者微信: tamgoooo diff --git a/tamguo-common/src/main/java/com/tamguo/common/encryption/ShaEncrypt.java b/tamguo-common/src/main/java/com/tamguo/common/encryption/ShaEncrypt.java index 629f807..641c744 100644 --- a/tamguo-common/src/main/java/com/tamguo/common/encryption/ShaEncrypt.java +++ b/tamguo-common/src/main/java/com/tamguo/common/encryption/ShaEncrypt.java @@ -3,62 +3,76 @@ package com.tamguo.common.encryption; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +// ShaEncrypt类用于实现SHA加密相关的功能,提供了生成SHA-256和SHA-512加密字符串的便捷方法 public class ShaEncrypt { /** - * 传入文本内容,返回 SHA-256 串 - * - * @param strText - * @return + * 根据传入的文本内容,生成对应的SHA-256加密字符串并返回。 + * 此方法内部调用了私有方法SHA进行实际的加密操作,传入固定的加密算法类型"SHA-256"。 + * + * @param strText 要进行加密的原始文本内容,不能为空字符串,否则将无法进行有效加密。 + * @return 返回经过SHA-256加密后的十六进制字符串表示结果,如果加密过程出现异常则返回null。 */ public static String SHA256(final String strText) { return SHA(strText, "SHA-256"); } /** - * 传入文本内容,返回 SHA-512 串 - * - * @param strText - * @return + * 根据传入的文本内容,生成对应的SHA-512加密字符串并返回。 + * 同样内部调用私有方法SHA进行加密,传入的加密算法类型为"SHA-512"。 + * + * @param strText 要进行加密的原始文本内容,不能为空字符串,否则无法进行有效加密。 + * @return 返回经过SHA-512加密后的十六进制字符串表示结果,若加密出现异常则返回null。 */ public static String SHA512(final String strText) { return SHA(strText, "SHA-512"); } /** - * 字符串 SHA 加密 - * - * @param strSourceText - * @return + * 私有方法,用于执行具体的字符串SHA加密操作,根据传入的加密算法类型对输入字符串进行加密处理。 + * + * @param strText 要进行加密的原始文本内容,需保证其不为空且有一定长度,才能进行有效加密。 + * @param strType 指定的SHA加密算法类型,如"SHA-256"或"SHA-512"等,用于确定具体使用哪种SHA算法进行加密。 + * @return 返回加密后的十六进制字符串表示结果,如果加密过程中出现指定算法不存在的异常情况,则返回null。 */ private static String SHA(final String strText, final String strType) { - // 返回值 + // 用于存储最终加密后的十六进制字符串结果,初始化为null,若加密成功则会被赋值并返回 String strResult = null; - // 是否是有效字符串 - if (strText != null && strText.length() > 0) { + // 首先判断传入的待加密字符串是否有效,即不能为空且要有一定长度,才进行后续的加密操作 + if (strText!= null && strText.length() > 0) { try { - // SHA 加密开始 - // 创建加密对象 并傳入加密類型 + // SHA加密开始 + + // 创建MessageDigest对象,用于实现指定类型的消息摘要算法(这里就是SHA加密算法),通过传入加密算法类型字符串来获取对应的算法实现实例。 + // 如果传入的算法类型不存在,会抛出NoSuchAlgorithmException异常。 MessageDigest messageDigest = MessageDigest.getInstance(strType); - // 传入要加密的字符串 + + // 使用创建好的MessageDigest对象,传入要加密的字符串对应的字节数组,更新摘要信息,准备进行加密计算。 messageDigest.update(strText.getBytes()); - // 得到 byte 類型结果 + + // 执行加密计算,得到加密后的字节数组结果,这个结果是二进制形式的加密数据表示。 byte byteBuffer[] = messageDigest.digest(); - // 將 byte 轉換爲 string + // 创建一个StringBuffer对象,用于后续拼接十六进制的加密结果字符串,方便对字节数组中的每个字节进行转换和拼接操作。 StringBuffer strHexString = new StringBuffer(); - // 遍歷 byte buffer + + // 遍历加密后的字节数组,将每个字节转换为十六进制字符串表示形式,并添加到strHexString中。 for (int i = 0; i < byteBuffer.length; i++) { + // 将字节转换为十六进制字符串,通过与0xff进行按位与操作,确保得到的是无符号的字节值对应的十六进制表示。 String hex = Integer.toHexString(0xff & byteBuffer[i]); + // 如果转换后的十六进制字符串长度为1(例如字节值小于16时),则在前面补0,以保证每个字节对应的十六进制表示都是两位。 if (hex.length() == 1) { strHexString.append('0'); } + // 将处理后的十六进制字符串添加到结果字符串缓冲区中 strHexString.append(hex); } - // 得到返回結果 + + // 将最终拼接好的十六进制字符串赋值给strResult,作为加密后的结果返回。 strResult = strHexString.toString(); } catch (NoSuchAlgorithmException e) { + // 如果在创建MessageDigest对象时,指定的加密算法类型不存在,会捕获该异常,并打印异常堆栈信息,同时返回null表示加密失败。 e.printStackTrace(); } } diff --git a/tamguo-common/src/main/java/com/tamguo/common/id/IdGen.java b/tamguo-common/src/main/java/com/tamguo/common/id/IdGen.java index 7462bd4..ed32e50 100644 --- a/tamguo-common/src/main/java/com/tamguo/common/id/IdGen.java +++ b/tamguo-common/src/main/java/com/tamguo/common/id/IdGen.java @@ -1,56 +1,75 @@ package com.tamguo.common.id; -public class IdGen -{ +// IdGen类用于生成唯一标识符(ID),很可能采用了类似雪花算法(Snowflake Algorithm)的思路来生成分布式环境下的唯一ID +public class IdGen { + // 工作机器ID,用于区分不同的工作节点,在分布式系统中每个节点有唯一的workerId private long workerId; + // 数据中心ID,用于区分不同的数据中心,不同的数据中心此ID不同 private long datacenterId; + // 序列号,用于在同一毫秒内对生成的ID进行区分,每生成一个新ID,该值可能会递增 private long sequence = 0L; + // 一个基准时间戳(以毫秒为单位),通常是一个过去的固定时间点,用于计算时间差值来生成ID中的时间部分 private long twepoch = 1288834974657L; + // 工作机器ID所占的位数,这里定义为5位,可以表示的范围是0 - 31(2^5 - 1) private long workerIdBits = 5L; - private long datacenterIdBits = 5L; + // 数据中心ID所占的位数,同样定义为5位,可表示范围也是0 - 31(2^5 - 1) private long maxWorkerId = -1L ^ (-1L << workerIdBits); private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); + // 序列号所占的位数,这里定义为12位,可表示范围是0 - 4095(2^12 - 1) private long sequenceBits = 12L; + // 用于计算最终ID时,将workerId左移的位数,基于sequenceBits的值来确定 private long workerIdShift = sequenceBits; + // 用于计算最终ID时,将datacenterId左移的位数,基于sequenceBits和workerIdBits的值来确定 private long datacenterIdShift = sequenceBits + workerIdBits; + // 用于计算最终ID时,将时间戳部分左移的位数,综合考虑了sequenceBits、workerIdBits和datacenterIdBits的值 private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; + // 序列号的掩码,用于对sequence进行位运算操作,保证其在合法范围内循环使用 private long sequenceMask = -1L ^ (-1L << sequenceBits); + // 上次生成ID时的时间戳,用于对比当前时间戳,判断是否需要处理时间回拨等情况 private long lastTimestamp = -1L; + // 要拼接在生成ID上的字符串后缀(或前缀,取决于flag的值),可为空字符串表示不拼接 private String suffix; + // 用于标识suffix是作为前缀(true)还是后缀(false)拼接在生成的ID上 private boolean flag; + // 内部静态类,用于实现单例模式,保证整个应用中只有一个IdGen实例(默认构造方式时) private static class IdGenHolder { private static final IdGen instance = new IdGen(); } - public static IdGen get(){ + // 获取IdGen的单例实例(默认无后缀的情况) + public static IdGen get() { return IdGenHolder.instance; } /** - * 获取字符串拼接id - * @param suffix 字符串 - * @param flag true:前缀 false:后缀 - * @return + * 获取带有字符串拼接的IdGen实例 + * + * @param suffix 要拼接的字符串,可以为null或者空字符串,表示不拼接 + * @param flag 用于指定suffix是作为前缀(true)还是后缀(false)拼接在生成的ID上 + * @return 根据参数情况返回对应的IdGen实例,如果suffix为空则返回单例实例,否则返回新创建的带有指定后缀拼接配置的实例 */ - 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); } } - + // 默认构造函数,调用另一个带有默认workerId和datacenterId(都为0)的构造函数进行初始化 public IdGen() { this(0L, 0L); } - public IdGen(String suffix,boolean flag){ + // 带有后缀字符串和拼接方式标识的构造函数,用于初始化suffix和flag成员变量 + public IdGen(String suffix, boolean flag) { this.suffix = suffix; this.flag = flag; } + // 构造函数,用于初始化workerId和datacenterId,同时会对传入的参数进行合法性校验 + // 如果workerId或datacenterId超出合法范围(大于最大允许值或者小于0),则抛出异常 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 +81,38 @@ public class IdGen this.datacenterId = datacenterId; } + // 生成下一个唯一ID的方法,是整个类的核心方法,需要保证线程安全(使用了synchronized关键字) public synchronized String nextId() { + // 获取当前时间戳(以毫秒为单位) long timestamp = timeGen(); + // 如果当前时间戳小于上次生成ID的时间戳,说明系统时间可能出现了回拨情况,抛出异常,避免生成重复ID if (timestamp < lastTimestamp) { throw new RuntimeException(String.format( "Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } + // 如果当前时间戳和上次时间戳相同,表示在同一毫秒内,需要递增序列号sequence if (lastTimestamp == timestamp) { sequence = (sequence + 1) & sequenceMask; + // 如果序列号达到最大值(sequenceMask对应的最大值),则需要等待到下一个毫秒再生成ID if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); } } else { + // 如果时间戳不同,说明进入了新的一毫秒,重置序列号为0 sequence = 0L; } + // 更新上次生成ID的时间戳为当前时间戳 lastTimestamp = timestamp; + // 组合生成最终的唯一序列号,通过位运算将时间戳、数据中心ID、工作机器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的值,决定是否将suffix拼接在生成的ID上以及拼接的位置(前缀或后缀),然后返回最终的ID字符串表示形式 + return (suffix == null || suffix.trim().length() == 0)? serialNumber + "" : (flag? (new StringBuffer()).append(suffix).append(serialNumber).toString() : (new StringBuffer()).append(serialNumber).append(suffix).toString()); } + // 用于等待直到下一个毫秒的方法,通过不断获取当前时间戳,直到其大于传入的上次时间戳为止 protected long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { @@ -92,8 +121,8 @@ public class IdGen return timestamp; } + // 获取当前系统时间戳(以毫秒为单位)的方法,供其他方法调用,获取当前时间情况 protected long timeGen() { return System.currentTimeMillis(); } - -} +} \ No newline at end of file diff --git a/tamguo-common/src/main/java/com/tamguo/common/image/CaptchaUtils.java b/tamguo-common/src/main/java/com/tamguo/common/image/CaptchaUtils.java index f9246ad..db67713 100644 --- a/tamguo-common/src/main/java/com/tamguo/common/image/CaptchaUtils.java +++ b/tamguo-common/src/main/java/com/tamguo/common/image/CaptchaUtils.java @@ -24,93 +24,129 @@ import org.patchca.utils.encoder.EncoderHelper; import org.patchca.word.RandomWordFactory; /** - * 验证码工具 + * 验证码工具类,用于生成带有各种样式和效果的验证码图片,并返回对应的验证码字符内容。 + * 该类基于Patchca库来实现验证码的生成功能,通过配置不同的属性来定制验证码的样式、字体、背景等元素。 * @author ThinkGem * @version 2017年12月23日 */ public class CaptchaUtils { + // 用于生成随机数,在验证码生成过程中多处用于随机取值,比如字体颜色、噪点位置等的随机化处理 private static Random random = new Random(); + // 可配置的验证码服务对象,用于设置和管理验证码生成的各项参数及执行生成操作 private static ConfigurableCaptchaService ccs; - private static WobbleRippleFilterFactory wrff; // 摆波纹 - private static DoubleRippleFilterFactory doff; // 双波纹 - private static CurvesRippleFilterFactory crff; // 曲线波纹 - private static DiffuseRippleFilterFactory drff; // 漫纹波 - private static MarbleRippleFilterFactory mrff; // 大理石 - - private static void initialize(){ - if (ccs == null){ - synchronized (CaptchaUtils.class) { - if (ccs == null){ - // 配置初始化 - ccs = new ConfigurableCaptchaService(); - - // 设置图片大小 - ccs.setWidth(100); - ccs.setHeight(28); - - // 设置文字数量 - RandomWordFactory wf = new RandomWordFactory(); - wf.setCharacters("ABDEFGHKMNRSWX2345689"); - wf.setMinLength(4); - wf.setMaxLength(4); - ccs.setWordFactory(wf); - - // 设置字体大小 - RandomFontFactory ff = new RandomFontFactory(); - ff.setMinSize(28); - ff.setMaxSize(28); - ccs.setFontFactory(ff); - - // 设置文字渲染边距 - BestFitTextRenderer tr = new BestFitTextRenderer(); - tr.setTopMargin(3); - tr.setRightMargin(3); - tr.setBottomMargin(3); - tr.setLeftMargin(3); + // 摆波纹滤镜工厂对象,用于创建摆波纹效果的滤镜,应用在验证码图片上以增加干扰效果和美观度 + private static WobbleRippleFilterFactory wrff; + // 双波纹滤镜工厂对象,类似地,用于生成双波纹效果的滤镜 + private static DoubleRippleFilterFactory doff; + // 曲线波纹滤镜工厂对象,用于生成曲线波纹效果的滤镜 + private static CurvesRippleFilterFactory crff; + // 漫纹波滤镜工厂对象,用于生成漫纹波效果的滤镜 + private static DiffuseRippleFilterFactory drff; + // 大理石滤镜工厂对象,用于生成大理石纹理效果的滤镜 + private static MarbleRippleFilterFactory mrff; + + /** + * 初始化方法,用于初始化验证码生成相关的配置和资源,采用了双重检查锁定(Double-Checked Locking)的单例模式来确保ConfigurableCaptchaService对象只被初始化一次。 + * 在多线程环境下,保证只有一个线程进行初始化操作,其他线程等待初始化完成后使用。 + */ + private static void initialize() { + if (ccs == null) { + // 使用类级别的锁,确保在多线程环境下对初始化代码块的互斥访问,防止多次初始化 + synchronized (CaptchaUtils.class) { + if (ccs == null) { + // 实例化ConfigurableCaptchaService对象,用于后续配置和生成验证码 + ccs = new ConfigurableCaptchaService(); + + // 设置验证码图片的宽度和高度,单位为像素,这里设置宽度为100像素,高度为28像素 + ccs.setWidth(100); + ccs.setHeight(28); + + // 配置随机文字生成工厂,用于确定验证码中出现的字符范围、长度等信息 + RandomWordFactory wf = new RandomWordFactory(); + // 设置验证码文字可使用的字符集合,排除了容易混淆的字符,提高验证码的辨识度 + wf.setCharacters("ABDEFGHKMNRSWX2345689"); + // 设置验证码文字的最小长度为4个字符 + wf.setMinLength(4); + // 设置验证码文字的最大长度也为4个字符,保证生成的验证码长度固定为4 + wf.setMaxLength(4); + ccs.setWordFactory(wf); + + // 配置随机字体生成工厂,用于设置验证码文字的字体大小范围 + RandomFontFactory ff = new RandomFontFactory(); + // 设置字体的最小尺寸为28像素,确保字体大小合适且相对统一 + ff.setMinSize(28); + // 设置字体的最大尺寸为28像素,与最小尺寸相同,保证字体大小固定 + ff.setMaxSize(28); + ccs.setFontFactory(ff); + + // 配置文字渲染器,用于设置验证码文字在图片上的边距,确保文字显示位置合适且美观 + BestFitTextRenderer tr = new BestFitTextRenderer(); + // 设置文字距离图片顶部的边距为3像素 + tr.setTopMargin(3); + // 设置文字距离图片右侧的边距为3像素 + tr.setRightMargin(3); + // 设置文字距离图片底部的边距为3像素 + tr.setBottomMargin(3); + // 设置文字距离图片左侧的边距为3像素 + tr.setLeftMargin(3); ccs.setTextRenderer(tr); - - // 设置字体颜色 - ccs.setColorFactory(new ColorFactory() { - @Override - public Color getColor(int x) { - int r = random.nextInt(90); - int g = random.nextInt(90); - int b = random.nextInt(90); - return new Color(r, g, b); - } - }); - - // 设置背景 - ccs.setBackgroundFactory(new BackgroundFactory() { + + // 配置字体颜色工厂,用于随机生成验证码文字的颜色,颜色取值范围在较浅的色彩区间内,以保证对比度和可读性 + ccs.setColorFactory(new ColorFactory() { + @Override + public Color getColor(int x) { + // 随机生成红色分量,范围在0 - 90之间,使得颜色相对较浅 + int r = random.nextInt(90); + // 随机生成绿色分量,范围在0 - 90之间,同样保证较浅的颜色 + int g = random.nextInt(90); + // 随机生成蓝色分量,范围在0 - 90之间 + int b = random.nextInt(90); + return new Color(r, g, b); + } + }); + + // 配置背景工厂,用于绘制验证码图片的背景,包括填充背景颜色、添加噪点和干扰线等操作,以增加验证码的复杂性和安全性 + ccs.setBackgroundFactory(new BackgroundFactory() { @Override public void fillBackground(BufferedImage image) { + // 获取图片的图形上下文对象,用于在图片上进行绘制操作 Graphics graphics = image.getGraphics(); - // 验证码图片的宽高 + // 获取验证码图片的宽度,单位为像素 int imgWidth = image.getWidth(); + // 获取验证码图片的高度,单位为像素 int imgHeight = image.getHeight(); - // 填充为白色背景 + // 设置填充颜色为白色,用于将整个验证码图片背景填充为白色 graphics.setColor(Color.WHITE); + // 使用白色填充整个图片区域,左上角坐标为(0, 0),宽度和高度为图片的实际宽高 graphics.fillRect(0, 0, imgWidth, imgHeight); - // 画 50 个噪点(颜色及位置随机) + + // 循环绘制50个噪点,噪点的颜色、位置、旋转角度和大小都是随机生成的,增加验证码的干扰性和安全性 for (int i = 0; i < 50; i++) { - // 随机颜色 - int rInt = random.nextInt(100)+50; - int gInt = random.nextInt(100)+50; - int bInt = random.nextInt(100)+50; + // 随机生成红色分量,范围在50 - 150之间,使得噪点颜色不过于浅或深,保证一定的辨识度 + int rInt = random.nextInt(100) + 50; + // 随机生成绿色分量,范围在50 - 150之间 + int gInt = random.nextInt(100) + 50; + // 随机生成蓝色分量,范围在50 - 150之间 + int bInt = random.nextInt(100) + 50; graphics.setColor(new Color(rInt, gInt, bInt)); - // 随机位置 + + // 随机生成噪点的x坐标,范围在0到图片宽度减3之间,避免超出图片边界 int xInt = random.nextInt(imgWidth - 3); + // 随机生成噪点的y坐标,范围在0到图片高度减2之间,同样避免超出边界 int yInt = random.nextInt(imgHeight - 2); - // 随机旋转角度 + // 随机生成噪点的起始旋转角度,范围在0 - 360度之间,增加噪点的随机性 int sAngleInt = random.nextInt(360); + // 随机生成噪点的结束旋转角度,范围在0 - 360度之间 int eAngleInt = random.nextInt(360); - // 随机大小 + // 随机生成噪点的宽度,范围在0 - 6像素之间,控制噪点大小 int wInt = random.nextInt(6); + // 随机生成噪点的高度,范围在0 - 6像素之间 int hInt = random.nextInt(6); - // 填充背景 + // 使用生成的参数绘制一个填充的弧形(可以看作是不规则形状的噪点),起始角度和结束角度、宽度和高度共同决定了弧形的形状和大小 graphics.fillArc(xInt, yInt, wInt, hInt, sAngleInt, eAngleInt); - // 画5条干扰线 + + // 每间隔10个噪点(即i % 10 == 0时),绘制一条干扰线,干扰线的起点是当前噪点位置,终点是图片内的随机位置,进一步增加验证码的干扰性 if (i % 10 == 0) { int xInt2 = random.nextInt(imgWidth); int yInt2 = random.nextInt(imgHeight); @@ -119,63 +155,61 @@ public class CaptchaUtils { } } }); - - // 效果初始化 - wrff = new WobbleRippleFilterFactory(); // 摆波纹 - doff = new DoubleRippleFilterFactory(); // 双波纹 - crff = new CurvesRippleFilterFactory(ccs.getColorFactory()); // 曲线波纹 - drff = new DiffuseRippleFilterFactory(); // 漫纹波 - mrff = new MarbleRippleFilterFactory(); // 大理石 - - } + + // 初始化各种滤镜工厂对象,用于后续根据随机选择为验证码图片应用不同的滤镜效果 + wrff = new WobbleRippleFilterFactory(); + doff = new DoubleRippleFilterFactory(); + crff = new CurvesRippleFilterFactory(ccs.getColorFactory()); + drff = new DiffuseRippleFilterFactory(); + mrff = new MarbleRippleFilterFactory(); + } } - } + } } /** - * 生成验证码 - * @param request - * @param response - * @throws IOException - * @return 验证码字符 + * 生成验证码的方法,通过配置好的ConfigurableCaptchaService对象生成验证码图片,并将其以PNG格式输出到指定的输出流中,同时返回验证码对应的字符内容。 + * + * @param outputStream 用于输出验证码图片数据的输出流,通常可以是HttpServletResponse的输出流或者文件输出流等,将验证码图片以PNG格式写入该流。 + * @throws IOException 如果在生成验证码图片或者向输出流写入数据过程中出现I/O异常,则抛出该异常。 + * @return 返回生成的验证码字符内容,即图片中显示的文本信息,供后续验证等操作使用。 */ - public static String generateCaptcha(OutputStream outputStream) throws IOException{ - - // 初始化设置 + public static String generateCaptcha(OutputStream outputStream) throws IOException { + // 调用初始化方法,确保验证码生成相关的配置和资源已正确初始化 initialize(); - - // 随机选择一个样式 - switch (random.nextInt(3)) { - case 0: - ccs.setFilterFactory(wrff); // 摆波纹 - break; - case 1: - ccs.setFilterFactory(doff); // 双波纹 - break; - case 2: - ccs.setFilterFactory(crff); // 曲线波纹 - break; - case 3: - ccs.setFilterFactory(drff); // 漫纹波 - break; - case 4: - ccs.setFilterFactory(mrff); // 大理石 - break; + + // 随机选择一个滤镜样式(共5种可选),并将对应的滤镜工厂设置到ConfigurableCaptchaService中,用于为验证码图片添加相应的滤镜效果 + switch (random.nextInt(5)) { + case 0: + ccs.setFilterFactory(wrff); // 摆波纹 + break; + case 1: + ccs.setFilterFactory(doff); // 双波纹 + break; + case 2: + ccs.setFilterFactory(crff); // 曲线波纹 + break; + case 3: + ccs.setFilterFactory(drff); // 漫纹波 + break; + case 4: + ccs.setFilterFactory(mrff); // 大理石 + break; } - - // 生成验证码 - String s = EncoderHelper.getChallangeAndWriteImage(ccs, "png", outputStream); + + // 使用EncoderHelper工具类,结合已配置好的ConfigurableCaptchaService对象,生成验证码图片并将其以PNG格式写入指定的输出流中,同时返回验证码的字符内容 + String s = EncoderHelper.getChallangeAndWriteImage(ccs, "png", outputStream); // System.out.println(s); - + return s; } -// public static void main(String[] args) throws IOException { +// public static void main(String[] args) throws IOException { // -// FileOutputStream fos = new FileOutputStream("x:\\captcha.png"); -// String s = generateCaptcha(fos); -// System.out.println(s); -// fos.close(); +// FileOutputStream fos = new FileOutputStream("x:\\captcha.png"); +// String s = generateCaptcha(fos); +// System.out.println(s); +// fos.close(); // -// } -} +// } +} \ No newline at end of file diff --git a/tamguo-common/src/main/java/com/tamguo/common/serialize/ObjectUtil.java b/tamguo-common/src/main/java/com/tamguo/common/serialize/ObjectUtil.java index 29ae18f..55290ea 100644 --- a/tamguo-common/src/main/java/com/tamguo/common/serialize/ObjectUtil.java +++ b/tamguo-common/src/main/java/com/tamguo/common/serialize/ObjectUtil.java @@ -6,9 +6,19 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +// ObjectUtil类继承自SerializeTranscoder类,主要用于对象的序列化、反序列化以及对象相等性比较的相关操作 public class ObjectUtil extends SerializeTranscoder { + + /** + * 对传入的对象进行序列化操作,将对象转换为字节数组。 + * 如果传入的对象为null,会抛出空指针异常,因为无法对null对象进行序列化。 + * + * @param value 要进行序列化的对象,不能为null。 + * @return 返回序列化后的字节数组,如果序列化过程中出现I/O异常,则会抛出包含异常信息的非法参数异常。 + */ @Override public byte[] serialize(Object value) { + // 若传入的对象为null,直接抛出空指针异常,明确提示不能序列化null对象 if (value == null) { throw new NullPointerException("Can't serialize null"); } @@ -16,54 +26,93 @@ public class ObjectUtil extends SerializeTranscoder { ByteArrayOutputStream bos = null; ObjectOutputStream os = null; try { + // 创建一个ByteArrayOutputStream,用于将对象序列化后的字节数据写入其中 bos = new ByteArrayOutputStream(); + // 使用ByteArrayOutputStream创建ObjectOutputStream,ObjectOutputStream用于将对象转换为字节流进行序列化 os = new ObjectOutputStream(bos); + // 将传入的对象写入到ObjectOutputStream中,触发对象的序列化过程 os.writeObject(value); + // 关闭ObjectOutputStream,释放相关资源 os.close(); + // 关闭ByteArrayOutputStream,释放相关资源 bos.close(); + // 将ByteArrayOutputStream中的字节数据转换为字节数组,作为序列化的最终结果 result = bos.toByteArray(); } catch (IOException e) { + // 如果在序列化过程中出现I/O异常,抛出包含异常信息的非法参数异常,提示对象不可序列化 throw new IllegalArgumentException("Non-serializable object", e); } finally { + // 调用close方法关闭ObjectOutputStream,确保资源被正确释放,即使出现异常也能执行 close(os); + // 调用close方法关闭ByteArrayOutputStream,确保资源被正确释放 close(bos); } return result; } + /** + * 对传入的字节数组进行反序列化操作,将字节数组转换回对象。 + * 如果字节数组为null,则返回null。如果反序列化过程中出现I/O异常或类未找到异常,会打印异常堆栈信息。 + * + * @param in 要进行反序列化的字节数组,可能为null。 + * @return 返回反序列化后的对象,如果出现异常则返回null(若字节数组本身为null也返回null)。 + */ @Override public Object deserialize(byte[] in) { Object result = null; ByteArrayInputStream bis = null; ObjectInputStream is = null; try { - if (in != null) { + // 首先判断传入的字节数组是否为null,如果为null则直接返回null,不进行后续反序列化操作 + if (in!= null) { + // 创建ByteArrayInputStream,将传入的字节数组作为数据源,用于后续反序列化读取字节数据 bis = new ByteArrayInputStream(in); + // 使用ByteArrayInputStream创建ObjectInputStream,ObjectInputStream用于从字节流中读取并还原对象 is = new ObjectInputStream(bis); + // 从ObjectInputStream中读取对象,触发反序列化过程,将字节数据还原为对象 result = is.readObject(); + // 关闭ObjectInputStream,释放相关资源 is.close(); + // 关闭ByteArrayInputStream,释放相关资源 bis.close(); } } catch (IOException e) { + // 如果在反序列化过程中出现I/O异常,打印异常堆栈信息,方便排查问题,但继续执行后续代码,最终返回null e.printStackTrace(); } catch (ClassNotFoundException e) { + // 如果在反序列化过程中出现找不到对应类的异常(例如序列化的对象类在反序列化时类路径下不存在了),打印异常堆栈信息,继续执行后续代码,最终返回null e.printStackTrace(); } finally { + // 调用close方法关闭ObjectInputStream,确保资源被正确释放,即使出现异常也能执行 close(is); + // 调用close方法关闭ByteArrayInputStream,确保资源被正确释放 close(bis); } return result; } + /** + * 比较两个对象是否相等的静态方法,用于判断两个对象在逻辑上是否相等。 + * 首先判断两个对象是否是同一个对象(内存地址相同),若是则返回true。 + * 如果其中一个对象为null,则返回false。 + * 若两个对象都不为null,则调用对象的equals方法来判断它们是否相等。 + * + * @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,表示两个对象不相等 return false; } else { + // 两个对象都不为null时,调用对象的equals方法来判断它们是否相等,并返回结果 return o1.equals(o2); } } -} +} \ No newline at end of file diff --git a/tamguo-common/src/main/java/com/tamguo/common/serialize/SerializeTranscoder.java b/tamguo-common/src/main/java/com/tamguo/common/serialize/SerializeTranscoder.java index 2920ee9..6bc77e4 100644 --- a/tamguo-common/src/main/java/com/tamguo/common/serialize/SerializeTranscoder.java +++ b/tamguo-common/src/main/java/com/tamguo/common/serialize/SerializeTranscoder.java @@ -2,19 +2,45 @@ package com.tamguo.common.serialize; import java.io.Closeable; +// SerializeTranscoder是一个抽象类,用于定义对象序列化和反序列化相关操作的抽象方法,同时提供了一个关闭资源的通用方法。 +// 它作为一种抽象的规范,子类需要实现具体的序列化和反序列化逻辑,可用于在不同的序列化场景下进行统一的操作处理。 public abstract class SerializeTranscoder { + /** + * 抽象方法,用于将对象序列化为字节数组。 + * 具体的序列化逻辑由子类去实现,不同的序列化方式(如Java原生序列化、JSON序列化等)会有不同的实现细节。 + * + * @param value 要进行序列化的对象,具体类型由调用者传入,子类在实现时需要考虑如何处理各种类型的对象。 + * @return 返回序列化后的字节数组,代表了传入对象在序列化后的二进制表示形式。 + */ public abstract byte[] serialize(Object value); + /** + * 抽象方法,用于将字节数组反序列化为对象。 + * 同样,具体的反序列化逻辑需要子类根据实际采用的序列化机制来实现,以将字节数组还原为对应的对象。 + * + * @param in 要进行反序列化的字节数组,其中包含了之前序列化后的对象数据,子类需要按照相应的规则解析并还原对象。 + * @return 返回反序列化后的对象,其类型取决于之前序列化时的原始对象类型以及具体的反序列化实现逻辑。 + */ public abstract Object deserialize(byte[] in); + /** + * 用于关闭实现了Closeable接口的资源的方法,比如输入输出流等在序列化和反序列化过程中经常使用到的资源。 + * 该方法会捕获关闭资源时可能抛出的异常,并仅打印异常堆栈信息,以避免异常向上传播导致程序中断(通常是一种较为保守的资源释放处理方式)。 + * + * @param closeable 要关闭的实现了Closeable接口的对象,例如流对象等,若传入null则不进行任何操作直接返回。 + */ public void close(Closeable closeable) { - if (closeable != null) { + // 首先判断传入的要关闭的资源对象是否为null,如果为null则不需要进行关闭操作,直接返回 + if (closeable!= null) { try { + // 调用Closeable接口定义的close方法来关闭资源,释放相关的系统资源(如文件句柄、网络连接等,具体取决于资源类型) closeable.close(); } catch (Exception e) { + // 如果在关闭资源过程中出现任何异常(例如文件已被删除导致关闭流失败等情况),则打印异常堆栈信息,方便排查问题, + // 但不会将异常继续向上抛出,避免影响程序的整体运行逻辑(除非上层代码明确需要处理这种关闭资源的异常情况) e.printStackTrace(); } } } -} +} \ No newline at end of file diff --git a/tamguo-common/src/main/java/com/tamguo/common/utils/AbstractRunningLogHandler.java b/tamguo-common/src/main/java/com/tamguo/common/utils/AbstractRunningLogHandler.java index f82fa18..0f6e8bd 100644 --- a/tamguo-common/src/main/java/com/tamguo/common/utils/AbstractRunningLogHandler.java +++ b/tamguo-common/src/main/java/com/tamguo/common/utils/AbstractRunningLogHandler.java @@ -4,81 +4,118 @@ import java.io.InterruptedIOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +// AbstractRunningLogHandler是一个抽象类,实现了LogHandler接口(从代码结构推测,虽然这里没展示LogHandler接口的定义内容), +// 主要用于处理日志相关操作,重点提供了获取运行时调用栈信息以及一些抽象的日志记录方法,子类可以根据具体需求重写这些日志记录方法来实现不同的日志处理逻辑。 public abstract class AbstractRunningLogHandler implements LogHandler { + // 通过反射获取Throwable类的getStackTrace方法,用于获取异常的调用栈信息,初始化为null,在静态代码块中尝试进行初始化。 private static Method getStackTraceMethod; + // 通过反射获取StackTraceElement类的getClassName方法,用于获取栈元素对应的类名,初始化为null,同样在静态代码块中初始化。 private static Method getClassNameMethod; + // 通过反射获取StackTraceElement类的getMethodName方法,用于获取栈元素对应的方法名,初始化为null,在静态代码块里进行初始化操作。 private static Method getMethodNameMethod; + // 通过反射获取StackTraceElement类的getFileName方法,用于获取栈元素对应的文件名,初始化为null,会在静态块中尝试初始化。 private static Method getFileNameMethod; + // 通过反射获取StackTraceElement类的getLineNumber方法,用于获取栈元素对应的行号,初始化为null,依靠静态块来初始化。 private static Method getLineNumberMethod; - + + // 静态代码块,在类加载时执行,用于通过反射获取相关的方法对象,以便后续在获取调用栈信息等操作中使用。 + // 如果在获取方法过程中出现ClassNotFoundException(比如对应的类找不到,可能是运行环境问题)或者NoSuchMethodException(要获取的方法不存在)异常, + // 则会在日志中记录将使用JDK 1.4之前的方法来确定位置相关信息(这里只是简单记录提示,具体如何使用旧方法后续代码中并未明确体现)。 static { try { + // 定义一个表示无参数的Class数组,用于指定获取方法时的参数类型情况,这里获取的方法都无参数,所以初始化为null。 Class[] noArgs = null; + // 通过反射获取Throwable类的getStackTrace方法,后续可利用此方法获取异常的调用栈信息。 getStackTraceMethod = Throwable.class.getMethod("getStackTrace", noArgs); + // 通过反射获取StackTraceElement类,因为后续要获取它里面的多个方法,所以先获取类对象。 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异常,记录日志提示将使用pre-JDK 1.4方法来确定位置,这里的LogDebug.debug应该是自定义的日志记录方法(具体实现未展示)。 LogDebug.debug("will use pre-JDK 1.4 methods to determine location."); } catch (NoSuchMethodException ex) { + // 如果出现NoSuchMethodException异常,同样记录日志提示使用pre-JDK 1.4方法来确定位置。 LogDebug.debug("will use pre-JDK 1.4 methods to determine location."); } } - + /** - * 获得指定class的StackTraceElement - * - * @param t - * @param fqnOfCallingClass - * - * @return + * 获得指定class的StackTraceElement方法,用于从给定的Throwable对象的调用栈中查找与指定类(通过全限定名指定)相关的栈元素信息, + * 如果能通过反射成功获取到相关方法(在静态代码块中初始化的那些方法),则尝试解析出对应的类名、方法名、文件名和行号等信息并封装成StackTraceElement对象返回; + * 如果在反射调用过程中出现异常(如非法访问、调用目标异常等),则记录相应日志并返回默认的StackTraceElement对象(通过createDefaultStackTrace方法创建)。 + * + * @param t 传入的Throwable对象,从中获取调用栈信息,通常是在出现异常情况时传递进来的异常实例。 + * @param fqnOfCallingClass 要查找的栈元素对应的类的全限定名,用于在调用栈中定位到特定类相关的栈元素。 + * @return 返回找到的与指定类相关的StackTraceElement对象,如果查找失败或者反射调用出现问题则返回默认创建的StackTraceElement对象。 */ protected StackTraceElement getRunningStackTrace(Throwable t, String fqnOfCallingClass) { - if (getLineNumberMethod != null) { + if (getLineNumberMethod!= null) { try { + // 定义一个表示无参数的Object数组,用于在反射调用方法时传递参数(这里调用的方法都无参数,所以初始化为null)。 Object[] noArgs = null; + // 通过反射调用Throwable的getStackTrace方法,获取异常的调用栈元素数组,返回的是Object数组,实际元素类型是StackTraceElement,需要后续进行类型转换处理。 Object[] elements = (Object[]) getStackTraceMethod.invoke(t, noArgs); + // 从后往前遍历调用栈元素数组,通常是希望找到离当前调用最近的与指定类相关的栈元素,因为栈的顺序是从外层调用逐步进入内层调用的。 for (int i = elements.length - 1; i >= 0; i--) { + // 通过反射调用StackTraceElement的getClassName方法,获取当前栈元素对应的类的全限定名,并转换为String类型进行比较。 String thisClass = (String) getClassNameMethod.invoke(elements[i], noArgs); + // 判断当前栈元素的类名是否与传入要查找的类的全限定名相等,如果相等则表示找到了目标栈元素,开始解析并封装相关信息。 if (fqnOfCallingClass.equals(thisClass)) { - // 执行class名称 + // 获取并记录目标栈元素对应的类名,这里直接使用传入的fqnOfCallingClass作为类名,因为前面已经验证相等了。 String className = fqnOfCallingClass; - // 执行方法名称 + // 通过反射调用StackTraceElement的getMethodName方法,获取目标栈元素对应的方法名,并转换为String类型进行记录。 String methodName = (String) getMethodNameMethod.invoke(elements[i], noArgs); - // 执行class文件名称 + // 通过反射调用StackTraceElement的getFileName方法,获取目标栈元素对应的文件名,并转换为String类型进行记录。 String fileName = (String) getFileNameMethod.invoke(elements[i], noArgs); - // 执行到行号 + // 通过反射调用StackTraceElement的getLineNumber方法,获取目标栈元素对应的行号,并转换为int类型进行记录,注意这里做了类型转换,将Integer对象转换为基本数据类型int。 int lineNumber = ((Integer) getLineNumberMethod.invoke(elements[i], noArgs)).intValue(); + // 使用获取到的类名、方法名、文件名和行号信息创建并返回一个StackTraceElement对象,用于表示找到的目标栈元素信息。 return new StackTraceElement(className, methodName, fileName, lineNumber); } } } catch (IllegalAccessException ex) { + // 如果在反射调用过程中出现非法访问异常(比如方法不可访问等原因),记录日志提示使用JDK 1.4方法失败,并传入异常对象方便排查问题,这里的日志记录方式同样是通过LogDebug.debug(具体实现未展示)。 LogDebug.debug("failed using JDK 1.4 methods", ex); } catch (InvocationTargetException ex) { + // 如果在反射调用过程中出现调用目标异常,需要进一步判断异常的目标异常类型, + // 如果目标异常是InterruptedException(线程中断异常)或者InterruptedIOException(I/O操作被中断异常), + // 则重新设置当前线程的中断状态,以符合Java中断机制的处理规范,然后记录日志提示使用JDK 1.4方法失败,并传入异常对象用于排查问题。 if (ex.getTargetException() instanceof InterruptedException || ex.getTargetException() instanceof InterruptedIOException) { Thread.currentThread().interrupt(); } LogDebug.debug("failed using JDK 1.4 methods", ex); } catch (RuntimeException ex) { + // 如果在反射调用过程中出现运行时异常,记录日志提示使用JDK 1.4方法失败,并传入异常对象用于排查问题。 LogDebug.debug("failed using JDK 1.4 methods", ex); } } + // 如果前面获取目标StackTraceElement对象失败(比如反射方法获取失败或者遍历调用栈没找到对应元素等情况),则调用createDefaultStackTrace方法创建并返回默认的StackTraceElement对象。 return this.createDefaultStackTrace(); } - + /** - * 创建默认StackTraceElement - * - * @return + * 创建默认的StackTraceElement对象的方法,用于在无法获取到真实的调用栈元素信息时返回一个默认的占位对象。 + * 这里创建的默认StackTraceElement对象使用当前类(AbstractRunningLogHandler)的类名作为类名、"log"作为方法名、当前类名作为文件名,行号设置为0。 + * + * @return 返回创建的默认StackTraceElement对象,用于在特定情况下作为默认的调用栈元素表示。 */ private StackTraceElement createDefaultStackTrace() { return new StackTraceElement(this.getClass().getName(), "log", this.getClass().getName(), 0); } + // 以下是一系列抽象的日志记录方法,由实现LogHandler接口而来,这些方法在AbstractRunningLogHandler类中没有具体实现逻辑, + // 子类需要根据具体的日志记录需求(比如记录到文件、输出到控制台、发送到远程日志服务器等)重写这些方法来实现不同级别的日志记录功能(info、error、debug、warning等), + // 每个方法都接收一个消息字符串以及要记录日志对应的类的全限定名作为参数,部分重载方法还额外接收一个Throwable对象用于记录异常相关的日志信息。 + @Override public void info(String msg, String fqnOfCallingClass) { } @@ -110,5 +147,5 @@ public abstract class AbstractRunningLogHandler implements LogHandler { @Override public void warning(String msg, Throwable t, String fqnOfCallingClass) { } - -} + +} \ No newline at end of file