文章目录
前言1、数据库准备2、工具类与相关基类使用2.1、工具类2.2、相关基类
3、web包目录说明4、注册功能设计(本文核心部分)4.1、注册页面设计4.2、注册逻辑设计
5、登陆功能设计5.1、登陆页面设计5.2、登陆逻辑设计
6、运行效果图
前言
大多数的网页都离不开注册登陆这两个功能,所以我想结合所学知识,使用vue.js、thymeleaf、Ajax做出一个简易通用的模板,该模板应具有如下功能:
用户名和密码是否按照一定格式输入提示用户名是否已存在两次输入密码是否一致密码进行md5加密处理验证码点击刷新符合条件的输入框显示对勾,否则提示具体错误信息只有全部显示正确才可以进行注册或者登陆
1、数据库准备
确保mysql正确安装配置,使用可视化工具如Navicat操作数据库,创建数据库名称如atweb,建立t_user表格,语句如下(新建查询后直接粘贴运行即可):
-- 创建数据库
create database atweb;
--创建表格
use atweb;
create table if not exists t_user
(
u_id int primary key auto_increment comment '主键且自增',
u_name varchar(40) not null,
u_pwd varchar(100) comment '考虑到加密,长度尽量多给',
u_describe varchar(50) comment '后续字段有需要自行添加'
)
成功创建的提示为:
2、工具类与相关基类使用
我们通常会把一些需要经常调用且很少变化的代码 封装 起来作为方法或者类,以提高开发效率和减少出错的可能行。
2.1、工具类
JDBCTools工具类:
主要用来读取数据库配置文件,具有连接数据库和释放连接的功能代码如下: package com.qb.util;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class JDBCTools {
// 1、创建数据源,即连接池
private static DataSource dataSource;
// 2、创建ThreadLocal对象
private static ThreadLocal
static {
try {
//1、读取druid.properties文件
Properties pro = new Properties();
pro.load(JDBCTools.class.getClassLoader().getResourceAsStream("druid.properties"));
//2、连接连接池
dataSource = DruidDataSourceFactory.createDataSource(pro);
//3、创建线程池
threadLocal = new ThreadLocal<>();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取连接的方法
* 后续知识: 数据库事务操作 必须使用同一个数据库连接
* @return
* @throws SQLException
*/
public static Connection getConnection() {
// 从线程中获取连接 threadLocal可以保证线程安全
Connection connection = threadLocal.get();
if (connection == null) {
// 从连接池中获取一个连接
try {
connection = dataSource.getConnection();
// 将连接与当前线程绑定
threadLocal.set(connection);
} catch (SQLException e) {
e.printStackTrace();
}
}
return connection;
}
//释放连接的方法
public static void releaseConnection() {
// 获取当前线程中的连接
Connection connection = threadLocal.get();
if (connection != null) {
try {
connection.close();
// 将已经关闭的连接从当前线程中移除
threadLocal.remove();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
MD5Util工具类
针对明文字符串执行MD5加密代码如下: package com.qb.util;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5Util {
public static String encode(String source) {
// 1.判断明文字符串是否有效
if (source == null || "".equals(source)) {
throw new RuntimeException("用于加密的明文不可为空");
}
// 2.声明算法名称
String algorithm = "md5";
// 3.获取MessageDigest对象
MessageDigest messageDigest = null;
try {
messageDigest = MessageDigest.getInstance(algorithm);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
// 4.获取明文字符串对应的字节数组
byte[] input = source.getBytes();
// 5.执行加密
byte[] output = messageDigest.digest(input);
// 6.创建BigInteger对象
int signum = 1;
BigInteger bigInteger = new BigInteger(signum, output);
// 7.按照16进制将bigInteger的值转换为字符串
int radix = 16;
String encoded = bigInteger.toString(radix).toUpperCase();
return encoded;
}
}
ThreadLocalUtil工具类
代码如下: package com.qb.util;
public class ThreadLocalUtil {
//1.指定ThreadLocal对象
private static ThreadLocal
public static void setStr(String name){
threadLocal.set(name);
//添加数据
}
public static String getStr(){
//获取数据
return threadLocal.get();
}
public static void remove(){
//移除数据
threadLocal.remove();
}
}
2.2、相关基类
BaseServlet类
该类继承HttpServlet类,封装模板引擎,方便跳转请求的编写封装doGet和doPost方法,可通过传递方法名称调用指定方法(利用反射)代码如下: public class BaseServlet extends HttpServlet {
private TemplateEngine templateEngine;
//使用模版引擎需要实例化模版引擎对象 并且设定前缀和后缀
@Override
public void init() throws ServletException {
// 1.获取ServletContext对象
ServletContext servletContext = this.getServletContext();
// 2.创建Thymeleaf解析器对象
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(servletContext);
// 3.给解析器对象设置参数
// ①HTML是默认模式,明确设置是为了代码更容易理解
templateResolver.setTemplateMode(TemplateMode.HTML);
// ②设置前缀
String viewPrefix = servletContext.getInitParameter("view-prefix");
templateResolver.setPrefix(viewPrefix);
// ③设置后缀
String viewSuffix = servletContext.getInitParameter("view-suffix");
templateResolver.setSuffix(viewSuffix);
// ④设置缓存过期时间(毫秒)
templateResolver.setCacheTTLMs(60000L);
// ⑤设置是否缓存
templateResolver.setCacheable(true);
// ⑥设置服务器端编码方式
templateResolver.setCharacterEncoding("utf-8");
// 4.创建模板引擎对象
templateEngine = new TemplateEngine();
// 5.给模板引擎对象设置模板解析器
templateEngine.setTemplateResolver(templateResolver);
}
//使用模版引擎 转发到页面 并且渲染数据
protected void processTemplate(String templateName, HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 1.设置响应体内容类型和字符集
resp.setContentType("text/html;charset=UTF-8");
// 2.创建WebContext对象
WebContext webContext = new WebContext(req, resp, getServletContext());
// 3.处理模板数据
templateEngine.process(templateName, webContext, resp.getWriter());
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
String methodName = req.getParameter("method");
try {
Method method = this.getClass().getDeclaredMethod(methodName,HttpServletRequest.class,HttpServletResponse.class);
method.setAccessible(true);
method.invoke(this,req,resp);
} catch (Exception e) {
System.out.println("反射方法有误,请检查:"+methodName);
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
BaseDaoImpl类
该类为数据库操作的基类封装了常用的数据库增删该查操作代码如下: public abstract class BaseDaoImpl {
private QueryRunner queryRunner = new QueryRunner();
/**
* 通用的增删改的方法
* @param sql String sql
* @param args Object... 如果sql中有?,就传入对应个数的?
* @return int 受影响行数
*/
protected int update(String sql,Object... args) {
try {
return queryRunner.update(JDBCTools.getConnection(),sql,args);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
/**
* 查询单个对象的方法
* @param clazz Class 记录对应的类类型
* @param sql String 查询语句
* @param args Object... 如果sql中有?,即根据条件查询
* @param
* @return T 一个对象
*/
protected
List
if(list != null && list.size()>0) {
//return getList(clazz, sql, args).get(0);
return list.get(0);
}
return null;
}
/**
* 通用查询多个对象的方法
* @param clazz Class 记录对应的类类型
* @param sql String 查询语句
* @param args Object... 如果sql中有?,即根据条件查询,可以设置?的值
* @param
* @return List
*/
protected
try {
return queryRunner.query(JDBCTools.getConnection(),sql,new BeanListHandler
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
protected Object getValue(String sql,Object... args){
try {
return queryRunner.query(JDBCTools.getConnection(),sql,new ScalarHandler(),args);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
protected void batch(String sql,Object[][] args){
try {
queryRunner.batch(JDBCTools.getConnection(),sql,args);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
SysResult类
用于封装系统操作结果,并提供了一些静态工厂方法来方便创建不同类型的响应对象代码如下: package com.qb.vo;
import java.io.Serializable;
public class SysResult implements Serializable {
private Boolean flag;
private String msg;
private Object data;
public static SysResult success(String msg,Object data){
return new SysResult(true,msg,data);
}
public static SysResult success(Object data){
return new SysResult(true,"业务调用成功",data);
}
public static SysResult success(){
return new SysResult(true,"业务调用成功",null);
}
public static SysResult fail(){
return new SysResult(false,"业务调用失败",null);
}
public Boolean getFlag() {
return flag;
}
public void setFlag(Boolean flag) {
this.flag = flag;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public SysResult() {}
public SysResult(Boolean flag, String msg, Object data) {
this.flag = flag;
this.msg = msg;
this.data = data;
}
}
3、web包目录说明
首先需要把所需的jar包引入到lib下,并执行Add as Library操作创建static包,并在里面创建script包,引入axios.js和vue.js脚本创建pages包和user包,新建注册和登陆的html网页配置web.xml文件(配置了验证码生成的servlet),内容如下:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0">
4、注册功能设计(本文核心部分)
注意:位于WEB-INF下的页面不可以通过地址栏直接访问,只能通过服务器内部跳转,因此需要一个IndexServlet来访问首页,它的功能就是简单的跳转,代码如下:
@WebServlet("/index.html")
public class IndexServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getRequestDispatcher("/WEB-INF/pages/index.html").forward(req,resp);
}
}
首页的设计很简单,提供两个超链接,负责 前往注册 和 前往登陆,代码如下:
上面的 xmlns:th="http://www.thymeleaf.org"提供了thymeleaf模板引擎内容,
4.1、注册页面设计
由于页面代码片段切分起来麻烦,我会整体展示页面源代码,并在代码间做详细注释
页面源代码展示:
//以下两行代码用来引入js脚本
// 这里的 app 将会在下面Vue对象的属性中进行绑定
用户名 | ||
密码 | ||
确认密码 | ||
个人描述 | ||
验证码 | ||
|
// vue对象
const app = new Vue({
el: "#app", // 绑定上面的div id
data: { // data代表属性,里面可以定义各种对象,并无限嵌套
msg: {// 消息对象
usernameErrorMsg: "用户名应为6~16位数字和字母组成",
passwordErrorMsg: "密码的长度至少为8位",
passwordErrorMsg2: "密码两次输入不一致",
describeMsg:"请输入个人描述",
codeMsg: "请输入正确的验证码"
},
// 全局变量,上面user.xx 就是和这里的user属性保持一致
user: {
username: '',
password: '',
rePassword:'',
describe:'',
},
// 标志数组,只有用户输入数据全部正确,才会发起注册请求
flagArray:[0,0,0,0,0],
// 验证码
code:'',
// 表示生成验证码的servlet
kaptchaImg:"kaptcha"
},
methods: {
// 异步请求,用来检查验证码是否输入正确,sysResult代表响应的对象,页面根据对象属性做不同操作
async checkCode() {
// axios.get这行代码,代表使用get请求,访问业务逻辑的user并携带method和code两个参数
let {data:sysResult} = await axios.get("user?method=checkCode&code=" + this.code)
if (sysResult.flag) {
this.msg.codeMsg = "√"
this.flagArray[4] = 1
}else{
this.msg.codeMsg = "请输入正确的验证码!"
this.flagArray[4] = 0
}
},
// 异步请求,用来规定用户名形式和检查用户名是否已存在
async checkUsername() {
// 正则表达式:用户名应为6~16个字母或数字组成
let usernameRege = /^[a-zA-Z0-9]{6,16}$/
let usernameFlag = usernameRege.test(this.user.username)
// 符合用户名正则的继续判断用户是否已存在,否则直接提示用户名格式错误的信息
if (usernameFlag) {
// 这里同上,使用get请求,访问user并携带两个参数,根据响应对象属性做出特定操作
let {data:sysResult} = await axios.get("user?method=checkNameUnique&username="+this.user.username)
if (sysResult.flag) {
this.msg.usernameErrorMsg = "√"
this.flagArray[0] = 1
}else{
this.msg.usernameErrorMsg = "用户名已存在!"
this.flagArray[0] = 0
}
}else{
this.msg.usernameErrorMsg = "用户名应为6~16位数字和字母组成"
this.flagArray[0] = 0
}
},
// 正则判断用户密码是否符合要求
checkUserPwd() {
// 描述至少为8个字符
let pwdRege = /^.{8,}$/
let pwdFlag = pwdRege.test(this.user.password)
if(pwdFlag){
this.msg.passwordErrorMsg = "√"
this.flagArray[1] = 1
}else{
this.msg.passwordErrorMsg = "密码的长度至少为8位"
this.flagArray[1] = 0
}
},
// 判断两次密码输入是否一致,注意判断使用三个连续等号
checkUserRePwd() {
if (this.user.password === this.user.rePassword) {
this.msg.passwordErrorMsg2 = "√"
this.flagArray[2] = 1
}else{
this.msg.passwordErrorMsg2="两次输入的密码不一致!"
this.flagArray[2] = 0
}
},
checkDescription() {
// 描述至少为两个字符
let desRege = /^.{2,}$/
let desFlag = desRege.test(this.user.describe)
if (desFlag) {
this.msg.describeMsg = "√";
this.flagArray[3] = 1
} else {
this.msg.describeMsg = "请输入个人描述"
this.flagArray[3] = 0
}
},
// 数组.join(",")可以将数组转化为字符串,元素使用逗号拼接
async register() {
if (this.flagArray.join(",") === "1,1,1,1,1") {
alert("表单校验成功!")
let p = this.user
// 这里将页面输入的数据全部以参数的形式传递到业务逻辑层
let {data:sysResult} = await axios.get(`user?method=register&uName=${p.username}&uPwd=${p.password}&uDescribe=${p.describe}`)
if(sysResult.flag){
// 注册成功应往登陆页面跳转,因此data属性保存的是重定向前往登陆的重定向路径
window.location.href = sysResult.data
}
}else{
// 阻止跳转
event.preventDefault();
alert("表单校验不成功!!!");
}
},
// 验证码图片的刷新逻辑,只需要改变kaptcha方法的参数即可,这样浏览器检测到变化后,重新发起请求,从而改变验证码图片
changeImage() {
//浏览器解析到src的属性变化 则会重新发起请求,new Date充当随机数的作用
this.kaptchaImg="kaptcha?date="+new Date()
},
}
})
4.2、注册逻辑设计
下面是业务逻辑层源码与重要注释:
@WebServlet("/user")
public class UserServlet extends BaseServlet {
private final UserService us = new UserServiceImpl();
// 该实例化对象用于调用writeValueAsString方法将对象转化为json串
private final ObjectMapper MAPPER = new ObjectMapper();
// 跳转到注册页面
protected void toRegister(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.processTemplate("user/register", req, resp);
}
// 跳转到登陆页面
protected void toLogin(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.processTemplate("user/login", req, resp);
}
// 注册功能,添加user对象
protected void register(HttpServletRequest req, HttpServletResponse resp) throws Exception {
User user = new User();
// 这行代码可以接收前端传来的数据,并将其作为对象属性封装到对象中
// 但是需要注意,形参的键需要和类的属性名保持一致
BeanUtils.populate(user,req.getParameterMap());
// 调用service层的方法(三层大家都了解过吧,后续再调用dao层,不做解释)
us.registerByUser(user);
/* 下面代码不生效,使用axios进行处理
简单理解为:通过ajax发起请求,那么这次请求就不能再使用resp的重定向,而是
将重定向数据作为数据存入到响应体中,最后在axios里完成重定向跳转
resp.sendRedirect(req.getContextPath()+"/user?method=toLogin");*/
String realHref = req.getContextPath()+"/user?method=toLogin";
// 将路径存入到SysResult(响应体对象)
String json = MAPPER.writeValueAsString(SysResult.success(realHref));
// 将json数据返回到原请求
resp.getWriter().write(json);
}
// 检查用户名是否已存在
protected void checkNameUnique(HttpServletRequest req, HttpServletResponse resp) throws Exception {
String username = req.getParameter("username");
// System.out.println(username);
// 逻辑:通过查询数据库中该用户名的数量来判断用户是否存在
long res = us.selectUserByCount(username);
String json = "";
if (res > 0) {
// 数量大于零,用户已存在,响应fail方法
json = MAPPER.writeValueAsString(SysResult.fail());
} else {
// 不存在则响应success方法
json = MAPPER.writeValueAsString(SysResult.success());
}
resp.getWriter().write(json);
}
// 检查验证码是否正确,代码上面方法类似,不做解释
protected void checkCode(HttpServletRequest req, HttpServletResponse resp) throws Exception {
String code = req.getParameter("code");
// realCode存储在session中
String realCode = (String) req.getSession().getAttribute("KAPTCHA_SESSION_KEY");
String json = "";
if (code.equals(realCode)) {
json = MAPPER.writeValueAsString(SysResult.success());
} else {
json = MAPPER.writeValueAsString(SysResult.fail());
}
resp.getWriter().write(json);
}
}
5、登陆功能设计
登陆的功能与注册类似,比起注册,登陆的代码量要少很多,而且完全可以复用注册的代码。
5.1、登陆页面设计
源代码展示(相关解释会在注释中出现):
用户名 | ||
密码 | ||
验证码 | ||
|
const app = new Vue({
el: "#app",
data: {
msg: {
usernameMsg: "请输入用户名",
passwordMsg: "请输入密码",
verifyMsg: "请输入验证码"
},
// 全局变量
user: {
username: '',
password: '',
code:''
},
flagArray:[0,0,0],
kapImg:"kaptcha"
},
methods: {
changeImg() {
this.kapImg="kaptcha?date=" + new Date()
},
async checkCode() {
let {data:sysResult} = await axios.get("user?method=checkCode&code=" + this.user.code)
if (sysResult.flag) {
this.msg.verifyMsg = "√"
this.flagArray[2] = 1
}else{
this.msg.verifyMsg = "请输入正确的验证码!"
this.flagArray[2] = 0
}
},
async checkUsername() {
// 用户名应为6~16个字母或数字组成
let usernameRege = /^[a-zA-Z0-9]{6,16}$/
let usernameFlag = usernameRege.test(this.user.username)
if (usernameFlag) {
this.msg.usernameMsg = "√"
this.flagArray[0] = 1
}else{
this.msg.usernameMsg = "用户名格式错误,请根据注册账号进行输入"
this.flagArray[0] = 0
}
},
checkPwd() {
let pwdRege = /^.{8,}$/
let pwdFlag = pwdRege.test(this.user.password)
if(pwdFlag){
this.msg.passwordMsg = "√"
this.flagArray[1] = 1
}else{
this.msg.passwordMsg = "密码的长度至少为8位"
this.flagArray[1] = 0
}
},
// 异步请求登陆,传递账号密码两个参数
async login() {
if (this.flagArray.join(",") === "1,1,1") {
let p = this.user
let {data:sysResult} = await axios.get(`user?method=login&uName=${p.username}&uPwd=${p.password}`)
if(sysResult.flag){
alert("表单校验成功!")
// 这里的data是首页地址,逻辑就是登陆成功跳转首页
window.location.href = sysResult.data
}else{
alert("用户名或密码错误")
event.preventDefault()
}
}else{
// 阻止跳转
event.preventDefault()
alert("表单校验不成功!!!")
}
}
}
})
5.2、登陆逻辑设计
与注册逻辑设计相比,登陆只有下面这段代码是核心:
protected void login(HttpServletRequest req, HttpServletResponse resp) throws Exception {
User user_login = new User();
BeanUtils.populate(user_login,req.getParameterMap());
String json = "";
boolean flag = us.selectByUser(user_login);
if (flag) {
String realHref = req.getContextPath()+"/index.html";
json = MAPPER.writeValueAsString(SysResult.success(realHref));
}else{
json = MAPPER.writeValueAsString(SysResult.fail());
}
resp.getWriter().write(json);
}
这段代码的意思就是创建User对象并通过工具类给对象的账号和密码属性赋值,随后根据账号密码来查找数据库中是否存在账号密码对应的用户,若有就响应success和首页地址,若没有就响应fail,最后通过resp将json数据返回前端ajax。
6、运行效果图
首页 注册 正确输入后的注册 登陆 正确输入后的登陆
码文不易,如有帮助请留下免费的大拇指,想要源码的评论区留言即可,我会上传资源在本平台。
推荐链接
发表评论