使用SSM框架实现个人博客管理平台以及实现Web自动化测试

news2024/12/24 21:14:37

文章目录

  • 前言
    • 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中;

  1. common包:设置统一数据格式返回代码(AjaxResult)、全局变量用于session存储(AppVariable)、密码加盐已经密码验证(PasswordUtils)、获取当前用户(UserSessionUtils)。
  2. config包:系统配置文件(Appconfig)用于设置登录拦截器规则、登录拦截器的实现(LoginIntercept)、数据返回增强,校验是否是统一格式返回不是则封装为统一格式(ResponseAdvice)
  3. controller包:控制层;连接页面请求和服务层,获取页面请求的参数,通过自动装配,映射不同的URL到相应的处理函数,并获取参数,对参数进行处理,之后传给服务层。关于博客文章的控制器(ArticleController),实现博客以及草稿的增删改查以及列表分页点击量统计等功能、关于用户的控制器(UserController)实现增删改查功能。
  4. entity包:实体类,定义文章以及用户属性,同时定义视图UserVo的属性。
  5. service包:服务层;为控制层提供服务,接受控制层的参数,完成相应的功能,并返回给控制层;
  6. mapper包:持久层;根据service相关功能定义与数据库相关的增删改查方法。

在这里插入图片描述

在resources包中保存着系统资源,其中:

  1. mapper:根据 com.example.blog_system.mapper中的方法实现sql语句的增删改查。
  2. 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 {

    @Override
    public 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);


    }

测试结果如下:符合预期
在这里插入图片描述
在这里插入图片描述

检查分页查询:首页、上一页、下一页、末页功能是否正常

//检查分页查询功能
    @Test
    public 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

总结:根据测试用例结果基本符合预期可以确定个人博客管理平台的基本功能能够正确执行。

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

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

相关文章

汽配企业MES管理系统有哪些特点

汽配企业是汽车产业链上至关重要的一环,其生产过程涉及到众多的零部件和半成品,因此需要一套完善的管理系统来确保生产过程的顺利进行和产品的质量。MES管理系统解决方案是一种面向制造企业的管理系统,在汽配行业中得到了广泛的应用。本文将介…

【论文精读】基于历史抽取信息的摘要抽取方法

前言 论文分享 今天分享的是来自2018ACL的长文本抽取式摘要方法论文,作者来自哈尔滨工业大学和微软,引用数369 Neural Document Summarization by Jointly Learning to Score and Select Sentences 摘要抽取通常分为两个部分,句子打分和句子…

04 编写自己的破解补丁

本章摘要: 1、提高od当中汇编可读性 当我们遇到push offset Console.dasgasuigicashi这种阅读性不高的汇编代码的时候 在od当中,点击:选项》调试设置 逆向微软提供的内核dll也会出现阅读性不高的情况出现 跳转到这个地址 2、为什么写成全局…

【暑期每日一练】 day11

目录 选择题 (1) 解析: (2) 解析: (3) 解析: (4) 解析: (5) 解析: 编程题 题一 描…

使用curl和postman调用Azure OpenAI Restful API

使用curl在cmd中调用时,注意:json大括号内的每一个双引号前需要加上\ curl https://xxxopenai.openai.azure.com/openai/deployments/Your_deployid/chat/completions?api-version2023-05-15 -H "Content-Type: application/json" -H "…

C++ 对象的生存期详解

1.局部对象 &#xff08;1&#xff09;对于局部定义的对象&#xff0c;每当程序控制流到达该对象定义处时&#xff0c;定义构造函数。当程序走出该局部域时&#xff0c;调用析构函数。 这种普通的局部对象具有动态生存期。 #include<iostream> using namespace std;cl…

学习C#编写上位机的基础知识和入门步骤:

00001. 掌握C#编程语言基础和.NET框架的使用。 00002. 学习WinForm窗体应用程序开发技术&#xff0c;包括控件的使用和事件驱动编程。 00003. 熟悉基本的数据结构和算法知识&#xff0c;如链表、栈、队列等。 00004. 理解串口通信协议和通信方法&#xff0c;用于与底层硬件设…

增强for循环原理详解

增强for循环 本质是采用了迭代器&#xff0c;并使用局部变量指向迭代器当前遍历到的元素&#xff0c;使用增强for循环时&#xff0c;无法修改集合当前索引位置的引用&#xff0c;但是如果元素是引用数据类型&#xff0c;那么是可以修改这个元素的信息的&#xff08;String除外…

PSP - HHblits 算法搜索 BFD 与 UniRef30 的结果分析 (bfd_uniref_hits.a3m)

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/132047940 MMseqs2 与 HHblits 的算法比较&#xff1a; 蛋白质序列搜索算法 MMseqs2 与 HHblits 的搜索结果差异HHblits 算法搜索 BFD 与 UniRef…

Vulnhub: Wayne Manor:1靶机

kali&#xff1a;192.168.111.111 靶机&#xff1a;192.168.111.172 信息收集 端口扫描 nmap -A -sC -v -sV -T5 -p- --scripthttp-enum 192.168.111.172 根据提示修改hosts文件 访问目标80&#xff0c;在主页发现三组数字&#xff0c;结合端口扫描的结果中21端口被过滤&am…

Java 线程的多种状态

前言 在前文中详细介绍了线程的启动、中断、休眠、等待。本文详细介绍线程的多种状态。 获取线程的当前状态代码是&#xff1a; 线程对象.getState(); 目录 前言 一、NEW 二、RUNNABLE 三、BLOCKED 四、WAITNG 五、TIMED_WAITNG 六、TERMINATED 结语 一、NEW Thread 对…

Your local changes to the following files would be overwritten by checkout

Git 之 Your local changes to the following files would be overwritten by checkout 今天在切换分支时遇到了这样一个问题&#xff1a; 首先翻译下&#xff1a; Your local changes to the following files would be overwritten by checkout 大致意思就是&#xff1a; 当…

中海油集团,建设与中国特色国际一流能源公司相匹配的供应管理体系

近日&#xff0c;由中国物流与采购联合会主办、北京筑龙承办的主题为“数智赋能创新发展”的“第四届国有企业数智化采购与智慧供应链论坛”在北京盛大举行。中国海油工程与物装部供应商管理处处长张彬出席论坛并发表讲话。 张彬处长出席在国有企业数字化采购与供应链论坛&…

电子邮件模板?如何做EDM邮件营销模板?

制作EDM电子邮件推广模板的方法&#xff1f;怎么写EDM电子邮件推销模板&#xff1f; 随着数字化时代的来临&#xff0c;EDM邮件营销模板已成为企业推广和客户沟通的重要工具。在本文中&#xff0c;我将分享一些关于如何制作高效的EDM邮件营销模板的技巧和建议&#xff0c;希望…

Kafka的安装和使用(Windows中)

1.安装Kafka 1.1下载安装包 通过百度网盘分享的文件&#xff1a;复制链接打开「百度网盘APP 即可获取」 链接&#xff1a;https://pan.baidu.com/s/1vC6Di3Pml6k1KMbnK0OE1Q?pwdhuan 提取码&#xff1a;huan 也可以访问官网&#xff0c;下载kafka2.4.0的安装文件 1.2解压…

Linux:shell脚本:基础使用(1)

Shell的作用 命令解释器&#xff0c;“翻译官”&#xff0c;介于系统内核与用户之间&#xff0c;负责解释命令行 用户的登录Shell 登录后默认使用的Shell程序&#xff0c;一般为 /bin/bash 不同Shell的内部指令、运行环境等会有所区别 cat /etc/shells 编写第一个Shell脚本 …

装饰器模式(Decorator)

装饰器模式是一种结构型设计模式&#xff0c;用来动态地给一个对象增加一些额外的职责。就增加对象功能来说&#xff0c;装饰器模式比生成子类实现更为灵活。装饰器模式的别名为包装器(Wrapper)&#xff0c;与适配器模式的别名相同&#xff0c;但它们适用于不同的场合。 Decor…

P1219 [USACO1.5] 八皇后 Checker Challenge

题目 思路 非常经典的dfs题&#xff0c;需要一点点的剪枝 剪枝①&#xff1a;行、列&#xff0c;对角线的标记 剪枝②&#xff1a;记录每个皇后位置 代码 #include<bits/stdc.h> using namespace std; const int maxn105; int a[maxn];int n,ans; bool vis1[maxn],vis…

如何使用无线通信设备实现室内外精准定位管理?

巡更功能的意义 电子巡更系统的建立&#xff0c;使巡逻安防工作更加科学合理&#xff0c;使安全管理更加规范化、标准化制度化。例如&#xff0c;当管理人员设置巡更线路&#xff0c;在巡更线路上设置巡更点&#xff0c;通过巡更管理系为巡更人员排班在巡更区域内形成了一张防范…

《Java-SE-第二十三章》之单例模式

文章目录 单例模式概述饿汉模式懒汉模式单线程版懒汉单例多线程版枚举实现单例 单例模式概述 单例模式是设计模式中的一种,其作用能保证某个类在程序中只存在唯一一份实例,而不会创建多份实例。单例模式具体的实现方式, 分成 “饿汉” 和 “懒汉” 两种.。饿汉模式中的饿不并不…