使用SSM框架实现个人博客管理平台以及实现Web自动化测试
文章目录
- 前言
- 1. 项目概述
- 2. 项目需求
- 2.1功能需求
- 2.2 其他需求
- 2.3 系统功能模块图
- 3. 开发环境
- 4. 项目结构
- 5. 部分功能介绍
- 5.1 数据库密码密文存储
- 5.2 统一数据格式返回
- 5.3 登录拦截器
- 6. 项目展示
- 7. 项目测试
- 7.1 测试用例
- 7.2 执行部分自动化测试用例
前言
在几个月前实现了一个servlet版本的博客系统,本项目则是在原有基础上进行升级。使用SSM框架、数据库密码使用加盐算法加密、使用Redis对session进行持久化存储从而支持分部式部署、通过拦截器实现用户登录校验、对于数据格式返回以及异常处理进行统一功能处理、增加分页以及点击量统计等功能。
1. 项目概述
用户进入主页可以看到他人发布的博客,同时也可以注册账号通过本平台编写发布博客,平台支持markdown编辑;用户也可以在个人中心修改个人信息,同时对博客可以随写随存以草稿形式,之后也可以发布修改草稿。用户可以通过博客的点击量,判断博客质量等等。
2. 项目需求
2.1功能需求
- 登录与注册功能
- 个人中心(查看、修改个人信息)
- 查看博客详情
- 编写个人博客
- 修改个人博客
- 删除个人博客
- 草稿箱(保存博客为草稿,修改博客为草稿,发布草稿为博客,草稿增删改查)
- 博客列表分页查询功能
- 博客阅读量统计
- 注销功能
2.2 其他需求
- 系统界面有良好的视觉体验,营造美观舒适的界面;
- 操作简单流畅,无可视卡顿;
- 有良好的引导提示,增强用户体验;
2.3 系统功能模块图
3. 开发环境
1.前端开发:
技术:HTML、CSS、JavaScript
工具:IntelliJ IDEA 2021.3.1
2.后端开发:
服务器:tomcat-8.5.50
数据库:MySQL5.7
开发语言:Java
技术框架:SpringBoot、SpringMVC、Mybatis、Redis
管理工具:Maven
开发工具:Intellij IDEA 2020.1.4
操作系统:Windows10
4. 项目结构
Java源代码位于com.example.blog_system中;
- common包:设置统一数据格式返回代码(AjaxResult)、全局变量用于session存储(AppVariable)、密码加盐已经密码验证(PasswordUtils)、获取当前用户(UserSessionUtils)。
- config包:系统配置文件(Appconfig)用于设置登录拦截器规则、登录拦截器的实现(LoginIntercept)、数据返回增强,校验是否是统一格式返回不是则封装为统一格式(ResponseAdvice)
- controller包:控制层;连接页面请求和服务层,获取页面请求的参数,通过自动装配,映射不同的URL到相应的处理函数,并获取参数,对参数进行处理,之后传给服务层。关于博客文章的控制器(ArticleController),实现博客以及草稿的增删改查以及列表分页点击量统计等功能、关于用户的控制器(UserController)实现增删改查功能。
- entity包:实体类,定义文章以及用户属性,同时定义视图UserVo的属性。
- service包:服务层;为控制层提供服务,接受控制层的参数,完成相应的功能,并返回给控制层;
- mapper包:持久层;根据service相关功能定义与数据库相关的增删改查方法。
在resources包中保存着系统资源,其中:
- mapper:根据 com.example.blog_system.mapper中的方法实现sql语句的增删改查。
- static:存储系统的前端页面HTML,CSS样式,markdown编辑器,jquery等相关资源。
5. 部分功能介绍
5.1 数据库密码密文存储
基于spring框架提供的md5加密的基础上通过UUID生成随机盐值,对用户密码进行加密;
加密过程如下:
//1.加盐并生成密码public static String encrypt(String password){//1.生成盐值String salt = UUID.randomUUID().toString().replace("-","");//2.生成加盐之后的密码String saltpassword = DigestUtils.md5DigestAsHex((salt+password).getBytes());//3。生成最终密码,32位盐值+"$"+加盐密码String finalpassword = salt+"$"+saltpassword;retur
在进行用户登录密码校验中,密码解密过程,从数据库取出密码,得到盐值,根据盐值以及用户输入密码进行加密,校验与数据库存储密码是否相同。
具体实现如下:
//2.生成最终密码(数据库中的,便于密码验证)public static String encrypt(String password,String salt){//2.通过盐值和原始密码生成加盐之后的密码String saltpassword = DigestUtils.md5DigestAsHex((salt+password).getBytes());
// 最终密码String finalPassword = salt+"$"+saltpassword;return finalPassword;}//3.验证密码/**** @param inputPassword 用户输入密码* @param finalPassword 数据库存储密码* @return*/public static boolean check(String inputPassword,String finalPassword){//先进性非空校验和长度校验if (StringUtils.hasLength(inputPassword)&&StringUtils.hasLength(finalPassword)&&finalPassword.length()==65){//1.得到盐值,从finalPassword,对$加上\\进行转义,它是关键字String salt = finalPassword.split("\\$")[0];//2.使用之前加密步骤将明文加密并生成最终密码String confinmrPassword = PasswordUtils.encrypt(inputPassword,salt);//3,进行比较return confinmrPassword.equals(finalPassword);}return false;}
5.2 统一数据格式返回
定义数据格式为状态码+状态码描述信息+返回数据,实现成功以及失败的返回。
具体实现如下:
package com.example.blog_system.common;import lombok.Data;import java.io.Serializable;/*** 统一数据格式返回*/
@Data
public class AjaxResult implements Serializable {// 状态码private Integer code;// 状态码描述信息private String msg;// 返回的数据private Object data;/*** 操作成功返回的结果*/public static AjaxResult succecc(Object data) {AjaxResult result = new AjaxResult();result.setCode(200);result.setMsg("");result.setData(data);return result;}public static AjaxResult succecc(int code, Object data) {AjaxResult result = new AjaxResult();result.setCode(code);result.setMsg("");result.setData(data);return result;}public static AjaxResult succecc(int code, String msg, Object data) {AjaxResult result = new AjaxResult();result.setCode(code);result.setMsg(msg);result.setData(data);return result;}/*** 返回失败结果*/public static AjaxResult fail(int code, String msg) {AjaxResult result = new AjaxResult();result.setCode(code);result.setMsg(msg);result.setData(null);return result;}public static AjaxResult fail(int code, String msg, Object data) {AjaxResult result = new AjaxResult();result.setCode(code);result.setMsg(msg);result.setData(data);return result;}}
5.3 登录拦截器
继承HandlerInterceptor实现一个登录拦截器,判断在用户进入主页、登录页、注册页、以外的页面进行相关操作时判断是否登录,未登录则自动跳转到登录页面。
具体实现如下:
/**登录拦截器* @author zq* @date 2023-07-22 22:33*/public class LoginIntercept implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {HttpSession session = request.getSession(false);if(session!=null&&session.getAttribute(AppVariable.USER_SESSION_KEY)!=null){//用户已登录return true;}//跳转到登录页面response.sendRedirect("/login.html");return false;}
}
6. 项目展示
主页:
我的博客列表页:
博客编辑页:
草稿列表页:
草稿编辑页:
个人中心:
修改个人信息:
登录页面:
注册页面:
7. 项目测试
接下来将对个人博客管理平台功能以及界面进行测试,编写Web自动化测试用例。
7.1 测试用例
编写的测试用例如下:
7.2 执行部分自动化测试用例
登录功能:执行登录测试用例检验结果是否符合预期、界面是否符合预期;
检测页面是否正确加载:
/*** 打开网页*/@Test@Order(1)public void openWeb() throws InterruptedException {driver.get("http://124.221.76.124:59090/login.html");sleep(3000);}//检测页面是否正常打开,元素是否存在@Test@Order(2)public void elementAppear(){driver.findElement(By.cssSelector("body > div.login-container > div > h3"));driver.findElement(By.cssSelector("#username"));driver.findElement(By.cssSelector("#password"));driver.findElement(By.cssSelector("#submit"));}
测试结果如下:符合预期
检测用户名密码错误是否登录失败:
@ParameterizedTest@CsvSource({"admin,12345"})@Order(2)//登录异常检测public void loginAbnormalTest(String username, String password) throws IOException, InterruptedException {//先清除用户名和密码框driver.findElement(By.cssSelector("#username")).clear();driver.findElement(By.cssSelector("#password")).clear();//输入账号和密码driver.findElement(By.cssSelector("#username")).sendKeys(username);driver.findElement(By.cssSelector("#password")).sendKeys(password);driver.findElement(By.cssSelector("#submit")).click();//期望结果和实际结果String expect = "登录失败,用户名或密码错误请重试";String actual = driver.findElement(By.cssSelector("body")).getText();Assertions.assertEquals(expect, actual);sleep(5000);}
测试结果如下:符合预期
检测用户名已经密码正确是否登录成功并且跳转到正确的用户下
/***登录正常测试*/@ParameterizedTest@CsvSource({"admin,123456"})@Order(3)public void loginNormalTest(String username, String password) throws IOException, InterruptedException {//先清除用户名和密码框driver.findElement(By.cssSelector("#username")).clear();driver.findElement(By.cssSelector("#password")).clear();//输入用户名和密码并点击登录driver.findElement(By.cssSelector("#username")).sendKeys(username);driver.findElement(By.cssSelector("#password")).sendKeys(password);driver.findElement(By.cssSelector("#submit")).click();//查看跳转页面是否是博客列表页sleep(3000);String url = "http://124.221.76.124:59090/myblog_list.html";Assertions.assertEquals(url,driver.getCurrentUrl());// 校验当前登录的用户是不是admin,如果是测试通过,否则测试不通过String user_name = driver.findElement(By.cssSelector("#username")).getText();driver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);Assertions.assertEquals(username, user_name);sleep(5000);}
测试结果如下:符合预期
分页查询功能:执行分页查询功能的测试用例查看主页响应是否符合预期
检查首页的博客内容是否正确显示:
//检查首页的博客标题,内容是否正确显示@Test@Order(1)public void checkElements(){String actual = driver.findElement(By.cssSelector("#artListDiv > div:nth-child(1) > div.title")).getText();Assertions.assertEquals("保姆级自动化测试教程(Selenium+java)",actual);String actual1 = driver.findElement(By.cssSelector("#artListDiv > div:nth-child(1) > div.date")).getText();Assertions.assertEquals("2023-07-31",actual1);String actual2 = driver.findElement(By.cssSelector("#artListDiv > div:nth-child(1) > div.desc")).getText();Assertions.assertEquals("自动化测试指软件测试的自动化,在预设状态下运行应用程序或者系统,预设条件包括正常和异常,最后评估运行结果。将人为驱动的测试行为转化为机器",actual2);}
测试结果如下:符合预期
检查分页查询:首页、上一页、下一页、末页功能是否正常
//检查分页查询功能@Testpublic void checkCha() throws InterruptedException {//首页driver.findElement(By.cssSelector("body > div.container > div > div.blog-pagnation-wrapper > button:nth-child(1)")).click();sleep(3000);Alert alert = driver.switchTo().alert();String atext = alert.getText();Assertions.assertEquals("已经是第一页",atext);//点击确定关闭弹窗alert.accept();//上一页driver.findElement(By.cssSelector("body > div.container > div > div.blog-pagnation-wrapper > button:nth-child(2)")).click();sleep(3000);Alert alert2 = driver.switchTo().alert();String atext2 = alert.getText();Assertions.assertEquals("已经是第一页",atext2);alert2.accept();//下一页driver.findElement(By.cssSelector("body > div.container > div > div.blog-pagnation-wrapper > button:nth-child(3)")).click();sleep(3000);Alert alert3 = driver.switchTo().alert();String atext3 = alert.getText();Assertions.assertEquals("已经是最后一页",atext3);alert3.accept();//尾页driver.findElement(By.cssSelector("body > div.container > div > div.blog-pagnation-wrapper > button:nth-child(4)")).click();sleep(3000);Alert alert4 = driver.switchTo().alert();String atext4 = alert.getText();Assertions.assertEquals("已经是最后一页",atext4);alert4.accept();}
测试结果如下:符合预期
博客编辑功能:查看编辑发布博客、保存为草稿以及博客编辑页面是否正常显示
检查不输入标题时提示信息是否正确,检查输入标题发布文章时是否跳转到我的博客列表页
@Test//@Order(2)//检查发布文章功能是否正常,分为输入为空、非空public void checkAdd() throws InterruptedException {sleep(3000);driver.findElement(By.cssSelector("body > div.nav > a:nth-child(5)")).click();sleep(3000);//1.输入为空driver.findElement(By.xpath("/html/body/div[2]/div[1]/button[1]")).click();sleep(3000);Alert alert1 = driver.switchTo().alert();String atext1 = alert1.getText();Assertions.assertEquals("确认提交?",atext1);alert1.accept();sleep(3000);Alert alert = driver.switchTo().alert();String atext = alert.getText();Assertions.assertEquals("请先输入标题!",atext);alert.accept();sleep(3000);//2,输入不为空时,是否正常发布,跳转到博客列表页driver.findElement(By.cssSelector("#title")).sendKeys("发布文章测试");sleep(3000);driver.findElement(By.xpath("/html/body/div[2]/div[1]/button[1]")).click();sleep(3000);Alert alert2 = driver.switchTo().alert();String atext2 = alert1.getText();Assertions.assertEquals("确认提交?",atext1);alert1.accept();sleep(3000);//不再继续提交Alert alert3 = driver.switchTo().alert();alert3.dismiss();sleep(3000);//验证当前页面是否是博客列表页面String url = driver.getCurrentUrl();Assertions.assertEquals("http://124.221.76.124:59090/myblog_list.html",url);}
测试结果如下:符合预期
同上述,检查保存为草稿时是否符合预期:
测试结果如下符合预期:
博客相关功能:删除、查看全文、修改
草稿相关功能:删除、查看全文、修改
测试代码与结果如下符合预期:
篇幅有限,关于其他功能的测试在此省略,测试完整代码可以查看我的码云:https://gitee.com/zxxqqa/automated-testing
总结:根据测试用例结果基本符合预期可以确定个人博客管理平台的基本功能能够正确执行。
相关文章:

使用SSM框架实现个人博客管理平台以及实现Web自动化测试
文章目录 前言1. 项目概述2. 项目需求2.1功能需求2.2 其他需求2.3 系统功能模块图 3. 开发环境4. 项目结构5. 部分功能介绍5.1 数据库密码密文存储5.2 统一数据格式返回5.3 登录拦截器 6. 项目展示7. 项目测试7.1 测试用例7.2 执行部分自动化测试用例 前言 在几个月前实现了一…...

【深度学习】MAT: Mask-Aware Transformer for Large Hole Image Inpainting
论文:https://arxiv.org/abs/2203.15270 代码:https://github.com/fenglinglwb/MAT 文章目录 AbstractIntroductionRelated WorkMethod总体架构卷积头Transformer主体Adjusted Transformer Block Multi-Head Contextual Attention Style Manipulation Mo…...

PyTorch BatchNorm2d详解
通常和卷积层,激活函数一起使用...

慕课网Go-4.package、单元测试、并发编程
package 1_1_User.go package usertype User struct {Name string }1_1_UserGet.go package userfunc GetCourse(c User) string {return c.Name }1_1_UserMain.go package mainimport ("fmt"Userch03 "goproj/IMOOC/ch03/user"//别名,防止同名…...

[JavaWeb]SQL介绍-DDL-DML
SQL介绍-DDL-DML 一.SQL简介1.简介2.SQL通用语法3.SQL语言的分类 二.DDL-操作数据库与表1.DDL操作数据库2.DDL操作表①.查询表(Retrieve)②.创建表(Create)③.修改表(Update)④.删除表(Delete) 三.Navicat的安装与使用四.DML-操作表数据1.添加(Insert)2.修改(Update)3.删除(Del…...
git源码安装(无sudo权限)
git源码安装(无sudo权限) 下载源码解压安装添加到环境变量 下载源码 去https://mirrors.edge.kernel.org/pub/software/scm/git/下载需要的版本。我下载的是2.41.0版本 wget https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.41.0.tar.gz解…...

Web3 叙述交易所授权置换概念 编写transferFrom与approve函数
前文 Web3带着大家根据ERC-20文档编写自己的第一个代币solidity智能合约 中 我们通过ERC-20一种开发者设计的不成文规定 也将我们的代币开发的很像个样子了 我们打开 ERC-20文档 我们transfer后面的函数就是transferFrom 这个也是 一个账号 from 发送给另一个账号 to 数量 val…...

Redis系列二:Clion+MAC+Redis环境搭建
1. ClionMACRedis-3.0-annotated环境搭建 参考: https://github.com/huangz1990/redis-3.0-annotated https://gitee.com/dumpcao/redis-3.0-annotated-cmake-in-clion https://tool.4xseo.com/a/12910.html 1.1 下载并导入Clion git clone https://gitee.com/dum…...

Linux下 Docker容器引擎基础(2)
目录 创建私有仓库 将修改过的nginx镜像做标记封装,准备上传到私有仓库 将镜像上传到私有仓库 从私有仓库中下载镜像到本地 CPU使用率 CPU共享比例 CPU周期限制 CPU 配额控制参数的混合案例 内存限制 Block IO 的限制 限制bps 和iops 创建私有仓库 仓库&a…...

现场服务管理系统有哪些?5个现场服务管理软件对比
现场售后服务管理软件的使用者通常是机械设备、家电、仪表仪器、医疗器械等厂商的工程师和客服调度人员。现场售后服务管理软件可将服务过程标准化,包括工单派发、服务过程步骤、配件订购出货和付款、客户评价都有系统支持,有的现场售后服务软件还支持数…...

leetcode 912.排序数组
⭐️ 题目描述 🌟 leetcode链接:排序数组 思路: 此题如果使用冒泡插入选择这些时间复杂度 O ( N 2 ) O(N^2) O(N2) 的算法会超时,使用快排 优化也过不去,因为里面有一个测试用例全是 2 即使加了三数取中也会是 O (…...

利用MMPreTrain微调图像分类模型
前言 MMPreTrain是一款基于PyTorch的开源深度学习预工具箱,是OpenMMLab项目的成员之一MMPreTrain的主要特性有: 支持多元化的主干网络与预训练模型支持多种训练策略(有监督学习,无监督学习,多模态学习等)提…...

express学习笔记3 - 三大件
便于统一管理router,创建 router 文件夹,创建 router/index.js: const express require(express)// 注册路由 const router express.Router() router.get(/,function(req,res){res.send(让我们开始express之旅) }) /*** 集中处理404请求的…...

Java课题笔记~Maven基础
2、Maven 基础 2.1 Maven安装与配置 下载安装 配置:修改安装目录/conf/settings.xml 本地仓库:存放的是下载的jar包 中央仓库:要从哪个网站去下载jar包 - 阿里云的仓库 2.2 创建Maven项目...

三步问题(力扣)n种解法 JAVA
目录 题目:1、dfs:2、dfs 备忘录(剪枝):(1)神器 HashMap 备忘录:(2)数组 memo 备忘录: 3、动态规划:4、利用 static 的储存功能:&…...
flask---》登录认证装饰器/配置文件/路由系统
登录认证装饰器 # 0 装饰器的本质原理-# 类装饰器:1 装饰类的装饰器 2 类作为装饰器 # 1 装饰器使用位置,顺序 # 3 flask路由下加装饰器,一定要加endpoint-如果不指定endpoint,反向解析的名字都是函数名,不加装饰器…...
Jvm实际运行情况-JVM(十七)
上篇文章说jmap和jstat的命令,如何查看youngGc和FullGc耗时和次数。 Jmap-JVM(十六) Jvm实际运行情况 背景: 机器配置:2核4G JVM内存大小:2G 系统运行天数:7天 期间发生FULL GC次数和耗时…...

【BASH】回顾与知识点梳理(二)
【BASH】回顾与知识点梳理 二 二. Shell 的变量功能2.1 什么是变量?2.2 变量的取用与设定: echo, 变量设定规则: set/unset2.3 环境变量的功能用 set 观察所有变量 (含环境变量与自定义变量)export: 自定义变量转成环境变量那如何将环境变量转成自定义变…...
【分布式训练】Accelerate 多卡训练,单卡评测,进程卡住的解决办法
最近想把之前的一个模型的改成多卡训练的。我并不懂DDP,DP。一开始打算使用Transformers的Trainer,但是配置的过程踩了很多坑也没有弄成功。【我是自己写的评测方法,但是我找不到能让触发Trainer去用我的方法评测的路劲】,后来偶然…...

时间复杂度为O(nlogn)的两种排序算法
1.归并排序 归并排序的核心思想:如果要排序一个数组,我们先把数组从中间分成前后两部分,然后对前后两部分分别排序,再将排好序的两部分合并在一起,这样整个数组就都有序了。 归并排序使用的就是分治思想。分治&#x…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩
目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...

【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...

云原生玩法三问:构建自定义开发环境
云原生玩法三问:构建自定义开发环境 引言 临时运维一个古董项目,无文档,无环境,无交接人,俗称三无。 运行设备的环境老,本地环境版本高,ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...

AirSim/Cosys-AirSim 游戏开发(四)外部固定位置监控相机
这个博客介绍了如何通过 settings.json 文件添加一个无人机外的 固定位置监控相机,因为在使用过程中发现 Airsim 对外部监控相机的描述模糊,而 Cosys-Airsim 在官方文档中没有提供外部监控相机设置,最后在源码示例中找到了,所以感…...
Git常用命令完全指南:从入门到精通
Git常用命令完全指南:从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...

Qemu arm操作系统开发环境
使用qemu虚拟arm硬件比较合适。 步骤如下: 安装qemu apt install qemu-system安装aarch64-none-elf-gcc 需要手动下载,下载地址:https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x…...

MySQL:分区的基本使用
目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区(Partitioning)是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分(分区)可以独立存储、管理和优化,…...
二维FDTD算法仿真
二维FDTD算法仿真,并带完全匹配层,输入波形为高斯波、平面波 FDTD_二维/FDTD.zip , 6075 FDTD_二维/FDTD_31.m , 1029 FDTD_二维/FDTD_32.m , 2806 FDTD_二维/FDTD_33.m , 3782 FDTD_二维/FDTD_34.m , 4182 FDTD_二维/FDTD_35.m , 4793...