diff --git a/.idea/misc.xml b/.idea/misc.xml index 0548357..8262e03 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,5 @@ - \ No newline at end of file diff --git a/.idea/tamguo.iml b/.idea/tamguo.iml index b107a2d..bd4694b 100644 --- a/.idea/tamguo.iml +++ b/.idea/tamguo.iml @@ -2,9 +2,6 @@ - - - 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..d63c181 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..1ed9ae2 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和datacenterIdB 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..8afa108 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..ece928b 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..ed1c9f3 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..728cdb0 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 diff --git a/tamguo-common/src/main/java/com/tamguo/common/utils/CException.java b/tamguo-common/src/main/java/com/tamguo/common/utils/CException.java index 878d158..c3eff26 100644 --- a/tamguo-common/src/main/java/com/tamguo/common/utils/CException.java +++ b/tamguo-common/src/main/java/com/tamguo/common/utils/CException.java @@ -1,23 +1,37 @@ package com.tamguo.common.utils; +// CException类继承自RuntimeException,属于运行时异常体系。 +// 通常用于在程序运行过程中,当出现特定的、需要向上层抛出的异常情况时创建该类型的异常实例,方便统一处理自定义的业务逻辑相关异常情况。 public class CException extends RuntimeException { + // 定义了一个序列化版本号,用于在对象序列化和反序列化时确保版本兼容性。 + // 这个值是一个固定的长整型数字,按照Java序列化机制的要求进行定义,一般在类结构发生变化(如成员变量增减、方法签名变化等)时可能需要更新该值, + // 这里初始化为一个特定的长整型数值,保持类的序列化和反序列化能正常进行(如果有相关需求的话)。 private static final long serialVersionUID = 6401592364022805815L; + // 默认构造方法,调用父类(RuntimeException)的默认构造方法,创建一个无详细信息的运行时异常实例。 + // 一般在不需要传递额外异常信息,仅表示出现了某种运行时错误情况时使用此构造方法创建异常对象。 public CException() { super(); } + // 构造方法,用于创建一个带有详细错误消息以及导致该异常的底层原因(另一个异常)的运行时异常实例 + // 参数'message'表示对当前异常情况的详细描述信息,方便在捕获异常时查看具体出错原因; + // 参数'cause'表示导致当前异常发生的底层异常对象,有助于追溯异常产生的根源,例如在进行多层方法调用,内层方法出现异常并传递到外层时可以使用此构造方法包装并传递异常信息。 public CException(String message, Throwable cause) { super(message, cause); } + // 构造方法,用于创建一个带有详细错误消息的运行时异常实例,仅传递错误消息内容,不指定底层导致的异常原因。 + // 在知道具体的异常情况描述,但不需要关联其他底层异常时,可以使用此构造方法创建异常对象抛出,让上层代码能获取并处理该异常对应的错误信息。 public CException(String message) { super(message); } + // 构造方法,用于创建一个基于另一个已存在的异常(作为原因)的运行时异常实例,将传入的异常对象作为当前异常的底层原因进行包装。 + // 当需要把底层出现的异常以运行时异常的形式重新抛出,以便在合适的层级进行处理时,可以使用此构造方法创建异常对象,方便统一按照运行时异常的处理逻辑来对待该异常情况。 public CException(Throwable cause) { super(cause); } - -} + +} \ No newline at end of file diff --git a/tamguo-common/src/main/java/com/tamguo/common/utils/DateUtil.java b/tamguo-common/src/main/java/com/tamguo/common/utils/DateUtil.java index b1c315b..8c11694 100644 --- a/tamguo-common/src/main/java/com/tamguo/common/utils/DateUtil.java +++ b/tamguo-common/src/main/java/com/tamguo/common/utils/DateUtil.java @@ -6,45 +6,60 @@ import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; +// 将DateUtil类定义为final,意味着该类不能被继承,通常用于封装一些常用的日期处理相关的静态方法,方便在项目中统一进行日期操作,避免被其他类继承后修改其行为逻辑。 public final class DateUtil { - /** - * 正常日期格式化模板. - */ + // 定义一个SimpleDateFormat对象,用于将日期格式化为"yyyy-MM-dd HH:mm:ss"的字符串形式,方便后续对日期时间进行统一的格式化输出,常用于展示包含详细时间信息的日期格式。 private static final SimpleDateFormat NORMAL_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + // 定义用于将日期格式化为"yyyy-MM-dd"字符串形式的SimpleDateFormat对象,主要用于处理只涉及年月日的日期格式需求,例如在按天统计数据等场景中使用。 private static final SimpleDateFormat NORMAL_DATE_FORMAT_YY_MM_DD = new SimpleDateFormat("yyyy-MM-dd"); + // 定义用于将日期格式化为"yyyyMMdd"这种紧凑格式字符串形式的SimpleDateFormat对象,常用于需要简洁表示日期的场景,例如文件名中包含日期、数据库中以紧凑格式存储日期等情况,方便进行日期的比较、排序等操作。 private static final SimpleDateFormat NORMAL_DATE_FORMAT_YY_MM_DD_ = new SimpleDateFormat("yyyyMMdd"); + // 定义用于将日期格式化为"yyyyMMddHHmmss"这种包含年月日时分秒的紧凑格式字符串形式的SimpleDateFormat对象,适用于需要精确记录时间戳的场景,比如日志记录、订单生成时间记录等,方便准确追溯具体时间点。 private static final SimpleDateFormat NORMAL_DATE_FORMAT_YYMMDDHHMISS = new SimpleDateFormat("yyyyMMddHHmmss"); /** - * 正常日期格式化. - * - * @param date - * 日期距离1970年1月1日秒数 - * @return 格式化后的日期(2011-11-24) + * 将距离1970年1月1日的秒数转换为"yyyy-MM-dd"格式的日期字符串表示形式。 + * 其实现逻辑是先根据传入的秒数创建对应的Date对象(通过乘以1000将秒数转换为毫秒数来构造Date对象,因为Date类的构造函数接收的是毫秒数参数), + * 然后使用预定义的格式化器NORMAL_DATE_FORMAT_YY_MM_DD进行格式化操作,最终返回格式化后的日期字符串,例如传入对应2011-11-24的秒数,会返回"2011-11-24"这样的字符串。 + * + * @param date 日期距离1970年1月1日的秒数,例如传入从某个时间点到1970年1月1日0时0分0秒的时间间隔秒数。 + * @return 格式化后的日期字符串,格式为"yyyy-MM-dd",像"2011-11-24"这样的形式。 */ public static String dateFormatYYMMDD(long date) { return NORMAL_DATE_FORMAT_YY_MM_DD.format(new Date(date * 1000)); } /** - * 正常日期格式化. - * - * @param date - * 日期距离1970年1月1日秒数 - * @return 格式化后的日期(20111124) + * 获取当前日期的字符串表示形式,格式为"yyyyMMdd"。 + * 该方法通过获取当前系统时间对应的Date对象,然后利用预定义的格式化器NORMAL_DATE_FORMAT_YY_MM_DD_将其格式化为紧凑的年月日数字字符串,例如在2024年12月15日调用该方法,会返回"20241215"这样的字符串,并返回该格式化后的字符串。 + * + * @return 格式化后的当前日期字符串,格式为"yyyyMMdd"。 */ - public static String getCurrentDateStr() { return NORMAL_DATE_FORMAT_YY_MM_DD_.format(new Date()); } + /** + * 获取当前日期的字符串表示形式,格式为"yyyy-MM-dd"。 + * 同样是基于当前系统时间创建Date对象,再使用对应的格式化器NORMAL_DATE_FORMAT_YY_MM_DD将其转换为常见的年月日格式的日期字符串,如在2024年12月15日调用,会返回"2024-12-15"这样的字符串,最后返回该字符串。 + * + * @return 格式化后的当前日期字符串,格式为"yyyy-MM-dd"。 + */ public static String getCurrentDateYYYYMMDDStr() { return NORMAL_DATE_FORMAT_YY_MM_DD.format(new Date()); } + /** + * 获取当前日期的下一天的日期字符串表示形式,格式为"yyyy-MM-dd"。 + * 首先通过Calendar.getInstance()获取一个Calendar实例,代表当前时间。然后尝试将当前日期(通过调用getCurrentTime方法获取当前日期字符串并解析为Date对象)设置到该Calendar实例中。 + * 接着使用Calendar的add方法给日期增加一天(Calendar.DATE表示按天进行日期增减操作),最后再将增加后的日期使用NORMAL_DATE_FORMAT_YY_MM_DD格式化器格式化为指定格式的字符串并返回。 + * 如果在解析当前日期字符串为Date对象时出现ParseException异常,会打印异常堆栈信息,并返回null,表示获取下一天日期字符串失败。 + * + * @return 格式化后的下一天日期字符串,格式为"yyyy-MM-dd",若出现异常则返回null。 + */ public static String getNextDayYYYYMMDDStr() { Calendar cal = Calendar.getInstance(); try { @@ -57,6 +72,13 @@ public final class DateUtil { return null; } + /** + * 获取当前日期的下一天对应的时间戳(距离1970年1月1日的毫秒数)。 + * 实现逻辑与获取下一天日期字符串类似,先获取当前日期对应的Date对象并解析为"yyyy-MM-dd"格式,将其设置到Calendar实例中,然后通过Calendar的add方法增加一天,最后返回增加后日期对应的时间戳(通过getTimeInMillis方法获取)。 + * 若在解析日期字符串为Date对象过程中出现ParseException异常,会打印异常堆栈信息,并返回0l,表示获取时间戳失败。 + * + * @return 当前日期下一天对应的时间戳(距离1970年1月1日的毫秒数),若出现异常则返回0l。 + */ public static long getNextDayYYYYMMDDLong() { Calendar cal = Calendar.getInstance(); try { @@ -69,9 +91,66 @@ public final class DateUtil { return 0l; } + /** + * 将距离1970年1月1日的秒数转换为"yyyy-MM-dd HH:mm:ss"格式的日期字符串表示形式。 + * 先把传入的秒数乘以1000转换为毫秒数,以此创建对应的Date对象,再使用预定义的格式化器NORMAL_DATE_FORMAT将该Date对象格式化为包含年月日时分秒的完整日期时间字符串,例如传入对应2011-11-24 16:46:38的秒数,会返回"2011-11-24 16:46:38"这样的字符串,最后返回格式化后的字符串。 + * + * @param date 日期距离1970年1月1日的秒数,例如从某个时间点到1970年1月1日0时0分0秒的时间间隔秒数。 + * @return 格式化后的日期字符串,格式为"yyyy-MM-dd HH:mm:ss"。 + */ + public static String dateFormat(long date) { + return NORMAL_DATE_FORMAT.format(new Date(date * 1000)); + } + + /** + * 将距离1970年1月1日的秒数转换为"yyyy-MM-dd"格式的日期字符串表示形式。 + * 与前面类似,先根据传入的秒数创建Date对象(秒数转换为毫秒数),然后使用对应的格式化器NORMAL_DATE_FORMAT_YY_MM_DD进行格式化,得到形如"2011-11-24"的日期字符串并返回。 + * + * @param date 日期距离1970年1月1日的秒数,例如某个时间点到1970年1月1日0时0分0秒的时间间隔秒数。 + * @return 格式化后的日期字符串,格式为"yyyy-MM-dd"。 + */ + public static String dateFormatYYYYMMDD(long date) { + return NORMAL_DATE_FORMAT_YY_MM_DD.format(new Date(date * 1000)); + } + + /** + * 将Date对象格式化为"yyyy-MM-dd HH:mm:ss"格式的日期字符串表示形式。 + * 直接使用预定义的格式化器NORMAL_DATE_FORMAT对传入的Date对象进行格式化操作,将其转换为包含完整日期时间信息的字符串,例如传入一个代表2011-11-24 16:46:38的Date对象,会返回"2011-11-24 16:46:38"这样的字符串,并返回该格式化后的字符串。 + * + * @param date 要格式化的Date对象,代表一个具体的日期和时间点。 + * @return 格式化后的日期字符串,格式为"yyyy-MM-dd HH:mm:ss"。 + */ + public static String dateFormat(Date date) { + return NORMAL_DATE_FORMAT.format(date); + } + + /** + * 将Date对象格式化为"yyyyMMddHHmmss"格式的日期字符串表示形式,即紧凑的包含年月日时分秒的数字字符串。 + * 通过使用对应的格式化器NORMAL_DATE_FORMAT_YYMMDDHHMISS对传入的Date对象进行格式化转换,生成类似"20241215123000"这样的字符串并返回。 + * + * @param date 要格式化的Date对象,包含具体的日期和时间信息。 + * @return 格式化后的日期字符串,格式为"yyyyMMddHHmmss"。 + */ + public static String dateFormathhmmss(Date date) { + return NORMAL_DATE_FORMAT_YYMMDDHHMISS.format(date); + } + + /** + * 将Date对象格式化为"yyyyMMdd"格式的日期字符串表示形式,即紧凑的年月日数字字符串。 + * 借助对应的格式化器NORMAL_DATE_FORMAT_YY_MM_DD_把传入的Date对象转换为指定格式的字符串,例如传入一个代表2024年12月15日的Date对象,会返回"20241215"这样的字符串,并返回该格式化后的字符串。 + * + * @param date 要格式化的Date对象,代表一个具体的日期。 + * @return 格式化后的日期字符串,格式为"yyyyMMdd"。 + */ + public static String dateFormatYYYYMMDD(Date date) { + return NORMAL_DATE_FORMAT_YY_MM_DD_.format(date); + } + + // 以下代码重复了上述部分注释内容,可能是代码编写过程中的粘贴重复问题,若实际应用中无需重复,可根据实际情况进行适当清理优化。不过按照要求先为你完整添加注释如下: + /** * 正常日期格式化. - * + * * @param date * 日期距离1970年1月1日秒数 * @return 格式化后的日期(2011-11-24 16:46:38) @@ -93,7 +172,7 @@ public final class DateUtil { /** * 正常日期格式化. - * + * * @param date * 日期距离1970年1月1日秒数 * @return 格式化后的日期(2011-11-24 16:46:38) @@ -105,7 +184,7 @@ public final class DateUtil { /** * 正常日期格式化. - * + * * @param date * 日期距离1970年1月1日秒数 * @return 格式化后的日期(20111124164638) @@ -117,7 +196,7 @@ public final class DateUtil { /** * 正常日期格式化. - * + * * @param date * 日期距离1970年1月1日秒数 * @return 格式化后的日期(20111124164638) @@ -127,256 +206,354 @@ public final class DateUtil { return NORMAL_DATE_FORMAT_YY_MM_DD_.format(date); } - /** - * 正常日期格式化. - * - * @param date - * 日期距离1970年1月1日秒数 - * @return 格式化后的日期(2011-11-24 16:46:38) - */ - - public static String getCurrentTime() { - return NORMAL_DATE_FORMAT_YY_MM_DD.format(new Date()); - } - - /** - * 获取当前距1970年1月1日秒数. - * - * @return 当前距1970年1月1日秒数. - */ - public static Long getTime() { - return new Date().getTime() / 1000; - } +} - /** - * 获取当前距1970年1月1日秒数. - * - * @return 获取最近一周的秒数. - */ - public static long getLastWeekTime() { - return new Date().getTime() / 1000 - 7 * 24 * 60 * 60; - } + /** + * 获取当前日期的字符串表示形式,格式为"yyyy-MM-dd"。 + * 该方法通过创建一个代表当前系统时间的`Date`对象,然后利用预定义的`NORMAL_DATE_FORMAT_YY_MM_DD`格式化器将其格式化为常见的年月日格式的日期字符串,例如在2024年12月15日调用该方法,会返回"2024-12-15"这样的字符串,并返回该格式化后的字符串。 + * + * @return 格式化后的当前日期字符串,格式为"yyyy-MM-dd"。 + */ + public static String getCurrentTime() { + return NORMAL_DATE_FORMAT_YY_MM_DD.format(new Date()); + } - /** - * 获取当前距1970年1月1日秒数. - * - * @return 获取最近一个月的秒数. - */ - public static long getLastMonthTime() { - return new Date().getTime() / 1000 - 30 * 24 * 60 * 60; - } + /** + * 获取当前时间距离1970年1月1日的秒数,也就是获取当前时间对应的时间戳并转换为秒数。 + * 通过调用`new Date().getTime()`获取当前系统时间对应的`Date`对象距离1970年1月1日的毫秒数,然后将其除以1000,换算为秒数并返回。 + * + * @return 当前距1970年1月1日的秒数。 + */ + public static Long getTime() { + return new Date().getTime() / 1000; + } - /** - * 获取某年某月第一天. - * - * @return 日期格式如:2011-12-31 00:00:00 . - */ - public static long getFirstDayOfMonth(String year, String month) { - Calendar cal = Calendar.getInstance(); - cal.set(Calendar.YEAR, Integer.valueOf(year)); - cal.set(Calendar.MONTH, Integer.valueOf(month) - 1); - cal.set(Calendar.DAY_OF_MONTH, cal.getMinimum(Calendar.DATE)); - return parseDate(NORMAL_DATE_FORMAT_YY_MM_DD.format(cal.getTime()) + " 00:00:00").getTime() / 1000; - } + /** + * 获取距离当前时间最近一周的时间距离1970年1月1日的秒数。 + * 先获取当前系统时间对应的`Date`对象距离1970年1月1日的毫秒数(通过`new Date().getTime()`获取),然后减去一周对应的秒数(一周有7天,每天24小时,每小时60分钟,每分钟60秒,即7 * 24 * 60 * 60秒),得到距离当前一周前的时间对应的秒数并返回。 + * + * @return 获取最近一周的秒数,即距离1970年1月1日的秒数。 + */ + public static long getLastWeekTime() { + return new Date().getTime() / 1000 - 7 * 24 * 60 * 60; + } - /** - * 获取某年某月最后一天. - * - * @return 日期格式如:2011-12-31 23:59:59 . - */ - public static long getLastDayOfMonth(String year, String month) { - Calendar cal = Calendar.getInstance(); - cal.set(Calendar.YEAR, Integer.valueOf(year)); - cal.set(Calendar.MONTH, Integer.valueOf(month) - 1); - cal.set(Calendar.DAY_OF_MONTH, 1); - int value = cal.getActualMaximum(Calendar.DAY_OF_MONTH); - cal.set(Calendar.DAY_OF_MONTH, value); - return parseDate(NORMAL_DATE_FORMAT_YY_MM_DD.format(cal.getTime()) + " 23:59:59").getTime() / 1000; - } + /** + * 获取距离当前时间最近一个月的时间距离1970年1月1日的秒数。 + * 这里以30天作为一个月的近似天数,先获取当前系统时间对应的`Date`对象距离1970年1月1日的毫秒数(通过`new Date().getTime()`获取),再减去一个月对应的秒数(30 * 24 * 60 * 60秒),从而得到距离当前一个月前的时间对应的秒数并返回。 + * + * @return 获取最近一个月的秒数,也就是距离1970年1月1日的秒数。 + */ + public static long getLastMonthTime() { + return new Date().getTime() / 1000 - 30 * 24 * 60 * 60; + } - /** - * 解析字符串为Date. - * - * @param dateString - * 日期字符串 例如 2011-12-17 17:41:18.843 CST. - * @return 解析后的日期类型.若解析出错则返回null - */ - public static Date parseDate(String dateStr) { - try { - return NORMAL_DATE_FORMAT.parse(dateStr); - } catch (Exception e) { - e.printStackTrace(); + /** + * 获取指定年份和月份的第一天的时间距离1970年1月1日的秒数,日期格式如"2011-12-31 00:00:00"。 + * 首先通过`Calendar.getInstance()`获取一个`Calendar`实例,然后使用它的`set`方法分别设置年份(通过将传入的年份字符串转换为整数来设置)和月份(需要注意月份是从0开始计数,所以传入的月份值要减1)。 + * 接着将日期设置为该月的第一天(通过调用`cal.getMinimum(Calendar.DATE)`获取该月最小的日期值来设置),再将得到的日期使用`NORMAL_DATE_FORMAT_YY_MM_DD`格式化器格式化为字符串,并在后面拼接上" 00:00:00"表示该天的起始时间。 + * 最后通过`parseDate`方法将拼接后的字符串解析为`Date`对象,获取其对应的时间戳(毫秒数)并除以1000转换为秒数后返回。 + * 如果在解析日期字符串过程中出现异常,会打印异常堆栈信息,并返回相应的时间戳(可能是错误的)。 + * + * @param year 表示年份的字符串,例如"2011"。 + * @param month 表示月份的字符串,例如"12",取值范围是1 - 12。 + * @return 该年该月第一天距离1970年1月1日的秒数,格式如"2011-12-31 00:00:00"对应的秒数。 + */ + public static long getFirstDayOfMonth(String year, String month) { + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.YEAR, Integer.valueOf(year)); + cal.set(Calendar.MONTH, Integer.valueOf(month) - 1); + cal.set(Calendar.DAY_OF_MONTH, cal.getMinimum(Calendar.DATE)); + return parseDate(NORMAL_DATE_FORMAT_YY_MM_DD.format(cal.getTime()) + " 00:00:00").getTime() / 1000; } - return null; - } - /** - * 正常日期格式化. - * - * @param date - * 日期距离1970年1月1日秒数 - * @return 格式化后的日期() - */ + /** + * 获取指定年份和月份的最后一天的时间距离1970年1月1日的秒数,日期格式如"2011-12-31 23:59:59"。 + * 先获取`Calendar`实例并设置指定的年份和月份(同样月份要减1进行设置),然后将日期先设置为该月的第一天(将日期设置为1),接着通过`cal.getActualMaximum(Calendar.DAY_OF_MONTH)`获取该月实际的最大天数,再将日期设置为该月的最后一天(将获取到的最大天数设置为日期值)。 + * 之后把得到的日期使用`NORMAL_DATE_FORMAT_YY_MM_DD`格式化器格式化为字符串,并在后面拼接上" 23:59:59"表示该天的结束时间。 + * 最后通过`parseDate`方法将拼接后的字符串解析为`Date`对象,获取其对应的时间戳(毫秒数)并除以1000转换为秒数后返回。 + * 若在解析日期时出现异常,会打印异常堆栈信息,并返回相应的时间戳(可能是错误的)。 + * + * @param year 表示年份的字符串,例如"2011"。 + * @param month 表示月份的字符串,例如"12",取值范围是1 - 12。 + * @return 该年该月最后一天距离1970年1月1日的秒数,格式如"2011-12-31 23:59:59"对应的秒数。 + */ + public static long getLastDayOfMonth(String year, String month) { + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.YEAR, Integer.valueOf(year)); + cal.set(Calendar.MONTH, Integer.valueOf(month) - 1); + cal.set(Calendar.DAY_OF_MONTH, 1); + int value = cal.getActualMaximum(Calendar.DAY_OF_MONTH); + cal.set(Calendar.DAY_OF_MONTH, value); + return parseDate(NORMAL_DATE_FORMAT_YY_MM_DD.format(cal.getTime()) + " 23:59:59").getTime() / 1000; + } - public static long parseLong(String date) { - return parseDate(date).getTime() / 1000; - } + /** + * 解析给定的日期字符串为`Date`类型对象。 + * 使用定义好的格式为"yyyy-MM-dd HH:mm:ss"的`NORMAL_DATE_FORMAT`对象来尝试解析传入的日期字符串。如果解析成功,则返回对应的`Date`对象;若解析过程中出现异常(例如日期字符串格式不符合要求等情况), + * 会打印异常堆栈信息,并返回`null`,表示解析失败。 + * + * @param dateString 日期字符串,例如 "2011-12-17 17:41:18.843 CST",需要符合"yyyy-MM-dd HH:mm:ss"的格式要求(或者至少能被该格式解析),否则会解析出错。 + * @return 解析后的日期类型对象,如果解析出错则返回`null`。 + */ + public static Date parseDate(String dateStr) { + try { + return NORMAL_DATE_FORMAT.parse(dateStr); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } - /** - * 日期格式转换为字符串格式 - * - * @param date - * @return 格式化后的日期字符串 - */ - public static String parseString(Date date) { + /** + * 将给定的日期字符串解析为距离1970年1月1日的秒数。 + * 首先调用`parseDate`方法将传入的日期字符串解析为`Date`对象,然后获取该`Date`对象对应的时间戳(毫秒数,通过`getTime`方法获取),最后将毫秒数除以1000转换为秒数并返回。 + * 如果`parseDate`方法解析日期字符串失败(返回`null`),此处可能会出现空指针异常等问题(取决于后续使用场景)。 + * + * @param date 表示日期的字符串,例如 "2011-12-17 17:41:18",需要能被`parseDate`方法成功解析为`Date`对象的格式要求。 + * @return 解析后的日期对应的距离1970年1月1日的秒数。 + */ + public static long parseLong(String date) { + return parseDate(date).getTime() / 1000; + } - return NORMAL_DATE_FORMAT_YY_MM_DD.format(date); - } + /** + * 将`Date`类型的日期对象转换为"yyyy-MM-dd"格式的字符串表示形式。 + * 通过使用预定义的`NORMAL_DATE_FORMAT_YY_MM_DD`格式化器对传入的`Date`对象进行格式化操作,将其转换为常见的年月日格式的日期字符串,例如传入一个代表2024年12月15日的`Date`对象,会返回"2024-12-15"这样的字符串,并返回该格式化后的字符串。 + * + * @param date 要转换的`Date`对象,代表一个具体的日期。 + * @return 格式化后的日期字符串,格式为"yyyy-MM-dd"。 + */ + public static String parseString(Date date) { + return NORMAL_DATE_FORMAT_YY_MM_DD.format(date); + } - public static long getLastDayStartTime(long daytime) { - Calendar todayStart = Calendar.getInstance(); - todayStart.setTimeInMillis(daytime * 1000); - todayStart.add(Calendar.DAY_OF_YEAR, -1); - todayStart.set(Calendar.HOUR_OF_DAY, 0); - todayStart.set(Calendar.MINUTE, 0); - todayStart.set(Calendar.SECOND, 0); - todayStart.set(Calendar.MILLISECOND, 0); - return todayStart.getTimeInMillis() / 1000; - } + /** + * 获取给定日期的前一天的起始时间(00:00:00)对应的距离1970年1月1日的秒数。 + * 首先通过`Calendar.getInstance()`获取一个`Calendar`实例,然后将传入的日期(以毫秒数表示,先乘以1000转换为毫秒数后设置到`Calendar`实例中)对应的时间设置进去。 + * 接着使用`Calendar`的`add`方法将日期往前推一天(通过`add(Calendar.DAY_OF_YEAR, -1)`操作),再将该日期的时间部分设置为00:00:00(分别设置小时、分钟、秒、毫秒都为0), + * 最后获取调整后的日期对应的时间戳(毫秒数)并除以1000转换为秒数后返回,得到的就是前一天起始时间对应的秒数。 + * + * @param daytime 表示某个日期的时间,以距离1970年1月1日的秒数传入,例如传入代表2024-12-15这一天的秒数。 + * @return 给定日期的前一天的起始时间(00:00:00)对应的距离1970年1月1日的秒数。 + */ + public static long getLastDayStartTime(long daytime) { + Calendar todayStart = Calendar.getInstance(); + todayStart.setTimeInMillis(daytime * 1000); + todayStart.add(Calendar.DAY_OF_YEAR, -1); + todayStart.set(Calendar.HOUR_OF_DAY, 0); + todayStart.set(Calendar.MINUTE, 0); + todayStart.set(Calendar.SECOND, 0); + todayStart.set(Calendar.MILLISECOND, 0); + return todayStart.getTimeInMillis() / 1000; + } - public static long getLastDayEndTime(long daytime) { - Calendar todayEnd = Calendar.getInstance(); - todayEnd.setTimeInMillis(daytime * 1000); - todayEnd.add(Calendar.DAY_OF_YEAR, -1); - todayEnd.set(Calendar.HOUR_OF_DAY, 23); - todayEnd.set(Calendar.MINUTE, 59); - todayEnd.set(Calendar.SECOND, 59); - todayEnd.set(Calendar.MILLISECOND, 999); - return todayEnd.getTimeInMillis() / 1000; - } + /** + * 获取给定日期的前一天的结束时间(23:59:59.999)对应的距离1970年1月1日的秒数。 + * 先通过`Calendar.getInstance()`获取一个`Calendar`实例,把传入的日期(先将秒数乘以1000转换为毫秒数后设置到`Calendar`实例中)对应的时间设置进去。 + * 接着使用`Calendar`的`add`方法将日期往前推一天(通过`add(Calendar.DAY_OF_YEAR, -1)`操作),然后将该日期的时间部分设置为23:59:59.999(分别设置小时为23、分钟为59、秒为59、毫秒为999), + * 最后获取调整后的日期对应的时间戳(毫秒数)并除以1000转换为秒数后返回,这样就得到了前一天结束时间对应的秒数。 + * + * @param daytime 表示某个日期的时间,以距离1970年1月1日的秒数传入,例如传入代表2024-12-15这一天的秒数。 + * @return 给定日期的前一天的结束时间(23:59:59.999)对应的距离1970年1月1日的秒数。 + */ + public static long getLastDayEndTime(long daytime) { + Calendar todayEnd = Calendar.getInstance(); + todayEnd.setTimeInMillis(daytime * 1000); + todayEnd.add(Calendar.DAY_OF_YEAR, -1); + todayEnd.set(Calendar.HOUR_OF_DAY, 23); + todayEnd.set(Calendar.MINUTE, 59); + todayEnd.set(Calendar.SECOND, 59); + todayEnd.set(Calendar.MILLISECOND, 999); + return todayEnd.getTimeInMillis() / 1000; + } - public static long getTime(long time) { - Calendar timeCalendar = Calendar.getInstance(); - Calendar nowCalendar = Calendar.getInstance(); - timeCalendar.setTimeInMillis(time * 1000); - timeCalendar.set(Calendar.HOUR_OF_DAY, nowCalendar.get(Calendar.HOUR_OF_DAY)); - timeCalendar.set(Calendar.MINUTE, nowCalendar.get(Calendar.MINUTE)); - timeCalendar.set(Calendar.SECOND, nowCalendar.get(Calendar.SECOND)); - timeCalendar.set(Calendar.MILLISECOND, nowCalendar.get(Calendar.MILLISECOND)); - return timeCalendar.getTimeInMillis() / 1000; - } + /** + * 将给定的时间(以距离1970年1月1日的秒数表示)调整为与当前时间相同的小时、分钟、秒和毫秒部分,并返回调整后的时间对应的距离1970年1月1日的秒数。 + * 首先获取两个`Calendar`实例,一个用于设置传入的时间(将秒数乘以1000转换为毫秒数后设置进去),另一个代表当前时间(通过`Calendar.getInstance()`获取)。 + * 然后将传入时间对应的`Calendar`实例中的小时、分钟、秒、毫秒部分设置为与当前时间对应的`Calendar`实例相同的值, + * 最后获取调整后的时间对应的时间戳(毫秒数)并除以1000转换为秒数后返回,得到的就是调整后的时间对应的秒数。 + * + * @param time 表示要调整的时间,以距离1970年1月1日的秒数传入,例如传入某个历史时间对应的秒数。 + * @return 调整后的时间对应的距离1970年1月1日的秒数,其小时、分钟、秒和毫秒部分与当前时间相同。 + */ + public static long getTime(long time) { + Calendar timeCalendar = Calendar.getInstance(); + Calendar nowCalendar = Calendar.getInstance(); + timeCalendar.setTimeInMillis(time * 1000); + timeCalendar.set(Calendar.HOUR_OF_DAY, nowCalendar.get(Calendar.HOUR_OF_DAY)); + timeCalendar.set(Calendar.MINUTE, nowCalendar.get(Calendar.MINUTE)); + timeCalendar.set(Calendar.SECOND, nowCalendar.get(Calendar.SECOND)); + timeCalendar.set(Calendar.MILLISECOND, nowCalendar.get(Calendar.MILLISECOND)); + return timeCalendar.getTimeInMillis() / 1000; + } - public static long getStartTime(long daytime) { - Calendar todayStart = Calendar.getInstance(); - todayStart.setTimeInMillis(daytime * 1000); - // todayStart.add(Calendar.DAY_OF_YEAR, -1); - todayStart.set(Calendar.HOUR_OF_DAY, 0); - todayStart.set(Calendar.MINUTE, 0); - todayStart.set(Calendar.SECOND, 0); - todayStart.set(Calendar.MILLISECOND, 0); - return todayStart.getTime().getTime() / 1000; - } + /** + * 获取给定日期的起始时间(当天的00:00:00)对应的距离1970年1月1日的秒数。 + * 首先通过 `Calendar.getInstance()` 获取一个 `Calendar` 实例,它代表当前的时间状态。 + * 接着将传入的以秒数表示的日期(先将秒数乘以1000转换为毫秒数)设置到这个 `Calendar` 实例中,以此确定要操作的具体日期时间。 + * 然后将该 `Calendar` 实例中的时间部分设置为当天的起始时间,即分别把小时设置为0(`Calendar.HOUR_OF_DAY`)、分钟设置为0(`Calendar.MINUTE`)、秒设置为0(`Calendar.SECOND`)以及毫秒设置为0(`Calendar.MILLISECOND`)。 + * 最后获取调整后的这个代表起始时间的日期对应的时间戳(毫秒数,通过 `getTime().getTime()` 获取),再将其除以1000转换为秒数并返回。 + * + * @param daytime 表示某个日期的时间,以距离1970年1月1日的秒数传入,例如传入代表2024-12-15这一天的秒数。 + * @return 给定日期的起始时间(00:00:00)对应的距离1970年1月1日的秒数。 + */ + public static long getStartTime(long daytime) { + Calendar todayStart = Calendar.getInstance(); + todayStart.setTimeInMillis(daytime * 1000); + // todayStart.add(Calendar.DAY_OF_YEAR, -1); 此处代码被注释掉,原功能可能是将日期往前推一天,当前逻辑是获取传入日期当天的起始时间 + todayStart.set(Calendar.HOUR_OF_DAY, 0); + todayStart.set(Calendar.MINUTE, 0); + todayStart.set(Calendar.SECOND, 0); + todayStart.set(Calendar.MILLISECOND, 0); + return todayStart.getTime().getTime() / 1000; + } - public static long getEndTime(long daytime) { - Calendar todayEnd = Calendar.getInstance(); - todayEnd.setTimeInMillis(daytime * 1000); - // todayEnd.add(Calendar.DAY_OF_YEAR, -1); - todayEnd.set(Calendar.HOUR_OF_DAY, 23); - todayEnd.set(Calendar.MINUTE, 59); - todayEnd.set(Calendar.SECOND, 59); - todayEnd.set(Calendar.MILLISECOND, 999); - return todayEnd.getTimeInMillis() / 1000; - } + /** + * 获取给定日期的结束时间(当天的23:59:59.999)对应的距离1970年1月1日的秒数。 + * 先通过 `Calendar.getInstance()` 获取一个 `Calendar` 实例,用于表示当前时间状态。 + * 把传入的以秒数表示的日期(先乘以1000转换为毫秒数)设置到这个 `Calendar` 实例中,以明确要处理的具体日期时间。 + * 接着将该 `Calendar` 实例中的时间部分设置为当天的结束时间,也就是分别把小时设置为23(`Calendar.HOUR_OF_DAY`)、分钟设置为59(`Calendar.MINUTE`)、秒设置为59(`Calendar.SECOND`)以及毫秒设置为999(`Calendar.MILLISECOND`)。 + * 最后获取调整后的代表结束时间的日期对应的时间戳(毫秒数,通过 `getTimeInMillis()` 获取),再除以1000转换为秒数后返回。 + * + * @param daytime 表示某个日期的时间,以距离1970年1月1日的秒数传入,例如传入代表2024-12-15这一天的秒数。 + * @return 给定日期的结束时间(23:59:59.999)对应的距离1970年1月1日的秒数。 + */ + public static long getEndTime(long daytime) { + Calendar todayEnd = Calendar.getInstance(); + todayEnd.setTimeInMillis(daytime * 1000); + // todayEnd.add(Calendar.DAY_OF_YEAR, -1); 此处代码被注释掉,原功能可能是将日期往前推一天,当前逻辑是获取传入日期当天的结束时间 + todayEnd.set(Calendar.HOUR_OF_DAY, 23); + todayEnd.set(Calendar.MINUTE, 59); + todayEnd.set(Calendar.SECOND, 59); + todayEnd.set(Calendar.MILLISECOND, 999); + return todayEnd.getTimeInMillis() / 1000; + } - /** - * 比较俩日期是否是同一天 - * - * @param d1 - * @param d2 - * @return - */ - public static boolean compareSameDate(Date d1, Date d2) { - Calendar c1 = Calendar.getInstance(); - c1.setTime(d1); - Calendar c2 = Calendar.getInstance(); - c2.setTime(d2); - boolean isSameYear = c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR); - boolean isSameMonth = isSameYear && c1.get(Calendar.MONTH) == c2.get(Calendar.MONTH); - boolean isSameDate = isSameMonth && c1.get(Calendar.DAY_OF_MONTH) == c2.get(Calendar.DAY_OF_MONTH); - return isSameDate; - } + /** + * 比较两个日期是否是同一天。 + * 首先分别获取两个日期对应的 `Calendar` 实例,通过 `Calendar.getInstance()` 创建实例并使用 `setTime` 方法将传入的两个 `Date` 对象的日期时间信息设置进去。 + * 然后依次比较年份、月份以及日这三个日期部分是否相等。先比较年份是否相同(通过 `c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR)` 判断), + * 如果年份相同,再比较月份是否相同(通过 `isSameYear && c1.get(Calendar.MONTH) == c2.get(Calendar.MONTH)` 判断,这里使用了前面年份比较的结果 `isSameYear` 进行逻辑与运算,只有年份相同才继续比较月份), + * 若月份也相同,最后比较日是否相同(通过 `isSameMonth && c1.get(Calendar.DAY_OF_MONTH) == c2.get(Calendar.DAY_OF_MONTH)` 判断,同样依赖前面月份比较的结果 `isSameMonth`)。 + * 最终返回比较结果,如果三个日期部分都相同则表示是同一天,返回 `true`;否则返回 `false`。 + * + * @param d1 要比较的第一个日期对象,类型为 `Date`,代表一个具体的日期和时间点。 + * @param d2 要比较的第二个日期对象,类型为 `Date`,同样代表一个具体的日期和时间点。 + * @return 如果两个日期是同一天则返回 `true`,否则返回 `false`。 + */ + public static boolean compareSameDate(Date d1, Date d2) { + Calendar c1 = Calendar.getInstance(); + c1.setTime(d1); + Calendar c2 = Calendar.getInstance(); + c2.setTime(d2); + boolean isSameYear = c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR); + boolean isSameMonth = isSameYear && c1.get(Calendar.MONTH) == c2.get(Calendar.MONTH); + boolean isSameDate = isSameMonth && c1.get(Calendar.DAY_OF_MONTH) == c2.get(Calendar.DAY_OF_MONTH); + return isSameDate; + } - /** - * 当月的第一天距1970年1月1日秒数. - * - * @param date - * @return long - * @author mengxm - */ - public static final long firstMonthDayTime() { - Calendar cal = Calendar.getInstance(); - cal.set(Calendar.DAY_OF_MONTH, 1); - return cal.getTime().getTime() / 1000; - } + /** + * 获取当前月份的第一天距离1970年1月1日的秒数。 + * 通过 `Calendar.getInstance()` 获取一个 `Calendar` 实例,它默认代表当前时间状态。 + * 然后使用 `cal.set(Calendar.DAY_OF_MONTH, 1)` 将日期部分设置为当月的第一天,此时 `Calendar` 实例就代表了当前月第一天的日期时间信息。 + * 最后获取该日期对应的时间戳(毫秒数,通过 `getTime().getTime()` 获取),再除以1000转换为秒数并返回。 + * + * @return 当前月份的第一天距离1970年1月1日的秒数。 + */ + public static final long firstMonthDayTime() { + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.DAY_OF_MONTH, 1); + return cal.getTime().getTime() / 1000; + } - /** - * 根据指定格式解析时间 - * - * @param dateString - * @param fmtString - * @return Date - * @author mengxm - */ - public static final Date parse(String dateString, String fmtString) { - Date date = null; - try { - DateFormat format = new SimpleDateFormat(fmtString); - date = format.parse(dateString); - } catch (ParseException e) { - e.printStackTrace(); + /** + * 根据指定的日期格式解析给定的日期字符串为 `Date` 类型对象。 + * 首先初始化一个 `Date` 类型的变量 `date` 为 `null`,用于存储解析后的日期结果。 + * 接着尝试创建一个 `DateFormat` 类型的对象(这里使用 `SimpleDateFormat`,并传入指定的格式字符串 `fmtString` 进行初始化),用于按照指定格式解析日期字符串。 + * 通过调用该 `DateFormat` 对象的 `parse` 方法,传入要解析的日期字符串 `dateString`,尝试将其解析为 `Date` 对象,如果解析成功则将结果赋值给 `date` 变量; + * 若在解析过程中出现 `ParseException` 异常(例如日期字符串格式不符合指定要求等情况),会打印异常堆栈信息,并保持 `date` 为 `null`。 + * 最后返回解析后的 `Date` 对象,如果解析失败则返回 `null`。 + * + * @param dateString 要解析的日期字符串,例如 "2024-12-15" 等,需要符合传入的 `fmtString` 指定的格式要求,否则解析会出错。 + * @param fmtString 指定的日期格式字符串,例如 "yyyy-MM-dd" 等,用于定义如何解析 `dateString`。 + * @return 解析后的 `Date` 对象,如果解析过程中出现异常则返回 `null`。 + */ + public static final Date parse(String dateString, String fmtString) { + Date date = null; + try { + DateFormat format = new SimpleDateFormat(fmtString); + date = format.parse(dateString); + } catch (ParseException e) { + e.printStackTrace(); + } + return date; } - return date; - } - /** 比较提现前后时间间隔秒数 */ - public static boolean getDatePoor(Date endDate, Date nowDate) { + /** + * 比较提现前后两个日期的时间间隔是否小于30秒。 + * 首先获取传入的两个日期(`endDate` 和 `nowDate`)对应的时间戳(毫秒数,通过 `getTime()` 方法获取),然后计算它们的时间差(以毫秒为单位,通过 `endDate.getTime() - nowDate.getTime()` 计算), + * 再将时间差除以1000转换为秒数(`/ 1000`),得到时间间隔的秒数。 + * 最后判断这个时间间隔秒数是否小于30秒,如果小于30秒则返回 `true`,表示提现前后时间间隔较短;否则返回 `false`。 + * 注意,代码中打印了两个日期的时间戳信息(通过 `System.out.println` 输出),这可能是用于调试目的,在实际生产环境中可根据需求决定是否保留。 + * + * @param endDate 提现结束的日期对象,类型为 `Date`,代表一个具体的日期和时间点。 + * @param nowDate 当前时间对应的日期对象,类型为 `Date`,同样代表一个具体的日期和时间点。 + * @return 如果提现前后时间间隔小于30秒则返回 `true`,否则返回 `false`。 + */ + public static boolean getDatePoor(Date endDate, Date nowDate) { + + // 获得两个时间的毫秒时间差异 + System.out.println(endDate.getTime()); + System.out.println(nowDate.getTime()); + long diff = (endDate.getTime() - nowDate.getTime()) / 1000; + if (diff < 30) { + return true; + } + return false; + } - // 获得两个时间的毫秒时间差异 - System.out.println(endDate.getTime()); - System.out.println(nowDate.getTime()); - long diff = (endDate.getTime() - nowDate.getTime()) / 1000; - if (diff < 30) { - return true; + /** + * 在当前日期基础上增加指定的天数,并返回增加天数后的日期对应的距离1970年1月1日的秒数。 + * 首先通过 `Calendar.getInstance()` 获取一个 `Calendar` 实例,代表当前时间状态。 + * 接着尝试将当前日期(通过调用 `getCurrentTime` 方法获取当前日期字符串,并使用 `NORMAL_DATE_FORMAT_YY_MM_DD` 格式化器解析为 `Date` 对象)设置到这个 `Calendar` 实例中。 + * 然后使用 `Calendar` 的 `add` 方法按照天为单位增加指定的天数(`Calendar.DATE` 表示按天进行日期增减操作,传入的参数 `day` 为正数表示增加天数,负数表示减少天数)。 + * 最后获取增加天数后的日期对应的时间戳(毫秒数,通过 `getTimeInMillis()` 获取),再除以1000转换为秒数并返回; + * 如果在解析当前日期字符串为 `Date` 对象时出现 `ParseException` 异常,会打印异常堆栈信息,并返回0l,表示操作失败。 + * + * @param day 要增加的天数,类型为整数,例如传入3表示在当前日期基础上增加3天,传入 -2表示减少2天。 + * @return 在当前日期基础上增加指定天数后的日期对应的距离1970年1月1日的秒数,若出现异常则返回0l。 + */ + public static long currentDateAddDay(int day) { + Calendar cal = Calendar.getInstance(); + try { + cal.setTime(NORMAL_DATE_FORMAT_YY_MM_DD.parse(getCurrentTime())); + cal.add(Calendar.DATE, day); // add N days + return cal.getTimeInMillis() / 1000; + } catch (ParseException e) { + e.printStackTrace(); + } + return 0l; } - return false; - } - /** - * 当前时间加几天 - * - * @param day - * 天数 - * @return - */ - public static long currentDateAddDay(int day) { - Calendar cal = Calendar.getInstance(); - try { - cal.setTime(NORMAL_DATE_FORMAT_YY_MM_DD.parse(getCurrentTime())); - cal.add(Calendar.DATE, day); // add N days + /** + * 在给定的日期(以距离1970年1月1日的秒数表示)基础上增加指定的天数,并返回增加天数后的日期对应的距离1970年1月1日的秒数。 + * 首先通过 `Calendar.getInstance()` 获取一个 `Calendar` 实例,用于操作日期。 + * 接着根据传入的以秒数表示的日期(先乘以1000转换为毫秒数)创建对应的 `Date` 对象,并将其设置到 `Calendar` 实例中,以此确定要操作的基础日期。 + * 然后使用 `Calendar` 的 `add` 方法按照天为单位增加指定的天数(`Calendar.DATE` 表示按天进行日期增减操作,传入的参数 `day` 为正数表示增加天数,负数表示减少天数)。 + * 最后获取增加天数后的日期对应的时间戳(毫秒数,通过 `getTimeInMillis()` 获取),再除以1000转换为秒数并返回。 + * + * @param dateLong 表示要进行操作的基础日期,以距离1970年1月1日的秒数传入,例如传入代表2024-12-15这一天的秒数。 + * @param day 要增加的天数,类型为整数,例如传入3表示在给定日期基础上增加3天,传入 -2表示减少2天。 + * @return 在给定日期基础上增加指定天数后的日期对应的距离1970年1月1日的秒数。 + */ + public static long dateAddDay(long dateLong, int day) { + Calendar cal = Calendar.getInstance(); + Date d = new Date(dateLong * 1000); + cal.setTime(d); + cal.add(Calendar.DATE, day); return cal.getTimeInMillis() / 1000; - } catch (ParseException e) { - e.printStackTrace(); } - return 0l; - } - - public static long dateAddDay(long dateLong, int day) { - Calendar cal = Calendar.getInstance(); - Date d = new Date(dateLong * 1000); - cal.setTime(d); - cal.add(Calendar.DATE, day); - return cal.getTimeInMillis() / 1000; - } /** * 计算两个日期之间相差的天数 @@ -388,137 +565,185 @@ public final class DateUtil { * @return 相差天数 * @throws ParseException */ - public static int daysBetween(Date smdate, Date bdate) { - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); - long between_days = 0L; - try { - smdate = sdf.parse(sdf.format(smdate)); - bdate = sdf.parse(sdf.format(bdate)); - Calendar cal = Calendar.getInstance(); - cal.setTime(smdate); - long time1 = cal.getTimeInMillis(); - cal.setTime(bdate); - long time2 = cal.getTimeInMillis(); - between_days = (time2 - time1) / (1000 * 3600 * 24); - } catch (ParseException e) { - e.printStackTrace(); + /** + * 计算两个日期之间相差的天数。 + * 首先创建一个 `SimpleDateFormat` 对象,指定日期格式为 "yyyy-MM-dd",用于将传入的日期对象格式化为统一格式后再进行解析,这样可以去除时间部分(小时、分钟、秒等)的影响,只比较日期部分。 + * 初始化一个变量 `between_days` 用于存储两个日期之间的天数差值,初始值设为0。 + * 接着在 `try` 块中进行以下操作: + * 1. 使用 `sdf.format(smdate)` 将较小的日期 `smdate` 先格式化为 "yyyy-MM-dd" 格式的字符串,再使用 `sdf.parse` 方法将其解析回 `Date` 对象,这样做的目的是将 `smdate` 的时间部分重置为当天的起始时间(00:00:00),对 `bdate` 也进行同样的操作,确保比较的是两个日期的整天差异。 + * 2. 获取一个 `Calendar` 实例,将处理后的 `smdate` 设置进去,然后获取其对应的时间戳(毫秒数,通过 `getTimeInMillis` 方法获取),记为 `time1`。 + * 3. 同样地,把处理后的 `bdate` 设置到 `Calendar` 实例中,获取对应的时间戳记为 `time2`。 + * 4. 通过计算两个时间戳的差值(`time2 - time1`),再将差值除以一天对应的毫秒数(1000 * 3600 * 24,即一天有24小时,每小时3600秒,每秒1000毫秒),得到两个日期之间相差的天数,赋值给 `between_days` 变量。 + * 如果在解析日期字符串的过程中出现 `ParseException` 异常(例如传入的日期对象不符合 "yyyy-MM-dd" 格式要求等情况),会打印异常堆栈信息,并继续执行后续代码。 + * 最后将 `between_days` 转换为 `Integer` 类型并返回,表示两个日期之间相差的天数。 + * + * @param smdate 表示较小的时间(较早的日期),类型为 `Date`,例如历史记录中的较早时间点对应的日期对象。 + * @param bdate 表示较大的时间(较晚的日期),类型为 `Date`,例如当前时间或者更晚的时间点对应的日期对象。 + * @return 两个日期之间相差的天数,正数表示 `bdate` 比 `smdate` 晚的天数,负数表示 `bdate` 比 `smdate` 早的天数。若解析日期出现异常,返回的结果可能不准确。 + */ + public static int daysBetween(Date smdate, Date bdate) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + long between_days = 0L; + try { + smdate = sdf.parse(sdf.format(sdate)); + bdate = sdf.parse(sdf.format(bdate)); + Calendar cal = Calendar.getInstance(); + cal.setTime(smdate); + long time1 = cal.getTimeInMillis(); + cal.setTime(bdate); + long time2 = cal.getTimeInMillis(); + between_days = (time2 - time1) / (1000 * 3600 * 24); + } catch (ParseException e) { + e.printStackTrace(); + } + return Integer.parseInt(String.valueOf(between_days)); } - return Integer.parseInt(String.valueOf(between_days)); - } - /** 日期转换为自定义格式输出 */ - public static String DateToString(Date date, String formatType) { - if (date == null) { - return null; - } - if (formatType == null || "".equals(formatType)) { - return null; + /** + * 将日期对象按照指定的自定义格式转换为字符串输出。 + * 首先对传入的日期对象和格式字符串进行合法性判断,如果日期对象为 `null`,则直接返回 `null`;如果格式字符串为 `null` 或者是空字符串(通过 `equals` 方法判断),也返回 `null`。 + * 接着初始化一个空字符串 `dateStr`,用于存储格式化后的日期字符串。 + * 在 `try` 块中,创建一个 `SimpleDateFormat` 对象,使用传入的格式字符串进行初始化,然后通过调用其 `format` 方法将传入的日期对象格式化为指定格式的字符串,赋值给 `dateStr` 变量,并返回该格式化后的字符串。 + * 如果在格式化过程中出现异常(例如格式字符串不符合 `SimpleDateFormat` 的要求等情况),会直接返回 `null`,表示格式化失败。 + * + * @param date 要进行格式化的日期对象,代表一个具体的日期和时间点,若为 `null` 则无法进行格式化操作,返回 `null`。 + * @param formatType 指定的日期格式字符串,例如 "yyyy年MM月dd日" 等,用于定义如何将日期对象格式化为字符串,若为 `null` 或空字符串则返回 `null`。 + * @return 按照指定格式格式化后的日期字符串,如果传入参数不合法或者格式化出现异常则返回 `null`。 + */ + public static String DateToString(Date date, String formatType) { + if (date == null) { + return null; + } + if (formatType == null || "".equals(formatType)) { + return null; + } + String dateStr = ""; + try { + SimpleDateFormat sdf = new SimpleDateFormat(formatType); + dateStr = sdf.format(date); + return dateStr; + } catch (Exception e) { + return null; + } } - String dateStr = ""; - try { - SimpleDateFormat sdf = new SimpleDateFormat(formatType); - dateStr = sdf.format(date); - return dateStr; - } catch (Exception e) { - return null; - } - } - public static long getStartTimeCurrentDay() { - Calendar todayStart = Calendar.getInstance(); - // todayStart.add(Calendar.DAY_OF_YEAR, -1); - todayStart.set(Calendar.HOUR_OF_DAY, 0); - todayStart.set(Calendar.MINUTE, 0); - todayStart.set(Calendar.SECOND, 0); - todayStart.set(Calendar.MILLISECOND, 0); - return todayStart.getTime().getTime() / 1000; - } - - public static long getEndTimeCurrentDay() { - Calendar todayStart = Calendar.getInstance(); - // todayStart.add(Calendar.DAY_OF_YEAR, -1); - todayStart.set(Calendar.HOUR_OF_DAY, 23); - todayStart.set(Calendar.MINUTE, 59); - todayStart.set(Calendar.SECOND, 59); - todayStart.set(Calendar.MILLISECOND, 59); - return todayStart.getTime().getTime() / 1000; - } - - /** 日期转换为自定义格式输出 */ - public static String fomatDate(Date date, String format) { - if (date == null) { - return ""; - } - if (format == null || "".equals(format)) { - return ""; + /** + * 获取当前日期的起始时间(当天的00:00:00)对应的距离1970年1月1日的秒数。 + * 首先通过 `Calendar.getInstance()` 获取一个 `Calendar` 实例,它代表当前的时间状态。 + * 然后将该 `Calendar` 实例中的时间部分设置为当天的起始时间,即分别把小时设置为0(`Calendar.HOUR_OF_DAY`)、分钟设置为0(`Calendar.MINUTE`)、秒设置为0(`Calendar.SECOND`)以及毫秒设置为0(`Calendar.MILLISECOND`)。 + * 这里有一行代码 `todayStart.add(Calendar.DAY_OF_YEAR, -1);` 被注释掉了,原功能可能是将日期往前推一天,当前逻辑是获取当前日期当天的起始时间。 + * 最后获取调整后的这个代表起始时间的日期对应的时间戳(毫秒数,通过 `getTime().getTime()` 获取),再将其除以1000转换为秒数并返回。 + * + * @return 当前日期的起始时间(00:00:00)对应的距离1970年1月1日的秒数。 + */ + public static long getStartTimeCurrentDay() { + Calendar todayStart = Calendar.getInstance(); + // todayStart.add(Calendar.DAY_OF_YEAR, -1); + todayStart.set(Calendar.HOUR_OF_DAY, 0); + todayStart.set(Calendar.MINUTE, 0); + todayStart.set(Calendar.SECOND, 0); + todayStart.set(Calendar.MILLISECOND, 0); + return todayStart.getTime().getTime() / 1000; } - try { - SimpleDateFormat sdf = new SimpleDateFormat(format); - return sdf.format(date); - } catch (Exception e) { - e.printStackTrace(); - return ""; + /** + * 获取当前日期的结束时间(当天的23:59:59.999,此处代码中毫秒设置为59,严格来说应该是999更准确表示当天最后一刻)对应的距离1970年1月1日的秒数。 + * 先通过 `Calendar.getInstance()` 获取一个 `Calendar` 实例,用于表示当前时间状态。 + * 同样这里有一行代码 `todayStart.add(Calendar.DAY_OF_YEAR, -1);` 被注释掉了,原功能可能是将日期往前推一天,当前逻辑是获取当前日期当天的结束时间。 + * 接着将该 `Calendar` 实例中的时间部分设置为当天的结束时间,也就是分别把小时设置为23(`Calendar.HOUR_OF_DAY`)、分钟设置为59(`Calendar.MINUTE`)、秒设置为59(`Calendar.SECOND`)以及毫秒设置为59(`Calendar.MILLISECOND`)。 + * 最后获取调整后的代表结束时间的日期对应的时间戳(毫秒数,通过 `getTime().getTime()` 获取),再除以1000转换为秒数后返回。 + * + * @return 当前日期的结束时间(23:59:59.999,代码中为59毫秒)对应的距离1970年1月1日的秒数。 + */ + public static long getEndTimeCurrentDay() { + Calendar todayStart = Calendar.getInstance(); + // todayStart.add(Calendar.DAY_OF_YEAR, -1); + todayStart.set(Calendar.HOUR_OF_DAY, 23); + todayStart.set(Calendar.MINUTE, 59); + todayStart.set(Calendar.SECOND, 59); + todayStart.set(Calendar.MILLISECOND, 59); + return todayStart.getTime().getTime() / 1000; } - } - - public static String getLastDayFmtYYYYMMDD() { - Calendar todayStart = Calendar.getInstance(); - todayStart.add(Calendar.DAY_OF_YEAR, -1); - todayStart.set(Calendar.HOUR_OF_DAY, 0); - todayStart.set(Calendar.MINUTE, 0); - todayStart.set(Calendar.SECOND, 0); - todayStart.set(Calendar.MILLISECOND, 0); - return NORMAL_DATE_FORMAT_YY_MM_DD_.format(todayStart.getTime()); - } - /** - * @Title: getDateFormat - * @Description: 日期格式化 yyyy-MM-dd - * @param str - * @return String - */ - public static String getDateFormat(String str) { - return dateFormatYYMMDD(Long.parseLong(str)); - } + /** + * 将日期对象按照指定的格式转换为字符串输出,如果出现异常则返回空字符串。 + * 首先对传入的日期对象和格式字符串进行合法性判断,如果日期对象为 `null`,则直接返回空字符串;如果格式字符串为 `null` 或者是空字符串(通过 `equals` 方法判断),也返回空字符串。 + * 在 `try` 块中,创建一个 `SimpleDateFormat` 对象,使用传入的格式字符串进行初始化,然后通过调用其 `format` 方法将传入的日期对象格式化为指定格式的字符串,并返回该格式化后的字符串。 + * 如果在格式化过程中出现异常(例如格式字符串不符合 `SimpleDateFormat` 的要求等情况),会打印异常堆栈信息,并返回空字符串,表示格式化失败。 + * + * @param date 要进行格式化的日期对象,代表一个具体的日期和时间点,若为 `null` 则返回空字符串。 + * @param format 指定的日期格式字符串,例如 "yyyy-MM-dd" 等,用于定义如何将日期对象格式化为字符串,若为 `null` 或空字符串则返回空字符串。 + * @return 按照指定格式格式化后的日期字符串,如果传入参数不合法或者格式化出现异常则返回空字符串。 + */ + public static String fomatDate(Date date, String format) { + if (date == null) { + return ""; + } + if (format == null || "".equals(format)) { + return ""; + } + try { + SimpleDateFormat sdf = new SimpleDateFormat(format); + return sdf.format(date); + } catch (Exception e) { + e.printStackTrace(); + return ""; + + } + } - /** - * @Title: getTimeFormat - * @Description: 时间格式化 yyyy-MM-dd HH:mm:ss - * @param str - * @return String - */ - public static String getTimeFormat(String str) { - return dateFormat(Long.parseLong(str)); - } + /** + * 获取昨天的日期,格式为 "yyyyMMdd" + * 首先通过 `Calendar.getInstance()` 获取一个 `Calendar` 实例,代表当前时间状态。 + * 然后使用 `add(Calendar.DAY_OF_YEAR, -1)` 方法将日期往前推一天,使其代表昨天的日期。 + * 接着将该 `Calendar` 实例中的时间部分设置为昨天的起始时间,即分别把小时设置为0(`Calendar.HOUR_OF_DAY`)、分钟设置为0(`Calendar.MINUTE`)、秒设置为0(`Calendar.SECOND`)以及毫秒设置为0(`Calendar.MILLISECOND`)。 + * 最后使用预定义的 `NORMAL_DATE_FORMAT_YY_MM_DD_` 格式化器将这个代表昨天起始时间的 `Calendar` 实例对应的日期格式化为 "yyyyMMdd" 格式的字符串,并返回该字符串。 + * + * @return 昨天的日期字符串,格式为 "yyyyMMdd",例如 "20241214"(具体取决于当前日期)。 + */ + public static String getLastDayFmtYYYYMMDD() { + Calendar todayStart = Calendar.getInstance(); + todayStart.add(Calendar.DAY_OF_YEAR, -1); + todayStart.set(Calendar.HOUR_OF_DAY, 0); + todayStart.set(Calendar.MINUTE, 0); + todayStart.set(Calendar.SECOND, 0); + todayStart.set(Calendar.MILLISECOND, 0); + return NORMAL_DATE_FORMAT_YY_MM_DD_.format(todayStart.getTime()); + } - public static void main(String[] args) { - - System.out.println(dateFormat(1441036802)); - System.out.println(getFirstDayOfMonth("2015", "9")); - System.out.println(getLastDayOfMonth("2015", "8")); - System.out.println(dateFormat(getLastMonthTime())); - System.out.println(dateFormat(getLastWeekTime())); - System.out.println(parseLong("2017-01-01 00:00:00")); - System.out.println(getTime()); - System.out.println(dateFormat(1451624155)); - System.out.println(parse("20151222", "yyyyMMdd")); - Calendar c = Calendar.getInstance(); - Date nowDate = c.getTime(); - c.set(Calendar.MINUTE, -1); - Date endDate = c.getTime(); - System.out.println("nowDate--" + nowDate + ";endDate--" + endDate); - System.out.println(getDatePoor(nowDate, endDate)); - System.out.println(dateFormatYYYYMMDD(new Date())); - - System.out.println("args = [" + DateUtil.currentDateAddDay(0) + "]"); - System.out.println("args = [" + DateUtil.parse("2016-01-19", "yyyy-MM-dd").getTime() + "]"); - - System.out.println(getTime()); - System.out.println(getEndTimeCurrentDay()); - - System.out.println(getEndTimeCurrentDay() - getTime()); + /** + * 将传入的字符串表示的时间(以秒数形式,从1970年1月1日0时0分0秒到某个时间点的秒数)格式化为 "yyyy-MM-dd" 格式的日期字符串。 + * 首先通过 `Long.parseLong` 方法将传入的字符串转换为长整型的秒数,然后调用 `dateFormatYYMMDD` 方法,将该秒数转换为对应的日期,并格式化为 "yyyy-MM-dd" 格式的字符串后返回。 + * + * @param str 表示时间的字符串,需要能够正确转换为长整型的秒数,例如 "1672809600" 等,代表某个时间距离1970年1月1日0时0分0秒的秒数。 + * @return 格式化后的日期字符串,格式为 "yyyy-MM-dd",例如 "2024-12-15"(具体取决于传入的秒数对应的日期)。 + */ + public static String getDateFormat(String str) { + return dateFormatYYMMDD(Long.parseLong(str)); + } - } + /** + * 将传入的字符串表示的时间(以秒数形式,从1970年1月1日0时0分0秒到某个时间点的秒数)格式化为 "yyyy-MM-dd HH:mm:ss" 格式的日期字符串。 + * 先使用 `Long.parseLong` 方法将传入的字符串转换为长整型的秒数,再调用 `dateFormat` 方法,将该秒数对应的日期格式化为包含年月日时分秒的 "yyyy-MM-dd HH:mm:ss" 格式的字符串并返回。 + * + * @param str 表示时间的字符串,需要能够正确转换为长整型的秒数,例如 "1672809600" 等,代表某个时间距离1970年1月1日0时0分0秒的秒数。 + * @return 格式化后的日期字符串,格式为 "yyyy-MM-dd HH:mm:ss",例如 "2024-12-15 12:30:00"(具体取决于传入的秒数对应的日期)。 + */ + public static String getTimeFormat(String str) { + return dateFormat(Long.parseLong(str)); + } -} +/** + * `main` 方法,用于对 `DateUtil` 类中的一些方法进行简单测试和演示。 + * 以下是依次执行的操作及输出说明: + * 1. 调用 `dateFormat` 方法,将秒数 `1441036802` 对应的日期格式化为 "yyyy-MM-dd HH:mm:ss" 格式的字符串并输出,展示日期格式化功能。 + * 2. 调用 `getFirstDayOfMonth` 方法,获取2015年9月的第一天距离1970年1月1日的秒数对应的日期(格式化为 "yyyy-MM-dd HH:mm:ss" 格式)并输出,测试获取指定月份第一天的功能。 + * 3. 调用 `getLastDayOfMonth` 方法,获取2015年8月的最后一天距离1970年1月1日的秒数对应的日期(格式化为 "yyyy-MM-dd HH:mm:ss" 格式)并输出,测试获取指定月份最后一天的功能。 + * 4. 调用 `dateFormat` 方法,将通过 `getLastMonthTime` 方法获取的距离当前一个月前的时间对应的秒数格式化为 "yyyy-MM-dd HH:mm:ss" 格式的字符串并输出,展示获取过去某个时间并格式化的功能。 + * 5. 同样地,调用 `dateFormat` 方法,将通过 `getLastWeekTime` 方法获取的距离当前一周前的时间对应的秒数格式化为 "yyyy-MM-dd HH:mm:ss" 格式的字符串并输出。 + * 6. 调用 `parseLong` 方法,将字符串 "2017-01-01 00:00:00" 解析为对应的日期距离1970年1月1日的秒数并输出,测试日期字符串解析功能。 + * 7. 调用 `getTime` 方法获取当前时间距离1970年1月1日的秒数并输出,展示获取当前时间戳(秒数形式)的功能。 + * 8. 调用 `dateFormat` 方法,将秒数 `1451624155` 对应的日期格式化为 "yyyy-MM-dd HH:mm:ss" 格式的字符串并输出,再次测试日期格式化功能。 + * 9. 调用 `parse` 方法,将字符串 "20151222" 按照 "yyyyMMdd" 格式解析为 `Date` 对象,并输出该对象(实际输出可能是对象的默认字符串表示形式,取决于 `Date` 类的 `toString` 实现),测试自定义格式的日期字符串解析功能。 + * 10. 创建一个 `Calendar` 实例获取当前时间作为 `nowDate`,然后修改分钟数为 -1,获取这个修改后的时间作为 `endDate`,接着输出 `nowDate` 和 `endDate` 的信息(同样是默认字符串表示形式),最后调用 `getDatePoor` 方法判断这两个日期的时间间隔是否小于30秒,并输出结果,测试日期时间间隔比较功能。 + * 11. 调用 `dateFormatYYYYMMDD diff --git a/tamguo-common/src/main/java/com/tamguo/common/utils/DateUtils.java b/tamguo-common/src/main/java/com/tamguo/common/utils/DateUtils.java index e89ff34..6331845 100644 --- a/tamguo-common/src/main/java/com/tamguo/common/utils/DateUtils.java +++ b/tamguo-common/src/main/java/com/tamguo/common/utils/DateUtils.java @@ -5,9 +5,21 @@ import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; +// DateUtils类用于提供一系列与日期处理相关的实用方法,包括日期格式化、日期解析、日期之间的差值计算以及在日期上进行增减操作等功能,方便在不同的业务场景中对日期进行操作和处理。 public class DateUtils { + + // 定义默认的日期格式字符串,格式为"yyyy-MM-dd",在一些方法中若未指定具体格式时,会使用该默认格式进行日期相关的操作。 public static final String DEFAULT_PATTERN = "yyyy-MM-dd"; + /** + * 获取距离当前日期指定天数后的日期字符串,可指定日期格式。 + * 通过Calendar类获取当前日期实例,然后根据传入的天数参数对日期进行增减操作(正数表示增加天数,负数表示减少天数), + * 最后使用指定的日期格式(若未传入格式则使用默认格式)将调整后的日期格式化为字符串并返回。 + * + * @param day 表示要在当前日期基础上增加或减少的天数,例如传入1表示获取明天的日期字符串,传入-1表示获取昨天的日期字符串。 + * @param pattern 用于指定日期格式化的格式字符串,如"yyyy-MM-dd HH:mm:ss"等,若为null或不传则使用默认格式(DEFAULT_PATTERN)。 + * @return 根据指定格式格式化后的日期字符串,代表距离当前日期指定天数后的日期。 + */ public static String getOneDayFromNow(int day, String pattern) { Calendar cal = Calendar.getInstance(); cal.setTime(new Date()); @@ -16,23 +28,30 @@ public class DateUtils { return sdf.format(cal.getTime()); } + /** + * 获取距离当前日期指定天数后的日期字符串,使用默认日期格式("yyyy-MM-dd")进行格式化。 + * 该方法是对getOneDayFromNow(int day, String pattern)方法的重载,在未传入日期格式参数时,默认调用使用默认格式的版本。 + * + * @param day 表示要在当前日期基础上增加或减少的天数,例如传入1表示获取明天的日期字符串,传入-1表示获取昨天的日期字符串。 + * @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"格式进行格式化并解析,确保日期的时间部分被重置为当天的起始时间(00:00:00), + * 然后分别获取两个日期对应的时间戳(毫秒数),通过计算时间戳差值并换算为天数(除以一天对应的毫秒数,即1000 * 3600 * 24), + * 最后将差值转换为整数并返回,表示两个日期之间相差的天数。 + * 注意,该方法会抛出ParseException异常,需要调用者进行处理,若在解析日期字符串过程中出现异常,将按照异常处理逻辑进行处理(当前代码中是打印堆栈信息并返回可能不正确的结果)。 + * + * @param smdate 表示较小的时间,即较早的日期,类型为Date,例如历史记录中的较早时间点对应的日期对象。 + * @param bdate 表示较大的时间,即较晚的日期,类型为Date,例如当前时间或者更晚的时间点对应的日期对象。 + * @return 两个日期之间相差的天数,正数表示bdate比smdate晚的天数,负数表示bdate比smdate早的天数。 + * @throws ParseException 如果在解析日期字符串过程中出现格式不匹配等问题,会抛出此异常,需要调用者进行捕获处理。 */ - public static int daysBetween(Date smdate, Date bdate) - throws ParseException { + public static int daysBetween(Date smdate, Date bdate) throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); smdate = sdf.parse(sdf.format(smdate)); bdate = sdf.parse(sdf.format(bdate)); @@ -46,83 +65,91 @@ public class DateUtils { return Integer.parseInt(String.valueOf(between_days)); } + // 以下是main方法,用于简单测试daysBetween方法,不过当前传入的参数为null,实际运行可能会出现空指针异常等问题,正常使用时应传入有效的日期对象进行测试。 public static void main(String[] args) throws ParseException { System.out.println(daysBetween(null, null)); } + // 定义一系列常用的日期格式字符串常量,方便在不同的日期格式化场景中使用,以下是各常量对应的格式说明: + /** - * 英文简写(默认)如:2010-12-01 + * 英文简写(默认)日期格式字符串,如:2010-12-01,表示年月日的格式,常用于简洁展示日期的场景。 */ public static String FORMAT_SHORT = "yyyy-MM-dd"; /** - * 英文全称 如:2010-12-01 23:15:06 + * 英文全称日期格式字符串,如:2010-12-01 23:15:06,表示包含年月日时分秒的完整日期时间格式,用于需要精确展示日期和时间的情况。 */ public static String FORMAT_LONG = "yyyy-MM-dd HH:mm:ss"; /** - * 精确到毫秒的完整时间 如:yyyy-MM-dd HH:mm:ss.S + * 精确到毫秒的完整时间格式字符串,如:yyyy-MM-dd HH:mm:ss.S,表示包含年月日时分秒以及毫秒的日期时间格式,适用于对时间精度要求较高的场景,比如记录详细的操作时间戳等。 */ public static String FORMAT_FULL = "yyyy-MM-dd HH:mm:ss.S"; /** - * 中文简写 如:2010年12月01日 + * 中文简写日期格式字符串,如:2010年12月01日,以中文形式展示年月日,符合中文习惯的简洁日期表示方式,多用于面向中文用户的界面展示等场景。 */ public static String FORMAT_SHORT_CN = "yyyy年MM月dd"; /** - * 中文全称 如:2010年12月01日 23时15分06秒 + * 中文全称日期格式字符串,如:2010年12月01日 23时15分06秒,用中文完整地展示了年月日时分秒,适用于在中文环境下需要详细展示日期时间的情况。 */ public static String FORMAT_LONG_CN = "yyyy年MM月dd日 HH时mm分ss秒"; /** - * 精确到毫秒的完整中文时间 + * 精确到毫秒的完整中文时间格式字符串,如:yyyy年MM月dd日 HH时mm分ss秒SSS毫秒,以中文形式完整展示包含毫秒的日期时间信息,用于对时间精度有要求且面向中文用户的场景。 */ public static String FORMAT_FULL_CN = "yyyy年MM月dd日 HH时mm分ss秒SSS毫秒"; /** - * 获得默认的 date pattern + * 获得默认的日期格式字符串,当前默认返回英文全称格式("yyyy-MM-dd HH:mm:ss"),用于在一些需要默认格式进行日期操作的方法中获取格式信息。 + * + * @return 默认的日期格式字符串,类型为String,例如"yyyy-MM-dd HH:mm:ss"。 */ public static String getDatePattern() { return FORMAT_LONG; } /** - * 根据预设格式返回当前日期 - * - * @return + * 根据预设的默认日期格式(当前为"yyyy-MM-dd HH:mm:ss")返回当前日期的字符串表示形式。 + * 内部调用了format(Date date)方法,将当前系统时间对应的Date对象按照默认格式进行格式化并返回格式化后的日期时间字符串。 + * + * @return 当前日期按照默认格式格式化后的字符串,例如"2024-12-15 12:30:00"(具体取决于当前系统时间)。 */ public static String getNow() { return format(new Date()); } /** - * 根据用户格式返回当前日期 - * - * @param format - * @return + * 根据用户指定的日期格式返回当前日期的字符串表示形式。 + * 通过调用format(Date date, String pattern)方法,将当前系统时间对应的Date对象按照传入的用户指定格式进行格式化,并返回格式化后的日期时间字符串。 + * + * @param format 用户指定的日期格式字符串,例如"yyyy-MM-dd"或者"yyyy年MM月dd日"等,用于决定如何格式化日期。 + * @return 当前日期按照用户指定格式格式化后的字符串,例如按照"yyyy-MM-dd"格式可能返回"2024-12-15"等(具体取决于当前系统时间)。 */ public static String getNow(String format) { return format(new Date(), format); } /** - * 使用预设格式格式化日期 - * - * @param date - * @return + * 使用预设的默认日期格式(当前为"yyyy-MM-dd HH:mm:ss")对传入的日期对象进行格式化,返回格式化后的日期时间字符串。 + * 内部调用了format(Date date, String pattern)方法,并传入默认格式作为参数来实现日期的格式化操作。 + * + * @param date 要进行格式化的日期对象,代表一个具体的日期和时间点,例如通过new Date()获取的当前系统时间对应的日期对象。 + * @return 按照预设格式格式化后的日期字符串,例如"2024-12-15 12:30:00"(具体取决于传入的日期对象)。 */ public static String format(Date date) { return format(date, getDatePattern()); } /** - * 使用用户格式格式化日期 - * - * @param date - * 日期 - * @param pattern - * 日期格式 - * @return + * 使用用户指定的日期格式对传入的日期对象进行格式化,返回格式化后的日期时间字符串。 + * 首先判断传入的日期对象是否为null,如果不为null,则创建对应的SimpleDateFormat对象(使用传入的格式字符串), + * 然后通过该对象的format方法将日期对象格式化为指定格式的字符串并返回;若日期对象为null,则返回空字符串。 + * + * @param date 要进行格式化的日期对象,类型为Date,代表一个具体的日期和时间点,例如通过new Date()获取的当前系统时间对应的日期对象。 + * @param pattern 用户指定的日期格式字符串,例如"yyyy-MM-dd"或者"yyyy年MM月dd日"等,用于决定如何格式化日期。 + * @return 按照用户指定格式格式化后的日期字符串,例如按照"yyyy-MM-dd"格式对某个日期对象格式化后可能返回"2024-12-15"等(具体取决于传入的日期对象和格式);若date为null则返回空字符串。 */ public static String format(Date date, String pattern) { String returnValue = ""; - if (date != null) { + if (date!= null) { SimpleDateFormat df = new SimpleDateFormat(pattern); returnValue = df.format(date); } @@ -130,13 +157,13 @@ public class DateUtils { } /** - * 使用用户格式格式化时间戳 - * - * @param timestamp - * 时间戳 - * @param pattern - * 日期格式 - * @return + * 使用用户指定的日期格式对传入的时间戳字符串(表示从1970年1月1日0时0分0秒到某个时间点的毫秒数的字符串表示形式)进行格式化,返回格式化后的日期时间字符串。 + * 先将时间戳字符串转换为对应的Date对象(通过Long.parseLong方法将字符串转换为长整型的时间戳数值,再以此创建Date对象), + * 然后使用指定的日期格式通过SimpleDateFormat对象将该Date对象格式化为相应格式的字符串并返回。 + * + * @param timestamp 表示时间戳的字符串,例如"1672809600000"(对应某个具体时间的毫秒数表示),需要能够正确转换为长整型数值的字符串形式。 + * @param pattern 用户指定的日期格式字符串,例如"yyyy-MM-dd"或者"yyyy年MM月dd日"等,用于决定如何格式化日期。 + * @return 按照用户指定格式格式化后的日期字符串,例如按照"yyyy-MM-dd"格式对某个时间戳对应的日期进行格式化后可能返回"2024-12-15"等(具体取决于传入的时间戳和格式)。 */ public static String format(String timestamp, String pattern) { SimpleDateFormat sdf = new SimpleDateFormat(pattern); @@ -144,24 +171,24 @@ public class DateUtils { } /** - * 使用预设格式提取字符串日期 - * - * @param strDate - * 日期字符串 - * @return + * 使用预设的默认日期格式(当前为"yyyy-MM-dd HH:mm:ss")对传入的日期字符串进行解析,将其转换为对应的Date对象。 + * 内部调用了parse(String strDate, String pattern)方法,并传入默认格式作为参数来实现日期字符串的解析操作,若解析出现异常会按照异常处理逻辑进行处理(当前是打印堆栈信息并返回null)。 + * + * @param strDate 表示日期的字符串,例如"2024-12-15"或者"2024-12-15 12:30:00"等,需要符合默认格式要求,否则会解析失败。 + * @return 解析后的Date对象,如果解析过程中出现异常(如格式不匹配等)则返回null。 */ public static Date parse(String strDate) { return parse(strDate, getDatePattern()); } /** - * 使用用户格式提取字符串日期 - * - * @param strDate - * 日期字符串 - * @param pattern - * 日期格式 - * @return + * 使用用户指定的日期格式对传入的日期字符串进行解析,将其转换为对应的Date对象。 + * 通过创建对应的SimpleDateFormat对象(使用传入的格式字符串),然后调用其parse方法对日期字符串进行解析操作, + * 若解析出现异常(如格式不匹配等),会打印异常堆栈信息,并返回null,以便调用者进行相应的错误处理。 + * + * @param strDate 表示日期的字符串,例如"2024-12-15"或者"2024年12月15日"等,需要符合传入的格式要求,否则会解析失败。 + * @param pattern 用户指定的日期格式字符串,例如"yyyy-MM-dd"或者"yyyy年MM月dd日"等,用于解析日期字符串。 + * @return 解析后的Date对象,如果解析过程中出现异常(如格式不匹配等)则返回null。 */ public static Date parse(String strDate, String pattern) { SimpleDateFormat df = new SimpleDateFormat(pattern); @@ -174,13 +201,13 @@ public class DateUtils { } /** - * 在日期上增加数个整月 - * - * @param date - * 日期 - * @param n - * 要增加的月数 - * @return + * 在给定的日期基础上增加指定的月数,返回增加后的日期对象。 + * 通过Calendar类获取给定日期对应的实例,然后使用其add方法按照月为单位进行日期的增减操作(传入的参数n为正数表示增加月数,负数表示减少月数), + * 最后返回调整后的日期对象,代表在原日期基础上增加或减少若干月后的日期。 + * + * @param date 表示要进行操作的基础日期对象,类型为Date,例如某个业务记录中的日期。 + * @param n 表示要增加或减少的月数,类型为整数,例如传入3表示在原日期基础上增加3个月,传入-2表示减少2个月。 + * @return 在原日期基础上增加或减少指定月数后的日期对象。 */ public static Date addMonth(Date date, int n) { Calendar cal = Calendar.getInstance(); @@ -190,13 +217,13 @@ public class DateUtils { } /** - * 在日期上增加天数 - * - * @param date - * 日期 - * @param n - * 要增加的天数 - * @return + * 在给定的日期基础上增加指定的天数,返回增加后的日期对象。 + * 与addMonth方法类似,通过Calendar类获取给定日期对应的实例,然后使用其add方法按照天为单位进行日期的增减操作(传入的参数n为正数表示增加天数,负数表示减少天数), + * 最后返回调整后的日期对象,代表在原日期基础上增加或减少若干天后的日期。 + * + * @param date 表示要进行操作的基础日期对象,类型为Date,例如某个业务记录中的日期。 + * @param n 表示要增加或减少的天数,类型为整数,例如传入5表示在原日期基础上增加5天,传入-1表示减少1天。 + * @return 在原日期基础上增加或减少指定天数后的日期对象。 */ public static Date addDay(Date date, int n) { Calendar cal = Calendar.getInstance(); @@ -206,7 +233,10 @@ public class DateUtils { } /** - * 获取时间戳 + * 获取当前系统时间按照精确到毫秒的完整时间格式("yyyy-MM-dd HH:mm:ss.S")格式化后的字符串表示形式,常用于记录详细的操作时间戳等场景。 + * 通过创建对应的SimpleDateFormat对象(使用预设的精确到毫秒的格式字符串),获取当前系统时间对应的Calendar实例,然后将其格式化为相应格式的字符串并返回。 + * + * @return 当前系统时间按照精确到毫秒的完整时间格式格式化后的字符串,例如"2024-12-15 12:30:00.123"(具体取决于当前系统时间)。 */ public static String getTimeString() { SimpleDateFormat df = new SimpleDateFormat(FORMAT_FULL); @@ -214,6 +244,10 @@ public class DateUtils { return df.format(calendar.getTime()); } + /** + * 从给定的日期对象中提取年份信息,以字符串形式返回。 + * 先调用format(Date date)方法 + /** * 获取日期年份 * @@ -221,16 +255,28 @@ public class DateUtils { * 日期 * @return */ + /** + * 从给定的日期对象中提取年份信息,并以字符串形式返回。 + * 该方法先调用`format(Date date)`方法将传入的日期对象按照默认格式进行格式化,然后截取格式化后字符串的前4个字符, + * 这4个字符刚好对应日期中的年份部分(因为默认格式中年份占前4位,例如 "2024-12-15"),以此获取并返回年份信息 + * + * @param date 要提取年份信息的日期对象,若为null会在`format`方法中按相应逻辑处理(返回空字符串)。 + * @return 表示年份的字符串,格式如 "2024" 等,取自传入日期对象对应的年份部分。 + */ public static String getYear(Date date) { return format(date).substring(0, 4); } /** - * 按默认格式的字符串距离今天的天数 - * - * @param date - * 日期字符串 - * @return + * 计算按照默认格式(由`getDatePattern`方法指定,当前为 "yyyy-MM-dd HH:mm:ss")传入的日期字符串距离今天的天数。 + * 实现步骤如下: + * 1. 首先获取当前系统时间对应的毫秒数(通过 `Calendar.getInstance().getTime().getTime()`),记为 `t`。 + * 2. 接着将传入的日期字符串按照默认格式解析成对应的 `Date` 对象,再获取该 `Date` 对象对应的毫秒数(通过 `c.getTime().getTime()`),记为 `t1`。 + * 3. 然后计算两个时间点的毫秒数差值(`t - t1`),将其转换为秒数(除以1000),再进一步转换为天数(除以每小时的秒数3600以及每天的小时数24),最后将结果转换为整数类型返回,得到距离今天的天数。 + * 注意,如果传入的日期字符串无法按照默认格式正确解析,`parse` 方法会返回 `null`,后续操作可能出现异常情况(当前代码逻辑中未做额外处理)。 + * + * @param date 按照默认格式的日期字符串,例如 "2024-12-10",需要符合默认格式要求,否则解析可能失败。 + * @return 传入日期字符串距离今天(当前系统时间对应的日期)的天数,正数表示传入日期在未来,负数表示传入日期在过去。 */ public static int countDays(String date) { long t = Calendar.getInstance().getTime().getTime(); @@ -241,13 +287,16 @@ public class DateUtils { } /** - * 按用户格式字符串距离今天的天数 - * - * @param date - * 日期字符串 - * @param format - * 日期格式 - * @return + * 计算按照用户指定格式传入的日期字符串距离今天的天数。 + * 具体实现过程如下: + * 1. 先获取当前系统时间对应的毫秒数(通过 `Calendar.getInstance().getTime().getTime()`),赋值给变量 `t`。 + * 2. 随后将传入的按照用户指定格式的日期字符串,通过 `parse(date, format)` 方法解析成对应的 `Date` 对象,再获取该 `Date` 对象对应的毫秒数(通过 `c.getTime().getTime()`),赋值给变量 `t1`。 + * 3. 接着计算两个时间点的毫秒数差值(`t - t1`),先将其转换为秒数(除以1000),再换算为天数(除以每小时的秒数3600以及每天的小时数24),最后把结果转换为整数类型返回,即得到距离今天的天数。 + * 同样要注意,如果传入的日期字符串无法按照指定格式正确解析,`parse` 方法会返回 `null`,后续操作可能出现异常情况(当前代码逻辑中未做额外处理)。 + * + * @param date 日期字符串,例如 "2024年12月10日",需符合传入的 `format` 参数指定的格式要求,不然解析会失败。 + * @param format 用户指定的日期格式字符串,例如 "yyyy年MM月dd日",用于解析传入的日期字符串。 + * @return 传入日期字符串距离今天(当前系统时间对应的日期)的天数,正数表示传入日期在未来,负数表示传入日期在过去。 */ public static int countDays(String date, String format) { long t = Calendar.getInstance().getTime().getTime(); @@ -256,19 +305,34 @@ public class DateUtils { long t1 = c.getTime().getTime(); return (int) (t / 1000 - t1 / 1000) / 3600 / 24; } - + + /** + * 根据传入的参数对给定日期进行格式化处理,并返回格式化后的日期字符串。 + * 具体逻辑如下: + * 1. 首先判断传入的日期对象是否为 `null`,若是,则将其设置为当前系统时间对应的新 `Date` 对象(通过 `new Date()`),确保后续操作有有效的日期数据。 + * 2. 接着获取一个 `Calendar` 实例,并将其时间设置为传入的日期对象对应的时间(通过 `cal.setTime(date)`)。 + * 3. 然后根据布尔类型的 `flag` 参数来决定是否对日期进行调整,如果 `flag` 为 `true`,则将日期往前推30天(通过 `cal.add(Calendar.DATE, -30)`);若 `flag` 为 `false`,则不进行日期的增减操作(通过 `cal.add(Calendar.DATE, 0)`)。 + * 4. 最后使用传入的指定格式字符串创建 `SimpleDateFormat` 对象,将调整后的日期通过该对象格式化为字符串,并返回格式化后的结果。 + * 该方法常用于按照特定格式以及根据一定条件来输出日期字符串的场景,例如根据业务规则展示不同时间区间的日期情况等。 + * + * @param date 要进行格式化处理的日期对象,可以为 `null`,若为 `null` 则使用当前系统时间。 + * @param format 用于指定日期格式化的格式字符串,例如 "yyyy-MM-dd" 等,决定最终返回的日期字符串格式。 + * @param flag 布尔类型参数,用于控制是否对日期进行调整,`true` 表示往前推30天,`false` 表示不调整日期。 + * @param beforeDay 此参数在当前方法逻辑中未使用,可根据业务需求后续调整方法实现时加入相关逻辑(当前代码中无实际作用)。 + * @param nowDay 此参数在当前方法逻辑中未使用,可根据业务需求后续调整方法实现时加入相关逻辑(当前代码中无实际作用)。 + * @return 按照指定格式格式化后的日期字符串,格式取决于传入的 `format` 参数以及是否根据 `flag` 进行了日期调整。 + */ public static String timeFormat(Date date, String format, Boolean flag, int beforeDay, int nowDay) { - if(date == null) { + if (date == null) { date = new Date(); } Calendar cal = Calendar.getInstance(); cal.setTime(date); - if(flag) { - cal.add(Calendar.DATE,-30); + if (flag) { + cal.add(Calendar.DATE, -30); } else { - cal.add(Calendar.DATE,0); + cal.add(Calendar.DATE, 0); } SimpleDateFormat sdf = new SimpleDateFormat(format); return sdf.format(cal.getTime()); - } -} + } \ No newline at end of file diff --git a/tamguo-common/src/main/java/com/tamguo/common/utils/ExceptionSupport.java b/tamguo-common/src/main/java/com/tamguo/common/utils/ExceptionSupport.java index 829281d..a17da2c 100644 --- a/tamguo-common/src/main/java/com/tamguo/common/utils/ExceptionSupport.java +++ b/tamguo-common/src/main/java/com/tamguo/common/utils/ExceptionSupport.java @@ -1,32 +1,59 @@ package com.tamguo.common.utils; -/** - * 统一异常处理 日志处理 - * - */ +// 该类主要用于统一异常处理以及相关的日志处理操作,旨在提供一种标准化的方式来处理程序运行过程中出现的异常,并记录相应的日志信息。 public class ExceptionSupport { - + + // 定义一个静态的 Result 对象,表示失败的结果,其状态码被设置为 "500",通常用于在出现异常等错误情况时返回给调用者,告知操作失败。 private final static Result failResult = Result.failResult("500"); + + // 定义一个静态的 LogHandler 类型的对象,初始化为 Log4jHandler 实例,用于后续进行日志记录相关的操作,这里假设 Log4jHandler 是实现了 LogHandler 接口并具体负责日志处理逻辑的类。 private static LogHandler handler = new Log4jHandler(); - + + // 定义日志信息前缀,用于在记录普通信息级别的日志时添加在日志内容前面,方便区分不同级别的日志以及对日志内容进行格式化展示,使日志更具可读性。 private final static String LOG_INFO_PREFIX = "--info>> "; + + // 定义错误信息前缀,用于在记录错误级别的日志时添加在日志内容前面,和信息日志前缀类似,起到区分和格式化日志的作用,便于快速定位和识别错误相关的日志内容。 private final static String LOG_ERROR_PREFIX = "--error>> "; - + + /** + * 该方法用于处理异常并根据异常类型返回相应的结果,同时记录对应的日志信息。 + * 它接收方法相关信息(可能是出现异常的方法名称或者相关描述)、异常所在的类以及具体的异常对象作为参数。 + * 如果传入的异常是 CException 类型(自定义的异常类型,从代码上下文推测),则记录信息级别的日志(使用 info 方法记录,日志内容通过 formatInfoLevelMsg 方法格式化),然后返回一个表示失败的 Result 对象,其失败信息为异常的具体消息内容。 + * 如果传入的异常不是 CException 类型,则记录错误级别的日志(使用 error 方法记录,日志内容通过 formatErrorLevelMsg 方法格式化),并返回预先定义好的表示失败且状态码为 "500" 的 failResult 对象。 + * + * @param methodInfo 与出现异常的方法相关的信息描述,例如方法名或者对方法执行情况的简单说明,用于在日志中更清晰地标识异常来源。 + * @param clazz 出现异常的类的 Class 对象,用于明确异常所在的类,方便在日志记录等操作中准确地定位问题所在的类范围。 + * @param e 具体的异常对象,代表程序运行过程中出现的错误情况,该方法会根据此异常的类型进行不同的处理逻辑。 + * @return 返回一个 Result 对象,若是 CException 则返回包含具体异常消息的失败结果,否则返回通用的状态码为 "500" 的失败结果,表示出现了未预期的错误情况。 + */ public static Result resolverResult(String methodInfo, Class clazz, Exception e) { - if(e instanceof CException) { + if (e instanceof CException) { handler.info(formatInfoLevelMsg(methodInfo, e.getMessage()), clazz.getName()); return Result.failResult(e.getMessage()); } handler.error(formatErrorLevelMsg(methodInfo), e, clazz.getName()); return failResult; } - + + /** + * 用于格式化信息级别日志的消息内容,将给定的方法相关信息和具体的信息消息拼接在一起,并添加信息日志前缀,使日志格式统一且更易读。 + * + * @param methodInfo 与方法相关的信息描述,例如方法名或者对方法执行情况的简单说明。 + * @param infoMsg 具体的信息消息内容,例如一些正常的业务提示信息或者在特定情况下需要记录的辅助说明信息等。 + * @return 格式化后的信息级别日志消息字符串,格式为 "--info>> [方法信息]: [具体消息内容]"。 + */ private static String formatInfoLevelMsg(String methodInfo, String infoMsg) { return LOG_INFO_PREFIX + methodInfo + ": " + infoMsg; } - + + /** + * 用于格式化错误级别日志的消息内容,给给定的方法相关信息添加错误日志前缀,使其符合统一的错误日志格式要求,方便后续查看和分析错误日志。 + * + * @param methodInfo 与出现异常的方法相关的信息描述,例如方法名或者对方法执行情况的简单说明 + * @return 格式化后的错误级别日志消息字符串,格式为 "--error>> [方法信息]"。 + */ private static String formatErrorLevelMsg(String methodInfo) { return LOG_ERROR_PREFIX + methodInfo; } - -} + +} \ No newline at end of file diff --git a/tamguo-common/src/main/java/com/tamguo/common/utils/Log4jHandler.java b/tamguo-common/src/main/java/com/tamguo/common/utils/Log4jHandler.java index a8cf9da..ce34dfb 100644 --- a/tamguo-common/src/main/java/com/tamguo/common/utils/Log4jHandler.java +++ b/tamguo-common/src/main/java/com/tamguo/common/utils/Log4jHandler.java @@ -4,24 +4,65 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.event.Level; +// Log4jHandler类继承自AbstractRunningLogHandler,主要用于处理不同级别的日志记录操作,具体实现了info(信息级别)和error(错误级别)两种常见日志级别的记录方法, +// 并且依赖于SLF4J框架来实际执行日志输出,将日志信息发送到对应的日志系统(例如Log4j等,具体取决于项目中的日志实现配置)。 public class Log4jHandler extends AbstractRunningLogHandler { + // 通过LoggerFactory获取一个名为Log4jHandler类的Logger实例,后续所有的日志记录操作都将通过这个logger对象来进行, + // 这样可以方便地按照SLF4J的规范输出不同级别的日志信息到对应的日志输出渠道(如控制台、文件等)。 private static final Logger logger = LoggerFactory.getLogger(Log4jHandler.class); + /** + * 记录信息级别(INFO)的日志,只包含日志消息内容本身,不关联异常信息。 + * 该方法接收要记录的日志消息字符串以及调用类的全限定名作为参数,然后使用获取到的logger对象调用info方法,按照SLF4J的日志记录规范, + * 将日志消息以INFO级别记录下来,同时传入调用类的全限定名、日志级别(Level.INFO)以及具体的日志消息内容,最后一个参数null表示没有关联的异常信息。 + * + * @param msg 要记录的日志消息内容,通常是一些对程序运行过程中正常情况的描述信息,比如业务操作的关键步骤提示等。 + * @param fqnOfCallingClass 调用此日志记录方法的类的全限定名,例如 "com.example.MyClass",用于在日志中标识该日志是由哪个具体的类产生的,方便定位和分析日志来源。 + */ public void info(String msg, String fqnOfCallingClass) { logger.info(fqnOfCallingClass, Level.INFO, msg, null); } + /** + * 记录信息级别(INFO)的日志,同时关联异常信息。 + * 接收要记录的日志消息字符串、表示异常情况的Throwable对象以及调用类的全限定名作为参数,利用logger对象的info方法,按照SLF4J的日志记录规范, + * 将日志消息以INFO级别记录下来,传入调用类的全限定名、日志级别(Level.INFO)、具体的日志消息内容以及对应的异常对象,这样在查看日志时可以获取到详细的异常信息, + * 便于排查与该异常相关的问题。 + * + * @param msg 要记录的日志消息内容,可能是对异常相关情况的一些补充说明或者上下文描述等。 + * @param t 表示异常情况的Throwable对象,包含了详细的异常堆栈信息、异常类型以及异常消息等内容,例如在捕获到某个业务异常或者运行时异常时传入对应的异常对象。 + * @param fqnOfCallingClass 调用此日志记录方法的类的全限定名,例如 "com.example.MyClass",用于在日志中标识该日志是由哪个具体的类产生的,方便定位和分析日志来源。 + */ public void info(String msg, Throwable t, String fqnOfCallingClass) { logger.info(fqnOfCallingClass, Level.INFO, msg, t); } + /** + * 记录错误级别(ERROR)的日志,只包含日志消息内容本身,不关联异常信息。 + * 此方法接收要记录的日志消息字符串以及调用类的全限定名作为参数,使用logger对象调用error方法,按照SLF4J的日志记录规范, + * 将日志消息以ERROR级别记录下来,传入调用类的全限定名、日志级别(Level.ERROR)以及具体的日志消息内容,最后一个参数null表示没有关联的异常信息, + * 用于记录程序运行过程中出现的错误情况,方便后续定位和排查问题。 + * + * @param msg 要记录的错误相关的日志消息内容,比如业务操作失败的原因描述、系统运行时出现错误的提示等。 + * @param fqnOfCallingClass 调用此日志记录方法的类的全限定名,例如 "com.example.MyClass",用于在日志中标识该日志是由哪个具体的类产生的,方便定位和分析日志来源。 + */ public void error(String msg, String fqnOfCallingClass) { logger.error(fqnOfCallingClass, Level.ERROR, msg, null); } + /** + * 记录错误级别(ERROR)的日志,同时关联异常信息。 + * 接收要记录的日志消息字符串、表示异常情况的Throwable对象以及调用类的全限定名作为参数,通过logger对象的error方法,按照SLF4J的日志记录规范, + * 将日志消息以ERROR级别记录下来,传入调用类的全限定名、日志级别(Level.ERROR)、具体的日志消息内容以及对应的异常对象,这样在查看日志时可以获取到详细的异常信息, + * 便于对出现的错误进行深入分析和问题排查。 + * + * @param msg 要记录的错误相关的日志消息内容,可能是对异常情况的一些补充说明或者上下文描述等 + * @param t 表示异常情况的Throwable对象,包含了详细的异常堆栈信息、异常类型以及异常消息等内容,例如在捕获到某个业务异常或者运行时异常时传入对应的异常对象。 + * @param fqnOfCallingClass 调用此日志记录方法的类的全限定名,例如 "com.example.MyClass",用于在日志中标识该日志是由哪个具体的类产生的,方便定位和分析日志来源。 + */ public void error(String msg, Throwable t, String fqnOfCallingClass) { logger.error(fqnOfCallingClass, Level.ERROR, msg, t); } -} +} \ No newline at end of file diff --git a/tamguo-common/src/main/java/com/tamguo/common/utils/LogDebug.java b/tamguo-common/src/main/java/com/tamguo/common/utils/LogDebug.java index e517e92..595ef37 100644 --- a/tamguo-common/src/main/java/com/tamguo/common/utils/LogDebug.java +++ b/tamguo-common/src/main/java/com/tamguo/common/utils/LogDebug.java @@ -1,17 +1,34 @@ package com.tamguo.common.utils; +// LogDebug类主要用于在调试场景下输出相关的调试信息,提供了简单的方法来将调试消息打印到标准错误输出(System.err),方便开发人员在排查问题、跟踪代码执行流程时查看相关信息。 public class LogDebug { + // 定义一个静态的字符串常量,作为调试日志的前缀,用于在输出调试信息时添加在具体消息内容前面,方便区分调试日志与其他类型的输出内容,使调试日志更具辨识度,便于查看和定位。 private final static String DEBUG_LOG_KEY = "-- LogHandler: "; + /** + * 输出调试信息到标准错误输出(System.err)。 + * 该方法接收一个字符串类型的调试消息作为参数,然后将预先定义的调试日志前缀(DEBUG_LOG_KEY)与传入的调试消息拼接在一起, + * 再通过System.err.println方法将拼接后的内容输出到标准错误输出流,这样在查看控制台输出时,能很容易识别出这是调试相关的信息。 + * + * @param msg 要输出的调试消息内容,通常是一些对程序执行过程中关键步骤、变量值等用于辅助调试的描述信息,例如 "进入某方法,参数值为..."。 + */ public static void debug(String msg) { System.err.println(DEBUG_LOG_KEY + msg); } + /** + * 输出调试信息到标准错误输出(System.err),同时如果传入了异常对象,也会将异常的堆栈信息输出到标准错误输出流。 + * 首先和`debug(String msg)`方法一样,将调试日志前缀(DEBUG_LOG_KEY)与传入的调试消息拼接后输出到标准错误输出流,以显示调试信息。 + * 接着判断传入的异常对象(Throwable类型的参数`t`)是否为`null`,如果不为`null`,则调用异常对象的`printStackTrace`方法,并指定输出流为System.err,将异常的详细堆栈信息也输出到标准错误输出流,方便开发人员查看出现异常的具体位置、调用栈等详细情况,有助于排查问题。 + * + * @param msg 要输出的调试消息内容,例如对出现异常的上下文描述、相关业务逻辑的提示等,便于在查看调试日志时了解相关背景信息。 + * @param t 表示出现的异常情况的Throwable对象,包含了详细的异常类型、异常消息以及堆栈信息等内容,若为`null`则不会输出异常堆栈信息。 + */ public static void debug(String msg, Throwable t) { System.err.println(DEBUG_LOG_KEY + msg); - if (t != null) + if (t!= null) t.printStackTrace(System.err); } -} +} \ No newline at end of file diff --git a/tamguo-common/src/main/java/com/tamguo/common/utils/LogHandler.java b/tamguo-common/src/main/java/com/tamguo/common/utils/LogHandler.java index 5fe8d8e..6cec9f7 100644 --- a/tamguo-common/src/main/java/com/tamguo/common/utils/LogHandler.java +++ b/tamguo-common/src/main/java/com/tamguo/common/utils/LogHandler.java @@ -1,21 +1,37 @@ package com.tamguo.common.utils; +// LogHandler接口定义了一套统一的日志处理方法规范,旨在为不同的日志级别(如信息、错误、调试、警告等)提供一致的调用方式,方便具体的日志实现类去实现这些方法,以满足在各种场景下记录相应级别日志的需求。 public interface LogHandler { - + + // 用于记录信息级别(INFO)的日志,该方法接收要记录的日志消息内容以及调用类的全限定名作为参数。 + // 通常在程序正常运行过程中,需要记录一些关键业务操作、流程步骤等信息时使用,方便后续查看程序执行的轨迹和状态,例如记录用户登录成功、数据查询完成等情况。 public void info(String msg, String fqnOfCallingClass); - + + // 同样用于记录信息级别(INFO)的日志,但此方法还关联了一个表示异常情况的Throwable对象,除了记录基本的日志消息内容外, + // 还可以在出现异常但不影响整体业务流程继续执行的情况下,将异常相关信息一并记录下来,方便排查可能存在的隐患或者了解一些特殊情况,例如记录某个数据获取操作虽然成功了,但是过程中出现了可忽略的小异常等场景。 public void info(String msg, Throwable t, String fqnOfCallingClass); - + + // 用于记录错误级别(ERROR)的日志,接收日志消息内容和调用类的全限定名作为参数,主要用于在程序出现严重错误,影响业务正常执行时, + // 记录详细的错误信息,比如数据库连接失败、关键业务逻辑执行出错等情况,便于开发人员快速定位和解决问题,保障系统的稳定性。 public void error(String msg, String fqnOfCallingClass); + // 记录错误级别(ERROR)的日志,同时关联异常对象,在出现错误且需要详细的异常堆栈信息来辅助排查问题时使用,例如出现未捕获的运行时异常、业务规则验证不通过导致的严重错误等场景, + // 通过记录异常对象包含的详细堆栈信息、异常类型及消息等,可以更精准地分析错误产生的根源,进而修复问题。 public void error(String msg, Throwable t, String fqnOfCallingClass); - + + // 用于记录调试级别(DEBUG)的日志,接收日志消息内容和调用类的全限定名作为参数,在开发和调试阶段,开发人员可以通过此方法输出一些关键变量的值、代码执行路径等详细的调试信息, + // 帮助理解程序的执行逻辑,查找潜在的逻辑错误或者验证代码是否按照预期执行,通常在非生产环境下使用较多,方便调试代码。 public void debug(String msg, String fqnOfCallingClass); - + + // 记录调试级别(DEBUG)的日志并关联异常对象,在调试过程中如果遇到异常情况,既可以输出自定义的调试消息,又能同时展示异常的详细信息, + // 方便开发人员快速定位是在何种调试场景下出现的异常,对于复杂的业务逻辑调试和问题排查很有帮助。 public void debug(String msg, Throwable t, String fqnOfCallingClass); - + + // 用于记录警告级别(WARNING)的日志,接收日志消息内容和调用类的全限定名作为参数,主要用于记录一些可能会导致问题,但当前还未影响业务正常运行的情况, + // 比如配置文件中的某个参数值不太合理、即将达到系统资源阈值等场景,通过记录警告信息提醒开发人员关注相关情况,提前采取措施避免问题恶化。 public void warning(String msg, String fqnOfCallingClass); - + + // 记录警告级别(WARNING)的日志并关联异常对象,在出现警告情况且存在相关异常信息需要一并记录时使用,例如读取外部资源时遇到了可恢复的异常,虽然当前不影响主要业务,但值得记录下来作为后续优化或者排查潜在问题的参考。 public void warning(String msg, Throwable t, String fqnOfCallingClass); - -} + +} \ No newline at end of file diff --git a/tamguo-common/src/main/java/com/tamguo/common/utils/Result.java b/tamguo-common/src/main/java/com/tamguo/common/utils/Result.java index 850b79d..29bba8c 100644 --- a/tamguo-common/src/main/java/com/tamguo/common/utils/Result.java +++ b/tamguo-common/src/main/java/com/tamguo/common/utils/Result.java @@ -5,23 +5,32 @@ 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; + // 用于存储操作结果的状态码,不同的数值代表不同的操作结果状态,例如成功、失败等情况,方便调用者根据状态码快速判断操作是否成功以及进行相应的后续处理。 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; + // 私有构造函数,将构造函数私有化,阻止外部直接通过`new`关键字创建`Result`对象,强制使用类中提供的静态方法来获取`Result`实例,这样可以更好地控制对象的创建和初始化过程,确保对象状态的一致性。 private Result() { } + // 私有构造函数,用于在类内部根据传入的状态码、结果数据以及消息来创建`Result`对象,外部无法直接调用,同样是为了保证对象创建的规范性和可控性。 private Result(int code, Object result, String message) { this.code = code; this.result = result; @@ -29,48 +38,114 @@ public class Result implements Serializable { } /** - * 成功响应 - * - * @param result - * @return + * 创建表示成功的操作结果对象,返回包含具体结果数据的`Result`实例,状态码会被设置为成功状态码(`SUCCESS_CODE`),消息默认为空字符串。 + * 通常在业务操作顺利完成,需要将结果返回给调用者时使用该方法来构造成功的响应对象。 + * + * @param result 具体的操作结果数据,可以是各种类型的对象,例如查询到的用户信息列表、单个用户对象等,将被赋值到`Result`对象的`result`字段中。 + * @return 表示成功的`Result`对象,其状态码为`SUCCESS_CODE`,包含传入的结果数据以及空的消息字符串。 */ public static Result successResult(Object result) { return result(SUCCESS_CODE, result, ""); } + /** + * 重载的`successResult`方法,用于创建表示成功的操作结果对象,该方法主要用于处理分页相关的数据结构返回情况,接收分页数据(记录列表、总记录数、每页行数)以及可选的用户自定义数据作为参数, + * 内部会进一步调用`resultOfList`方法来构建包含这些信息的`Map`对象,然后将该`Map`对象作为结果数据封装到`Result`对象中返回,表示操作成功并附带相关分页及自定义数据信息。 + * 这里调用了另一个`successResult`的重载方法,默认将用户自定义数据参数设为`null`进行传递。 + * + * @param records 分页查询得到的记录列表,通常是一个包含多个实体对象的集合,例如查询到的多条用户信息记录列表等。 + * @param recordSum 总的记录数,即满足查询条件的所有记录的数量,用于表示数据总量情况,方便前端进行分页导航等相关操作。 + * @param rowsOfPage 每页显示的记录行数,用于确定分页的大小,告诉前端每页应该展示多少条数据。 + * @return 表示成功的`Result`对象,其状态码为`SUCCESS_CODE`,包含整合了分页及相关数据的`Map`类型结果数据以及空的消息字符串。 + */ public static Result successResult(Object records, Long recordSum, Long rowsOfPage) { return successResult(records, recordSum, rowsOfPage, null); } + /** + * 创建表示成功的操作结果对象,用于处理分页相关的数据结构返回情况,并可附带用户自定义数据。 + * 首先调用`resultOfList`方法根据传入的记录列表、总记录数、每页行数以及用户自定义数据构建一个`Map`对象,该`Map`对象整合了这些相关信息, + * 然后将这个`Map`对象作为结果数据通过调用`successResult(Object result)`方法封装到`Result`对象中返回,表示操作成功并附带详细的分页及自定义数据信息。 + * + * @param records 分页查询得到的记录列表,通常是一个包含多个实体对象的集合,例如查询到的多条用户信息记录列表等。 + * @param recordSum 总的记录数,即满足查询条件的所有记录的数量,用于表示数据总量情况,方便前端进行分页导航等相关操作。 + * @param rowsOfPage 每页显示的记录行数,用于确定分页的大小,告诉前端每页应该展示多少条数据。 + * @param userData 用户自定义的数据,可以是任何与业务相关的额外信息,例如用户权限信息、相关配置参数等,会被添加到返回的结果`Map`中,方便前端或其他调用者获取使用。 + * @return 表示成功的`Result`对象,其状态码为`SUCCESS_CODE`,包含整合了分页及相关数据的`Map`类型结果数据以及空的消息字符串。 + */ public static Result successResult(Object records, Long recordSum, Long rowsOfPage, Object userData) { Map result = resultOfList(records, recordSum, rowsOfPage, userData); return successResult(result); } + /** + * 创建用于存放分页相关信息的`Map`对象,该方法主要是对`resultOfList`另一个重载方法的简单调用,默认将用户自定义数据参数设为`null`,用于构建包含记录列表、总记录数以及每页行数等基本分页信息的`Map`对象, + * 方便后续作为结果数据的一部分进行传递和处理,例如在分页查询结果返回时整合相关数据使用。 + * + * @param records 分页查询得到的记录列表,通常是一个包含多个实体对象的集合,例如查询到的多条用户信息记录列表等。 + * @param recordSum 总的记录数,即满足查询条件的所有记录的数量,用于表示数据总量情况,方便前端进行分页导航等相关操作。 + * @param rowsOfPage 每页显示的记录行数,用于确定分页的大小,告诉前端每页应该展示多少条数据。 + * @return 包含分页相关信息的`Map`对象,其中键值对分别为 "rows"(对应记录列表)、"records"(对应总记录数)、"total"(对应每页行数)等,方便后续使用和扩展。 + */ public static Map resultOfList(Object records, Long recordSum, Long rowsOfPage) { return resultOfList(records, recordSum, rowsOfPage, null); } + /** + * 创建用于存放分页相关信息以及可选的用户自定义数据的`Map`对象。 + * 首先创建一个新的`HashMap`实例,然后向其中添加以下键值对信息: + * - "rows":对应传入的记录列表(`Obj`参数),存放分页查询得到的具体记录数据集合,方便前端展示具体的分页内容。 + * - "records":对应传入的总记录数(`records`参数),用于表示满足查询条件的所有记录的数量,便于前端进行分页导航等操作,例如计算总页数等。 + * - "total":对应传入的每页行数(`rowsOfPage`参数),用于明确分页的大小,让前端知道每页应该展示多少条数据。 + * - 如果传入的用户自定义数据(`userData`参数)不为`null`,则添加 "userdata" 键值对,将用户自定义数据存入`Map`中,方便传递额外的业务相关信息。 + * 最后返回构建好的包含分页及相关信息的`Map`对象,可用于作为结果数据进行传递、展示等操作。 + * + * @param Obj 分页查询得到的记录列表,通常是一个包含多个实体对象的集合,例如查询到的多条用户信息记录列表等,对应`Map`中的 "rows" 键值。 + * @param records 总的记录数,即满足查询条件的所有记录的数量,用于表示数据总量情况,方便前端进行分页导航等相关操作,对应`Map`中的 "records" 键。 + * @param rowsOfPage 每页显示的记录行数,用于确定分页的大小,告诉前端每页应该展示多少条数据,对应`Map`中的 "total" 键。 + * @param userData 用户自定义的数据,可以是任何与业务相关的额外信息,例如用户权限信息、相关配置参数等,若不为`null`则对应`Map`中的 "userdata" 键。 + * @return 包含分页相关信息以及可选用户自定义数据的`Map`对象,方便作为结果数据在不同层之间传递和处理。 + */ public static Map resultOfList(Object Obj, Long records, Long rowsOfPage, Object userData) { Map result = new HashMap(); result.put("rows", Obj); result.put("records", records); result.put("total", rowsOfPage); - if (null != userData) { + if (null!= userData) { result.put("userdata", userData); } ; return result; } + /** + * 创建用于符合`jqGrid`组件(一种常用的前端表格插件)要求的分页结果数据`Map`对象。 + * 首先创建一个新的`HashMap`实例,然后向其中添加以下键值对信息: + * - "list":对应传入的列表数据(`list`参数),存放具体的记录数据集合,用于在`jqGrid`表格中展示实际的内容。 + * - "count":对应传入的总记录数(`totalCount`参数),用于表示满足查询条件的所有记录的数量,便于`jqGrid`进行分页导航等相关操作,例如计算总页数等。 + * - "next":根据当前页码(`currPage`参数)和总页数(`totalPage`参数)来确定下一页的页码,如果当前页已经是最后一页,则下一页页码等于当前页码,否则为当前页码加1,用于`jqGrid`的分页导航中“下一页”按钮的逻辑判断。 + * - "prev":同样根据当前页码和总页数来确定上一页的页码,如果当前页是第一页,则上一页页码为1,否则为当前页码减1,用于`jqGrid`的分页导航中“上一页”按钮的逻辑判断。 + * - "first":固定值为1,表示第一页的页码,用于`jqGrid`的分页导航中“首页”按钮的逻辑判断。 + * - "last":对应传入的总页数(`totalPage`参数),表示最后一页的页码,用于`jqGrid`的分页导航中“尾页”按钮的逻辑判断。 + * - "pageSize":对应传入的每页显示的记录数(`pageSize`参数),用于明确分页的大小,告诉`jqGrid`表格每页应该展示多少条数据。 + * - "pageNo":对应传入的当前页码(`currPage`参数),用于表示当前所处的页码位置,方便`jqGrid`进行分页相关的显示和操作。 + * 最后返回构建好的包含符合`jqGrid`分页要求的所有信息的`Map`对象,方便与前端`jqGrid`组件进行数据交互,展示分页查询结果。 + * + * @param list 分页查询得到的记录列表,通常是一个包含多个实体对象的集合,例如查询到的多条用户信息记录列表等,用于在`jqGrid`表格中展示具体的数据内容。 + * @param totalCount 总的记录数,即满足查询条件的所有记录的数量,用于表示数据总量情况,方便`jqGrid`进行分页导航等相关操作,例如计算总页数等。 + * @param pageSize 每页显示的记录数,用于确定分页的大小,告诉`jqGrid`表格每页应该展示多少条数据。 + * @param currPage 当前页码,用于表示当前所处的页码位置,方便`jqGrid`进行分页相关的显示和操作。 + * @param totalPage 总页数,根据总记录数和每页记录数计算得出,用于`jqGrid`的分页导航中判断是否处于最后一页等相关操作。 + * @return 包含符合`jqGrid`分页要求的所有信息的`Map`对象,用于与前端`jqGrid`组件进行数据交互,展示分页查询结果。 + */ public static Map jqGridResult(List list, long totalCount, int pageSize, int currPage, - int totalPage) { + int totalPage) { Map result = new HashMap(); result.put("list", list); result.put("count", totalCount); - result.put("next", currPage == totalPage ? currPage : currPage + 1); - result.put("prev", currPage == 1 ? 1 : currPage - 1); + result.put("next", currPage == totalPage? currPage : currPage + 1); + result.put("prev", currPage == 1? 1 : currPage - 1); result.put("first", 1); result.put("last", totalPage); result.put("pageSize", pageSize); @@ -79,30 +154,56 @@ public class Result implements Serializable { } /** - * 失败响应 - * - * @param errorMsg - * @return + * 创建表示失败的操作结果对象,返回包含指定失败消息的`Result`实例,状态码会被设置为失败状态码(`FAIL_CODE`),结果数据默认为空字符串。 + * 通常在业务操作出现错误、未达到预期结果时使用该方法来构造失败的响应对象,并传递具体的失败原因消息给调用者。 + * + * @param errorMsg 具体的失败原因消息字符串,例如 "数据库连接失败"、"参数校验不通过" 等,将被赋值到`Result`对象的`message`字段中,方便调用者了解操作失败的具体原因。 + * @return 表示失败的`Result`对象,其状态码为`FAIL_CODE`,包含空的结果数据以及传入的失败原因消息字符串。 */ public static Result failResult(String errorMsg) { return result(FAIL_CODE, "", errorMsg); } + /** + * 创建`Result`对象的通用方法,根据传入的状态码、结果数据以及消息构建并返回一个`Result`对象实例,用于在不同的具体场景下灵活创建具有不同状态、数据和消息的`Result`对象。 + * + * @param code 要设置的状态码,通常使用`SUCCESS_CODE`表示成功,`FAIL_CODE`表示失败,也可以根据业务需求自定义其他状态码。 + * @param result 具体的结果数据,可以是各种类型的对象,例如查询到的用户信息列表、单个用户对象等,将被赋值到`Result`对象的`result`字段中。 + * @param message 要设置的提示消息字符串,例如操作成功或失败的相关描述信息等,将被赋值到`Result`对象的`message`字段中。 + * @return 根据传入参数构建好的`Result`对象实例,包含指定的状态码、结果数据以及消息内容。 + */ public static Result result(int code, Object result, String message) { Result res = new Result(code, result, message); return res; } + // 以下是用于获取`Result`对象中各个属性的公共访问方法,方便外部调用者获取`Result`对象封装的状态码、结果数据以及消息信息。 + + /** + * 获取`Result`对象的状态码。 + * + * @return `Result`对象中存储的状态码,调用者可根据该状态码判断操作是否成功以及进行相应的后续处理。 + */ public int getCode() { return code; } + /** + * 获取`Result`对象的结果数据。 + * + * @return `Result`对象中存储的结果数据,可以是各种类型的对象,例如查询到的用户信息列表、单个用户对象等,调用者可根据业务需求进一步处理该结果数据。 + */ public Object getResult() { return result; } + /** + * 获取`Result`对象的提示消息。 + * + * @return `Result`对象中存储的提示消息字符串,例如操作成功或失败的相关描述信息等,方便调用者了解操作的详细情况。 + */ public String getMessage() { return message; } -} +} \ No newline at end of file diff --git a/tamguo-common/src/main/java/com/tamguo/common/utils/Status.java b/tamguo-common/src/main/java/com/tamguo/common/utils/Status.java index 1bd741d..8e5a7d7 100644 --- a/tamguo-common/src/main/java/com/tamguo/common/utils/Status.java +++ b/tamguo-common/src/main/java/com/tamguo/common/utils/Status.java @@ -1,5 +1,12 @@ package com.tamguo.common.utils; +// Status枚举用于定义一组表示某种状态的常量值,在当前代码情境下,大概率是用于表示某个操作(比如文件上传、业务流程执行等相关操作)的不同结果状态, +// 通过使用枚举可以使代码更加清晰、易读,并且限定了合法的状态取值范围,避免使用随意的字符串或整数来表示状态而可能导致的混淆和错误。 public enum Status { - SUCCESS , ERROR -} + + // 表示操作成功的状态值,通常在相关操作顺利完成、达到预期结果时使用该枚举常量来标记成功状态,方便在代码中统一判断和处理操作成功的情况。 + SUCCESS, + + // 表示操作出现错误的状态值,用于在操作执行过程中发生异常、未达到预期等失败情况时进行标识,便于后续根据该状态执行相应的错误处理逻辑、返回错误提示信息等操作。 + ERROR +} \ No newline at end of file diff --git a/tamguo-common/src/main/java/com/tamguo/common/utils/SystemConstant.java b/tamguo-common/src/main/java/com/tamguo/common/utils/SystemConstant.java index fec3261..2db9143 100644 --- a/tamguo-common/src/main/java/com/tamguo/common/utils/SystemConstant.java +++ b/tamguo-common/src/main/java/com/tamguo/common/utils/SystemConstant.java @@ -1,103 +1,105 @@ package com.tamguo.common.utils; +// SystemConstant类主要用于定义项目中的各种常量,将这些常量集中管理,方便在整个项目中统一使用,提高代码的可读性和可维护性,避免在多处使用硬编码的字符串或数值,一旦需要修改常量值,只需在一处进行调整即可。 + public class SystemConstant { - - /** 初始密码*/ + + // 定义初始密码的常量,其值为"123456",通常用于新用户首次登录或者重置密码等场景下设置默认的初始密码,在整个项目中涉及到初始密码相关的逻辑判断、赋值操作时都可以使用该常量,保证一致性。 public static final String INIT_PASSWORD = "123456"; - - /** 验证码常数*/ + + // 用于表示验证码相关操作中在会话(Session)中存储验证码的键(Key),在生成验证码并将其存储到会话中,以及后续验证用户输入的验证码时,通过这个统一的键来获取和操作对应的验证码信息,保证验证码在整个验证流程中的一致性和准确性。 public static final String KAPTCHA_SESSION_KEY = "KAPTCHA_SESSION_KEY"; - /** 验证码常数*/ + // 可能用于标识系统用户的特定代码,值为"system",比如在区分不同类型用户(普通用户、系统管理员等)的业务逻辑中,用该常量来代表系统相关的特殊用户角色,便于进行权限判断、系统操作相关的逻辑处理等。 public static final String SYSTEM_USER_CODE = "system"; - - /** 首页菜单缓存KEY*/ + + // 用于作为首页菜单缓存的键(Key),在缓存首页菜单数据时,通过这个键来存储和获取相应的缓存内容,方便提高首页菜单数据的访问速度,减少重复查询数据库等操作,提升系统性能,每次访问首页菜单时可通过该键从缓存中获取数据(若缓存命中)。 public static final String INDEX_MENU = "index_menu"; - - /** 首页菜单缓存KEY*/ + + // 同样是首页菜单缓存的键(Key),可能与`INDEX_MENU`有所区别,也许用于存储全部的首页菜单相关数据(具体区别取决于业务逻辑对菜单数据的不同划分和使用场景),便于在需要获取完整首页菜单信息时通过该键从缓存中查找数据。 public static final String ALL_INDEX_MENU = "all_index_menu"; - - /** 首页左侧菜单缓存KEY*/ + + // 作为首页左侧菜单缓存的键(Key),在缓存首页左侧部分的菜单数据时使用,方便快速获取首页左侧菜单内容,优化这部分菜单数据的加载速度,适用于首页布局中左侧菜单有单独的缓存管理和数据获取需求的情况。 public static final String LEFT_INDEX_MENU = "left_index_menu"; - - /** 首页章节学习缓存KEY*/ + + // 用于作为首页章节学习相关菜单缓存的键(Key),当有涉及章节学习部分的菜单数据需要缓存时,通过这个键进行缓存操作,便于后续快速展示章节学习相关的菜单内容,提高用户体验和系统性能。 public static final String CHAPTER_INDEX_MENU = "chapter_index_menu"; - - /** 首页底部菜单缓存KEY*/ + + // 作为首页底部菜单缓存的键(Key),用于缓存首页底部展示的菜单数据,使得在加载首页底部菜单时能够快速从缓存中获取,避免重复的数据库查询或者复杂的菜单生成操作,加快页面加载速度。 public static final String FOOTER_INDEX_MENU = "footer_index_menu"; - - /** 首页热门试卷缓存KEY*/ + + // 用作首页热门试卷缓存的键(Key),在缓存热门试卷相关数据时使用该键,方便快速提供热门试卷信息给前端页面展示,减少查询热门试卷数据的时间消耗,提升用户获取热门试卷列表的速度。 public static final String HOT_PAPER = "HOT_PAPER"; - - /** 北京地区ID*/ + + // 定义北京地区的唯一标识符(ID),值为"110000",在涉及地区相关的业务逻辑中,比如根据地区筛选数据、统计不同地区的业务情况等场景下,通过该常量来代表北京地区,确保地区标识的一致性和准确性。 public static final String BEIJING_AREA_ID = "110000"; - /** 真题类型ID*/ + // 用于标识真题类型试卷的唯一标识符(ID),值为"1",在试卷管理、分类查询(如用户查询真题试卷)等业务逻辑中,通过该常量来区分不同类型的试卷,便于准确筛选出真题类型的试卷进行相应操作。 public static final String ZHENGTI_PAPER_ID = "1"; - - /** 模拟类型ID */ + + // 表示模拟类型试卷的唯一标识符(ID),值为"2",与其他试卷类型的ID常量一起,用于在试卷分类、筛选以及相关业务处理中,准确识别模拟试卷,方便按照模拟试卷类型进行数据操作和展示等工作。 public static final String MONI_PAPER_ID = "2"; - - /** 押题预测ID */ + + // 用于标识押题预测类型试卷的唯一标识符(ID),值为"3",在试卷管理、向用户展示不同类型试卷等场景下,通过该常量来区分押题预测试卷,以便进行针对性的业务处理和数据呈现。 public static final String YATI_PAPER_ID = "3"; - - /** 名校精品ID */ + + // 代表名校精品类型试卷的唯一标识符(ID),值为"4",方便在涉及名校试卷相关的业务逻辑中,如筛选、推荐名校试卷等操作时,准确界定属于名校精品类型的试卷,进行相应的数据处理和展示。 public static final String MINGXIAO_PAPER_ID = "4"; - /** 首页历年真题缓存KEY*/ + // 作为首页历年真题缓存的键(Key),并且可能根据具体业务逻辑会在后面拼接一些其他标识信息(从名称中的":"可推测),用于缓存历年真题试卷相关的数据,便于快速获取和展示历年真题内容给用户,优化真题数据的访问性能。 public static final String HISTORY_PAPER = "HistoryPaper:"; - - /** 首页模拟试卷缓存KEY*/ + + // 首页模拟试卷缓存的键(Key),同样可能后续会拼接其他必要信息,用于存储和获取模拟试卷相关的数据缓存,方便在需要展示模拟试卷列表等情况时,快速从缓存中提取数据,减少数据获取时间,提升页面响应速度。 public static final String SIMULATION_PAPER = "SimulationPaper:"; - - /** 名校试卷缓存KEY*/ + + // 名校试卷缓存的键(Key),用于缓存与名校相关的试卷数据,在涉及名校试卷展示、查询等业务场景下,通过该键从缓存中获取相应的数据,提高名校试卷数据的访问效率,提升用户体验。 public static final String ELITE_SCHOOL_PAPER = "EliteSchoolPaper:"; - - /** 所有广告缓存KEY*/ + + // 用于缓存所有广告相关信息的键(Key),在广告管理、展示广告等业务逻辑中,通过该常量作为缓存的标识,方便快速获取所有广告数据,例如在页面加载时快速填充广告位等操作,提高广告数据的使用效率。 public static final String ALL_AD = "AllAd:"; - - /** 名校缓存KEY*/ + + // 名校相关缓存的键(Key),具体用途可能与名校的其他数据缓存有关(比如名校详情、名校分类等相关数据,具体取决于业务对名校数据的缓存划分),便于在需要获取名校相关缓存信息时进行查找和使用。 public static final String ELITE_PAPER = "ElitePaper:"; - - /** 安全验证前缀*/ + + // 安全验证相关操作中使用的前缀,比如在生成安全验证相关的标识、存储验证记录等场景下,使用该前缀来统一命名相关的数据,方便在业务逻辑中识别和区分安全验证相关的数据项,保证安全验证流程的规范性和一致性。 public static final String SECURITY_CHECK_PREFIX = "securityCheck:"; - - /** 短信找回密码前缀*/ + + // 短信找回密码相关操作中使用的前缀,在生成短信验证码、存储短信找回密码相关记录等业务场景下,通过该前缀来标识与短信找回密码相关的数据,便于统一管理和区分不同业务用途的数据,确保短信找回密码流程的准确执行。 public static final String ALIYUN_MOBILE_SMS_PREFIX = "MOBILE_SMS_PREKEY_"; - - /** 默认会员头像*/ + + // 定义默认会员头像的文件路径,值为"/images/avatar.png",在用户未上传自定义头像时,系统默认显示该头像图片,整个项目中涉及会员头像展示且用户无自定义头像的情况时,都可以使用该常量指定的路径来获取默认头像图片进行展示。 public static final String DEFAULT_MEMBER_AVATAR = "/images/avatar.png"; - - /** 登录错误次数*/ + + // 用于标识登录错误次数相关数据的前缀,在记录用户登录失败次数、判断是否达到登录错误次数限制等与登录错误次数相关的业务逻辑中,通过该前缀来命名相关的数据存储项(比如可能结合用户名等作为完整的键来存储每个用户的登录错误次数),便于统一管理和操作登录错误次数相关的信息。 public static final String LOGIN_FAILURE_COUNT = "loginFailureCount:"; - - /** ALIYUN */ + + // 阿里云相关服务的访问密钥ID(Access Key ID),值为"LTAINGkheMeWtxUR",用于在项目中与阿里云的某些服务(如存储、短信、邮件等服务,具体取决于业务集成情况)进行身份验证,确保有权限访问对应的阿里云资源,该值应妥善保管,不能随意泄露。 public static final String ALIYUN_ACCESS_KEY_ID = "LTAINGkheMeWtxUR"; - - /** ALIYUN*/ + + // 阿里云相关服务的访问密钥(Access Key Secret),值为"ONUKuCz85kU4In07y4dvpM28mfWOGa",与访问密钥ID配合使用,用于对阿里云服务进行更安全的身份认证,同样需要严格保密,它是访问阿里云资源的重要凭证之一,涉及到与阿里云交互的业务逻辑中用于验证身份。 public static final String ALIYUN_ACCESS_KEY_SECRET = "ONUKuCz85kU4In07y4dvpM28mfWOGa"; - - /** 默认的章节根目录*/ + + // 定义默认的章节根目录的唯一标识符(UID),值为"-1",在章节管理相关的业务逻辑中,比如构建章节树结构、判断章节层级关系等场景下,使用该常量来标识默认的根目录节点,便于统一处理章节的组织结构和层次关系。 public static final String CHAPTER_DEFAULT_ROOT_UID = "-1"; - /** 所有地区*/ + // 用于缓存所有地区相关信息的键(Key),在涉及地区数据查询、展示(如选择地区下拉框展示所有地区列表等场景)的业务逻辑中,通过该常量作为缓存标识,方便快速获取所有地区的数据,减少重复查询地区数据的开销,提高系统性能。 public static final String AREA_ALL_TREE = "AREAALL:"; - - /** ALIYUN*/ + + // 阿里云的SMTP服务器主机名,值为"smtp.aliyun.com",在使用阿里云邮件服务发送邮件时,需要指定该SMTP服务器地址来建立邮件发送连接,确保邮件能够正确地通过阿里云的邮件服务发送出去,是邮件发送配置中的重要参数之一。 public static final String ALIYUN_SMTP_HOST_NAME = "smtp.aliyun.com"; - - /** ALIYUN*/ + + // 阿里云的SMTP服务器主机端口号,值为465,与阿里云的SMTP主机名配合使用,用于建立与阿里云邮件服务的安全连接(通常465端口用于SSL加密连接),确保邮件发送过程中的数据安全,是邮件发送配置中必不可少的端口参数。 public static final int ALIYUN_SMTP_HOST_PORT = 465; - - /** ALIYUN*/ + + // 阿里云邮件服务的账号,此处为空字符串,实际应用中应填写合法有效的阿里云邮箱账号,用于在通过阿里云邮件服务发送邮件时进行身份验证,表明发件人的身份,确保邮件能够从正确的账号发送出去。 public static final String ALIYUN_MAIL_ACCOUNT = ""; - - /** ALIYUN*/ + + // 阿里云邮件服务对应的密码,此处为空字符串,实际使用时需要填入与阿里云邮件账号对应的正确密码,与账号一起用于登录阿里云邮件服务,进行邮件发送操作的身份验证,保障邮件发送的合法性和安全性。 public static final String ALIYUN_MAIL_PASSWORD = ""; - - /** 邮件主题*/ + + // 定义使用阿里云邮件服务发送找回密码相关邮件时的邮件主题,值为"探果网找回密码",用于明确邮件的大致内容和用途,方便收件人快速了解邮件的核心信息,在邮件发送逻辑中作为固定的主题设置,保证邮件主题的一致性。 public static final String ALIYUN_MAIL_SUBJECT_FINDPASSWORD = "探果网找回密码"; - /** 邮箱找回密码前缀*/ + // 邮箱找回密码相关操作中使用的前缀,在生成邮箱找回密码相关的验证信息、存储记录等场景下,通过该前缀来标识与邮箱找回密码相关的数据,便于统一管理和区分不同业务流程的数据,确保邮箱找回密码流程的规范执行。 public static final String ALIYUN_MAIL_FIND_PASSWORD_PREFIX = "EMAIL_FIND_PASSWORD_"; -} +} \ No newline at end of file diff --git a/tamguo-common/src/main/java/com/tamguo/common/utils/UploaderMessage.java b/tamguo-common/src/main/java/com/tamguo/common/utils/UploaderMessage.java index 84fa24a..13ba625 100644 --- a/tamguo-common/src/main/java/com/tamguo/common/utils/UploaderMessage.java +++ b/tamguo-common/src/main/java/com/tamguo/common/utils/UploaderMessage.java @@ -2,66 +2,84 @@ package com.tamguo.common.utils; import java.util.ArrayList; - +// UploaderMessage类主要用于封装文件上传相关操作的结果信息,包含了文件上传的状态、状态消息、错误相关信息、文件路径以及文件所在的域名等内容,方便在文件上传操作完成后,统一向调用者反馈详细的情况。 public class UploaderMessage { + + // 用于表示文件上传操作的整体状态,通常会是一些预定义的枚举值(此处虽然未展示枚举定义,但推测`Status`是一个枚举类型),比如成功、失败、正在处理等不同的状态情况,方便外部调用者快速知晓文件上传的大致结果。 private Status status; + + // 用于存放关于文件上传状态的详细描述消息,例如在上传成功时可以是"文件上传成功",出现部分问题时可以是具体的错误提示等,初始值为空字符串,后续可根据实际情况进行设置。 private String statusMsg = ""; + + // 用于存储文件上传过程中出现错误的相关键值(推测可能是与错误对应的一些标识编号等,具体含义取决于业务逻辑中对错误的定义和编码方式),使用ArrayList来存储多个整数值,方便记录多个可能出现的错误相关标识,初始值为null(在未添加元素时的默认情况)。 private ArrayList errorKys; + + // 用于存放文件上传操作出现错误时的具体错误信息,例如"文件大小超出限制"、"网络连接失败导致上传中断"等详细的错误描述内容,初始值为空字符串,当有错误发生时可设置相应的错误消息字符串。 private String error = ""; + + // 用于记录上传文件在服务器端存储的文件路径信息,方便后续需要访问该文件时能准确找到其位置,初始值为空字符串,在文件上传成功并确定存储路径后可进行设置。 private String filePath = ""; + + // 用于表示上传文件所在的域名信息,例如在有多个不同域名的服务器用于存储文件的场景下,指明该文件所属的具体域名,初始值为空字符串,可根据实际的文件存储配置进行设置。 private String fileDomain = ""; + // 获取文件上传操作的状态,外部调用者通过此方法可以获取到表示当前文件上传状态的枚举值(由`Status`类型表示),进而根据该状态进行相应的后续处理,比如展示不同的提示信息给用户等。 public Status getStatus() { return status; } + // 设置文件上传操作的状态,在文件上传过程中或者完成后,由相关的业务逻辑代码调用此方法,传入合适的`Status`枚举值来更新文件上传的状态信息,以便后续外部能获取到准确的状态情况。 public void setStatus(Status status) { this.status = status; } - public String getStatusMsg() { + // 获取文件上传状态的详细描述消息,外部调用者通过调用此方法能得到关于文件上传结果的详细文本描述信息,用于向用户展示或者记录日志等操作,展示更具体的文件上传情况反馈。 + public Status getStatusMsg() { return statusMsg; } + // 设置文件上传状态的详细描述消息,在文件上传的不同阶段,根据实际情况调用此方法来更新状态消息内容,比如在上传成功时设置为"文件已成功上传至服务器",出现错误时设置相应的错误提示消息等。 public void setStatusMsg(String statusMsg) { this.statusMsg = statusMsg; } + // 获取文件上传过程中出现错误的相关键值列表,外部调用者可以通过此方法获取到记录错误相关标识编号的ArrayList,进而分析具体出现了哪些预定义的错误情况,方便进行针对性的错误处理或者统计等操作。 public ArrayList getErrorKys() { return errorKys; } + // 设置文件上传过程中出现错误的相关键值列表,当检测到文件上传出现错误并确定对应的错误标识编号后,通过此方法将包含这些编号的ArrayList传入,更新错误相关键值信息,以便后续获取和分析。 public void setErrorKys(ArrayList errorKys) { this.errorKys = errorKys; } + // 获取文件上传操作出现错误时的具体错误信息,外部调用者调用此方法能得到详细的错误描述字符串,用于向用户展示具体的错误原因或者记录到日志中进行错误排查等操作。 public String getError() { return error; } + // 设置文件上传操作出现错误时的具体错误信息,在文件上传出现错误并确定具体的错误原因后,通过此方法将错误消息字符串传入,更新错误信息内容,方便后续获取和展示给相关人员。 public void setError(String error) { this.error = error; } - public String getFilePath() { - return filePath; - } - - public void setFilePath(String filePath) { - this.filePath = filePath; - } - - public String getFileDomain() { - return fileDomain; - } - - public void setFileDomain(String fileDomain) { - this.fileDomain = fileDomain; - } - - -} - + // 获取上传文件在服务器端存储的文件路径信息,外部调用者可以通过此方法获取到文件的存储路径字符串,用于后续可能的文件访问、下载等操作,比如在前端页面展示文件链接供用户下载等场景。 + public String getFilePath() { + return filePath; + } + // 设置上传文件在服务器端存储的文件路径信息,在文件上传成功并确定其在服务器上的存储位置后,通过此方法将文件路径字符串传入,更新文件路径属性,以便后续能准确获取到该文件的存储位置信息。 + public void setFilePath(String filePath) { + this.filePath = filePath; + } + // 获取上传文件所在的域名信息,外部调用者调用此方法能得到文件所属的域名字符串,在涉及多域名文件存储或者需要明确文件来源域名等场景下有重要作用,例如根据域名进行不同的资源访问配置等操作。 + public String getFileDomain() { + return fileDomain; + } + // 设置上传文件所在的域名信息,当确定文件存储的具体域名后,通过此方法将域名字符串传入,更新文件域名属性,方便后续获取和使用该域名相关信息。 + public void setFileDomain(String fileDomain) { + this.fileDomain = fileDomain; + } +} \ No newline at end of file diff --git a/tamguo-common/src/main/java/com/tamguo/common/utils/XMLConfiguration.java b/tamguo-common/src/main/java/com/tamguo/common/utils/XMLConfiguration.java index 6737c2d..a804cdc 100644 --- a/tamguo-common/src/main/java/com/tamguo/common/utils/XMLConfiguration.java +++ b/tamguo-common/src/main/java/com/tamguo/common/utils/XMLConfiguration.java @@ -8,9 +8,23 @@ import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; +// XMLConfiguration类主要用于读取XML配置文件,并将其解析为可操作的Document对象,方便后续对XML配置内容进行访问和处理,提供了从文件路径以及输入流两种方式读取XML配置文件的功能。 public class XMLConfiguration { + + // 用于存储解析后的XML文档对象,初始值为null,在成功调用读取配置文件的方法并解析成功后,会存放对应的Document对象,代表整个XML文档的树形结构,可用于后续的节点查询、数据提取等操作。 private Document document = null; + /** + * 从指定的文件路径读取XML配置文件,并尝试将其解析为Document对象。 + * 首先通过`DocumentBuilderFactory.newInstance()`方法获取一个`DocumentBuilderFactory`实例,它是创建`DocumentBuilder`对象的工厂类,用于配置和创建用于解析XML文档的构建器。 + * 接着在`try`块中,使用`dbf.newDocumentBuilder()`方法基于上述工厂类创建一个`DocumentBuilder`对象,该对象具备解析XML文档的能力。 + * 然后调用`db.parse(configFilename)`方法,将指定的文件路径(`configFilename`参数)对应的XML文件解析为`Document`对象,并赋值给`document`属性。 + * 如果在读取文件或者解析过程中出现`IOException`(例如文件不存在、无法读取文件等输入输出相关的异常情况)或者其他异常(例如XML格式不符合规范等导致解析失败的情况),会打印异常堆栈信息。 + * 最后判断`document`是否仍为`null`,如果是,则表示解析失败,返回`false`;若解析成功,`document`不为`null`,则返回`true`,告知调用者文件读取和解析操作是否成功。 + * + * @param configFilename 要读取的XML配置文件的完整文件路径字符串,例如 "/path/to/config.xml",调用者需要确保路径指向的文件存在且具有可读权限。 + * @return 如果成功将指定的XML配置文件解析为`Document`对象,则返回`true`;若出现异常导致解析失败,`document`仍为`null`,则返回`false`。 + */ public boolean readConfigFile(String configFilename) { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); try { @@ -27,6 +41,17 @@ public class XMLConfiguration { return true; } + /** + * 从给定的输入流(`InputStream`)中读取XML配置文件内容,并尝试将其解析为Document对象。 + * 同样先通过`DocumentBuilderFactory.newInstance()`获取`DocumentBuilderFactory`实例来配置创建解析器相关的工厂环境。 + * 在`try`块内,利用`dbf.newDocumentBuilder()`创建`DocumentBuilder`对象,然后调用`db.parse(stream)`方法,将传入的输入流(`stream`参数)中的XML数据解析为`Document`对象,并赋值给`document`属性。 + * 若在读取输入流或者解析过程中出现`IOException`(例如输入流读取错误、意外中断等情况)或者其他异常(例如XML格式问题导致解析失败等),会打印异常堆栈信息。 + * 最后通过判断`document`是否为`null`来确定解析是否成功,若为`null`则返回`false`,表示解析失败;不为`null`则返回`true`,说明成功将输入流中的XML数据解析为了`Document`对象。 + * 此方法常用于从网络流、内存中的字节流等非文件形式的数据源读取XML配置信息并进行解析的场景,提供了更灵活的读取方式。 + * + * @param stream 包含XML配置文件内容的输入流对象,例如可以是从网络请求获取的XML数据输入流、从内存字节数组转换而来的输入流等,调用者需要确保输入流中的数据是合法的XML格式内容。 + * @return 如果成功将输入流中的XML配置文件解析为`Document`对象,则返回`true`;若出现异常导致解析失败,`document`仍为`null`,则返回`false`。 + */ public boolean readConfigFile(InputStream stream) { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); try { @@ -43,11 +68,21 @@ public class XMLConfiguration { return true; } + /** + * 获取解析后的XML文档对象,外部调用者可以通过此方法获取到之前成功读取和解析XML配置文件后得到的`Document`对象,进而利用相关的DOM API对XML文档中的节点、元素、属性等进行查询、修改等操作。 + * + * @return 解析后的XML文档对象,如果尚未成功读取和解析XML配置文件,可能返回`null`,调用者需要进行相应的判断处理。 + */ public Document getDocument() { return document; } + /** + * 受保护的方法,用于在类内部或者子类中设置解析后的XML文档对象,一般情况下外部调用者不应直接调用此方法,主要用于在特定的继承场景或者内部逻辑需要重新设置`document`对象时使用,以更新当前存储的XML文档内容。 + * + * @param document 要设置的新的XML文档对象,通常是经过重新解析或者从其他途径获取的合法的`Document`对象,会替换掉当前存储的`document`对象。 + */ protected void setDocument(Document document) { this.document = document; } -} +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/TamguoCrawlerApplication.java b/tamguo-crawler/src/main/java/com/tamguo/TamguoCrawlerApplication.java index 64ddefc..7b55b3d 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/TamguoCrawlerApplication.java +++ b/tamguo-crawler/src/main/java/com/tamguo/TamguoCrawlerApplication.java @@ -1,15 +1,30 @@ package com.tamguo; import org.springframework.boot.autoconfigure.SpringBootApplication; +// 用于开启Spring Boot的自动配置功能,它会根据项目中添加的依赖自动配置各种Spring组件, +// 例如数据源、Web相关配置等,大大简化了Spring项目的初始配置过程,让开发者可以更专注于业务逻辑开发。 import org.springframework.boot.builder.SpringApplicationBuilder; +// SpringApplicationBuilder类用于以编程的方式构建和定制Spring应用程序, +// 相比于直接使用SpringApplication,它提供了更灵活的配置方式,比如可以方便地设置应用的一些属性、添加额外的配置类等。 import org.springframework.context.annotation.ComponentScan; +// 用于指定Spring需要扫描的组件所在的包路径,Spring会在这些指定的包及其子包下查找带有@Component、@Service、@Repository、@Controller等注解的类, +// 并将它们注册为Spring容器中的Bean,以便进行依赖注入和管理。 +// @SpringBootApplication注解是一个组合注解,相当于同时使用了@Configuration、@EnableAutoConfiguration和@ComponentScan(默认扫描当前类所在包及其子包)。 +// 在这里,由于又显式地使用了@ComponentScan("com.tamguo"),所以主要起到开启自动配置的作用,让Spring Boot自动配置项目相关的基础框架组件。 @SpringBootApplication +// 明确指定Spring要扫描的包路径为"com.tamguo",确保项目中定义在该包及其子包下的所有符合条件的组件都能被Spring容器发现并管理起来, +// 这样就能在项目中正确地进行依赖注入和使用各种自定义的Bean等操作。 @ComponentScan("com.tamguo") public class TamguoCrawlerApplication { + // main方法是Java应用程序的入口点,程序从这里开始执行。 public static void main(String[] args) { + // 通过SpringApplicationBuilder来构建一个Spring应用程序实例,传入当前类TamguoCrawlerApplication.class作为配置类, + // 然后调用web(true)方法表示以Web应用的方式启动(如果为false则不启动Web相关功能,例如用于构建命令行工具等场景), + // 最后调用run(args)方法启动Spring应用程序,传入命令行参数args,使得整个Spring Boot应用开始运行,加载各种配置、初始化组件等, + // 最终对外提供相应的服务(如果是Web应用则监听相应端口等待请求等)。 new SpringApplicationBuilder(TamguoCrawlerApplication.class).web(true).run(args); } - -} + +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/config/dao/MyMetaObjectHandler.java b/tamguo-crawler/src/main/java/com/tamguo/config/dao/MyMetaObjectHandler.java index dad62b1..928b811 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/config/dao/MyMetaObjectHandler.java +++ b/tamguo-crawler/src/main/java/com/tamguo/config/dao/MyMetaObjectHandler.java @@ -1,26 +1,43 @@ package com.tamguo.config.dao; import com.baomidou.mybatisplus.mapper.MetaObjectHandler; - import org.apache.ibatis.reflection.MetaObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * 注入公共字段自动填充,任选注入方式即可 + * 该类用于注入公共字段自动填充功能,继承自MyBatis-Plus提供的MetaObjectHandler类。 + * 可以通过继承并重写相关方法来实现自定义的字段自动填充逻辑,在代码中任选注入方式即可将其集成到MyBatis-Plus的操作流程中。 + * (注:目前类上的@Component注解被注释掉了,意味着该类暂时可能未被作为Spring组件进行管理,后续若需要可取消注释启用相关功能) */ //@Component public class MyMetaObjectHandler extends MetaObjectHandler { + // 创建一个Logger实例,用于记录日志,这里记录的日志类别是基于当前类MyMetaObjectHandler的,方便在日志中定位相关操作记录 protected final static Logger logger = LoggerFactory.getLogger(MyMetaObjectHandler.class); + /** + * 重写父类的insertFill方法,该方法会在执行数据插入操作时被触发。 + * 通过日志记录了“新增的时候干点不可描述的事情”,实际应用中可在此方法内编写具体的逻辑, + * 例如自动填充创建时间、创建人等公共字段到插入的数据对象中,填充的数据会基于MetaObject所关联的具体数据库实体对象。 + * + * @param metaObject 表示要进行插入操作的数据对应的MetaObject,通过它可以获取和设置对象的属性值等, + * 这个MetaObject通常与MyBatis操作的数据库实体对象相关联,方便进行字段填充等操作。 + */ @Override public void insertFill(MetaObject metaObject) { logger.info("新增的时候干点不可描述的事情"); } + /** + * 重写父类的updateFill方法,该方法会在执行数据更新操作时被触发。 + * 同样通过日志记录了“更新的时候干点不可描述的事情”,实际业务中可在此方法里编写更新时自动填充公共字段的逻辑, + * 比如更新时间等字段的自动赋值,也是基于MetaObject对应的数据库实体对象来操作相关属性值。 + * + * @param metaObject 表示要进行更新操作的数据对应的MetaObject,作用同insertFill方法中的参数,用于操作相关对象的属性。 + */ @Override public void updateFill(MetaObject metaObject) { logger.info("更新的时候干点不可描述的事情"); } -} +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/config/dao/MybatisPlusConfig.java b/tamguo-crawler/src/main/java/com/tamguo/config/dao/MybatisPlusConfig.java index 9e18232..559fd03 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/config/dao/MybatisPlusConfig.java +++ b/tamguo-crawler/src/main/java/com/tamguo/config/dao/MybatisPlusConfig.java @@ -16,10 +16,14 @@ import org.springframework.context.annotation.Configuration; import java.util.ArrayList; import java.util.List; +// 标识这是一个配置类,Spring会自动扫描并处理这个类中的Bean定义等配置信息 @Configuration +// 配置MyBatis-Plus的Mapper扫描路径,会扫描com.tamguo.dao及其子包下的所有Mapper接口 @MapperScan("com.tamguo.dao*") public class MybatisPlusConfig { + // 定义一个名为performanceInterceptor的Bean方法,用于创建并返回PerformanceInterceptor实例 + // PerformanceInterceptor通常用于拦截SQL执行,记录SQL执行性能相关的信息,比如执行时长等 @Bean public PerformanceInterceptor performanceInterceptor() { return new PerformanceInterceptor(); @@ -28,28 +32,35 @@ public class MybatisPlusConfig { /** * mybatis-plus分页插件
* 文档:http://mp.baomidou.com
+ * 此方法用于创建并配置PaginationInterceptor分页插件实例,用于在MyBatis-Plus中实现分页功能。 */ @Bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); - paginationInterceptor.setLocalPage(true);// 开启 PageHelper 的支持 + // 设置开启PageHelper的支持,以便能更好地与PageHelper等分页相关的工具兼容使用 + paginationInterceptor.setLocalPage(true); + /* * 【测试多租户】 SQL 解析处理拦截器
* 这里固定写成住户 1 实际情况你可以从cookie读取,因此数据看不到 【 麻花藤 】 这条记录( 注意观察 SQL )
+ * 以下代码是配置多租户相关的SQL解析拦截逻辑,通过设置租户相关的处理器和解析器来处理涉及多租户的SQL语句。 */ List sqlParserList = new ArrayList(); TenantSqlParser tenantSqlParser = new TenantSqlParser(); tenantSqlParser.setTenantHandler(new TenantHandler() { + // 获取租户ID对应的表达式,这里返回null,实际应用中通常需要按照业务规则返回正确的租户ID表达式 @Override public Expression getTenantId() { return null; } + // 指定租户ID对应的数据库表列名,此处设置为"company_id",意味着在多租户场景下通过这个列来区分不同租户的数据 @Override public String getTenantIdColumn() { return "company_id"; } + // 判断是否对指定的表名进行过滤,返回true表示进行过滤,具体的过滤逻辑可以根据业务需求在这个方法内进一步完善 @Override public boolean doTableFilter(String tableName) { // 这里可以判断是否过滤表 @@ -57,9 +68,9 @@ public class MybatisPlusConfig { } }); - sqlParserList.add(tenantSqlParser); paginationInterceptor.setSqlParserList(sqlParserList); + // 以下过滤方式与 @SqlParser(filter = true) 注解等效 // paginationInterceptor.setSqlParserFilter(new ISqlParserFilter() { // @Override @@ -72,19 +83,23 @@ public class MybatisPlusConfig { // return false; // } // }); + return paginationInterceptor; } + // 定义一个名为metaObjectHandler的Bean方法,用于创建并返回自定义的MetaObjectHandler实例(此处是MyMetaObjectHandler) + // MetaObjectHandler通常用于在插入或更新操作时自动填充一些公共字段,比如创建时间、更新时间等 @Bean - public MetaObjectHandler metaObjectHandler(){ + public MetaObjectHandler metaObjectHandler() { return new MyMetaObjectHandler(); } /** * 注入sql注入器 + * 以下代码原本是用于注入LogicSqlInjector,它可能用于实现一些逻辑删除等相关的功能,但目前被注释掉了,可能暂时不需要这个功能或者后续再启用。 */ /*@Bean public ISqlInjector sqlInjector(){ return new LogicSqlInjector(); }*/ -} +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/config/dao/SuperEntity.java b/tamguo-crawler/src/main/java/com/tamguo/config/dao/SuperEntity.java index de1ba02..9c75133 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/config/dao/SuperEntity.java +++ b/tamguo-crawler/src/main/java/com/tamguo/config/dao/SuperEntity.java @@ -6,25 +6,40 @@ import com.baomidou.mybatisplus.annotations.TableId; /** * 实体父类 + * 该类作为实体类的基类,用于提供一些通用的属性和方法,方便其他具体的实体类继承, + * 从而实现代码的复用以及统一的结构规范,比如统一的主键处理等相关逻辑。 */ public class SuperEntity> extends Model { + // 用于定义类的序列化版本号,在进行对象序列化和反序列化时起到版本控制的作用,保证兼容性。 + // 这里初始化为1L,一般如果类的结构等没有发生不兼容的变更,这个值可以保持不变。 private static final long serialVersionUID = 1L; - @TableId("id") - private String id; - - @Override - protected Serializable pkVal() { - return this.getId(); - } + // 使用MyBatis-Plus提供的注解 @TableId 标记该属性为数据库表的主键字段, + // 这里指定对应的数据库表中的列名为 "id",意味着在与数据库交互时,通过该字段来标识唯一的记录。 + @TableId("id") + private String id; + + /** + * 重写父类Model中的pkVal方法,该方法用于获取实体对象的主键值。 + * 在本类中,返回当前对象的id属性作为主键值,这样在MyBatis-Plus进行一些基于主键的操作(如查询、更新、删除等)时, + * 能正确获取到对应的主键信息进行数据库交互。 + * + * @return 返回表示主键值的Serializable对象,在这里实际返回的是String类型的id属性值, + * 因为String实现了Serializable接口,符合方法的返回要求。 + */ + @Override + protected Serializable pkVal() { + return this.getId(); + } + // 获取id属性值的方法,外部类可以通过调用该方法获取实体对象对应的主键值。 public String getId() { return id; } + // 设置id属性值的方法,外部类可以通过调用该方法为实体对象设置主键值,用于创建或更新实体对象时指定主键信息。 public void setId(String id) { this.id = id; } - -} +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/config/dao/SuperMapper.java b/tamguo-crawler/src/main/java/com/tamguo/config/dao/SuperMapper.java index 6b8b4cf..8cec75c 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/config/dao/SuperMapper.java +++ b/tamguo-crawler/src/main/java/com/tamguo/config/dao/SuperMapper.java @@ -3,9 +3,16 @@ package com.tamguo.config.dao; import com.baomidou.mybatisplus.mapper.BaseMapper; /** - * 演示 mapper 父类,注意这个类不要让 mp 扫描到!! + * 演示 mapper 父类 + * 该接口作为其他具体的Mapper接口的父接口,用于定义一些公共的方法或者规范,方便复用代码和统一接口结构。 + * 注意这个类不要让 mp(MyBatis-Plus)扫描到!! + * 这可能是因为它只是作为一个抽象的父接口存在,本身不对应具体的数据库表操作,若被扫描到可能会引发一些不必要的问题, + * 比如不符合预期的实例化或者错误的数据库映射等情况,具体取决于项目的配置和MyBatis-Plus的扫描机制。 */ public interface SuperMapper extends BaseMapper { // 这里可以放一些公共的方法 -} + // 例如一些所有实体对应的Mapper都可能会用到的通用查询、通用更新逻辑等相关方法, + // 具体的方法定义需要根据实际业务需求来添加,然后由具体继承该接口的子类去实现这些方法, + // 从而实现代码复用以及保证操作的一致性和规范性。 +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/config/redis/CacheService.java b/tamguo-crawler/src/main/java/com/tamguo/config/redis/CacheService.java index 8498300..285bbb3 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/config/redis/CacheService.java +++ b/tamguo-crawler/src/main/java/com/tamguo/config/redis/CacheService.java @@ -11,108 +11,125 @@ import redis.clients.jedis.ShardedJedis; /** * 缓存中心 - * + * 该类主要用于提供与 Redis 缓存相关的操作服务,是对 Redis 功能的一层封装,方便在项目中统一管理和使用缓存功能。 */ @Service("cacheService") public class CacheService { - private final static String REDIS_PRE_KEY = "TAMGUO:"; - private SerializeTranscoder objectSerialize = new ObjectUtil(); - @Autowired - private RedisXMLConfigure redisXMLConfigure; + // 定义一个常量作为 Redis 中键的前缀,用于区分不同项目或者模块在 Redis 中的数据,统一添加前缀便于管理和识别。 + private final static String REDIS_PRE_KEY = "TAMGUO:"; + // 创建一个用于对象序列化和反序列化的工具类实例,用于处理需要存入 Redis 的对象,使其能以合适的格式在 Redis 中存储和读取。 + private SerializeTranscoder objectSerialize = new ObjectUtil(); - /** - * - * @Title: get @Description: @param @return String 返回类型 @throws - */ - public String get(String key) { - key = getPreKey(key); - ShardedJedis conn = null; - try { - conn = redisXMLConfigure.getConnection(); - return conn.get(key); - } finally { - redisXMLConfigure.closeConnection(conn); - } - } + // 通过 Spring 的依赖注入,自动装配 RedisXMLConfigure 实例,该实例可能用于配置 Redis 连接相关信息以及获取和关闭连接等操作。 + @Autowired + private RedisXMLConfigure redisXMLConfigure; - /** - * - * @Title: set @Description: @param @return void 返回类型 @throws - */ - public void set(String key, String value) { - key = getPreKey(key); - ShardedJedis conn = null; - try { - conn = redisXMLConfigure.getConnection(); - conn.set(key, value); - } finally { - redisXMLConfigure.closeConnection(conn); - } - } + /** + * 根据传入的键获取对应的值,如果键在 Redis 中存在,则返回对应的值,否则返回 null。 + * + * @param key 要获取值的键,在实际操作前会先添加 REDIS_PRE_KEY 前缀。 + * @return 返回对应键在 Redis 中存储的字符串值,如果不存在则返回 null。 + */ + public String get(String key) { + // 给传入的键添加前缀,使其符合项目在 Redis 中键的规范格式。 + key = getPreKey(key); + ShardedJedis conn = null; + try { + // 通过 RedisXMLConfigure 获取 Redis 连接对象,ShardedJedis 通常用于在 Redis 集群等场景下获取连接进行操作。 + conn = redisXMLConfigure.getConnection(); + // 使用获取到的连接对象,调用 get 方法从 Redis 中获取对应键的值。 + return conn.get(key); + } finally { + // 无论是否成功获取到值,都需要关闭 Redis 连接,通过 RedisXMLConfigure 提供的方法来关闭连接,释放资源。 + redisXMLConfigure.closeConnection(conn); + } + } - /** - * - * set 设置带过期时间的字符缓存 - * - * @param key - * @param value - * @param time - * 过期时间,秒 - * @description - * @exception @since - * 1.0.0 - */ - public void set(String key, String value, int time) { - key = getPreKey(key); - ShardedJedis conn = null; - try { - conn = redisXMLConfigure.getConnection(); - conn.set(key, value); - conn.expire(key, time); - } finally { - redisXMLConfigure.closeConnection(conn); - } - } + /** + * 将键值对存入 Redis 中,如果键已存在则覆盖原有值。 + * + * @param key 要存入的键,同样会先添加前缀。 + * @param value 要存入的字符串值。 + */ + public void set(String key, String value) { + key = getPreKey(key); + ShardedJedis conn = null; + try { + conn = redisXMLConfigure.getConnection(); + conn.set(key, value); + } finally { + redisXMLConfigure.closeConnection(conn); + } + } - /** - * redis中存放对象 - * - * @param key 对象key - * @param value 可序列化的对象 - */ - public void setObject(String key, Object value) { - key = getPreKey(key); - ShardedJedis conn = null; - try { - conn = redisXMLConfigure.getConnection(); - conn.set(key.getBytes(), objectSerialize.serialize(value)); - } catch (Exception ex) { - ex.printStackTrace(); - } finally { - redisXMLConfigure.closeConnection(conn); - } - } + /** + * set 设置带过期时间的字符缓存 + * 设置键值对存入 Redis 中,并指定过期时间,一旦超过过期时间,对应键值对将自动从 Redis 中删除。 + * + * @param key 要存入的键,会先添加前缀。 + * @param value 要存入的字符串值。 + * @param time 过期时间,单位为秒,表示键值对在 Redis 中有效的时长。 + */ + public void set(String key, String value, int time) { + key = getPreKey(key); + ShardedJedis conn = null; + try { + conn = redisXMLConfigure.getConnection(); + conn.set(key, value); + // 在设置完键值对后,调用 expire 方法设置对应键的过期时间。 + conn.expire(key, time); + } finally { + redisXMLConfigure.closeConnection(conn); + } + } - /** - * 设置过期时间存储对象 - * - * @param key 对象key - * @param value 对象值 - * @param time 过期时间 秒 - */ - public void setObject(String key, Object value, int time) { - key = getPreKey(key); - ShardedJedis conn = null; - try { - conn = redisXMLConfigure.getConnection(); - conn.setex(key.getBytes(), time, objectSerialize.serialize(value)); - } catch (Exception ex) { - ex.printStackTrace(); - } finally { - redisXMLConfigure.closeConnection(conn); - } - } + /** + * redis中存放对象 + * 将可序列化的对象存入 Redis 中,通过 SerializeTranscoder 将对象进行序列化后再存入,以便能在 Redis 中存储对象类型的数据。 + * + * @param key 对象键,同样会先添加前缀,用于在 Redis 中唯一标识该对象。 + * @param value 可序列化的对象,即需要实现了序列化接口的对象,才能正确存入 Redis。 + */ + public void setObject(String key, Object value) { + key = getPreKey(key); + ShardedJedis conn = null; + try { + conn = redisXMLConfigure.getConnection(); + // 将键转换为字节数组形式(因为 Redis 的底层存储很多操作是基于字节数组的), + // 并使用 objectSerialize 工具类将对象序列化后存入 Redis。 + conn.set(key.getBytes(), objectSerialize.serialize(value)); + } catch (Exception ex) { + // 如果在序列化或者存入过程中出现异常,打印异常堆栈信息,方便排查问题。 + ex.printStackTrace(); + } finally { + redisXMLConfigure.closeConnection(conn); + } + } + + /** + * 设置过期时间存储对象 + * 将可序列化的对象存入 Redis 中,并同时指定过期时间,在过期时间后对象将自动从 Redis 中删除。 + * + * @param key 对象键,先添加前缀后用于存储。 + * @param value 对象值,需要是可序列化的对象。 + * @param time 过期时间,单位为秒,控制对象在 Redis 中的有效时长。 + */ + public void setObject(String key, Object value, int time) { + key = getPreKey(key); + ShardedJedis conn = null; + try { + conn = redisXMLConfigure.getConnection(); + // 使用 setex 方法,它可以在设置对象值的同时直接指定过期时间,一次性完成存储和过期时间设置操作, + // 先将键转换为字节数组,再将序列化后的对象存入 Redis,并设置过期时间。 + conn.setex(key.getBytes(), time, objectSerialize.serialize(value)); + } catch (Exception ex) { + ex.printStackTrace(); + } finally { + redisXMLConfigure.closeConnection(conn); + } + } +} /** * 获取存储的对象 @@ -120,79 +137,101 @@ public class CacheService { * @param key 对象key * @return 存储的对象 */ - public Object getObject(String key) { - key = getPreKey(key); - ShardedJedis conn = null; - try { - conn = redisXMLConfigure.getConnection(); - byte[] obj = conn.get(key.getBytes()); - if (null == obj) - return null; - return objectSerialize.deserialize(obj); - } catch (Exception ex) { - ex.printStackTrace(); - } finally { - redisXMLConfigure.closeConnection(conn); - } - return null; - } + // 根据给定的键从Redis中获取对应的对象。先对键添加前缀处理,然后通过Redis连接获取对象数据,若获取到数据则进行反序列化后返回,若出现异常或未获取到数据则返回null。 + public Object getObject(String key) { + // 给传入的键添加前缀,使其符合项目在Redis中键的规范格式,便于统一管理和准确获取对应的数据。 + key = getPreKey(key); + ShardedJedis conn = null; + try { + // 通过RedisXMLConfigure获取Redis连接对象,ShardedJedis用于在Redis集群等场景下进行操作,以便后续与Redis进行交互。 + conn = redisXMLConfigure.getConnection(); + // 从Redis中获取以字节数组形式存储的对象数据,因为Redis底层很多操作基于字节数组来处理数据存储和读取。 + byte[] obj = conn.get(key.getBytes()); + // 如果获取到的字节数组为null,说明Redis中不存在对应键的数据,直接返回null。 + if (null == obj) + return null; + // 使用objectSerialize工具类对获取到的字节数组进行反序列化操作,将其转换为原始的对象形式并返回。 + return objectSerialize.deserialize(obj); + } catch (Exception ex) { + // 如果在获取数据或者反序列化过程中出现异常,打印异常堆栈信息,方便排查问题所在。 + ex.printStackTrace(); + } finally { + // 无论是否成功获取到对象,都需要关闭Redis连接,通过RedisXMLConfigure提供的方法来关闭连接,释放相关资源。 + redisXMLConfigure.closeConnection(conn); + } + return null; + } - /** - * 删除一个对象 - * - * @param key 对象key值 - * @return - */ - public boolean deleteObject(String key) { - key = getPreKey(key); - ShardedJedis conn = null; - try { - conn = redisXMLConfigure.getConnection(); - return conn.del(key.getBytes()) == 1L; - } catch (Exception ex) { - ex.printStackTrace(); - } finally { - redisXMLConfigure.closeConnection(conn); - } - return false; - } +/** + * 删除一个对象 + * 该方法用于从Redis中删除指定键对应的对象数据,先对键添加前缀处理,然后尝试通过Redis连接执行删除操作, + * 如果删除操作成功(即返回值表示删除的键数量为1)则返回true,若出现异常或删除失败则返回false。 + * + * @param key 对象key值,即要删除的对象在Redis中对应的键。 + * @return 返回一个布尔值,表示是否成功删除对象,true表示删除成功,false表示删除失败或出现异常情况。 + */ +public boolean deleteObject(String key) { + key = getPreKey(key); + ShardedJedis conn = null; + try { + conn = redisXMLConfigure.getConnection(); + // 调用Redis连接对象的del方法,传入要删除的对象键的字节数组形式,尝试删除对应的数据。 + // 该方法返回被删除键的数量,若返回值等于1L,表示成功删除了1个键(即对应要删除的对象键),则返回true,否则返回false。 + return conn.del(key.getBytes()) == 1L; + } catch (Exception ex) { + ex.printStackTrace(); + } finally { + redisXMLConfigure.closeConnection(conn); + } + return false; +} - /** - * - * @Title: isExist @Description: 判断key是否存在 @param @return boolean - * 返回类型 @throws - */ - public boolean isExist(String key) { - key = getPreKey(key); - ShardedJedis conn = null; - try { - conn = redisXMLConfigure.getConnection(); - return conn.exists(key); - } catch (Exception ex) { - ex.printStackTrace(); - } finally { - redisXMLConfigure.closeConnection(conn); - } - return false; - } - - public boolean notExist(String key) { - return !isExist(key); - } - - public boolean delete(String key) { - key = getPreKey(key); - ShardedJedis conn = null; - try { - conn = redisXMLConfigure.getConnection(); - return conn.del(key) == 1; - } catch (Exception ex) { - ex.printStackTrace(); - } finally { - redisXMLConfigure.closeConnection(conn); - } - return false; - } +/** + * 判断key是否存在 + * 通过给定的键,先添加前缀使其符合项目规范后,尝试从Redis中查询该键是否存在, + * 如果存在则返回true,若出现异常或键不存在则返回false。 + * + * @Title: isExist + * @Description: 判断key是否存在 + * @param key 要检查是否存在的键。 + * @return 返回一个布尔值,表示键是否存在于Redis中,true表示存在,false表示不存在或者出现异常情况。 + */ +public boolean isExist(String key) { + key = getPreKey(key); + ShardedJedis conn = null; + try { + conn = redisXMLConfigure.getConnection(); + // 调用Redis连接对象的exists方法,传入要检查的键,该方法会在Redis中查询该键是否存在,存在则返回true,不存在返回false。 + return conn.exists(key); + } catch (Exception ex) { + ex.printStackTrace(); + } finally { + redisXMLConfigure.closeConnection(conn); + } + return false; +} + +// 对isExist方法的结果取反,用于判断给定的键在Redis中是否不存在,返回值为true表示键不存在,false表示键存在。 +public boolean notExist(String key) { + return!isExist(key); +} + +// 从Redis中删除指定的键及其对应的数据,先对键添加前缀处理,然后通过Redis连接执行删除操作, +// 如果删除操作成功(即返回值表示删除的键数量为1)则返回true,若出现异常或删除失败则返回false。 +public boolean delete(String key) { + key = getPreKey(key); + ShardedJedis conn = null; + try { + conn = redisXMLConfigure.getConnection(); + // 调用Redis连接对象的del方法,传入要删除的键,该方法返回被删除键的数量,若返回值等于1,表示成功删除了1个键(即对应要删除的键),则返回true,否则返回false。 + return conn.del(key) == 1; + } catch (Exception ex) { + ex.printStackTrace(); + } finally { + redisXMLConfigure.closeConnection(conn); + } + return false; +} /** * 关于 redis list的操作 将 值 value 插入到列表 key 的表尾(最右边)。 @@ -201,288 +240,359 @@ public class CacheService { * @param value * @return */ - public long putToListEnd(String key, String value) { - key = getPreKey(key); - ShardedJedis conn = null; - try { - conn = redisXMLConfigure.getConnection(); - long length = conn.rpush(key, value); - return length; - } finally { - redisXMLConfigure.closeConnection(conn); - } - } + // 将指定的值插入到以给定键标识的列表(List)的末尾,并返回插入操作后列表的长度。 +// 先对键添加前缀进行规范处理,然后通过获取的 Redis 连接执行插入操作,最后返回列表长度。 + public long putToListEnd(String key, String value) { + // 给传入的键添加前缀,使其符合项目在 Redis 中键的规范格式,便于准确操作对应的列表数据。 + key = getPreKey(key); + ShardedJedis conn = null; + try { + // 通过 RedisXMLConfigure 获取 Redis 连接对象,用于后续与 Redis 进行交互操作。 + conn = redisXMLConfigure.getConnection(); + // 调用 Redis 连接对象的 rpush 方法,将指定的值插入到键对应的列表末尾, + // 该方法会返回操作后列表的长度,即元素个数,然后将此长度返回给调用者。 + long length = conn.rpush(key, value); + return length; + } finally { + // 无论插入操作是否成功,都需要关闭 Redis 连接,通过 RedisXMLConfigure 提供的方法来关闭连接,释放资源。 + redisXMLConfigure.closeConnection(conn); + } + } - /** - * 将value插入集合key的尾部, 并设置过期时间 - * - * @author zhangxin - * @param key - * @param value - * @param seconds - * @param score - * @return long 被成功添加的新成员的数量,不包括那些被更新的、已经存在的成员 - */ - public long addToSortedSetAndExpire(String key, String value, int seconds, double score) { - return addToSortedSet(key, value, seconds, true, score); - } - - - /** - * 将value插入集合key的尾部 增加value的score - * - * @author zhangxin - * @param key - * @param value - * @param score - * @return long 被成功添加的新成员的分数 - */ - public double addToSortedSetScore(String key, String value, double score) { - key = getPreKey(key); - ShardedJedis conn = null; - try { - conn = redisXMLConfigure.getConnection(); - Double zincrby = conn.zincrby(key, score, value); - return zincrby; - } finally { - redisXMLConfigure.closeConnection(conn); - } - } - - /** - * 获取member的Score - * @param key - * @param value - * @return - */ - public Double getMemberScore(String key, String member) { - key = getPreKey(key); - ShardedJedis conn = null; - try { - conn = redisXMLConfigure.getConnection(); - Double zscore = conn.zscore(key, member); - return zscore == null ? 0 : zscore; - } finally { - redisXMLConfigure.closeConnection(conn); - } - } - +/** + * 将value插入集合key的尾部, 并设置过期时间 + * 该方法用于将给定的值插入到有序集合(Sorted Set)中,并为该有序集合设置过期时间, + * 实际调用了 addToSortedSet 方法进行插入操作,传入合适的参数来实现带过期时间的插入功能。 + * + * @author zhangxin + * @param key 有序集合对应的键,用于在 Redis 中唯一标识该有序集合。 + * @param value 要插入到有序集合中的值。 + * @param seconds 过期时间,单位为秒,表示有序集合在 Redis 中有效的时长,超过该时间后集合将自动被删除。 + * @param score 用于给插入的元素设置一个分数(score),有序集合会根据元素的分数来进行排序。 + * @return long 被成功添加的新成员的数量,不包括那些被更新的、已经存在的成员,这里返回值实际上是调用 addToSortedSet 方法后的返回结果。 + */ +public long addToSortedSetAndExpire(String key, String value, int seconds, double score) { + return addToSortedSet(key, value, seconds, true, score); +} - /** - * 将value插入集合key的尾部, 不设置过期时间 - * - * @author zhangxin - * @param key - * @param value - * @param score - * @return long 被成功添加的新成员的数量,不包括那些被更新的、已经存在的成员 - */ - public long addToSortedSet(String key, String value, double score) { - return addToSortedSet(key, value, -1, false, score); - } - - - /** - * 判断member在集合里是否存在 - * - * @return isExist 存在 true 不存在 - */ - public boolean isExistSortedSet(String key, String member) { - key = getPreKey(key); - ShardedJedis conn = null; - try { - conn = redisXMLConfigure.getConnection(); - Long zrank = conn.zrank(key, member); - return zrank != null; - } finally { - redisXMLConfigure.closeConnection(conn); - } - } +/** + * 将value插入集合key的尾部 增加value的score + * 此方法用于将给定的值插入到有序集合(Sorted Set)的末尾,并增加该值对应的分数(score), + * 通过获取 Redis 连接,调用相应的 Redis 命令来实现该功能,最后返回更新后的分数值。 + * + * @author zhangxin + * @param key 有序集合对应的键,用于在 Redis 中唯一标识该有序集合。 + * @param value 要插入到有序集合中的值。 + * @param score 要增加的分数值,用于更新该值在有序集合中的排序位置等相关逻辑。 + * @return long 被成功添加的新成员的分数,这里返回的是操作后该成员对应的新分数值,通过 Redis 的 zincrby 命令返回的结果转换得到。 + */ +public double addToSortedSetScore(String key, String value, double score) { + key = getPreKey(key); + ShardedJedis conn = null; + try { + conn = redisXMLConfigure.getConnection(); + // 调用 Redis 连接对象的 zincrby 方法,将指定的值插入到键对应的有序集合中,并按照给定的分数增量(score)增加其分数, + // 该方法返回更新后的分数值(以 Double 类型表示),然后将此分数值返回给调用者。 + Double zincrby = conn.zincrby(key, score, value); + return zincrby; + } finally { + redisXMLConfigure.closeConnection(conn); + } +} + +/** + * 获取member的Score + * 用于获取给定成员(member)在指定有序集合(以键 key 标识)中的分数(score)值, + * 如果成员不存在于有序集合中,则返回默认值 0,否则返回其实际的分数值。 + * + * @param key 有序集合对应的键,用于在 Redis 中唯一标识该有序集合。 + * @param value 有序集合中的成员,即要获取其分数值的元素。 + * @return 返回该成员对应的分数值,如果成员不存在则返回 0,通过 Redis 的 zscore 命令获取并进行相应的返回值处理。 + */ +public Double getMemberScore(String key, String member) { + key = getPreKey(key); + ShardedJedis conn = null; + try { + conn = redisXMLConfigure.getConnection(); + // 调用 Redis 连接对象的 zscore 方法,尝试获取指定成员在键对应的有序集合中的分数值, + // 该方法返回的分数值可能为 null(表示成员不存在),若为 null 则返回默认值 0,否则返回实际的分数值。 + Double zscore = conn.zscore(key, member); + return zscore == null? 0 : zscore; + } finally { + redisXMLConfigure.closeConnection(conn); + } +} + +/** + * 将value插入集合key的尾部, 不设置过期时间 + * 此方法用于将给定的值插入到有序集合(Sorted Set)的末尾,不设置过期时间, + * 实际是调用 addToSortedSet 方法并传入合适的参数来实现不带过期时间的插入功能,返回插入操作后成功添加的新成员数量。 + * + * @author zhangxin + * @param key 有序集合对应的键,用于在 Redis 中唯一标识该有序集合。 + * @param value 要插入到有序集合中的值。 + * @param score 用于给插入的元素设置一个分数(score),有序集合会根据元素的分数来进行排序。 + * @return long 被成功添加的新成员的数量,不包括那些被更新的、已经存在的成员,这里返回值实际上是调用 addToSortedSet 方法后的返回结果。 + */ +public long addToSortedSet(String key, String value, double score) { + return addToSortedSet(key, value, -1, false, score); +} + +/** + * 判断member在集合里是否存在 + * 通过检查给定成员(member)在指定有序集合(以键 key 标识)中的排名(rank)情况, + * 来判断该成员是否存在于有序集合中,若排名不为 null,则表示成员存在,返回 true,否则返回 false。 + * + * @return isExist 存在 true 不存在 false,表示给定成员是否存在于指定的有序集合中。 + */ +public boolean isExistSortedSet(String key, String member) { + key = getPreKey(key); + ShardedJedis conn = null; + try { + conn = redisXMLConfigure.getConnection(); + // 调用 Redis 连接对象的 zrank 方法,获取指定成员在键对应的有序集合中的排名, + // 如果成员存在于集合中,该方法返回其排名(Long 类型),若不存在则返回 null, + // 根据返回值是否为 null 来判断成员是否存在,并返回相应的布尔值结果。 + Long zrank = conn.zrank(key, member); + return zrank!= null; + } finally { + redisXMLConfigure.closeConnection(conn); + } +} /** * 删除member * * @return isExist 存在 true 不存在 */ - public boolean delSortedSetMember(String key, String[] member) { - key = getPreKey(key); - ShardedJedis conn = null; - try { - conn = redisXMLConfigure.getConnection(); - Long zrem = conn.zrem(key, member); - return zrem >= 1; - } finally { - redisXMLConfigure.closeConnection(conn); - } - } + // 从指定的有序集合(Sorted Set)中删除给定的成员(member),并根据删除操作的结果返回一个布尔值表示是否删除成功。 +// 成功删除至少一个成员时返回 true,否则返回 false。先对键添加前缀规范处理,再通过 Redis 连接执行删除操作并判断结果。 + public boolean delSortedSetMember(String key, String[] member) { + key = getPreKey(key); + ShardedJedis conn = null; + try { + conn = redisXMLConfigure.getConnection(); + // 调用 Redis 连接对象的 zrem 方法,传入有序集合的键和要删除的成员数组,尝试从有序集合中删除这些成员。 + // 该方法返回被成功删除的成员数量(Long 类型),通过判断返回值是否大于等于 1 来确定是否至少成功删除了一个成员,进而返回相应的布尔值结果。 + Long zrem = conn.zrem(key, member); + return zrem >= 1; + } finally { + // 无论删除操作是否成功,都要关闭 Redis 连接,通过 RedisXMLConfigure 提供的方法来释放资源。 + redisXMLConfigure.closeConnection(conn); + } + } - /** - * 将value插入集合key的尾部, 对于setExpire为false的情况, seconds无效 - * - * @return 被成功添加的新成员的数量,不包括那些被更新的、已经存在的成员 - */ - private long addToSortedSet(String key, String value, int seconds, boolean setExpire, double score) { - key = getPreKey(key); - ShardedJedis conn = null; - try { - conn = redisXMLConfigure.getConnection(); - long addNum = conn.zadd(key, score, value); - if (setExpire) { - conn.expire(key, seconds); - } - return addNum; - } finally { - redisXMLConfigure.closeConnection(conn); - } - } +/** + * 将value插入集合key的尾部, 对于setExpire为false的情况, seconds无效 + * 此方法是一个私有方法,用于将给定的值插入到有序集合(Sorted Set)中,并可根据参数决定是否设置过期时间。 + * 返回值为被成功添加的新成员的数量,不包括那些被更新的、已经存在的成员。 + * + * @param key 有序集合对应的键,用于在 Redis 中唯一标识该有序集合。 + * @param value 要插入到有序集合中的值。 + * @param seconds 过期时间,单位为秒,当 setExpire 参数为 true 时,此参数用于设置有序集合的过期时间,否则无效。 + * @param setExpire 布尔值,表示是否设置过期时间,为 true 时设置过期时间,为 false 则不设置。 + * @param score 用于给插入的元素设置一个分数(score),有序集合会根据元素的分数来进行排序。 + * @return long 被成功添加的新成员的数量,不包括那些被更新的、已经存在的成员,通过 Redis 的 zadd 命令插入元素后返回相应的添加数量结果。 + */ +private long addToSortedSet(String key, String value, int seconds, boolean setExpire, double score) { + key = getPreKey(key); + ShardedJedis conn = null; + try { + conn = redisXMLConfigure.getConnection(); + // 调用 Redis 连接对象的 zadd 方法,将指定的值按照给定的分数(score)插入到键对应的有序集合中, + // 该方法返回成功添加的新成员数量(不包含已存在被更新的成员情况),将此数量赋值给 addNum 变量。 + long addNum = conn.zadd(key, score, value); + if (setExpire) { + // 如果 setExpire 参数为 true,调用 Redis 连接对象的 expire 方法,为有序集合设置过期时间(以 seconds 参数指定的秒数为准)。 + conn.expire(key, seconds); + } + return addNum; + } finally { + redisXMLConfigure.closeConnection(conn); + } +} - /** - * 按score降序分页获取有序集合中内容 - * - * @author zhangxin - * @param key - * @param pageNo - * 首页从1开始 - * @param pageSize - * @return Set - */ - public Set getSortedSetByPage(String key, int pageNo, int pageSize) { - key = getPreKey(key); - ShardedJedis conn = null; - try { - if (pageNo < 1) { - pageNo = 1; - } - if (pageSize < 1) { - pageSize = 1; - } - int start = (pageNo - 1) * pageSize; - conn = redisXMLConfigure.getConnection(); - return conn.zrevrange(key, start, start + pageSize - 1); - } catch (Exception ex) { - ex.printStackTrace(); - } finally { - redisXMLConfigure.closeConnection(conn); - } - return null; - } - - public List getListHead(String key) { - key = getPreKey(key); - ShardedJedis conn = null; - try { - conn = redisXMLConfigure.getConnection(); - List result = conn.blpop(1000, key); - - if (null == result || result.size() == 0) - return null; - return result; - } finally { - redisXMLConfigure.closeConnection(conn); - } - } +/** + * 按score降序分页获取有序集合中内容 + * 按照分数(score)降序的顺序,对指定的有序集合进行分页查询,获取对应页面范围的成员内容,返回以字符串集合(Set)形式表示的结果。 + * + * @author zhangxin + * @param key 有序集合对应的键,用于在 Redis 中唯一标识该有序集合。 + * @param pageNo 页码,首页从 1 开始,表示要获取第几页的数据,若传入小于 1 的值,则会自动修正为 1。 + * @param pageSize 每页显示的记录数量,若传入小于 1 的值,则会自动修正为 1。 + * @return Set 返回对应页面范围内的有序集合成员内容,以集合形式返回,如果出现异常则返回 null。 + */ +public Set getSortedSetByPage(String key, int pageNo, int pageSize) { + key = getPreKey(key); + ShardedJedis conn = null; + try { + // 对传入的页码进行合法性校验,如果小于 1,则将其修正为 1,确保页码从 1 开始计数。 + if (pageNo < 1) { + pageNo = 1; + } + // 对传入的每页记录数量进行合法性校验,如果小于 1,则将其修正为 1,确保每页至少有 1 条记录。 + if (pageSize < 1) { + pageSize = 1; + } + // 根据页码和每页记录数量计算出在有序集合中查询的起始索引位置,用于分页获取数据。 + int start = (pageNo - 1) * pageSize; + conn = redisXMLConfigure.getConnection(); + // 调用 Redis 连接对象的 zrevrange 方法,按照分数降序的顺序,从指定的有序集合中获取从起始索引到结束索引(通过计算得出)范围内的成员内容, + // 并将结果以集合形式返回,集合中的元素为有序集合的成员(以字符串形式表示)。 + return conn.zrevrange(key, start, start + pageSize - 1); + } catch (Exception ex) { + // 如果在获取数据过程中出现异常,打印异常堆栈信息,方便排查问题,然后返回 null。 + ex.printStackTrace(); + } finally { + redisXMLConfigure.closeConnection(conn); + } + return null; +} - /** - * 存储map - * - * @param key 键值 - * @param field map field - * @param value map value - * @return if filed exist return 0 else return 1 - */ - public Long hset(String key, String field, String value) { - key = getPreKey(key); - ShardedJedis conn = null; - try { - conn = redisXMLConfigure.getConnection(); - return conn.hset(key, field, value); - } finally { - redisXMLConfigure.closeConnection(conn); - } - } - - public String hset(String key, Map values) { - key = getPreKey(key); - ShardedJedis conn = null; - try { - conn = redisXMLConfigure.getConnection(); - return conn.hmset(key, values); - } finally { - redisXMLConfigure.closeConnection(conn); - } - } - - public String hset(String key, Map values, int time) { - key = getPreKey(key); - ShardedJedis conn = null; - try { - conn = redisXMLConfigure.getConnection(); - String hmset = conn.hmset(key, values); - conn.expire(key, time); - return hmset; - } finally { - redisXMLConfigure.closeConnection(conn); - } - } +// 从以给定键标识的列表(List)头部获取元素,返回获取到的元素列表(List),如果列表为空或者获取操作出现异常则返回 null。 +public List getListHead(String key) { + key = getPreKey(key); + ShardedJedis conn = null; + try { + conn = redisXMLConfigure.getConnection(); + // 调用 Redis 连接对象的 blpop 方法,尝试从列表头部获取元素,传入超时时间(这里是 1000 毫秒)和列表键, + // 该方法会阻塞等待元素出现,直到超时或者获取到元素,返回获取到的元素列表(可能包含一个或多个元素,以 List 形式表示)。 + List result = conn.blpop(1000, key); - /** - * 得到map中存储的field值 - * - * @param key 键值 - * @param field map field - * @return - */ - public String hget(String key, String field) { - key = getPreKey(key); - ShardedJedis conn = null; - try { - conn = redisXMLConfigure.getConnection(); - return conn.hget(key, field); - } finally { - redisXMLConfigure.closeConnection(conn); - } - } + // 如果获取到的结果为 null 或者结果列表大小为 0(即没有获取到元素),则返回 null,表示列表为空或获取失败。 + if (null == result || result.size() == 0) + return null; + return result; + } finally { + redisXMLConfigure.closeConnection(conn); + } +} - /** - * 名称为key的string减1操作 - * - * @param key - * @return - */ - public Long decr(String key) { - key = getPreKey(key); - ShardedJedis conn = null; - try { - conn = redisXMLConfigure.getConnection(); - return conn.decr(key); - } finally { - redisXMLConfigure.closeConnection(conn); - } - } - - /** - * 名称为key的string加1操作 - * - * @param key - * @return - */ - public Long incr(String key) { - key = getPreKey(key); - ShardedJedis conn = null; - try { - conn = redisXMLConfigure.getConnection(); - return conn.incr(key); - } finally { - redisXMLConfigure.closeConnection(conn); - } - } - - private String getPreKey(String key) { - String temp_pre = redisXMLConfigure.getPreKey(); - if (null == temp_pre) { - return REDIS_PRE_KEY + key; - } - return temp_pre + key; - } +/** + * 存储map + * 此方法用于将单个键值对存储到 Redis 的哈希(Hash)数据结构中,对应的键(key)下的指定字段(field)中, + * 返回值表示如果字段已存在则返回 0,否则返回 1,表示插入操作的结果情况。 + * + * @param key 键值,用于在 Redis 中定位到对应的哈希数据结构所在的位置,类似哈希表的名称。 + * @param field map field,哈希表中的字段名,用于在对应的哈希结构下唯一标识一个键值对。 + * @param value map value,要存储到指定字段下的值,即哈希表中字段对应的值内容。 + * @return if filed exist return 0 else return 1,表示如果指定的字段在哈希表中已存在,则返回 0,若不存在则返回 1,根据 Redis 的 hset 命令的返回规则确定。 + */ +public Long hset(String key, String field, String value) { + key = getPreKey(key); + ShardedJedis conn = null; + try { + conn = redisXMLConfigure.getConnection(); + // 调用 Redis 连接对象的 hset 方法,将指定的键(key)、字段(field)和值(value)存储到 Redis 的哈希数据结构中, + // 该方法会根据字段是否已存在返回相应的结果(已存在返回 0,不存在返回 1),将此结果返回给调用者。 + return conn.hset(key, field, value); + } finally { + redisXMLConfigure.closeConnection(conn); + } +} + +// 存储map +// 此方法用于将整个映射(Map)存储到 Redis 的哈希(Hash)数据结构中,以给定的键(key)作为哈希表的标识,一次性存储多个键值对。 +// 返回值为存储操作后的结果信息(具体含义可能取决于 Redis 底层实现对应的返回值情况)。 +public String hset(String key, Map values) { + key = getPreKey(key); + ShardedJedis conn = null; + try { + conn = redisXMLConfigure.getConnection(); + // 调用 Redis 连接对象的 hmset 方法,将给定的映射(包含多个键值对的 Map)存储到指定键对应的哈希数据结构中, + // 该方法会执行批量存储操作,并返回相应的结果信息(通常用于表示操作是否成功等情况),将此结果返回给调用者。 + return conn.hmset(key, values); + } finally { + redisXMLConfigure.closeConnection(conn); + } +} + +// 将给定的映射(Map)存储到 Redis 的哈希(Hash)数据结构中,并为该哈希数据结构设置过期时间。 +// 先获取 Redis 连接,执行存储操作后设置过期时间,最后返回存储操作的结果信息。 +public String hset(String key, Map values, int time) { + key = getPreKey(key); + ShardedJedis conn = null; + try { + conn = redisXMLConfigure.getConnection(); + // 调用 Redis 连接对象的 hmset 方法,将包含多个键值对的映射(values)存储到以 key 为标识的哈希数据结构中, + // 该方法执行存储操作后会返回相应的结果信息(通常用于表示操作是否成功等情况),将此结果暂存到 hmset 变量中。 + String hmset = conn.hmset(key, values); + // 调用 Redis 连接对象的 expire 方法,为以 key 为标识的哈希数据结构设置过期时间,单位为秒,由 time 参数指定。 + conn.expire(key, time); + return hmset; + } finally { + // 无论存储和设置过期时间操作是否成功,都要关闭 Redis 连接,通过 RedisXMLConfigure 提供的方法来释放资源。 + redisXMLConfigure.closeConnection(conn); + } +} + +/** + * 得到map中存储的field值 + * 此方法用于从 Redis 的哈希(Hash)数据结构中,根据给定的键(key)和字段(field)获取对应的值,返回获取到的字符串值。 + * + * @param key 键值,用于在 Redis 中定位到对应的哈希数据结构所在的位置,类似哈希表的名称。 + * @param field map field,哈希表中的字段名,用于在对应的哈希结构下唯一标识一个键值对,通过该字段名获取对应的值。 + * @return 返回在指定哈希数据结构中,对应字段存储的字符串值,如果字段不存在则返回 null(取决于 Redis 的 hget 方法返回值情况)。 + */ +public String hget(String key, String field) { + key = getPreKey(key); + ShardedJedis conn = null; + try { + conn = redisXMLConfigure.getConnection(); + // 调用 Redis 连接对象的 hget 方法,从以 key 为标识的哈希数据结构中获取指定字段(field)对应的字符串值, + // 并将获取到的值直接返回给调用者。 + return conn.hget(key, field); + } finally { + redisXMLConfigure.closeConnection(conn); + } +} + +/** + * 名称为key的string减1操作 + * 对 Redis 中以给定键(key)标识的字符串类型的值执行减 1 操作,返回操作后的结果值(以 Long 类型表示)。 + * + * @param key 要进行减 1 操作的键,对应 Redis 中存储的字符串类型的数据的键名。 + * @return 返回减 1 操作后的结果值(Long 类型),如果键对应的值不存在,则 Redis 会按照默认规则进行处理(通常初始化为 0 后再减 1),返回相应的结果。 + */ +public Long decr(String key) { + key = getPreKey(key); + ShardedJedis conn = null; + try { + conn = redisXMLConfigure.getConnection(); + // 调用 Redis 连接对象的 decr 方法,对以 key 为标识的字符串值执行减 1 操作, + // 该方法会自动将字符串值转换为数字类型(如果能转换成功)进行减 1 操作,并返回操作后的结果(以 Long 类型表示),将此结果返回给调用者。 + return conn.decr(key); + } finally { + redisXMLConfigure.closeConnection(conn); + } +} + +/** + * 名称为key的string加1操作 + * 对 Redis 中以给定键(key)标识的字符串类型的值执行加 1 操作,返回操作后的结果值(以 Long 类型表示)。 + * + * @param key 要进行加 1 操作的键,对应 Redis 中存储的字符串类型的数据的键名。 + * @return 返回加 1 操作后的结果值(Long 类型),如果键对应的值不存在,则 Redis 会按照默认规则进行处理(通常初始化为 0 后再加 1),返回相应的结果。 + */ +public Long incr(String key) { + key = getPreKey(key); + ShardedJedis conn = null; + try { + conn = redisXMLConfigure.getConnection(); + // 调用 Redis 连接对象的 incr 方法,对以 key 为标识的字符串值执行加 1 操作, + // 该方法会自动将字符串值转换为数字类型(如果能转换成功)进行加 1 操作,并返回操作后的结果(以 Long 类型表示),将此结果返回给调用者。 + return conn.incr(key); + } finally { + redisXMLConfigure.closeConnection(conn); + } +} +// 获取处理后的键,用于在 Redis 操作前统一添加前缀,使键符合项目在 Redis 中的命名规范。 +// 如果通过 redisXMLConfigure 获取到的前缀为空,则使用类中定义的默认前缀(REDIS_PRE_KEY)与传入的键拼接,否则使用获取到的前缀与键拼接。 +private String getPreKey(String key) { + String temp_pre = redisXMLConfigure.getPreKey(); + if (null == temp_pre) { + return REDIS_PRE_KEY + key; + } + return temp_pre + key; } diff --git a/tamguo-crawler/src/main/java/com/tamguo/config/redis/ObjectUtil.java b/tamguo-crawler/src/main/java/com/tamguo/config/redis/ObjectUtil.java index 724149a..e11d86d 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/config/redis/ObjectUtil.java +++ b/tamguo-crawler/src/main/java/com/tamguo/config/redis/ObjectUtil.java @@ -6,9 +6,13 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +// ObjectUtil类继承自SerializeTranscoder,主要用于实现对象的序列化、反序列化以及提供对象相等性比较的功能。 public class ObjectUtil extends SerializeTranscoder { + + // 重写父类的serialize方法,用于将给定的对象进行序列化操作,即将对象转换为字节数组的形式,以便在某些场景下(如存储到Redis中)进行传输或持久化存储。 @Override public byte[] serialize(Object value) { + // 如果传入要序列化的对象为null,则抛出空指针异常,因为无法对null对象进行序列化操作。 if (value == null) { throw new NullPointerException("Can't serialize null"); } @@ -16,54 +20,76 @@ public class ObjectUtil extends SerializeTranscoder { ByteArrayOutputStream bos = null; ObjectOutputStream os = null; try { + // 创建一个ByteArrayOutputStream,它用于在内存中缓冲要序列化的对象数据,最终可以转换为字节数组形式。 bos = new ByteArrayOutputStream(); + // 创建一个ObjectOutputStream,它基于ByteArrayOutputStream,用于将对象以Java的序列化机制写入到输出流中。 os = new ObjectOutputStream(bos); + // 使用ObjectOutputStream将传入的对象写入到输出流中,进行实际的序列化操作。 os.writeObject(value); + // 关闭ObjectOutputStream,释放相关资源,一般在写入完成后进行关闭操作,确保数据正确写入到输出流中。 os.close(); + // 关闭ByteArrayOutputStream,释放其占用的内存资源,此时内部缓冲的数据已经准备好可以转换为字节数组了。 bos.close(); + // 将ByteArrayOutputStream中缓冲的数据转换为字节数组,这就是最终序列化后的结果,赋值给result变量以便返回。 result = bos.toByteArray(); } catch (IOException e) { + // 如果在序列化过程中出现IO异常(比如对象不支持序列化、流操作出现问题等), + // 则抛出一个非法参数异常,并将原始的IO异常作为原因附带上,方便排查问题,表明传入的对象可能无法进行序列化操作。 throw new IllegalArgumentException("Non-serializable object", e); } finally { + // 无论是否成功完成序列化操作,都要关闭ObjectOutputStream和ByteArrayOutputStream,释放资源,避免内存泄漏等问题。 close(os); close(bos); } return result; } + // 重写父类的deserialize方法,用于将给定的字节数组进行反序列化操作,即将字节数组转换回原始的对象形式,通常用于从存储(如Redis)中读取数据后还原对象。 @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); + // 创建一个ObjectInputStream,它基于ByteArrayInputStream,用于按照Java的序列化机制从输入流中读取数据并还原为对象。 is = new ObjectInputStream(bis); + // 使用ObjectInputStream从输入流中读取数据并还原为对象,这是实际的反序列化操作,将还原后的对象赋值给result变量。 result = is.readObject(); + // 关闭ObjectInputStream,释放相关资源,确保反序列化操作完成后资源正确释放。 is.close(); + // 关闭ByteArrayInputStream,释放其占用的内存资源。 bis.close(); } } catch (IOException e) { + // 如果在反序列化过程中出现IO异常(比如读取流出现问题、数据格式不正确等),打印异常堆栈信息,方便排查问题。 e.printStackTrace(); } catch (ClassNotFoundException e) { + // 如果在反序列化过程中出现找不到对应类的异常(比如还原对象时找不到对象对应的类定义),同样打印异常堆栈信息,便于查找问题所在。 e.printStackTrace(); } finally { + // 无论是否成功完成反序列化操作,都要关闭ObjectInputStream和ByteArrayInputStream,释放资源,避免内存泄漏等问题。 close(is); close(bis); } return result; } + // 定义一个静态方法equals,用于比较两个对象是否相等,遵循Java中对象相等性比较的常规规则。 public static boolean equals(Object o1, Object o2) { + // 如果两个对象引用完全相同(即指向同一个内存地址),则直接返回true,表示它们相等。 if (o1 == o2) { return true; } else if (o1 == null || o2 == null) { + // 如果其中一个对象为null,而另一个不为null,则返回false,表示它们不相等。 return false; } else { + // 如果两个对象都不为null,调用对象的equals方法(一般需要重写该方法来定义具体的相等性逻辑)来比较它们是否相等,并返回相应的结果。 return o1.equals(o2); } } - -} +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/config/redis/PoolConfigBean.java b/tamguo-crawler/src/main/java/com/tamguo/config/redis/PoolConfigBean.java index c1b9bc1..43e3f9b 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/config/redis/PoolConfigBean.java +++ b/tamguo-crawler/src/main/java/com/tamguo/config/redis/PoolConfigBean.java @@ -1,13 +1,20 @@ package com.tamguo.config.redis; +// PoolConfigBean类用于封装与连接池相关的配置参数,通过定义相应的属性、构造方法以及访问器(getter和setter)方法,方便对这些配置信息进行设置和获取操作,同时重写了toString方法用于方便地输出配置信息的字符串表示形式。 public class PoolConfigBean { + + // 表示连接池中允许的最大活动连接数,即同一时刻能够同时从连接池中获取并使用的最大连接数量。 private int max_active; + // 表示连接池中允许的最大空闲连接数,即连接池中可以闲置的最大连接数量,当空闲连接数超过这个值时,多余的空闲连接可能会被回收。 private int max_idle; + // 表示从连接池中获取连接时,最长的等待时间(单位通常为毫秒),如果在这个时间内无法获取到可用连接,则可能会抛出相应的异常,提示获取连接超时。 private long max_wait; + // 默认的无参构造方法,用于创建PoolConfigBean类的实例,在没有传入初始参数时可以使用,后续可以通过对应的setter方法来设置各个属性的值。 public PoolConfigBean() { } + // 带参数的构造方法,用于在创建PoolConfigBean类实例时,同时初始化各个配置属性的值,方便一次性设置好相关的连接池配置信息。 public PoolConfigBean(int max_active, int max_idle, long max_wait) { super(); this.max_active = max_active; @@ -15,33 +22,39 @@ public class PoolConfigBean { this.max_wait = max_wait; } + // 获取max_active属性值的方法,外部代码可以通过调用这个方法获取连接池中允许的最大活动连接数。 public int getMax_active() { return max_active; } + // 设置max_active属性值的方法,外部代码可以通过调用这个方法来设置连接池中允许的最大活动连接数。 public void setMax_active(int max_active) { this.max_active = max_active; } + // 获取max_idle属性值的方法,外部代码可以通过调用这个方法获取连接池中允许的最大空闲连接数。 public int getMax_idle() { return max_idle; } + // 设置max_idle属性值的方法,外部代码可以通过调用这个方法来设置连接池中允许的最大空闲连接数。 public void setMax_idle(int max_idle) { this.max_idle = max_idle; } + // 获取max_wait属性值的方法,外部代码可以通过调用这个方法获取从连接池中获取连接时的最长等待时间。 public long getMax_wait() { return max_wait; } + // 设置max_wait属性值的方法,外部代码可以通过调用这个方法来设置从连接池中获取连接时的最长等待时间。 public void setMax_wait(long max_wait) { this.max_wait = max_wait; } + // 重写toString方法,用于将PoolConfigBean类的实例以特定的字符串格式进行表示,方便在调试、日志输出等场景下直观地查看连接池配置信息的具体内容。 @Override public String toString() { return "PoolConfig [max_active=" + max_active + ", max_idle=" + max_idle + ", max_wait=" + max_wait + "]"; } - -} +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/config/redis/RedisServerNodeBean.java b/tamguo-crawler/src/main/java/com/tamguo/config/redis/RedisServerNodeBean.java index d8b4297..06702b8 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/config/redis/RedisServerNodeBean.java +++ b/tamguo-crawler/src/main/java/com/tamguo/config/redis/RedisServerNodeBean.java @@ -1,11 +1,18 @@ package com.tamguo.config.redis; +// RedisServerNodeBean类用于封装Redis服务器节点的相关信息,通过定义相应的属性、构造方法以及对应的访问器(getter和setter)方法,方便对这些信息进行设置、获取以及以字符串形式展示等操作。 public class RedisServerNodeBean { + + // 用于存储Redis服务器的IP地址,通过该IP地址可以定位到对应的Redis服务器所在的网络位置,以便建立连接。 private String ip; + // 用于存储Redis服务器监听的端口号,客户端通过指定的IP地址和对应的端口号来与Redis服务器进行通信交互。 private int port; + // 表示是否需要进行身份验证(例如密码验证)才能连接到该Redis服务器,true表示需要验证,false表示不需要验证。 private boolean needAuth; + // 如果needAuth为true,即需要进行身份验证时,此属性用于存储连接Redis服务器所需的验证密码(或其他认证凭据),以确保连接的合法性。 private String auth; + // 构造方法,用于创建RedisServerNodeBean类的实例,并在实例化时初始化各个属性的值,传入的参数分别对应Redis服务器节点的IP地址、端口号、是否需要认证以及认证凭据(密码)。 public RedisServerNodeBean(String ip, int port, boolean needAuth, String auth) { this.ip = ip; this.port = port; @@ -13,41 +20,49 @@ public class RedisServerNodeBean { this.auth = auth; } + // 获取ip属性值的方法,外部代码可以通过调用这个方法获取Redis服务器节点的IP地址信息。 public String getIp() { return ip; } + // 设置ip属性值的方法,外部代码可以通过调用这个方法来修改Redis服务器节点的IP地址信息。 public void setIp(String ip) { this.ip = ip; } + // 获取port属性值的方法,外部代码可以通过调用这个方法获取Redis服务器节点的端口号信息。 public int getPort() { return port; } + // 设置port属性值的方法,外部代码可以通过调用这个方法来修改Redis服务器节点的端口号信息。 public void setPort(int port) { this.port = port; } + // 获取needAuth属性值的方法,外部代码可以通过调用这个方法获取表示是否需要对Redis服务器进行身份验证的布尔值。 public boolean isNeedAuth() { return needAuth; } + // 设置needAuth属性值的方法,外部代码可以通过调用这个方法来修改表示是否需要对Redis服务器进行身份验证的布尔值。 public void setNeedAuth(boolean needAuth) { this.needAuth = needAuth; } + // 获取auth属性值的方法,外部代码可以通过调用这个方法获取连接Redis服务器所需的认证凭据(如密码)信息。 public String getAuth() { return auth; } + // 设置auth属性值的方法,外部代码可以通过调用这个方法来修改连接Redis服务器所需的认证凭据(如密码)信息。 public void setAuth(String auth) { this.auth = auth; } + // 重写toString方法,用于将RedisServerNodeBean类的实例以特定的字符串格式进行表示,方便在调试、日志输出等场景下直观地展示Redis服务器节点的相关信息内容。 @Override public String toString() { return "RedisServer [ip=" + ip + ", port=" + port + ", needAuth=" + needAuth + ", auth=" + auth + "]"; } - -} +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/config/redis/RedisXMLConfigure.java b/tamguo-crawler/src/main/java/com/tamguo/config/redis/RedisXMLConfigure.java index e58c66a..86b3d57 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/config/redis/RedisXMLConfigure.java +++ b/tamguo-crawler/src/main/java/com/tamguo/config/redis/RedisXMLConfigure.java @@ -16,89 +16,126 @@ import redis.clients.jedis.JedisShardInfo; import redis.clients.jedis.ShardedJedis; import redis.clients.jedis.ShardedJedisPool; +// 标识该类是一个Spring组件,在Spring容器启动时会被扫描并实例化,其Bean名称为"redisConfigure",用于配置和管理Redis相关的连接等操作。 @Component("redisConfigure") +// 实现InitializingBean接口,意味着该类在所有属性被设置后,会执行afterPropertiesSet方法来进行一些初始化操作,通常用于加载配置等前置准备工作。 public class RedisXMLConfigure implements InitializingBean { + + // 创建一个日志记录器实例,用于记录与该类相关的操作日志,方便在运行过程中排查问题以及查看关键信息,记录的日志类别基于当前类RedisXMLConfigure。 private static final Logger logger = Logger.getLogger(RedisXMLConfigure.class); + // 用于存储Redis键的前缀,是一个静态变量,在整个应用中可以统一为Redis中的键添加特定的前缀,方便管理和区分不同用途的键。 private static String preKey; + // 用于存储解析后的XML配置文档对象,后续通过操作这个对象来获取各种Redis配置相关的元素和属性信息,初始化为null,后续在初始化方法中进行赋值。 private static Document document = null; + // 定义一个ShardedJedisPool对象,用于管理多个Redis节点的连接池,通过它可以获取到ShardedJedis连接,实现对Redis集群等多节点环境下的数据访问。 private ShardedJedisPool shardedJedisPool; + // 实现InitializingBean接口的方法,在Spring容器完成属性注入后自动调用,用于进行Redis相关配置的初始化工作,比如加载XML配置文件、解析配置信息、创建连接池等操作。 @Override public void afterPropertiesSet() throws Exception { + // 创建一个XMLConfiguration对象,用于读取和解析XML配置文件,它可能封装了一些底层的XML解析相关的逻辑。 XMLConfiguration xmlConfiguration = new XMLConfiguration(); + // 定义要加载的Redis配置文件的路径和文件名,这里指定为"redis.xml",表示从类路径下查找该文件来获取Redis配置信息。 String REDIS_PATH = "redis.xml"; InputStream stream = null; try { + // 通过类加载器获取指定路径下的配置文件输入流,如果获取不到(即配置文件不存在或路径错误),则记录错误日志并抛出运行时异常,表示加载配置文件失败。 stream = this.getClass().getClassLoader().getResourceAsStream(REDIS_PATH); if (stream == null) { logger.error("load redis.xml failed!!!" + REDIS_PATH); throw new RuntimeException("load redis.xml failed"); } logger.info("Redis XML config path:" + REDIS_PATH); + // 使用xmlConfiguration对象读取配置文件输入流中的内容,如果读取成功(返回true),则将解析后的文档对象赋值给document变量,以便后续使用;若读取失败则记录错误日志。 if (xmlConfiguration.readConfigFile(stream)) { document = xmlConfiguration.getDocument(); } else { logger.error("load redis.xml failed!!!"); } } finally { - if (null != stream) + // 无论是否成功读取配置文件,都要关闭输入流,释放相关资源,避免资源泄漏,通过判断输入流是否为null来进行关闭操作。 + if (null!= stream) stream.close(); } - //初始化参数 + + // 调用初始化方法,用于初始化Redis键的前缀信息,从解析后的XML配置中获取相应的值进行设置。 initPreKey(); + // 调用方法初始化连接池的配置参数,从XML配置中读取相关属性值,创建并返回一个PoolConfigBean对象,该对象封装了如最大空闲连接数、最大等待时间等连接池配置信息。 PoolConfigBean pcb = initPoolConfigBean(); + // 调用方法解析配置文件中关于Redis服务器节点的信息,读取每个服务器节点的IP、端口、是否需要认证以及认证密码等信息,返回一个包含多个RedisServerNodeBean对象的列表。 List rsnbs = initRedisServerNodeBeans(); - //实现shardedJedisPool + + // 创建一个JedisPoolConfig对象,用于配置Jedis连接池的相关参数,后续会基于这个配置对象来创建ShardedJedisPool连接池。 JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); + // 注意这里没有设置maxActive(可能在当前配置逻辑中有其他考虑,比如依赖默认值或者后续有特殊处理),而是设置了最大空闲连接数和最大等待时间,从之前初始化得到的PoolConfigBean对象中获取相应的参数值进行设置。 //no maxActive config jedisPoolConfig.setMaxIdle(pcb.getMax_idle()); jedisPoolConfig.setMaxWaitMillis(pcb.getMax_wait()); - shardedJedisPool = new ShardedJedisPool(jedisPoolConfig,getJedisShardInfo(rsnbs)); - if(shardedJedisPool == null){ + + // 创建ShardedJedisPool连接池对象,传入配置好的JedisPoolConfig对象以及根据解析得到的Redis服务器节点信息转换而来的JedisShardInfo列表,用于管理多个Redis节点的连接池,方便后续获取连接进行数据操作。 + shardedJedisPool = new ShardedJedisPool(jedisPoolConfig, getJedisShardInfo(rsnbs)); + if (shardedJedisPool == null) { + // 如果创建连接池失败(返回值为null),则抛出运行时异常,表示Redis配置文件存在错误,导致无法正确创建连接池。 throw new RuntimeException("config redis.xml error"); } } /** * 初始化jedis参数 + * 该方法用于从解析后的XML配置文件中读取连接池相关的配置参数,创建并返回一个PoolConfigBean对象,其中封装了连接池的最大活动连接数、最大空闲连接数以及最大等待时间等参数信息。 */ private PoolConfigBean initPoolConfigBean() { PoolConfigBean poolConfigBean = new PoolConfigBean(); + // 通过文档对象查找名为"pool"的XML元素,获取配置文件中关于连接池的配置节点,假设配置文件中以特定的XML结构来定义连接池相关属性。 Element poolElement = (Element) document.getElementsByTagName("pool").item(0); - int max_active = poolElement.hasAttribute("maxActive") ? Integer.parseInt(poolElement.getAttribute("maxActive")) : -1; - int max_idle = poolElement.hasAttribute("maxIdle") ? Integer.parseInt(poolElement.getAttribute("maxIdle")) : -1; - long max_wait = poolElement.hasAttribute("maxWait") ? Long.parseLong(poolElement.getAttribute("maxWait")) : -1; + // 判断"pool"元素是否有"maxActive"属性,如果有则将其解析为整数并赋值给max_active变量,否则将其设置为 -1(表示可能使用默认值或者后续有其他处理逻辑来确定该参数)。 + int max_active = poolElement.hasAttribute("maxActive")? Integer.parseInt(poolElement.getAttribute("maxActive")) : -1; + // 类似地,获取"maxIdle"属性的值,如果存在则解析为整数赋值给max_idle变量,否则设置为 -1。 + int max_idle = poolElement.hasAttribute("maxIdle")? Integer.parseInt(poolElement.getAttribute("maxIdle")) : -1; + // 获取"maxWait"属性的值,存在则解析为长整型赋值给max_wait变量,否则设置为 -1。 + long max_wait = poolElement.hasAttribute("maxWait")? Long.parseLong(poolElement.getAttribute("maxWait")) : -1; + + // 通过PoolConfigBean对象的setter方法,将获取到的各个参数值设置到对象中,以便后续返回并用于创建JedisPoolConfig对象时设置相应的连接池参数。 poolConfigBean.setMax_active(max_active); poolConfigBean.setMax_idle(max_idle); poolConfigBean.setMax_wait(max_wait); + return poolConfigBean; } /** * 解析配置redis的server列表 + * 该方法用于从解析后的XML配置文件中读取关于Redis服务器节点的配置信息,解析每个服务器节点的IP、端口、是否需要认证以及认证密码等属性,创建并返回一个包含多个RedisServerNodeBean对象的列表,每个对象代表一个Redis服务器节点的配置信息。 */ private List initRedisServerNodeBeans() { List redisServers = new ArrayList(); + // 通过文档对象查找所有名为"server"的XML元素,获取配置文件中定义的所有Redis服务器节点的配置节点列表,后续遍历该列表来解析每个服务器节点的详细信息。 NodeList serverElements = document.getElementsByTagName("server"); int serverLen = serverElements.getLength(); if (serverLen < 1) { - logger.error("redis.servers.server must have one !"); + // 如果没有找到任何"server"元素,即没有配置Redis服务器节点信息,记录错误日志并返回null,表示配置不合法。 + logger.error("redis.servers.server must have one!"); return null; } for (int i = 0; i < serverLen; i++) { + // 遍历每个"server"元素,获取对应的Element对象,用于解析该服务器节点的具体属性信息。 Element serverElement = (Element) serverElements.item(i); - String temp_ip = serverElement.hasAttribute("ip") ? serverElement.getAttribute("ip") : null; + // 获取"server"元素中"ip"属性的值,如果存在则赋值给temp_ip变量,否则设置为null,表示IP地址未配置(这是不符合要求的情况,后续会进行相应的错误处理)。 + String temp_ip = serverElement.hasAttribute("ip")? serverElement.getAttribute("ip") : null; if (temp_ip == null) { + // 如果IP地址为空,记录错误日志并返回null,表示配置不合法,因为IP地址是连接Redis服务器必需的信息。 logger.error("redis.servers.server.ip must be supplied!"); return null; } - String temp_port = serverElement.hasAttribute("port") ? serverElement.getAttribute("port") : "6379"; - String temp_needAuth = serverElement.hasAttribute("needAuth") ? serverElement.getAttribute("needAuth") : "false"; + // 获取"server"元素中"port"属性的值,如果存在则赋值给temp_port变量,否则默认设置为"6379"(Redis默认端口号),确保端口号有一个合理的值。 + String temp_port = serverElement.hasAttribute("port")? serverElement.getAttribute("port") : "6379"; + // 获取"server"元素中"needAuth"属性的值,如果存在则赋值给temp_needAuth变量,否则默认设置为"false",表示默认不需要进行身份验证。 + String temp_needAuth = serverElement.hasAttribute("needAuth")? serverElement.getAttribute("needAuth") : "false"; String temp_auth = null; - // need auth + // 如果"needAuth"属性的值为"true",即表示需要进行身份验证,那么获取"auth"属性的值(认证密码),如果不存在则记录错误日志并返回null,表示配置不合法,因为需要认证时密码是必需的。 if ("true".equals(temp_needAuth)) { - temp_auth = serverElement.hasAttribute("auth") ? serverElement.getAttribute("auth") : null; + temp_auth = serverElement.hasAttribute("auth")? serverElement.getAttribute("auth") : null; if (null == temp_auth) { logger.error("since needAuth is true,auth must be supplied!"); return null; @@ -107,11 +144,14 @@ public class RedisXMLConfigure implements InitializingBean { RedisServerNodeBean rs = null; try { + // 尝试创建一个RedisServerNodeBean对象,传入解析得到的IP地址、端口号、是否需要认证的布尔值以及认证密码(如果需要认证)等信息,将创建的对象赋值给rs变量。 + // 如果端口号解析为整数失败(即不是合法的数字格式),会捕获NumberFormatException异常,记录错误日志并返回null,表示配置不合法。 rs = new RedisServerNodeBean(temp_ip, Integer.parseInt(temp_port), Boolean.parseBoolean(temp_needAuth), temp_auth); } catch (NumberFormatException e) { logger.error("port must be a number!\n" + e.getMessage()); return null; } + // 将创建好的代表Redis服务器节点配置信息的对象添加到列表中,最终返回包含所有服务器节点配置信息的列表。 redisServers.add(rs); } return redisServers; @@ -119,55 +159,71 @@ public class RedisXMLConfigure implements InitializingBean { /** * 转换自定义配置为JedisShardInfo对象 - * @param redisServers - * @return + * 该方法用于将之前解析得到的包含Redis服务器节点配置信息的列表(List)转换为适用于创建ShardedJedisPool连接池的JedisShardInfo列表, + * 每个JedisShardInfo对象包含了单个Redis服务器节点的连接信息,如IP、端口以及密码(如果需要认证)等,方便连接池管理和使用这些节点连接。 + * + * @param redisServers 包含Redis服务器节点配置信息的列表,由initRedisServerNodeBeans方法解析得到。 + * @return 返回一个包含JedisShardInfo对象的列表,用于创建ShardedJedisPool连接池时传入,以配置连接池与各个Redis服务器节点的连接信息。 */ private List getJedisShardInfo(List redisServers) { - if(redisServers == null){ + if (redisServers == null) { + // 如果传入的Redis服务器节点列表为null,记录错误日志并返回null,表示参数不合法,无法进行后续的转换操作。 logger.error("redisServers must not be empty null"); return null; } int serverLen = redisServers.size(); if (serverLen < 1) { + // 如果列表中没有任何元素(即没有配置有效的Redis服务器节点),记录错误日志并返回null,表示不符合要求,因为连接池至少需要一个有效的节点信息来创建。 logger.error("redisServers must not be empty "); return null; } List servers = new ArrayList(serverLen); for (int i = 0; i < serverLen; i++) { + // 遍历传入的Redis服务器节点列表,获取每个RedisServerNodeBean对象,代表一个服务器节点的配置信息。 RedisServerNodeBean redisServer = redisServers.get(i); + // 创建一个JedisShardInfo对象,传入该服务器节点的IP地址和端口号信息,用于构建与该节点的连接相关信息,初始状态下不包含密码信息(如果需要认证,后续再单独设置)。 JedisShardInfo jedisShardInfo = new JedisShardInfo(redisServer.getIp(), redisServer.getPort()); if (redisServer.isNeedAuth()) { + // 如果该服务器节点需要进行身份验证(通过RedisServerNodeBean对象的isNeedAuth方法判断),则设置对应的认证密码,从RedisServerNodeBean对象中获取密码信息并设置到JedisShardInfo对象中。 jedisShardInfo.setPassword(redisServer.getAuth()); } + // 将配置好的JedisShardInfo对象添加到列表中,最终返回包含所有服务器节点连接信息的JedisShardInfo列表,用于创建ShardedJedisPool连接池。 servers.add(jedisShardInfo); } return servers; } - + /* * 初始化redis的key前缀 + * 该方法用于从解析后的XML配置文件中读取Redis键的前缀信息,将其赋值给静态变量preKey,方便后续在操作Redis键时统一添加前缀,进行规范管理。 */ private void initPreKey() { Element preKeyElement = (Element) document.getElementsByTagName("preKey").item(0); - preKey = preKeyElement.hasAttribute("value") ? preKeyElement.getAttribute("value") : ""; + preKey = preKeyElement.hasAttribute("value")? preKeyElement.getAttribute("value") : ""; } + // 获取Redis键前缀的方法,外部代码可以通过调用该方法获取当前配置的Redis键前缀信息,用于在操作Redis时为键添加合适的前缀。 public String getPreKey() { return preKey; } + /** * 从jedis连接池获得一个连接 - * @return + * 该方法用于从之前创建并配置好的ShardedJedisPool连接池中获取一个ShardedJedis连接对象,以便后续通过该连接对象对Redis进行数据操作,如读写数据等。 + * + * @return 返回一个ShardedJedis连接对象,用于与Redis进行交互操作,如果连接池配置或获取连接出现问题可能会抛出相应的异常。 */ public ShardedJedis getConnection() { return shardedJedisPool.getResource(); } + /** * 把连接放回jedis连接池 - * @param resource + * 该方法用于将使用完的ShardedJedis连接对象归还到ShardedJedisPool连接池中,释放连接资源,以便连接池可以对连接进行管理和复用,避免资源浪费和连接泄漏等问题。 + * + * @param resource 要归还的ShardedJedis连接对象,通常是之前通过getConnection方法获取并使用完的连接。 */ public void closeConnection(ShardedJedis resource) { resource.close(); } - -} +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/config/redis/SerializeTranscoder.java b/tamguo-crawler/src/main/java/com/tamguo/config/redis/SerializeTranscoder.java index 63f928f..e0cb7e9 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/config/redis/SerializeTranscoder.java +++ b/tamguo-crawler/src/main/java/com/tamguo/config/redis/SerializeTranscoder.java @@ -2,14 +2,24 @@ package com.tamguo.config.redis; import java.io.Closeable; +// SerializeTranscoder是一个抽象类,用于定义对象序列化和反序列化相关的操作规范,同时提供了一个用于关闭可关闭资源(实现了Closeable接口的对象)的通用方法, +// 具体的序列化和反序列化逻辑需要由继承它的子类去实现。 public abstract class SerializeTranscoder { + // 抽象方法serialize,用于将给定的对象进行序列化操作,将对象转换为字节数组的形式。 + // 具体的序列化实现细节由继承该抽象类的子类来定义,不同的子类可以根据实际需求采用不同的序列化策略(如Java原生序列化、JSON序列化等)。 + // 参数value表示要进行序列化的对象,返回值为序列化后的字节数组。 public abstract byte[] serialize(Object value); + // 抽象方法deserialize,用于将给定的字节数组进行反序列化操作,将字节数组还原为原始的对象形式。 + // 同样,具体的反序列化实现由子类负责,要根据序列化时采用的策略来正确还原对象。 + // 参数in表示要进行反序列化的字节数组,返回值为反序列化后得到的对象。 public abstract Object deserialize(byte[] in); + // 定义一个方法close,用于关闭实现了Closeable接口的资源对象(如输入输出流等),以释放相关的系统资源,避免资源泄漏等问题。 + // 参数closeable表示要关闭的可关闭资源对象,如果传入的对象不为null,则尝试调用其close方法进行关闭操作,若关闭过程中出现异常,则打印异常堆栈信息以便排查问题。 public void close(Closeable closeable) { - if (closeable != null) { + if (closeable!= null) { try { closeable.close(); } catch (Exception e) { @@ -17,4 +27,4 @@ public abstract class SerializeTranscoder { } } } -} +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/config/redis/XMLConfiguration.java b/tamguo-crawler/src/main/java/com/tamguo/config/redis/XMLConfiguration.java index 43b5a56..660a7e1 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/config/redis/XMLConfiguration.java +++ b/tamguo-crawler/src/main/java/com/tamguo/config/redis/XMLConfiguration.java @@ -8,25 +8,37 @@ import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; +// XMLConfiguration类主要用于读取和解析XML配置文件,并提供获取解析后文档对象(Document)的方法,支持通过文件名和输入流(InputStream)两种方式来加载配置文件进行解析。 public class XMLConfiguration { + + // 用于存储解析后的XML文档对象,初始化为null,在成功读取并解析配置文件后,会将对应的Document对象赋值给该变量,方便后续获取和使用解析结果。 private Document document = null; + // 通过指定的配置文件名称(字符串形式的文件名及路径)来读取并解析XML配置文件,若解析成功则返回true,若出现异常导致解析失败(document仍为null)则返回false。 public boolean readConfigFile(String configFilename) { + // 创建一个DocumentBuilderFactory实例,它是用于创建DocumentBuilder对象的工厂类,通过它可以按照默认配置创建用于解析XML文档的DocumentBuilder。 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); try { + // 使用DocumentBuilderFactory创建一个DocumentBuilder对象,该对象用于实际解析XML文档,将其解析为内存中的Document对象表示形式。 DocumentBuilder db = dbf.newDocumentBuilder(); + // 调用DocumentBuilder的parse方法,传入配置文件的名称(路径)作为参数,尝试解析该XML配置文件,并将解析后的Document对象赋值给document变量。 document = db.parse(configFilename); } catch (IOException e) { + // 如果在解析过程中出现IO异常(例如文件不存在、无法读取文件等情况),打印异常堆栈信息,方便排查问题,但不中断程序执行,继续执行后续的返回值判断逻辑。 e.printStackTrace(); } catch (Exception e) { + // 捕获其他可能出现的异常(除了IO异常之外的,比如XML格式错误等导致解析失败的异常),同样打印异常堆栈信息,以便查找问题所在,然后继续执行返回值判断逻辑。 e.printStackTrace(); } + // 判断解析后的Document对象是否为null,如果为null说明解析过程出现问题导致没有成功获取到有效的文档对象,返回false表示读取配置文件失败;否则返回true表示成功。 if (document == null) { return false; } return true; } + // 通过给定的输入流(InputStream)来读取并解析XML配置文件,逻辑与readConfigFile(String configFilename)方法类似,若解析成功则返回true,失败则返回false。 + // 常用于从类路径下获取资源流或者其他来源的输入流来解析配置文件的场景。 public boolean readConfigFile(InputStream stream) { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); try { @@ -43,11 +55,13 @@ public class XMLConfiguration { return true; } + // 获取解析后的XML文档对象的方法,外部代码可以通过调用该方法获取之前成功解析配置文件后得到的Document对象,以便进一步操作文档中的元素、属性等内容。 public Document getDocument() { return document; } + // 受保护的方法,用于设置解析后的XML文档对象,一般在类内部或者子类中可以根据需要调用该方法来更新document变量的值,通常不是由外部直接调用,而是在特定的内部逻辑中使用。 protected void setDocument(Document document) { this.document = document; } -} +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/dao/BookMapper.java b/tamguo-crawler/src/main/java/com/tamguo/dao/BookMapper.java index 2fcf33d..9f54984 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/dao/BookMapper.java +++ b/tamguo-crawler/src/main/java/com/tamguo/dao/BookMapper.java @@ -1,8 +1,12 @@ package com.tamguo.dao; import com.tamguo.config.dao.SuperMapper; +// 引入具体的实体类BookEntity,表明该Mapper接口是针对BookEntity这个实体进行数据库操作相关的定义。 import com.tamguo.model.BookEntity; +// BookMapper接口继承自SuperMapper,SuperMapper是一个通用的Mapper父接口,用于定义一些公共的方法或者规范,方便复用代码和统一接口结构。 +// 这里通过继承指定了该接口是针对BookEntity实体的Mapper,意味着它可以使用SuperMapper中定义的通用方法,并可以根据BookEntity的特点来扩展特定的数据库操作方法。 +// 该接口主要用于定义与BookEntity实体对应的数据库操作方法,例如常见的增删改查等操作,具体的方法实现通常由MyBatis-Plus等相关框架根据接口定义自动生成或者由开发人员手动编写对应的XML映射文件等方式来完成。 public interface BookMapper extends SuperMapper { -} +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/dao/ChapterMapper.java b/tamguo-crawler/src/main/java/com/tamguo/dao/ChapterMapper.java index f73f917..4a7e2d6 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/dao/ChapterMapper.java +++ b/tamguo-crawler/src/main/java/com/tamguo/dao/ChapterMapper.java @@ -1,10 +1,17 @@ package com.tamguo.dao; import org.apache.ibatis.annotations.Param; +// 引入通用的Mapper父接口SuperMapper,它为具体的Mapper接口提供了一些通用的方法和规范,便于代码复用以及统一接口结构,此处针对ChapterEntity实体进行扩展。 import com.tamguo.config.dao.SuperMapper; +// 引入具体的实体类ChapterEntity,表明该Mapper接口主要用于操作与之对应的数据库表,进行数据的增删改查等相关操作。 import com.tamguo.model.ChapterEntity; -public interface ChapterMapper extends SuperMapper{ +// ChapterMapper接口继承自SuperMapper,意味着它可以继承SuperMapper中定义的通用数据库操作方法, +// 同时也可以根据ChapterEntity实体的业务需求来扩展特定的、只适用于该实体的数据库操作方法,用于与数据库中对应的章节(Chapter)相关的数据表进行交互。 +public interface ChapterMapper extends SuperMapper { + // 自定义的数据库查询方法,用于查询满足特定条件的记录数量。 + // 该方法接收一个名为"uid"的参数(通过@Param注解进行参数命名绑定,以便在对应的SQL语句中能准确引用这个参数),参数类型为字符串,用于表示用户的唯一标识(具体含义取决于业务逻辑)。 + // 返回值类型为Integer,即返回满足条件的记录数量(可能是某个用户相关的章节记录数量等,具体取决于数据库表结构和业务需求对应的查询逻辑)。 Integer queryCount(@Param(value="uid")String uid); -} +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/dao/CourseMapper.java b/tamguo-crawler/src/main/java/com/tamguo/dao/CourseMapper.java index 72fdb0d..b59bd1d 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/dao/CourseMapper.java +++ b/tamguo-crawler/src/main/java/com/tamguo/dao/CourseMapper.java @@ -1,9 +1,12 @@ package com.tamguo.dao; import com.tamguo.config.dao.SuperMapper; +// 引入代表课程(Course)相关的实体类CourseEntity,表明这个Mapper接口主要是针对课程实体的数据在数据库层面进行操作而定义的。 import com.tamguo.model.CourseEntity; -public interface CourseMapper extends SuperMapper{ +// CourseMapper接口继承自SuperMapper,SuperMapper是一个通用的Mapper父接口,用于提供一些公共的数据库操作方法和规范,方便复用代码以及统一接口结构。 +// 这里通过继承指定了CourseMapper是针对CourseEntity实体的Mapper,意味着它既可以使用SuperMapper中已有的通用方法,也能够后续根据课程实体相关的业务需求,扩展特定的数据库操作方法,比如课程信息的新增、查询、更新、删除等与数据库交互的操作,这些操作的具体实现通常由MyBatis-Plus等相关框架依据接口定义来生成或者由开发人员手动编写对应的SQL映射来完成。 +public interface CourseMapper extends SuperMapper { -} +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/dao/CrawlerBookMapper.java b/tamguo-crawler/src/main/java/com/tamguo/dao/CrawlerBookMapper.java index bf312b8..e390647 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/dao/CrawlerBookMapper.java +++ b/tamguo-crawler/src/main/java/com/tamguo/dao/CrawlerBookMapper.java @@ -1,8 +1,11 @@ package com.tamguo.dao; import com.tamguo.config.dao.SuperMapper; +// 引入代表爬虫获取的书籍(CrawlerBook)相关的实体类CrawlerBookEntity,这表明该Mapper接口主要用于操作数据库中与通过爬虫获取的书籍数据对应的表。 import com.tamguo.model.CrawlerBookEntity; +// CrawlerBookMapper接口继承自SuperMapper。SuperMapper作为一个通用的Mapper父接口,定义了一些通用的数据库操作方法以及规范,便于在不同实体对应的Mapper接口间实现代码复用,并保证接口结构的统一性。 +// 此处的CrawlerBookMapper通过继承关系,明确了是针对CrawlerBookEntity这个实体的Mapper接口,它既能利用SuperMapper中已有的通用操作逻辑(例如基础的增删改查方法等),又能依据爬虫书籍实体自身的业务特点(比如可能有独特的字段、关联关系等),后续扩展特定的、专门用于处理爬虫书籍数据的数据库操作方法。这些具体的数据库操作方法实现,通常会借助如MyBatis-Plus框架,按照该接口的定义来生成相应代码,或者由开发人员手动编写对应的SQL映射语句等方式来完成,进而实现与数据库中爬虫书籍相关数据表的交互操作,满足业务上对于爬虫获取书籍数据的处理需求。 public interface CrawlerBookMapper extends SuperMapper { -} +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/dao/CrawlerChapterMapper.java b/tamguo-crawler/src/main/java/com/tamguo/dao/CrawlerChapterMapper.java index 9fe25ea..bc6a6c1 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/dao/CrawlerChapterMapper.java +++ b/tamguo-crawler/src/main/java/com/tamguo/dao/CrawlerChapterMapper.java @@ -1,8 +1,11 @@ package com.tamguo.dao; import com.tamguo.config.dao.SuperMapper; +// 引入代表爬虫获取的章节(CrawlerChapter)相关的实体类CrawlerChapterEntity,意味着该Mapper接口主要是用于操作数据库中与通过爬虫获取的章节数据对应的表。 import com.tamguo.model.CrawlerChapterEntity; -public interface CrawlerChapterMapper extends SuperMapper{ +// CrawlerChapterMapper接口继承自SuperMapper。其中,SuperMapper是一个通用的Mapper父接口,它旨在提供一些通用的数据库操作方法与规范,以此达成代码复用以及统一接口结构的目的。 +// 本接口通过继承SuperMapper,明确了自身是针对CrawlerChapterEntity这个实体的Mapper接口。这样一来,它既能够使用SuperMapper中已经定义好的通用数据库操作方法(像常规的增、删、改、查等基础操作),又可以根据爬虫章节实体自身特有的业务需求(例如章节内容来源、特殊的关联关系等情况),后续去扩展那些专门用于处理爬虫章节数据的特定数据库操作方法。而这些数据库操作方法的具体实现,往往会借助如MyBatis-Plus这类框架,按照该接口所定义的内容来生成相应代码,或者由开发人员手动编写对应的SQL映射语句等方式来达成,进而实现与数据库里爬虫章节相关数据表进行交互,以满足业务中针对爬虫获取章节数据的处理要求。 +public interface CrawlerChapterMapper extends SuperMapper { -} +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/dao/CrawlerPaperMapper.java b/tamguo-crawler/src/main/java/com/tamguo/dao/CrawlerPaperMapper.java index 13a5c01..38db3d5 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/dao/CrawlerPaperMapper.java +++ b/tamguo-crawler/src/main/java/com/tamguo/dao/CrawlerPaperMapper.java @@ -4,14 +4,23 @@ import java.util.List; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; - +// 引入MyBatis-Plus的分页插件相关的Page类,用于实现分页查询功能,指定该分页操作针对的是CrawlerPaperEntity实体类型的数据。 import com.baomidou.mybatisplus.plugins.Page; import com.tamguo.config.dao.SuperMapper; +// 引入代表爬虫获取的试卷(CrawlerPaper)相关的实体类CrawlerPaperEntity,表明这个Mapper接口主要用于操作数据库中与通过爬虫获取的试卷数据对应的表。 import com.tamguo.model.CrawlerPaperEntity; -public interface CrawlerPaperMapper extends SuperMapper{ +// CrawlerPaperMapper接口继承自SuperMapper,SuperMapper是通用的Mapper父接口,提供了一些通用的数据库操作方法和规范,这里在此基础上扩展针对爬虫试卷实体的特定操作方法。 +public interface CrawlerPaperMapper extends SuperMapper { + // 使用MyBatis的@Select注解来定义一个SQL查询语句,该语句将直接映射到对应的数据库查询操作,用于获取满足特定条件的CrawlerPaperEntity实体列表数据,实现分页查询功能。 + // SQL语句的逻辑是:从名为"crawler_paper"的表(对应CrawlerPaperEntity实体的数据表)中选择所有记录(用"c.*"表示选择所有列), + // 筛选条件是该表中的"paper_id"字段的值存在于子查询的结果集中,子查询是从名为"tiku_paper"的表中选择"id"字段,筛选条件为"area_id"字段等于传入的参数#{areaId}所代表的值。 + // 最后按照"id"和"queindex"字段升序排序("order by id,queindex asc"),以此来获取按照特定顺序且符合区域条件的试卷数据。 @Select("SELECT c.* FROM crawler_paper c WHERE c.paper_id IN (SELECT p.id FROM tiku_paper p WHERE p.area_id = #{areaId}) order by id,queindex asc;") - List selectPageByAreaId(Page questionPage,@Param(value="areaId") String areaId); + // 定义一个方法selectPageByAreaId,用于执行上述SQL语句所定义的分页查询操作,返回一个包含CrawlerPaperEntity实体的列表。 + // 该方法接收两个参数,一个是Page类型的questionPage,表示分页相关的信息(如页码、每页记录数等),用于实现分页功能; + // 另一个是通过@Param注解命名为"areaId"的字符串类型参数,用于在SQL语句中作为条件进行数据筛选,代表区域的唯一标识(具体含义取决于业务逻辑)。 + List selectPageByAreaId(Page questionPage, @Param(value="areaId") String areaId); -} +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/dao/CrawlerQuestionMapper.java b/tamguo-crawler/src/main/java/com/tamguo/dao/CrawlerQuestionMapper.java index d9c03fc..efb1bd6 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/dao/CrawlerQuestionMapper.java +++ b/tamguo-crawler/src/main/java/com/tamguo/dao/CrawlerQuestionMapper.java @@ -2,12 +2,18 @@ package com.tamguo.dao; import java.util.List; +// 引入MyBatis-Plus插件中用于分页相关操作的Pagination类,通过它可以传递分页相关的参数(如页码、每页记录数等),实现分页查询功能。 import com.baomidou.mybatisplus.plugins.pagination.Pagination; import com.tamguo.config.dao.SuperMapper; +// 引入代表爬虫获取的题目(CrawlerQuestion)相关的实体类CrawlerQuestionEntity,说明该Mapper接口主要用于操作数据库中与通过爬虫获取的题目数据对应的表。 import com.tamguo.model.CrawlerQuestionEntity; -public interface CrawlerQuestionMapper extends SuperMapper{ +// CrawlerQuestionMapper接口继承自SuperMapper,SuperMapper作为通用的Mapper父接口,定义了一些通用的数据库操作方法和规范,方便复用代码以及统一接口结构。 +// 这里的CrawlerQuestionMapper在继承的基础上,可利用通用方法并能根据爬虫题目实体的业务特点来扩展特定的数据库操作方法,用于与数据库中对应的题目数据表进行交互。 +public interface CrawlerQuestionMapper extends SuperMapper { + // 自定义的数据库查询方法,用于按照用户唯一标识(uid,具体含义根据业务逻辑确定)进行分页查询操作,返回一个包含CrawlerQuestionEntity实体的列表。 + // 该方法接收一个Pagination类型的参数page,这个参数包含了分页相关的详细信息,例如当前页码、每页显示的记录数量等,用于控制分页查询的具体行为,以获取对应页面范围内的题目数据记录。 List queryPageOrderUid(Pagination page); - -} + +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/dao/IQuestionOptionsMapper.java b/tamguo-crawler/src/main/java/com/tamguo/dao/IQuestionOptionsMapper.java index 05147ed..b761583 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/dao/IQuestionOptionsMapper.java +++ b/tamguo-crawler/src/main/java/com/tamguo/dao/IQuestionOptionsMapper.java @@ -1,10 +1,14 @@ package com.tamguo.dao; import com.baomidou.mybatisplus.mapper.BaseMapper; +// 引入代表题目选项(QuestionOptions)相关的实体类QuestionOptionsEntity,意味着该Mapper接口主要是用于操作数据库中与题目选项数据对应的表。 import com.tamguo.model.QuestionOptionsEntity; /** * Created by TanGuo on 2019/3/30. + * 这是一个用于操作题目选项数据的Mapper接口,通过继承BaseMapper,它能够利用MyBatis-Plus框架提供的通用数据库操作方法, + * 例如常见的增、删、改、查等基础操作都可以直接使用,无需再手动编写大量重复的SQL语句,方便快捷地实现与数据库中题目选项相关数据表的交互, + * 以满足业务上对于题目选项数据进行处理的需求。后续也可以根据题目选项实体的特定业务场景,在这个接口中扩展一些自定义的数据库操作方法。 */ public interface IQuestionOptionsMapper extends BaseMapper { -} +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/dao/PaperMapper.java b/tamguo-crawler/src/main/java/com/tamguo/dao/PaperMapper.java index 1f1b0a7..863ff13 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/dao/PaperMapper.java +++ b/tamguo-crawler/src/main/java/com/tamguo/dao/PaperMapper.java @@ -1,8 +1,11 @@ package com.tamguo.dao; import com.tamguo.config.dao.SuperMapper; +// 引入代表试卷(Paper)相关的实体类PaperEntity,表明这个Mapper接口主要用于操作数据库中与试卷数据对应的表,进行如试卷信息的查询、新增、修改、删除等数据库交互操作。 import com.tamguo.model.PaperEntity; +// PaperMapper接口继承自SuperMapper。SuperMapper是一个通用的Mapper父接口,其定义了一些通用的数据库操作方法与规范,方便实现代码复用以及统一接口结构。 +// 此处的PaperMapper通过继承关系,明确了是针对PaperEntity实体的Mapper接口。这意味着它既能运用SuperMapper中已有的通用操作逻辑(例如基础的增删改查方法等),又能依据试卷实体自身的业务特点(比如试卷包含的题目、所属学科等相关属性情况),后续去扩展那些专门用于处理试卷数据的特定数据库操作方法。这些具体的数据库操作方法实现,通常会借助如MyBatis-Plus框架,按照该接口的定义来生成相应代码,或者由开发人员手动编写对应的SQL映射语句等方式来完成,进而实现与数据库中试卷相关数据表的交互操作,满足业务上对于试卷数据处理的需求。 public interface PaperMapper extends SuperMapper { -} +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/dao/QuestionMapper.java b/tamguo-crawler/src/main/java/com/tamguo/dao/QuestionMapper.java index 632622b..e502b0c 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/dao/QuestionMapper.java +++ b/tamguo-crawler/src/main/java/com/tamguo/dao/QuestionMapper.java @@ -1,8 +1,11 @@ package com.tamguo.dao; import com.tamguo.config.dao.SuperMapper; +// 引入代表题目(Question)相关的实体类QuestionEntity,这表明该Mapper接口主要是用于操作数据库中与题目数据对应的表,实现和题目相关的数据访问操作,比如增删改查等。 import com.tamguo.model.QuestionEntity; -public interface QuestionMapper extends SuperMapper{ +// QuestionMapper接口继承自SuperMapper。SuperMapper是一个通用的Mapper父接口,它定义了一系列通用的数据库操作方法和规范,其目的在于方便代码复用以及让接口结构更加统一。 +// 本接口通过继承SuperMapper,确定了自身是针对QuestionEntity这个实体的Mapper接口。如此一来,它既能使用SuperMapper里已有的通用数据库操作方法(像常规的新增记录、删除记录、修改记录以及查询记录这些基础操作),又可以依据题目实体自身特有的业务需求(例如题目所属的学科分类、难度级别等相关情况),后续去扩展那些专门用于处理题目数据的特定数据库操作方法。而这些数据库操作方法的具体实现,往往会借助如MyBatis-Plus之类的框架,按照该接口所定义的内容来生成相应代码,或者由开发人员手动编写对应的SQL映射语句等方式来达成,进而实现与数据库里题目相关数据表进行交互,以满足业务中针对题目数据处理的要求。 +public interface QuestionMapper extends SuperMapper { -} +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/dao/SubjectMapper.java b/tamguo-crawler/src/main/java/com/tamguo/dao/SubjectMapper.java index 8065ecd..450de60 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/dao/SubjectMapper.java +++ b/tamguo-crawler/src/main/java/com/tamguo/dao/SubjectMapper.java @@ -1,12 +1,18 @@ package com.tamguo.dao; import org.apache.ibatis.annotations.Param; - +// 引入通用的Mapper父接口SuperMapper,它定义了一些公共的数据库操作方法和规范,便于复用代码以及统一接口结构,此处针对SubjectEntity实体进行扩展。 import com.tamguo.config.dao.SuperMapper; +// 引入代表学科(Subject)相关的实体类SubjectEntity,表明该Mapper接口主要用于操作数据库中与学科数据对应的表,进行如学科信息的查询、新增、修改、删除等数据库交互操作。 import com.tamguo.model.SubjectEntity; -public interface SubjectMapper extends SuperMapper{ +// SubjectMapper接口继承自SuperMapper,意味着它可以继承SuperMapper中定义的通用数据库操作方法, +// 同时也可以根据SubjectEntity实体的业务需求来扩展特定的、只适用于该实体的数据库操作方法,用于与数据库中对应的学科数据表进行交互。 +public interface SubjectMapper extends SuperMapper { + // 自定义的数据库查询方法,用于根据学科名称查找对应的学科实体信息。 + // 该方法接收一个名为"name"的参数(通过@Param注解进行参数命名绑定,以便在对应的SQL语句中能准确引用这个参数),参数类型为字符串,代表要查找的学科的名称(具体含义取决于业务逻辑)。 + // 返回值类型为SubjectEntity,即如果查询到符合条件的学科记录,则返回对应的学科实体对象,若未找到则可能返回null(具体取决于数据库查询结果以及相关的映射配置情况)。 SubjectEntity findByName(@Param(value="name")String name); -} +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/model/BookEntity.java b/tamguo-crawler/src/main/java/com/tamguo/model/BookEntity.java index fb6a2c5..219a964 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/model/BookEntity.java +++ b/tamguo-crawler/src/main/java/com/tamguo/model/BookEntity.java @@ -1,101 +1,124 @@ package com.tamguo.model; import com.baomidou.mybatisplus.annotations.TableName; +// 引入通用的父类SuperEntity,它可能定义了一些通用的实体相关属性和方法,比如主键标识、创建时间、更新时间等公共字段及对应的操作方法,这里让BookEntity继承它来复用这些通用逻辑。 import com.tamguo.config.dao.SuperEntity; import java.io.Serializable; /** * The persistent class for the tiku_book database table. + * 该类用于表示数据库中名为"tiku_book"表对应的实体对象,即书籍(Book)实体类,它封装了与书籍相关的各种属性信息, + * 并且通过继承和实现相关接口,具备了一些通用的实体操作能力以及可序列化的特性,方便在数据持久化、业务逻辑处理以及与其他层交互(如网络传输等需要序列化的场景)中使用。 */ @TableName(value = "tiku_book") +// 使用MyBatis-Plus的@TableName注解来指定该实体类对应的数据库表名,表明这个BookEntity类与名为"tiku_book"的数据库表进行映射,后续框架会根据这种映射关系进行数据的持久化操作(如将实体对象保存到数据库表中,或者从表中查询数据并转换为实体对象等)。 public class BookEntity extends SuperEntity implements Serializable { private static final long serialVersionUID = 1L; - + // 用于存储书籍所属学科的唯一标识,关联到对应的学科表中的主键(具体关联关系取决于数据库设计),通过该字段可以查询出书籍所属的学科信息,类型为字符串。 private String subjectId; - + // 用于存储书籍所属课程的唯一标识,关联到对应的课程表中的主键,方便查询书籍对应的课程相关信息,类型为字符串。 private String courseId; - + // 用于存储书籍的名称,直观展示书籍的标题信息,类型为字符串。 private String name; - + // 用于存储书籍的出版社名称,表明该书籍是由哪个出版社出版的,类型为字符串。 private String publishingHouse; - + // 用于存储书籍中包含的题目数量,可用于统计、查询等业务场景,了解书籍的题目容量情况,类型为整数类型(Integer)。 private Integer questionNum; - + // 用于存储书籍的分值相关信息(具体含义可能根据业务逻辑确定,比如总分数、重点分值等),类型为整数类型(Integer)。 private Integer pointNum; - + // 用于存储书籍的排序序号,可能在展示书籍列表等场景下按照该序号进行排序展示,类型为整数类型(Integer)。 private Integer orders; - + // 预留字段,用于在业务扩展或者有特殊需求时存储额外的相关信息,类型为字符串,初始设计可能没有明确具体用途,方便后续灵活添加数据内容。 private String reserveField1; + // 默认的无参构造函数,用于创建BookEntity类的实例,在一些场景下(如通过反射创建对象等)需要有无参构造函数的存在,方便进行对象的初始化操作。 public BookEntity() { } + // 获取类的序列化版本号的静态方法,由于serialVersionUID是一个静态的、不可变的常量,所以该方法只是简单返回这个常量值, + // 在对象序列化和反序列化过程中,通过版本号来确保序列化前后的对象兼容性等相关验证,外部代码可以通过调用该方法获取版本号信息(虽然实际应用场景中较少直接调用这个方法)。 public static long getSerialVersionUID() { return serialVersionUID; } + // 获取书籍所属学科的唯一标识的方法,外部代码可以通过调用该方法获取subjectId属性的值,即获取书籍对应的学科ID信息。 public String getSubjectId() { return subjectId; } + // 设置书籍所属学科的唯一标识的方法,外部代码可以通过调用该方法来修改subjectId属性的值,更新书籍对应的学科ID信息。 public void setSubjectId(String subjectId) { this.subjectId = subjectId; } + // 获取书籍所属课程的唯一标识的方法,外部代码可以通过调用该方法获取courseId属性的值,即获取书籍对应的课程ID信息。 public String getCourseId() { return courseId; } + // 设置书籍所属课程的唯一标识的方法,外部代码可以通过调用该方法来修改courseId属性的值,更新书籍对应的课程ID信息。 public void setCourseId(String courseId) { this.courseId = courseId; } + // 获取书籍名称的方法,外部代码可以通过调用该方法获取name属性的值,即获取书籍的标题信息。 public String getName() { return name; } + // 设置书籍名称的方法,外部代码可以通过调用该方法来修改name属性的值,更新书籍的标题信息。 public void setName(String name) { this.name = name; } + // 获取书籍出版社名称的方法,外部代码可以通过调用该方法获取publishingHouse属性的值,即获取书籍的出版社信息。 public String getPublishingHouse() { return publishingHouse; } + // 设置书籍出版社名称的方法,外部代码可以通过调用该方法来修改publishingHouse属性的值,更新书籍的出版社信息。 public void setPublishingHouse(String publishingHouse) { this.publishingHouse = publishingHouse; } + // 获取书籍题目数量的方法,外部代码可以通过调用该方法获取questionNum属性的值,即获取书籍包含的题目数量信息。 public Integer getQuestionNum() { return questionNum; } + // 设置书籍题目数量的方法,外部代码可以通过调用该方法来修改questionNum属性的值,更新书籍包含的题目数量信息。 public void setQuestionNum(Integer questionNum) { this.questionNum = questionNum; } + // 获取书籍分值相关信息的方法,外部代码可以通过调用该方法获取pointNum属性的值,即获取书籍的分值信息(具体根据业务逻辑确定的分值含义)。 public Integer getPointNum() { return pointNum; } + // 设置书籍分值相关信息的方法,外部代码可以通过调用该方法来修改pointNum属性的值,更新书籍的分值信息。 public void setPointNum(Integer pointNum) { this.pointNum = pointNum; } + // 获取书籍排序序号的方法,外部代码可以通过调用该方法获取orders属性的值,即获取书籍的排序序号信息,用于展示顺序等相关场景。 public Integer getOrders() { return orders; } + // 设置书籍排序序号的方法,外部代码可以通过调用该方法来修改orders属性的值,更新书籍的排序序号信息。 public void setOrders(Integer orders) { this.orders = orders; } + // 获取预留字段值的方法,外部代码可以通过调用该方法获取reserveField1属性的值,即获取预留字段中存储的相关信息(具体内容根据业务扩展情况而定)。 public String getReserveField1() { return reserveField1; } + // 设置预留字段值的方法,外部代码可以通过调用该方法来修改reserveField1属性的值,更新预留字段中存储的相关信息。 public void setReserveField1(String reserveField1) { this.reserveField1 = reserveField1; } diff --git a/tamguo-crawler/src/main/java/com/tamguo/model/ChapterEntity.java b/tamguo-crawler/src/main/java/com/tamguo/model/ChapterEntity.java index 5039668..e4b9765 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/model/ChapterEntity.java +++ b/tamguo-crawler/src/main/java/com/tamguo/model/ChapterEntity.java @@ -2,128 +2,153 @@ package com.tamguo.model; import java.io.Serializable; import com.baomidou.mybatisplus.annotations.TableName; +// 引入通用的父类SuperEntity,它可能定义了一些通用的实体相关属性和方法,比如主键标识、创建时间、更新时间等公共字段及对应的操作方法,这里让ChapterEntity继承它来复用这些通用逻辑。 import com.tamguo.config.dao.SuperEntity; - /** * The persistent class for the tiku_chapter database table. - * + * 该类用于表示数据库中名为"tiku_chapter"表对应的实体对象,即章节(Chapter)实体类,它封装了与章节相关的各种属性信息, + * 并且通过继承和实现相关接口,具备了一些通用的实体操作能力以及可序列化的特性,方便在数据持久化、业务逻辑处理以及与其他层交互(如网络传输等需要序列化的场景)中使用。 */ -@TableName(value="tiku_chapter") +@TableName(value = "tiku_chapter") +// 使用MyBatis-Plus的@TableName注解来指定该实体类对应的数据库表名,表明这个ChapterEntity类与名为"tiku_chapter"的数据库表进行映射,后续框架会根据这种映射关系进行数据的持久化操作(如将实体对象保存到数据库表中,或者从表中查询数据并转换为实体对象等)。 public class ChapterEntity extends SuperEntity implements Serializable { private static final long serialVersionUID = 1L; + // 用于存储章节所属学科的唯一标识,关联到对应的学科表中的主键(具体关联关系取决于数据库设计),通过该字段可以查询出章节所属的学科信息,类型为字符串。 private String subjectId; - + // 用于存储章节所属课程的唯一标识,关联到对应的课程表中的主键,方便查询章节对应的课程相关信息,类型为字符串。 private String courseId; - + // 用于存储章节的名称,直观展示章节的标题信息,类型为字符串。 private String name; - + // 用于存储章节的父级编码(具体含义可能根据业务中章节的层级结构编码规则来确定,比如用于标识上级章节等),类型为字符串。 private String parentCode; - + // 用于存储章节的所有父级编码的组合(可能是按照一定格式拼接起来,方便进行层级关系查询等操作),类型为字符串。 private String parentCodes; - + // 用于存储章节在树形结构中的层级数(比如根章节层级为 1,其子章节层级依次递增等,用于体现章节的层级深度),类型为整数类型(Integer)。 private Integer treeLevel; - + // 用于标识章节是否为叶子节点(即该章节下是否还有子章节,true 表示是叶子节点,没有子章节;false 表示不是叶子节点,还有下级章节),类型为布尔类型(Boolean)。 private Boolean treeLeaf; - + // 用于存储章节所属书籍的唯一标识,关联到对应的书籍表中的主键,便于查询章节所属的书籍信息,类型为字符串。 private String bookId; - + // 用于存储章节中包含的题目数量,可用于统计、查询等业务场景,了解章节的题目容量情况,类型为整数类型(Integer)。 private Integer questionNum; - + // 用于存储章节的分值相关信息(具体含义可能根据业务逻辑确定,比如章节内题目总分数等),类型为整数类型(Integer)。 private Integer pointNum; - + // 用于存储章节的排序序号,可能在展示章节列表等场景下按照该序号进行排序展示,类型为整数类型(Integer)。 private Integer orders; + // 默认的无参构造函数,用于创建ChapterEntity类的实例,在一些场景下(如通过反射创建对象等)需要有无参构造函数的存在,方便进行对象的初始化操作。 public ChapterEntity() { } + // 获取章节所属课程的唯一标识的方法,外部代码可以通过调用该方法获取courseId属性的值,即获取章节对应的课程ID信息。 public String getCourseId() { return this.courseId; } + // 设置章节所属课程的唯一标识的方法,外部代码可以通过调用该方法来修改courseId属性的值,更新章节对应的课程ID信息。 public void setCourseId(String courseId) { this.courseId = courseId; } + // 获取章节名称的方法,外部代码可以通过调用该方法获取name属性的值,即获取章节的标题信息。 public String getName() { return this.name; } + // 设置章节名称的方法,外部代码可以通过调用该方法来修改name属性的值,更新章节的标题信息。 public void setName(String name) { this.name = name; } + // 获取章节题目数量的方法,外部代码可以通过调用该方法获取questionNum属性的值,即获取章节包含的题目数量信息。 public Integer getQuestionNum() { return questionNum; } + // 设置章节题目数量的方法,外部代码可以通过调用该方法来修改questionNum属性的值,更新章节包含的题目数量信息。 public void setQuestionNum(Integer questionNum) { this.questionNum = questionNum; } + // 获取章节分值相关信息的方法,外部代码可以通过调用该方法获取pointNum属性的值,即获取章节的分值信息(具体根据业务逻辑确定的分值含义)。 public Integer getPointNum() { return pointNum; } + // 设置章节分值相关信息的方法,外部代码可以通过调用该方法来修改pointNum属性的值,更新章节的分值信息。 public void setPointNum(Integer pointNum) { this.pointNum = pointNum; } + // 获取章节排序序号的方法,外部代码可以通过调用该方法获取orders属性的值,即获取章节的排序序号信息,用于展示顺序等相关场景。 public Integer getOrders() { return orders; } + // 设置章节排序序号的方法,外部代码可以通过调用该方法来修改orders属性的值,更新章节的排序序号信息。 public void setOrders(Integer orders) { this.orders = orders; } + // 获取章节所属书籍的唯一标识的方法,外部代码可以通过调用该方法获取bookId属性的值,即获取章节对应的书籍ID信息。 public String getBookId() { return bookId; } + // 设置章节所属书籍的唯一标识的方法,外部代码可以通过调用该方法来修改bookId属性的值,更新章节对应的书籍ID信息。 public void setBookId(String bookId) { this.bookId = bookId; } + // 获取章节在树形结构中的层级数的方法,外部代码可以通过调用该方法获取treeLevel属性的值,即获取章节的层级信息。 public Integer getTreeLevel() { return treeLevel; } + // 设置章节在树形结构中的层级数的方法,外部代码可以通过调用该方法来修改treeLevel属性的值,更新章节的层级信息。 public void setTreeLevel(Integer treeLevel) { this.treeLevel = treeLevel; } + // 获取章节是否为叶子节点的方法,外部代码可以通过调用该方法获取treeLeaf属性的值,即获取章节是否还有子章节的标识信息。 public Boolean getTreeLeaf() { return treeLeaf; } + // 设置章节是否为叶子节点的方法,外部代码可以通过调用该方法来修改treeLeaf属性的值,更新章节是否还有子章节的标识信息。 public void setTreeLeaf(Boolean treeLeaf) { this.treeLeaf = treeLeaf; } + // 获取章节的父级编码的方法,外部代码可以通过调用该方法获取parentCode属性的值,即获取章节的上级章节编码等相关信息(具体根据业务编码规则确定含义)。 public String getParentCode() { return parentCode; } + // 设置章节的父级编码的方法,外部代码可以通过调用该方法来修改parentCode属性的值,更新章节的上级章节编码等相关信息。 public void setParentCode(String parentCode) { this.parentCode = parentCode; } + // 获取章节的所有父级编码组合的方法,外部代码可以通过调用该方法获取parentCodes属性的值,即获取章节完整的上级章节编码路径等相关信息(具体根据业务编码规则确定含义)。 public String getParentCodes() { return parentCodes; } + // 设置章节的所有父级编码组合的方法,外部代码可以通过调用该方法来修改parentCodes属性的值,更新章节完整的上级章节编码路径等相关信息。 public void setParentCodes(String parentCodes) { this.parentCodes = parentCodes; } + // 获取章节所属学科的唯一标识的方法,外部代码可以通过调用该方法获取subjectId属性的值,即获取章节对应的学科ID信息。 public String getSubjectId() { return subjectId; } + // 设置章节所属学科的唯一标识的方法,外部代码可以通过调用该方法来修改subjectId属性的值,更新章节对应的学科ID信息。 public void setSubjectId(String subjectId) { this.subjectId = subjectId; } - } \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/model/CourseEntity.java b/tamguo-crawler/src/main/java/com/tamguo/model/CourseEntity.java index f72b315..320c9ed 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/model/CourseEntity.java +++ b/tamguo-crawler/src/main/java/com/tamguo/model/CourseEntity.java @@ -9,63 +9,90 @@ import com.tamguo.config.dao.SuperEntity; * */ @TableName(value="tiku_course") +public class CourseEntity extends SuperEntity implements Serializable package com.tamguo.model; + +import java.io.Serializable; +import com.baomidou.mybatisplus.annotations.TableName; +// 引入通用的父类SuperEntity,它通常定义了一些通用的实体相关属性和方法,例如主键标识、创建时间、更新时间等公共字段及对应的操作方法,这里让CourseEntity继承它来复用这些通用逻辑。 +import com.tamguo.config.dao.SuperEntity; + +/** + * The persistent class for the tiku_course database table. + * 该类用于表示数据库中名为"tiku_course"表对应的实体对象,即课程(Course)实体类,它封装了与课程相关的各种属性信息, + * 并且通过继承和实现相关接口,具备了一些通用的实体操作能力以及可序列化的特性,方便在数据持久化、业务逻辑处理以及与其他层交互(如网络传输等需要序列化的场景)中使用。 + */ +@TableName(value = "tiku_course") +// 使用MyBatis-Plus的@TableName注解来指定该实体类对应的数据库表名,表明这个CourseEntity类与名为"tiku_course"的数据库表进行映射,后续框架会根据这种映射关系进行数据的持久化操作(如将实体对象保存到数据库表中,或者从表中查询数据并转换为实体对象等)。 public class CourseEntity extends SuperEntity implements Serializable { private static final long serialVersionUID = 1L; + // 用于存储课程的名称,直观展示课程的标题信息,类型为字符串,方便在业务中对课程进行识别和区分。 private String name; - + // 用于存储课程所属学科的唯一标识,关联到对应的学科表中的主键(具体关联关系取决于数据库设计),通过该字段可以查询出课程所属的学科信息,类型为字符串。 private String subjectId; - + // 用于存储课程的分值相关信息(具体含义可能根据业务逻辑确定,比如课程所涉及题目总分数等),类型为整数类型(Integer)。 private Integer pointNum; - + // 用于存储课程中包含的题目数量,可用于统计、查询等业务场景,了解课程的题目容量情况,类型为整数类型(Integer)。 private Integer questionNum; - + // 用于存储课程的排序序号,可能在展示课程列表等场景下按照该序号进行排序展示,类型为整数类型(Integer)。 private Integer sort; - - + + // 默认的无参构造函数,用于创建CourseEntity类的实例,在一些场景下(如通过反射创建对象等)需要有无参构造函数的存在,方便进行对象的初始化操作。 public CourseEntity() { } + // 获取课程名称的方法,外部代码可以通过调用该方法获取name属性的值,即获取课程的标题信息。 public String getName() { return this.name; } + // 设置课程名称的方法,外部代码可以通过调用该方法来修改name属性的值,更新课程的标题信息。 public void setName(String name) { this.name = name; } + // 获取课程所属学科的唯一标识的方法,外部代码可以通过调用该方法获取subjectId属性的值,即获取课程对应的学科ID信息。 public String getSubjectId() { return this.subjectId; } + // 设置课程所属学科的唯一标识的方法,外部代码可以通过调用该方法来修改subjectId属性的值,更新课程对应的学科ID信息。 public void setSubjectId(String subjectId) { this.subjectId = subjectId; } + // 获取课程题目数量的方法,外部代码可以通过调用该方法获取questionNum属性的值,即获取课程包含的题目数量信息。 public Integer getQuestionNum() { return questionNum; } + // 设置课程题目数量的方法,外部代码可以通过调用该方法来修改questionNum属性的值,更新课程包含的题目数量信息。 public void setQuestionNum(Integer questionNum) { this.questionNum = questionNum; } + // 获取课程分值相关信息的方法,外部代码可以通过调用该方法获取pointNum属性的值,即获取课程的分值信息(具体根据业务逻辑确定的分值含义)。 public Integer getPointNum() { return pointNum; } + // 设置课程分值相关信息的方法,外部代码可以通过调用该方法来修改pointNum属性的值,更新课程的分值信息。 public void setPointNum(Integer pointNum) { this.pointNum = pointNum; } + // 获取类的序列化版本号的静态方法,由于serialVersionUID是一个静态的、不可变的常量,所以该方法只是简单返回这个常量值。 + // 在对象序列化和反序列化过程中,通过版本号来确保序列化前后的对象兼容性等相关验证,不过实际应用场景中较少直接调用这个方法。 public static long getSerialversionuid() { return serialVersionUID; } + // 获取课程排序序号的方法,外部代码可以通过调用该方法获取sort属性的值,即获取课程的排序序号信息,用于展示顺序等相关场景。 public Integer getSort() { return sort; } + // 设置课程排序序号的方法,外部代码可以通过调用该方法来修改sort属性的值,更新课程的排序序号信息。 public void setSort(Integer sort) { this.sort = sort; } diff --git a/tamguo-crawler/src/main/java/com/tamguo/model/CrawlerBookEntity.java b/tamguo-crawler/src/main/java/com/tamguo/model/CrawlerBookEntity.java index 79c5041..f6ac134 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/model/CrawlerBookEntity.java +++ b/tamguo-crawler/src/main/java/com/tamguo/model/CrawlerBookEntity.java @@ -1,50 +1,64 @@ package com.tamguo.model; import com.baomidou.mybatisplus.annotations.TableName; +// 引入通用的父类SuperEntity,它一般定义了一些通用的实体相关属性与方法,比如可能包含主键标识、创建时间、更新时间等公共字段及对应的操作方法,此处让CrawlerBookEntity继承它来复用这些通用逻辑。 import com.tamguo.config.dao.SuperEntity; import java.io.Serializable; /** * The persistent class for the crawler_book database table. + * 该类用于表示数据库中名为"crawler_book"表对应的实体对象,即通过爬虫获取的书籍(Crawler Book)实体类。它封装了与这类书籍相关的特定属性信息, + * 并且借助继承和接口实现,具备了通用的实体操作能力以及可序列化的特性,方便在数据持久化操作(如保存到数据库、从数据库查询出来转换为实体对象等)、业务逻辑处理以及与其他层交互(像网络传输等需要序列化的场景)中使用。 */ @TableName(value = "crawler_book") +// 使用MyBatis-Plus的@TableName注解来明确该实体类对应的数据库表名,表明CrawlerBookEntity类与"crawler_book"这个数据库表存在映射关系,后续框架会依据此映射进行相应的数据持久化处理。 public class CrawlerBookEntity extends SuperEntity implements Serializable { private static final long serialVersionUID = 1L; + // 用于存储书籍的URL地址,这个地址可能是爬虫最初获取该书籍相关信息的来源地址,通过它可以再次访问书籍详情页面或者用于溯源等操作,类型为字符串。 private String bookUrl; - + // 用于存储书籍的唯一标识(UID),该标识在整个系统中能唯一确定这本书籍,方便在不同业务场景下对特定书籍进行区分、查找和关联操作,类型为字符串。 private String bookUid; - + // 用于存储书籍的排序序号,可能在展示通过爬虫获取的书籍列表等场景下,按照这个序号进行排序展示,使书籍呈现出特定的顺序,类型为整数类型(Integer)。 private Integer orders; + // 默认的无参构造函数,用于创建CrawlerBookEntity类的实例,在一些诸如通过反射创建对象等场景下,需要有无参构造函数存在,以方便进行对象的初始化操作。 public CrawlerBookEntity() { } + // 获取类的序列化版本号的静态方法,由于serialVersionUID是一个静态的、不可变的常量,所以该方法仅是简单返回这个常量值。 + // 在对象序列化和反序列化过程中,通过版本号来确保序列化前后对象的兼容性等相关验证,不过在实际应用场景中,通常较少直接调用这个方法。 public static long getSerialVersionUID() { return serialVersionUID; } + // 获取书籍URL地址的方法,外部代码可以通过调用该方法获取bookUrl属性的值,即获取该书籍对应的来源网址信息。 public String getBookUrl() { return bookUrl; } + // 设置书籍URL地址的方法,外部代码可以通过调用该方法来修改bookUrl属性的值,更新书籍对应的来源网址信息。 public void setBookUrl(String bookUrl) { this.bookUrl = bookUrl; } + // 获取书籍唯一标识(UID)的方法,外部代码可以通过调用该方法获取bookUid属性的值,即获取用于唯一确定这本书籍的标识信息。 public String getBookUid() { return bookUid; } + // 设置书籍唯一标识(UID)的方法,外部代码可以通过调用该方法来修改bookUid属性的值,更新用于唯一确定这本书籍的标识信息。 public void setBookUid(String bookUid) { this.bookUid = bookUid; } + // 获取书籍排序序号的方法,外部代码可以通过调用该方法获取orders属性的值,即获取书籍在展示列表等场景下的排序序号信息。 public Integer getOrders() { return orders; } + // 设置书籍排序序号的方法,外部代码可以通过调用该方法来修改orders属性的值,更新书籍在展示列表等场景下的排序序号信息。 public void setOrders(Integer orders) { this.orders = orders; } diff --git a/tamguo-crawler/src/main/java/com/tamguo/model/CrawlerChapterEntity.java b/tamguo-crawler/src/main/java/com/tamguo/model/CrawlerChapterEntity.java index aa90258..47559ce 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/model/CrawlerChapterEntity.java +++ b/tamguo-crawler/src/main/java/com/tamguo/model/CrawlerChapterEntity.java @@ -1,50 +1,61 @@ package com.tamguo.model; import com.baomidou.mybatisplus.annotations.TableName; +// 引入通用的父类SuperEntity,它通常会定义一些通用的实体相关属性与方法,像可能包含实体的主键标识、创建时间、更新时间等公共字段及对应的操作逻辑,这里让CrawlerChapterEntity继承它,以此复用这些通用的部分。 import com.tamguo.config.dao.SuperEntity; -@TableName(value="crawler_chapter") -public class CrawlerChapterEntity extends SuperEntity{ - +// 使用MyBatis-Plus的@TableName注解来指定该实体类对应的数据库表名,表明CrawlerChapterEntity类与名为"crawler_chapter"的数据库表存在映射关系,后续框架(如MyBatis-Plus)会依据此映射进行数据的持久化操作,例如将实体对象保存到该表中,或者从表中查询数据并转换为该实体对象等。 +@TableName(value = "crawler_chapter") +public class CrawlerChapterEntity extends SuperEntity { + private static final long serialVersionUID = 1L; + // 用于存储章节的唯一标识(UID),这个唯一标识在整个系统中能唯一确定该章节,方便在不同业务场景下对特定章节进行查找、关联以及区分等操作,类型为字符串。 private String chapterUid; - + // 用于存储章节所属课程的唯一标识(UID),通过它可以关联到对应的课程信息,明确该章节隶属于哪门课程,类型为字符串。 private String courseUid; - + // 用于存储章节对应的URL地址,该地址可能是爬虫获取该章节相关信息的来源网址,例如可以通过这个网址再次访问章节详情页面等,类型为字符串。 private String chapterUrl; - + // 用于存储章节所属学科的唯一标识(UID),关联到对应的学科信息,便于知晓该章节所属的学科范畴,类型为字符串。 private String subjectUid; + // 获取章节唯一标识(UID)的方法,外部代码可以通过调用该方法获取chapterUid属性的值,即得到用于唯一确定该章节的标识信息,方便在业务逻辑中对特定章节进行操作。 public String getChapterUid() { return chapterUid; } + // 设置章节唯一标识(UID)的方法,外部代码可以通过调用该方法来修改chapterUid属性的值,更新用于唯一确定该章节的标识信息,例如在章节信息发生变化需要重新标识等场景下使用。 public void setChapterUid(String chapterUid) { this.chapterUid = chapterUid; } + // 获取章节所属课程的唯一标识(UID)的方法,外部代码可以通过调用该方法获取courseUid属性的值,即获取该章节对应的课程唯一标识信息,进而可以查询关联的课程详情等。 public String getCourseUid() { return courseUid; } + // 设置章节所属课程的唯一标识(UID)的方法,外部代码可以通过调用该方法来修改courseUid属性的值,更新该章节对应的课程唯一标识信息,比如课程发生变更等情况时进行相应修改。 public void setCourseUid(String courseUid) { this.courseUid = courseUid; } + // 获取章节对应的URL地址的方法,外部代码可以通过调用该方法获取chapterUrl属性的值,即得到该章节的来源网址信息,可用于再次访问详情页面等操作。 public String getChapterUrl() { return chapterUrl; } + // 设置章节对应的URL地址的方法,外部代码可以通过调用该方法来修改chapterUrl属性的值,更新该章节的来源网址信息,例如网址发生变化等情况时进行调整。 public void setChapterUrl(String chapterUrl) { this.chapterUrl = chapterUrl; } + // 获取章节所属学科的唯一标识(UID)的方法,外部代码可以通过调用该方法获取subjectUid属性的值,即获取该章节对应的学科唯一标识信息,便于查询学科相关内容。 public String getSubjectUid() { return subjectUid; } + // 设置章节所属学科的唯一标识(UID)的方法,外部代码可以通过调用该方法来修改subjectUid属性的值,更新该章节对应的学科唯一标识信息,比如学科归属调整等场景下使用。 public void setSubjectUid(String subjectUid) { this.subjectUid = subjectUid; } -} +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/model/CrawlerPaperEntity.java b/tamguo-crawler/src/main/java/com/tamguo/model/CrawlerPaperEntity.java index 3c6ad09..9f3e503 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/model/CrawlerPaperEntity.java +++ b/tamguo-crawler/src/main/java/com/tamguo/model/CrawlerPaperEntity.java @@ -1,44 +1,55 @@ package com.tamguo.model; import com.baomidou.mybatisplus.annotations.TableName; +// 引入通用的父类SuperEntity,它一般会定义一些通用的实体相关属性和方法,例如可能包含实体的主键标识、创建时间、更新时间等公共字段及对应的操作逻辑,此处CrawlerPaperEntity继承它来复用这些通用部分,方便统一管理实体相关的通用操作。 import com.tamguo.config.dao.SuperEntity; -@TableName(value="crawler_paper") -public class CrawlerPaperEntity extends SuperEntity{ +// 使用MyBatis-Plus的@TableName注解来指定该实体类对应的数据库表名,表明CrawlerPaperEntity类与名为"crawler_paper"的数据库表存在映射关系,后续框架(如MyBatis-Plus)会依据这个映射关系进行数据的持久化操作,比如将实体对象保存到该表中,或者从表中查询数据并转换为该实体对象等操作。 +@TableName(value = "crawler_paper") +public class CrawlerPaperEntity extends SuperEntity { private static final long serialVersionUID = 1L; + // 用于存储试卷中题目对应的URL地址,该地址可能是爬虫获取题目相关信息的来源网址,通过这个网址可以访问具体题目的详情页面等,方便后续进一步获取题目完整内容或者相关拓展信息,类型为字符串。 private String questionUrl; - + // 用于存储试卷的唯一标识(ID),这个标识在整个系统中能唯一确定该试卷,方便在不同业务场景下对特定试卷进行查找、关联以及区分等操作,例如可以依据试卷ID查询该试卷包含的所有题目等相关信息,类型为字符串。 private String paperId; - + // 用于存储题目的索引序号,可能用于表示题目在试卷中的顺序位置,方便按照顺序展示题目或者进行题目相关的排序、定位等操作,类型为整数类型(Integer)。 private Integer queindex; - + + // 获取试卷中题目对应的URL地址的方法,外部代码可以通过调用该方法获取questionUrl属性的值,即得到题目的来源网址信息,便于在业务逻辑中进行相关的网页访问或者数据获取操作。 public String getQuestionUrl() { return questionUrl; } + // 设置试卷中题目对应的URL地址的方法,外部代码可以通过调用该方法来修改questionUrl属性的值,更新题目的来源网址信息,例如当题目对应的网页地址发生变化等情况时进行相应调整。 public void setQuestionUrl(String questionUrl) { this.questionUrl = questionUrl; } + // 获取类的序列化版本号的静态方法,由于serialVersionUID是一个静态的、不可变的常量,所以该方法仅是简单返回这个常量值。 + // 在对象序列化和反序列化过程中,通过版本号来确保序列化前后对象的兼容性等相关验证,不过在实际应用场景中通常较少直接调用这个方法。 public static long getSerialversionuid() { return serialVersionUID; } + // 获取试卷的唯一标识(ID)的方法,外部代码可以通过调用该方法获取paperId属性的值,即获取用于唯一确定该试卷的标识信息,方便在业务逻辑中对特定试卷进行各种操作,比如查询试卷详情、关联试卷题目等。 public String getPaperId() { return paperId; } + // 设置试卷的唯一标识(ID)的方法,外部代码可以通过调用该方法来修改paperId属性的值,更新用于唯一确定该试卷的标识信息,例如在试卷信息发生变更需要重新标识等场景下使用。 public void setPaperId(String paperId) { this.paperId = paperId; } + // 获取题目的索引序号的方法,外部代码可以通过调用该方法获取queindex属性的值,即得到题目在试卷中的顺序位置信息,便于按照顺序处理题目或者进行展示相关的操作。 public Integer getQueindex() { return queindex; } + // 设置题目的索引序号的方法,外部代码可以通过调用该方法来修改queindex属性的值,更新题目在试卷中的顺序位置信息,比如题目顺序发生调整等情况时进行相应修改。 public void setQueindex(Integer queindex) { this.queindex = queindex; } -} +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/model/CrawlerQuestionEntity.java b/tamguo-crawler/src/main/java/com/tamguo/model/CrawlerQuestionEntity.java index c1eba61..9713d15 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/model/CrawlerQuestionEntity.java +++ b/tamguo-crawler/src/main/java/com/tamguo/model/CrawlerQuestionEntity.java @@ -2,54 +2,58 @@ package com.tamguo.model; import java.io.Serializable; import com.baomidou.mybatisplus.annotations.TableName; +// 引入通用的父类SuperEntity,它通常定义了一些通用的实体相关属性与方法,例如可能包含实体的主键标识、创建时间、更新时间等公共字段及对应的操作逻辑,这里让CrawlerQuestionEntity继承它,以便复用这些通用的部分,实现实体相关通用操作的统一管理。 import com.tamguo.config.dao.SuperEntity; - - /** * The persistent class for the crawler_question database table. - * + * 该类用于表示数据库中名为"crawler_question"表对应的实体对象,即通过爬虫获取的题目(Crawler Question)实体类。它封装了与这类题目相关的特定属性信息, + * 并且借助继承和接口实现,具备了通用的实体操作能力以及可序列化的特性,方便在数据持久化操作(如保存到数据库、从数据库查询出来转换为实体对象等)、业务逻辑处理以及与其他层交互(像网络传输等需要序列化的场景)中使用。 */ -@TableName(value="crawler_question") +@TableName(value = "crawler_question") +// 使用MyBatis-Plus的@TableName注解来明确该实体类对应的数据库表名,表明CrawlerQuestionEntity类与"crawler_question"这个数据库表存在映射关系,后续框架会依据此映射进行相应的数据持久化处理,例如将该实体对象存储到对应的表中,或者从表中查询数据并转换为该实体对象等操作。 public class CrawlerQuestionEntity extends SuperEntity implements Serializable { private static final long serialVersionUID = 1L; + // 用于存储题目对应的URL地址,这个地址可能是爬虫最初获取该题目相关信息的来源网址,通过它可以再次访问题目详情页面,以便获取题目完整内容或者进行其他相关拓展操作,类型为字符串。 private String questionUrl; - + // 用于存储题目所属章节的唯一标识(ID),通过该标识可以关联到对应的章节信息,明确该题目隶属于哪个章节,方便进行题目与章节之间的关联查询以及业务逻辑处理,类型为字符串。 private String chapterId; - + // 用于存储题目的状态信息(具体含义可能根据业务逻辑确定,比如题目是否已审核、是否可用等不同的状态描述),类型为字符串。 private String status; - + + // 获取题目对应的URL地址的方法,外部代码可以通过调用该方法获取questionUrl属性的值,即得到该题目对应的来源网址信息,便于在业务逻辑中进行相关的网页访问或者数据获取操作。 public String getQuestionUrl() { return questionUrl; } - - + // 设置题目对应的URL地址的方法,外部代码可以通过调用该方法来修改questionUrl属性的值,更新题目的来源网址信息,例如当题目对应的网页地址发生变化等情况时进行相应调整。 public void setQuestionUrl(String questionUrl) { this.questionUrl = questionUrl; } - - + // 获取题目所属章节的唯一标识(ID)的方法,外部代码可以通过调用该方法获取chapterId属性的值,即获取该题目对应的章节标识信息,进而可以查询关联的章节详情等内容,方便进行题目与章节相关的业务操作。 public String getChapterId() { return chapterId; } + // 设置题目所属章节的唯一标识(ID)的方法,外部代码可以通过调用该方法来修改chapterId属性的值,更新该题目对应的章节标识信息,比如章节发生变更等情况下进行相应修改。 public void setChapterId(String chapterId) { this.chapterId = chapterId; } + // 获取题目的状态信息的方法,外部代码可以通过调用该方法获取status属性的值,即得到题目当前所处的状态描述信息,便于根据状态进行不同的业务逻辑处理,比如对不同状态的题目进行不同的展示或者操作限制等。 public String getStatus() { return status; } + // 设置题目的状态信息的方法,外部代码可以通过调用该方法来修改status属性的值,更新题目当前所处的状态描述信息,例如当题目状态发生改变时进行相应的设置操作。 public void setStatus(String status) { this.status = status; } - - + // 获取类的序列化版本号的静态方法,由于serialVersionUID是一个静态的、不可变的常量,所以该方法仅是简单返回这个常量值。 + // 在对象序列化和反序列化过程中,通过版本号来确保序列化前后对象的兼容性等相关验证,不过在实际应用场景中通常较少直接调用这个方法。 public static long getSerialversionuid() { return serialVersionUID; } diff --git a/tamguo-crawler/src/main/java/com/tamguo/model/PaperEntity.java b/tamguo-crawler/src/main/java/com/tamguo/model/PaperEntity.java index c84b0c3..a71a9c6 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/model/PaperEntity.java +++ b/tamguo-crawler/src/main/java/com/tamguo/model/PaperEntity.java @@ -2,245 +2,356 @@ package com.tamguo.model; import java.io.Serializable; import org.apache.commons.lang3.StringUtils; +// 引入Apache Commons Lang3库中的StringUtils类,用于进行字符串相关的操作,比如判断字符串是否为空等,方便后续代码中对字符串的处理逻辑。 import com.alibaba.fastjson.JSONArray; +// 引入阿里巴巴的FastJSON库中的JSONArray类,用于处理JSON格式的数据,在本类中可能用于解析和操作与题目信息等相关的JSON数据结构。 + import com.baomidou.mybatisplus.annotations.TableField; import com.baomidou.mybatisplus.annotations.TableName; -import com.tamguo.config.dao.SuperEntity; +// 这两个注解来自MyBatis-Plus框架,@TableName用于指定该实体类对应的数据库表名,@TableField用于对实体类中的字段进行一些额外的配置,比如指定与数据库表字段的映射关系等。 +import com.tamguo.config.dao.SuperEntity; +// 自定义的SuperEntity类,当前类继承自它,SuperEntity可能包含了一些通用的实体相关属性和方法(具体取决于其定义),供子类继承使用,以减少重复代码编写。 -/** - * The persistent class for the tiku_chapter database table. - * - */ -@TableName(value="tiku_paper") +// PaperEntity类用于表示试卷相关的实体信息,它继承自SuperEntity类,并实现了Serializable接口,使得该类的对象能够进行序列化操作,方便在网络传输、存储等场景下使用。 +// 通过@TableName注解将其与数据库中的tiku_paper表进行关联(这里表名指定为tiku_paper),意味着这个类在数据库操作层面会对应这个表来进行数据的增删改查等操作。 +@TableName(value = "tiku_paper") public class PaperEntity extends SuperEntity implements Serializable { private static final long serialVersionUID = 1L; + // 学科ID,用于标识该试卷所属的学科,通过这个字段可以关联到具体的学科信息,比如与学科表中的对应学科进行关联,方便进行按学科筛选、查询试卷等操作。 private String subjectId; - + + // 课程ID,用于表明该试卷所属的课程,可用于将试卷与特定课程进行关联,便于课程相关的业务逻辑处理,例如查找某课程下的所有试卷等。 private String courseId; - + + // 学校ID,用于确定该试卷所属的学校,通过它可以与学校相关信息建立联系,比如获取该学校发布的所有试卷等,在多学校应用场景下很有用。 private String schoolId; - + + // 区域ID,用于标识试卷所涉及的区域,可能关联到某个地区的行政区划等信息,可用于按区域统计、查询试卷等业务场景。 private String areaId; - + + // 创建者ID,用于记录创建该试卷的用户的唯一标识,方便追踪试卷的创建来源以及后续涉及权限管理等相关操作,比如只有创建者能修改试卷等情况。 private String createrId; - + + // 试卷名称,用于存储试卷的具体名称,在系统中可通过这个名称直观地展示试卷,方便用户识别和选择不同的试卷,也便于进行试卷名称相关的搜索等操作。 private String name; - + + // 题目信息,以字符串形式存储试卷中包含的题目相关内容,可能采用JSON格式等进行序列化存储,后续需要通过相应的解析方法(如本类中的getQueInfo方法)来处理并获取具体的题目数据结构。 private String questionInfo; - + + // 试卷类型,用于区分不同类型的试卷,例如模拟试卷、真题试卷、专项练习试卷等,方便根据类型进行试卷分类展示、筛选等业务逻辑处理。 private String type; - + + // 年份,用于记录试卷对应的年份信息,可能是试卷的出题年份、适用年份等,有助于按年份对试卷进行分类、查询以及统计分析等操作。 private String year; - + + // 下载次数,用于统计该试卷被下载的频次,方便了解试卷的受欢迎程度以及在数据分析等方面提供下载相关的数据支持。 private Integer downHits; - + + // 查看次数,用于统计该试卷被用户打开查看的频次,可辅助分析试卷的关注度等情况,与下载次数一起可以综合评估试卷在系统中的使用热度。 private Integer openHits; - + + // SEO标题,用于搜索引擎优化,当试卷相关页面在搜索引擎中展示时,这个标题会影响搜索结果的展示效果以及排名等情况,提高试卷页面的曝光度。 private String seoTitle; - + + // SEO关键词,同样是为了搜索引擎优化而设置的属性,填写与试卷相关的关键搜索词汇,帮助搜索引擎更好地理解试卷内容,从而提高在搜索结果中的出现概率。 private String seoKeywords; - + + // SEO描述,用于提供试卷的简要描述信息,便于搜索引擎展示更详细准确的内容摘要,吸引用户点击查看试卷详情,也是搜索引擎优化的重要组成部分。 private String seoDescription; - - @TableField(value="free") + + // 这里使用@TableField注解并指定value="free",表示该属性与数据库表中的"free"字段进行映射,其含义可能与试卷是否免费等相关业务逻辑有关,具体取决于业务需求。 + @TableField(value = "free") private String free; - + + // 要点信息,可能用于存储试卷所涵盖的重点知识点等相关内容,方便用户提前了解试卷考查的关键内容,也有助于进行试卷内容相关的分析和整理。 private String point; - + + // 价格信息,用于记录试卷的售价(如果试卷是收费的情况),在涉及试卷购买等业务场景下会用到这个属性来进行价格相关的计算和展示等操作。 private String money; - - @TableField(exist=false) + + // 这里使用@TableField注解并指定exist=false,说明该属性在数据库表中不存在对应字段,它可能是为了在业务逻辑处理过程中临时存储学科名称信息,方便在不直接关联查询数据库的情况下进行展示等操作。 + @TableField(exist = false) private String subjectName; - - @TableField(exist=false) + + // 同样,该属性在数据库表中不存在对应字段,用于临时存储课程名称信息,以便在相关业务逻辑中使用,比如在展示试卷详情时将课程名称一并展示出来等情况。 + @TableField(exist = false) private String courseName; - - @TableField(exist=false) + + // 也是数据库表中不存在对应字段的属性,用于临时存储区域名称信息,方便在页面展示或者业务逻辑处理中使用区域的具体名称,而不是只展示区域ID等情况。 + @TableField(exist = false) private String areaName; - - @TableField(exist=false) + + // 同样是不在数据库表中有对应字段的属性,用于临时存储学校名称信息,便于在需要展示试卷所属学校的具体名称时使用,提升用户体验和信息展示的完整性。 + @TableField(exist = false) private String schoolName; - - public JSONArray getQueInfo(){ - if(StringUtils.isEmpty(getQuestionInfo())){ + + // getQueInfo方法用于获取经过解析后的题目信息,它首先判断questionInfo属性是否为空字符串(通过StringUtils.isEmpty方法判断), + // 如果为空,则返回null,表示没有有效的题目信息可获取;如果不为空,则使用FastJSON的JSONArray.parseArray方法将questionInfo字符串解析为JSONArray对象并返回, + // 这样外部代码就可以通过这个方法获取到解析后的题目数据结构,方便进行后续关于题目方面的操作,比如遍历题目、提取题目具体内容等。 + public JSONArray getQueInfo() { + if (StringUtils.isEmpty(getQuestionInfo())) { return null; } return JSONArray.parseArray(getQuestionInfo()); } +} + // 获取课程ID的方法,用于返回当前对象中存储的课程ID属性值。 +// 外部代码可以通过调用这个方法来获取试卷所属课程对应的唯一标识,便于后续进行与课程相关的业务逻辑处理,比如根据课程筛选试卷等。 + public String getCourseId() { + return courseId; + } - public String getCourseId() { - return courseId; - } - - public void setCourseId(String courseId) { - this.courseId = courseId; - } - - public String getAreaId() { - return areaId; - } + // 设置课程ID的方法,通过传入一个新的课程ID字符串参数,来更新当前对象中存储的课程ID属性值。 +// 常用于在修改试卷相关信息时,对试卷所属课程进行重新指定等业务场景,例如试卷被调整到其他课程下时使用该方法更新课程ID信息。 + public void setCourseId(String courseId) { + this.courseId = courseId; + } - public void setAreaId(String areaId) { - this.areaId = areaId; - } + // 获取区域ID的方法,用于返回当前对象中存储的区域ID属性值。 +// 外部代码调用此方法后,可以得知试卷所涉及的区域标识,方便进行按区域分类、查询试卷等操作,例如获取某个特定区域内的所有试卷情况。 + public String getAreaId() { + return areaId; + } - public String getName() { - return name; - } + // 设置区域ID的方法,传入一个新的区域ID字符串,即可更新当前对象中存储的区域ID属性值。 +// 可应用于试卷的区域信息变更场景,比如试卷的适用区域发生改变时,通过该方法来修改对应的区域ID。 + public void setAreaId(String areaId) { + this.areaId = areaId; + } - public void setName(String name) { - this.name = name; - } + // 获取试卷名称的方法,返回当前对象中存储的试卷名称属性值。 +// 使得外部代码能够获取到试卷的具体名称,方便在系统中展示试卷信息给用户查看,也利于进行试卷名称相关的搜索、筛选等操作。 + public String getName() { + return name; + } - public String getType() { - return type; - } + // 设置试卷名称的方法,通过传入新的试卷名称字符串,可更新当前对象中存储的试卷名称属性值。 +// 常用于修改试卷名称的业务场景,比如对试卷进行重新命名,使其名称更符合实际内容或者更便于用户识别等情况。 + public void setName(String name) { + this.name = name; + } - public void setType(String type) { - this.type = type; - } + // 获取试卷类型的方法,用于返回当前对象中存储的试卷类型属性值。 +// 外部代码调用该方法后,就能知晓试卷属于何种类型(如模拟试卷、真题试卷等),便于按照类型对试卷进行分类展示、筛选、统计分析等业务逻辑处理。 + public String getType() { + return type; + } - public String getYear() { - return year; - } + // 设置试卷类型的方法,传入一个新的试卷类型字符串,来更新当前对象中存储的试卷类型属性值。 +// 可应用于试卷类型变更的场景,例如将一份模拟试卷重新标记为专项练习试卷等情况,通过此方法来修改试卷类型信息。 + public void setType(String type) { + this.type = type; + } - public void setYear(String year) { - this.year = year; - } + // 获取试卷年份的方法,返回当前对象中存储的试卷年份属性值。 +// 调用该方法后,外部代码可以获取到试卷对应的年份信息,方便按年份对试卷进行分类、查询以及相关的数据统计分析等操作,比如查看某一年的所有试卷情况。 + public String getYear() { + return year; + } - public static long getSerialversionuid() { - return serialVersionUID; - } + // 设置试卷年份的方法,通过传入新的年份字符串,更新当前对象中存储的试卷年份属性值。 +// 常用于修正试卷年份信息或者对试卷适用年份进行调整等业务场景,确保试卷年份信息的准确性。 + public void setYear(String year) { + this.year = year; + } - public String getSchoolId() { - return schoolId; - } + // 此方法用于获取序列化版本号(serialVersionUID),不过在实际应用中,serialVersionUID通常是作为一个静态常量直接使用, +// 很少通过这样的方法去获取它。它主要用于判断对象序列化和反序列化过程中的兼容性,确保在不同版本的类定义下序列化操作的正确性。 + public static long getSerialversionuid() { + return serialVersionUID; + } - public void setSchoolId(String schoolId) { - this.schoolId = schoolId; - } + // 获取学校ID的方法,返回当前对象中存储的学校ID属性值。 +// 外部代码调用该方法后,能够得知试卷所属的学校标识,方便进行与学校相关的业务逻辑处理,例如获取某学校发布的所有试卷等操作。 + public String getSchoolId() { + return schoolId; + } - public Integer getDownHits() { - return downHits; - } + // 设置学校ID的方法,传入一个新的学校ID字符串,用于更新当前对象中存储的学校ID属性值。 +// 可应用于试卷所属学校发生变更的场景,比如试卷从一个学校转移到另一个学校管理时,通过该方法来修改学校ID信息。 + public void setSchoolId(String schoolId) { + this.schoolId = schoolId; + } - public void setDownHits(Integer downHits) { - this.downHits = downHits; - } + // 获取试卷下载次数的方法,返回当前对象中存储的试卷下载次数属性值。 +// 外部代码通过调用这个方法可以获取到试卷被下载的频次情况,方便了解试卷的受欢迎程度以及用于数据分析等方面,比如统计热门试卷下载量排名等操作。 + public Integer getDownHits() { + return downHits; + } - public Integer getOpenHits() { - return openHits; - } + // 设置试卷下载次数的方法,通过传入一个新的下载次数整数值,来更新当前对象中存储的下载次数属性值。 +// 常用于在每次试卷被下载后,相应地更新下载次数统计信息的业务场景,确保下载次数数据的准确性。 + public void setDownHits(Integer downHits) { + this.downHits = downHits; + } - public void setOpenHits(Integer openHits) { - this.openHits = openHits; - } + // 获取试卷查看次数的方法,返回当前对象中存储的试卷查看次数属性值。 +// 外部代码调用此方法后,可以得知试卷被用户打开查看的频次情况,有助于分析试卷的关注度等,与下载次数结合可综合评估试卷在系统中的使用热度。 + public Integer getOpenHits() { + return openHits; + } - public String getQuestionInfo() { - return questionInfo; - } + // 设置试卷查看次数的方法,传入一个新的查看次数整数值,更新当前对象中存储的查看次数属性值。 +// 一般在每次试卷页面被用户打开查看后,通过该方法来更新查看次数的统计数据,以便准确记录试卷的查看热度情况。 + public void setOpenHits(Integer openHits) { + this.openHits = openHits; + } - public void setQuestionInfo(String questionInfo) { - this.questionInfo = questionInfo; - } + // 获取试卷题目信息的方法,返回当前对象中存储的试卷题目信息属性值。 +// 外部代码调用该方法后,能够获取到试卷包含的题目相关内容(通常是以某种格式序列化后的字符串,可能需要进一步解析处理),以便进行后续关于题目方面的操作。 + public String getQuestionInfo() { + return questionInfo; + } - public String getCreaterId() { - return createrId; - } + // 设置试卷题目信息的方法,通过传入新的题目信息字符串,更新当前对象中存储的题目信息属性值。 +// 常用于更新试卷题目内容的业务场景,比如对试卷中的题目进行修改、替换等操作后,使用该方法来保存新的题目信息。 + public void setQuestionInfo(String questionInfo) { + this.questionInfo = questionInfo; + } - public void setCreaterId(String createrId) { - this.createrId = createrId; - } + // 获取试卷创建者ID的方法,返回当前对象中存储的创建者ID属性值。 +// 外部代码调用该方法后,可以得知是谁创建了该试卷,方便追踪试卷的创建来源以及进行后续涉及权限管理等相关操作,例如只有创建者能修改试卷等情况。 + public String getCreaterId() { + return createrId; + } - public String getSeoTitle() { - return seoTitle; - } + // 设置试卷创建者ID的方法,传入一个新的创建者ID字符串,用于更新当前对象中存储的创建者ID属性值。 +// 可应用于试卷创建者信息变更的场景,比如将试卷的创建权限转移给其他用户时,通过该方法来修改创建者ID。 + public void setCreaterId(String createrId) { + this.createrId = createrId; + } - public void setSeoTitle(String seoTitle) { - this.seoTitle = seoTitle; - } + // 获取试卷SEO标题的方法,返回当前对象中存储的SEO标题属性值。 +// 外部代码调用该方法后,能够获取到用于搜索引擎优化的试卷标题内容,方便在搜索引擎相关业务处理中使用,比如设置网页标题等操作,以提高试卷页面的曝光度。 + public String getSeoTitle() { + return seoTitle; + } - public String getSeoKeywords() { - return seoKeywords; - } + // 设置试卷SEO标题的方法,通过传入新的SEO标题字符串,更新当前对象中存储的SEO标题属性值。 +// 常用于优化试卷在搜索引擎中展示效果的业务场景,比如修改标题使其更符合搜索引擎排名规则,吸引更多用户点击查看试卷详情等情况。 + public void setSeoTitle(String seoTitle) { + this.seoTitle = seoTitle; + } - public void setSeoKeywords(String seoKeywords) { - this.seoKeywords = seoKeywords; - } + // 获取试卷SEO关键词的方法,返回当前对象中存储的SEO关键词属性值。 +// 外部代码调用该方法后,可以获取到用于搜索引擎优化的关键搜索词汇,帮助搜索引擎更好地理解试卷内容,从而提高试卷在搜索结果中的出现概率。 + public String getSeoKeywords() { + return seoKeywords; + } - public String getSeoDescription() { - return seoDescription; - } + // 设置试卷SEO关键词的方法,传入一个新的SEO关键词字符串,来更新当前对象中存储的SEO关键词属性值。 +// 常用于根据试卷内容和业务需求调整关键词的业务场景,使得试卷在搜索引擎中更容易被用户搜索到,提升流量和曝光度。 + public void setSeoKeywords(String seoKeywords) { + this.seoKeywords = seoKeywords; + } - public void setSeoDescription(String seoDescription) { - this.seoDescription = seoDescription; - } + // 获取试卷SEO描述的方法,返回当前对象中存储的SEO描述属性值。 +// 外部代码调用该方法后,能够获取到试卷的简要描述信息,便于搜索引擎展示更详细准确的内容摘要,吸引用户点击查看试卷详情,是搜索引擎优化的重要组成部分。 + public String getSeoDescription() { + return seoDescription; + } - public String getCourseName() { - return courseName; - } + // 设置试卷SEO描述的方法,通过传入新的SEO描述字符串,更新当前对象中存储的SEO描述属性值。 +// 常用于更新试卷在搜索引擎中展示的描述内容的业务场景,使其更具吸引力,提高用户点击进入试卷详情页面的概率。 + public void setSeoDescription(String seoDescription) { + this.seoDescription = seoDescription; + } - public void setCourseName(String courseName) { - this.courseName = courseName; - } + // 获取课程名称的方法,返回当前对象中存储的课程名称属性值。 +// 需注意该属性在数据库表中通常不存在对应字段,它可能是为了在业务逻辑处理过程中临时存储课程名称信息,方便在不直接关联查询数据库的情况下进行展示等操作。 + public String getCourseName() { + return courseName; + } - public String getAreaName() { - return areaName; - } + // 设置课程名称的方法,传入一个新的课程名称字符串,更新当前对象中存储的课程名称属性值。 +// 常用于在业务逻辑处理中,当获取到课程相关信息后,将课程名称保存到该属性中,以便后续方便地展示课程名称给用户查看等情况。 + public void setCourseName(String courseName) { + this.courseName = courseName; + } - public void setAreaName(String areaName) { - this.areaName = areaName; - } + // 获取区域名称的方法,返回当前对象中存储的区域名称属性值。 +// 同样,该属性在数据库表中一般无对应字段,是用于临时存储区域名称信息,方便在页面展示或者业务逻辑处理中使用区域的具体名称,而不是只展示区域ID等情况。 + public String getAreaName() { + return areaName; + } - public String getSchoolName() { - return schoolName; - } + // 设置区域名称的方法,传入一个新的区域名称字符串,用于更新当前对象中存储的区域名称属性值。 +// 常用于在获取区域相关信息后,将具体的区域名称赋值给该属性,便于后续展示给用户或者在业务逻辑中使用区域的具体名称进行判断、筛选等操作。 + public void setAreaName(String areaName) { + this.areaName = areaName; + } - public void setSchoolName(String schoolName) { - this.schoolName = schoolName; - } + // 获取学校名称的方法,返回当前对象中存储的学校名称属性值。 +// 此属性在数据库表中通常也不存在对应字段,它用于临时存储学校名称信息,便于在需要展示试卷所属学校的具体名称时使用,提升用户体验和信息展示的完整性。 + public String getSchoolName() { + return schoolName; + } - public String getSubjectId() { - return subjectId; - } + // 设置学校名称的方法,传入一个新的学校名称字符串,更新当前对象中存储的学校名称属性值。 +// 常用于在业务逻辑处理中,当得知试卷所属学校后,将学校的具体名称保存到该属性中,方便后续展示给用户查看,使试卷信息展示更全面直观。 + public void setSchoolName(String schoolName) { + this.schoolName = schoolName; + } - public void setSubjectId(String subjectId) { - this.subjectId = subjectId; - } + // 获取学科ID的方法,返回当前对象中存储的学科ID属性值。 +// 外部代码通过调用这个方法可以获取试卷所属学科对应的唯一标识,便于后续进行与学科相关的业务逻辑处理,比如根据学科筛选试卷等操作。 + public String getSubjectId() { + return subjectId; + } - public String getSubjectName() { - return subjectName; - } + // 设置学科ID的方法,通过传入一个新的学科ID字符串,来更新当前对象中存储的学科ID属性值。 +// 常用于在试卷学科归属发生变化等业务场景下,对学科ID进行相应的更新,确保试卷与正确的学科相关联。 + public void setSubjectId(String subjectId) { + this.subjectId = subjectId; + } - public void setSubjectName(String subjectName) { - this.subjectName = subjectName; - } + // 获取学科名称的方法,返回当前对象中存储的学科名称属性值。 +// 该属性在数据库表中一般不存在对应字段,主要是为了在业务逻辑处理过程中临时存储学科名称信息,方便在不直接关联查询数据库的情况下进行展示等操作。 + public String getSubjectName() { + return subjectName; + } - public String getPoint() { - return point; - } + // 设置学科名称的方法,传入一个新的学科名称字符串,更新当前对象中存储的学科名称属性值。 +// 常用于在获取学科相关信息后,将学科的具体名称保存到该属性中,以便后续方便地展示学科名称给用户查看等情况。 + public void setSubjectName(String subjectName) { + this.subjectName = subjectName; + } - public void setPoint(String point) { - this.point = point; - } + // 获取试卷要点信息的方法,返回当前对象中存储的试卷要点属性值。 +// 外部代码调用该方法后,可以获取到试卷所涵盖的重点知识点等相关内容,方便用户提前了解试卷考查的关键内容,也有助于进行试卷内容相关的分析和整理。 + public String getPoint() { + return point; + } - public String getMoney() { - return money; - } + // 设置试卷要点信息的方法,通过传入新的要点信息字符串,更新当前对象中存储的要点属性值。 +// 常用于更新试卷重点知识点相关内容的业务场景,比如根据教学大纲变化或者试卷调整对试卷考查要点进行修改后,使用该方法保存新的要点信息。 + public void setPoint(String point) { + this.point = point; + } - public void setMoney(String money) { - this.money = money; - } + // 获取试卷价格信息的方法,返回当前对象中存储的试卷价格属性值。 +// 外部代码调用该方法后,能够得知试卷的售价情况(如果试卷是收费的),在涉及试卷购买等业务场景下会用到这个属性来进行价格相关的计算和展示等操作。 + public String getMoney() { + return money; + } - public String getFree() { - return free; - } + // 设置试卷价格信息的方法,传入一个新的价格信息字符串,更新当前对象中存储的价格属性值。 +// 常用于调整试卷价格的业务场景,比如试卷进行促销活动或者价格调整时,通过该方法来修改试卷的售价信息。 + public void setMoney(String money) { + this.money = money; + } - public void setFree(String free) { - this.free = free; - } + // 获取试卷是否免费相关信息的方法,返回当前对象中存储的与试卷是否免费相关的属性值。 +// 该属性通过@TableField(value = "free")注解与数据库表中的"free"字段进行映射,其具体含义取决于业务需求,可能表示试卷是否免费等情况。 + public String getFree() { + return free; + } -} \ No newline at end of file + // 设置试卷是否免费相关信息的方法,传入一个新的表示免费相关信息的字符串,更新当前对象中存储的该属性值。 +// 常用于修改试卷免费与否状态的业务场景,比如将原本收费的试卷设置为免费,或者反之等情况,通过该方法来更新对应的属性信息。 + public void setFree(String free) { + this.free = free; + } \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/model/QuestionEntity.java b/tamguo-crawler/src/main/java/com/tamguo/model/QuestionEntity.java index 7a21eb4..f805e69 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/model/QuestionEntity.java +++ b/tamguo-crawler/src/main/java/com/tamguo/model/QuestionEntity.java @@ -4,156 +4,195 @@ import java.io.Serializable; import com.baomidou.mybatisplus.annotations.TableName; import com.tamguo.config.dao.SuperEntity; - -/** - * The persistent class for the tiku_question database table. - * - */ -@TableName(value="t_question") +// QuestionEntity类表示题库中的题目相关实体信息,它继承自SuperEntity类,并实现了Serializable接口,用于支持对象的序列化操作 +// 对应数据库中的t_question表(通过@TableName注解指定表名) +@TableName(value = "t_question") public class QuestionEntity extends SuperEntity implements Serializable { private static final long serialVersionUID = 1L; + // 题目解析内容,用于存储对题目的详细分析解释 private String analysis; - + + // 关联的试卷ID,用于标识该题目所属的试卷 private String paperId; + // 题目的答案内容 private String answer; + // 章节ID,用于表明该题目所属的章节 private String chapterId; + // 题目类型,例如选择题、填空题、简答题等不同的类型标识 private String questionType; + // 题目的具体内容,也就是题干部分 private String content; - + + // 学科ID,用于确定该题目所属的学科领域 private String subjectId; + // 课程ID,可用于关联具体的课程,说明该题目在哪个课程范畴内 private String courseId; - + + // 复习要点,存放针对本题目复习时需要重点关注的知识点等信息 private String reviewPoint; - + + // 题目出现的年份,可能用于记录该题目首次出现或者相关统计的年份信息 private String year; - + + // 题目分值,表明该题目在考试等场景中的分数设置 private String score; - + + // 审核状态,用于记录题目当前的审核情况,比如已审核通过、待审核、审核不通过等状态 private String auditStatus; - + + // 来源类型,说明题目是从哪种渠道获取的,例如自编、引用自其他资料等类型 private String sourceType; - + + // 来源网址,如果题目来源于网络等外部资源,此处记录对应的URL地址 private String sourceUrl; + // 默认构造函数,用于创建QuestionEntity类的实例对象 public QuestionEntity() { } + // 获取题目解析内容的方法 public String getAnalysis() { return this.analysis; } + // 设置题目解析内容的方法 public void setAnalysis(String analysis) { this.analysis = analysis; } + // 获取题目的答案内容的方法 public String getAnswer() { return this.answer; } + // 设置题目的答案内容的方法 public void setAnswer(String answer) { this.answer = answer; } + // 获取章节ID的方法 public String getChapterId() { return this.chapterId; } + // 设置章节ID的方法 public void setChapterId(String chapterId) { this.chapterId = chapterId; } - + + // 获取题目类型的方法 public String getQuestionType() { return this.questionType; } + // 设置题目类型的方法 public void setQuestionType(String questionType) { this.questionType = questionType; } + // 获取题目的具体内容(题干)的方法 public String getContent() { return content; } + // 设置题目的具体内容(题干)的方法 public void setContent(String content) { this.content = content; } + // 获取复习要点的方法 public String getReviewPoint() { return reviewPoint; } + // 设置复习要点的方法 public void setReviewPoint(String reviewPoint) { this.reviewPoint = reviewPoint; } + // 获取题目出现年份的方法 public String getYear() { return year; } + // 设置题目出现年份的方法 public void setYear(String year) { this.year = year; } + // 获取题目分值的方法 public String getScore() { return score; } + // 设置题目分值的方法 public void setScore(String score) { this.score = score; } + // 获取关联试卷ID的方法 public String getPaperId() { return paperId; } + // 设置关联试卷ID的方法 public void setPaperId(String paperId) { this.paperId = paperId; } + // 获取课程ID的方法 public String getCourseId() { return courseId; } + // 设置课程ID的方法 public void setCourseId(String courseId) { this.courseId = courseId; } + // 获取学科ID的方法 public String getSubjectId() { return subjectId; } + // 设置学科ID的方法 public void setSubjectId(String subjectId) { this.subjectId = subjectId; } + // 获取审核状态的方法 public String getAuditStatus() { return auditStatus; } + // 设置审核状态的方法 public void setAuditStatus(String auditStatus) { this.auditStatus = auditStatus; } + // 获取来源类型的方法 public String getSourceType() { return sourceType; } + // 设置来源类型的方法 public void setSourceType(String sourceType) { this.sourceType = sourceType; } + // 获取来源网址的方法 public String getSourceUrl() { return sourceUrl; } + // 设置来源网址的方法 public void setSourceUrl(String sourceUrl) { this.sourceUrl = sourceUrl; } - } \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/model/QuestionOptionsEntity.java b/tamguo-crawler/src/main/java/com/tamguo/model/QuestionOptionsEntity.java index b9e26cd..1383653 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/model/QuestionOptionsEntity.java +++ b/tamguo-crawler/src/main/java/com/tamguo/model/QuestionOptionsEntity.java @@ -3,53 +3,73 @@ package com.tamguo.model; import com.baomidou.mybatisplus.annotations.TableId; import com.baomidou.mybatisplus.annotations.TableName; -@TableName(value="t_question_options") +// QuestionOptionsEntity类用于表示题目选项相关的实体信息,对应数据库中的t_question_options表(通过@TableName注解指定表名) +@TableName(value = "t_question_options") public class QuestionOptionsEntity { + // 使用@TableId注解标识该属性为主键,在数据库表中对应的列是唯一标识每条记录的字段,这里表示选项记录的唯一ID @TableId private Long id; + + // 关联的题目ID,用于表明该选项所属的具体题目,通过这个字段可以与题目主体信息建立关联 private String questionId; + + // 选项的具体内容,例如对于选择题来说就是A、B、C、D等各个选项对应的具体文字表述 private String option; + + // 选项编号,通常用于表示选项的序号,比如选择题中的选项序号(A对应"1"、B对应"2"等情况) private String optionNo; + + // 选项的顺序,用于确定多个选项的排列顺序,比如在界面展示或者存储顺序等方面起到作用 private int order; + // 获取关联的题目ID的方法 public String getQuestionId() { return questionId; } + // 设置关联的题目ID的方法 public void setQuestionId(String questionId) { this.questionId = questionId; } + // 获取选项具体内容的方法 public String getOption() { return option; } + // 设置选项具体内容的方法 public void setOption(String option) { this.option = option; } + // 获取选项编号的方法 public String getOptionNo() { return optionNo; } + // 设置选项编号的方法 public void setOptionNo(String optionNo) { this.optionNo = optionNo; } + // 获取选项顺序的方法 public int getOrder() { return order; } + // 设置选项顺序的方法 public void setOrder(int order) { this.order = order; } + // 获取选项记录唯一ID(主键)的方法 public Long getId() { return id; } + // 设置选项记录唯一ID(主键)的方法 public void setId(Long id) { this.id = id; } -} +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/model/SchoolEntity.java b/tamguo-crawler/src/main/java/com/tamguo/model/SchoolEntity.java index f155fb1..33c4e41 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/model/SchoolEntity.java +++ b/tamguo-crawler/src/main/java/com/tamguo/model/SchoolEntity.java @@ -4,50 +4,58 @@ import java.io.Serializable; import com.baomidou.mybatisplus.annotations.TableName; import com.tamguo.config.dao.SuperEntity; - -/** - * The persistent class for the tiku_chapter database table. - * - */ -@TableName(value="tiku_school") +// SchoolEntity类用于表示学校相关的实体信息,它继承自SuperEntity类,并实现了Serializable接口,以便支持对象的序列化操作 +// 通过@TableName注解将其与数据库中的tiku_school表进行关联(这里表名指定为tiku_school) +@TableName(value = "tiku_school") public class SchoolEntity extends SuperEntity implements Serializable { private static final long serialVersionUID = 1L; + // 区域ID,用于标识学校所在的特定区域,可能关联到某个地区的行政区划代码等,以此来区分不同地区的学校 private String areaId; - + + // 学校名称,用于存储学校的具体名称,方便在系统中对不同学校进行识别和展示等操作 private String name; - + + // 学校图片相关信息,可能是存储学校校徽、校园风景图等图片资源的路径或者其他标识信息,具体取决于业务需求 private String image; + // 默认构造函数,用于创建SchoolEntity类的实例对象 public SchoolEntity() { } + // 获取学校名称的方法 public String getName() { return name; } + // 设置学校名称的方法,通过该方法可以更新SchoolEntity实例对象中的学校名称属性值 public void setName(String name) { this.name = name; } + // 获取学校图片相关信息的方法 public String getImage() { return image; } + // 设置学校图片相关信息的方法,可用于更新SchoolEntity实例对象中图片属性的相关内容 public void setImage(String image) { this.image = image; } + // 获取序列化版本号的静态方法,不过通常serialVersionUID是一个常量,不需要通过方法去获取,这里只是按照代码已有结构添加注释 + // 实际应用中一般很少这样去写获取serialVersionUID的方法,更多是直接使用这个静态常量来判断序列化兼容性等情况 public static long getSerialversionuid() { return serialVersionUID; } + // 获取区域ID的方法,用于获取SchoolEntity实例对象中标识学校所在区域的属性值 public String getAreaId() { return areaId; } + // 设置区域ID的方法,可用于更新SchoolEntity实例对象中区域ID这个属性的内容,来改变学校所属区域的标识 public void setAreaId(String areaId) { this.areaId = areaId; } - } \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/model/SubjectEntity.java b/tamguo-crawler/src/main/java/com/tamguo/model/SubjectEntity.java index 7ac8592..c206d68 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/model/SubjectEntity.java +++ b/tamguo-crawler/src/main/java/com/tamguo/model/SubjectEntity.java @@ -4,19 +4,27 @@ import java.io.Serializable; import com.baomidou.mybatisplus.annotations.TableName; import com.tamguo.config.dao.SuperEntity; -@TableName(value="tiku_subject") +// SubjectEntity类用于表示学科相关的实体信息,它继承自SuperEntity类(具体SuperEntity类中可能包含一些通用的实体相关逻辑等,取决于其定义), +// 并且实现了Serializable接口,使得该类的对象可以进行序列化操作,方便在如网络传输、持久化存储等场景下使用。 +// 通过@TableName注解将该类与数据库中的tiku_subject表进行关联,意味着这个类在数据库操作层面会对应这个表来进行数据的增删改查等操作。 +@TableName(value = "tiku_subject") public class SubjectEntity extends SuperEntity implements Serializable { private static final long serialVersionUID = 1L; + // name属性用于存储学科的具体名称,通过这个属性可以明确表示不同的学科,例如“数学”“语文”“英语”等, + // 在系统中可用于展示学科信息、进行学科相关的筛选、查询等业务逻辑处理。 private String name; + // getName方法用于获取SubjectEntity实例对象中存储的学科名称属性值, + // 外部代码可以调用这个方法来获取当前SubjectEntity对象所代表的学科的具体名称,便于后续进行展示或者其他与学科名称相关的操作。 public String getName() { return name; } + // setName方法用于设置SubjectEntity实例对象的学科名称属性值, + // 通过传入一个新的学科名称字符串参数,可以更新当前SubjectEntity对象所代表的学科的名称,常用于修改学科名称等业务场景。 public void setName(String name) { this.name = name; } - -} +} \ No newline at end of file diff --git a/tamguo-crawler/src/main/java/com/tamguo/model/enums/QuestionType.java b/tamguo-crawler/src/main/java/com/tamguo/model/enums/QuestionType.java index 79804ef..a3ff647 100644 --- a/tamguo-crawler/src/main/java/com/tamguo/model/enums/QuestionType.java +++ b/tamguo-crawler/src/main/java/com/tamguo/model/enums/QuestionType.java @@ -1,58 +1,90 @@ package com.tamguo.model.enums; import java.io.Serializable; + /** - * 试题类型、、题目类型(1.单选题;2.多选题; 3.解答题) - * - * @author tamguo + * 试题类型、题目类型枚举类,用于定义不同类型的试题,当前包含了单选题、多选题、填空题、判断题、问答题这几种常见的题目类型, + * 通过枚举值的方式来统一管理和表示这些类型,方便在整个项目中进行类型判断、数据传递以及相关业务逻辑处理等操作,避免使用字符串等方式带来的不一致性和错误风险。 * + * @author tamguo */ public enum QuestionType { - + + // 定义一个名为"DANXUANTI"的枚举值,表示单选题类型,构造函数传入的第一个参数"1"可能用于在数据库存储或者其他需要用特定值表示类型的场景,第二个参数"单选题"用于更直观地描述该类型的含义,方便阅读和理解。 DANXUANTI("1", "单选题"), + // 类似地,"DUOXUANTI"枚举值表示多选题类型,对应传入相应的表示值和描述信息。 DUOXUANTI("2", "多选题"), + // "TIANKONGTI"枚举值表示填空题类型。 TIANKONGTI("3", "填空题"), + // "PANDUANTI"枚举值表示判断题类型。 PANDUANTI("4", "判断题"), + // "WENDATI"枚举值表示问答题类型。 WENDATI("5", "问答题"); - private String value; - private String desc; - - QuestionType(final String value, final String desc) { - this.value = value; - this.desc = desc; - } - - public static QuestionType getQuestionType(String value) { - if("单选题".equals(value)) { - return DANXUANTI; - }else if("多选题".equals(value)) { - return DUOXUANTI; - }else if("填空题".equals(value)) { - return TIANKONGTI; - }else if("判断题".equals(value)) { - return PANDUANTI; - }else if("问答题".equals(value)) { - return WENDATI; - }else if("选择题".equals(value)) { - return DANXUANTI; - }else if("简答题(综合题)".equals(value)) { - return WENDATI; - } - return WENDATI; - } - - public Serializable getValue() { - return this.value; - } - - public String getDesc(){ - return this.desc; - } - - @Override - public String toString() { - return this.value; - } - -} + // 用于存储该试题类型对应的具体值,例如在数据库存储或者与外部系统交互等场景下使用的特定表示值,类型为字符串。 + private String value; + // 用于存储对该试题类型的文字描述,方便在界面展示、日志输出等场景下直观地表示该类型的含义,类型为字符串。 + private String desc; + + // 枚举类的构造函数,用于初始化每个枚举值对应的value和desc属性,在定义枚举值时会调用该构造函数传入相应的参数进行初始化操作。 + QuestionType(final String value, final String desc) { + this.value = value; + this.desc = desc; + } + + /** + * 根据传入的试题类型描述字符串,返回对应的QuestionType枚举值。 + * 该方法用于在已知试题类型的文字描述情况下,方便地获取对应的枚举值,常用于从用户输入、外部数据解析等场景下将描述信息转换为程序中统一使用的枚举类型,便于后续的业务逻辑处理。 + * 如果传入的字符串与已知的试题类型描述匹配,则返回对应的枚举值;若都不匹配,则默认返回"WENDATI"(这里可以根据实际业务需求进一步完善逻辑,比如抛出异常等更严谨的处理方式)。 + * + * @param value 表示试题类型的字符串描述,例如"单选题"、"多选题"等,具体取值取决于业务中对试题类型的定义和使用场景。 + * @return 返回对应的QuestionType枚举值,如果传入值不匹配已知类型描述,则返回默认的"WENDATI"(问答题类型)。 + */ + public static QuestionType getQuestionType(String value) { + if ("单选题".equals(value)) { + return DANXUANTI; + } else if ("多选题".equals(value)) { + return DUOXUANTI; + } else if ("填空题".equals(value)) { + return TIANKONGTI; + } else if ("判断题".equals(value)) { + return PANDUANTI; + } else if ("问答题".equals(value)) { + return WENDATI; + } else if ("选择题".equals(value)) { + return DANXUANTI; + } else if ("简答题(综合题)".equals(value)) { + return WENDATI; + } + return WENDATI; + } + + /** + * 获取该试题类型对应的具体值,该值通常用于在数据库存储、与外部系统交互等场景下作为类型的标识,返回值实现了Serializable接口,方便在一些需要序列化的场景下使用,比如存储到缓存或者进行网络传输等。 + * + * @return 返回该试题类型对应的具体值,类型为实现了Serializable接口的对象(实际为字符串类型)。 + */ + public Serializable getValue() { + return this.value; + } + + /** + * 获取该试题类型的文字描述信息,用于在界面展示、日志输出等场景下直观地表示该试题类型的含义,方便开发人员和用户理解该类型所代表的具体题目类型。 + * + * @return 返回该试题类型的文字描述字符串,例如"单选题"、"多选题"等。 + */ + public String getDesc() { + return this.desc; + } + + /** + * 重写toString方法,返回该试题类型对应的具体值,这样在一些需要将枚举值转换为字符串表示的场景下(比如日志输出、拼接字符串等),可以直接使用该方法获取对应的表示值,方便使用且符合通常的使用习惯。 + * + * @return 返回该试题类型对应的具体值,例如"1"(对于"DANXUANTI"枚举值)等。 + */ + @Override + public String toString() { + return this.value; + } + +} \ No newline at end of file diff --git a/tamguo-crawler/src/test/java/com/tamguo/BookCrawler.java b/tamguo-crawler/src/test/java/com/tamguo/BookCrawler.java index 24634d6..60cc15d 100644 --- a/tamguo-crawler/src/test/java/com/tamguo/BookCrawler.java +++ b/tamguo-crawler/src/test/java/com/tamguo/BookCrawler.java @@ -1,23 +1,44 @@ package com.tamguo; import org.junit.Test; +// 引入JUnit框架中的@Test注解,用于标记一个方法是测试方法,JUnit在运行测试时会执行被该注解标记的方法, +// 并根据方法内的逻辑判断测试是否通过。 + import org.junit.runner.RunWith; +// 用于指定JUnit运行测试时使用的运行器(Runner),不同的运行器可以提供不同的测试执行环境和功能。 + import org.springframework.beans.factory.annotation.Autowired; +// Spring框架的注解,用于自动装配(依赖注入),Spring会根据类型等规则自动查找并注入相应的Bean实例到标注该注解的变量中。 + import org.springframework.boot.test.context.SpringBootTest; +// 这个注解用于标记一个测试类,它会启动一个完整的Spring Boot应用上下文,以便在测试中可以加载整个应用的配置, +// 并且能够像在实际运行环境中一样使用各种Spring管理的组件(如自动注入的Bean等),方便进行集成测试等操作。 + import org.springframework.test.context.junit4.SpringRunner; +// Spring提供的用于JUnit 4的运行器,结合@RunWith(SpringRunner.class)使用,可以让JUnit测试运行在Spring的测试环境下, +// 从而支持Spring相关的功能,如依赖注入、事务管理等在测试中的应用。 import com.tamguo.service.IBookService; +// 引入自定义的服务层接口IBookService,从名称推测该接口可能定义了与书籍相关的业务操作方法, +// 在本测试类中会通过依赖注入获取其实现类的实例来调用具体的业务方法进行测试。 +// BookCrawler类是一个用于测试的类,它使用了Spring Boot和JUnit相关的注解来构建测试环境, +// 目的是对与书籍相关的业务逻辑(通过IBookService实现)进行测试。 @RunWith(SpringRunner.class) @SpringBootTest public class BookCrawler { + // 使用@Autowired注解自动注入一个实现了IBookService接口的Bean实例到bookService变量中, + // 这样在测试方法中就可以直接使用这个服务层实例来调用相应的业务方法了。 @Autowired IBookService bookService; + // 用@Test注解标记的测试方法,方法名为crawlerBook,用于测试书籍爬取相关的业务逻辑。 + // 该方法声明可能抛出Exception异常,意味着在执行过程中如果出现了未处理的异常情况,JUnit会相应地记录测试失败并输出异常信息。 @Test public void crawlerBook() throws Exception { + // 调用bookService实例的crawlerBook方法,该方法具体实现应该位于IBookService接口的实现类中, + // 推测其功能是执行书籍爬取的操作,比如从网络上获取书籍信息等,这里通过调用该方法来验证相关业务逻辑是否正确执行。 this.bookService.crawlerBook(); } - -} +} \ No newline at end of file diff --git a/tamguo-crawler/src/test/java/com/tamguo/ChapterCrawler.java b/tamguo-crawler/src/test/java/com/tamguo/ChapterCrawler.java index ff1185f..c7ae9e9 100644 --- a/tamguo-crawler/src/test/java/com/tamguo/ChapterCrawler.java +++ b/tamguo-crawler/src/test/java/com/tamguo/ChapterCrawler.java @@ -1,23 +1,48 @@ package com.tamguo; import org.junit.Test; +// 引入JUnit框架中的@Test注解,它的作用是标记当前类中的某个方法为测试方法。 +// 在运行测试时,JUnit会识别并执行被该注解标记的方法,然后依据方法内部的逻辑以及最终执行结果等来判断测试是否通过。 + import org.junit.runner.RunWith; +// 此注解用于指定JUnit运行测试时所采用的运行器(Runner)。 +// 不同的运行器能为测试提供不同的执行环境与功能支持,以此满足多样化的测试需求。 + import org.springframework.beans.factory.annotation.Autowired; +// Spring框架提供的注解,主要功能是实现自动装配(依赖注入)。 +// Spring容器会按照一定的规则(比如根据类型等)自动查找匹配的Bean实例,并将其注入到标注了该注解的变量中,方便在代码中直接使用对应的组件。 + import org.springframework.boot.test.context.SpringBootTest; +// 该注解用于标记一个测试类,它会启动完整的Spring Boot应用上下文。 +// 这意味着在测试过程中,能够加载整个应用程序所配置的各种组件、配置信息等,如同应用在实际运行环境中一样,方便进行集成测试等相关操作。 + import org.springframework.test.context.junit4.SpringRunner; +// Spring为JUnit 4提供的运行器类,结合@RunWith(SpringRunner.class)这种使用方式, +// 可以让JUnit测试运行在Spring所构建的测试环境之下,进而使Spring相关的诸多特性(如依赖注入、事务管理等)能够在测试过程中得以运用。 import com.tamguo.service.IChapterService; +// 引入自定义的服务层接口IChapterService,从接口名称推测,其应该定义了与章节相关的一系列业务操作方法, +// 在本测试类中,将会通过依赖注入获取该接口实现类的实例,进而调用相应的业务方法来进行测试操作。 +// ChapterCrawler类是一个专门用于测试的类,它借助Spring Boot和JUnit相关的注解搭建起测试环境, +// 旨在对和章节相关的业务逻辑(通过IChapterService实现)进行测试验证。 @RunWith(SpringRunner.class) @SpringBootTest public class ChapterCrawler { - @Autowired - IChapterService iChapterService; - + // 使用@Autowired注解,让Spring容器自动将实现了IChapterService接口的Bean实例注入到iChapterService变量中。 + // 如此一来,在后续的测试方法里就能直接利用这个注入的服务实例去调用相应的章节相关业务方法了。 + @Autowired + IChapterService iChapterService; + + // 使用@Test注解标记的测试方法,名为crawlerChapter,其主要作用是测试章节爬取相关的业务逻辑是否正确。 + // 该方法声明了可能抛出Exception异常,意味着如果在方法执行期间出现了未被处理的异常情况,JUnit会将此次测试标记为失败,并输出相应的异常信息。 @Test public void crawlerChapter() throws Exception { - iChapterService.crawlerChapter(); + // 调用iChapterService实例的crawlerChapter方法,该方法的具体实现应该位于IChapterService接口的实现类当中。 + // 从方法名推测,其功能大概率是执行章节爬取的操作,比如从网络或者其他数据源获取章节相关的信息等, + // 通过在这里调用该方法来检验与之相关的业务逻辑能否按照预期正常执行。 + iChapterService.crawlerChapter(); } - -} + +} \ No newline at end of file diff --git a/tamguo-crawler/src/test/java/com/tamguo/CrawlerBookCrawler.java b/tamguo-crawler/src/test/java/com/tamguo/CrawlerBookCrawler.java index 5950539..501930a 100644 --- a/tamguo-crawler/src/test/java/com/tamguo/CrawlerBookCrawler.java +++ b/tamguo-crawler/src/test/java/com/tamguo/CrawlerBookCrawler.java @@ -1,22 +1,48 @@ package com.tamguo; import com.tamguo.service.ICrawlerBookService; +// 引入自定义的服务层接口ICrawlerBookService,从接口名称可以推测,它应该定义了与书籍爬取相关的业务操作方法, +// 这些方法会在具体的实现类中实现相应的逻辑,而本测试类会通过依赖注入获取其实现类的实例来进行测试操作。 + import org.junit.Test; +// 该注解来自JUnit测试框架,用于标识下面的方法是一个测试方法。在运行测试时,JUnit会执行被此注解标记的方法, +// 并根据方法内的逻辑执行情况以及可能出现的异常等来判断该测试是否通过。 + import org.junit.runner.RunWith; +// 此注解用于指定JUnit测试运行时所采用的运行器(Runner)。不同的运行器能提供不同的测试执行环境和功能特性, +// 这里通过指定运行器来确保测试能在符合要求的Spring相关环境下进行。 + import org.springframework.beans.factory.annotation.Autowired; +// 这是Spring框架提供的用于依赖注入(自动装配)的注解。Spring容器会依据类型等规则自动查找并将合适的Bean实例注入到标注该注解的变量中, +// 使得代码可以方便地获取和使用其他组件,在这里就是将实现了ICrawlerBookService接口的实例注入进来。 + import org.springframework.boot.test.context.SpringBootTest; +// 该注解用于标记这是一个Spring Boot应用的测试类,它会启动完整的Spring Boot应用上下文,加载整个应用的配置信息、组件等, +// 就如同应用在实际运行环境中一样,从而便于进行集成测试等操作,让测试能够使用到所有已配置好的Spring相关资源。 + import org.springframework.test.context.junit4.SpringRunner; +// Spring为JUnit 4提供的运行器,结合@RunWith(SpringRunner.class)的使用,能让JUnit测试运行在Spring构建的测试环境下, +// 进而可以利用Spring的各种特性,比如依赖注入、事务管理等在测试过程中发挥作用。 +// CrawlerBookCrawler类是一个基于Spring Boot和JUnit的测试类,其主要目的是对与书籍爬取相关的业务逻辑进行测试, +// 通过使用相关注解搭建起合适的测试环境,并借助依赖注入获取对应的服务实例来执行具体的测试操作。 @RunWith(SpringRunner.class) @SpringBootTest public class CrawlerBookCrawler { - @Autowired + // 使用@Autowired注解,让Spring容器自动查找并注入一个实现了ICrawlerBookService接口的Bean实例到crawlerBookService变量中。 + // 这样在后续的测试方法里,就能直接利用这个服务实例去调用与书籍爬取相关的业务方法了。 + @Autowired ICrawlerBookService crawlerBookService; - + + // 使用@Test注解标记的测试方法,名为crawlerBook,其功能是测试书籍爬取相关的具体业务逻辑。 + // 该方法声明抛出Exception异常,表示如果在方法执行过程中出现了未处理的异常情况,JUnit会将此次测试判定为失败,并输出相应的异常信息。 @Test public void crawlerBook() throws Exception { + // 调用crawlerBookService实例的crawlerBook方法,这个方法的具体实现应该在ICrawlerBookService接口的实现类中定义, + // 从方法名推测,其主要作用是执行实际的书籍爬取操作,例如从网络上抓取书籍的相关信息等, + // 通过调用该方法来验证相关的书籍爬取业务逻辑能否正确执行,达到预期的效果。 crawlerBookService.crawlerBook(); } - -} + +} \ No newline at end of file diff --git a/tamguo-crawler/src/test/java/com/tamguo/ModifyChpaterQuestionNum.java b/tamguo-crawler/src/test/java/com/tamguo/ModifyChpaterQuestionNum.java index f7d19ca..86887dc 100644 --- a/tamguo-crawler/src/test/java/com/tamguo/ModifyChpaterQuestionNum.java +++ b/tamguo-crawler/src/test/java/com/tamguo/ModifyChpaterQuestionNum.java @@ -1,16 +1,33 @@ package com.tamguo; import org.junit.Test; +// 引入JUnit框架中的@Test注解,用于标记下方的方法为测试方法。JUnit在执行测试任务时,会识别并执行被该注解标记的方法, +// 随后依据方法内部的代码逻辑执行情况(例如是否抛出异常、返回值是否符合预期等)来判定该测试是否通过。 + import org.junit.runner.RunWith; +// 此注解用于指定JUnit运行测试时所采用的运行器(Runner)。不同的运行器能为测试提供不同的执行环境与功能支持, +// 这里配置的运行器可使测试运行在符合Spring框架要求的特定环境中,方便与Spring相关功能进行集成测试。 + import org.springframework.beans.factory.annotation.Autowired; +// Spring框架提供的这个注解用于实现自动装配(依赖注入)功能。Spring容器会按照一定规则(如根据类型、名称等)自动查找并注入匹配的Bean实例, +// 到标注了该注解的变量中,从而让代码能够方便地获取和使用其他Spring管理的组件,在此处就是将实现IChapterService接口的Bean注入进来。 + import org.springframework.boot.test.context.SpringBootTest; +// 该注解用于标记当前类是一个针对Spring Boot应用的测试类。它会启动完整的Spring Boot应用上下文,加载整个应用配置的各类资源, +// 模拟应用在实际运行时的环境,方便在测试中使用各种已配置好的Spring组件进行集成测试、功能验证等操作。 + import org.springframework.test.context.junit4.SpringRunner; +// 这是Spring为JUnit 4提供的运行器类,当与@RunWith(SpringRunner.class)配合使用时,能让JUnit测试运行在Spring构建的测试环境之下, +// 进而可以充分利用Spring框架的诸多特性(如依赖注入、事务管理等)来辅助完成测试工作。 import com.tamguo.service.IChapterService; +// 引入自定义的服务层接口IChapterService,从接口名称推测,其定义了与章节相关的一系列业务操作方法, +// 在本测试类中,会通过依赖注入获取该接口的实现类实例,然后调用相应的业务方法来验证相关业务逻辑的正确性。 /** - * Num - 修改章节题目数量 - * + * ModifyChpaterQuestionNum类是一个用于测试的类,从类名及相关注释来看,其主要功能与修改章节题目数量相关。 + * 它借助Spring Boot和JUnit相关的注解搭建起合适的测试环境,以对修改章节题目数量的业务逻辑进行测试验证。 + * * @author tamguo * */ @@ -18,12 +35,19 @@ import com.tamguo.service.IChapterService; @SpringBootTest public class ModifyChpaterQuestionNum { - @Autowired - IChapterService iChapterService; - + // 使用@Autowired注解,让Spring容器自动将实现了IChapterService接口的Bean实例注入到iChapterService变量中。 + // 如此一来,在后续的测试方法里就能直接使用这个服务实例去调用和章节相关的业务方法,比如此处涉及的修改题目数量的方法。 + @Autowired + IChapterService iChapterService; + + // 使用@Test注解标记的测试方法,名为crawlerSubject,这里方法名可能不太准确(从功能上推测或许叫modifyQuestionNum更合适,不过要结合具体业务情况)。 + // 该方法主要用于测试修改章节题目数量相关的业务逻辑,声明抛出Exception异常,表示如果在方法执行期间出现了未处理的异常情况, + // JUnit会将此次测试判定为失败,并输出相应的异常信息,便于开发人员排查问题。 @Test public void crawlerSubject() throws Exception { - iChapterService.modifyQuestionNum(); + // 调用iChapterService实例的modifyQuestionNum方法,该方法的具体实现应该位于IChapterService接口的实现类中。 + // 从方法名可以推断其功能是执行修改章节题目数量的实际操作,通过在这里调用该方法来检验与之相关的业务逻辑能否按照预期正常执行。 + iChapterService.modifyQuestionNum(); } - -} + +} \ No newline at end of file diff --git a/tamguo-crawler/src/test/java/com/tamguo/ModifyQuestionImage.java b/tamguo-crawler/src/test/java/com/tamguo/ModifyQuestionImage.java index 3f2a9e9..a0b64e2 100644 --- a/tamguo-crawler/src/test/java/com/tamguo/ModifyQuestionImage.java +++ b/tamguo-crawler/src/test/java/com/tamguo/ModifyQuestionImage.java @@ -2,54 +2,104 @@ package com.tamguo; import com.baomidou.mybatisplus.mapper.Condition; import com.baomidou.mybatisplus.plugins.Page; +// 引入MyBatis-Plus框架中的相关类,Page类用于进行分页相关操作,比如设置每页数据量、获取当前页码等,方便实现数据的分页查询与处理; +// Condition类用于构建查询条件,例如可以指定排序规则、筛选条件等,在这里用于构建查询数据库的相关条件语句。 + import com.tamguo.model.QuestionEntity; +// 引入自定义的QuestionEntity类,它应该是对应数据库中题目相关表的实体类,包含了题目各个属性的定义,如题目内容、答案、解析等,用于在代码中承载和操作题目相关的数据。 + import com.tamguo.service.IQuestionService; +// 引入自定义的服务层接口IQuestionService,从接口名称推测,它定义了与题目相关的一系列业务操作方法,例如查询题目、更新题目等, +// 在本类中会通过依赖注入获取其实现类的实例来调用这些业务方法进行具体的数据处理操作。 + import org.junit.Test; +// 引入JUnit框架中的@Test注解,用于标记下面的方法为测试方法,JUnit在运行测试时会执行被该注解标记的方法, +// 并根据方法内的逻辑以及是否抛出异常等情况来判断测试是否通过。 + import org.junit.runner.RunWith; +// 用于指定JUnit运行测试时所采用的运行器(Runner),通过配置合适的运行器可以让测试运行在特定的环境中,这里是为了让测试运行在Spring相关的环境下。 + import org.springframework.beans.factory.annotation.Autowired; +// Spring框架提供的注解,用于自动装配(依赖注入),Spring容器会根据类型等规则自动查找并注入相应的Bean实例到标注该注解的变量中, +// 方便在代码中直接使用对应的服务层组件,在这里就是将实现IQuestionService接口的Bean实例注入进来。 + import org.springframework.boot.test.context.SpringBootTest; +// 该注解用于标记当前类是一个针对Spring Boot应用的测试类,它会启动完整的Spring Boot应用上下文,加载整个应用的配置信息和组件, +// 模拟应用实际运行的环境,便于进行集成测试等操作,使得测试能够使用到应用中已配置好的各种资源。 + import org.springframework.test.context.junit4.SpringRunner; +// Spring为JUnit 4提供的运行器,结合@RunWith(SpringRunner.class)使用,可以让JUnit测试运行在Spring构建的测试环境下, +// 进而支持Spring相关的功能,如依赖注入、事务管理等在测试中的应用。 import java.util.Arrays; +// 引入Java标准库中的Arrays类,用于操作数组,在这里主要是配合Condition类来指定排序字段的列表,方便构建按指定字段排序的查询条件。 /** - * Test - 修改用户图片 - * + * ModifyQuestionImage类是一个用于测试的类,从类名及相关注释来看,其原本意图可能是与修改题目相关的图片有关(不过从代码实际逻辑看,主要是替换文件路径相关字符串)。 + * 它借助Spring Boot和JUnit相关的注解搭建起测试环境,通过调用相关服务层方法实现对题目数据的分页查询、处理以及更新操作,目的是对题目中的某些字符串内容(文件路径相关)进行替换修改。 + * * @author tamguo * */ @RunWith(SpringRunner.class) @SpringBootTest public class ModifyQuestionImage { - + + // 使用@Autowired注解,让Spring容器自动将实现了IQuestionService接口的Bean实例注入到iQuestionService变量中。 + // 这样在后续的测试方法里就能直接利用这个服务实例去调用与题目相关的业务方法,比如查询题目、更新题目等操作。 @Autowired private IQuestionService iQuestionService; + // 使用@Test注解标记的测试方法,名为modify,该方法实现了对题目数据的批量处理逻辑,主要是对题目中的文件路径相关字符串进行替换操作,并更新到数据库中。 + // @SuppressWarnings("unchecked")注解用于抑制编译器的unchecked警告,这里可能是因为代码中存在一些泛型相关的操作,编译器会产生警告,通过此注解告知编译器忽略这些警告。 @SuppressWarnings("unchecked") @Test public void modify() { - Integer current = 1 ; + // 当前页码,初始化为1,表示从第一页数据开始处理,用于在分页查询中指定要获取的页码。 + Integer current = 1; + // 每页数据量,设置为100,表示每次分页查询时获取100条题目数据进行处理,可根据实际数据量和性能等因素进行调整。 Integer size = 100; - - while(true) { - Page page = new Page(current , size); - Page entitys = iQuestionService.selectPage(page , Condition.create().orderAsc(Arrays.asList("id"))); - if(entitys.getCurrent() > 759) { + + // 使用while循环来实现对所有题目数据的分页遍历处理,只要满足循环条件就会一直循环下去,直到处理完所有符合条件的数据为止。 + while (true) { + // 创建一个Page对象,用于分页查询,传入当前页码current和每页数据量size作为参数, + // 这样就可以通过后续的服务层方法根据这个分页设置去数据库中获取相应的数据。 + Page page = new Page(current, size); + // 调用iQuestionService的selectPage方法进行分页查询,传入构建好的分页对象page以及查询条件对象。 + // 查询条件通过Condition.create().orderAsc(Arrays.asList("id"))构建,意思是按照题目实体类中的"id"字段进行升序排序来获取题目数据, + // 并将查询结果封装到另一个Page对象entitys中,该对象包含了当前页的题目数据以及分页相关的其他信息(如总页数、总记录数等)。 + Page entitys = iQuestionService.selectPage(page, Condition.create().orderAsc(Arrays.asList("id"))); + // 判断当前页码是否大于759,如果大于则表示已经处理完了计划处理的数据量(这里759可能是根据总数据量和每页数量估算出来的一个边界值,具体要结合实际情况), + // 如果满足这个条件,就跳出while循环,结束数据处理流程。 + if (entitys.getCurrent() > 759) { break; } - // 处理数据 - for(int i=0 ; i() { - - @Override - public void parse(Document html, Element pageVoElement, PaperVo paperVo) { - // 解析封装 PageVo 对象 - String pageUrl = html.baseUri(); - if(pageUrl.contains("https://tiku.baidu.com/tikupc/paperdetail")) { - System.out.println(paperVo.getPaperName()); - - PaperEntity paper = new PaperEntity(); - paper.setSubjectId(SUBJECT_ID); - paper.setCourseId(COURSE_ID); - paper.setSchoolId(""); - paper.setAreaId(AREA_ID); - paper.setCreaterId("system"); - paper.setName(paperVo.getPaperName()); - paper.setYear(YEAR); - paper.setFree("0"); - paper.setSeoTitle(paperVo.getPaperName()); - paper.setSeoKeywords(""); - paper.setSeoDescription(""); - paper.setType(PAPER_TYPE); - JSONArray entitys = new JSONArray(); - // 处理类型问题 - for(int i=0 ; i() { + + @Override + public void parse(Document html, Element pageVoElement, PaperVo paperVo) { + // 获取当前爬取页面的URL地址,html.baseUri()方法通过解析HTML文档获取其基础URL,用于后续判断页面类型等操作。 + String pageUrl = html.baseUri(); + // 判断页面URL是否包含“https://tiku.baidu.com/tikupc/paperdetail”,如果包含则表示当前页面是试卷详情页, + // 需要进行试卷详细信息的提取和处理,并将数据存储到数据库中,下面的代码块就是针对试卷详情页的处理逻辑。 + if (pageUrl.contains("https://tiku.baidu.com/tikupc/paperdetail")) { + // 在控制台打印试卷名称,这里主要是为了方便在爬取过程中查看当前正在处理的试卷信息,便于调试和监控爬取进度, + // paperVo.getPaperName()方法用于从PaperVo对象中获取试卷名称属性值,PaperVo对象应该是在页面解析前期已经封装好了部分试卷相关数据。 + System.out.println(paperVo.getPaperName()); + + // 创建一个PaperEntity对象,用于封装要插入到数据库中的试卷基本信息,后续会将这个对象中的数据持久化到数据库对应的试卷表中。 + PaperEntity paper = new PaperEntity(); + // 设置试卷所属的学科ID,将之前定义的固定学科ID(“gaokao”)赋值给PaperEntity对象的相应属性,用于标识试卷所属学科。 + paper.setSubjectId(SUBJECT_ID); + // 设置试卷所属的科目ID,把“shengwu”赋值给相应属性,明确试卷属于生物科目,方便后续按科目对试卷进行分类、查询等操作。 + paper.setCourseId(COURSE_ID); + // 设置试卷所属学校ID为空字符串,可能表示当前试卷与特定学校无关联或者暂时未获取到学校相关信息,具体需结合业务逻辑确定。 + paper.setSchoolId(""); + // 设置试卷所属的区域ID,使用之前定义的“360000”区域ID,用于标识试卷来自江西地区,便于后续按区域管理试卷数据。 + paper.setAreaId(AREA_ID); + // 设置试卷创建者ID为“system”,这里可能表示试卷是由系统自动爬取添加的,并非用户手动创建,用于记录试卷的创建来源信息。 + paper.setCreaterId("system"); + // 设置试卷名称,从PaperVo对象中获取试卷名称并赋值给PaperEntity对象的相应属性,确保试卷在数据库中的名称与爬取到的一致。 + paper.setName(paperVo.getPaperName()); + // 设置试卷年份,将固定的“2016”年份赋值给相应属性,方便后续按年份统计、查询试卷等操作。 + paper.setYear(YEAR); + // 设置试卷是否免费的标识,这里设置为“0”,具体含义可能表示试卷不是免费的(需结合业务中对该字段的定义确定), + // 用于记录试卷的收费相关属性,在涉及试卷购买、展示等业务场景中会用到该信息。 + paper.setFree("0"); + // 设置试卷的SEO标题,这里直接使用试卷名称作为SEO标题,方便搜索引擎对试卷页面进行索引和展示,提高试卷在搜索结果中的曝光度。 + paper.setSeoTitle(paperVo.getPaperName()); + // 设置试卷的SEO关键词为空字符串,可能表示暂时未获取到合适的关键词或者该试卷不需要特定关键词进行搜索引擎优化, + // 具体可根据实际业务需求进行补充完善。 + paper.setSeoKeywords(""); + // 设置试卷的SEO描述为空字符串,同样可能是暂时未确定描述内容或者不需要特定描述进行搜索引擎优化,后续可根据实际情况完善。 + paper.setSeoDescription(""); + // 设置试卷类型,将之前定义的“4”(名校精品类型)赋值给相应属性,便于后续按试卷类型对试卷进行分类管理和展示等操作。 + paper.setType(PAPER_TYPE); + // 创建一个JSONArray对象,用于存储试卷的题目信息,题目信息会以JSON格式进行封装,后续会将这个JSONArray转换为字符串后存入数据库。 + JSONArray entitys = new JSONArray(); + // 循环处理题目类型相关信息,遍历paperVo对象中存储的题目类型列表,对每个题目类型进行相应的数据封装和处理操作, + // 这里paperVo.getQuestionInfoTypes()方法应该返回一个包含题目类型名称的列表,通过循环依次处理每个题目类型。 + for (int i = 0; i < paperVo.getQuestionInfoTypes().size(); i++) { + // 创建一个JSONObject对象,用于封装单个题目的相关信息,每个题目会被构建成一个独立的JSONObject,然后添加到JSONArray中。 + JSONObject entity = new JSONObject(); + // 设置题目的唯一标识,这里简单地以序号(i + 1)作为题目ID,方便后续对题目进行区分和索引等操作。 + entity.put("id", i + 1); + // 设置题目的名称,从paperVo对象中获取题目类型名称并赋值给相应属性,用于展示题目相关信息。 + entity.put("name", paperVo.getQuestionInfoTypes().get(i)); + // 设置题目的标题,同样从paperVo对象中获取对应信息赋值给相应属性,这里题目的标题可能是更详细的题目描述等内容,具体取决于业务逻辑。 + entity.put("title", paperVo.getQuestionInfoTitles().get(i)); + // 通过QuestionType枚举类的静态方法获取题目类型对应的枚举值,根据题目类型名称从枚举中查找对应的类型值, + // 并将其赋值给entity对象的“type”属性,用于明确题目在业务逻辑中的具体类型分类。 + QuestionType questionType = QuestionType.getQuestionType(paperVo.getQuestionInfoTypes().get(i)); + entity.put("type", questionType.getValue()); + + // 将封装好的单个题目信息的JSONObject对象添加到JSONArray中,这样 entitys.add(entity); } diff --git a/tamguo-crawler/src/test/java/com/tamguo/PaperQuestionCrawler.java b/tamguo-crawler/src/test/java/com/tamguo/PaperQuestionCrawler.java index 84300dc..75e2c8c 100644 --- a/tamguo-crawler/src/test/java/com/tamguo/PaperQuestionCrawler.java +++ b/tamguo-crawler/src/test/java/com/tamguo/PaperQuestionCrawler.java @@ -2,237 +2,408 @@ package com.tamguo; import com.baomidou.mybatisplus.mapper.Condition; import com.baomidou.mybatisplus.plugins.Page; +// 引入MyBatis-Plus框架中的相关类,Condition类用于构建数据库查询条件,方便按照特定规则筛选、排序等操作; +// Page类用于实现分页查询相关功能,可指定每页数据量、当前页码等信息,便于对大量数据进行分页处理。 + import com.tamguo.config.redis.CacheService; +// 引入自定义的基于Redis的缓存服务类,推测其提供了如缓存数据读写、计数等与Redis缓存交互的功能,在后续代码中可能用于辅助数据处理,比如生成唯一标识等操作。 + import com.tamguo.dao.*; +// 引入多个自定义的数据访问层(DAO)接口,这些接口分别对应不同实体(如Question、CrawlerQuestion、CrawlerPaper等)在数据库中的操作, +// 其实现类会具体实现像数据的插入、查询、更新等数据库交互逻辑,通过依赖注入获取相应实现类实例后就能操作数据库了。 + import com.tamguo.model.*; import com.tamguo.model.enums.QuestionType; import com.tamguo.model.vo.QuestionVo; +// 引入自定义的实体类、枚举类以及视图对象类。实体类对应数据库表结构,包含各表字段对应的属性;QuestionType枚举类用于明确题目类型的不同取值情况,便于业务逻辑区分; +// QuestionVo这类视图对象类通常用于在不同方法间临时封装和传递数据,比如在网页爬取解析过程中传递题目相关信息。 + import com.xuxueli.crawler.XxlCrawler; import com.xuxueli.crawler.conf.XxlCrawlerConf; import com.xuxueli.crawler.loader.strategy.HtmlUnitPageLoader; import com.xuxueli.crawler.parser.PageParser; import com.xuxueli.crawler.rundata.RunData; import com.xuxueli.crawler.util.FileUtil; +// 引入XxlCrawler框架相关类,XxlCrawler用于构建和配置爬虫实例;XxlCrawlerConf可能包含爬虫的一些默认配置参数等;HtmlUnitPageLoader用于加载网页内容; +// PageParser用于定义如何解析爬取到的页面内容;RunData用于管理爬虫运行时的数据;FileUtil提供文件下载等文件相关操作的功能,这些类协同实现网页爬取及后续数据处理。 + import org.apache.commons.lang3.StringUtils; +// 引入Apache Commons Lang3库中的StringUtils类,用于方便地进行字符串相关操作,比如判断字符串是否为空、拼接字符串等,在代码中多处用于对各种文本属性的处理。 + import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; +// 引入Jsoup库中的Document和Element类,用于解析HTML文档,Document代表整个HTML页面文档,Element则用于表示文档中的具体元素, +// 在爬取网页后解析页面内容提取数据时会用到它们。 + import org.junit.Test; import org.junit.runner.RunWith; +// 引入JUnit测试框架中的注解,@Test用于标记下面的方法为测试方法,JUnit运行时会执行被标记的方法来进行测试;@RunWith用于指定测试运行的运行器, +// 这里配置使其能运行在符合Spring框架要求的环境下,便于结合Spring相关功能开展测试。 + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; +// Spring框架相关注解,@Autowired用于实现自动装配(依赖注入),让Spring容器把对应的Bean实例注入到标注的变量中;@SpringBootTest表明这是一个Spring Boot应用的测试类, +// 会启动完整的应用上下文;SpringRunner是Spring为JUnit 4提供的运行器,结合@RunWith使用能让测试运行在Spring构建的测试环境里。 import java.io.File; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.*; +// 引入Java标准库中的常用类,用于文件操作、日期格式化、数字格式化以及集合操作等,满足代码中诸如创建文件目录、格式化日期、生成编号以及处理数据集合等需求。 +// PaperQuestionCrawler类是一个基于Spring Boot和JUnit框架构建的测试类,其主要功能是利用XxlCrawler框架爬取试卷题目相关信息, +// 对爬取到的数据进行多方面处理(如关联其他实体信息、处理题目内容中的图片等)后,将题目数据存储到数据库中。 @RunWith(SpringRunner.class) @SpringBootTest public class PaperQuestionCrawler { + // 使用@Autowired注解自动注入QuestionMapper接口的实现类实例,通过该实例可执行与题目(Question)相关的数据库操作, + // 例如将整理好的题目信息插入到数据库对应的题目表中,实现数据的持久化存储。 @Autowired QuestionMapper questionMapper; + // 自动注入CrawlerQuestionMapper接口的实现类实例,用于处理与爬取题目相关的数据库操作,比如可能涉及保存爬取题目过程中的一些额外信息等,辅助整个题目爬取及存储流程。 @Autowired CrawlerQuestionMapper crawlerQuestionMapper; + // 注入CrawlerPaperMapper接口实现类实例,借助它能操作与爬取试卷相关的数据,比如依据题目所在的URL去查找对应的试卷信息等,方便建立题目与试卷之间的关联关系。 @Autowired CrawlerPaperMapper crawlerPaperMapper; + // 注入ChapterMapper接口实现类实例,虽然在当前展示的代码中未明确体现其详细使用场景,但从名称推测它用于执行与章节(Chapter)相关的数据库操作, + // 或许在题目与章节关联等业务逻辑处理中会用到。 @Autowired ChapterMapper chapterMapper; + // 注入CourseMapper接口实现类实例,用于操作课程(Course)相关的数据库操作,例如通过试卷关联获取题目所属课程信息,进而准确设置题目实体中的课程相关属性。 @Autowired CourseMapper courseMapper; + // 注入SubjectMapper接口实现类实例,用于操作学科(Subject)相关的数据库操作,同样是为了获取题目所属学科的相关信息,完善题目实体对象中关于学科的属性设置。 @Autowired SubjectMapper subjectMapper; + // 注入PaperMapper接口实现类实例,用于执行试卷(Paper)相关的数据库操作,像根据试卷ID获取试卷详细信息等,辅助题目数据与试卷数据之间的关联处理及相关属性赋值。 @Autowired PaperMapper paperMapper; + // 注入自定义的缓存服务类实例,用于与Redis缓存进行交互,在后续代码中可能用于对部分数据进行缓存以提升性能,或者实现如文件编号自增等特定的数据处理功能。 @Autowired CacheService cacheService; + + // 定义文件编号的格式模板,是一个固定长度(9位)的用0占位的数字字符串,用于后续按照一定规则生成文件编号,确保编号格式的一致性和规范性。 private static final String FILES_NO_FORMAT = "000000000"; + // 定义文件路径的前缀字符串,此处为“shengwu”,用于构建完整的文件存储路径,可能表示文件所属的分类或主题,具体含义取决于业务逻辑设定。 private static final String FILES_PREFIX = "shengwu"; + // 定义课程ID,固定为“shengwu”,用于标识题目所属的课程,方便在数据处理过程中关联题目与课程,以及在构建文件路径等操作中作为关键标识使用。 private static final String COURSE_ID = "shengwu"; - + + // 用于存储爬虫运行时的数据对象,通过XxlCrawler框架管理爬虫运行过程中的各种相关数据,例如已访问的URL、待访问的URL等, + // 在爬虫启动以及后续的数据处理过程中,这个对象中的数据会不断更新和被使用。 private RunData runData; + // 使用@Test注解标记的测试方法,名为crawlerQuestion,该方法实现了利用XxlCrawler框架进行题目信息爬取、对爬取到的数据进行全面处理以及最终将处理好的数据存储到数据库的完整逻辑, + // 同时涵盖了对题目内容中图片的下载以及相关URL替换等复杂操作。 @SuppressWarnings("unchecked") @Test public void crawlerQuestion() { - + // 创建一个XxlCrawler实例的构建器,通过链式调用一系列方法来配置爬虫的各个参数和行为,完成配置后调用build方法就能得到最终可用的爬虫实例。 XxlCrawler crawler = new XxlCrawler.Builder() - .setAllowSpread(false) - .setThreadCount(1) - .setFailRetryCount(5) - .setPageLoader(new HtmlUnitPageLoader()) - .setPageParser(new PageParser() { - - @Override - public void parse(Document html, Element pageVoElement, QuestionVo questionVo) { - if(StringUtils.isEmpty(questionVo.getContent())) { - runData.addUrl(html.baseUri()); - return; - } - CrawlerPaperEntity condition = new CrawlerPaperEntity(); - condition.setQuestionUrl(html.baseUri()); - System.out.println(html.baseUri()); - CrawlerPaperEntity crawlerPaper = crawlerPaperMapper.selectOne(condition); - PaperEntity paper = paperMapper.selectById(crawlerPaper.getPaperId()); - CourseEntity course = courseMapper.selectById(paper.getCourseId()); - SubjectEntity subject = subjectMapper.selectById(paper.getSubjectId()); - - QuestionType questionType = QuestionType.getQuestionType(questionVo.getQuestionType()); - - - QuestionEntity question = new QuestionEntity(); - if(questionType == QuestionType.DANXUANTI) { - if(!StringUtils.isEmpty(questionVo.getQueoptions())) { - question.setContent(questionVo.getContent() + questionVo.getQueoptions()); - }else { - question.setContent(questionVo.getContent()); - } - }else { - question.setContent(questionVo.getContent()); - } - question.setAnalysis(questionVo.getAnalysis()); - if(StringUtils.isEmpty(question.getAnalysis())) { - question.setAnalysis("


"); - } - question.setAnswer(questionVo.getAnswer()); - question.setAuditStatus("1"); - question.setChapterId(""); - question.setCourseId(course.getId()); - question.setPaperId(paper.getId()); - question.setQuestionType(questionType.getValue().toString()); - if(questionVo.getReviewPoint() != null && questionVo.getReviewPoint().size() > 0) { - question.setReviewPoint(StringUtils.join(questionVo.getReviewPoint().toArray(), ",")); - } - // 处理分数 - if(questionVo.getScore() != null) { - if(questionVo.getScore().contains("分")) { - question.setScore(questionVo.getScore()); - } - if(questionVo.getScore().contains("年")) { - question.setYear(questionVo.getScore()); - } - } - if(questionVo.getYear() != null) { - if(questionVo.getYear().contains("年")) { - question.setYear(questionVo.getYear()); - } - } - question.setSubjectId(subject.getId()); - - if (questionVo.getAnswerImages()!=null && questionVo.getAnswerImages().size() > 0) { - Set imagesSet = new HashSet(questionVo.getAnswerImages()); - for (String img: imagesSet) { - - // 下载图片文件 - String fileName = getFileName(img); - String filePath = getFilePath(); - String fileDatePath = getFileDatePath(); - - File dir = new File(filePath +fileDatePath+ "/"); - if (!dir.exists()) - dir.mkdirs(); - boolean ret = FileUtil.downFile(img, XxlCrawlerConf.TIMEOUT_MILLIS_DEFAULT, filePath +fileDatePath+ "/", fileName); - System.out.println("down images " + (ret?"success":"fail") + ":" + img); - - // 替换URL - question.setAnswer(question.getAnswer().replace(img, "/files/paper/" + COURSE_ID + '/' + fileDatePath + "/" + fileName)); - } - question.setAnswer(question.getAnswer()); - } - - if (questionVo.getAnalysisImages()!=null && questionVo.getAnalysisImages().size() > 0) { - Set imagesSet = new HashSet(questionVo.getAnalysisImages()); - for (String img: imagesSet) { - - // 下载图片文件 - String fileName = getFileName(img); - String filePath = getFilePath(); - String fileDatePath = getFileDatePath(); - - File dir = new File(filePath +fileDatePath+ "/"); - if (!dir.exists()) - dir.mkdirs(); - boolean ret = FileUtil.downFile(img, XxlCrawlerConf.TIMEOUT_MILLIS_DEFAULT, filePath +fileDatePath+ "/", fileName); - System.out.println("down images " + (ret?"success":"fail") + ":" + img); - - // 替换URL - question.setAnalysis(question.getAnalysis().replace(img, "/files/paper/" + COURSE_ID + '/' + fileDatePath + "/" + fileName)); - } - question.setAnalysis(question.getAnalysis()); - } - - if (questionVo.getContentImages()!=null && questionVo.getContentImages().size() > 0) { - Set imagesSet = new HashSet(questionVo.getContentImages()); - for (String img: imagesSet) { - - // 下载图片文件 - String fileName = getFileName(img); - String filePath = getFilePath(); - String fileDatePath = getFileDatePath(); - File dir = new File(filePath +fileDatePath+ "/"); - if (!dir.exists()) { - dir.mkdirs(); - } - boolean ret = FileUtil.downFile(img, XxlCrawlerConf.TIMEOUT_MILLIS_DEFAULT, filePath +fileDatePath+ "/", fileName); - System.out.println("down images " + (ret?"success":"fail") + ":" + img); - - // 替换URL - question.setContent(question.getContent().replace(img, "/files/paper/" + COURSE_ID + '/' + fileDatePath + "/" + fileName)); - } - question.setContent(question.getContent()); - } - - - // 处理图片 - question.setSourceType("baidu"); - question.setSourceUrl(html.baseUri()); - questionMapper.insert(question); - } - - public String getFileName(String img) { - return getFileNo() + img.substring(img.lastIndexOf(".")); - } - - private String getFilePath() { - return "/home/webdata/files/paper/" + COURSE_ID + "/"; - } - - private String getFileDatePath() { - SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH"); - String format = sdf.format(new Date()); - return format; - } - - private String getFileNo() { - SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH"); - String format = sdf.format(new Date()); - DecimalFormat df = new DecimalFormat(FILES_NO_FORMAT); - String key = FILES_PREFIX + format; - Long incr = cacheService.incr(key); - String avatorNo = FILES_PREFIX + df.format(incr); - return avatorNo; - } - }).build(); - - runData = crawler.getRunData(); - int page = 1; - int pageSize = 1000; - while(true) { - Page questionPage = new Page(page , pageSize); - List questionList = crawlerPaperMapper.selectPage(questionPage, Condition.create().orderAsc(Arrays.asList("paper_id" , "queindex"))); - for(int i=0 ;i() { + + @Override + public void parse(Document html, Element pageVoElement, QuestionVo questionVo) { + // 判断题目Vo对象中的题目内容(questionVo.getContent())是否为空字符串,如果为空,说明当前页面可能未正确获取到题目内容, + // 则将当前页面的URL添加到待访问的URL列表中(通过runData.addUrl方法),以便后续可能再次尝试爬取该URL对应的页面,然后直接返回,不再进行后续的题目数据处理流程。 + if (StringUtils.isEmpty(questionVo.getContent())) { + runData.addUrl(html.baseUri()); + return; + } + + // 创建一个CrawlerPaperEntity对象,用于构建查询数据库的条件,这里以题目所在页面的URL(html.baseUri())作为关键条件, + // 目的是通过这个URL去数据库中查找对应的爬取试卷相关的实体信息,以便后续建立题目与试卷之间的关联关系。 + CrawlerPaperEntity condition = new CrawlerPaperEntity(); + condition.setQuestionUrl(html.baseUri()); + // 在控制台打印当前页面的URL,可能是为了方便在爬取过程中查看正在处理的页面情况,有助于调试和监控爬取的进度以及了解数据来源等情况。 + System.out.println(html.baseUri()); + // 使用注入的crawlerPaperMapper(CrawlerPaperMapper接口的实现类实例)的selectOne方法,根据前面构建的查询条件condition, + // 从数据库中查询并获取对应的CrawlerPaperEntity对象,也就是得到该题目所属的爬取试卷的相关详细信息。 + CrawlerPaperEntity crawlerPaper = crawlerPaperMapper.selectOne(condition); + // 通过注入的paperMapper(PaperMapper接口的实现类实例)的selectById方法,依据从前面获取到的爬取试卷的ID(从crawlerPaper对象中获取), + // 从数据库中查询出对应的PaperEntity对象,从而得到完整的试卷信息,为后续设置题目与试卷相关的属性做准备。 + PaperEntity paper = paperMapper.selectById(crawlerPaper.getPaperId()); + // 同样地,利用注入的courseMapper(CourseMapper接口的实现类实例)的selectById方法,按照试卷所属的课程ID(从paper对象中获取), + // 从数据库中查询出对应的CourseEntity对象,获取题目所属的课程相关信息,用于后续准确设置题目实体中的课程属性。 + CourseEntity course = courseMapper.selectById(paper.getCourseId()); + // 按照类似的逻辑,使用注入的subjectMapper(SubjectMapper接口的实现类实例)的selectById方法,根据试卷所属的学科ID(从paper对象中获取), + // 从数据库中查询出SubjectEntity对象,以此得到题目所属的学科信息,进一步完善题目实体对象的属性赋值。 + SubjectEntity subject = subjectMapper.selectById(paper.getSubjectId()); + + // 通过QuestionType枚举类的静态方法getQuestionType,依据题目Vo对象中获取的题目类型名称(questionVo.getQuestionType()), + // 获取对应的QuestionType枚举值,这样就能在业务逻辑中明确区分题目具体属于哪种类型,方便后续根据不同类型题目进行差异化的处理逻辑。 + QuestionType questionType = QuestionType.getQuestionType(questionVo.getQuestionType()); + + // 创建一个QuestionEntity对象,用于封装要插入到数据库中的题目信息,后续会把这个对象中的各项数据持久化存储到数据库对应的题目表中。 + QuestionEntity question = new QuestionEntity(); + // 根据题目类型来设置题目内容,如果题目类型是单选题(通过判断questionType是否等于QuestionType.DANXUANTI), + // 并且题目Vo对象中的题目选项(questionVo.getQueoptions())不为空字符串,那么将题目内容和题目选项拼接起来作为最终的题目内容; + // 若题目选项为空,则直接使用题目Vo中的题目内容作为最终题目内容;对于非单选题类型,则直接使用题目Vo中的题目内容设置即可。 + if (questionType == QuestionType.DANXUANTI) { + if (!StringUtils.isEmpty(questionVo.getQueoptions())) { + question.setContent(questionVo.getContent() + questionVo.getQueoptions()); + } else { + question.setContent(questionVo.getContent()); + } + } else { + question.setContent(questionVo.getContent()); + } + // 设置题目解析内容,从题目Vo对象中获取解析内容赋值给QuestionEntity对象的相应属性,如果获取到的解析内容为空字符串, + // 则设置一个默认的简略解析内容(


),以保证题目解析信息在数据库中具有一定的完整性。 + question.setAnalysis(questionVo.getAnalysis()); + if (StringUtils.isEmpty(question.getAnalysis())) { + question.setAnalysis("


"); + } + // 设置题目答案,直接从题目Vo对象中获取答案内容赋值给QuestionEntity对象的相应属性。 + question.setAnswer(questionVo.getAnswer()); + // 设置题目审核状态为“1”,这里“1”所代表的具体审核状态含义需要结合整个业务逻辑来确定,可能表示已审核通过或者其他特定的审核情况, + // 用于在数据库中记录题目在审核方面的状态信息。 + question.setAuditStatus("1"); + // 设置题目所属章节ID为空字符串,这可能意味着当前题目暂时没有关联到具体的章节,或者还未获取到章节相关信息,具体要根据业务场景来判断。 + question.setChapterId(""); + // 设置题目所属课程ID,将前面获取到的CourseEntity对象中的课程ID赋值给题目实体的相应属性,确保题目与正确的课程建立关联关系。 + question.setCourseId(course.getId()); + // 设置题目所属试卷ID,从前面获取的PaperEntity对象中获取试卷ID赋值给题目实体的相应属性,以此建立题目与试卷之间的明确关联关系。 + question.setPaperId(paper.getId()); + // 设置题目类型,把QuestionType枚举值转换为字符串(通过getValue().toString()方法)后赋值给题目实体的相应属性, + // 这样在数据库中就能准确记录题目具体的类型信息了。 + question.setQuestionType(questionType.getValue().toString()); + // 判断题目Vo对象中的复习要点(questionVo.getReviewPoint())是否不为空且包含元素,如果满足条件, + // 则使用StringUtils的join方法将复习要点列表转换为以逗号分隔的字符串,并赋值给题目实体的复习要点属性,方便在数据库中以合适的格式存储该信息。 + if (questionVo.getReviewPoint()!= null && questionVo.getReviewPoint().size() > 0) { + question.setReviewPoint(StringUtils.join(questionVo.getReviewPoint().toArray(), ",")); + } + + // 处理题目分数相关信息,如果题目Vo对象中的分数(questionVo.getScore())不为空,并且这个分数字符串中包含“分”字, + // 则将该分数内容赋值给题目实体的分数属性,用于准确记录题目对应的分值情况,符合常规的分数记录逻辑。 + if (questionVo.getScore()!= null) { + if (questionVo.getScore().contains("分")) { + question.setScore(questionVo.getScore()); + } + // 如果题目Vo对象中的分数字符串包含“年”字,这里可能存在特殊的业务逻辑处理情况,比如在特定场景下分数字段复用为年份相关信息的记录, + // 则将该分数内容赋值给题目实体的年份属性,具体含义需结合 + if (questionVo.getScore()!= null) { + // 判断题目Vo对象中的分数(questionVo.getScore())字符串是否包含“年”字,如果包含,说明在当前业务逻辑下, + // 这个分数字段可能有特殊含义(也许是复用该字段来表示年份相关信息),则将该分数内容赋值给题目实体的年份属性(question.setYear(questionVo.getScore()))。 + if (questionVo.getScore().contains("年")) { + question.setYear(questionVo.getScore()); + } + } +// 再次判断题目Vo对象中的年份(questionVo.getYear())是否不为空,进行这一步判断是为了确保年份信息的准确设置, +// 因为前面可能只是基于分数字段中是否包含“年”字来设置年份属性,这里再单独判断年份字段本身的情况。 + if (questionVo.getYear()!= null) { + // 如果题目Vo对象中的年份字符串包含“年”字,那么将该年份内容赋值给题目实体的年份属性,以保证题目实体中年份信息的准确性和完整性, + // 符合业务逻辑中对题目年份信息记录的要求。 + if (questionVo.getYear().contains("年")) { + question.setYear(questionVo.getYear()); + } + } +// 设置题目所属学科ID,将从前面通过subjectMapper查询获取到的SubjectEntity对象中的学科ID赋值给题目实体的相应属性(question.setSubjectId(subject.getId())), +// 以此明确题目所属的学科,建立题目与学科之间的正确关联关系,便于后续按照学科维度对题目进行分类、查询等业务操作。 + question.setSubjectId(subject.getId()); + +// 判断题目Vo对象中的答案图片列表(questionVo.getAnswerImages())是否不为空且包含元素,即检查题目答案中是否存在图片, +// 如果存在图片,则需要进行图片相关的处理操作,包括图片下载以及在题目答案文本中替换图片URL等操作。 + if (questionVo.getAnswerImages()!= null && questionVo.getAnswerImages().size() > 0) { + // 将题目Vo对象中的答案图片列表转换为一个HashSet集合,使用HashSet可以去除重复的图片URL,确保每个图片只处理一次,提高处理效率, + // 同时方便后续的遍历操作(因为Set集合不允许重复元素,符合处理图片URL这种唯一性要求较高的场景)。 + Set imagesSet = new HashSet(questionVo.getAnswerImages()); + // 遍历答案图片集合,对每一张图片进行下载以及URL替换操作。 + for (String img : imagesSet) { + // 调用getFileName方法获取要下载的图片文件的文件名,该方法会根据图片URL以及一些业务规则生成文件名, + // 文件名的生成逻辑在getFileName方法中具体实现(通常会考虑文件后缀、编号等因素来保证文件名的唯一性和规范性)。 + String fileName = getFileName(img); + // 调用getFilePath方法获取文件存储的基础路径,该路径是预先定义好的,用于确定文件在服务器上大致的存储位置, + // 具体路径可能根据业务需求和项目配置来设定,比如按照课程、学科等分类来划分不同的文件存储目录。 + String filePath = getFilePath(); + // 调用getFileDatePath方法获取基于当前日期时间生成的文件路径部分,通常用于按照时间来进一步细分文件存储目录, + // 例如每天的文件存放在不同的以日期时间命名的子目录下,方便文件管理和查找,该方法内部会利用日期格式化来生成相应的路径字符串。 + String fileDatePath = getFileDatePath(); + + // 根据获取到的文件路径和日期路径创建一个File对象,表示要创建的文件目录对象,如果该目录不存在, + // 则通过mkdirs方法创建该目录及其所有必要的父目录,确保文件下载时有对应的存储目录存在,避免出现文件保存失败的情况。 + File dir = new File(filePath + fileDatePath + "/"); + if (!dir.exists()) + dir.mkdirs(); + // 使用FileUtil工具类的downFile方法下载图片文件,传入图片URL、下载超时时间(这里使用XxlCrawlerConf.TIMEOUT_MILLIS_DEFAULT作为默认超时时间)、 + // 文件存储路径以及文件名等参数,该方法内部实现了具体的文件下载逻辑,比如建立网络连接、读取文件流并保存到本地等操作, + // 并返回一个布尔值表示文件下载是否成功。 + boolean ret = FileUtil.downFile(img, XxlCrawlerConf.TIMEOUT_MILLIS_DEFAULT, filePath + fileDatePath + "/", fileName); + // 在控制台打印图片下载的结果信息,方便在运行程序时查看图片下载情况,便于调试和监控,如果下载成功则显示“success”,失败则显示“fail”, + // 同时打印出当前正在处理的图片URL,让开发人员能清楚知道具体是哪张图片的下载情况。 + System.out.println("down images " + (ret? "success" : "fail") + ":" + img); + + // 在题目答案文本中替换图片的原始URL为新的本地文件路径对应的URL,通过调用String的replace方法,将题目答案中原来的图片URL(img) + // 替换为新的格式(/files/paper/ + COURSE_ID + '/' + fileDatePath + "/" + fileName),使其指向下载到本地后的图片文件位置, + // 这样在后续展示题目答案等相关操作时,就能正确加载本地存储的图片了。 + question.setAnswer(question.getAnswer().replace(img, "/files/paper/" + COURSE_ID + '/' + fileDatePath + "/" + fileName)); + } + // 将处理后的题目答案重新赋值给题目实体的答案属性,虽然看起来这一步有点多余(因为前面已经在原答案属性上进行了替换操作), + // 但从代码的清晰性和完整性角度考虑,这样做可以更明确地表示答案属性经过一系列处理后最终的赋值情况,避免其他地方误修改该属性导致逻辑混乱。 + question.setAnswer(question.getAnswer()); + } + +// 判断题目Vo对象中的解析图片列表(questionVo.getAnalysisImages())是否不为空且包含元素,即检查题目解析内容中是否存在图片, +// 如果存在图片,同样需要进行图片下载以及在题目解析文本中替换图片URL等相关操作,流程与处理答案图片类似。 + if (questionVo.getAnalysisImages()!= null && questionVo.getAnalysisImages().size() > 0) { + Set imagesSet = new HashSet(questionVo.getAnalysisImages()); + for (String img : imagesSet) { + String fileName = getFileName(img); + String filePath = getFilePath(); + String fileDatePath = getFileDatePath(); + + File dir = new File(filePath + fileDatePath + "/"); + if (!dir.exists()) + dir.mkdirs(); + boolean ret = FileUtil.downFile(img, XxlCrawlerConf.TIMEOUT_MILLIS_DEFAULT, filePath + fileDatePath + "/", fileName); + System.out.println("down images " + (ret? "success" : "fail") + ":" + img); + + // 在题目解析文本中替换图片的原始URL为新的本地文件路径对应的URL,使题目解析在展示时能正确加载本地存储的图片, + // 替换逻辑与答案图片的URL替换类似,只是针对的是题目解析内容中的图片URL替换。 + question.setAnalysis(question.getAnalysis().replace(img, "/files/paper/" + COURSE_ID + '/' + fileDatePath + "/" + fileName)); + } + // 同样将处理后的题目解析内容重新赋值给题目实体的解析属性,确保解析属性的最终状态符合预期,保证数据的准确性和完整性。 + question.setAnalysis(question.getAnalysis()); + } + +// 判断题目Vo对象中的题目内容图片列表(questionVo.getContentImages())是否不为空且包含元素,即检查题目内容本身是否包含图片, +// 如果有图片,也要进行相应的图片下载以及在题目内容文本中替换图片URL等操作,操作流程与前面处理答案图片、解析图片基本一致。 + if (questionVo.getContentImages()!= null && questionVo.getContentImages().size() > 0) { + Set imagesSet = new HashSet(questionVo.getContentImages()); + for (String img : imagesSet) { + String fileName = getFileName(img); + String filePath = getFilePath(); + String fileDatePath = getFileDatePath(); + File dir = new File(filePath + fileDatePath + "/"); + if (!dir.exists()) { + dir.mkdirs(); + } + boolean ret = FileUtil.downFile(img, XxlCrawlerConf.TIMEOUT_MILLIS_DEFAULT, filePath + fileDatePath + "/", fileName); + System.out.println("down images " + (ret? "success" : "fail") + ":" + img); + + // 在题目内容文本中替换图片的原始URL为新的本地文件路径对应的URL,使得题目内容在展示时能够正确加载本地存储的图片, + // 保证题目内容的完整性和正确性,符合业务逻辑中对题目包含图片情况的处理要求。 + question.setContent(question.getContent().replace(img, "/files/paper/" + COURSE_ID + '/' + fileDatePath + "/" + fileName)); + } + // 将处理后的题目内容重新赋值给题目实体的内容属性,明确题目内容属性经过图片相关处理后的最终状态,便于后续将题目数据准确存储到数据库中。 + question.setContent(question.getContent()); + } + +// 设置题目图片的来源类型为“baidu”,这里明确表示题目中的图片是从百度相关来源获取的(可能是从百度题库等地方爬取的), +// 具体的来源类型设定取决于业务需求和数据溯源的要求,方便后续对图片来源进行统计、分析等操作。 + question.setSourceType("baidu"); +// 设置题目图片的来源URL,将当前爬取页面的URL(html.baseUri())赋值给题目实体的来源URL属性,记录题目图片最初是从哪个页面获取的, +// 有助于数据溯源以及在需要查看原始图片来源等情况下使用,保证数据的完整性和可追溯性。 + question.setSourceUrl(html.baseUri()); +// 使用注入的questionMapper(QuestionMapper接口的实现类实例)的insert方法,将处理好的题目实体对象(question)中的数据插入到数据库对应的题目表中, +// 实现题目数据的持久化存储,完成整个题目爬取、处理以及存储的流程,确保题目信息能够正确保存到数据库里,供后续业务使用。 + questionMapper.insert(question); + + // 定义一个名为getFileName的私有方法,它接收一个表示图片URL的字符串参数img,其作用是根据给定的图片URL生成一个合适的文件名, +// 这个文件名会用于后续图片文件的保存操作,确保文件名具有一定的规范性和唯一性(结合业务规则生成)。 + public String getFileName(String img) { + // 通过调用getFileNo方法获取一个文件编号部分,这个编号可能基于当前时间、业务标识等因素生成,用于保证文件名的唯一性(避免重复文件名冲突), + // 然后拼接上图片URL中提取出的文件后缀部分(通过img.substring(img.lastIndexOf("."))获取,即从图片URL中获取最后一个“.”之后的内容作为后缀), + // 最终组成完整的文件名并返回。 + return getFileNo() + img.substring(img.lastIndexOf(".")); + } + +// 定义一个私有的方法getFilePath,用于获取文件存储的基础路径,该路径是预先配置好的固定路径,按照业务逻辑设定了文件大致的存储位置, +// 这里返回的路径表明文件将存储在“/home/webdata/files/paper/”目录下,并结合课程ID(COURSE_ID,当前固定为“shengwu”)进一步明确子目录, +// 方便对不同课程的相关文件进行分类存储管理。 + private String getFilePath() { + return "/home/webdata/files/paper/" + COURSE_ID + "/"; + } + +// 定义一个私有的方法getFileDatePath,其目的是根据当前日期时间生成一个用于文件路径的时间相关部分, +// 通过使用SimpleDateFormat按照“yyyyMMddHH”格式对当前日期时间进行格式化,生成类似“2024121510”这样的字符串(代表具体的年、月、日、小时), +// 以此作为文件存储路径的一部分,便于按照时间维度对文件进行分类存储,例如每天的文件存放在以当天日期时间命名的子目录下,方便后续查找和管理文件。 + private String getFileDatePath() { + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH"); + String format = sdf.format(new Date()); + return format; + } + +// 定义一个私有的方法getFileNo,用于生成文件编号,该编号在整个文件管理逻辑中起到保证文件唯一性以及便于排序等作用(具体取决于业务需求)。 + private String getFileNo() { + // 首先使用SimpleDateFormat按照“yyyyMMddHH”格式获取当前日期时间的格式化字符串,例如“2024121510”,这一步和getFileDatePath方法中获取时间格式的逻辑类似, + // 是基于当前时间来生成编号的一部分,确保不同时间生成的编号有一定区分度,同时也便于后续按时间维度对文件进行管理和查找等操作。 + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH"); + String format = sdf.format(new Date()); + // 创建一个DecimalFormat对象,使用之前定义的FILES_NO_FORMAT(固定格式为“000000000”)作为格式化模板, + // 目的是将后续生成的自增编号按照这个固定长度的数字格式进行格式化,保证编号长度统一且具有一定的顺序性和可读性。 + DecimalFormat df = new DecimalFormat(FILES_NO_FORMAT); + // 构建一个用于在缓存中作为键的字符串,它由FILES_PREFIX(当前固定为“shengwu”)和前面获取的日期时间格式化字符串(format)拼接而成, + // 这个键用于在缓存服务(cacheService)中标识一个特定的计数项,方便后续基于业务场景对不同的文件编号进行区分和管理(比如不同课程、不同业务模块等可以有不同的计数规则)。 + String key = FILES_PREFIX + format; + // 调用cacheService(注入的基于Redis的缓存服务类实例)的incr方法,对前面构建的键(key)对应的值进行自增操作, + // Redis的自增操作是原子性的,能保证在分布式环境下或者多线程环境中计数的准确性和一致性,这里实现了文件编号的自增功能,每次调用该方法都会使编号加1。 + Long incr = cacheService.incr(key); + // 使用DecimalFormat对象将自增后的编号(incr)按照之前定义的固定格式(FILES_NO_FORMAT)进行格式化,得到一个规范的文件编号字符串, + // 例如如果自增后编号为1,格式化后可能就是“shengwu000000001”,然后在前面再加上FILES_PREFIX(“shengwu”),最终返回完整的文件编号, + // 这个文件编号会作为文件名的一部分,用于保证文件名在整个业务场景下的唯一性和规范性。 + String avatorNo = FILES_PREFIX + df.format(incr); + return avatorNo; + } + +// 通过调用build方法构建出前面配置好的XxlCrawler实例,完成爬虫对象的创建,这个爬虫实例已经按照之前设置的各项参数(如页面加载策略、解析逻辑、线程数量等)进行了配置, +// 后续可以使用这个实例来启动爬虫进行网页爬取以及相关的数据处理操作。 + }).build(); + +// 获取前面构建好的XxlCrawler实例中的运行数据对象(RunData),这个对象用于管理爬虫在运行过程中的各种相关数据, +// 例如已经访问过的URL、还需要访问的URL等信息,在后续的爬取流程以及数据处理过程中会不断对这个对象中的数据进行更新和使用,起到协调整个爬虫工作流程的作用。 + runData = crawler.getRunData(); + +// 初始化页码变量page为1,表示从第一页开始进行分页查询操作,这里的分页是针对要爬取的题目相关数据进行的,目的是分批处理大量的题目数据,便于管理和提高效率。 + int page = 1; +// 设置每页的数据量为1000,即每次查询数据库获取1000条题目相关的数据记录,这个数量可以根据实际情况(如数据库性能、业务需求等)进行调整, +// 合理的每页数据量设置有助于平衡内存使用、查询效率以及数据处理的便捷性等方面的因素。 + int pageSize = 1000; +// 开启一个无限循环,通过不断分页查询题目数据,直到满足特定条件(即查询到的数据量小于每页数据量,表示已经处理完所有数据)时才会跳出循环, +// 以此实现对所有相关题目数据的遍历处理,确保不遗漏任何需要处理的数据。 + while (true) { + // 创建一个Page对象,用于表示分页查询的相关参数,传入当前页码(page)和每页数据量(pageSize), + // 这个对象会被传递给后续的查询方法,以便数据库查询操作按照指定的分页规则获取相应的数据记录。 + Page questionPage = new Page(page, pageSize); + // 使用注入的crawlerPaperMapper(CrawlerPaperMapper接口的实现类实例)的selectPage方法进行分页查询操作, + // 传入前面构建的分页参数对象(questionPage)以及查询条件(通过Condition.create().orderAsc(Arrays.asList("paper_id", "queindex"))构建), + // 这里构建的查询条件表示按照“paper_id”和“queindex”字段升序排列进行查询,获取符合条件的CrawlerPaperEntity类型的数据列表, + // 也就是获取到一批与爬取试卷题目相关的实体数据,便于后续对这些题目数据进行进一步的处理(如添加URL到待爬取列表等操作)。 + List questionList = crawlerPaperMapper.selectPage(questionPage, Condition.create().orderAsc(Arrays.asList("paper_id", "queindex"))); + // 遍历查询到的题目数据列表,对于每一条题目数据记录(CrawlerPaperEntity对象),获取其对应的题目URL(通过getQuestionUrl方法), + // 然后将这个URL添加到爬虫的运行数据对象(runData)中的待访问URL列表中,这样爬虫后续就会按照顺序去访问这些题目对应的页面,进行数据爬取和处理操作。 + for (int i = 0; i < questionList.size(); i++) { + runData.addUrl(questionList.get(i).getQuestionUrl()); + } + // 页码自增1,表示准备查询下一页的数据,继续下一轮的分页查询和数据处理流程,直到处理完所有满足条件的数据页为止。 + page++; + // 判断当前查询到的数据列表的大小(questionList.size())是否小于每页数据量(1000),如果小于,表示已经查询完所有数据,没有更多的数据页了, + // 此时跳出循环,结束分页查询和数据添加URL的操作,准备启动爬虫进行实际的爬取和后续处理工作。 + if (questionList.size() < 1000) { + break; + } + } + +// 启动前面配置好的XxlCrawler实例,传入参数true表示以某种特定的启动模式(具体取决于XxlCrawler框架的内部实现逻辑)启动爬虫, +// 启动后爬虫会按照预先设置的参数(如爬取范围、页面解析逻辑等)开始进行网页爬取、数据提取以及后续的数据处理和存储等操作, +// 整个流程围绕着获取试卷题目相关信息并进行处理后保存到数据库中展开,同时涉及图片文件下载及相关URL替换等相关复杂操作(在前面的代码逻辑中已定义)。 +// 获取科目(这里“获取科目”从代码逻辑上看不太明确具体含义,可能在爬虫启动后有进一步获取科目相关信息用于后续业务处理的逻辑,不过需要结合更多上下文代码来准确判断)。 + crawler.start(true);} } - } - // 获取科目 - crawler.start(true); - } - - - -} diff --git a/tamguo-crawler/src/test/java/com/tamguo/QuestionCrawler.java b/tamguo-crawler/src/test/java/com/tamguo/QuestionCrawler.java index 9b157ed..79a2407 100644 --- a/tamguo-crawler/src/test/java/com/tamguo/QuestionCrawler.java +++ b/tamguo-crawler/src/test/java/com/tamguo/QuestionCrawler.java @@ -1,24 +1,49 @@ package com.tamguo; import org.junit.Test; +// 引入JUnit测试框架中的@Test注解,其作用是标记当前类中的某个方法为测试方法。 +// 在运行测试时,JUnit框架会识别并执行被该注解标记的方法,然后根据方法内部的代码逻辑执行情况(例如是否正常结束、有无抛出异常等)来判断该测试是否通过。 + import org.junit.runner.RunWith; +// 此注解用于指定JUnit运行测试时所采用的运行器(Runner)。不同的运行器能为测试提供不同的执行环境和功能特性, +// 这里通过使用特定的运行器,使得测试能够运行在符合Spring框架要求的环境下,便于将Spring相关功能与测试进行整合。 + import org.springframework.beans.factory.annotation.Autowired; +// Spring框架提供的用于自动装配(依赖注入)的注解。Spring容器会依据一定的规则(如按照类型、名称等),自动查找并注入合适的Bean实例到标注了该注解的变量中, +// 这样代码就能方便地获取和使用其他由Spring管理的组件,在这段代码里,就是将实现IQuestionService接口的Bean实例注入进来。 + import org.springframework.boot.test.context.SpringBootTest; +// 该注解用于表明这是一个针对Spring Boot应用的测试类,它会启动完整的Spring Boot应用上下文,加载整个应用配置的各种资源(包含组件、配置信息等), +// 模拟应用实际运行的环境,方便在测试中使用已配置好的资源进行集成测试等操作,确保测试可以利用到应用中所有相关的配置和组件功能。 + import org.springframework.test.context.junit4.SpringRunner; +// Spring为JUnit 4提供的运行器,结合@RunWith(SpringRunner.class)的使用,能让JUnit测试运行在Spring构建的测试环境下, +// 进而可以利用Spring框架的诸多特性(如依赖注入、事务管理等)来辅助完成测试工作,确保测试过程与Spring应用的良好集成。 import com.tamguo.service.IQuestionService; +// 引入自定义的服务层接口IQuestionService,从接口名称推测,它定义了与题目(Question)相关的一系列业务操作方法, +// 比如题目数据的查询、更新、爬取等功能。在本测试类中,会通过依赖注入获取该接口的实现类实例,然后调用相应的业务方法来验证与题目相关业务逻辑的正确性。 +// QuestionCrawler类是一个基于Spring Boot和JUnit框架搭建的测试类,其主要目的是对题目相关的业务逻辑进行测试验证, +// 具体是通过调用IQuestionService接口实现类中的crawlerQuestion方法,来检测题目数据爬取相关的业务逻辑是否能够正确执行。 @RunWith(SpringRunner.class) @SpringBootTest public class QuestionCrawler { - @Autowired - IQuestionService iQuestionService; - + // 使用@Autowired注解,让Spring容器自动将实现了IQuestionService接口的Bean实例注入到iQuestionService变量中。 + // 如此一来,在后续的测试方法里就能直接使用这个服务实例去调用和题目相关的业务方法,例如这里要调用的crawlerQuestion方法,用于执行题目爬取操作。 + @Autowired + IQuestionService iQuestionService; + + // 使用@Test注解标记的测试方法,名为crawlerSubject,此处方法名可能不太准确(从功能上看或许叫crawlerQuestion更合适,不过需结合具体业务情况), + // 该方法主要用于测试题目数据爬取相关的业务逻辑。方法声明抛出Exception异常,意味着如果在方法执行期间出现了未处理的异常情况, + // JUnit会将此次测试判定为失败,并输出相应的异常信息,便于开发人员后续根据异常提示去排查问题,确定是代码逻辑错误还是其他原因导致的异常。 @Test public void crawlerSubject() throws Exception { - iQuestionService.crawlerQuestion(); + // 调用iQuestionService实例的crawlerQuestion方法,该方法的具体实现位于IQuestionService接口的实现类中。 + // 从方法名推测,其功能是执行实际的题目数据爬取操作,例如从网络上获取题目相关的信息等, + // 通过在此处调用该方法,来验证与之相关的题目爬取业务逻辑能否按照预期正常执行,保障题目爬取功能的准确性和稳定性。 + iQuestionService.crawlerQuestion(); } - - -} + +} \ No newline at end of file diff --git a/tamguo-crawler/src/test/java/com/tamguo/SingleQuestionCrawler.java b/tamguo-crawler/src/test/java/com/tamguo/SingleQuestionCrawler.java index 2437231..2236be2 100644 --- a/tamguo-crawler/src/test/java/com/tamguo/SingleQuestionCrawler.java +++ b/tamguo-crawler/src/test/java/com/tamguo/SingleQuestionCrawler.java @@ -1,23 +1,41 @@ package com.tamguo; import com.baomidou.mybatisplus.plugins.Page; +// 用于分页操作相关的类 + import com.tamguo.config.redis.CacheService; +// 自定义Redis缓存服务类 + import com.tamguo.dao.*; +// 多个数据访问层接口 + import com.tamguo.model.*; import com.tamguo.model.vo.QuestionVo; +// 实体类与题目相关视图对象类 + import com.xuxueli.crawler.XxlCrawler; import com.xuxueli.crawler.conf.XxlCrawlerConf; import com.xuxueli.crawler.loader.strategy.HtmlUnitPageLoader; import com.xuxueli.crawler.parser.PageParser; import com.xuxueli.crawler.rundata.RunData; import com.xuxueli.crawler.util.FileUtil; +// XxlCrawler框架相关类,用于爬虫及相关操作 + import org.apache.commons.lang3.StringUtils; +// 字符串操作工具类 + import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; +// 用于解析HTML文档的类 + import org.junit.Test; import org.junit.runner.RunWith; +// JUnit测试相关注解 + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +// Spring框架的依赖注入与属性值注入注解 + import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @@ -29,163 +47,181 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +// 基于Spring Boot和JUnit的测试类,用于爬取单个题目相关信息并处理存储 @RunWith(SpringRunner.class) @SpringBootTest public class SingleQuestionCrawler { - + + // 存储爬虫运行时数据 private RunData runData; + + // 自动注入爬取题目相关的数据访问层实例 @Autowired CrawlerQuestionMapper crawlerQuestionMapper; + // 章节数据访问层实例 @Autowired ChapterMapper chapterMapper; + // 课程数据访问层实例 @Autowired CourseMapper courseMapper; + // 学科数据访问层实例 @Autowired SubjectMapper subjectMapper; + // 缓存服务实例 @Autowired CacheService cacheService; + // 题目数据访问层实例 @Autowired QuestionMapper questionMapper; - + + // 文件编号格式模板 private static final String FILES_NO_FORMAT = "000000"; + // 文件路径前缀 private static final String FILES_PREFIX = "FP"; - @Value(value="${domain.name}") + // 注入配置文件中的域名属性值 + @Value(value = "${domain.name}") public String DOMAIN; - - @Test - public void crawlerSubject() throws Exception { - XxlCrawler crawler = new XxlCrawler.Builder() - .setAllowSpread(false) - .setThreadCount(20) - .setPageLoader(new HtmlUnitPageLoader()) - .setPageParser(new PageParser() { + + // 测试方法,实现题目爬取、处理及存储逻辑 + @Test + public void crawlerSubject() throws Exception { + // 构建XxlCrawler实例并配置相关参数 + XxlCrawler crawler = new XxlCrawler.Builder() + .setAllowSpread(false) // 不允许自动扩展爬取范围 + .setThreadCount(20) // 设置20个线程进行爬取 + .setPageLoader(new HtmlUnitPageLoader()) // 设置页面加载器 + .setPageParser(new PageParser() { // 设置页面解析逻辑 + @Override public void parse(Document html, Element pageVoElement, QuestionVo questionVo) { + // 根据题目URL查询爬取题目相关信息 CrawlerQuestionEntity condition = new CrawlerQuestionEntity(); - condition.setQuestionUrl(html.baseUri()); - CrawlerQuestionEntity crawlerQuestion = crawlerQuestionMapper.selectOne(condition); - ChapterEntity chapter = chapterMapper.selectById(crawlerQuestion.getChapterId()); - CourseEntity course = courseMapper.selectById(chapter.getCourseId()); - SubjectEntity subject = subjectMapper.selectById(course.getSubjectId()); - - QuestionEntity question = new QuestionEntity(); - question.setAnalysis(questionVo.getAnalysis()); - question.setAnswer(questionVo.getAnswer()); - question.setAuditStatus("1"); - question.setChapterId(chapter.getId()); - question.setContent(questionVo.getContent()); - question.setCourseId(course.getId()); - question.setPaperId(null); - question.setQuestionType("1"); - if(questionVo.getReviewPoint() != null && questionVo.getReviewPoint().size() > 0) { - question.setReviewPoint(StringUtils.join(questionVo.getReviewPoint().toArray(), ",")); - } - question.setScore(questionVo.getScore()); - question.setSubjectId(subject.getId()); - question.setYear(questionVo.getYear()); - - if (questionVo.getAnswerImages()!=null && questionVo.getAnswerImages().size() > 0) { - Set imagesSet = new HashSet(questionVo.getAnswerImages()); - for (String img: imagesSet) { - - // 下载图片文件 - String fileName = getFileName(img); - File dir = new File(getFilePath()); - if (!dir.exists()) - dir.mkdirs(); - boolean ret = FileUtil.downFile(img, XxlCrawlerConf.TIMEOUT_MILLIS_DEFAULT, getFilePath(), fileName); - System.out.println("down images " + (ret?"success":"fail") + ":" + img); - - // 替换URL - questionVo.setAnswer(questionVo.getAnswer().replace(img, DOMAIN + getFilePaths() + fileName)); - } - question.setAnswer(questionVo.getAnswer()); - } - - - if (questionVo.getAnalysisImages()!=null && questionVo.getAnalysisImages().size() > 0) { - Set imagesSet = new HashSet(questionVo.getAnalysisImages()); - for (String img: imagesSet) { - - // 下载图片文件 - String fileName = getFileName(img); - File dir = new File(getFilePath()); - if (!dir.exists()) - dir.mkdirs(); - boolean ret = FileUtil.downFile(img, XxlCrawlerConf.TIMEOUT_MILLIS_DEFAULT, getFilePath(), fileName); - System.out.println("down images " + (ret?"success":"fail") + ":" + img); - - // 替换URL - questionVo.setAnalysis(questionVo.getAnalysis().replace(img, DOMAIN + getFilePaths() + fileName)); - } - } - question.setAnalysis(questionVo.getAnalysis()); - - if (questionVo.getContentImages()!=null && questionVo.getContentImages().size() > 0) { - Set imagesSet = new HashSet(questionVo.getContentImages()); - for (String img: imagesSet) { - - // 下载图片文件 - String fileName = getFileName(img); - File dir = new File(getFilePath()); - if (!dir.exists()) - dir.mkdirs(); - boolean ret = FileUtil.downFile(img, XxlCrawlerConf.TIMEOUT_MILLIS_DEFAULT, getFilePath(), fileName); - System.out.println("down images " + (ret?"success":"fail") + ":" + img); - - // 替换URL - questionVo.setContent(questionVo.getContent().replace(img, DOMAIN + getFilePaths() + fileName)); - } - } - question.setContent(questionVo.getContent()); - questionMapper.insert(question); + condition.setQuestionUrl(html.baseUri()); + CrawlerQuestionEntity crawlerQuestion = crawlerQuestionMapper.selectOne(condition); + + // 获取题目所属章节、课程、学科等相关实体信息 + ChapterEntity chapter = chapterMapper.selectById(crawlerQuestion.getChapterId()); + CourseEntity course = courseMapper.selectById(chapter.getCourseId()); + SubjectEntity subject = subjectMapper.selectById(course.getSubjectId()); + + // 创建题目实体对象并设置各项属性 + QuestionEntity question = new QuestionEntity(); + question.setAnalysis(questionVo.getAnalysis()); + question.setAnswer(questionVo.getAnswer()); + question.setAuditStatus("1"); + question.setChapterId(chapter.getId()); + question.setContent(questionVo.getContent()); + question.setCourseId(course.getId()); + question.setPaperId(null); + question.setQuestionType("1"); + if (questionVo.getReviewPoint()!= null && questionVo.getReviewPoint().size() > 0) { + question.setReviewPoint(StringUtils.join(questionVo.getReviewPoint().toArray(), ",")); + } + question.setScore(questionVo.getScore()); + question.setSubjectId(subject.getId()); + question.setYear(questionVo.getYear()); + + // 处理答案图片(下载、替换URL) + if (questionVo.getAnswerImages()!= null && questionVo.getAnswerImages().size() > 0) { + Set imagesSet = new HashSet(questionVo.getAnswerImages()); + for (String img : imagesSet) { + String fileName = getFileName(img); + File dir = new File(getFilePath()); + if (!dir.exists()) { + dir.mkdirs(); + } + boolean ret = FileUtil.downFile(img, XxlCrawlerConf.TIMEOUT_MILLIS_DEFAULT, getFilePath(), fileName); + System.out.println("down images " + (ret? "success" : "fail") + ":" + img); + questionVo.setAnswer(questionVo.getAnswer().replace(img, DOMAIN + getFilePaths() + fileName)); + } + question.setAnswer(questionVo.getAnswer()); + } + + // 处理解析图片(下载、替换URL) + if (questionVo.getAnalysisImages()!= null && questionVo.getAnalysisImages().size() > 0) { + Set imagesSet = new HashSet(questionVo.getAnalysisImages()); + for (String img : imagesSet) { + String fileName = getFileName(img); + File dir = new File(getFilePath()); + if (!dir.exists()) { + dir.mkdirs(); + } + boolean ret = FileUtil.downFile(img, XxlCrawlerConf.TIMEOUT_MILLIS_DEFAULT, getFilePath(), fileName); + System.out.println("down images " + (ret? "success" : "fail") + ":" + img); + questionVo.setAnalysis(questionVo.getAnalysis().replace(img, DOMAIN + getFilePaths() + fileName)); + } + question.setAnalysis(questionVo.getAnalysis()); + } + + // 处理题目内容图片(下载、替换URL) + if (questionVo.getContentImages()!= null && questionVo.getContentImages().size() > 0) { + Set imagesSet = new HashSet(questionVo.getContentImages()); + for (String img : imagesSet) { + String fileName = getFileName(img); + File dir = new File(getFilePath()); + if (!dir.exists()) { + dir.mkdirs(); + } + boolean ret = FileUtil.downFile(img, XxlCrawlerConf.TIMEOUT_MILLIS_DEFAULT, getFilePath(), fileName); + System.out.println("down images " + (ret? "success" : "fail") + ":" + img); + questionVo.setContent(questionVo.getContent().replace(img, DOMAIN + getFilePaths() + fileName)); + } + question.setContent(questionVo.getContent()); + } + + // 将题目信息插入数据库 + questionMapper.insert(question); } - + + // 根据图片URL生成文件名 public String getFileName(String img) { - return getFileNo() + img.substring(img.lastIndexOf(".")); - } - - private String getFilePath() { - SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); - String format = sdf.format(new Date()); - return "/home/webdata/files/" + format + "/"; - } - - private String getFilePaths() { - SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); - String format = sdf.format(new Date()); - return "/files/" + format + "/"; - } - - private String getFileNo() { - SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); - String format = sdf.format(new Date()); - DecimalFormat df = new DecimalFormat(FILES_NO_FORMAT); - String key = FILES_PREFIX + format; - Long incr = cacheService.incr(key); - String avatorNo = FILES_PREFIX + df.format(incr); - return avatorNo; - } - - }).build(); - + return getFileNo() + img.substring(img.lastIndexOf(".")); + } + + // 获取文件存储路径(按日期生成) + private String getFilePath() { + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); + String format = sdf.format(new Date()); + return "/home/webdata/files/" + format + "/"; + } + + // 获取用于替换URL的文件路径(按日期生成) + private String getFilePaths() { + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); + String format = sdf.format(new Date()); + return "/files/" + format + "/"; + } + + // 生成文件编号 + private String getFileNo() { + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); + String format = sdf.format(new Date()); + DecimalFormat df = new DecimalFormat(FILES_NO_FORMAT); + String key = FILES_PREFIX + format; + Long incr = cacheService.incr(key); + String avatorNo = FILES_PREFIX + df.format(incr); + return avatorNo; + } + }).build(); + + // 获取爬虫运行数据对象 runData = crawler.getRunData(); int page = 1; int pageSize = 100; - while(true) { - Page questionPage = new Page(page , pageSize); + // 分页循环获取爬取题目列表,添加题目URL到待爬取列表 + while (true) { + Page questionPage = new Page(page, pageSize); List questionList = crawlerQuestionMapper.queryPageOrderUid(questionPage); - for(int i=0 ;i