JavaWeb项目(登录注册页面)全过程详细总结

news2025/2/27 5:28:49

JavaWeb项目(登录注册页面)全过程总结

文章目录

  • JavaWeb项目(登录注册页面)全过程总结
  • 一、环境准备与开发工具
  • 二、创建 JavaWeb 项目
    • 2.1 新建Dynamic Web Project项目
    • 2.2 创建前端页面
      • 2.2.1 登录页面
        • 1.login.jsp
        • 2. login.js
        • 3. time.js
        • 4. focus.js + animate.js
      • 2.2.2 注册页面(register.jsp)
      • 2.2.3 修改密码的页面(change_pwd.jsp)
      • 2.2.4 登录成功的页面(success.jsp)
    • 2.3 创建相关配置类
      • 2.3.1 DAO类(DAO.java)
      • 2.3.2 JDBCUtil类(JDBCUtil.java)
      • 2.3.3 JsonResult类(JsonResult.java)
      • 2.3.4 MailUtil类(MailUtil.java)
      • 2.3.5 User类(User.java)
    • 2.4 创建 Servlet
      • 2.4.1 登录界面的Servlet(LoginServlet)
      • 2.3.2 注册界面的Servlet(RegisterServlet)
      • 2.3.3 修改密码的Servlet(HandlePwdServlet)
      • 2.3.4 发送邮件的Servlet(EmailServlet)

项目需求:

  1. 实现登录页面:
    • 顶部有logo栏目
    • 左侧提供轮播图
    • 提供两种登录方式:①账号密码登录 ②邮箱+验证码登录
    • 登录成功后跳到显示“登录成功”四字的页面(简单设计)
    • 有修改密码的功能
  2. 注册页面
  3. 数据统一存储在数据库

一、环境准备与开发工具

此次项目用到的工具是:

① 前端:HTML + CSS + JS

② 后端:Tomcat 9 + Servlet

③ 项目开发工具 :Eclipse(Java EE IDE) java运行环境是:jdk 15

④ 数据库:Mysql + Navicat 15 for MySQL

       此次实验需要提前安装配置好Eclipse、jdk、tomcat,tomcat的安装和servlet的基本使用请见另两篇文章:Servlet的使用 和 Tomcat的使用

 

二、创建 JavaWeb 项目

2.1 新建Dynamic Web Project项目

image-20211121155204030

里面的Dynamic Web module version 我使用的2.5

image-20211121155413481

项目的目录结构如下:
image-20211121155735914
image-20211121155615544

 

2.2 创建前端页面

这里只展示 jsp 和 js 文件的代码,页面中的 icon 是使用的 icomoon

2.2.1 登录页面

1.login.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
 	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>LARP-LOGIN</title>
	<!-- 导入基础样式 -->
  	<link rel="stylesheet" href="../css/base.css">
  	<!-- 导入登录页面的样式 -->
  	<link rel="stylesheet" href="../css/login.css">
 	<!-- 导入轮播图的js -->
  	<script src="../js/focus.js"></script>
 	<!-- 导入animate.js -->
 	<script src="../js/animate.js"></script>
 	<!-- 导入时间的js -->
  	<script src="../js/time.js"></script>
  	<!-- 导入登录的js -->
  	<script src="../js/login.js"></script>
</head>

<body>
	<div class="container">
    <!-- 顶部导航栏 -->
    <header class="header">
      <img src="../images/LARP.png" alt="">
      <div class="logo">
        <h6>Load Assessment And Risk Prediction</h6>
        <h6>运动员负荷评估和风险预测系统</h6>
      </div>
      <div class="vline"></div>
      <h2 class="brand">LARP数据可视化管理平台</h2>
      <div class="time">
        <h6 class="date"></h6>
        <h6 class="second"></h6>
      </div>
    </header>

    <!-- 主体内容 -->
    <main>
      <!-- 左侧轮播图 -->
      <div class="left">
        <div class="left_container">
          <div class="focus">
            <!-- 左侧按钮 -->
            <a href="javascript:;" class="arrow-l"></a>
            <!-- 右侧按钮 -->
            <a href="javascript:;" class="arrow-r"></a>
            <!-- 轮播图的图片 -->
            <ul>
              <li><img src="../images/1.jpg" alt=""></li>
              <li><img src="../images/2.png" alt=""></li>
              <li><img src="../images/3.png" alt=""></li>
            </ul>
            <!-- 小圆点 -->
            <ol class="circle">
            </ol>
          </div>
        </div>
      </div>

      <!-- 右侧登陆界面 -->
      <div class="right">
        <form action="/my_login/LoginServlet" method="post" class="login_container">
          <!-- 登录方式 -->
          <div class="login_method">
            <span><a href="javascript:;" class="account_a">账号登录</a></span>
            <span><a href="javascript:;" class="phone_a">邮箱登录</a> </span>
          </div>

          <!-- 分割线 -->
          <div class="hline"></div>

          <!-- 输入框 用来占位 -->
          <div class="input_box"></div>

          <!-- 修改密码 -->
          <a href="change_pwd.jsp" class="forget_pwd">修改密码</a>

          <!-- 登录按钮 -->
          <input type="submit" class="click_login" value="登录"/>

          <!-- 未注册 -->
          <a href="register.jsp" class="to_register">未注册,先去注册</a>
        </form>

        <!-- 其他登录方式 -->
        <div class="other_methods">
          <div class="ways">其他登录方式</div>
          <a href="javascript:;" class="icon qq"></a>
          <a href="javascript:;" class="icon wechat"></a>
        </div>

        <!-- 作者 -->
        <h3 class="author">-Directed By Elvira-</h3>
      </div>
    </main>
  </div>
</body>
</html>

 

2. login.js

功能讲解:

① 登录方式 tab 栏切换

       最开始我是在 login.jsp 把两种登录方式的 html 代码都写上,然后再父盒子上使用的display:none 和 block 来切换实现,在显示上是可以做到切换显示和隐藏,但是再代码层两种方式的代码都存在,在表单提交时,就会出现问题,因为其提交的时两种方法中4个输入框中的内容,且无法通过 required 约束表单不能为空,造成表单不能提交(因为其要求了4个输入框都需要填内容,而有两个输入框隐藏)。

转换思路:

  1. 在 login.jsp 中使用一个容器来占位 <div class="input_box"></div>
  2. 通过 js 控制具体显示的是哪种登录方法的 html 代码
  3. 在 login.js 中定义 html 模板以及变量 isAccount,给 tab 栏的登录方式添加点击事件,点击到哪种方法就展示哪个方法对应的 html 模板和样式

② 邮箱登录方式下获取验证码按钮点击后禁用,10s后解禁

思想:

  1. 定义定时器 setInterval 和变量 second(定义要禁用的时间)
  2. 禁用点击按钮
  3. 用 innerText 替换按钮中的文字,定时器每隔1s刷新一次按钮中的文字,seond自减1
  4. 若 second <= 0,清除定时器,解禁按钮,并将按钮中的文字换回:获取验证码
// 登录方式切换
window.addEventListener('load', function () {
  // 是否是账户登录
  var isAccount = true

  // html模板
  var user_template = `
    <div class="account_input">
      <div class="item">
        <i class="user-icon"></i>
        <input type="text" id="username" name="username" placeholder="请输入账号" autofocus="autofocus" required>
      </div>
      <div class="item">
        <i class="pwd-icon"></i>
        <input type="password" id="password" name="password" placeholder="请输入密码" required>
      </div>
    </div>
  `
  var phone_template = `
    <div class="phone_input">
      <div class="item_phone">
        <i class="phone-icon"></i>
        <input type="email" id="phone" name="email" placeholder="请输入邮箱" autofocus="autofocus" required>
      </div>
      <div class="item_check">
        <input type="text" id="check" name="code" placeholder="请输入验证码" required>
        <button class="getCode" type="button">获取验证码</button>
      </div>
    </div>
  `
  // 获取输入框的元素
  var input_box = document.querySelector('.input_box')

  // 挂载用户密码登录方式的html
  input_box.innerHTML = user_template

  var account_a = document.querySelector('.account_a')
  var phone_a = document.querySelector('.phone_a')
  var forget_pwd = document.querySelector('.forget_pwd')

  // 给账号登录的链接添加事件
  account_a.addEventListener('click', function () {
    // 将用户密码方法的html代码渲染,必须放在前面,不然获取不到元素
    input_box.innerHTML = user_template

    //获取输入框中的值
    var input_user = document.querySelector('#username')

    isAccount = true
    account_a.style.color = '#03a9f4'
    phone_a.style.color = '#666'
    forget_pwd.style.display = 'block'
    input_user.focus() // 解决切换页面后输入框的聚焦问题
  })

  // 给手机登录的链接添加事件
  phone_a.addEventListener('click', function () {
    // 将手机号验证码方法的html代码渲染,必须放在前面,不然获取不到元素
    input_box.innerHTML = phone_template

    //获取元素
    var phone_input = document.querySelector('.phone_input')
    //输入框中的值
    var input_phone = document.querySelector('#phone')

    isAccount = false
    account_a.style.color = '#666'
    phone_a.style.color = '#03a9f4'
    forget_pwd.style.display = 'none'
    phone_input.style.marginBottom = '8.1vh'
    input_phone.focus()
	
	// 创建XMLHttpRequest
    function CreateXmlHttp() {
      // 定义XMLHttpRequest对象
      var xhr = null
      // 创建XMLHttpRequest对象
      if (window.XMLHttpRequest) {
        // 其他浏览器
        xhr = new XMLHttpRequest()
      } else if (window.ActiveXObject) {
        // IE浏览器 IE5 IE6
        xhr = new ActiveXObject('Microsoft.XMLHTTP')
      }

      return xhr
    }

	// 获取点击获取验证码的按钮
    var getCodeBtn = document.querySelector(".getCode");

	// 获取验证码点击按钮点击后禁用
	getCodeBtn.addEventListener("click", function() {
		// 点击按钮后,将按钮禁用10秒钟
	    getCodeBtn.disabled = true;
        var second = 10;
        var timer = setInterval(function () {
          getCodeBtn.innerText = second + "s 后可重新获取"
          if (second <= 0) {
            clearInterval(timer);
            getCodeBtn.innerText = "获取验证码"
            getCodeBtn.disabled = false;
          }
          second--;
        }, 1000);

		// 发送post请求
	    // 创建XMLHttpRequest
	    var xhr = CreateXmlHttp()
		var email = input_phone.value
		
		// 指定响应函数(回调函数)
	    xhr.onreadystatechange = function () {
	      if (xhr.readyState == 4) {
	        // 请求已经完成,信息已经成功返回,开始处理信息
	        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
	          // 将从服务器端返回是JSON格式数据转换成JavaScript对象
	          var res = xhr.responseText
			  var jsonObj = eval("("+res+")")
			  console.log("res:"+res)
			  if(jsonObj.type == 0) {
				alert(jsonObj.error);
			  } else {
	          	alert("邮箱发送成功,请查阅邮箱,尽快认证")   
			  }    
	        } else {
			  alert("邮箱发送失败")   
			}
	      }
	    }

	    xhr.open('POST','/my_login/EmailServlet',true)
	    // 设置HTTP的输出内容类型为json格式数据:application/x-www-form-urlencoded
	    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
	    // 设置浏览器不使用缓存,服务器不从缓存中找,重新执行代码,而且服务器返回给浏览器的时候,告诉浏览器也不要保存缓存。
	    xhr.setRequestHeader('If-Modified-Since', '0')		    	
	    // 发送请求
	    xhr.send("email="+email);

	})
  })
})

 

3. time.js

顶部导航栏动态显示时间

// 获取时间
window.addEventListener('load', function () {
  // 获取元素
  var date = document.querySelector('.date')
  var sec = document.querySelector('.second')
  setInterval(function () {
    // 获取当前时间
    var d = new Date()
    var year = d.getFullYear() // 获取年
    var month = d.getMonth() + 1 // 获取月
    var day = d.getDate() // 获取日期
    var hour = d.getHours() // 获取小时
    var minute = d.getMinutes() // 获取分钟
    var second = d.getSeconds() // 获取秒

    if (month < 10) month = '0' + month
    if (day < 10) day = '0' + day
    if (hour < 10) day = '0' + hour
    if (minute < 10) minute = '0' + minute
    if (second < 10) second = '0' + second

    // 拼接字符串
    var date_str = year + ' 年 ' + month + ' 月 ' + day + ' 日 '
    var sec_str = hour + ' : ' + minute + ' : ' + second

    date.innerHTML = date_str
    sec.innerHTML = sec_str
  }, 1000)
})

 

4. focus.js + animate.js

轮播图的实现,animate.js是抽象出来的元素移动的函数

轮播图功能:

  • 鼠标经过轮播图模块,左右按钮显示,离开隐藏左右按钮;
  • 点击右侧按钮一次,图片向左移动播放后一张,左侧按钮同理;
  • 图片播放的同时,下面小圆圈模块跟随一起变化;
  • 点击小圆圈,可以播放相应的图片;
  • 鼠标不经过轮播图,轮播图自动播放图片;
  • 鼠标经过轮播图模块,自动播放停止

轮播图功能实现思想:

① 动态生成小圆圈

  • 核心思路:小圆圈个数与图片数目一致
  • 具体实现步骤:
    1. 首先的得到 ul 里面图片的张数(即 li 的个数)
    2. 利用循环动态生成小圆圈(小圆圈放在 ol 里面)
    3. 创建 li 节点 createElement(“li”)
    4. 插入 ol 节点 ol.appendChild(“li”)
    5. 第一个小圆圈添加 current 类(当前显示的元素的样式)

② 点击小圆圈滚动图片

  • 核心算法:点击某个小圆圈,就让图片滚动:小圆圈索引号乘以图片的宽度作为 ul 的移动距离
  • 注意:
    1. 此时用到 animate.js 函数,将 js 文件引入(因为 index.js 依赖 animate.js, 所以animate.js 要写到 index.js 上面)
    2. 使用animate.js 动画函数的前提,该元素必须要有定位
    3. 移动 ul 不是 li
    4. 需要知道小圆圈的索引号,可以在生成小圆圈的时候,给他设置一个自定义属性,点击的时候获取该自定义属性

③ 点击右侧按钮一次,就让图片滚动一张(左侧按钮类似)

  • 核心思想:声明一个变量 num,点击一次,自增1,让这个变量乘以图片宽度,就是 ul 的滚动距离
  • 图片无缝滚动原理:
    1. 把 ul 第一个 li 复制一份,放到 ul 最后面
    2. 当图片滚动到克隆的最后一张照片时, 让 ul 快速的、不做动画的跳到最左侧:left:0
    3. 同时 num 赋值为 0, 就可以重新开始滚动图片了

④ 点击右侧按钮,小圆圈跟随变化

思想:

  1. 声明变量 circle,每次点击自增1,注意:左侧按钮也需要这个变量,所以声明全局变量
  2. 图片有 4 张,小圆圈只有 3 个,所以添加判断条件:如果 circle == 3,就重新复原为 0

⑤ 自动播放

思想:

  1. 添加一个定时器,自动播放轮播图,就类似于点击了右侧按钮
  2. 使用手动调用右侧按钮点击事件 arrow_r.click()
  3. 鼠标经过轮播图就停止定时器
  4. 鼠标离开轮播图就开启定时器

⑥ 节流阀

  • 作用:防止轮播图按钮连续点击造成播放过快

  • 目的:当上一个函数动画内容执行完毕,再去执行下一个函数动画,让事件无法连续触发

  • 核心思路:利用回调函数,添加一个变量控制,锁住函数和解锁函数

    设置变量 var flag = true;

    if(flag){ flag = false; do something} 关闭水龙头

    利用回调函数,动画执行完毕,flag = true 打开水龙头

focus.js

window.addEventListener('load', function () {
  //1、获取元素
  var arrow_l = document.querySelector('.arrow-l')
  var arrow_r = document.querySelector('.arrow-r')
  var focus = document.querySelector('.focus')
  var focusWidth = focus.offsetWidth

  //2、鼠标经过focus 就显示隐藏的左右按钮
  focus.addEventListener('mouseenter', function () {
    arrow_l.style.display = 'block'
    arrow_r.style.display = 'block'
    clearInterval(timer)
    timer = null //清除定时器变量,让图片静止
  })
  //鼠标离开focus 就隐藏左右按钮
  focus.addEventListener('mouseleave', function () {
    arrow_l.style.display = 'none'
    arrow_r.style.display = 'none'
    timer = setInterval(function () {
      //手动调用点击事件
      arrow_r.click()
    }, 3500)
  })

  // 3、动态生成小圆圈,有几张图片就有几个小圆圈
  var ul = focus.querySelector('ul')
  var ol = focus.querySelector('.circle')
  for (var i = 0; i < ul.children.length; i++) {
    // 创建小li
    var li = document.createElement('li')
    // 用自定义属性记录当前小圆圈的索引号
    li.setAttribute('index', i)
    // 把小li插入到ol 里面
    ol.appendChild(li)

    // 4、小圆圈的排他思想,我们可以直接在生成小圆圈的同时直接绑定事件
    li.addEventListener('click', function () {
      // 干掉所有人 ,把所有的小li 清除current 类名
      for (var i = 0; i < ol.children.length; i++) {
        ol.children[i].className = ''
      }
      //留下我自己 当前的小li设置current属性
      this.className = 'current'

      // 5、点击小圆圈,移动图片 移动的是ul 不是li
      // 当我们点击了某个小li,就拿到当前li 的索引号
      var index = this.getAttribute('index')
      num = index //没有这句话图片不会跟着小圆点变化
      circle = index //没有这句话小圆点不会跟着变化
      animate(ul, -index * focusWidth)
    })
  }

  //把 ol 里面的第一个小li设置类名为 current
  ol.children[0].className = 'current'

  //6、克隆第一张图片放到ul最后,写在生成li的后面
  var first = ul.children[0].cloneNode(true)
  ul.appendChild(first)

  // 7、点击右侧按钮,图片滚动一张
  var num = 0
  // circle 控制小圆圈的播放
  var circle = 0
  // flag节流阀
  var flag = true
  arrow_r.addEventListener('click', function () {
    if (flag) {
      flag = false
      // 如果走到了最后一张复制的图片,此时ul要快速复原left为0
      if (num == ul.children.length - 1) {
        ul.style.left = 0
        num = 0
      }
      num++
      animate(ul, -num * focusWidth, function () {
        flag = true // 只有一张图片播放完了才展示下一张,点击多快都没有用
      })

      circle++
      //如果circle=4,说明走到克隆的那张图片了
      if (circle == ol.children.length) {
        circle = 0
      }

      circleChange()
    }
  })

  // 8.左侧按钮
  arrow_l.addEventListener('click', function () {
    if (flag) {
      flag = false
      if (num == 0) {
        num = ul.children.length - 1 //num=3
        ul.style.left = -num * focusWidth + 'px'
      }
      num--
      animate(ul, -num * focusWidth, function () {
        flag = true
      })

      circle-- //circle为序号
      // if (circle < 0) {
      //   circle = ol.children.length - 1;  //circle=2
      // }
      circle = circle < 0 ? ol.children.length - 1 : circle

      circleChange()
    }
  })

  function circleChange() {
    // 先清除其他小圆圈的current类名,
    for (var i = 0; i < ol.children.length; i++) {
      ol.children[i].className = ''
    }
    // 当前的留下current
    ol.children[circle].className = 'current'
  }

  //自动播放模块
  var timer = setInterval(function () {
    //手动调用点击事件
    arrow_r.click()
  }, 2000)
})

animate.js

function animate(obj, target, callback) {  //前面必须要加function关键字
  clearInterval(obj.timer);
  obj.timer = setInterval(function () {
    var step = (target - obj.offsetLeft) / 10;
    step = step > 0 ? Math.ceil(step) : Math.floor(step);
    if (obj.offsetLeft == target) {
      //停止动画 本质停止定时器
      clearInterval(obj.timer);
      //回调函数写到定时器结束里面
      // if (callback) {
      //   //调用函数
      //   callback();
      // }
      callback && callback();  //短路的思想
    }

    obj.style.left = obj.offsetLeft + step + 'px';
  },15);
}

登录页面界面如下:

image-20211121172817745
image-20211121173349267

2.2.2 注册页面(register.jsp)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>LARP-REGISTER</title>
	<!-- 导入基础样式 -->
  	<link rel="stylesheet" href="../css/base.css">
 	<link rel="stylesheet" href="../css/register.css">
 	<script src="../js/register.js"></script>
</head>

<body>
  <div class="container">
    <form action="/my_login/RegisterServlet" method="post" id="myform">
      <h2>新用户注册</h2>
      <div class="item">用户名称:<input type="text" name="username" id="username" required></div>
      <div class="item">设置密码:<input type="password" name="password" id="password" required></div>
      <div class="item">确认密码:<input type="password" name="again_password" id="again_password" required></div>
      <div class="button">
        <input type="submit" value="确认" id="submit">
        <input type="reset" value="重置" id="reset">
      </div>
    </form>
  </div>
</body>
</html>

注册页面界面如下:

image-20211121173253448

 

2.2.3 修改密码的页面(change_pwd.jsp)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>修改密码</title>
	<!-- 导入基础样式 -->
    <link rel="stylesheet" href="../css/base.css">
    <link rel="stylesheet" href="../css/change_pwd.css">
	<script src="../js/change_pwd.js"></script>
</head>
<body>
  <div class="container">
    <form action="/my_login/HandlePwdServlet" method="post" id="myform">
      <h2>修改密码</h2>
      <div class="item">登录名称:<input type="text" name="username" id="username" required></div>
      <div class="item">修改密码:<input type="password" name="password" id="password" required></div>
      <div class="item">确认密码:<input type="password" name="again_password" id="again_password" required></div>
      <div class="button">
        <input type="submit" value="确认" id="submit">
        <input type="reset" value="重置" id="reset">
      </div>
    </form>
  </div>
</body>
</html>

修改密码界面如下:
image-20211121174050743

2.2.4 登录成功的页面(success.jsp)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>success</title>
	<style>
	    body {
	      display: flex;
	      min-height: 100vh;
	      margin: 0;
	    }
	
	    .success {
	      height: 18vh;
	      width: 25vw;
	      line-height: 18vh;
	      margin: auto;
	      font-size: 2.4em;
	      text-align: center;
	      letter-spacing: 0.3em;
	      background-color: rgba(255, 255, 255, 0.815);
	    }
	
	    #background_video {
	      overflow: hidden;
	      width: 100%;
	      position: fixed;
	      z-index: -1;
	    }
    </style>
</head>

<body>
  <div class="success">登录成功</div>
  <video  id="background_video" muted loop autoplay>
    <source src="../images/starbroken.mp4" type="video/mp4" />
  </video>
</body>
</html>

登录成功界面:
image-20211121174132288

 

2.3 创建相关配置类

2.3.1 DAO类(DAO.java)

DAO 类增加数据库查询用户的功能

JDBC开发的步骤:

导入驱动jar包 mysql-connector-java-5.1.47-bin.jar

  • 复制粘贴到lib

  • 右键点击 build path -> add to bulid path (在java 工程当中引入了 jar 包)

注册驱动

获取数据库的连接对象 Connection

基本操作:执行sql

  • 定义sql语句
  • 获取执行sql语句的对象 Statement
  • 执行sql,用 ResultSet 接收返回的结果集
  • 遍历处理结果集

释放资源

以上的步骤2、3、5 都是在 JDBCUtil.java 中完成的

package modle;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class DAO {
	// 登录验证
	public User login(Connection conn,User user) throws Exception{
        User resultUser = null;
        // sql 查询语句
        String sql="select * from users where username=? and password=?";
        // 获得执行sql语句的对象
        PreparedStatement pstatement =conn.prepareStatement(sql);
        pstatement.setString(1, user.getUsername());
        pstatement.setString(2, user.getPassword());
        // 执行sql语句获得结果集
        ResultSet rs = pstatement.executeQuery();
        if(rs.next()){ 
            resultUser = new User();
            resultUser.setUsersname(rs.getString("username"));
            resultUser.setPassword(rs.getString("password"));
        }
        
        return resultUser;
    }
	
	// 修改密码查找用户
	public User searchUser(Connection conn,User user) throws Exception {
		User resultUser = null;
        // sql 查询语句
        String sql="select * from users where username=?";
        // 获得执行sql语句的对象
        PreparedStatement pstatement =conn.prepareStatement(sql);
        pstatement.setString(1, user.getUsername());
        // 执行sql语句获得结果集
        ResultSet rs = pstatement.executeQuery();
        if(rs.next()){ 
            resultUser = new User();
            resultUser.setUsersname(rs.getString("username"));
        }
        
        return resultUser;
	}
	
	// 注册验证
	public boolean register(Connection conn,User user) throws Exception {
		boolean flag = false;
        // sql 查询语句
        String sql="insert into users(username,password)values(?,?)";
        // 获得执行sql语句的对象
        PreparedStatement pstatement =conn.prepareStatement(sql);
        pstatement.setString(1, user.getUsername());
        pstatement.setString(2, user.getPassword());
        // 执行sql语句获得结果集
        int res = pstatement.executeUpdate();
        if(res > 0) {
        	flag = true;
        }
        return flag;
	}
}

2.3.2 JDBCUtil类(JDBCUtil.java)

JDBCUtil类提供与数据库连接时 jdbc 的相关配置

package modle;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class JDBCUtil {
	// 数据库的参数
	private String dbUrl="jdbc:mysql://localhost:3306/my_login?useSSL=false";
    private String dbUsername="root";
    private String dbPassword="XXXX"; // 自己的密码
    
    // 与数据库连接
    public Connection getConn() {
    	try {
    		// 加载驱动
    		Class.forName("com.mysql.jdbc.Driver");
		} catch (Exception e) {
			e.printStackTrace();
		}
    	Connection conn = null;
    	
    	try {
    		// 获得连接,返回connection 对象
    		conn = DriverManager.getConnection(dbUrl, dbUsername, dbPassword);
		} catch (Exception e) {
			e.printStackTrace();
		}
    	return conn;
    }
    
    // 释放资源
    // 关闭结果集 ResultSet
    public void close(ResultSet resultSet) throws Exception {
		if(resultSet != null) {
			resultSet.close();
		}
	}
    
    // 关闭 sql 语句对象 Statement
    public void close(Statement statement) throws Exception {
    	if(statement != null) {
    		statement.close();
    	}
    }
    
    // 关闭连接对象 Connection
    public void close(Connection conn) throws Exception {
    	if(conn != null) {
    		conn.close();
    	}
    }
}

2.3.3 JsonResult类(JsonResult.java)

JsonResult类处理 Json 返回的数据。

package modle;

public class JsonResult {
	private int type; //0为失败, 1为成功
	private String error;  //错误信息
	
	public int getType() {
		return type;
	}
	
	public void setType(int type) {
		this.type = type;
	}
	
	public String getError() {
		return error;
	}
	
	public void setError(String error) {
		this.error = error;
	}
}

 

2.3.4 MailUtil类(MailUtil.java)

MailUtil 类提供调用邮件发邮件时的相关配置。这里我用的126邮箱,记得去打开邮箱中的 POP3和SMTP服务,记住授权密码,需要导入 javax.mail.jar 包:
image-20211122141057536

开启服务后的界面:
image-20211122141143233

package modle;

import java.util.Properties;

import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

public class MailUtil {
	/**
	 * @param email 登录用户的邮件
	 * @param emailMsg  发送的邮件信息
	 * @throws Exception
	 */
	public void sendMail(String userEmail, String emailMsg) throws Exception {
		
        // 1. 创建一封邮件,创建一个程序与邮件服务器会话对象session
        Properties props = new Properties();
        props.setProperty("mail.transport.protocol", "SMTP");
        props.setProperty("mail.host", "smtp.126.com"); //smtp.126.com为SMTP服务器地址,为指定这个服务器发送邮件
        props.setProperty("mail.smtp.auth", "true"); // 指定验证为true
        
        // 创建验证器
        Authenticator auth = new Authenticator() {
        	public PasswordAuthentication getPasswordAuthentication() {
				return new PasswordAuthentication("xxxx", "xxxx"); //参数分别为:用户名和授权密码
			}
        };
        
        // 用于连接邮件服务器的参数配置(发送邮件时需要用到)
        Session session= Session.getInstance(props,auth);  // 根据参数配置,创建会话对象(为了发送邮件准备的)
        

        // 2.创建邮件对象message,相当于邮件内容
        Message message = new MimeMessage(session);

        // From: 发件人
        // 其中 InternetAddress 的三个参数分别为: 邮箱, 显示的昵称(只用于显示, 没有特别的要求), 昵称的字符集编码
        // 真正要发送时, 邮箱必须是真实有效的邮箱。
        message.setFrom(new InternetAddress("xxxxxxx"));  

        // To: 收件人 设置收件人和发送方式
        message.setRecipient(MimeMessage.RecipientType.TO, new InternetAddress(userEmail));
      
        // Subject: 邮件主题
        message.setSubject("邮箱验证");

        // Content: 邮件正文(可以使用html标签)
        message.setContent(emailMsg, "text/html;charset=UTF-8");

        // 3. 创建 transport 用于将邮件发出
        Transport.send(message);
    }
}

 

2.3.5 User类(User.java)

User类提供用户基本信息的配置

package modle;

public class User {
	private String username;
	private String password;
	
	// 构造函数
	public User() {}
	 
	public User(String username, String password) {
		this.username = username;
		this.password = password;
	}
	
	// 获取用户名
	public String getUsername() {
		return username;
	}
	
	// 设置用户名
	public void setUsersname(String username) {
		this.username = username;
	}
	
	// 获取密码
	public String getPassword() {
		return password;
	}
	
	// 设置密码
	public void setPassword(String password) {
		 this.password = password;
	}
}

 

2.4 创建 Servlet

2.4.1 登录界面的Servlet(LoginServlet)

主要思想:

  1. 接收前台传来的值:账号和密码、邮箱和验证码,通过判断账号和密码或是邮箱和验证码谁不为空判断出前台使用的哪种登录方式
  2. 邮箱和验证码登录方式中:检验验证码是否正确是从 session 中取出 emailCode 的内容(在EmailServlet.java中在随机创建出6位的验证码后就把其存入 session 中了)与用户输入的进行核对。但其不够完美,因为有时间限制,超出时间后 session 就失效了。
package controller;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.alibaba.fastjson.JSON;

import modle.DAO;
import modle.JDBCUtil;
import modle.JsonResult;
import modle.User;

/**
 * 登录的 Servlet
 */

public class LoginServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {		
		this.doPost(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {				
		
		// 接收前台传来的值 账号和密码 或是 邮箱和验证码
        String username = request.getParameter("username");        
        String password = request.getParameter("password");
        String email = request.getParameter("email");
        String code = request.getParameter("code");
                
        System.out.println(password);
        System.out.println(email);
        System.out.println(code);
        
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        
        // 账号密码登录的方式
        if(username != null && password != null) {
            //解决中文字符乱码
            byte[] bytes = username.getBytes("ISO-8859-1");
            username = new String(bytes,"utf-8");
            System.out.println(username);
            
        	JDBCUtil db = new JDBCUtil();
            // 创建一个用户保存下将密码和用户名保存
            User user = new User(username,password);
            DAO dao = new DAO();
            try {
                //数据库连接
                Connection conn = db.getConn();
                
                if(dao.login(conn, user) != null) {
                	request.getSession().setAttribute("user", user);
                	response.sendRedirect("jsp/success.jsp");
                } else {
                	out.println("<h1>用户名或者密码错误,验证失败</h1>");
                	out.println("<h2>3秒以后跳转回登录页面</h2>");
                	response.setHeader("Refresh", "3;url=jsp/login.jsp");
                }            
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
    			out.close();
    		}
        } else if(email != null && code != null) { //邮箱验证码方式
        	// 判断emailCode是否正确
        	String s_emailCode = (String)request.getSession().getAttribute("emailCode");
        	JsonResult jr = new JsonResult();
        	if(!code.equalsIgnoreCase(s_emailCode)) {
            	out.println("<h1>邮件验证码错误,验证失败</h1>");
            	out.println("<h2>3秒以后跳转回登录页面</h2>");
            	response.setHeader("Refresh", "3;url=jsp/login.jsp");
        	} else { // 验证成功
        		response.sendRedirect("jsp/success.jsp");
        	}
        	out.close();
        }      
	}
}

 

2.3.2 注册界面的Servlet(RegisterServlet)

package controller;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import modle.DAO;
import modle.JDBCUtil;
import modle.User;

/**
 * 注册的servlet
 */
public class RegisterServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {		
		this.doPost(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		// 获取注册名和密码
		String username = request.getParameter("username").trim();
        String password = request.getParameter("password").trim();
        String again_password = request.getParameter("again_password").trim();
        
        //解决中文字符乱码
        byte[] bytes = username.getBytes("ISO-8859-1");
        username = new String(bytes,"utf-8");
        
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        
        JDBCUtil db = new JDBCUtil();
        // 创建一个用户保存下将密码和用户名保存
        User user = new User(username,password);
        DAO dao = new DAO();
        
        try {
        	//数据库连接
            Connection conn = db.getConn();
            
            if(!password.equals(again_password)) {
            	out.println("<h2>两次输入的密码不一致</h2>");
	        	out.println("<h2>3秒以后返回注册页面</h2>");
            	response.setHeader("Refresh", "3;url=jsp/register.jsp");
            } else {
            	if(dao.register(conn, user)) {
            		out.println("<h1>注册新用户成功</h1>");
                	out.println("<h2>3秒以后跳转回注册页面</h2>");
                	response.setHeader("Refresh", "3;url=jsp/login.jsp");                
                } else {
                	out.println("<h1>注册新用户失败,用户名已经存在</h1>");
                	out.println("<h2>3秒以后跳转回注册页面</h2>");
                	response.setHeader("Refresh", "3;url=jsp/register.jsp");
                }
            }            
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			out.close();
		}
	}
}

 

2.3.3 修改密码的Servlet(HandlePwdServlet)

package controller;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import modle.DAO;
import modle.JDBCUtil;
import modle.User;

/**
 * 修改密码的 servlet
 */
public class HandlePwdServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		this.doPost(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// 获取数据
		String username = request.getParameter("username").trim();
        String password = request.getParameter("password").trim();
        String again_password = request.getParameter("again_password").trim();
        //解决中文字符乱码
        byte[] bytes = username.getBytes("ISO-8859-1");
        username = new String(bytes,"utf-8");
        
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        
        JDBCUtil db = new JDBCUtil();
        // 创建一个用户保存下将密码和用户名保存
        User user = new User(username,password);
        DAO dao = new DAO();
        
        try {
        	//数据库连接
            Connection conn = db.getConn();
            // 数据库中没有该用户
            if(dao.searchUser(conn, user) == null) {
	        	out.println("<h2>该用户不存在,请先去注册</h2>");
	        	out.println("<h2>3秒以后返回修改密码页面</h2>");
            	response.setHeader("Refresh", "3;url=jsp/change_pwd.jsp");
            } else {
				if(!password.equals(again_password)) {
					out.println("<h2>两次输入的密码不一致</h2>");
		        	out.println("<h2>3秒以后返回修改密码页面</h2>");
	            	response.setHeader("Refresh", "3;url=jsp/change_pwd.jsp");
				} else {
					String sql="update users set password=? where username=?";
			        // 获得执行sql语句的对象
			        PreparedStatement pstatement =conn.prepareStatement(sql);
			        pstatement.setString(1, user.getPassword());
			        pstatement.setString(2, user.getUsername());
			        // 返回受影响修改的行数
			        int res = pstatement.executeUpdate();
			        if(res != 0) {
			        	out.println("<h1>修改密码成功</h1>");
		            	out.println("<h2>3秒以后跳转回登录页面</h2>");
		            	response.setHeader("Refresh", "3;url=jsp/login.jsp");
			        } else {
			        	out.println("<h2>修改密码失败</h2>");
			        	out.println("<h2>3秒以后返回修改密码页面</h2>");
		            	response.setHeader("Refresh", "3;url=jsp/change_pwd.jsp");
					}
				}				
			}
        } catch (Exception e) {
        	e.printStackTrace();
		} finally {
			out.close();
		}
	}
}

 

2.3.4 发送邮件的Servlet(EmailServlet)

主要思想:

  1. 获取前台用户的邮箱
  2. 随机生成6位数的验证码(需要导入commons-lang3-3.12.0.jar包)
  3. 在服务器端通过 session 保存验证码
  4. 通过 MailUtil 中对邮箱的配置发送邮件
package controller;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.RandomStringUtils;

import com.alibaba.fastjson.JSON;

import modle.JsonResult;
import modle.MailUtil;
import net.sf.json.JSONObject;

/**
 * 处理邮件的Servlet
 */
public class EmailServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		this.doPost(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取邮箱
		String email = request.getParameter("email");
        System.out.println(email);
        // 获取随机的6位数
        String num = RandomStringUtils.randomNumeric(6);
        // 邮件内容
        String emailMsg = "邮箱验证码为:"+ num +"请勿泄漏给他人!";
        
        // 在服务器端保存邮件验证码
        request.getSession().setAttribute("emailCode", num);
        
        JsonResult jr = new JsonResult();
        
        try {
        	MailUtil mail = new MailUtil();
			// 发送邮件
        	mail.sendMail(email, emailMsg);
        	jr.setType(1); // 发送成功
        	response.getWriter().write(JSON.toJSONString(jr));
        	return;
		} catch (Exception e) {			
			e.printStackTrace();
			jr.setType(0); // 发送失败
			jr.setError("邮件发送失败");
			response.getWriter().write(JSON.toJSONString(jr));
        	return;
		}
	}
}

注意:

  1. 在运行过程中,mail.sendMail(email, emailMsg); 这部总是报 java.lang.ClassNotFoundException: javax.activation.DataHandler的错误,查找资料下载导入了 javax.activation-1.2.0.jar就解决问题了

  2. 使用 JSON.toJSONString(jr)是导入了第三方的包来对 Json 进行快速处理,使用该方法可以导入两种类型的包:

    ① 导入fastjson-1.2.75.jar 一个包就行, 下载地址:Maven Repository: com.alibaba » fastjson (mvnrepository.com)

    ② 导入 6 个包:

    • commons-beanutils-1.9.4.jar

    • commons-collections4-4.4.jar

    • commons-lang3-3.12.0.jar

    • commons-logging-1.2.jar

    • ezmorph-1.0.6.jar

    • json-lib-2.4-jdk15.jar

    可以在下面的地址中下载:

    • http://commons.apache.org/index.html

    • http://json-lib.sourceforge.net/

    • http://ezmorph.sourceforge.net/

    • http://www.docjar.com/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/405266.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何使用nvm切换npm版本

我比较懒惰,如非必要,不喜欢npm版本切换来切换去,感觉浪费我编程的时间.后来发现,现在偷的懒都是为将来的忙碌埋下的祸根. 言归正传,本文主要是讲解一下,如何使用nvm进行npm版本的切换. 工欲善其事必先利其器,我们先下载nvm;直接上下载链接(针对windows哈,毕竟我没mac本); 下载…

前端开发工程师简历

简历是什么 找工作之前投递的个人信息和工作能力----不全面 应该是&#xff1a;个人当前阶段的价值体现 前者&#xff1a;我能卖多少钱&#xff1b;后者&#xff1a;我现在值多少钱 建议&#xff1a;每隔半年/一年给自己做总结的时候写一份简历&#xff08;相当于个人价值总结…

render函数

render函数 通常我们都会把我们的页面结构逻辑都写在 template 中&#xff0c;然后再通过vue将我们的代码转换成虚拟DOM&#xff0c;相比于真实DOM&#xff0c;虚拟DOM是通过js代码处理的&#xff0c;所以消耗的性能相对较小&#xff0c;当然大部分情况下使用 template 创建我…

(详解)Vue设置select下拉框的默认选项(解决select空白的bug)

最近在用vue设置表单数据时发现了一个小问题&#xff1a;用vue动态渲染select下拉框时&#xff0c;select下拉框会出现空白的bug。 <template><div><select name"art-cate" v-model"select"><option disabled selected style"d…

Vue项目启动出现的问题及解决方法

今天在公司入职第二天&#xff0c;昨天拉下来的代码没有跑起来&#xff0c;看了各种博客也没有解决这个报错 今天一大早来公司&#xff0c;捋了一下顺序 ①删除要启动项目里的 node_modules 文件夹&#xff0c;这是vue项目的依赖包。 因为“node_modules”文件夹太大&#x…

《Vue插件》瀑布流插件vue-masonry的使用与踩坑记录

这是一个没有套路的前端博主&#xff0c;热衷各种前端向的骚操作&#xff0c;经常想到哪就写到哪&#xff0c;如果有感兴趣的技术和前端效果可以留言&#xff5e;博主看到后会去代替大家踩坑的&#xff5e; 主页: oliver尹的主页 格言: 跌倒了爬起来就好&#xff5e; 《Vue插件…

Moment.js的基本使用

一、Moment.js的简介 Moment.js是一个轻量级的JavaScript时间库&#xff0c;以前我们转化时间&#xff0c;都会进行很复杂的操作&#xff0c;而Moment.js的出现&#xff0c;简化了我们开发中对时间的处理&#xff0c;提高了开发效率。日常开发中&#xff0c;通常会对时间进行下…

React面试题最全

1.什么是虚拟DOM&#xff1f; 虚拟DOM是真实DOM在内存中的表示&#xff0c;ul的表示形式保存在内存中&#xff0c;并且与实际的DOM同步&#xff0c;这是一个发生在渲染函数被调用和元素在屏幕上显示的步骤&#xff0c;整个过程被称为调和 2.类组件和函数组件之间的区别是什么…

【uni-app】小程序实现微信在线聊天(私聊/群聊)

之前学习使用uni-app简单实现一个在线聊天的功能&#xff0c;今天记录一下项目核心功能的实现过程。页面UI以及功能逻辑全部来源于微信&#xff0c;即时聊天业务的实现使用socket.io&#xff0c;前端使用uni-app开发&#xff0c;后端服务器基于node实现&#xff0c;数据库选择m…

vue3生命周期及setup介绍

&#x1f337; 生命周期 下图对比了vue3&#xff08;左&#xff09;和vue2&#xff08;右&#xff09;的生命周期&#xff1a;vue3将destoryed该名成了unmounted&#xff0c;相应的beforeDestory改成了beforeUnmounted。除此之外在组合式API中新增了个钩子函数&#xff1a;set…

vue项目:大屏自适应解决方案(两种)

css缩放方案&#xff1a; 利用transform&#xff1a;scale 进行适配 推荐使用v-scale-screen 值得注意的是&#xff1a; vue 2.6、2.7 要使用 npm install v-scale-screen1.0.2 vue3&#xff1a;要使用v-scale-screen版本 npm install v-scale-screen2.0.0 用法&#x…

解决npm ERR! Cannot read properties of null (reading ‘pickAlgorithm‘)

文章目录1. 复现问题2. 分析问题3. 解决问题1. 复现问题 今天准备克隆Redis桌面(GUI)管理客户端&#xff0c;故按照官方文档给出的指令运行时&#xff0c;如下图所示&#xff1a; 但在执行指令npm install --platformwin32却报出如下图错误&#xff1a; PS D:\Software\Redis…

Vue中使用Datav 完成大屏基本布局

效果图 大屏前言 在实际开发过程中&#xff0c;我们经常需要一个大屏进行一些常规数据的展示。在Vue中也是提供了这样的容器组件 我们可以使用基于Vue的 Datav组件 Vue-Baidu-Map地图组件 Echarts图表组件 时间戳就实现基本布局 在Vue中配置大屏路由的时候&#xff0c;我们…

vue播放rtsp视频流

工作有要播放视频监控的需求&#xff0c;最近就自己先了解了一下网页播放rtsp视频流的方法&#xff0c;以下是我的个人经验。 最终选择了vuewebrtc-streamer实现在网页播放rtsp流这种方法进行测试。 个人经验 第一次尝试了vue-video-playervideojs的方法&#xff0c;发现只适…

WPS JS宏入门案例集锦

JS宏官方API文档&#xff1a;https://qn.cache.wpscdn.cn/encs/doc/office_v19/index.htm 批量创建工作表/簿 批量创建工作表&#xff1a; function 批量创建工作表(){for (var city of ["成都","上海","北京"]){let sht Worksheets.Add();s…

无需本地部署 在线使用Stable Diffusion Webui 使用共享模型

尝试本地部署Stable Diffusion的时候遇到了很多的麻烦&#xff0c;自己训练AI也非常的麻烦&#xff0c;可以尝试使用Webui使用别人上传的模型第一步进入网站https://github.com/camenduru/stable-diffusion-webui-colab向下拉到readme第一个 stable_diffusion_webui_colab&…

前端常见八大设计模式

一、设计模式是什么&#xff1f; 设计模式是在某种场合下对某个问题的一种解决方案。设计模式是通过概念总结出来的模版&#xff0c;总结出来的固定的东西。每一个模式描述了一个在我们周围不断重复发生的问题&#xff0c;以及该问题的解决方案的核心。 二、设计原则–设计模…

给女友的网页小惊喜,(生日,周年,表白通用) ☞谁说程序员不懂浪漫

有女朋友的拿去给女朋友一个惊喜&#xff0c;没女朋友的拿去表白&#xff0c;或者NEW它10000000个&#xfeff;ε≡٩(๑>₃<)۶ 文章目录前言适用范围网页展示登录界面文字界面图片界面尾部界面获取源码前言 前些日子是女友的一周年&#xff0c;康康想用一种特殊的方式…

vue实现导出word文档(含多张图片)

一、实现效果 以填写并导出房屋出租审批表为例&#xff0c;首先填写表格相应内容后&#xff0c;点击" 导出 "按钮实现word文档的导出功能&#xff0c;界面如下所示&#xff1a; 最后导出word文档如下所示&#xff1a; 二、所需插件 这里使用npm对以下所需依赖进…

【SpringBoot+Vue】全网最简单但实用的前后端分离项目实战笔记 - 前端

配套视频地址&#xff1a;https://www.bilibili.com/video/BV1dG4y1T7yp/ 前端笔记 1. node环境 官网&#xff1a;https://nodejs.org 注意&#xff0c;node可以比我稍低&#xff0c;但不要更高 2. 下载vue-admin-template https://panjiachen.gitee.io/vue-element-admin…