Co-authored-by: p532chjow <3588709675@qq.com>
Co-committed-by: p532chjow <3588709675@qq.com>
develop
p532chjow 8 months ago committed by pbn32vpxk
parent d8eae693f8
commit 1cdae7ce03

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

@ -2,9 +2,6 @@
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>

@ -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-256null
*/
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-512null
*/
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();
}
}

@ -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 - 312^5 - 1
private long workerIdBits = 5L;
private long datacenterIdBits = 5L;
// 数据中心ID所占的位数同样定义为5位可表示范围也是0 - 312^5 - 1
private long maxWorkerId = -1L ^ (-1L << workerIdBits);
private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
// 序列号所占的位数这里定义为12位可表示范围是0 - 40952^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 suffixtruefalseID
* @return IdGensuffix
*/
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();
}
}
}

@ -24,93 +24,129 @@ import org.patchca.utils.encoder.EncoderHelper;
import org.patchca.word.RandomWordFactory;
/**
*
*
* Patchca
* @author ThinkGem
* @version 20171223
*/
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 LockingConfigurableCaptchaService
* 线线线使
*/
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
* ConfigurableCaptchaServicePNG
*
* @param outputStream HttpServletResponsePNG
* @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();
//
// }
}
// }
}

@ -6,9 +6,19 @@ import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
// ObjectUtil类继承自SerializeTranscoder类主要用于对象的序列化、反序列化以及对象相等性比较的相关操作
public class ObjectUtil extends SerializeTranscoder {
/**
*
* nullnull
*
* @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创建ObjectOutputStreamObjectOutputStream用于将对象转换为字节流进行序列化
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;
}
/**
*
* nullnullI/O
*
* @param in null
* @return nullnullnull
*/
@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创建ObjectInputStreamObjectInputStream用于从字节流中读取并还原对象
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
* nullfalse
* nullequals
*
* @param o1 null
* @param o2 null
* @return truefalse
*/
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);
}
}
}
}

@ -2,19 +2,45 @@ package com.tamguo.common.serialize;
import java.io.Closeable;
// SerializeTranscoder是一个抽象类用于定义对象序列化和反序列化相关操作的抽象方法同时提供了一个关闭资源的通用方法。
// 它作为一种抽象的规范,子类需要实现具体的序列化和反序列化逻辑,可用于在不同的序列化场景下进行统一的操作处理。
public abstract class SerializeTranscoder {
/**
*
* JavaJSON
*
* @param value
* @return
*/
public abstract byte[] serialize(Object value);
/**
*
*
*
* @param in
* @return
*/
public abstract Object deserialize(byte[] in);
/**
* Closeable使
*
*
* @param closeable Closeablenull
*/
public void close(Closeable closeable) {
if (closeable != null) {
// 首先判断传入的要关闭的资源对象是否为null如果为null则不需要进行关闭操作直接返回
if (closeable!= null) {
try {
// 调用Closeable接口定义的close方法来关闭资源释放相关的系统资源如文件句柄、网络连接等具体取决于资源类型
closeable.close();
} catch (Exception e) {
// 如果在关闭资源过程中出现任何异常(例如文件已被删除导致关闭流失败等情况),则打印异常堆栈信息,方便排查问题,
// 但不会将异常继续向上抛出,避免影响程序的整体运行逻辑(除非上层代码明确需要处理这种关闭资源的异常情况
e.printStackTrace();
}
}
}
}
}

@ -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.");
}
}
/**
* classStackTraceElement
*
* @param t
* @param fqnOfCallingClass
*
* @return
* classStackTraceElementThrowable
* StackTraceElement
* 访StackTraceElementcreateDefaultStackTrace
*
* @param t Throwable
* @param fqnOfCallingClass
* @return StackTraceElementStackTraceElement
*/
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线程中断异常或者InterruptedIOExceptionI/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) {
}
}
}

@ -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);
}
}
}

@ -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 bdatesmdatebdatesmdate
* @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";
/**
* 20101201
* 20101201
*/
public static String FORMAT_SHORT_CN = "yyyy年MM月dd";
/**
* 20101201 231506
* 20101201 231506
*/
public static String FORMAT_LONG_CN = "yyyy年MM月dd日 HH时mm分ss秒";
/**
*
* yyyyMMdd HHmmssSSS
*/
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
* 使
* nullnullSimpleDateFormat使
* formatnull
*
* @param date Datenew Date()
* @param pattern "yyyy-MM-dd""yyyy年MM月dd日"
* @return "yyyy-MM-dd""2024-12-15"datenull
*/
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
* 使197011000
* DateLong.parseLongDate
* 使SimpleDateFormatDate
*
* @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 Datenull
*/
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 Datenull
*/
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使addn
*
*
* @param date Date
* @param n 33-22
* @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
*
* addMonthCalendar使addn
*
*
* @param date Date
* @param n 55-11
* @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
* 44 "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`1000360024
* `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`1000360024
* `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());
}
}
}

@ -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;
}
}
}

@ -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
* 使loggerinfoSLF4J
* INFOLevel.INFOnull
*
* @param msg
* @param fqnOfCallingClass "com.example.MyClass"便
*/
public void info(String msg, String fqnOfCallingClass) {
logger.info(fqnOfCallingClass, Level.INFO, msg, null);
}
/**
* INFO
* ThrowableloggerinfoSLF4J
* INFOLevel.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
* 使loggererrorSLF4J
* ERRORLevel.ERRORnull
* 便
*
* @param msg
* @param fqnOfCallingClass "com.example.MyClass"便
*/
public void error(String msg, String fqnOfCallingClass) {
logger.error(fqnOfCallingClass, Level.ERROR, msg, null);
}
/**
* ERROR
* ThrowableloggererrorSLF4J
* ERRORLevel.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);
}
}
}

@ -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);
}
}
}

@ -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);
}
}

@ -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<String, Object> 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<String, Object> 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<String, Object> resultOfList(Object Obj, Long records, Long rowsOfPage, Object userData) {
Map<String, Object> result = new HashMap<String, Object>();
result.put("rows", Obj);
result.put("records", records);
result.put("total", rowsOfPage);
if (null != userData) {
if (null!= userData) {
result.put("userdata", userData);
}
;
return result;
}
/**
* `jqGrid``Map`
* `HashMap`
* - "list"`list``jqGrid`
* - "count"`totalCount`便`jqGrid`
* - "next"`currPage``totalPage`1`jqGrid`
* - "prev"11`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<String, Object> jqGridResult(List<?> list, long totalCount, int pageSize, int currPage,
int totalPage) {
int totalPage) {
Map<String, Object> result = new HashMap<String, Object>();
result.put("list", list);
result.put("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;
}
}
}

@ -1,5 +1,12 @@
package com.tamguo.common.utils;
// Status枚举用于定义一组表示某种状态的常量值在当前代码情境下大概率是用于表示某个操作比如文件上传、业务流程执行等相关操作的不同结果状态
// 通过使用枚举可以使代码更加清晰、易读,并且限定了合法的状态取值范围,避免使用随意的字符串或整数来表示状态而可能导致的混淆和错误。
public enum Status {
SUCCESS , ERROR
}
// 表示操作成功的状态值,通常在相关操作顺利完成、达到预期结果时使用该枚举常量来标记成功状态,方便在代码中统一判断和处理操作成功的情况。
SUCCESS,
// 表示操作出现错误的状态值,用于在操作执行过程中发生异常、未达到预期等失败情况时进行标识,便于后续根据该状态执行相应的错误处理逻辑、返回错误提示信息等操作。
ERROR
}

@ -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 */
// 阿里云相关服务的访问密钥IDAccess 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_";
}
}

@ -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<Integer> 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<Integer> getErrorKys() {
return errorKys;
}
// 设置文件上传过程中出现错误的相关键值列表当检测到文件上传出现错误并确定对应的错误标识编号后通过此方法将包含这些编号的ArrayList传入更新错误相关键值信息以便后续获取和分析。
public void setErrorKys(ArrayList<Integer> 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;
}
}

@ -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;
/**
* XMLDocument
* `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`XMLDocument
* `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 XMLXMLXML
* @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;
}
/**
* XMLXML`Document`DOM APIXML
*
* @return XMLXML`null`
*/
public Document getDocument() {
return document;
}
/**
* XML`document`使XML
*
* @param document XML`Document``document`
*/
protected void setDocument(Document document) {
this.document = document;
}
}
}

@ -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);
}
}
}

@ -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-PlusMetaObjectHandler
* MyBatis-Plus
* @ComponentSpring
*/
//@Component
public class MyMetaObjectHandler extends MetaObjectHandler {
// 创建一个Logger实例用于记录日志这里记录的日志类别是基于当前类MyMetaObjectHandler的方便在日志中定位相关操作记录
protected final static Logger logger = LoggerFactory.getLogger(MyMetaObjectHandler.class);
/**
* insertFill
*
* MetaObject
*
* @param metaObject MetaObject
* MetaObjectMyBatis便
*/
@Override
public void insertFill(MetaObject metaObject) {
logger.info("新增的时候干点不可描述的事情");
}
/**
* updateFill
*
* MetaObject
*
* @param metaObject MetaObjectinsertFill
*/
@Override
public void updateFill(MetaObject metaObject) {
logger.info("更新的时候干点不可描述的事情");
}
}
}

@ -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<br>
* http://mp.baomidou.com<br>
* PaginationInterceptorMyBatis-Plus
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
paginationInterceptor.setLocalPage(true);// 开启 PageHelper 的支持
// 设置开启PageHelper的支持以便能更好地与PageHelper等分页相关的工具兼容使用
paginationInterceptor.setLocalPage(true);
/*
* SQL <br>
* 1 cookie SQL <br>
* SQLSQL
*/
List<ISqlParser> sqlParserList = new ArrayList<ISqlParser>();
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();
}*/
}
}

@ -6,25 +6,40 @@ import com.baomidou.mybatisplus.annotations.TableId;
/**
*
* 便
*
*/
public class SuperEntity<T extends Model<?>> extends Model<T> {
// 用于定义类的序列化版本号,在进行对象序列化和反序列化时起到版本控制的作用,保证兼容性。
// 这里初始化为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;
/**
* ModelpkVal
* idMyBatis-Plus
*
*
* @return SerializableStringid
* StringSerializable
*/
@Override
protected Serializable pkVal() {
return this.getId();
}
// 获取id属性值的方法外部类可以通过调用该方法获取实体对象对应的主键值。
public String getId() {
return id;
}
// 设置id属性值的方法外部类可以通过调用该方法为实体对象设置主键值用于创建或更新实体对象时指定主键信息。
public void setId(String id) {
this.id = id;
}
}
}

@ -3,9 +3,16 @@ package com.tamguo.config.dao;
import com.baomidou.mybatisplus.mapper.BaseMapper;
/**
* mapper mp
* mapper
* Mapper便
* mpMyBatis-Plus
*
* MyBatis-Plus
*/
public interface SuperMapper<T> extends BaseMapper<T> {
// 这里可以放一些公共的方法
}
// 例如一些所有实体对应的Mapper都可能会用到的通用查询、通用更新逻辑等相关方法
// 具体的方法定义需要根据实际业务需求来添加,然后由具体继承该接口的子类去实现这些方法,
// 从而实现代码复用以及保证操作的一致性和规范性。
}

@ -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);
}
}
}
}

@ -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 + "]";
}
}
}

@ -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 + "]";
}
}
}

@ -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<RedisServerNodeBean> 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
* XMLPoolConfigBean
*/
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;
}
/**
* redisserver
* XMLRedisIPRedisServerNodeBeanRedis
*/
private List<RedisServerNodeBean> initRedisServerNodeBeans() {
List<RedisServerNodeBean> redisServers = new ArrayList<RedisServerNodeBean>();
// 通过文档对象查找所有名为"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
* RedisList<RedisServerNodeBean>ShardedJedisPoolJedisShardInfo
* JedisShardInfoRedisIP便使
*
* @param redisServers RedisinitRedisServerNodeBeans
* @return JedisShardInfoShardedJedisPoolRedis
*/
private List<JedisShardInfo> getJedisShardInfo(List<RedisServerNodeBean> 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<JedisShardInfo> servers = new ArrayList<JedisShardInfo>(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;
}
/*
* rediskey
* XMLRedispreKey便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
* ShardedJedisPoolShardedJedis便Redis
*
* @return ShardedJedisRedis
*/
public ShardedJedis getConnection() {
return shardedJedisPool.getResource();
}
/**
* jedis
* @param resource
* 使ShardedJedisShardedJedisPool便
*
* @param resource ShardedJedisgetConnection使
*/
public void closeConnection(ShardedJedis resource) {
resource.close();
}
}
}

@ -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 {
}
}
}
}
}

@ -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;
}
}
}

@ -1,8 +1,12 @@
package com.tamguo.dao;
import com.tamguo.config.dao.SuperMapper;
// 引入具体的实体类BookEntity表明该Mapper接口是针对BookEntity这个实体进行数据库操作相关的定义。
import com.tamguo.model.BookEntity;
// BookMapper接口继承自SuperMapper<BookEntity>SuperMapper是一个通用的Mapper父接口用于定义一些公共的方法或者规范方便复用代码和统一接口结构。
// 这里通过继承指定了该接口是针对BookEntity实体的Mapper意味着它可以使用SuperMapper中定义的通用方法并可以根据BookEntity的特点来扩展特定的数据库操作方法。
// 该接口主要用于定义与BookEntity实体对应的数据库操作方法例如常见的增删改查等操作具体的方法实现通常由MyBatis-Plus等相关框架根据接口定义自动生成或者由开发人员手动编写对应的XML映射文件等方式来完成。
public interface BookMapper extends SuperMapper<BookEntity> {
}
}

@ -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<ChapterEntity>{
// ChapterMapper接口继承自SuperMapper<ChapterEntity>意味着它可以继承SuperMapper中定义的通用数据库操作方法
// 同时也可以根据ChapterEntity实体的业务需求来扩展特定的、只适用于该实体的数据库操作方法用于与数据库中对应的章节Chapter相关的数据表进行交互。
public interface ChapterMapper extends SuperMapper<ChapterEntity> {
// 自定义的数据库查询方法,用于查询满足特定条件的记录数量。
// 该方法接收一个名为"uid"的参数(通过@Param注解进行参数命名绑定以便在对应的SQL语句中能准确引用这个参数参数类型为字符串用于表示用户的唯一标识具体含义取决于业务逻辑
// 返回值类型为Integer即返回满足条件的记录数量可能是某个用户相关的章节记录数量等具体取决于数据库表结构和业务需求对应的查询逻辑
Integer queryCount(@Param(value="uid")String uid);
}
}

@ -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<CourseEntity>{
// CourseMapper接口继承自SuperMapper<CourseEntity>SuperMapper是一个通用的Mapper父接口用于提供一些公共的数据库操作方法和规范方便复用代码以及统一接口结构。
// 这里通过继承指定了CourseMapper是针对CourseEntity实体的Mapper意味着它既可以使用SuperMapper中已有的通用方法也能够后续根据课程实体相关的业务需求扩展特定的数据库操作方法比如课程信息的新增、查询、更新、删除等与数据库交互的操作这些操作的具体实现通常由MyBatis-Plus等相关框架依据接口定义来生成或者由开发人员手动编写对应的SQL映射来完成。
public interface CourseMapper extends SuperMapper<CourseEntity> {
}
}

@ -1,8 +1,11 @@
package com.tamguo.dao;
import com.tamguo.config.dao.SuperMapper;
// 引入代表爬虫获取的书籍CrawlerBook相关的实体类CrawlerBookEntity这表明该Mapper接口主要用于操作数据库中与通过爬虫获取的书籍数据对应的表。
import com.tamguo.model.CrawlerBookEntity;
// CrawlerBookMapper接口继承自SuperMapper<CrawlerBookEntity>。SuperMapper作为一个通用的Mapper父接口定义了一些通用的数据库操作方法以及规范便于在不同实体对应的Mapper接口间实现代码复用并保证接口结构的统一性。
// 此处的CrawlerBookMapper通过继承关系明确了是针对CrawlerBookEntity这个实体的Mapper接口它既能利用SuperMapper中已有的通用操作逻辑例如基础的增删改查方法等又能依据爬虫书籍实体自身的业务特点比如可能有独特的字段、关联关系等后续扩展特定的、专门用于处理爬虫书籍数据的数据库操作方法。这些具体的数据库操作方法实现通常会借助如MyBatis-Plus框架按照该接口的定义来生成相应代码或者由开发人员手动编写对应的SQL映射语句等方式来完成进而实现与数据库中爬虫书籍相关数据表的交互操作满足业务上对于爬虫获取书籍数据的处理需求。
public interface CrawlerBookMapper extends SuperMapper<CrawlerBookEntity> {
}
}

@ -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<CrawlerChapterEntity>{
// CrawlerChapterMapper接口继承自SuperMapper<CrawlerChapterEntity>。其中SuperMapper是一个通用的Mapper父接口它旨在提供一些通用的数据库操作方法与规范以此达成代码复用以及统一接口结构的目的。
// 本接口通过继承SuperMapper<CrawlerChapterEntity>明确了自身是针对CrawlerChapterEntity这个实体的Mapper接口。这样一来它既能够使用SuperMapper中已经定义好的通用数据库操作方法像常规的增、删、改、查等基础操作又可以根据爬虫章节实体自身特有的业务需求例如章节内容来源、特殊的关联关系等情况后续去扩展那些专门用于处理爬虫章节数据的特定数据库操作方法。而这些数据库操作方法的具体实现往往会借助如MyBatis-Plus这类框架按照该接口所定义的内容来生成相应代码或者由开发人员手动编写对应的SQL映射语句等方式来达成进而实现与数据库里爬虫章节相关数据表进行交互以满足业务中针对爬虫获取章节数据的处理要求。
public interface CrawlerChapterMapper extends SuperMapper<CrawlerChapterEntity> {
}
}

@ -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<CrawlerPaperEntity>{
// CrawlerPaperMapper接口继承自SuperMapper<CrawlerPaperEntity>SuperMapper是通用的Mapper父接口提供了一些通用的数据库操作方法和规范这里在此基础上扩展针对爬虫试卷实体的特定操作方法。
public interface CrawlerPaperMapper extends SuperMapper<CrawlerPaperEntity> {
// 使用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<CrawlerPaperEntity> selectPageByAreaId(Page<CrawlerPaperEntity> questionPage,@Param(value="areaId") String areaId);
// 定义一个方法selectPageByAreaId用于执行上述SQL语句所定义的分页查询操作返回一个包含CrawlerPaperEntity实体的列表。
// 该方法接收两个参数一个是Page<CrawlerPaperEntity>类型的questionPage表示分页相关的信息如页码、每页记录数等用于实现分页功能
// 另一个是通过@Param注解命名为"areaId"的字符串类型参数用于在SQL语句中作为条件进行数据筛选代表区域的唯一标识具体含义取决于业务逻辑
List<CrawlerPaperEntity> selectPageByAreaId(Page<CrawlerPaperEntity> questionPage, @Param(value="areaId") String areaId);
}
}

@ -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<CrawlerQuestionEntity>{
// CrawlerQuestionMapper接口继承自SuperMapper<CrawlerQuestionEntity>SuperMapper作为通用的Mapper父接口定义了一些通用的数据库操作方法和规范方便复用代码以及统一接口结构。
// 这里的CrawlerQuestionMapper在继承的基础上可利用通用方法并能根据爬虫题目实体的业务特点来扩展特定的数据库操作方法用于与数据库中对应的题目数据表进行交互。
public interface CrawlerQuestionMapper extends SuperMapper<CrawlerQuestionEntity> {
// 自定义的数据库查询方法用于按照用户唯一标识uid具体含义根据业务逻辑确定进行分页查询操作返回一个包含CrawlerQuestionEntity实体的列表。
// 该方法接收一个Pagination类型的参数page这个参数包含了分页相关的详细信息例如当前页码、每页显示的记录数量等用于控制分页查询的具体行为以获取对应页面范围内的题目数据记录。
List<CrawlerQuestionEntity> queryPageOrderUid(Pagination page);
}
}

@ -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.
* MapperBaseMapper<QuestionOptionsEntity>MyBatis-Plus
* 使SQL便
*
*/
public interface IQuestionOptionsMapper extends BaseMapper<QuestionOptionsEntity> {
}
}

@ -1,8 +1,11 @@
package com.tamguo.dao;
import com.tamguo.config.dao.SuperMapper;
// 引入代表试卷Paper相关的实体类PaperEntity表明这个Mapper接口主要用于操作数据库中与试卷数据对应的表进行如试卷信息的查询、新增、修改、删除等数据库交互操作。
import com.tamguo.model.PaperEntity;
// PaperMapper接口继承自SuperMapper<PaperEntity>。SuperMapper是一个通用的Mapper父接口其定义了一些通用的数据库操作方法与规范方便实现代码复用以及统一接口结构。
// 此处的PaperMapper通过继承关系明确了是针对PaperEntity实体的Mapper接口。这意味着它既能运用SuperMapper中已有的通用操作逻辑例如基础的增删改查方法等又能依据试卷实体自身的业务特点比如试卷包含的题目、所属学科等相关属性情况后续去扩展那些专门用于处理试卷数据的特定数据库操作方法。这些具体的数据库操作方法实现通常会借助如MyBatis-Plus框架按照该接口的定义来生成相应代码或者由开发人员手动编写对应的SQL映射语句等方式来完成进而实现与数据库中试卷相关数据表的交互操作满足业务上对于试卷数据处理的需求。
public interface PaperMapper extends SuperMapper<PaperEntity> {
}
}

@ -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<QuestionEntity>{
// QuestionMapper接口继承自SuperMapper<QuestionEntity>。SuperMapper是一个通用的Mapper父接口它定义了一系列通用的数据库操作方法和规范其目的在于方便代码复用以及让接口结构更加统一。
// 本接口通过继承SuperMapper<QuestionEntity>确定了自身是针对QuestionEntity这个实体的Mapper接口。如此一来它既能使用SuperMapper里已有的通用数据库操作方法像常规的新增记录、删除记录、修改记录以及查询记录这些基础操作又可以依据题目实体自身特有的业务需求例如题目所属的学科分类、难度级别等相关情况后续去扩展那些专门用于处理题目数据的特定数据库操作方法。而这些数据库操作方法的具体实现往往会借助如MyBatis-Plus之类的框架按照该接口所定义的内容来生成相应代码或者由开发人员手动编写对应的SQL映射语句等方式来达成进而实现与数据库里题目相关数据表进行交互以满足业务中针对题目数据处理的要求。
public interface QuestionMapper extends SuperMapper<QuestionEntity> {
}
}

@ -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<SubjectEntity>{
// SubjectMapper接口继承自SuperMapper<SubjectEntity>意味着它可以继承SuperMapper中定义的通用数据库操作方法
// 同时也可以根据SubjectEntity实体的业务需求来扩展特定的、只适用于该实体的数据库操作方法用于与数据库中对应的学科数据表进行交互。
public interface SubjectMapper extends SuperMapper<SubjectEntity> {
// 自定义的数据库查询方法,用于根据学科名称查找对应的学科实体信息。
// 该方法接收一个名为"name"的参数(通过@Param注解进行参数命名绑定以便在对应的SQL语句中能准确引用这个参数参数类型为字符串代表要查找的学科的名称具体含义取决于业务逻辑
// 返回值类型为SubjectEntity即如果查询到符合条件的学科记录则返回对应的学科实体对象若未找到则可能返回null具体取决于数据库查询结果以及相关的映射配置情况
SubjectEntity findByName(@Param(value="name")String name);
}
}

@ -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<BookEntity> 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;
}

@ -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<ChapterEntity> 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;
}
}

@ -9,63 +9,90 @@ import com.tamguo.config.dao.SuperEntity;
*
*/
@TableName(value="tiku_course")
public class CourseEntity extends SuperEntity<CourseEntity> 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<CourseEntity> 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;
}

@ -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<CrawlerBookEntity> 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;
}

@ -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<CrawlerChapterEntity>{
// 使用MyBatis-Plus的@TableName注解来指定该实体类对应的数据库表名表明CrawlerChapterEntity类与名为"crawler_chapter"的数据库表存在映射关系后续框架如MyBatis-Plus会依据此映射进行数据的持久化操作例如将实体对象保存到该表中或者从表中查询数据并转换为该实体对象等。
@TableName(value = "crawler_chapter")
public class CrawlerChapterEntity extends SuperEntity<CrawlerChapterEntity> {
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;
}
}
}

@ -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<CrawlerChapterEntity>{
// 使用MyBatis-Plus的@TableName注解来指定该实体类对应的数据库表名表明CrawlerPaperEntity类与名为"crawler_paper"的数据库表存在映射关系后续框架如MyBatis-Plus会依据这个映射关系进行数据的持久化操作比如将实体对象保存到该表中或者从表中查询数据并转换为该实体对象等操作。
@TableName(value = "crawler_paper")
public class CrawlerPaperEntity extends SuperEntity<CrawlerChapterEntity> {
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;
}
}
}

@ -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<CrawlerQuestionEntity> 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;
}

@ -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<PaperEntity> 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;
}
}
// 设置试卷是否免费相关信息的方法,传入一个新的表示免费相关信息的字符串,更新当前对象中存储的该属性值。
// 常用于修改试卷免费与否状态的业务场景,比如将原本收费的试卷设置为免费,或者反之等情况,通过该方法来更新对应的属性信息。
public void setFree(String free) {
this.free = free;
}

@ -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<QuestionEntity> 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;
}
}

@ -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;
}
}
}

@ -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<SchoolEntity> 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;
}
}

@ -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<SubjectEntity> 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;
}
}
}

@ -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;
}
}

@ -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();
}
}
}

@ -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();
}
}
}

@ -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();
}
}
}

@ -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 BootJUnit
*
* @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();
}
}
}

@ -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 BootJUnit
*
* @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<QuestionEntity> page = new Page<QuestionEntity>(current , size);
Page<QuestionEntity> entitys = iQuestionService.selectPage(page , Condition.create().orderAsc(Arrays.asList("id")));
if(entitys.getCurrent() > 759) {
// 使用while循环来实现对所有题目数据的分页遍历处理只要满足循环条件就会一直循环下去直到处理完所有符合条件的数据为止。
while (true) {
// 创建一个Page对象用于分页查询传入当前页码current和每页数据量size作为参数
// 这样就可以通过后续的服务层方法根据这个分页设置去数据库中获取相应的数据。
Page<QuestionEntity> page = new Page<QuestionEntity>(current, size);
// 调用iQuestionService的selectPage方法进行分页查询传入构建好的分页对象page以及查询条件对象。
// 查询条件通过Condition.create().orderAsc(Arrays.asList("id"))构建,意思是按照题目实体类中的"id"字段进行升序排序来获取题目数据,
// 并将查询结果封装到另一个Page对象entitys中该对象包含了当前页的题目数据以及分页相关的其他信息如总页数、总记录数等
Page<QuestionEntity> entitys = iQuestionService.selectPage(page, Condition.create().orderAsc(Arrays.asList("id")));
// 判断当前页码是否大于759如果大于则表示已经处理完了计划处理的数据量这里759可能是根据总数据量和每页数量估算出来的一个边界值具体要结合实际情况
// 如果满足这个条件就跳出while循环结束数据处理流程。
if (entitys.getCurrent() > 759) {
break;
}
// 处理数据
for(int i=0 ; i<entitys.getSize() ; i++) {
// 遍历当前页获取到的题目数据列表,开始对每条题目数据进行处理,这里通过循环依次获取每条题目记录进行相关操作。
for (int i = 0; i < entitys.getSize(); i++) {
// 从当前页的题目数据列表中获取第i条题目记录封装为QuestionEntity对象方便后续对该条题目各个属性进行操作。
QuestionEntity question = entitys.getRecords().get(i);
// 对题目解析内容analysis属性进行字符串替换操作将其中的"files/"替换为"/files/"
// 可能是为了统一文件路径的格式,确保文件能够正确被访问等业务需求,具体取决于实际的文件存储和访问逻辑。
question.setAnalysis(question.getAnalysis().replaceAll("files/", "/files/"));
// 对题目内容content属性也进行同样的字符串替换操作将"files/"替换为"/files/",理由与上述对题目解析的处理类似,都是为了规范文件路径格式。
question.setContent(question.getContent().replaceAll("files/", "/files/"));
if(question.getAnswer() == null) {
// 判断题目答案answer属性是否为null如果是null则将其设置为空字符串
// 这样可以避免后续对null值进行字符串操作时可能出现的空指针异常等问题保证数据的完整性和后续处理的一致性。
if (question.getAnswer() == null) {
question.setAnswer("");
}
// 对题目答案answer属性同样进行字符串替换操作将其中的"files/"替换为"/files/",目的也是规范文件路径相关的字符串内容。
question.setAnswer(question.getAnswer().replaceAll("files/", "/files/"));
}
// 调用iQuestionService的updateAllColumnBatchById方法将处理后的当前页所有题目记录批量更新到数据库中
// 确保对题目数据所做的修改(文件路径字符串替换等操作)能够持久化保存到数据库里,实现数据的更新。
iQuestionService.updateAllColumnBatchById(entitys.getRecords());
// 将当前页码加1准备处理下一页的数据实现分页数据的依次处理不断循环这个过程直到处理完所有符合条件的数据页。
current = current + 1;
// 在控制台打印当前页码信息,方便在程序运行过程中查看数据处理的进度情况,便于调试和监控整个处理流程。
System.out.println("当前Current" + current);
}
}
}
}

@ -1,94 +1,181 @@
package com.tamguo;
import com.xuxueli.crawler.loader.strategy.HtmlUnitPageLoader;
// 引入XxlCrawler框架中的HtmlUnitPageLoader类用于加载HTML页面它基于HtmlUnit库实现能够模拟浏览器行为来获取网页内容是进行网页爬取的重要组件。
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
// 引入Jsoup库中的Document和Element类Jsoup用于解析HTML文档Document代表整个HTML文档Element则用于表示HTML文档中的元素如标签等
// 在网页爬取后解析页面内容时会用到这两个类来提取和操作相关数据。
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实例到标注该注解的变量中
// 方便在代码中直接使用对应的组件在这里就是将相关的数据访问层Mapper接口的实现类实例注入进来。
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 com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
// 引入阿里巴巴的FastJSON库中的JSONArray和JSONObject类用于处理JSON格式的数据方便在代码中进行JSON数据的构建、解析以及操作
// 例如将对象转换为JSON字符串或者从JSON字符串解析出对象等在这里用于对爬取到的试卷相关数据进行处理和存储。
import com.tamguo.dao.CrawlerPaperMapper;
import com.tamguo.dao.PaperMapper;
// 引入自定义的数据库访问层接口CrawlerPaperMapper和PaperMapper从名称推测它们分别定义了与爬取试卷信息以及试卷基本信息相关的数据库操作方法
// 如插入、查询等操作,在代码中会通过依赖注入获取其实现类实例来与数据库进行交互,实现数据的持久化。
import com.tamguo.model.CrawlerPaperEntity;
import com.tamguo.model.PaperEntity;
// 引入自定义的实体类CrawlerPaperEntity和PaperEntity分别对应数据库中与爬取试卷相关表以及试卷基本信息表的实体对象
// 包含了各自表中字段对应的属性,用于在代码中承载和传递相关数据,方便进行数据的处理和存储操作。
import com.tamguo.model.enums.QuestionType;
// 引入自定义的枚举类QuestionType从名称推测它定义了不同类型的题目类型枚举值用于在代码中明确区分和表示题目所属的类型便于业务逻辑处理。
import com.tamguo.model.vo.PaperVo;
// 引入自定义的视图对象类PaperVo它可能是用于在网页爬取过程中临时封装和传递试卷相关数据的对象包含了如试卷名称、题目信息、URL等属性
// 方便在解析网页和后续数据处理过程中进行数据的传递和操作。
import com.xuxueli.crawler.XxlCrawler;
import com.xuxueli.crawler.parser.PageParser;
import com.xuxueli.crawler.rundata.RunData;
// 引入XxlCrawler框架中的核心类XxlCrawler用于构建和配置爬虫实例PageParser用于定义如何解析爬取到的页面内容RunData用于管理爬虫运行时的数据
// 例如记录已访问的URL、待访问的URL等这些类协同工作来实现网页爬取及相关数据处理的功能。
// 北京模拟试卷,真题试卷已经爬取完毕
// 该类的注释表示北京模拟试卷、真题试卷已经爬取完毕但从代码功能看可能此处在进行其他类型试卷从START_URL及相关逻辑推测的爬取及处理操作
// 不过具体还需结合整体业务情况确定。此为一个基于Spring Boot和JUnit的测试类主要功能是利用XxlCrawler框架进行试卷相关信息的爬取并将数据持久化到数据库中。
@RunWith(SpringRunner.class)
@SpringBootTest
public class PaperCrawler {
// 高考
// 定义高考的学科ID从代码中看它是一个固定的标识字符串用于区分不同学科这里表示高考相关的学科具体对应关系取决于业务逻辑设定。
private final String SUBJECT_ID = "gaokao";
// 科目
// 定义具体的科目ID这里是“生物”科目同样是一个固定标识用于准确标识所爬取试卷所属的具体科目便于后续数据分类、存储等操作。
private final String COURSE_ID = "shengwu";
// 定义区域ID这里的代码注释列出了多个地区的区域ID示例当前设置的是“江西360000”的区域ID用于表明试卷所属的地理区域
// 在数据库存储以及后续按区域筛选、统计试卷等业务场景中会用到该标识。
// 110000 北京 | 310000 上海 | 500000 重庆 | 120000 天津 | 370000 山东 | 410000 河南 | 420000 湖北 | 320000 江苏 | 330000 浙江
// 140000 山西 | 350000 福建 | 340000 安徽 | 220000 吉林 | 150000 内蒙古 | 640000 宁夏 | 650000 新疆 | 广西 450000 | 210000 辽宁
// 230000 黑龙江 | 610000 陕西 | 360000 江西 | 440000 广东 | 430000 湖南 | 460000 海南 | 530000 云南 | 510000 四川 | 630000 青海
// 620000 甘肃 | 130000 河北 | 540000 西藏 | 贵州 520000
private final String AREA_ID = "360000";
// 年份
// 定义年份固定为“2016”用于标识试卷对应的年份在按年份对试卷进行分类、查询以及数据统计等业务场景中会用到该属性。
private final String YEAR = "2016";
// 真题试卷 类型(1:真题试卷,2:模拟试卷,3:押题预测,4:名校精品)
// 定义试卷类型这里设置为“4”根据代码注释中的类型说明1:真题试卷,2:模拟试卷,3:押题预测,4:名校精品),
// 表示当前要爬取和处理的是名校精品类型的试卷,方便后续根据试卷类型进行分类展示、筛选等操作。
private final String PAPER_TYPE = "4";
// 开始采集的URL
// 定义开始采集的URL即爬虫开始爬取试卷信息的初始网页地址这里指向百度题库中特定的试卷列表页面
// 爬虫会从这个URL出发按照设定的规则去获取相关试卷的详细信息以及其他关联数据。
private final String START_URL = "https://tiku.baidu.com/tikupc/paperlist/1bfd700abb68a98271fefa04-20-7-2016-1360-1-download";
// 用于存储爬虫运行时的数据对象通过XxlCrawler框架管理爬虫在运行过程中的各种相关数据如已访问的URL、待访问的URL等
// 在后续的爬虫启动以及数据处理过程中会不断更新和使用这个对象中的数据。
private RunData runData;
// 使用@Autowired注解自动注入PaperMapper接口的实现类实例用于操作试卷基本信息相关的数据库操作
// 比如将爬取到并处理好的试卷基本信息插入到数据库对应的表中,实现数据持久化存储。
@Autowired
private PaperMapper paperMapper;
// 使用@Autowired注解自动注入CrawlerPaperMapper接口的实现类实例用于处理与爬取试卷相关的数据持久化操作
// 例如将爬取过程中涉及的试卷与题目URL等关联信息插入到对应的数据库表中保存爬取过程中的详细数据。
@Autowired
private CrawlerPaperMapper crawlerPaperMapper;
// 使用@Test注解标记的测试方法名为crawler该方法实现了利用XxlCrawler框架进行试卷信息爬取以及将爬取到的数据存储到数据库的完整逻辑。
@Test
public void crawler() {
// 创建一个XxlCrawler实例的构建器通过链式调用一系列方法来配置爬虫的各项参数和行为后续调用build方法即可构建出最终的爬虫实例。
XxlCrawler crawler = new XxlCrawler.Builder()
.setUrls(START_URL)
.setAllowSpread(false)
.setFailRetryCount(5)
.setThreadCount(1)
.setPageLoader(new HtmlUnitPageLoader())
.setPageParser(new PageParser<PaperVo>() {
@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<paperVo.getQuestionInfoTypes().size() ; i++) {
JSONObject entity = new JSONObject();
entity.put("id", i+1);
entity.put("name", paperVo.getQuestionInfoTypes().get(i));
entity.put("title", paperVo.getQuestionInfoTitles().get(i));
QuestionType questionType = QuestionType.getQuestionType(paperVo.getQuestionInfoTypes().get(i));
entity.put("type", questionType.getValue());
// 设置爬虫要爬取的URL列表这里只传入了START_URL表示从这个初始URL开始进行试卷相关信息的爬取
// 如果需要爬取多个初始URL可以传入包含多个URL的集合等数据结构具体根据框架要求
.setUrls(START_URL)
// 设置是否允许爬虫自动扩展爬取链接设置为false表示只按照设置的初始URL进行爬取不会根据页面中的链接自动扩展爬取范围
// 如果设置为true则爬虫会自动根据页面中的超链接等继续去爬取更多相关页面需根据具体业务需求谨慎设置避免过度爬取
.setAllowSpread(false)
// 设置当爬取某个页面失败时的重试次数这里设置为5次表示如果在爬取某个URL对应的页面出现失败情况会尝试重新爬取最多5次
// 以此来提高爬取成功率,确保尽可能获取到完整的试卷信息。
.setFailRetryCount(5)
// 设置爬虫运行时使用的线程数量这里设置为1表示采用单线程方式进行爬取可根据实际情况调整线程数量来提高爬取效率
// 但要注意线程数量过多可能会给目标网站带来较大压力以及可能遇到反爬虫限制等问题。
.setThreadCount(1)
// 设置页面加载器为HtmlUnitPageLoader即使用基于HtmlUnit的方式来加载网页内容模拟浏览器行为获取页面的HTML文档
// 以便后续进行页面解析和数据提取操作。
.setPageLoader(new HtmlUnitPageLoader())
// 设置页面解析器通过匿名内部类实现PageParser接口并重写parse方法来定义如何解析爬取到的页面内容
// 根据页面内容的不同情况如是否是试卷详情页、试卷列表页等进行相应的数据提取和处理操作下面的parse方法中就是具体的解析逻辑实现。
.setPageParser(new PageParser<PaperVo>() {
@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);
}

@ -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<QuestionVo>() {
@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("<p> <span> 略 </span> <br> </p>");
}
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<String> imagesSet = new HashSet<String>(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<String> imagesSet = new HashSet<String>(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<String> imagesSet = new HashSet<String>(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<CrawlerPaperEntity> questionPage = new Page<CrawlerPaperEntity>(page , pageSize);
List<CrawlerPaperEntity> questionList = crawlerPaperMapper.selectPage(questionPage, Condition.create().orderAsc(Arrays.asList("paper_id" , "queindex")));
for(int i=0 ;i<questionList.size() ; i++) {
runData.addUrl(questionList.get(i).getQuestionUrl());
}
page++;
if(questionList.size() < 1000) {
break;
// 设置是否允许爬虫自动根据页面中的链接扩展爬取范围设置为false表示仅按照预先设定的初始URL进行爬取不会主动去爬取页面中发现的其他链接指向的页面
// 若设为true则爬虫会自动依据页面内的超链接等去爬取更多相关页面但需谨慎使用以免出现过度爬取或不符合预期的情况。
.setAllowSpread(false)
// 设置爬虫运行时使用的线程数量这里配置为1表示采用单线程方式来执行爬取操作可根据实际情况如目标网站的负载能力、爬取效率需求等调整线程数量
// 不过线程过多可能会给目标网站带来较大压力,甚至触发反爬虫机制等问题。
.setThreadCount(1)
// 设置当爬取某个页面失败时的重试次数此处设定为5次意味着如果在尝试爬取某个URL对应的页面出现失败情况时爬虫会自动再次尝试爬取该页面最多重试5次
// 这样能在一定程度上提高爬取的成功率,尽量保证可以完整获取到需要的题目相关信息。
.setFailRetryCount(5)
// 设置页面加载器为HtmlUnitPageLoader即利用基于HtmlUnit的方式来加载网页内容它能够模拟浏览器的行为获取页面的HTML文档
// 以便后续可以顺利地对获取到的页面进行解析以及提取相关数据等操作。
.setPageLoader(new HtmlUnitPageLoader())
// 设置页面解析器通过匿名内部类实现PageParser接口并重新实现parse方法以此来定义针对爬取到的页面内容具体的解析逻辑
// 根据页面的不同情况如包含的元素、文本内容等进行相应的数据提取、整理以及后续处理操作下面的parse方法内就是具体的解析逻辑实现部分。
.setPageParser(new PageParser<QuestionVo>() {
@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对象用于构建查询数据库的条件这里以题目所在页面的URLhtml.baseUri())作为关键条件,
// 目的是通过这个URL去数据库中查找对应的爬取试卷相关的实体信息以便后续建立题目与试卷之间的关联关系。
CrawlerPaperEntity condition = new CrawlerPaperEntity();
condition.setQuestionUrl(html.baseUri());
// 在控制台打印当前页面的URL可能是为了方便在爬取过程中查看正在处理的页面情况有助于调试和监控爬取的进度以及了解数据来源等情况。
System.out.println(html.baseUri());
// 使用注入的crawlerPaperMapperCrawlerPaperMapper接口的实现类实例的selectOne方法根据前面构建的查询条件condition
// 从数据库中查询并获取对应的CrawlerPaperEntity对象也就是得到该题目所属的爬取试卷的相关详细信息。
CrawlerPaperEntity crawlerPaper = crawlerPaperMapper.selectOne(condition);
// 通过注入的paperMapperPaperMapper接口的实现类实例的selectById方法依据从前面获取到的爬取试卷的ID从crawlerPaper对象中获取
// 从数据库中查询出对应的PaperEntity对象从而得到完整的试卷信息为后续设置题目与试卷相关的属性做准备。
PaperEntity paper = paperMapper.selectById(crawlerPaper.getPaperId());
// 同样地利用注入的courseMapperCourseMapper接口的实现类实例的selectById方法按照试卷所属的课程ID从paper对象中获取
// 从数据库中查询出对应的CourseEntity对象获取题目所属的课程相关信息用于后续准确设置题目实体中的课程属性。
CourseEntity course = courseMapper.selectById(paper.getCourseId());
// 按照类似的逻辑使用注入的subjectMapperSubjectMapper接口的实现类实例的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对象的相应属性如果获取到的解析内容为空字符串
// 则设置一个默认的简略解析内容(<p> <span> 略 </span> <br> </p>),以保证题目解析信息在数据库中具有一定的完整性。
question.setAnalysis(questionVo.getAnalysis());
if (StringUtils.isEmpty(question.getAnalysis())) {
question.setAnalysis("<p> <span> 略 </span> <br> </p>");
}
// 设置题目答案直接从题目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<String> imagesSet = new HashSet<String>(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方法将题目答案中原来的图片URLimg
// 替换为新的格式(/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<String> imagesSet = new HashSet<String>(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<String> imagesSet = new HashSet<String>(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将当前爬取页面的URLhtml.baseUri()赋值给题目实体的来源URL属性记录题目图片最初是从哪个页面获取的
// 有助于数据溯源以及在需要查看原始图片来源等情况下使用,保证数据的完整性和可追溯性。
question.setSourceUrl(html.baseUri());
// 使用注入的questionMapperQuestionMapper接口的实现类实例的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/”目录下并结合课程IDCOURSE_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<CrawlerPaperEntity> questionPage = new Page<CrawlerPaperEntity>(page, pageSize);
// 使用注入的crawlerPaperMapperCrawlerPaperMapper接口的实现类实例的selectPage方法进行分页查询操作
// 传入前面构建的分页参数对象questionPage以及查询条件通过Condition.create().orderAsc(Arrays.asList("paper_id", "queindex"))构建),
// 这里构建的查询条件表示按照“paper_id”和“queindex”字段升序排列进行查询获取符合条件的CrawlerPaperEntity类型的数据列表
// 也就是获取到一批与爬取试卷题目相关的实体数据便于后续对这些题目数据进行进一步的处理如添加URL到待爬取列表等操作
List<CrawlerPaperEntity> 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);
}
}

@ -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();
}
}
}

@ -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<QuestionVo>() {
// 测试方法,实现题目爬取、处理及存储逻辑
@Test
public void crawlerSubject() throws Exception {
// 构建XxlCrawler实例并配置相关参数
XxlCrawler crawler = new XxlCrawler.Builder()
.setAllowSpread(false) // 不允许自动扩展爬取范围
.setThreadCount(20) // 设置20个线程进行爬取
.setPageLoader(new HtmlUnitPageLoader()) // 设置页面加载器
.setPageParser(new PageParser<QuestionVo>() { // 设置页面解析逻辑
@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<String> imagesSet = new HashSet<String>(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<String> imagesSet = new HashSet<String>(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<String> imagesSet = new HashSet<String>(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<String> imagesSet = new HashSet<String>(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<String> imagesSet = new HashSet<String>(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<String> imagesSet = new HashSet<String>(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<CrawlerQuestionEntity> questionPage = new Page<CrawlerQuestionEntity>(page , pageSize);
// 分页循环获取爬取题目列表添加题目URL到待爬取列表
while (true) {
Page<CrawlerQuestionEntity> questionPage = new Page<CrawlerQuestionEntity>(page, pageSize);
List<CrawlerQuestionEntity> questionList = crawlerQuestionMapper.queryPageOrderUid(questionPage);
for(int i=0 ;i<questionList.size() ; i++) {
for (int i = 0; i < questionList.size(); i++) {
runData.addUrl(questionList.get(i).getQuestionUrl());
}
page++;
if(questionList.size() < 100) {
if (questionList.size() < 100) {
break;
}
}
crawler.start(true);
}
}
// 启动爬虫开始爬取
crawler.start(true);
}
}

@ -1,23 +1,52 @@
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管理的组件。
// 在本代码中就是利用该注解将实现ISubjectService接口的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 com.tamguo.service.ISubjectService;
// 引入自定义的服务层接口ISubjectService从接口名称推测它应该定义了与学科Subject相关的一系列业务操作方法
// 例如学科数据的获取、学科信息的更新或者学科相关资源的爬取等操作。在本测试类中,会通过依赖注入获取该接口的实现类实例,
// 进而调用对应的业务方法来验证相关业务逻辑是否正确执行。
// SubjectCrawler类是一个基于Spring Boot和JUnit框架构建的测试类其核心功能在于对学科相关业务逻辑进行测试
// 具体而言是通过调用ISubjectService接口实现类中的方法此处为crawlerSubject方法来测试学科数据爬取相关的业务逻辑是否符合预期。
@RunWith(SpringRunner.class)
@SpringBootTest
public class SubjectCrawler {
@Autowired
ISubjectService iSubjectService;
// 使用@Autowired注解让Spring容器自动查找并注入一个实现了ISubjectService接口的Bean实例到iSubjectService变量中。
// 如此一来后续在测试方法里就能直接利用这个服务实例去调用和学科相关的业务方法了例如在这个类中调用的crawlerSubject方法。
@Autowired
ISubjectService iSubjectService;
// 使用@Test注解标记的测试方法名为crawlerSubject该方法主要用于测试学科数据爬取相关的具体业务逻辑。
// 方法声明抛出Exception异常表示如果在方法执行过程中出现了未被处理的异常情况JUnit会将此次测试判定为失败
// 并输出相应的异常信息,方便开发人员排查问题所在,进而分析和修复可能存在的代码错误或者业务逻辑问题。
@Test
public void crawlerSubject() throws Exception {
// 调用iSubjectService实例的crawlerSubject方法该方法的具体实现应该位于ISubjectService接口的实现类中。
// 从方法名推测,其主要功能是执行实际的学科数据爬取操作,比如从网络上抓取学科相关的信息等,
// 通过在这里调用该方法来检验与之相关的业务逻辑能否按照预期正常执行,确保学科数据爬取功能的正确性和稳定性。
iSubjectService.crawlerSubject();
}
}
}
Loading…
Cancel
Save