主要技术栈
Java
Mysql
SpringBoot
Tomcat
HTML CSS JavaScript
该课设必备环境配置教程:(参考给出的链接和给出的关键链接)
JAVA课设必备环境配置 教程 JDK Tomcat配置 IDEA开发环境配置 项目部署参考视频 若依框架 链接数据库格式注意事项
在Java中连接MySQL数据库
application配置文件
# 项目相关配置
ruoyi:
# 名称
name: 2022项目实践题库管理系统
# 版本
version: 4.7.3
# 版权年份
copyrightYear: 2022
# 实例演示开关
demoEnabled: false
# 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
profile: D:/ruoyi/uploadPath
# 获取ip地址开关
addressEnabled: false
# 开发环境配置
server:
# 服务器的HTTP端口,默认为80
port: 801
servlet:
# 应用的访问路径
context-path: /
tomcat:
# tomcat的URI编码
uri-encoding: UTF-8
# 连接数满后的排队数,默认为100
accept-count: 1000
threads:
# tomcat最大线程数,默认为200
max: 800
# Tomcat启动初始化的线程数,默认值10
min-spare: 100
# 日志配置
logging:
level:
com.ruoyi: debug
org.springframework: warn
# 用户配置
user:
password:
# 密码错误{maxRetryCount}次锁定10分钟
maxRetryCount: 5
# Spring配置
spring:
# 模板引擎
thymeleaf:
mode: HTML
encoding: utf-8
# 禁用缓存
cache: false
# 资源信息
messages:
# 国际化资源文件路径
basename: static/i18n/messages
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
profiles:
active: druid
# 文件上传
servlet:
multipart:
# 单个文件大小
max-file-size: 10MB
# 设置总上传的文件大小
max-request-size: 20MB
# 服务模块
devtools:
restart:
# 热部署开关
enabled: true
# MyBatis
mybatis:
# 搜索指定包别名
typeAliasesPackage: com.ruoyi.project.**.domain
# 配置mapper的扫描,找到所有的mapper.xml映射文件
mapperLocations: classpath:mybatis/**/*Mapper.xml
# 加载全局的配置文件
configLocation: classpath:mybatis/mybatis-config.xml
# PageHelper分页插件
pagehelper:
helperDialect: mysql
supportMethodsArguments: true
params: count=countSql
# Shiro
shiro:
user:
# 登录地址
loginUrl: /login
# 权限认证失败地址
unauthorizedUrl: /unauth
# 首页地址
indexUrl: /index
# 验证码开关
captchaEnabled: true
# 验证码类型 math 数组计算 char 字符
captchaType: math
cookie:
# 设置Cookie的域名 默认空,即当前访问的域名
domain:
# 设置cookie的有效访问路径
path: /
# 设置HttpOnly属性
httpOnly: true
# 设置Cookie的过期时间,天为单位
maxAge: 30
# 设置密钥,务必保持唯一性(生成方式,直接拷贝到main运行即可)Base64.encodeToString(CipherUtils.generateNewKey(128, "AES").getEncoded()) (默认启动生成随机秘钥,随机秘钥会导致之前客户端RememberMe Cookie无效,如设置固定秘钥RememberMe Cookie则有效)
cipherKey:
session:
# Session超时时间,-1代表永不过期(默认30分钟)
expireTime: 30
# 同步session到数据库的周期(默认1分钟)
dbSyncPeriod: 1
# 相隔多久检查一次session的有效性,默认就是10分钟
validationInterval: 10
# 同一个用户最大会话数,比如2的意思是同一个账号允许最多同时两个人登录(默认-1不限制)
maxSession: -1
# 踢出之前登录的/之后登录的用户,默认踢出之前登录的用户
kickoutAfter: false
rememberMe:
# 是否开启记住我
enabled: true
# 防止XSS攻击
xss:
# 过滤开关
enabled: true
# 排除链接(多个用逗号分隔)
excludes: /system/notice/*
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*
# Swagger配置
swagger:
# 是否开启swagger
enabled: true
# 代码生成
gen:
# 作者
author: ruoyi
# 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool
packageName: com.ruoyi.project.system
# 自动去除表前缀,默认是true
autoRemovePre: true
# 表前缀(生成类名不会包含表前缀,多个用逗号分隔)
tablePrefix: sys_
application-druid.yml
文件配置解析
application-druid.yml
文件是Spring Boot项目中用来配置Druid数据源及其相关特性的一个关键配置文件。Druid是一个高性能且功能丰富的数据库连接池组件,主要用于Java应用程序,它可以提供数据库连接管理、SQL执行监控、数据源状态监控等功能。下面详细解析该配置文件的作用:
1. Spring Data Source Configuration
这部分配置定义了Spring框架中数据源的详细信息,包括:
- Type: 数据源的类型,这里指定为Druid数据源。
- Driver-Class-Name: 数据库驱动的全限定类名,这里用于MySQL数据库。
- URL: 数据库的连接URL,包含数据库服务器地址、端口、数据库名以及一些连接参数。
- Username: 连接数据库的用户名。
- Password: 连接数据库的密码。
2. Druid DataSource Specific Configuration
这部分是Druid数据源特有的配置,主要包括:
- InitialSize: 初始创建的连接数。
- MinIdle: 最小空闲连接数。
- MaxActive: 最大连接数。
- MaxWait: 获取连接的最大等待时间。
- TimeBetweenEvictionRunsMillis: 连接有效性检测的时间间隔。
- MinEvictableIdleTimeMillis: 连接空闲时间超过此值则会被回收。
- ValidationQuery: 用于验证连接有效性的SQL语句。
- TestWhileIdle: 是否在连接空闲时进行有效性检测。
- TestOnBorrow: 是否在获取连接时进行有效性检测。
- TestOnReturn: 是否在归还连接时进行有效性检测。
- PoolPreparedStatements: 是否开启PreparedStatement缓存。
- MaxPoolPreparedStatementPerConnectionSize: 每个连接的PreparedStatement缓存的最大数量。
- Filters: 启用的过滤器,如监控统计(stat)、SQL防注入(wall)、日志记录(log4j)。
3. Druid Web Monitoring Configuration
这部分配置了Druid的Web监控页面,包括:
- Stat-View-Servlet: 监控页面的启用、URL模式、登录信息、允许和拒绝访问的IP地址。
- Web-Stat-Filter: 监控页面的过滤规则,定义哪些URL请求会被监控,哪些会被排除。
通过这些配置,Druid不仅可以高效地管理数据库连接,还可以提供详细的SQL执行情况统计、数据源状态监控等信息,对于开发和运维人员来说,是非常有价值的工具,可以帮助他们更好地理解和优化数据库操作。同时,Druid的Web监控界面也提供了直观的图形化展示,使得监控和调试更加方便。
在Spring Boot项目中,application.yml
或 application.properties
是非常核心的配置文件,它们用于配置应用的各种属性,包括但不限于数据源、日志、安全设置、外部服务的URL等。下面我将分别介绍这两种文件的基本结构和用途:
application.yml配置
1. application.yml
application.yml
是使用YAML(YAML Ain’t Markup Language)格式的配置文件,它是一种人类可读的数据序列化格式,特别适合用于配置文件,因为它结构清晰,易于阅读和编写。
示例内容:
server:
port: 8080
servlet:
context-path: /myapp
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydatabase
username: myuser
password: mypassword
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
logging:
level:
root: INFO
com.example.myapp: DEBUG
在这个例子中,我们配置了服务器端口、数据源信息、JPA的Hibernate设置以及日志级别。
2. application.properties
application.properties
是使用传统属性文件格式的配置文件,每一行都是键值对的形式,以等号(=)或冒号(:)分隔。
示例内容:
server.port=8080
server.servlet.context-path=/myapp
spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase
spring.datasource.username=myuser
spring.datasource.password=mypassword
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
logging.level.root=INFO
logging.level.com.example.myapp=DEBUG
同样的,这个例子中包含了服务器端口、数据源信息、JPA设置和日志级别的配置。
选择哪种格式?
- YAML 更加结构化,适合复杂嵌套的配置,且更易读。
- Properties 更加简单直接,适合简单的键值对配置。
在Spring Boot中,你可以自由选择使用application.yml
或application.properties
,Spring Boot会自动识别并加载配置。在大型项目中,使用application.yml
可能会更加直观和便于维护。
无论选择哪一种,这些配置文件都扮演着极其重要的角色,它们帮助Spring Boot应用动态地配置和适应不同的运行环境,比如开发、测试和生产环境。
java连接mysql(jdbk)文字详细说明
使用Java连接MySQL数据库通常涉及JDBC(Java Database Connectivity),这是一个Java API,允许客户端应用程序连接到各种类型的数据库。以下是使用JDBC连接到MySQL数据库的详细步骤:
步骤一:准备MySQL JDBC驱动程序
首先,你需要下载适用于MySQL的JDBC驱动程序,即MySQL Connector/J
。你可以从MySQL官方网站或Maven中央仓库下载这个驱动程序。如果你使用的是Maven或Gradle等构建工具,你只需要在你的pom.xml
或build.gradle
文件中添加相应的依赖。
Maven依赖示例:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version> <!-- 确保使用最新版本 -->
</dependency>
Gradle依赖示例:
implementation 'mysql:mysql-connector-java:8.0.30'
步骤二:导入必要的类
在你的Java代码中,你需要导入JDBC API中的必要类。典型的导入语句如下:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.sql.ResultSet;
步骤三:建立数据库连接
使用DriverManager
类的getConnection()
方法建立与MySQL数据库的连接。你将需要提供数据库的URL、用户名和密码。URL通常格式如下:
String url = "jdbc:mysql://hostname:port/databaseName";
String username = "yourUsername";
String password = "yourPassword";
然后,你可以使用以下代码建立连接:
Connection conn = DriverManager.getConnection(url, username, password);
步骤四:执行SQL语句
一旦你有了连接,你可以创建Statement
或PreparedStatement
对象来执行SQL语句。例如:
Statement stmt = conn.createStatement();
String sql = "SELECT * FROM tableName";
ResultSet rs = stmt.executeQuery(sql);
步骤五:处理结果集
你可以遍历ResultSet
对象来处理查询结果。例如:
while(rs.next()) {
System.out.println(rs.getString("columnName"));
}
步骤六:关闭连接
在完成所有数据库操作后,确保关闭ResultSet
、Statement
和Connection
以释放资源:
rs.close();
stmt.close();
conn.close();
注意事项:
- 确保在处理异常时使用
try-catch
块。 - 使用
PreparedStatement
而不是Statement
可以防止SQL注入攻击,并且在处理参数化查询时更加高效。 - 在实际生产环境中,考虑使用连接池来管理数据库连接,以提高性能和资源利用效率。
以上步骤将帮助你使用Java和JDBC成功连接到MySQL数据库。
Tomcat 说明
Apache Tomcat 是一个开源的轻量级Web应用服务器,主要功能是运行基于Java的Web应用,尤其是那些使用Servlet和JavaServer Pages (JSP)技术的应用。Tomcat由Apache Software Foundation (ASF)维护,是Jakarta项目的一部分。以下是Tomcat的主要作用和特点:
-
Servlet和JSP容器:
Tomcat本质上是一个Servlet容器,能够执行Java Servlet规范中的Servlet和JSP页面。Servlet是一种运行在服务器端的小程序,用于处理HTTP请求并生成响应。JSP则是用于生成动态HTML页面的一种技术。 -
HTTP服务器:
尽管Tomcat主要作为Servlet容器运行,但它也具备基本的HTTP服务器功能,能够处理静态HTML、图像和其他非Java内容。然而,它的静态文件处理能力通常不如专门的Web服务器如Apache HTTP Server或Nginx。 -
轻量级和灵活:
Tomcat设计为轻量级,适用于开发和小型至中型规模的生产环境。它易于安装和配置,非常适合快速搭建Java Web应用的开发和测试环境。 -
支持现代Web技术:
Tomcat支持最新的Servlet和JSP规范,这使得开发者能够使用最新的Java Web开发技术。它还支持WebSocket、HTTP/2等现代Web通信协议。 -
扩展性和插件支持:
可以通过添加各种连接器和Valves来扩展Tomcat的功能,如SSL支持、负载均衡、身份验证等。这使得Tomcat可以根据具体需求进行定制。 -
开源和免费:
Tomcat是开源软件,遵循Apache License 2.0许可,这意味着它是免费的,且其源代码可以自由地查看、修改和分发。 -
监控和管理:
提供了管理控制台,允许管理员远程管理服务器,包括启动、停止应用,配置服务器属性,以及监控服务器状态和性能。 -
跨平台:
Tomcat可以在多种操作系统上运行,包括Windows、Linux、macOS等,这得益于它基于Java的架构。 -
集群和高可用性:
支持集群部署,可以将多个Tomcat实例配置为一个集群,以提高性能和实现故障转移。 -
易于集成:
Tomcat可以与其他Web服务器如Apache HTTP Server或IIS一起使用,通过配置适当的代理或反向代理设置,可以让这些Web服务器前端处理静态内容,而后端的Tomcat专注于动态内容的生成。
总之,Tomcat是一个强大的、广泛使用的Java Web应用服务器,它不仅适合开发阶段的快速迭代,也适合在生产环境中部署和运行各种规模的Java Web应用。
详细解释验证码模块(题库管理系统)
这段代码是Spring框架下对验证码生成模块的配置,主要使用了Google的Kaptcha库来生成图形验证码。下面是对代码中各个部分的详细解释:
1. 类定义和注解
@Configuration
public class CaptchaConfig {
@Configuration
注解表明这是一个配置类,Spring会扫描并读取此类中的@Bean注解来初始化bean。
2. 创建默认验证码生成器
@Bean(name = "captchaProducer")
public DefaultKaptcha getKaptchaBean() {
@Bean
注解用于声明一个由Spring管理的bean。这里初始化了一个DefaultKaptcha
实例,用来生成普通的验证码。properties
是一个Properties
对象,用于存储Kaptcha的各种配置参数,如边框、颜色、尺寸、样式等。KAPTCHA_BORDER
,KAPTCHA_TEXTPRODUCER_FONT_COLOR
,KAPTCHA_IMAGE_WIDTH
,KAPTCHA_IMAGE_HEIGHT
,KAPTCHA_TEXTPRODUCER_FONT_SIZE
,KAPTCHA_SESSION_CONFIG_KEY
,KAPTCHA_TEXTPRODUCER_CHAR_LENGTH
,KAPTCHA_TEXTPRODUCER_FONT_NAMES
,KAPTCHA_OBSCURIFICATOR_IMPL
等都是Kaptcha库的配置键,用于控制验证码的外观和行为。
3. 创建数学表达式验证码生成器
@Bean(name = "captchaProducerMath")
public DefaultKaptcha getKaptchaBeanMath() {
- 这个bean用于生成包含数学表达式的验证码,通常用于增加破解难度。
- 除了基本的外观配置外,这里还设置了数学表达式的文本生成器
KAPTCHA_TEXTPRODUCER_IMPL
,指向自定义的类com.ruoyi.framework.config.KaptchaTextCreator
,这应该是一个实现了Kaptcha的文本生产者接口的类,负责生成数学表达式字符串。
4. 设置配置并返回bean
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
Config
类用于将Properties
对象转换成Kaptcha可识别的配置,然后设置给DefaultKaptcha
实例。
总结
这段代码展示了如何通过Spring框架配置Kaptcha库来生成两种不同类型的验证码:一种是普通的字符验证码,另一种是包含数学表达式的验证码。通过不同的@Bean
定义,可以根据需要灵活地在应用中使用这些验证码生成器。此外,通过自定义KaptchaTextCreator
类,还可以进一步定制验证码的内容生成逻辑,以满足特定的安全需求。
Java实现验证码模块
/**
* 验证码配置
*
* @author ruoyi
*/
package com.ruoyi.framework.config;
import java.util.Properties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import static com.google.code.kaptcha.Constants.*;
@Configuration
public class CaptchaConfig
{
@Bean(name = "captchaProducer")
public DefaultKaptcha getKaptchaBean()
{
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
// 是否有边框 默认为true 我们可以自己设置yes,no
properties.setProperty(KAPTCHA_BORDER, "yes");
// 验证码文本字符颜色 默认为Color.BLACK
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black");
// 验证码图片宽度 默认为200
properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
// 验证码图片高度 默认为50
properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
// 验证码文本字符大小 默认为40
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38");
// KAPTCHA_SESSION_KEY
properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode");
// 验证码文本字符长度 默认为5
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
// 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
// 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
@Bean(name = "captchaProducerMath")
public DefaultKaptcha getKaptchaBeanMath()
{
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
// 是否有边框 默认为true 我们可以自己设置yes,no
properties.setProperty(KAPTCHA_BORDER, "yes");
// 边框颜色 默认为Color.BLACK
properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90");
// 验证码文本字符颜色 默认为Color.BLACK
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
// 验证码图片宽度 默认为200
properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
// 验证码图片高度 默认为50
properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
// 验证码文本字符大小 默认为40
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35");
// KAPTCHA_SESSION_KEY
properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath");
// 验证码文本生成器
properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.ruoyi.framework.config.KaptchaTextCreator");
// 验证码文本字符间距 默认为2
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3");
// 验证码文本字符长度 默认为5
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6");
// 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
// 验证码噪点颜色 默认为Color.BLACK
properties.setProperty(KAPTCHA_NOISE_COLOR, "white");
// 干扰实现类
properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
// 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
提供的代码是使用Java中的Kaptcha库设置验证码生成的Spring配置类。以下是代码的详细分解:
类定义和注解
CaptchaConfig
类被 @Configuration
注解标记,表明这是一个配置类,Spring将从这个类中读取bean的定义并注册到应用上下文中。
Bean定义
1. captchaProducer
这个方法定义了一个名为 captchaProducer
的bean,该bean是一个 DefaultKaptcha
实例。它通过一系列属性来定制验证码的外观和行为,包括边框、文本颜色、图片尺寸、字体大小、session key、字符长度、字体样式、背景样式等。这些属性被封装在 Properties
对象中,并通过 Config
类设置给 DefaultKaptcha
。
2. captchaProducerMath
另一个方法定义了名为 captchaProducerMath
的bean,同样是 DefaultKaptcha
的实例。这个bean与前一个类似,但是它有一些不同的配置,比如边框颜色、文本颜色、文本生成器(这里指定为自定义的 KaptchaTextCreator
类)、字符间距、噪点颜色和噪点实现类。这通常用于数学运算型的验证码生成。
总结
这段代码的作用是为应用提供两个不同配置的验证码生成器,可以根据场景选择使用。例如,一个可能用于简单的文本验证码,而另一个可能用于需要数学计算的验证码场景。这样的配置灵活性有助于提高网站的安全性和用户体验。
package com.ruoyi.framework.config;
import java.security.SecureRandom;
import java.util.Random;
import com.google.code.kaptcha.text.impl.DefaultTextCreator;
/**
* 验证码文本生成器
*
* @author ruoyi
*/
public class KaptchaTextCreator extends DefaultTextCreator
{
private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(",");
// 生成验证码文本的方法
@Override
public String getText()
{
Integer result = 0;
Random random = new SecureRandom();
int x = random.nextInt(10);
int y = random.nextInt(10);
StringBuilder suChinese = new StringBuilder();
int randomoperands = (int) Math.round(Math.random() * 2);
if (randomoperands == 0)
{
result = x * y;
suChinese.append(CNUMBERS[x]);
suChinese.append("*");
suChinese.append(CNUMBERS[y]);
}
else if (randomoperands == 1)
{
if (!(x == 0) && y % x == 0)
{
result = y / x;
suChinese.append(CNUMBERS[y]);
suChinese.append("/");
suChinese.append(CNUMBERS[x]);
}
else
{
result = x + y;
suChinese.append(CNUMBERS[x]);
suChinese.append("+");
suChinese.append(CNUMBERS[y]);
}
}
else if (randomoperands == 2)
{
if (x >= y)
{
result = x - y;
suChinese.append(CNUMBERS[x]);
suChinese.append("-");
suChinese.append(CNUMBERS[y]);
}
else
{
result = y - x;
suChinese.append(CNUMBERS[y]);
suChinese.append("-");
suChinese.append(CNUMBERS[x]);
}
}
else
{
result = x + y;
suChinese.append(CNUMBERS[x]);
suChinese.append("+");
suChinese.append(CNUMBERS[y]);
}
suChinese.append("=?@" + result);
return suChinese.toString();
}
}
这段代码定义了一个名为 KaptchaTextCreator
的类,它继承自 DefaultTextCreator
,主要功能是生成数学运算符形式的验证码文本。这个类主要用于配合Kaptcha库生成包含简单算术问题的验证码图片,以增加验证码的复杂度和安全性。下面是代码的详细解释:
类定义
- 包名:
com.ruoyi.framework.config
表示这是RuoYi框架的一部分,位于其配置目录下。 - 类名:
KaptchaTextCreator
继承自DefaultTextCreator
,意味着它重写了父类的一些方法,特别是getText()
方法,用于生成验证码文本。
属性
- CNUMBERS:一个字符串数组,包含了0到10的数字字符串,用于生成随机数和构建算术表达式。
方法
-
getText():此方法是核心,它负责生成具体的验证码文本。方法内部首先生成两个介于0至9之间的随机整数
x
和y
,然后基于随机选择的运算类型(乘法、除法或加减法),构建一个算术表达式,并计算出结果。具体过程如下:- 如果运算符类型是0,则生成乘法表达式。
- 如果是1,尝试生成除法表达式,如果
y
不是x
的倍数则退化为加法表达式。 - 如果是2,则根据
x
和y
的大小生成加法或减法表达式。 - 最后,如果没有以上情况匹配,则默认生成加法表达式。
-
在生成表达式的同时,将结果也拼接到表达式后面,并以特殊符号
=?@
分隔,以便在用户完成计算后可以验证答案是否正确。
总结
通过这种方式,KaptchaTextCreator
类可以生成如 2*3=?@6
或者 5+4=?@9
这样的验证码文本,这比单纯的字母或数字验证码更难被自动破解程序识别,提高了网站的安全性。同时,由于包含了运算过程,这种类型的验证码对真实用户来说仍然相对友好且易于解决。
用户权限设置
sys_user
sys_post
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
<head>
<th:block th:include="include :: header('用户分配角色')" />
</head>
<body>
<div class="main-content">
<form id="form-user-add" class="form-horizontal">
<input type="hidden" id="userId" name="userId" th:value="${user.userId}">
<h4 class="form-header h4">基本信息</h4>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label class="col-sm-4 control-label is-required">用户名称:</label>
<div class="col-sm-8">
<input name="userName" class="form-control" type="text" disabled th:value="${user.userName}">
</div>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label class="col-sm-4 control-label is-required">登录账号:</label>
<div class="col-sm-8">
<input name="loginName" class="form-control" type="text" disabled th:value="${user.loginName}">
</div>
</div>
</div>
</div>
<h4 class="form-header h4">分配角色</h4>
<div class="row">
<div class="col-sm-12">
<div class="col-sm-12 select-table table-striped">
<table id="bootstrap-table"></table>
</div>
</div>
</div>
</form>
</div>
<div class="row">
<div class="col-sm-offset-5 col-sm-10">
<button type="button" class="btn btn-sm btn-primary" onclick="submitHandler()"><i class="fa fa-check"></i>保 存</button>
<button type="button" class="btn btn-sm btn-danger" onclick="closeItem()"><i class="fa fa-reply-all"></i>关 闭 </button>
</div>
</div>
<th:block th:include="include :: footer" />
<script th:inline="javascript">
var prefix = ctx + "system/user/authRole";
var roles = [[${roles}]]
$(function() {
var options = {
data: roles,
sidePagination: "client",
sortName: "roleSort",
showSearch: false,
showRefresh: false,
showToggle: false,
showColumns: false,
clickToSelect: true,
maintainSelected: true,
columns: [{
checkbox: true,
formatter:function (value, row, index) {
if($.common.isEmpty(value)) {
return { checked: row.flag };
} else {
return { checked: value }
}
}
},
{
field: 'roleId',
title: '角色编号'
},
{
field: 'roleSort',
title: '排序',
sortable: true,
visible: false
},
{
field: 'roleName',
title: '角色名称'
},
{
field: 'roleKey',
title: '权限字符',
sortable: true
},
{
field: 'createTime',
title: '创建时间',
sortable: true
}]
};
$.table.init(options);
});
/* 添加角色-提交 */
function submitHandler(index, layero){
var rows = $.table.selectFirstColumns();
var data = { "userId": $("#userId").val(), "roleIds": rows.join() };
$.operate.saveTab(prefix + "/insertAuthRole", data);
}
</script>
</body>
</html>
Java实现具体页面表格分页处理器
package com.ruoyi.framework.web.page;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.text.Convert;
/**
* 表格数据处理
*
* @author ruoyi
*/
public class TableSupport
{
/**
* 当前记录起始索引
*/
public static final String PAGE_NUM = "pageNum";
/**
* 每页显示记录数
*/
public static final String PAGE_SIZE = "pageSize";
/**
* 排序列
*/
public static final String ORDER_BY_COLUMN = "orderByColumn";
/**
* 排序的方向 "desc" 或者 "asc".
*/
public static final String IS_ASC = "isAsc";
/**
* 分页参数合理化
*/
public static final String REASONABLE = "reasonable";
/**
* 封装分页对象
*/
public static PageDomain getPageDomain()
{
PageDomain pageDomain = new PageDomain();
pageDomain.setPageNum(Convert.toInt(ServletUtils.getParameter(PAGE_NUM), 1));
pageDomain.setPageSize(Convert.toInt(ServletUtils.getParameter(PAGE_SIZE), 1));
pageDomain.setOrderByColumn(ServletUtils.getParameter(ORDER_BY_COLUMN));
pageDomain.setIsAsc(ServletUtils.getParameter(IS_ASC));
pageDomain.setReasonable(ServletUtils.getParameterToBool(REASONABLE));
return pageDomain;
}
public static PageDomain buildPageRequest()
{
return getPageDomain();
}
}
这段代码展示了如何在Java Web应用中实现分页功能,特别是在处理表格数据时。TableSupport
类提供了一系列静态方法来封装和解析与分页相关的请求参数,从而简化了前端和后端之间分页数据的交互。下面是对代码的具体分析:
类定义和属性
-
类名:
TableSupport
,这是一个公共的工具类,用于支持表格数据的分页处理。 -
常量:定义了多个字符串常量,用于标识HTTP请求中的分页参数:
PAGE_NUM
:当前页码PAGE_SIZE
:每页显示的记录数ORDER_BY_COLUMN
:排序依据的列名IS_ASC
:排序方向,true
代表升序,false
代表降序REASONABLE
:是否启用分页参数的合理性检查,例如防止非法的页码或每页记录数
方法
-
getPageDomain():该方法从HTTP请求中读取分页相关的参数,并使用这些参数初始化并返回一个
PageDomain
对象。PageDomain
是一个自定义的类,用于封装分页参数。Convert.toInt()
方法用于将字符串转换成整数,如果转换失败或参数不存在,则使用默认值(通常是1)。 -
buildPageRequest():这是
getPageDomain()
的别名,提供了相同的逻辑和功能,用于构建分页请求。
使用场景
当用户请求带有分页参数的表格数据时,前端会将上述参数(如页码、每页数量、排序字段等)作为查询字符串发送给服务器。TableSupport
类中的方法会被调用来解析这些参数,并创建一个 PageDomain
对象。这个对象随后会被传递给数据查询逻辑,用于限制数据库查询的结果集大小以及指定查询的排序方式。
总结
TableSupport
类通过提供统一的接口来处理分页参数,使得开发者无需在每个涉及分页的数据处理方法中重复编写解析参数的代码。这不仅减少了代码的冗余,也提高了代码的可维护性和可读性。在实际应用中,这有助于快速构建高效且用户友好的数据展示界面。
登陆校验方法
package com.ruoyi.framework.shiro.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.ShiroConstants;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.exception.user.CaptchaException;
import com.ruoyi.common.exception.user.UserBlockedException;
import com.ruoyi.common.exception.user.UserDeleteException;
import com.ruoyi.common.exception.user.UserNotExistsException;
import com.ruoyi.common.exception.user.UserPasswordNotMatchException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.security.ShiroUtils;
import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.framework.manager.factory.AsyncFactory;
import com.ruoyi.project.system.user.domain.User;
import com.ruoyi.project.system.user.domain.UserStatus;
import com.ruoyi.project.system.user.service.IUserService;
/**
* 登录校验方法
*
* @author ruoyi
*/
@Component
public class LoginService
{
@Autowired
private PasswordService passwordService;
@Autowired
private IUserService userService;
/**
* 登录
*/
public User login(String username, String password)
{
// 验证码校验
if (ShiroConstants.CAPTCHA_ERROR.equals(ServletUtils.getRequest().getAttribute(ShiroConstants.CURRENT_CAPTCHA)))
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
throw new CaptchaException();
}
// 用户名或密码为空 错误
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password))
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
throw new UserNotExistsException();
}
// 密码如果不在指定范围内 错误
if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
throw new UserPasswordNotMatchException();
}
// 用户名不在指定范围内 错误
if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
throw new UserPasswordNotMatchException();
}
// 查询用户信息
User user = userService.selectUserByLoginName(username);
/**
if (user == null && maybeMobilePhoneNumber(username))
{
user = userService.selectUserByPhoneNumber(username);
}
if (user == null && maybeEmail(username))
{
user = userService.selectUserByEmail(username);
}
*/
if (user == null)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.not.exists")));
throw new UserNotExistsException();
}
if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.delete")));
throw new UserDeleteException();
}
if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.blocked", user.getRemark())));
throw new UserBlockedException();
}
passwordService.validate(user, password);
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
recordLoginInfo(user.getUserId());
return user;
}
/**
private boolean maybeEmail(String username)
{
if (!username.matches(UserConstants.EMAIL_PATTERN))
{
return false;
}
return true;
}
private boolean maybeMobilePhoneNumber(String username)
{
if (!username.matches(UserConstants.MOBILE_PHONE_NUMBER_PATTERN))
{
return false;
}
return true;
}
*/
/**
* 记录登录信息
*
* @param userId 用户ID
*/
public void recordLoginInfo(Long userId)
{
User user = new User();
user.setUserId(userId);
user.setLoginIp(ShiroUtils.getIp());
user.setLoginDate(DateUtils.getNowDate());
userService.updateUserInfo(user);
}
}
网页图标登录页面标题设置
这段代码展示了如何在Java Web应用中实现分页功能,特别是在处理表格数据时。TableSupport
类提供了一系列静态方法来封装和解析与分页相关的请求参数,从而简化了前端和后端之间分页数据的交互。下面是对代码的具体分析:
类定义和属性
-
类名:
TableSupport
,这是一个公共的工具类,用于支持表格数据的分页处理。 -
常量:定义了多个字符串常量,用于标识HTTP请求中的分页参数:
PAGE_NUM
:当前页码PAGE_SIZE
:每页显示的记录数ORDER_BY_COLUMN
:排序依据的列名IS_ASC
:排序方向,true
代表升序,false
代表降序REASONABLE
:是否启用分页参数的合理性检查,例如防止非法的页码或每页记录数
方法
-
getPageDomain():该方法从HTTP请求中读取分页相关的参数,并使用这些参数初始化并返回一个
PageDomain
对象。PageDomain
是一个自定义的类,用于封装分页参数。Convert.toInt()
方法用于将字符串转换成整数,如果转换失败或参数不存在,则使用默认值(通常是1)。 -
buildPageRequest():这是
getPageDomain()
的别名,提供了相同的逻辑和功能,用于构建分页请求。
使用场景
当用户请求带有分页参数的表格数据时,前端会将上述参数(如页码、每页数量、排序字段等)作为查询字符串发送给服务器。TableSupport
类中的方法会被调用来解析这些参数,并创建一个 PageDomain
对象。这个对象随后会被传递给数据查询逻辑,用于限制数据库查询的结果集大小以及指定查询的排序方式。
总结
TableSupport
类通过提供统一的接口来处理分页参数,使得开发者无需在每个涉及分页的数据处理方法中重复编写解析参数的代码。这不仅减少了代码的冗余,也提高了代码的可维护性和可读性。在实际应用中,这有助于快速构建高效且用户友好的数据展示界面。
增删改查通用变量函数设置
package com.ruoyi.common.constant;
/**
-
权限通用常量
-
@author ruoyi
/
public class PermissionConstants
{
/* 新增权限 */
public static final String ADD_PERMISSION = “add”;/** 修改权限 */
public static final String EDIT_PERMISSION = “edit”;/** 删除权限 */
public static final String REMOVE_PERMISSION = “remove”;/** 导出权限 */
public static final String EXPORT_PERMISSION = “export”;/** 显示权限 */
public static final String VIEW_PERMISSION = “view”;/** 查询权限 */
public static final String LIST_PERMISSION = “list”;
}
文件组件
这个文件夹结构看起来像是一个Java项目中的工具类集合,其中包含了许多实用工具类,分别针对不同的任务进行优化。以下是对每个子文件夹及其内容的简要说明:
- bean: 可能包含一些通用的Java Bean辅助类或者容器相关的工具类。
- file: 文件操作相关的工具类,可能用于读写文件、文件路径处理等功能。
- html: HTML相关工具类,可能用于HTML解析、生成或者其他HTML相关的操作。
- http: HTTP相关的工具类,可能包含HTTP请求、响应处理等。
- poi: Apache POI库的扩展或辅助类,用于处理Excel、Word等Office文档格式。
- reflect: 反射相关的工具类,可能用于动态获取或修改类的信息、属性、方法等。
- security: 安全相关的工具类,可能包含加密解密、权限校验等功能。
- spring: Spring框架相关的工具类,可能包含Spring Bean的辅助操作或其他Spring生态系统的集成。
- sql: SQL相关的工具类,可能包含数据库连接、SQL语句生成、执行等功能。
- text: 文本处理相关的工具类,可能包含字符串操作、正则表达式处理等。
- uuid: UUID相关的工具类,可能用于生成唯一ID。
- AddressUtils: 地址相关的工具类,可能用于地址解析、编码、地理定位等功能。
这些工具类通常是为了复用和简化开发工作而设计的,它们可以帮助开发者避免重复编写常见的代码片段,提高代码质量和效率。
JAVA中sql语句检查代码
package com.ruoyi.common.utils.sql;
import com.ruoyi.common.exception.UtilException;
import com.ruoyi.common.utils.StringUtils;
/**
* sql操作工具类
*
* @author ruoyi
*/
public class SqlUtil
{
/**
* 定义常用的 sql关键字
*/
public static String SQL_REGEX = "select |insert |delete |update |drop |count |exec |chr |mid |master |truncate |char |and |declare ";
/**
* 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序)
*/
public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+";
/**
* 检查字符,防止注入绕过
*/
public static String escapeOrderBySql(String value)
{
if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value))
{
throw new UtilException("参数不符合规范,不能进行查询");
}
return value;
}
/**
* 验证 order by 语法是否符合规范
*/
public static boolean isValidOrderBySql(String value)
{
return value.matches(SQL_PATTERN);
}
/**
* SQL关键字检查
*/
public static void filterKeyword(String value)
{
if (StringUtils.isEmpty(value))
{
return;
}
String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|");
for (String sqlKeyword : sqlKeywords)
{
if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1)
{
throw new UtilException("参数存在SQL注入风险");
}
}
}
}
SqlUtil
类是设计用于Java应用程序中SQL操作的工具类,它包含防止SQL注入攻击和验证SQL相关字符串的方法。下面是其功能的详细解析:
-
SQL_KEYWORDS: 一个定义了常见SQL关键词的正则表达式字符串。这个列表可以用来检查任何用户提供的字符串是否包含这些关键词,如果有,可能表示存在SQL注入的风险。
-
SQL_PATTERN: 一个正则表达式模式,指定了在order-by子句或类似的SQL语法中允许的字符。它允许字母、数字、下划线、空格、逗号和句点。这个模式用于在将其用于SQL查询之前验证用户输入。
-
escapeOrderBySql(String value): 这个方法检查提供的字符串(
value
)是否符合SQL_PATTERN
。如果不符,将抛出UtilException
,指示参数不符合规范。这个方法对于清洗打算用于order-by子句的输入很有用。 -
isValidOrderBySql(String value): 验证提供的字符串(
value
)是否匹配SQL_PATTERN
。如果匹配则返回true
,否则返回false
。这是一个辅助方法,被escapeOrderBySql
所使用。 -
filterKeyword(String value): 检查给定的字符串(
value
)是否包含任何定义的SQL关键词。如果包含,将抛出UtilException
,表明存在潜在的SQL注入风险。这个方法可以在将输入包含在SQL语句之前用于清洗输入。
这些方法通过验证和清洗输入提供了基本的SQL注入防护。然而,值得注意的是,这种验证不应是抵御SQL注入的唯一防线。使用预编译语句或参数化查询是处理动态数据时更安全的做法,因为它们可以有效防止SQL注入攻击。在实际应用中,结合使用这些方法和其他安全实践,如输入验证和输出编码,能够提供更全面的安全保障。
这个文件夹结构似乎包含了一些HTML文件,可能是某个Web项目的视图层(View)部分。这些文件可能对应着不同的用户管理功能,例如:
- avatar.html: 用户头像编辑或上传页面。
- profile.html: 用户个人资料页面。
- resetPwd.html: 用户密码重置页面。
- add.html: 新增用户页面。
- authRole.html: 用户角色授权页面。
- edit.html: 用户编辑页面。
- user.html: 用户列表或概览页面。
这些HTML文件可能与后端控制器(Controller)关联,用于呈现用户管理的相关功能。例如,avatar.html
可能用于让用户上传或更改他们的头像,profile.html
显示用户的个人信息,resetPwd.html
允许用户重设密码,等等。这些文件通常与后端服务一起工作,接收来自控制器的数据,并将其渲染为用户可见的界面。
课程管理 课程信息
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<th:block th:include="include :: header('学生信息列表')" />
</head>
<body class="gray-bg">
<div class="container-div">
<div class="row">
<div class="col-sm-12 search-collapse">
<form id="formId">
<div class="select-list">
<ul>
<li>
<label>课程名称:</label>
<input type="text" name="sname"/>
</li>
<li>
<label>章节:</label>
<input type="text" name="sid"/>
</li>
<li>
<label>关键字:</label>
<select name="ssex" th:with="type=${@dict.getType('sys_user_sex')}">
<option value="">所有</option>
<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
</select>
</li>
<li>
<a class="btn btn-primary btn-rounded btn-sm" onclick="$.treeTable.search()"><i class="fa fa-search"></i> 搜索</a>
<a class="btn btn-warning btn-rounded btn-sm" onclick="$.form.reset()"><i class="fa fa-refresh"></i> 重置</a>
</li>
</ul>
</div>
</form>
</div>
<div class="btn-group-sm" id="toolbar" role="group">
<a class="btn btn-success" onclick="$.operate.add()" shiro:hasPermission="system:student:add">
<i class="fa fa-plus"></i> 新增
</a>
<a class="btn btn-primary" onclick="$.operate.edit()" shiro:hasPermission="system:student:edit">
<i class="fa fa-edit"></i> 修改
</a>
<a class="btn btn-info" id="expandAllBtn">
<i class="fa fa-exchange"></i> 展开/折叠
</a>
</div>
<div class="col-sm-12 select-table table-striped">
<table id="bootstrap-tree-table"></table>
</div>
</div>
</div>
<th:block th:include="include :: footer" />
<script th:inline="javascript">
var addFlag = [[${@permission.hasPermi('system:student:add')}]];
var editFlag = [[${@permission.hasPermi('system:student:edit')}]];
var removeFlag = [[${@permission.hasPermi('system:student:remove')}]];
var ssexDatas = [[${@dict.getType('sys_user_sex')}]];
var prefix = ctx + "system/student";
$(function() {
var options = {
code: "id",
parentCode: "parentId",
expandColumn: "1",
uniqueId: "id",
url: prefix + "/list",
createUrl: prefix + "/add/{id}",
updateUrl: prefix + "/edit/{id}",
removeUrl: prefix + "/remove/{id}",
modalName: "学生信息",
columns: [{
field: 'selectItem',
radio: true
},
{
field: 'sname',
title: '课程名称',
align: 'left'
},
{
field: 'sid',
title: '章节号',
align: 'left'
},
{
field: 'ssex',
title: '课程编号',
align: 'left',
formatter: function(value, row, index) {
return $.table.selectDictLabel(ssexDatas, value);
}
},
{
title: '',
align: 'center',
align: 'left',
formatter: function(value, row, index) {
var actions = [];
return actions.join('');
}
}]
};
$.treeTable.init(options);
});
</script>
</body>
</html>
这段HTML代码是一个使用Thymeleaf模板引擎和Shiro权限管理框架的Web页面,主要功能是展示和管理学生信息列表。下面是对各个部分的详细解释:
页面头部
<!DOCTYPE html>
:声明文档类型。<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
:定义HTML文档语言为中文,并引入Thymeleaf和Shiro的命名空间,用于模板表达式和权限控制。<th:block th:include="include :: header('学生信息列表')"/>
:使用Thymeleaf的th:include
指令来包含其他模板文件中的头部区域,标题设置为“学生信息列表”。
主体部分
<body class="gray-bg">
:定义页面主体的背景色。<div class="container-div">
:包含整个页面内容的容器。<div class="col-sm-12 search-collapse">
:搜索表单区域,包含课程名称、章节、关键字的输入框和搜索、重置按钮。<div class="btn-group-sm" id="toolbar" role="group">
:操作按钮区域,有新增、修改、展开/折叠功能的按钮,且按钮的显示受Shiro权限控制。<div class="col-sm-12 select-table table-striped">
:包含一个表格ID为bootstrap-tree-table
,用于展示学生信息。
JavaScript脚本
<script th:inline="javascript">
:内联JavaScript脚本,使用Thymeleaf表达式来动态获取权限和数据。var addFlag = [[${@permission.hasPermi('system:student:add')}]];
:检查是否有添加学生的权限。var editFlag = [[${@permission.hasPermi('system:student:edit')}]];
:检查是否有编辑学生的权限。var removeFlag = [[${@permission.hasPermi('system:student:remove')}]];
:检查是否有删除学生的权限。var ssexDatas = [[${@dict.getType('sys_user_sex')}]];
:获取性别字典数据。$(function() { ... });
:jQuery文档加载完成后执行的函数,初始化树形表格组件,设置URLs和表格列信息,包括课程名称、章节号、课程编号等,其中课程编号列使用字典数据进行格式化显示。
结构与功能
- 页面布局清晰,包括搜索表单、操作按钮和数据展示表格。
- 使用Thymeleaf表达式动态填充数据,如权限判断和字典数据获取。
- 通过JavaScript脚本初始化表格组件,设置URLs和列信息,实现动态数据加载和权限控制下的操作功能。
整体而言,这个页面设计用于学生信息管理,提供了搜索、新增、编辑功能,并且通过Shiro框架实现了细粒度的权限控制,确保只有具有相应权限的用户才能执行特定的操作。
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<th:block th:include="include :: header('学生信息列表')" />
</head>
<body class="gray-bg">
<div class="container-div">
<div class="row">
<div class="col-sm-12 search-collapse">
<form id="formId">
<div class="select-list">
<ul>
<li>
<label>姓名:</label>
<input type="text" name="sname"/>
</li>
<li>
<label>学号:</label>
<input type="text" name="sid"/>
</li>
<li>
<label>性别:</label>
<select name="ssex" th:with="type=${@dict.getType('sys_user_sex')}">
<option value="">所有</option>
<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
</select>
</li>
<li>
<a class="btn btn-primary btn-rounded btn-sm" onclick="$.treeTable.search()"><i class="fa fa-search"></i> 搜索</a>
<a class="btn btn-warning btn-rounded btn-sm" onclick="$.form.reset()"><i class="fa fa-refresh"></i> 重置</a>
</li>
</ul>
</div>
</form>
</div>
<div class="btn-group-sm" id="toolbar" role="group">
<a class="btn btn-success" onclick="$.operate.add()" shiro:hasPermission="system:student:add">
<i class="fa fa-plus"></i> 新增
</a>
<a class="btn btn-primary" onclick="$.operate.edit()" shiro:hasPermission="system:student:edit">
<i class="fa fa-edit"></i> 修改
</a>
<a class="btn btn-info" id="expandAllBtn">
<i class="fa fa-exchange"></i> 展开/折叠
</a>
</div>
<div class="col-sm-12 select-table table-striped">
<table id="bootstrap-tree-table"></table>
</div>
</div>
</div>
<th:block th:include="include :: footer" />
<script th:inline="javascript">
var addFlag = [[${@permission.hasPermi('system:student:add')}]];
var editFlag = [[${@permission.hasPermi('system:student:edit')}]];
var removeFlag = [[${@permission.hasPermi('system:student:remove')}]];
var ssexDatas = [[${@dict.getType('sys_user_sex')}]];
var prefix = ctx + "system/student";
$(function() {
var options = {
code: "id",
parentCode: "parentId",
expandColumn: "1",
uniqueId: "id",
url: prefix + "/list",
createUrl: prefix + "/add/{id}",
updateUrl: prefix + "/edit/{id}",
removeUrl: prefix + "/remove/{id}",
modalName: "学生信息",
columns: [{
field: 'selectItem',
radio: true
},
{
field: 'sname',
title: '姓名',
align: 'left'
},
{
field: 'sid',
title: '学号',
align: 'left'
},
{
field: 'ssex',
title: '性别',
align: 'left',
formatter: function(value, row, index) {
return $.table.selectDictLabel(ssexDatas, value);
}
},
{
title: '操作',
align: 'center',
align: 'left',
formatter: function(value, row, index) {
var actions = [];
actions.push('<a class="btn btn-success btn-xs ' + editFlag + '" href="javascript:void(0)" οnclick="$.operate.edit(\'' + row.id + '\')"><i class="fa fa-edit"></i>编辑</a> ');
actions.push('<a class="btn btn-info btn-xs ' + addFlag + '" href="javascript:void(0)" οnclick="$.operate.add(\'' + row.id + '\')"><i class="fa fa-plus"></i>新增</a> ');
actions.push('<a class="btn btn-danger btn-xs ' + removeFlag + '" href="javascript:void(0)" οnclick="$.operate.remove(\'' + row.id + '\')"><i class="fa fa-remove"></i>删除</a>');
return actions.join('');
}
}]
};
$.treeTable.init(options);
});
</script>
</body>
</html>
这段HTML代码展示了一个使用Thymeleaf模板引擎和Shiro权限框架的Web页面,主要功能是展示学生信息列表并提供搜索、编辑、添加和删除操作。下面对代码进行详细的中文解释:
页面结构
<!DOCTYPE html>
:定义文档类型为HTML5。<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
:声明页面语言为中文,并引入Thymeleaf和Shiro的命名空间。<head>
:页面头部区域,通过<th:block th:include="include :: header('学生信息列表')"/>
包含头部信息,设置标题为“学生信息列表”。
页面主体
<body class="gray-bg">
:设置页面背景颜色。<div class="container-div">
:容器div,包裹整个页面内容。<div class="row">
:行div,用于布局。<div class="col-sm-12 search-collapse">
:搜索栏,包含姓名、学号输入框及性别下拉选择,其中性别选项通过Thymeleaf表达式th:each
循环填充。<div class="btn-group-sm" id="toolbar" role="group">
:工具栏,包含新增、修改、展开/折叠按钮,其中新增和修改按钮的显示受Shiro权限控制。<div class="col-sm-12 select-table table-striped">
:表格容器,其中<table id="bootstrap-tree-table"></table>
为数据展示表格。
动态脚本
<script th:inline="javascript">
:内嵌JavaScript脚本,通过Thymeleaf表达式获取权限标志和性别数据。- 初始化变量
addFlag
、editFlag
、removeFlag
用于检查用户是否有添加、编辑、删除的权限,ssexDatas
用于存储性别数据。 - 设置URL前缀
prefix
,用于构建不同的操作URL。 - 使用jQuery和自定义的
$.treeTable.init(options)
函数初始化表格,配置表格列信息,包括姓名、学号、性别和操作列。其中性别列通过formatter
函数使用性别数据进行格式化,操作列根据用户权限显示编辑、新增、删除按钮。
功能描述
- 搜索功能:用户可以通过输入姓名、学号和选择性别来搜索学生信息。
- 权限控制:新增和修改按钮的显示取决于用户是否拥有相应的权限。
- 数据展示:表格展示学生信息,包括姓名、学号和性别,其中性别列通过性别数据映射展示。
- 操作功能:表格的每行提供编辑、新增和删除操作,这些操作的可用性同样受到权限控制。
总结
此页面是一个典型的学生信息管理系统页面,结合了Thymeleaf模板引擎和Shiro权限管理框架,实现了数据展示、搜索和基于权限的操作功能。
题库管理 课程信息
题库管理 课程信息
添加页面前端设计
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
<head>
<th:block th:include="include :: header('新增题目信息')" />
<th:block th:include="include :: summernote-css" />
</head>
<body class="white-bg">
<div class="wrapper wrapper-content animated fadeInRight ibox-content">
<form class="form-horizontal m" id="form-question-add">
<div class="form-group">
<label class="col-sm-3 control-label is-required">课程名:</label>
<div class="col-sm-8">
<select name="classId" class="form-control m-b" th:with="type=${@dict.getType('sys_class')}" required>
<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label is-required">题目类型:</label>
<div class="col-sm-8">
<select name="typeId" class="form-control m-b" th:with="type=${@dict.getType('sys_question_type')}" required>
<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label is-required">关键字:</label>
<div class="col-sm-8">
<input name="questionKey" class="form-control" type="text" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">题目描述:</label>
<div class="col-sm-8">
<input type="hidden" class="form-control" name="questionDescription">
<div class="summernote" id="questionDescription"></div>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">答案:</label>
<div class="col-sm-8">
<input type="hidden" class="form-control" name="questionAnswer">
<div class="summernote" id="questionAnswer"></div>
</div>
</div>
</form>
</div>
<th:block th:include="include :: footer" />
<th:block th:include="include :: summernote-js" />
<script th:inline="javascript">
var prefix = ctx + "system/question"
$("#form-question-add").validate({
focusCleanup: true
});
function submitHandler() {
if ($.validate.form()) {
$.operate.save(prefix + "/add", $('#form-question-add').serialize());
}
}
$(function() {
$('.summernote').summernote({
lang: 'zh-CN',
dialogsInBody: true,
callbacks: {
onChange: function(contents, $edittable) {
$("input[name='" + this.id + "']").val(contents);
},
onImageUpload: function(files) {
var obj = this;
var data = new FormData();
data.append("file", files[0]);
$.ajax({
type: "post",
url: ctx + "common/upload",
data: data,
cache: false,
contentType: false,
processData: false,
dataType: 'json',
success: function(result) {
if (result.code == web_status.SUCCESS) {
$('#' + obj.id).summernote('insertImage', result.url);
} else {
$.modal.alertError(result.msg);
}
},
error: function(error) {
$.modal.alertWarning("图片上传失败。");
}
});
}
}
});
});
</script>
</body>
</html>
修改题目信息前端设计
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
<head>
<th:block th:include="include :: header('修改题目信息')" />
<th:block th:include="include :: summernote-css" />
</head>
<body class="white-bg">
<div class="wrapper wrapper-content animated fadeInRight ibox-content">
<form class="form-horizontal m" id="form-question-edit" th:object="${question}">
<input name="id" th:field="*{id}" type="hidden">
<div class="form-group">
<label class="col-sm-3 control-label is-required">课程名:</label>
<div class="col-sm-8">
<select name="classId" class="form-control m-b" th:with="type=${@dict.getType('sys_class')}" required>
<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}" th:field="*{classId}"></option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label is-required">题目类型:</label>
<div class="col-sm-8">
<select name="typeId" class="form-control m-b" th:with="type=${@dict.getType('sys_question_type')}" required>
<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}" th:field="*{typeId}"></option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label is-required">关键字:</label>
<div class="col-sm-8">
<input name="questionKey" th:field="*{questionKey}" class="form-control" type="text" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">题目描述:</label>
<div class="col-sm-8">
<input type="hidden" class="form-control" th:field="*{questionDescription}">
<div class="summernote" id="questionDescription"></div>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">答案:</label>
<div class="col-sm-8">
<input type="hidden" class="form-control" th:field="*{questionAnswer}">
<div class="summernote" id="questionAnswer"></div>
</div>
</div>
</form>
</div>
<th:block th:include="include :: footer" />
<th:block th:include="include :: summernote-js" />
<script th:inline="javascript">
var prefix = ctx + "system/question";
$("#form-question-edit").validate({
focusCleanup: true
});
function submitHandler() {
if ($.validate.form()) {
$.operate.save(prefix + "/edit", $('#form-question-edit').serialize());
}
}
$(function() {
$('.summernote').each(function(i) {
$('#' + this.id).summernote({
lang: 'zh-CN',
dialogsInBody: true,
callbacks: {
onChange: function(contents, $edittable) {
$("input[name='" + this.id + "']").val(contents);
},
onImageUpload: function(files) {
var obj = this;
var data = new FormData();
data.append("file", files[0]);
$.ajax({
type: "post",
url: ctx + "common/upload",
data: data,
cache: false,
contentType: false,
processData: false,
dataType: 'json',
success: function(result) {
if (result.code == web_status.SUCCESS) {
$('#' + obj.id).summernote('insertImage', result.url);
} else {
$.modal.alertError(result.msg);
}
},
error: function(error) {
$.modal.alertWarning("图片上传失败。");
}
});
}
}
});
var content = $("input[name='" + this.id + "']").val();
$('#' + this.id).summernote('code', content);
})
});
</script>
</body>
</html>
问题页面总览设计
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<th:block th:include="include :: header('题目信息列表')" />
</head>
<body class="gray-bg">
<div class="container-div">
<div class="row">
<div class="col-sm-12 search-collapse">
<form id="formId">
<div class="select-list">
<ul>
<li>
<label>课程名:</label>
<select name="classId" th:with="type=${@dict.getType('sys_class')}">
<option value="">所有</option>
<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
</select>
</li>
<li>
<label>题目类型:</label>
<select name="typeId" th:with="type=${@dict.getType('sys_question_type')}">
<option value="">所有</option>
<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
</select>
</li>
<li>
<label>关键字:</label>
<input type="text" name="questionKey"/>
</li>
<li>
<a class="btn btn-primary btn-rounded btn-sm" onclick="$.table.search()"><i class="fa fa-search"></i> 搜索</a>
<a class="btn btn-warning btn-rounded btn-sm" onclick="$.form.reset()"><i class="fa fa-refresh"></i> 重置</a>
</li>
</ul>
</div>
</form>
</div>
<div class="btn-group-sm" id="toolbar" role="group">
<a class="btn btn-success" onclick="$.operate.add()" shiro:hasPermission="system:question:add">
<i class="fa fa-plus"></i> 添加
</a>
<a class="btn btn-primary single disabled" onclick="$.operate.edit()" shiro:hasPermission="system:question:edit">
<i class="fa fa-edit"></i> 修改
</a>
<a class="btn btn-danger multiple disabled" onclick="$.operate.removeAll()" shiro:hasPermission="system:question:remove">
<i class="fa fa-remove"></i> 删除
</a>
<a class="btn btn-warning" onclick="$.table.exportExcel()" shiro:hasPermission="system:question:export">
<i class="fa fa-download"></i> 导出
</a>
</div>
<div class="col-sm-12 select-table table-striped">
<table id="bootstrap-table"></table>
</div>
</div>
</div>
<th:block th:include="include :: footer" />
<script th:inline="javascript">
var editFlag = [[${@permission.hasPermi('system:question:edit')}]];
var removeFlag = [[${@permission.hasPermi('system:question:remove')}]];
var classIdDatas = [[${@dict.getType('sys_class')}]];
var typeIdDatas = [[${@dict.getType('sys_question_type')}]];
var prefix = ctx + "system/question";
$(function() {
var options = {
url: prefix + "/list",
createUrl: prefix + "/add",
updateUrl: prefix + "/edit/{id}",
removeUrl: prefix + "/remove",
exportUrl: prefix + "/export",
modalName: "题目信息",
columns: [{
checkbox: true
},
{
field: 'id',
title: '题号',
visible: false
},
{
field: 'classId',
title: '课程名',
formatter: function(value, row, index) {
return $.table.selectDictLabel(classIdDatas, value);
}
},
{
field: 'typeId',
title: '题目类型',
formatter: function(value, row, index) {
return $.table.selectDictLabel(typeIdDatas, value);
}
},
{
field: 'questionKey',
title: '关键字'
},
{
field: 'questionDescription',
title: '题目描述'
},
{
field: 'questionAnswer',
title: '答案'
},
{
title: '操作',
align: 'center',
formatter: function(value, row, index) {
var actions = [];
actions.push('<a class="btn btn-success btn-xs ' + editFlag + '" href="javascript:void(0)" οnclick="$.operate.edit(\'' + row.id + '\')"><i class="fa fa-edit"></i>编辑</a> ');
actions.push('<a class="btn btn-danger btn-xs ' + removeFlag + '" href="javascript:void(0)" οnclick="$.operate.remove(\'' + row.id + '\')"><i class="fa fa-remove"></i>删除</a>');
return actions.join('');
}
}]
};
$.table.init(options);
});
</script>
</body>
</html>
这个HTML页面是利用Thymeleaf模板引擎和Shiro权限管理框架构建的,主要用于学生信息列表的展示与管理。下面将详细解释页面各部分的作用:
页面结构与布局
- DOCTYPE声明: 声明文档类型为HTML5。
- HTML标签: 定义了页面的基本结构,使用了Thymeleaf和Shiro的命名空间。
- Head部分: 包含页面的头部信息,通过
th:include
指令引用了外部的头部模板,预设了页面标题为“学生信息列表”。 - Body部分: 页面的主要内容区域,设置了背景颜色,并组织了不同的功能区块。
搜索功能
- 搜索表单: 包含三个字段——课程名称、章节、关键字——用于过滤学生信息列表。使用了Thymeleaf的
th:with
指令来处理表单中的下拉列表选项,这些选项来源于后端的数据字典。 - 搜索与重置按钮: 分别用于提交搜索条件和清除已有的搜索条件,以刷新数据列表。
操作按钮
- 工具栏: 包含“新增”、“修改”和“展开/折叠”按钮。其中,“新增”和“修改”按钮使用Shiro的
shiro:hasPermission
指令来控制按钮的显示,确保只有具备相应权限的用户才能看到这些按钮。
数据表格
- 学生信息表格: 使用
id="bootstrap-tree-table"
的表格元素,用于展示学生信息。尽管具体的数据填充和格式化逻辑在JavaScript脚本中实现,但表格的设计和样式已经预先设定好。
JavaScript脚本
- 动态脚本: 利用Thymeleaf的
th:inline="javascript"
属性,脚本能够直接读取服务器端的状态,如权限标识和数据字典。这使得脚本可以根据后端返回的权限状态动态生成操作按钮的可见性,并能正确地处理数据字典的值。 - 初始化表格: 脚本中定义了表格的初始化逻辑,包括设置表格的列名、列宽、数据来源等。特别地,对于“性别”列,使用了
formatter
函数来根据value
查找对应的字典标签进行显示。 - 操作按钮绑定: 脚本还绑定了操作按钮的功能,如“新增”和“修改”,并且这些功能的可用性会根据用户的权限进行调整。
综合功能
- 权限控制: 页面上多个元素的可见性和功能,如操作按钮,都受到Shiro权限管理框架的控制,确保用户只能访问其权限范围内的功能。
- 数据展示与交互: 页面不仅展示了学生信息列表,还提供了搜索、添加、修改等功能,使得管理员能够高效地管理和维护学生数据。
总之,这个页面集成了用户界面设计、数据展示、权限控制和动态脚本处理,形成了一个功能完善的学生信息管理系统前端界面。
这段代码使用Thymeleaf模板引擎来构建一个用于添加题目信息的表单,结合了表单验证、富文本编辑器和Ajax通信功能。以下是各个部分的详细解析:
HTML结构和命名空间
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
- DOCTYPE声明:指定文档类型为HTML5。
- HTML语言属性:设置页面的语言为中文。
- Thymeleaf命名空间:允许在HTML中使用Thymeleaf表达式和指令。
头部和脚部模板包含
<th:block th:include="include :: header('新增题目信息')" />
<th:block th:include="include :: summernote-css" />
...
<th:block th:include="include :: footer" />
<th:block th:include="include :: summernote-js" />
- header:包含页面头部,如标题和CSS样式。
- summernote-css:包含Summernote编辑器所需的CSS。
- footer:包含页面底部,可能包括脚本和其他HTML尾部元素。
- summernote-js:包含Summernote编辑器所需的JS。
表单元素
<form class="form-horizontal m" id="form-question-add">
...
</form>
- 表单类:
form-horizontal
为Bootstrap样式,m
可能是自定义样式。 - 表单ID:
form-question-add
用于识别和操作表单。
表单项
<div class="form-group">
<label>课程名:</label>
<div>
<select name="classId" th:with="type=${@dict.getType('sys_class')}" required>
<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
</select>
</div>
</div>
- 选择框:使用Thymeleaf的
th:with
和th:each
来填充下拉菜单的选项,required
属性确保必填。 - 文本输入:如
<input name="questionKey" ...>
用于输入关键字,同样带有required
属性。
Summernote编辑器
<input type="hidden" class="form-control" name="questionDescription">
<div class="summernote" id="questionDescription"></div>
- 隐藏输入框:用于存储编辑器的内容。
- Summernote容器:
<div>
元素,由Summernote编辑器填充。
JavaScript脚本
<script th:inline="javascript">
...
</script>
- 表单验证:使用
$("#form-question-add").validate()
初始化验证。 - 提交处理程序:
submitHandler
函数用于处理表单提交事件。 - Summernote配置:设置编辑器的选项,如语言、回调函数等。
- Ajax上传图片:当用户在编辑器中尝试上传图片时,执行Ajax请求上传图片到服务器。
整体来看,这段代码构建了一个功能完整的题目信息添加表单,集成了数据验证、富文本编辑和Ajax通信,确保了良好的用户体验和数据处理能力。
下拉菜单动态读取数据库
在Thymeleaf模板中,你所展示的代码片段用于生成一个下拉选择框(<select>
),并填充基于后端提供的数据字典中的选项。具体分析如下:
-
th:with
指令: 这个指令用于在当前作用域内定义一个变量。在这个场景中,type=${@dict.getType('sys_class')}
意味着它调用了名为dict
的服务对象的一个方法getType(String typeCode)
,传入参数sys_class
。假设这个方法返回一个列表或集合,里面包含了课程分类的信息,每个元素都有dictLabel
和dictValue
属性,分别对应下拉菜单的显示文本和值。 -
th:each
指令: 这个指令用于遍历一个集合或数组。在<option>
标签中,th:each="dict : ${type}"
表示对于type
集合中的每一个元素(这里命名为dict
),都会生成一个<option>
标签。 -
th:text
和th:value
指令: 这两个指令分别用于设置<option>
标签的显示文本和值。在<option>
中,th:text="${dict.dictLabel}"
和th:value="${dict.dictValue}"
意味着每一个生成的<option>
标签的文本和值将分别从遍历的dict
对象中获取dictLabel
和dictValue
属性的值。 -
required
属性: 这个HTML5属性用于表示该字段是必须填写的。如果用户提交表单时没有选择任何选项,浏览器将阻止表单提交并显示错误信息。
综上所述,这段代码的作用是动态生成一个下拉菜单,菜单中的每个选项都是从后端服务调用中获取的课程分类数据,保证了数据的实时性和准确性,同时增加了表单的交互性和用户体验。
修改题目信息代码
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
<head>
<th:block th:include="include :: header('修改题目信息')" />
<th:block th:include="include :: summernote-css" />
</head>
<body class="white-bg">
<div class="wrapper wrapper-content animated fadeInRight ibox-content">
<form class="form-horizontal m" id="form-question-edit" th:object="${question}">
<input name="id" th:field="*{id}" type="hidden">
<div class="form-group">
<label class="col-sm-3 control-label is-required">课程名:</label>
<div class="col-sm-8">
<select name="classId" class="form-control m-b" th:with="type=${@dict.getType('sys_class')}" required>
<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}" th:field="*{classId}"></option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label is-required">题目类型:</label>
<div class="col-sm-8">
<select name="typeId" class="form-control m-b" th:with="type=${@dict.getType('sys_question_type')}" required>
<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}" th:field="*{typeId}"></option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label is-required">关键字:</label>
<div class="col-sm-8">
<input name="questionKey" th:field="*{questionKey}" class="form-control" type="text" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">题目描述:</label>
<div class="col-sm-8">
<input type="hidden" class="form-control" th:field="*{questionDescription}">
<div class="summernote" id="questionDescription"></div>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">答案:</label>
<div class="col-sm-8">
<input type="hidden" class="form-control" th:field="*{questionAnswer}">
<div class="summernote" id="questionAnswer"></div>
</div>
</div>
</form>
</div>
<th:block th:include="include :: footer" />
<th:block th:include="include :: summernote-js" />
<script th:inline="javascript">
var prefix = ctx + "system/question";
$("#form-question-edit").validate({
focusCleanup: true
});
function submitHandler() {
if ($.validate.form()) {
$.operate.save(prefix + "/edit", $('#form-question-edit').serialize());
}
}
$(function() {
$('.summernote').each(function(i) {
$('#' + this.id).summernote({
lang: 'zh-CN',
dialogsInBody: true,
callbacks: {
onChange: function(contents, $edittable) {
$("input[name='" + this.id + "']").val(contents);
},
onImageUpload: function(files) {
var obj = this;
var data = new FormData();
data.append("file", files[0]);
$.ajax({
type: "post",
url: ctx + "common/upload",
data: data,
cache: false,
contentType: false,
processData: false,
dataType: 'json',
success: function(result) {
if (result.code == web_status.SUCCESS) {
$('#' + obj.id).summernote('insertImage', result.url);
} else {
$.modal.alertError(result.msg);
}
},
error: function(error) {
$.modal.alertWarning("图片上传失败。");
}
});
}
}
});
var content = $("input[name='" + this.id + "']").val();
$('#' + this.id).summernote('code', content);
})
});
</script>
</body>
</html>
这段代码构建了一个用于修改题目信息的HTML页面,它使用了Thymeleaf模板引擎,jQuery库,以及Summernote富文本编辑器。下面是每个部分的详细解析:
HTML与Thymeleaf
1. DOCTYPE声明与语言属性
<!DOCTYPE html>
:声明这是一个HTML5文档。lang="zh"
:指明文档语言为中文。
2. Thymeleaf命名空间
xmlns:th="http://www.thymeleaf.org"
:引入Thymeleaf的命名空间,使模板能使用Thymeleaf指令。
3. Header与Footer包含
<th:block th:include="include :: header('修改题目信息')">
:通过th:include
指令引入头部模板,标题为“修改题目信息”。<th:block th:include="include :: summernote-css">
:引入Summernote编辑器所需的CSS样式。<th:block th:include="include :: footer">
:引入页面底部模板。<th:block th:include="include :: summernote-js">
:引入Summernote编辑器所需的JavaScript脚本。
Body内容
4. 表单结构
<form class="form-horizontal m" id="form-question-edit" th:object="${question}">
:定义一个水平布局的表单,使用th:object
绑定表单到question
对象。- 输入字段如
<input name="questionKey" th:field="*{questionKey}"...
:使用th:field
指令双向绑定输入字段与question
对象的属性。
5. 下拉菜单
<select name="classId" th:with="type=${@dict.getType('sys_class')}">
:定义一个下拉菜单,使用th:with
指令获取课程分类数据。<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}" th:field="*{classId}">
:使用th:each
遍历type
列表,生成下拉选项。
JavaScript脚本
6. 表单验证
$("#form-question-edit").validate({ focusCleanup: true })
:使用jQuery Validate插件初始化表单验证。
7. 提交处理
function submitHandler() { ... }
:定义提交处理函数,检查表单验证通过后,使用Ajax发送表单数据。
8. Summernote配置
$(function() { ... })
:文档加载完成后执行的代码。.summernote({ ... })
:初始化Summernote编辑器,设置语言、对话框显示方式、回调函数等。onChange
:内容变化时更新隐藏输入框的值。onImageUpload
:处理图片上传,使用Ajax上传图片并插入到编辑器中。
9. 动态加载内容
var content = $("input[name='" + this.id + "']").val();
:获取隐藏输入框的值。$('#' + this.id).summernote('code', content);
:将值加载到编辑器中。
整体而言,这段代码展示了如何使用Thymeleaf进行动态数据绑定,结合jQuery与Summernote编辑器实现富文本编辑功能,以及如何处理表单验证和Ajax数据提交。
SQL代码解析
实现课程题型的基本信息管理
-- Table structure for class
-- ----------------------------
CREATE TABLE `class` (
`class_id` int(0) NOT NULL COMMENT '课程号',
`class_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '课程名',
PRIMARY KEY
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
2. CREATE TABLE `class` (...) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; - 这部分代码是用来创建一个名为`class`的表。表`class`中包含了两个字段:`class_id`是整数类型的课程号,`class_name`是可变长度字符串类型的课程名。其中,`class_id`是主键字段。指定了该表使用InnoDB存储引擎,字符集为utf8mb4,排序规则为utf8mb4_0900_ai_ci,行格式为Dynamic。
总的来说,上述代码的含义是创建一个名为`class`的表,表结构包含了课程号和课程名两个字段,其中`class_id`是主键字段,使用InnoDB存储引擎,字符集为utf8mb4,排序规则为utf8mb4_0900_ai_ci,行格式为Dynamic。
- Records of class
-- ----------------------------
INSERT INTO `class` VALUES (1, '计算机组成原理');
INSERT INTO `class` VALUES (2, '数据库系统原理');
INSERT INTO `class` VALUES (3, '操作系统原理');
INSERT INTO `class` VALUES (4, '算法设计与分析');
INSERT INTO `class` VALUES (5, 'c++');
NSERT INTO是用于向数据库表中插入新记录的SQL语句。在这里,我们使用INSERT INTO语句将课程记录插入到名为"class"的表中。每条记录包括课程编号和课程名称。
Table structure for type
-- ----------------------------
CREATE TABLE `type` (
`type_id` int(0) NOT NULL COMMENT '类型号',
`type_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '类型名',
PRIMARY KEY (`type_id`)
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
表中有两个字段: type_id 是一个整数类型且不为空,并有注释说明是“类型号”,被设置为主键,并使用了 BTREE 索引。 type_name 是一个可变长度的字符类型(最大长度 255),字符集为 utf8mb4 ,排序规则为 utf8mb4_0900_ai_ci ,不为空,并有注释说明是“类型名”。
表使用的存储引擎是 InnoDB ,字符集是 utf8mb4 ,行格式是 Dynamic 。
-- ----------------------------
-- Records of type
-- ----------------------------
INSERT INTO `type` VALUES (1, '填空题');
INSERT INTO `type` VALUES (2, '选择题');
INSERT INTO `type` VALUES (3, '编程题');
INSERT INTO `type` VALUES (4, '综合题');
SET FOREIGN_KEY_CHECKS = 1;
在 MySQL 中,默认情况下外键约束检查是关闭的( SET FOREIGN_KEY_CHECKS = 0 )。当执行 SET FOREIGN_KEY_CHECKS = 1 后,数据库在进行数据操作时会检查外键约束,以确保数据的完整性和一致性。
实现习题信息的管理,能按照题型或章节录入每门课的习题
- Table structure for student
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
`parent_id` bigint NULL DEFAULT NULL COMMENT '',
`sname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '课程名称',
`sid` int NULL DEFAULT NULL COMMENT '章节号',
`ssex` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '课程编号',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 109 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES (100, 0, '西安石油大学 题库管理系统', NULL, '');
INSERT INTO `student` VALUES (101, 100, '计算机组成原理', NULL, '第一');
INSERT INTO `student` VALUES (102, 100, '数据库系统原理', NULL, '第二');
INSERT INTO `student` VALUES (103, 100, '操作系统原理', NULL, '第三');
INSERT INTO `student` VALUES (104, 101, '第一章节', 1, '');
INSERT INTO `student` VALUES (105, 101, '第二章节', 2, '');
INSERT INTO `student` VALUES (106, 102, '第一章节', 1, '');
INSERT INTO `student` VALUES (107, 103, '第一章节', 1, '');
INSERT INTO `student` VALUES (108, 103, '第二章节', 2, '');
SET FOREIGN_KEY_CHECKS = 1;
-- ----------------------------
管理课程的题型和章节
CREATE TABLE `gen_table_column` (
`column_id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT '编号',
`column_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '列名称',
`column_comment` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '列描述',
`column_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '列类型',
`java_type` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'JAVA类型',
`java_field` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'JAVA字段名',
这部分代码是在一个SQL语句中定义了两个字段,分别是`java_type`和`java_field`。具体含义如下:
1. `java_type` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'JAVA类型' - 这个字段用来存储Java类型,类型为可变长度字符串,最大长度为500个字符。设置了字符集为utf8mb4,排序规则为utf8mb4_0900_ai_ci,允许存储NULL值,默认值为NULL。并且添加了注释说明该字段存储的是Java类型。
2. `java_field` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'JAVA字段名' - 这个字段用来存储Java字段名,类型为可变长度字符串,最大长度为200个字符。同样设置了字符集为utf8mb4,排序规则为utf8mb4_0900_ai_ci,允许存储NULL值,默认值为NULL。并且添加了注释说明该字段存储的是Java字段名。
这些字段的定义表明,这个表可能用于记录Java类型和字段名的对应关系,或者其他与Java相关的信息。字符集和排序规则的设置可以确保存储的数据能够正确地处理多字节字符,同时注释提供了字段用途的说明。
`is_pk` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '是否主键(1是)',
- Records of gen_table_column
INSERT INTO `gen_table_column` VALUES (68, 'id', 'ID', 'bigint', 'Long', 'id', '1', '1',
INSERT INTO `gen_table_column` VALUES (69, 'parent_id', '所属班级', 'bigint', 'Long', 'parentId', '0', '0'
INSERT INTO `gen_table_column` VALUES (71, 'sname', '课程名称', 'varchar(255)', 'String', 'sname', '0', '0',
INSERT INTO `gen_table_column` VALUES (72, 'sid', '章节号', 'int', 'Long', 'sid', '0', '0',
INSERT INTO `gen_table_column` VALUES (73, '11', 'ssex', '课程编号', 'varchar(255)', 'String', 'ssex', '0', '0',
INSERT INTO `gen_table_column` VALUES (92, '16', 'id', '', 'int', 'Long', 'id', '1', '1',
INSERT INTO `gen_table_column` VALUES (99, '18', 'id', '题号', 'int', 'Long', 'id', '1', '1',
INSERT INTO `gen_table_column` VALUES (100, '18', 'class_id', '课程名', 'int', 'Long', 'classId', '0', '0',
INSERT INTO `gen_table_column` VALUES (101, '18', 'type_id', '题目类型', 'int', 'Long', 'typeId', '0', '0',
INSERT INTO `gen_table_column` VALUES (102, '18', 'question_key', '关键字', 'longtext', 'String', 'questionKey', '0', '0',
INSERT INTO `gen_table_column` VALUES (103, '18', 'question_description', '题目描述', 'longtext', 'String', 'questionDescription', '0', '0',
INSERT INTO `gen_table_column` VALUES (104, '18', 'question_answer', '答案', 'longtext', 'String', 'questionAnswer',
创建表:
CREATE TABLE class (
class_id INT PRIMARY KEY,
class_type VARCHAR(255)
);
CREATE TABLE type (
type_id INT PRIMARY KEY,
type_name VARCHAR(255)
);
CREATE TABLE chapters (
chapter_id INT PRIMARY KEY,
class_id INT,
chapter_name VARCHAR(255),
FOREIGN KEY (class_id) REFERENCES classes(class_id)
);
CREATE TABLE questions (
question_id INT AUTO_INCREMENT PRIMARY KEY,
class_id INT NOT NULL,
question_text TEXT NOT NULL,
type_id INT NOT NULL,
FOREIGN KEY (class_id) REFERENCES classes(class_id),
FOREIGN KEY (type_id) REFERENCES types(type_id));
问题四:
定义一个存储过程,以查询指定课程的所有章节及其对应的题型数量:
CREATE PROCEDURE GetQuestionStats(IN course_id INT)
BEGIN
SELECT c.chapter_name, t.type_name, COUNT(q.question_id) AS question_count
FROM questions q
JOIN chapters c ON q.chapter_id = c.chapter_id
JOIN types t ON q.type_id = t.type_id
WHERE q.class_id = course_id
GROUP BY c.chapter_name, t.type_name;
问题五:
CREATE VIEW CourseQuestionTypesWithCounts AS
SELECT c.class_id, c.class_type, t.type_name, COUNT(q.question_id) AS question_count
FROM classes c
JOIN questions q ON c.class_id = q.class_id
JOIN types t ON q.type_id = t.type_id
GROUP BY c.class_id, t.type_name;
查询:
SELECT * FROM CourseQuestionTypesWithCounts;
问题六:
questions表的创建
INSERT INTO questions (class_id, question_text, type_id)
VALUES (1, ‘这是一个示例问题。’, 1);
下面就无需手动增加序号
INSERT INTO questions (class_id, question_text, type_id)
VALUES (2, ‘这是另一个示例问题。’, 2);
问题七:
CREATE PROCEDURE GetQuestionCountsByClassAndType()
BEGIN
– 创建临时表用于存储结果
CREATE TEMPORARY TABLE IF NOT EXISTS temp_question_counts (
class_id INT,
type_id INT,
count INT
);
-- 清空临时表
TRUNCATE TABLE temp_question_counts;
-- 插入统计数据到临时表
INSERT INTO temp_question_counts (class_id, type_id, count)
SELECT q.class_id, q.type_id, COUNT(q.question_id)
FROM questions q
GROUP BY q.class_id, q.type_id;
-- 显示结果
SELECT c.class_name, t.type_name, ttc.count AS question_count
FROM temp_question_counts ttc
JOIN classes c ON c.class_id = ttc.class_id
JOIN types t ON t.type_id = ttc.type_id;
这个存储过程做了以下几件事:
- 定义了一个临时表
temp_question_counts
,用于存储课程ID、题型ID以及对应的习题数量。 - 清空临时表,确保每次运行存储过程时,结果都是新的。
- 使用
INSERT INTO ... SELECT
语句从questions
表中计算每门课程每种题型的习题数量,并插入到临时表中。 - 最后,从临时表中读取数据,并通过JOIN操作与
classes
和types
表连接,以便获取课程名和题型名,然后输出结果。
要调用这个存储过程,只需执行以下命令:
CALL GetQuestionCountsByClassAndType();