使用ChatGPT关于登录产品代码的代码评审

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 整体来看,您的测试代码结构清晰,覆盖面广,关注了安全性测试。通过一些小的改进,可以提高代码的可维护性和安全性。继续保持良好的测试习惯,并考虑使用一些现代化的工具和库来简化测试工作。

12.1 前端代码

前端代码包括login.jsp文件提取的HTML代码和index.js,由于index.css没有改动,不做评审。

12.1.1申请前端代码评审

请对下面前端代码进行评审

1)login.jsp文件提取的HTML代码      

代码语言:javascript

复制

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="../css/register.css">
  <script src="../js/crypto-js.min.js" type="text/javascript"></script>
  <script src="../js/core.js" type="text/javascript"></script>
  <script src="../js/sha256.js" type="text/javascript"></script>
  <script src="../js/index.js" type="text/javascript"></script>
  <title>登录页面</title>
</head>
<body>
<div class="container">
    <h2>登录</h2>
   <form id="loginForm" name="loginForm" method="POST" action="login.jsp" onsubmit="return validateLoginForm();">
   <input type="hidden" id="csrftoken" name="csrftoken" value="ckK8E1SnIKqVj4No6odSVJyhDgwBHJopwBBvDrbfp6TkkwChw9CuhBrhZUBUtZ6hYroTuvpl0TkECQ5vqrIQ2SqtqLLTatzBk70d">
            <div>
                <label for="username">用户名:</label>
                <input type="text" id="username" name="username" placeholder="输入账号" required>
                <div id="usernameError" class="error"></div>
            </div>
            <div>
                <label for="password">密码:</label>
                <input type="password" id="password" name="password" placeholder="输入密码" required>
                <div id="passwordError" class="error"></div>
            </div>
            <button type="submit">登录</button>
    </form>
  <div id="loginError" class="error">
    </div>
  </div>
</body>
</html>

2)index.js

代码语言:javascript

复制

function validateForm() {
    const username = document.getElementById('username').value;
    const password = document.getElementById('password').value;
    const confirmPassword = document.getElementById('confirmPassword').value;
    const phone = document.getElementById('phone').value;
    const email = document.getElementById('email').value;
 
    let isValid = true;
    clearErrorMessagesRegister(); // 清空所有错误信息
 
    const usernamePattern = /^[a-zA-Z0-9]{5,20}$/;
    if (!usernamePattern.test(username)) {
        showError('usernameError', '账号必须是5-20位的字母或数字');
        isValid = false;
    }
 
    const passwordPattern = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{5,30}$/;
    if (!passwordPattern.test(password)) {
        showError('passwordError', '密码必须包含大小写字母、数字和特殊字符,长度在5-30之间');
        isValid = false;
    }
 
    if (password !== confirmPassword) {
        showError('confirmPasswordError', '密码确认不一致');
        isValid = false;
    }
 
    const phonePattern = /^1[3-9]\d{9}$/;
    if (!phonePattern.test(phone)) {
        showError('phoneError', '请输入有效的中国手机号');
        isValid = false;
    }
 
    const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
    if (!emailPattern.test(email)) {
        showError('emailError', '请输入有效的Email地址');
        isValid = false;
    }
 
    if (isValid) {
        // 获取密码并进行SHA-256哈希
        let passwordField = document.forms["myForm"]["password"];
        let hashedPassword = CryptoJS.SHA256(password).toString(CryptoJS.enc.Base64);
        
        // 将哈希后的密码重新赋值给表单的密码字段
        passwordField.value = hashedPassword;
        console.log('加密后的密码:', hashedPassword);
        console.log('注册成功!');
    }
 
    return isValid;
}
 
function validateLoginForm() {
    const username = document.getElementById('username').value;
    const password = document.getElementById('password').value;
 
    let isValid = true;
    clearErrorMessagesLogin(); // 清空所有错误信息
 
    const usernamePattern = /^[a-zA-Z0-9]{5,20}$/;
    if (!usernamePattern.test(username)) {
        showError('usernameError', '账号必须是5-20位的字母或数字');
        isValid = false;
    }
 
    const passwordPattern = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{5,30}$/;
    if (!passwordPattern.test(password)) {
        showError('passwordError', '密码必须包含大小写字母、数字和特殊字符,长度在5-30之间');
        isValid = false;
    }
 
    if (isValid) {
        // 获取密码并进行SHA-256哈希
        let passwordField = document.forms["loginForm"]["password"];
        let hashedPassword = CryptoJS.SHA256(password).toString(CryptoJS.enc.Base64);
        
        // 将哈希后的密码重新赋值给表单的密码字段
        passwordField.value = hashedPassword;
        console.log('登录成功!');
    }
 
       return isValid;
}
 
//用于显示错误信息
function showError(elementId, message) {
    document.getElementById(elementId).innerText = message;
}
 
//用于清空登录所有错误信息
function clearErrorMessagesRegister() {
    document.getElementById('usernameError').innerText = '';
    document.getElementById('passwordError').innerText = '';
    document.getElementById('confirmPasswordError').innerText = '';
    document.getElementById('phoneError').innerText = '';
    document.getElementById('emailError').innerText = '';
}
 
 
//用于清空所有错误信息
function clearErrorMessagesLogin() {
    document.getElementById('usernameError').innerText = '';
    document.getElementById('passwordError').innerText = '';
}

12.1.2前端代码评审回复

1)login.jsp文件提取的HTML代码

优点:

结构清晰:HTML 结构简洁明了,表单字段和错误提示清晰可见,便于用户理解。

使用了 CSRF 令牌:通过隐藏字段传递 CSRF 令牌,增强了安全性。

表单验证:通过 JavaScript 进行前端验证,提升了用户体验。

改进建议:

CSS和JS文件路径:相对路径可能在不同环境下导致加载失败,建议使用绝对路径或确保路径正确。

表单提交:表单的action属性指向login.jsp,这可能导致表单提交后页面重新加载。可以考虑将其指向一个处理登录请求的Servlet。

错误信息展示:当前错误信息的显示方式较为简单,可以考虑使用更友好的提示框或样式来增强用户体验。

2)index.js 文件评审

优点:

输入验证:提供了多种输入验证,确保用户输入符合预期格式,增强了安全性。

使用CryptoJS进行密码加密:在客户端对密码进行SHA-256加密,增加了密码的安全性。

改进建议:

密码哈希的安全性:虽然在客户端进行 SHA-256 加密可以提供一定的安全性,但仍然建议在服务器端进行密码的加密和验证。客户端的加密可能会被攻击者截获,因此应考虑使用 HTTPS。

重复代码:validateForm和validateLoginForm函数中有许多重复的逻辑(如用户名和密码的验证)。可以考虑将这些逻辑提取为一个共享的验证函数,以减少代码重复。

未使用的变量:在validateForm中,confirmPassword、phone 和 email 的验证逻辑在登录表单中并不需要,建议将这些代码移除以减少混乱。

日志信息:在控制台中输出的成功信息(如“注册成功!”)可能会引起误解,建议更改为“登录成功!”以匹配实际操作。

index.js做如下修改

代码语言:javascript

复制

function CheckField(Pattern, value, field, message, isValid) {
    if (!Pattern.test(value)) {
        showError(field, message);
        isValid = false; // 修改 isValid
    }
    return isValid; // 返回 isValid
}
 
function hash256(isValid, passwordField, message) {
    if (isValid) {
        let password = passwordField.value; // 从密码字段获取值
        let hashedPassword = CryptoJS.SHA256(password).toString(CryptoJS.enc.Base64);
        passwordField.value = hashedPassword;
    }
    return isValid;
}
 
function check(username, password, confirmPassword, phone, email) {
    let isValid = true;
    clearErrorMessages("reg"); // 清空所有错误信息
 
    const usernamePattern = /^[a-zA-Z0-9]{5,20}$/;
    const passwordPattern = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{5,30}$/;
    const phonePattern = /^1[3-9]\d{9}$/;
    const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
 
    isValid = CheckField(usernamePattern, username, 'usernameError', '账号必须是5-20位的字母或数字', isValid);
    isValid = CheckField(passwordPattern, password, 'passwordError', '密码必须包含大小写字母、数字和特殊字符,长度在5-30之间', isValid);
    isValid = CheckField(phonePattern, phone, 'phoneError', '请输入有效的中国手机号', isValid);
    isValid = CheckField(emailPattern, email, 'emailError', '请输入有效的Email地址', isValid);
    
    if (password !== confirmPassword) {
        document.getElementById('confirmPasswordError').innerText = '密码确认不一致';
        isValid = false;
    }
    
    return hash256(isValid, document.forms["myForm"]["password"], '注册成功!');
}
 
function checkLogin(username, password) {
    let isValid = true;
    clearErrorMessages("log"); // 清空所有错误信息
 
    const usernamePattern = /^[a-zA-Z0-9]{5,20}$/;
    const passwordPattern = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{5,30}$/;
 
    isValid = CheckField(usernamePattern, username, 'usernameError', '账号必须是5-20位的字母或数字', isValid);
    isValid = CheckField(passwordPattern, password, 'passwordError', '密码必须包含大小写字母、数字和特殊字符,长度在5-30之间', isValid);
    
    return hash256(isValid, document.forms["loginForm"]["password"], '登录成功!');
}
 
function validateRegisterForm() {
    const username = document.getElementById('username') ? document.getElementById('username').value : '';
    const password = document.getElementById('password') ? document.getElementById('password').value : '';
    const confirmPassword = document.getElementById('confirmPassword') ? document.getElementById('confirmPassword').value : '';
    const phone = document.getElementById('phone') ? document.getElementById('phone').value : '';
    const email = document.getElementById('email') ? document.getElementById('email').value : '';
 
    return check(username, password, confirmPassword, phone, email);
}
 
function validateLoginForm() {
    const username = document.getElementById('username') ? document.getElementById('username').value : '';
    const password = document.getElementById('password') ? document.getElementById('password').value : '';
 
    return checkLogin(username, password); // 调用正确的函数
}
 
//用于显示错误信息
function showError(elementId, message) {
    document.getElementById(elementId).innerText = message;
}
 
//用于清空注册所有错误信息
function clearErrorMessages(sign) {
    if (sign === "reg") {
        document.getElementById('usernameError').innerText = '';
        document.getElementById('passwordError').innerText = '';
        document.getElementById('confirmPasswordError').innerText = '';
        document.getElementById('phoneError').innerText = '';
        document.getElementById('emailError').innerText = '';
    } else if (sign === "log") {
        document.getElementById('usernameError').innerText = '';
        document.getElementById('passwordError').innerText = '';
    } else {
        console.log("sig for clearErrorMessages is error");
    }
}

总体评审

用户体验:整体上,前端代码在用户体验方面表现良好,表单清晰且易于理解,但在错误处理和提示方面可以进一步优化。

安全性:在密码处理上使用了加密,增强了安全性,但仍需注意客户端加密的局限性,建议在服务器端进行更严格的处理。

代码可维护性:代码结构合理,但存在重复逻辑,建议提取公共方法以提高可维护性。

结论

整体来看,前端代码功能良好,结构清晰,但在安全性、代码重复性和用户体验方面仍有提升空间。通过优化这些方面,可以提升代码的可维护性和用户体验。

12.2后端代码

到目前为止共有以下后端产品代码更改过:

register.jsp、login.jsp、welcome.jsp、logout.jsp以及CookiesManager.java、DBuser.java、login.java、register.java、User.java。validateUser.java和Util.javam没有发生变化。

12.2.1 jsp文件进行代码评审

1)申请jsp文件进行代码评审

对下面jsp文件进行评审。

① register.jsp

代码语言:javascript

复制

<%@ page contentType="text/html; charset=utf-8" %>
<%@ page import="java.sql.*, ebusiness.DBuser, ebusiness.validateUser, ebusiness.Util, ebusiness.User,ebusiness.CookiesManager,ebusiness.register" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>用户注册界面</title>
    <link rel="stylesheet" type="text/css" href="../css/index.css"/>
    <script src="../js/index.js" type="text/javascript"></script>
    <script src="../js/crypto-js.min.js" type="text/javascript"></script>
  <script src="../js/core.js" type="text/javascript"></script>
  <script src="../js/sha256.js" type="text/javascript"></script>
  <script src="../js/index.js" type="text/javascript"></script>
</head>
<body>
<%
Util util = new Util();
String csrftoken=util.generateRandomString(100);
%>
<div class="container">
    <h2>用户注册</h2>
    <form name="myForm" method="POST" action="register.jsp" onsubmit="return validateForm();">
      <input type="hidden" id="csrftoken" name="csrftoken" value="<%=csrftoken%>">
        <label for="username">账号 (5-20位字母或数字):</label>
        <input type="text" id="username" name="username" placeholder="输入账号" required>
        <div id="usernameError" class="error"></div>
        <label for="password">密码 (5-30位,包含大小写字母、数字和特殊字符):</label>
        <input type="password" id="password" name="password" placeholder="输入密码" required>
        <div id="passwordError" class="error"></div>
        <label for="confirmPassword">密码确认:</label>
        <input type="password" id="confirmPassword" name="confirmPassword"  placeholder="输入确认密码" required>
        <div id="confirmPasswordError" class="error"></div>
        <label for="phone">手机号 (中国):</label>
        <input type="tel" id="phone" name="phone"  placeholder="输入手机号" required>
        <div id="phoneError" class="error"></div>
        <label for="email">邮箱:</label>
        <input type="email" id="email" name="email"  placeholder="输入邮箱" required>
        <div id="emailError" class="error"></div>
        <button type="submit">注册</button>
    </form>
    <div id="registerError" class="error">
        <%
      CookiesManager ct = new CookiesManager("csrftoken",csrftoken, response,request);
      String method = request.getMethod();
      if ("get".equalsIgnoreCase(method)) {
        ct.setCookie();
      }else if("post".equalsIgnoreCase(method)){
        String csrftoken_post = request.getParameter("csrftoken");
        String csrftoken_cookies = ct.getCookie();
        ct.setCookie();
        if((csrftoken_cookies.equals(csrftoken_post))&&(csrftoken_cookies!=null)&&(csrftoken_cookies.length() > 0)){
          register myregister = new register(response,request);
    %>
    <%= myregister.myRegister()%>
    <%
        }
    }
    %>
    </div>
</div>
</body>
</html>

login.jsp

代码语言:javascript

复制

<%@ page contentType="text/html; charset=utf-8" %>
<%@ page import="java.sql.*, ebusiness.DBuser, ebusiness.validateUser, ebusiness.Util, ebusiness.User,ebusiness.CookiesManager,ebusiness.login" %>
<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="../css/index.css">
  <script src="../js/crypto-js.min.js" type="text/javascript"></script>
  <script src="../js/core.js" type="text/javascript"></script>
  <script src="../js/sha256.js" type="text/javascript"></script>
  <script src="../js/index.js" type="text/javascript"></script>
  <title>登录页面</title>
</head>
<body>
<%
Util util = new Util();
String csrftoken=util.generateRandomString(100);
%>
<div class="container">
    <h2>登录</h2>
   <form id="loginForm" name="loginForm" method="POST" action="login.jsp" onsubmit="return validateLoginForm();">
   <input type="hidden" id="csrftoken" name="csrftoken" value="<%=csrftoken%>">
            <div>
                <label for="username">用户名:</label>
                <input type="text" id="username" name="username" placeholder="输入账号" required>
                <div id="usernameError" class="error"></div>
            </div>
            <div>
                <label for="password">密码:</label>
                <input type="password" id="password" name="password" placeholder="输入密码" required>
                <div id="passwordError" class="error"></div>
            </div>
            <button type="submit">登录</button>
    </form>
  <div id="loginError" class="error">
        <%
      CookiesManager ct = new CookiesManager("csrftoken",csrftoken, response,request);
      String method = request.getMethod();
      if ("get".equalsIgnoreCase(method)) {
        ct.setCookie();
      }else if("post".equalsIgnoreCase(method)){
        String csrftoken_post = request.getParameter("csrftoken");
        String csrftoken_cookies = ct.getCookie();
        ct.setCookie(); 
        if((csrftoken_cookies.equals(csrftoken_post))&&(csrftoken_cookies!=null)&&(csrftoken_cookies.length() > 0)){
          login mylogin = new login(response,request);
    %>
    <%= mylogin.myLogin()%>
    <%
        }
    }
    %>
    </div>
  </div>
</body>
</html>

welcome.jsp

代码语言:javascript

复制

<%@ page contentType="text/html; charset=utf-8" %>
<%@ page import="ebusiness.CookiesManager" %>
<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="../css/index.css">
  <script src="../js/index.js" type="text/javascript"></script>
    <title>系统欢迎您</title>
</head>
<body>
<%
CookiesManager myusername = new CookiesManager("username",response,request);
String currentusername=myusername.getCookie();
if (currentusername==null){
  response.sendRedirect("login.jsp");
}else{
%>
<h1>系统欢迎您</h1>
欢迎<%=currentusername%>进入XXX系统
<a href="logout.jsp">登出</a>
<%
} 
%>
</body>
</html>

logout.jsp

代码语言:javascript

复制

<%
Cookie expiredCookie = new Cookie("username", "");
expiredCookie.setMaxAge(0); // 设置为立即过期
expiredCookie.setPath("/"); // 确保路径一致,以便被正确清除
response.addCookie(expiredCookie);
response.sendRedirect("login.jsp");
%>

2) jsp文件进行代码评审回复

对这四个 JSP 文件的评审主要集中在代码的可读性、安全性、结构性和维护性。以下是对每个文件的具体评审意见:

register.jsp

优点:

使用了CSRF令牌来防止跨站请求伪造攻击,增强了安全性。

表单验证通过JavaScript实现,提升了用户体验。

改进建议:

代码结构:尽量将业务逻辑(如用户注册处理)移到 Servlet 中,而不是在 JSP 中处理。JSP 应该主要负责视图层,而不是业务逻辑。

避免重复代码:在register.jsp和login.jsp中,获取CSRF令牌和Cookie的逻辑重复。可以考虑将这些逻辑提取到一个公共的Java类中。

错误处理:如果注册失败,应该在页面上提供用户友好的错误信息,而不仅仅是返回 myregister.myRegister()的结果。可以使用一个更友好的方式来展示错误信息。

输入验证:虽然有JavaScript验证,但也应该在服务器端进行验证,以防止恶意用户绕过客户端验证。

② login.jsp

优点:

类似于register.jsp,使用了CSRF令牌。

表单结构清晰,易于理解。

改进建议:

同样的代码重复:与register.jsp中的建议相同,考虑提取公共逻辑。

错误信息展示:如果登录失败,应该提供具体的错误信息,而不仅仅是调用 mylogin.myLogin()。可以考虑使用一个状态变量来显示错误信息。

安全性:确保密码在传输过程中是安全的,考虑使用 HTTPS。

③ welcome.jsp

优点:

简洁明了,能够有效地检查用户是否已登录。

改进建议:

重定向逻辑:可以考虑使用HttpServletResponse的sendRedirect方法更好地处理重定向逻辑,避免在JSP中直接使用response.sendRedirect。

用户体验:可以考虑在用户未登录时,提供一个更友好的提示信息,而不仅仅是重定向。

④ logout.jsp

优点:

逻辑简单明了,能够有效地清除 Cookie 并重定向。

改进建议:

安全性:在注销时,除了清除Cookie之外,还可以考虑使CSRF令牌失效,以增强安全性。

用户反馈:可以考虑在注销后提供一个用户反馈,例如“您已成功注销”。

总体评审

安全性:整体上,代码中考虑了一些安全措施(如CSRF令牌),但仍需确保在服务器端进行充分的输入验证和错误处理。

代码重用性:建议将重复的逻辑提取到公共类或方法中,以提高代码的可维护性。

用户体验:在表单处理和错误反馈方面,可以进一步增强用户体验,提供更友好的提示和反馈信息。

综上所述,这些 JSP 文件在功能上是有效的,但在代码结构和安全性方面还有改进的空间。希望这些建议能帮助你提升代码的质量和可维护性!

12.2.2 Java文件进行代码评审

1)申请Java文件进行代码评审

对下面Java文件进行评审。

CookiesManager.java

代码语言:javascript

复制

package ebusiness;
 
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletRequest;
 
public class CookiesManager {
       private HttpServletResponse response;
       private HttpServletRequest request;
       private String cookies_name;
       private String cookies_value;
 
    // 构造函数,接受 cookies 和 response 对象
    public CookiesManager(String cookies_name,String cookies_value, HttpServletResponse response,HttpServletRequest request) {
           this.cookies_name = cookies_name;
        this.cookies_value = cookies_value;
        this.response = response;
        this.request = request;
    }
    
    public CookiesManager(String cookies_name, HttpServletResponse response,HttpServletRequest request) {
           this.cookies_name = cookies_name;
        this.response = response;
        this.request = request;
    }
 
    // 设置cookies的方法
    public void setCookie() {
        if (this.cookies_value != null && !this.cookies_value.isEmpty()) {
            // 创建一个新的 Cookie
            Cookie myCookie = new Cookie(this.cookies_name, this.cookies_value);
            // 设置 Cookie 的有效期为 1 天(单位是秒)
            myCookie.setMaxAge(60 * 60 * 24);
            // 设置 Cookie 的路径
            myCookie.setPath("/");
            // 如果使用 HTTPS,确保 Cookie 只通过安全连接发送
            myCookie.setSecure(true);
            // 防止客户端脚本访问 Cookie
            myCookie.setHttpOnly(true);
            // 将 Cookie 添加到响应中
            response.addCookie(myCookie);
        } else {
            // 处理cookies为空的情况
            System.out.println("cookie is null or empty.");
        }
    }
    
    // 获取cookies的方法
    public String getCookie() {
           Cookie[] cookies = this.request.getCookies(); // 获取所有的cookies
              if (cookies != null) {
                     for (Cookie cookie : cookies) {
                            String name = cookie.getName(); // 获取cookie的名称
                            String value = cookie.getValue(); // 获取cookie的值
                            if (name.equals(this.cookies_name) &&(value!=null)||(value.equals(""))){
                                   this.cookies_value=value;
                            }
                     }                     
              }
              return this.cookies_value;
    }
}

② DBuser.java

代码语言:javascript

复制

package ebusiness;
 
import java.sql.*;
 
public class DBuser {
    private static final String url = "jdbc:mysql://localhost:3306/ebusiness";
    private static final String user = "root";
    private static final String password = "123456";
    private static Statement stmt = null;
    private static ResultSet rs;
    
    public Connection connect() throws SQLException {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection conn = DriverManager.getConnection(url, user, password);
            stmt = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
            return conn;
        } catch (ClassNotFoundException e) {
            System.out.println("JDBC Driver not found: " + e.getMessage());
        } catch (SQLException e) {
            System.out.println("Database connection failed: " + e.getMessage());
        }
        return null;
    }
    
    public void disconnect(Connection conn) {
        try {
            if (rs != null) {
                rs.close();
            }
            stmt.close();
            conn.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public String insert(Connection conn, User user) throws SQLException {
        if (!userExists(conn, user.getUsername(), user.getEmail(), user.getPhone()).equals("0")) {
            return userExists(conn, user.getUsername(), user.getEmail(), user.getPhone());
        }
        String insertString = "INSERT INTO user (username, password, phone, email) VALUES (?, ?, ?, ?)";
        try {
            PreparedStatement stmt = conn.prepareStatement(insertString);
            stmt.setString(1, user.getUsername());
            stmt.setString(2, user.getPassword());
            stmt.setString(3, user.getPhone());
            stmt.setString(4, user.getEmail());
            int affectedRows = stmt.executeUpdate();
            return String.valueOf(affectedRows);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return "-1";
    }
      
    public String userExists(Connection conn, String username, String email, String phone) throws SQLException {       
           if (!judgeInfo(conn,"username",username).equals("0"))
                  return "注册用户的用户名必须唯一";
           else if(!judgeInfo(conn,"phone",phone).equals("0"))
                  return "注册用户的手机必须唯一";
           else if(!judgeInfo(conn,"email",email).equals("0"))
               return "注册用户的邮箱必须唯一";
           else
                  return "0";
    }
    
    public String judgeInfo(Connection conn, String field,String value) throws SQLException {
           String queryString = "SELECT * FROM user WHERE "+field+" = ?";
        try {
            PreparedStatement stmt = conn.prepareStatement(queryString);
            stmt.setString(1, value);
            ResultSet rs = stmt.executeQuery();
            if(rs.next()) 
                   return field; 
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return "0";
    }
    
    public String login(Connection conn, User user) throws SQLException {
           String username=user.getUsername();
           String password=user.getPassword();
        PreparedStatement stmt = null;
        ResultSet rs = null;
        try {
               String queryString = "SELECT * FROM user WHERE username=? AND password=?";
               stmt = conn.prepareStatement(queryString);
            stmt.setString(1, username);
            stmt.setString(2, password);
            rs = stmt.executeQuery(); 
            if (!rs.next()) {
                return "0";
            } else {
                   return "1";
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                if (rs != null) rs.close();
                if (stmt != null) stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return "-1";
    }
}

③ login.java

代码语言:javascript

复制

package ebusiness;
 
import java.sql.Connection;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletRequest;
 
public class login {
       private HttpServletResponse response;
       private HttpServletRequest request;
       
       public login(HttpServletResponse response,HttpServletRequest request) {
        this.response = response;
        this.request = request;
    }
       
       public String myLogin() {
              String username = request.getParameter("username");
              String password = request.getParameter("password");              
              User user = new User(username, password);
              validateUser ValidateUser = new validateUser();
              String validationResult = ValidateUser.validate(user);
              if (validationResult.equals("验证成功"))  {
                     try {
                            DBuser mysql = new DBuser();
                            Connection conn = mysql.connect();
                            if (mysql.login(conn, user).equals("0")){
                                   return "用户名或密码错误";
                            }else{
                                   CookiesManager myusername = new CookiesManager("username",username, response,request);
                                   myusername.setCookie();
                                   response.sendRedirect("welcome.jsp");
                            }
                            mysql.disconnect(conn);
                     } catch (Exception e) {
                            e.printStackTrace();
                     }
              }else{
                     return validationResult;
              }
              return "0";
       }
}

④ register.java

代码语言:javascript

复制

package ebusiness;
 
import java.sql.Connection;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletRequest;
 
public class register {
       private HttpServletResponse response;
       private HttpServletRequest request;
       
       public register(HttpServletResponse response,HttpServletRequest request) {
        this.response = response;
        this.request = request;
    }
       
       public String myRegister() {
              String username = this.request.getParameter("username");
              String password = this.request.getParameter("password");
              String phone = this.request.getParameter("phone");
              String email = this.request.getParameter("email");
       
              User user = new User(username,password,phone,email);
              validateUser ValidateUser = new validateUser();
              String validationResult = ValidateUser.validate(user);
              if (!validationResult.equals("验证成功")) {
                     return validationResult;
              }else {
                     try {
                            DBuser mysql = new DBuser();
                            Connection conn = mysql.connect();
                            String info = mysql.insert(conn, user);
                            if (info.equals("1")) {
                                   this.response.sendRedirect("login.jsp");
                            } else {
                                   return info;
                            }
                            mysql.disconnect(conn);
                     } catch (Exception e) {
                            return "数据库错误: " + e.getMessage();
                     }
              }
              return "0";
       }
}

⑤ User.java

代码语言:javascript

复制

package ebusiness;
 
public class User {
    private int id;
    private String username;
    private String password;
    private String phone;
    private String email;
    
    public User(String username,String password) {
           this.username = username;
           this.password = password;
           this.phone = "13611112222";
           this.email = "jerry@126.com";
    }
    
    public User(String username,String password,String phone,String email) {
           this.username = username;
           this.password = password;
           this.phone = phone;
           this.email = email;
    }
    
    // Getters and Setters
    public int getId() {
        return id;
    }
 
    public String getUsername() {
        return username;
    }
 
    public void setUsername(String username) {
           this.username = username;
    }
 
    public String getPassword() {
        return password;
    }
 
    public void setPassword(String password) {
           this.password = password;
    }
 
    public String getPhone() {
        return phone;
    }
 
    public void setPhone(String phone) {
           this.phone = phone;
    }
 
    public String getEmail() {
        return email;
    }
 
    public void setEmail(String email) {
           this.email = email;
    }

2)Java文件进行代码评审回复

CookiesManager.java

优点:

提供了setCookie和getCookie方法,封装了 Cookie 操作,方便调用。

使用了HttpOnly和Secure属性,提高了 Cookie 的安全性,防止JavaScript访问和在非 HTTPS 连接下传输。

改进建议:

cookies_value的空检查:在getCookie方法中,对于value != null和value.equals("")的判断逻辑不太清晰,可以改为if (value == null || value.isEmpty())来明确判断空值情况。

代码语言:javascript

复制

if (name.equals(this.cookies_name) &&(value!=null)||(value.equals(""))){

改为

代码语言:javascript

复制

if (name.equals(this.cookies_name) && (value == null || value.isEmpty())){

日志记录:System.out.println用于记录Cookie操作的异常信息,这在生产环境中不适用。建议使用日志框架(如Log4j、SLF4J来记录错误信息。

询问ChatGPT如何为使用Log4j,ChatGPT回答:

log4j 是一个常用的 Java 日志记录库,它可以帮助你在应用程序中记录日志、输出信息和进行调试。以下是如何使用 log4j 记录日志的基本步骤:

1. 添加 log4j 依赖

首先,你需要将 log4j 添加到项目的依赖中。假设你使用的是 Maven,可以在 pom.xml 文件中添加以下依赖:

代码语言:javascript

复制

<dependencies>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version> <!-- 或者使用其他版本 -->
    </dependency>
</dependencies>

如果你使用 Gradle,则在 build.gradle 中添加:

代码语言:javascript

复制

dependencies {
    implementation 'log4j:log4j:1.2.17' // 或者其他版本
}

2. 配置 log4j(log4j.properties 或 log4j.xml)

log4j 需要一个配置文件来指定日志输出的格式、级别和输出位置。你可以选择使用 log4j.properties 或 log4j.xml 文件。

使用 log4j.properties 配置文件

在你的 src/main/resources 目录下创建一个 log4j.properties 文件,内容示例:

代码语言:javascript

复制

# 设置日志级别
log4j.rootLogger=DEBUG, console, file
 
# 设置控制台输出
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c - %m%n
 
# 设置文件输出
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=logs/app.log
log4j.appender.file.Append=true
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c - %m%n

使用 log4j.xml 配置文件

或者,你可以使用 XML 配置文件来设置日志。创建 log4j.xml 文件并放在 src/main/resources 目录下:

代码语言:javascript

复制

<?xml version="1.0" encoding="UTF-8"?>
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    <!-- 设置根日志级别 -->
    <root>
        <level value="DEBUG" />
        <appender-ref ref="console" />
        <appender-ref ref="file" />
    </root>
    <!-- 设置控制台输出 -->
    <appender name="console" class="org.apache.log4j.ConsoleAppender">
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d{ISO8601} %-5p [%t] %c - %m%n" />
        </layout>
    </appender>
    <!-- 设置文件输出 -->
    <appender name="file" class="org.apache.log4j.FileAppender">
        <param name="File" value="logs/app.log" />
        <param name="Append" value="true" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d{ISO8601} %-5p [%t] %c - %m%n" />
        </layout>
    </appender>
</log4j:configuration>

3. 在代码中使用 log4j 记录日志

一旦你添加了 log4j 配置文件并将依赖加入项目中,你可以在 Java 代码中使用 log4j 记录日志。

示例代码:

代码语言:javascript

复制

import org.apache.log4j.Logger;
 
public class MyApp {
    // 创建日志记录器
    private static final Logger logger = Logger.getLogger(MyApp.class);
    public static void main(String[] args) {
        // 记录不同级别的日志
        logger.debug("这是调试日志");
        logger.info("这是信息日志");
        logger.warn("这是警告日志");
        logger.error("这是错误日志");
        logger.fatal("这是致命错误日志");
        
        // 模拟一个异常
        try {
            int result = 10 / 0;
        } catch (Exception e) {
            logger.error("发生异常: ", e);
        }
    }
}

4. 日志输出

在控制台:日志会根据配置输出到控制台(console appender)。

在文件:日志也会被写入到指定的日志文件中(file appender),例如 logs/app.log。

5. 日志级别

log4j 提供了不同的日志级别,按优先级从低到高排列如下:

TRACE:最细粒度的日志记录,用于程序的微小事件。

DEBUG:调试信息,通常在开发过程中使用。

INFO:常规的信息日志,用于记录应用程序的运行情况。

WARN:警告日志,用于表示潜在的或非致命的问题。

ERROR:错误日志,用于记录运行时错误。

FATAL:严重错误日志,通常表示程序不能继续运行。

6. 其他配置选项

日志文件滚动:你可以配置日志文件自动滚动,避免文件过大。你可以使用 RollingFileAppender 来实现日志文件的滚动。

代码语言:javascript

复制

log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=logs/app.log
log4j.appender.file.MaxFileSize=10MB
log4j.appender.file.MaxBackupIndex=5
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c - %m%n

以上配置会使日志文件的大小限制为 10MB,并且保留最多 5 个旧的日志文件。

总结

通过以上步骤,你可以使用 log4j 在 Java 应用中记录不同级别的日志。配置文件允许你灵活控制日志的输出位置、格式和级别,使得开发和维护过程更加高效。如果你使用的是 log4j 2.x,配置和用法会有所不同,但大体思路类似。

根据回复

在pom.xml配置

代码语言:javascript

复制

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

在资源目录(如src/main/resources)下创建Log4j配置文件log4j.properties。

代码语言:javascript

复制

# 设置日志级别
log4j.rootLogger=DEBUG, console, file
 # 设置控制台输出
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c - %m%n
 # 设置文件输出
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=logs/app.log
log4j.appender.file.Append=true
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c - %m%n

在CookiesManager.java中使用Log4j记录日志:

代码语言:javascript

复制

import org.apache.log4j.Logger;
 
public class CookiesManager {
       private static final Logger logger = Logger.getLogger(CookiesManager.class);
// 处理cookies为空的情况
logger.info("cookie is null or empty.");

如果使用Tomcat作为WebServer,可以询问ChatGPT,如何在Tomcat中使用Log4j。结果与上面基本类似,在这里不再做描述。

多余的构造函数:构造函数CookiesManager(String cookies_name, HttpServletResponse response,HttpServletRequest request) 没有设置cookies_value,建议在没有值的情况下将其设为 null,或在构造函数中明确设定默认值。

修改构造函数CookiesManager(String cookies_name, HttpServletResponse response,HttpServletRequest request),在里面加入:this.cookies_value = null;,即:

代码语言:javascript

复制

public CookiesManager(String cookies_name, HttpServletResponse response,HttpServletRequest request) {
           this.cookies_name = cookies_name;
           this.cookies_value = null;
        this.response = response;
        this.request = request;
    }

DBuser.java

优点:

提供了连接数据库、插入数据、检查用户是否存在等功能,封装了数据库操作,功能模块清晰。

使用了PreparedStatement来避免 SQL 注入,确保查询安全。

改进建议:

资源管理:stmt和rs是静态变量,可能导致在多线程环境下资源共享问题。建议将它们的声明修改为方法级变量,避免静态持有连接。

改为:

代码语言:javascript

复制

private Statement stmt = null;
private ResultSet rs;

SQL异常处理:在捕获 SQL 异常时,可以将异常信息记录到日志中,而不仅仅是 e.printStackTrace(),这样可以帮助开发者在生产环境中进行调试。

在DBuser.java中加入

代码语言:javascript

复制

import org.apache.log4j.Logger;
    public Connection connect() throws SQLException {
               logger.error("JDBC Driver not found: " + e.getMessage());
        } catch (SQLException e) {
               logger.error("JDBC Driver not found: " + e.getMessage());
        }

数据库连接关闭:mysql.disconnect(conn) 关闭连接的代码应该放在finally块中,确保即使在数据库操作过程中发生异常时也能正确释放资源。

在register.java和login.java中做如下修改

代码语言:javascript

复制

public String myRegister() throws SQLException {
              if (!validationResult.equals("验证成功")) {
                     return validationResult;
              }else {
                     DBuser mysql = new DBuser();
                     Connection conn = mysql.connect();
                     try {
                            String info = mysql.insert(conn, user);
                            if (info.equals("1")) {
                                   this.response.sendRedirect("login.jsp");
                            } else {
                                   return info;
                            }
                     } catch (Exception e) {
                            return "数据库错误: " + e.getMessage();
                     }finally {
                            mysql.disconnect(conn);
                     }
              }
              return "0";
       }
 
public String myLogin() throws SQLException {
       if (validationResult.equals("验证成功"))  {
              DBuser mysql = new DBuser();
              Connection conn = mysql.connect();
              try {
                     if (mysql.login(conn, user).equals("0")){
                            return "用户名或密码错误";
                     }else{
                            CookiesManager myusername = new CookiesManager("username",username, response,request);
                            myusername.setCookie();
                            response.sendRedirect("welcome.jsp");
                     }
              } catch (Exception e) {
                     e.printStackTrace();
              }finally {
                     mysql.disconnect(conn);
              }
       }else{
              return validationResult;
       }
       return "0";
}

修改后需要修改LoginTest.java。

代码语言:javascript

复制

@Test
    public void testSuccessfulLogin() throws IOException, SQLException {
    }
 
    @Test
    public void testFailedLogin() throws SQLException {
}

RegisterTest.java

代码语言:javascript

复制

@Test
    public void testSuccessfulRegistration() throws IOException, SQLException {
    }
 
    @Test
    public void testFailedRegistration() throws SQLException {
    }

③ login.java

优点:

清晰的流程控制,使用了validateUser类进行用户验证。

登录成功后,使用CookiesManager设置用户的登录信息(如用户名 Cookie),并跳转到 welcome.jsp。

改进建议:

返回值处理:myLogin() 方法返回的字符串用于显示错误信息。可以考虑使用 response 直接返回状态码或重定向,避免在业务逻辑中处理视图显示逻辑。

异常处理:在catch块中仅调用e.printStackTrace(),应当记录详细日志并返回适当的错误信息,而不是简单打印堆栈信息。

重复代码:建议将数据库连接部分提取为公共方法,以减少代码重复,提高可维护性。

④ register.java

优点:

逻辑清晰,注册成功后跳转到登录页面。

使用了validateUser对用户输入进行验证。

改进建议:

返回值处理:myRegister() 方法的返回值是一个字符串。可以考虑通过抛出异常来处理错误,并在外层捕获异常以返回用户友好的错误消息。

数据库连接和断开:和login.java类似,数据库连接的管理应当在finally块中进行,确保连接的关闭即使发生异常也能执行。

前面已处理。

异常处理:对数据库错误的捕获有些简单,建议提供更详细的错误信息或者记录错误日志。

⑤ User.java

优点:

提供了getters和setters,简洁明了,便于后续扩展。

改进建议:

构造函数默认值:在构造函数中为phone和email提供默认值 13611112222 和 jerry@126.com 可能是不合适的,特别是在创建新用户时,应该确保这些信息被传入并且是有效的。

ID 属性:id 是User类的一个字段,但它没有在构造函数中初始化,应该在构造时传入或通过数据库自动生成。

总体评审

代码结构和模块化:整体上,代码结构清晰,将数据库操作、用户验证和 Cookie 管理进行了良好的分离。每个类都有明确的职责。

异常处理和日志记录:代码中的异常处理相对简单,建议增强异常捕获、日志记录和错误反馈机制,以便在生产环境中能更好地调试和追踪问题。

安全性:数据库操作和 Cookie 管理考虑到了安全性,但建议对密码的存储和验证(例如使用加密算法)做更多安全措施。

性能优化:数据库连接在每个请求中都被创建和关闭,考虑使用连接池来优化性能,避免频繁的连接创建与销毁。

总结:

代码结构良好,功能清晰,但在异常处理、安全性、日志记录等方面有进一步优化的空间。可以通过将数据库连接和其他重复逻辑提取到服务层、使用日志框架来提高代码的健壮性和可维护性。

注意:修改所有代码后,请运行所有测试用例,以保证没有引入新的缺陷。

12.3 测试代码

测试代码Test_Register.py没有发生变动,Test_Login.py更新过,另外还产生了Test_register_GUI.py和Test_login_GUI.py 两个测试程序。还有一个作为测试用例用到的公共方法的文件Util.py。

在评审之前,先修改Util.py

去掉login_for_name()和login_for_password()两个方法,合并为:

代码语言:javascript

复制

#为登录初始化数据
    def init_login(self,username,password):
        connection = self.initDB()
        try:
            with connection.cursor() as cursor:
                if username == "":
                    username = "validUser"
                else:
                    username = username
                if password == "":
                    password = HASH.hash_password("ValidPassword123!")
                else:
                    password = HASH.hash_password(password)
                # SQL语句,用于清空user表
                sql = "INSERT INTO user (username, password, phone, email) VALUES (\""+username+"\",\""+password+"\", \"13611223344\", \"a@b.com\")"  
                # 执行SQL语句
                cursor.execute(sql)       
                # 提交事务
                connection.commit()
        finally:
            self.closedb(connection)

在test_Login.py和Test_login_GUI.py中,修改

代码语言:javascript

复制

self.db.init_login()

代码语言:javascript

复制

self.db.init_login(username="",password="")

在test_Login.py中,修改

代码语言:javascript

复制

self.db.login_for_name("validUser12345678900")
self.db.login_for_name("user1")
self.db. login_for_password ("ValidPassword1234567890123456!")
self.db. login_for_password("P@ss1")

代码语言:javascript

复制

self.db.init_login(username="validUser12345678900",password="")
self.db.init_login(username="user1",password="")
self.db.init_login(username="",password="ValidPassword1234567890123456!")
self.db.init_login(username="",password="P@ss1")

12.3.1申请测试代码评审

请对下面测试代码进行评审。

1)Test_Login.py

代码语言:javascript

复制

import requests
import unittest
from Util import ClassDB
from Util import CSRF
from Util import HASH
 
#全局URL
BASE_URL = "http://127.0.0.1:8080/CharGPTEbusiness/jsp/login.jsp"  # 替换为实际的登录API URL
WELCOME_URL = "http://127.0.0.1:8080/CharGPTEbusiness/jsp/welcome.jsp"  # 替换为实际的受保护页面URL
class TestLoginAPI(unittest.TestCase):
    def setUp(self):
        self.session = requests.Session()
        response = self.session.get(BASE_URL)
        self.csrf = CSRF.get_csrf(response.text)
        self.token={"csrftoken":self.csrf}
        self.db = ClassDB()
        self.db.init_login(username="",password="")
              
    def tearDown(self):
        self.db.init_db()
       
    def test_valid_login(self):
        """TC-001:验证有效用户名和密码登录"""
        self.token['username'] = "validUser"
        response = requests.post(BASE_URL, data={
           'csrftoken':self.csrf,
           'username': "validUser",
           'password': HASH.hash_password("ValidPassword123!")
       },cookies=self.token)
        self.assertEqual(response.status_code, 200)
        self.assertIn("系统欢迎您", response.text)
 
 
    def test_invalid_username(self):
        """TC-002:验证无效用户名登录"""
        self.token['username'] = "invalidUser"
        response = requests.post(BASE_URL,
       data={
           'csrftoken':self.csrf,
           'username': "invalidUser",
           'password': HASH.hash_password("ValidPassword123!")
       },cookies=self.token)
        self.assertEqual(response.status_code, 200)
        self.assertIn("用户名或密码错误", response.text)
 
    def test_invalid_password(self):
        """TC-003,验证无效密码登录"""
        self.token['username'] = "validUser"
        response = requests.post(BASE_URL, data={
           'csrftoken':self.csrf,
           'username': "validUser",
           'password': HASH.hash_password("wrongPassword")
           },cookies=self.token)
        self.assertEqual(response.status_code, 200)
        self.assertIn("用户名或密码错误", response.text)
 
    def test_empty_username(self):
        """TC-004,验证空用户名登录"""
        self.token['username'] = ""
        response = requests.post(BASE_URL, data={
           'csrftoken':self.csrf,
           'username': "",
           'password':HASH. hash_password("validPassword")
           },cookies=self.token)
        self.assertEqual(response.status_code, 200)
        self.assertIn("      ", response.text)
 
    def test_empty_password(self):
        """TC-005,验证空密码登录"""
        self.token['username'] = "validUser"
        response = requests.post(BASE_URL, data={
           'csrftoken':self.csrf,
           'username': "validUser",
           'password': HASH.hash_password("")
           },cookies=self.token)
        self.assertEqual(response.status_code, 200)
        self.assertIn("用户名或密码错误", response.text)
 
    def test_username_max_length(self):
        """TC-008,验证用户名最大长度"""
        self.token['username'] = "validUser12345678900"
        self.db.init_login(username="validUser12345678900",password="")
        response = requests.post(BASE_URL, data={
           'csrftoken':self.csrf,
           'username': "validUser12345678900",
           'password': HASH.hash_password("ValidPassword123!")
           },cookies=self.token)
        self.assertEqual(response.status_code, 200)
        self.assertIn("系统欢迎您", response.text)
 
    def test_username_min_length(self):
        """TC-009,验证用户名最小长度"""
        self.token['username'] =  "user1"
        self.db.init_login(username="user1",password="")
        response = requests.post(BASE_URL, data={
           'csrftoken':self.csrf,
           'username': "user1",
           'password':HASH.hash_password("ValidPassword123!")
           },cookies=self.token)
        self.assertEqual(response.status_code, 200)
        self.assertIn("系统欢迎您", response.text)
 
    def test_password_max_length(self):
        """TC-010,验证密码最大长度"""
        self.token['username'] = "validUser"
        self.db.init_login(username="",password="ValidPassword1234567890123456!")
        response = requests.post(BASE_URL, data={
           'csrftoken':self.csrf,
           'username': "validUser",
           'password': HASH.hash_password("ValidPassword1234567890123456!")
           },cookies=self.token)
        self.assertEqual(response.status_code, 200)
        self.assertIn("系统欢迎您", response.text)
 
    def test_password_min_length(self):
        """TC-011,验证密码最小长度"""
        self.token['username'] = "validUser"
        self.db.init_login(username="",password="P@ss1")
        response = requests.post(BASE_URL, data={
           'csrftoken':self.csrf,
           'username': "validUser",
           'password': HASH.hash_password("P@ss1")
           },cookies=self.token)
        self.assertEqual(response.status_code, 200)
        self.assertIn("系统欢迎您", response.text)
 
    def test_xss_injection(self):
        """TC-012,验证XSS注入"""
        self.token['username'] = "     <>       alert('XSS')       "
        response = requests.post(BASE_URL, data={
           'csrftoken':self.csrf,
            'username': "     <>       alert('XSS1')       ",
           'password': HASH.hash_password("ValidPassword123!")
           },cookies=self.token)
        self.assertEqual(response.status_code, 200)
        self.assertIn("账号必须是5-20位字母或数字", response.text)
 
    def test_csrf_protection(self):
        """TC-013,验证CSRF保护"""
       # 假设CSRF令牌是必需的,这里我们不提供CSRF令牌
        self.token['username'] = "validUser"
        response = requests.post(BASE_URL, data={
           'username': "validUser",
           'password': HASH.hash_password("ValidPassword123!")
           },cookies=self.token)
        self.assertEqual(response.status_code, 200)
        self.assertIn("      ", response.text)
 
    def test_sql_injection(self):
        """TC-014,验证SQL注入"""
        self.token['username'] = "admin' --"
        response = requests.post(BASE_URL, data={
           'csrftoken':self.csrf,
           'username': "admin' --",
           'password': HASH.hash_password("ValidPassword123!")
           },cookies=self.token)
        self.assertEqual(response.status_code, 200)
        self.assertIn("账号必须是5-20位字母或数字", response.text)
 
if __name__ == "__main__":
       unittest.main()

2)Test_register_GUI.py

代码语言:javascript

复制

import pytest
from playwright.sync_api import Page
from Util import ClassDB
import time
 
REGISTER_URL = 'http://127.0.0.1:8080/CharGPTEbusiness/jsp/register.jsp'
# 测试类
class TestUserRegistration:   
    def teardown(self):
           self.db = ClassDB()
           self.db.init_db()
        
    # 测试用例
    @pytest.mark.parametrize("username, password, repassword, phone, email, expected_message", [
    ("validUser123", "ValidPass123!", "ValidPass123!","13800138000","test@example.com","登录"),  #GTC1-账号有效性测试
       ("testUser", "Aa1@", "Aa1@","13800138000","test@example.com","密码必须包含大小写字母、数字和特殊字符,长度在5-30之间"),  #GTC2 - 密码长度不足测试
       ("testUser", "iLongpassword12@Longpassword12@", "iLongpassword12@Longpassword12@","13800138000","test@example.com","密码必须包含大小写字母、数字和特殊字符,长度在5-30之间"),  #GTC3 - 密码长度超出测试
       ("testUser", "ValidPass123!", "validPass123!","13800138000","test@example.com","密码确认不一致"),  #GTC4 - 输入密码与确认密码确保不一致性
       ("testUser", "ValidPass123!", "ValidPass123!","12345","test@example.com","请输入有效的中国手机号"),  ##GTC5 - 手机号格式错误测试
    ])
    def test_Login(self,page: Page,username, password, repassword, phone, email, expected_message):
        page.goto(REGISTER_URL)
        page.reload()
        page.fill('input[name="username"]', username)
        page.fill('input[name="password"]', password)
        page.fill('input[name="confirmPassword"]', repassword)
        page.fill('input[name="phone"]',  phone)
        page.fill('input[name="email"]', email)
        page.click('button[type="submit"]')
        # 检查注册是否成功(这里需要根据实际页面反馈调整)
        if expected_message == "登录":
            assert page.get_by_role("heading", name="登录")
        else:
            assert page.locator('text='+expected_message).is_visible()
      
 
    # 测试用例
    @pytest.mark.parametrize("username, password, repassword, phone, email, username_1,phone_1,email_1,expected_message", [
        ("duplicateUser", "ValidPass123!", "ValidPass123!","13800138000","duplicate@example.com","duplicateUser","13800138001","duplicate2@example.com","注册用户的用户名必须唯一"),  #GTC6 – 一个用户重复注册两次。
       ("duplicateUser3", "ValidPass123!", "ValidPass123!","13800138003","duplicate1@example.com","duplicateUser4","13800138003","duplicate1@example.com","注册用户的手机必须唯一"),  #GTC7 – – 不同用户注册,手机号重复
       ("duplicateUser1", "ValidPass123!", "ValidPass123!","13800138004","duplicate3@example.com","duplicateUser4","13800138005","duplicate3@example.com","注册用户的邮箱必须唯一"), #GTC8 – 不同用户注册,邮件重复
    ])
    def test_duplicate(self,page: Page,username, password, repassword, phone, email, username_1,phone_1,email_1,expected_message):
        page.goto(REGISTER_URL)
        page.reload()
        # 第一次注册
        page.fill('input[name="username"]', username)
        page.fill('input[name="password"]', password)
        page.fill('input[name="confirmPassword"]', repassword)
        page.fill('input[name="phone"]', phone)
        page.fill('input[name="email"]', email)
        page.click('button[type="submit"]')
        # 检查注册成功
        assert page.get_by_role("heading", name="登录")
        # 第二次注册相同用户
        page.goto(REGISTER_URL)
        page.reload()
        page.fill('input[name="username"]', username_1)
        page.fill('input[name="password"]', password)
        page.fill('input[name="confirmPassword"]', repassword)
        page.fill('input[name="phone"]', phone_1)
        page.fill('input[name="email"]', email_1)
        page.click('button[type="submit"]')
        # 检查错误信息
        assert page.locator('text='+expected_message).is_visible()
 
if __name__ == '__main__':
    pytest.main(["-sv", "Test_register_GUI.py"])

3)Test_login_GUI.py

代码语言:javascript

复制

import pytest
from playwright.sync_api import Page
from Util import ClassDB
import time
 
 
LOGIN_URL = 'http://127.0.0.1:8080/CharGPTEbusiness/jsp/login.jsp'
WELCOME_URL ='http://127.0.0.1:8080/CharGPTEbusiness/jsp/welcome.jsp'
# 测试类
class TestUserLogin:
    def setup_class(self):
        self.db = ClassDB()
        self.db.init_login(username="",password="")
 
    def teardown_class(self):
        self.db.init_db()
 
    # 测试用例
    @pytest.mark.parametrize("username, password, expected_message", [
        ("validUser", "ValidPassword123!", "系统欢迎您"),  # TC-001
        ("invalidUser", "ValidPassword123!", "用户名或密码错误"),  # TC-002
        ("a", "ValidPassword123!", "账号必须是5-20位的字母或数字"),  # TC-006
        ("validUser", "123", "密码必须包含大小写字母、数字和特殊字符,长度在5-30之间"),  # TC-007
 
    ])
    def test_login(self,page: Page,username, password, expected_message):
        page.goto(LOGIN_URL)
        page.reload()
 
        # 填充用户名和密码
        page.get_by_placeholder("输入账号").fill(username)
        page.get_by_placeholder("输入密码").fill(password)
        page.get_by_role("button", name="登录").click()
        
        # 等待页面加载并验证消息
        if expected_message == "系统欢迎您":
            page.wait_for_selector("h1")  # 等待成功消息
        else:
            page.wait_for_selector("h2")  # 等待登录错误
        
        if username == "validUser":
            if password != "123":
                assert page.locator("h1").inner_text() == expected_message
            else:
                assert expected_message in page.locator("#passwordError").inner_text()            
        elif username == "invalidUser":
            assert expected_message in page.locator("#loginError").inner_text()
        elif username == "a":
            assert expected_message in page.locator("#usernameError").inner_text()
        else:
                assert 0==1
            
 
    def test_access_protected_page_after_logout(self,page: Page):       
        # 登录
        page.goto(LOGIN_URL)
        page.reload()
        page.get_by_placeholder("输入账号").fill("validUser")
        page.get_by_placeholder("输入密码").fill("ValidPassword123!")
        page.get_by_role("button", name="登录").click()
        page.wait_for_selector("h1")  # 等待成功消息
        page.click("body > a")  # 替换为实际的注销按钮选择器
        
        # 尝试访问受保护的页面
        page.goto(WELCOME_URL)  # 替换为欢迎页面的真实路径
        # 验证重定向回登录页面
        assert page.url == LOGIN_URL  # 替换为登录页面的真实路径
 
if __name__ == '__main__':
    pytest.main(["-sv", "Test_login_GUI.py"])

4)Util.py

代码语言:javascript

复制

import pymysql
import re
import hashlib
 
class ClassDB:
    #初始化数据库连接
    def initDB(self):
        host = 'localhost'
        user = 'root'
        password = '123456'
        database = 'ebusiness'
        # 创建数据库连接
        connection = pymysql.connect(host=host, user=user, password=password, database=database)
        return connection
        
    # 关闭数据库连接
    def closedb(self,connection):
        connection.close()
 
    # 清空数据库user表中数据,用于DearDown
    def init_db(self):
        connection = self.initDB()
        try:
            with connection.cursor() as cursor:
                # SQL语句,用于清空user表
                sql = "TRUNCATE TABLE user"    
                # 执行SQL语句
                cursor.execute(sql)   
                # 提交事务
                connection.commit()
        finally:
            self.closedb(connection)
 
    #为登录初始化数据
    def init_login(self,username,password):
        connection = self.initDB()
        try:
            with connection.cursor() as cursor:
                if username == "":
                    username = "validUser"
                else:
                    username = username
                if password == "":
                    password = HASH.hash_password("ValidPassword123!")
                else:
                    password = HASH.hash_password(password)
                # SQL语句,用于清空user表
                sql = "INSERT INTO user (username, password, phone, email) VALUES (\""+username+"\",\""+password+"\", \"13611223344\", \"a@b.com\")"  
                # 执行SQL语句
                cursor.execute(sql)       
                # 提交事务
                connection.commit()
        finally:
            self.closedb(connection)
              
           
class CSRF:
    def get_csrf(html):
        pattern = r'input[^>]+value="([^"]+)"'
        # 使用 re.search 查找匹配
        match = re.search(pattern, html)
        # 提取并打印 value 值
        if match:
            csrftoken_value = match.group(1)
            return csrftoken_value
        else:
            return "未找到 csrftoken"
 
class HASH:
    #对字符串仅更新SHA256散列
    def hash_password(password):
        """对密码进行SHA256散列"""
        return hashlib.sha256(password.encode('utf-8')).hexdigest()

12.3.2测试评审回复

下面是对您提供的测试代码的评审,包括 Test_Login.py、Test_register_GUI.py、Test_login_GUI.py 和 Util.py 的分析和建议。

1Test_Login.py

优点:

结构清晰:使用unittest框架,组织良好,便于理解。

覆盖面广:测试了有效和无效的登录情况,包括用户名和密码的边界条件。

安全性测试:包含了XSS、CSRF 和SQL注入的测试用例,关注安全性。

建议:

URL 配置:将 BASE_URL 和 WELCOME_URL 提取到配置文件或环境变量中,以便于在不同环境中使用。

数据库操作:在setUp和tearDown中的数据库初始化和清理逻辑可以考虑使用上下文管理器,以确保数据库连接的正确关闭。

测试数据:可以考虑使用工厂模式或假数据生成库(如 Faker)来生成测试数据,以提高测试的灵活性和可维护性。

状态码验证:在一些测试用例中,response.status_code 的验证可以根据 API 的实际设计进行调整,例如,可能需要验证 401 或 403 状态码。

2)Test_register_GUI.py

优点:

使用 pytest:利用 pytest 的参数化特性,简化了测试用例的编写。

清晰的测试逻辑:每个测试用例都有明确的目的,便于理解。

建议:

teardown 方法:建议将teardown方法重命名为teardown_class,以便更好地遵循 pytest的命名约定。

将teardown方法重命名为teardown_class。

测试用例的描述:在参数化测试中,考虑使用更具描述性的测试用例名称,以便在测试失败时更容易理解问题。

错误信息验证:在验证错误信息时,可以考虑使用更具体的选择器,确保测试的稳定性。

3)Test_login_GUI.py

优点:

结构清晰:与Test_register_GUI.py 类似,使用pytest和参数化测试。

涵盖基本用例:测试了有效和无效的登录情况。

建议:

setup_class和teardown_class:同样,建议将teardown方法重命名为 teardown_class 以符合pytest的约定。

页面等待:在测试中使用 wait_for_selector 是个好主意,但可以考虑使用更具体的等待条件,以提高测试的可靠性。

重复代码:test_login 方法中存在一些重复代码,考虑将登录逻辑提取到一个单独的方法中,以提高代码的复用性。

4)Util.py

优点:

功能模块化:将数据库操作、CSRF 处理和密码哈希功能分开,增强了代码的可读性和可维护性。

建议:

SQL注入风险:在init_login方法中,使用字符串拼接构建 SQL 查询可能导致 SQL 注入风险。建议使用参数化查询来避免此问题。

测试代码,不考虑SQL注入。

异常处理:在数据库操作中,考虑添加异常处理,以便在数据库连接失败或查询出错时能够捕获并处理异常。

代码注释:尽管代码相对清晰,但适当的注释可以帮助其他开发者更快理解代码逻辑,尤其是在复杂的逻辑部分。

总结

整体来看,您的测试代码结构清晰,覆盖面广,关注了安全性测试。通过一些小的改进,可以提高代码的可维护性和安全性。继续保持良好的测试习惯,并考虑使用一些现代化的工具和库来简化测试工作。

目录
相关文章
|
20天前
|
前端开发 JavaScript 测试技术
使用ChatGPT生成登录产品代码的测试用例和测试脚本
使用ChatGPT生成登录产品代码的测试用例和测试脚本
74 35
|
20天前
|
前端开发 JavaScript Java
使用ChatGPT生成登录产品代码
使用ChatGPT生成登录产品代码
65 33
|
20天前
|
JavaScript 前端开发 Java
使用ChatGPT生成关于登录产品代码的单元测试代码
使用ChatGPT生成关于登录产品代码的单元测试代码
41 16
|
1月前
|
机器学习/深度学习 人工智能 搜索推荐
哈佛推出全新类ChatGPT癌症诊断AI,登上Nature!准确率高达96%
哈佛大学研究团队开发的新型AI模型CHIEF,在《自然》期刊发表,癌症诊断准确率达96%。CHIEF基于深度学习,能自动识别、分类癌症并预测生存期,具高准确性、多任务能力和泛化性。它结合病理图像与基因组学等数据,显著提升诊断效率和个性化治疗水平,有望改善医疗资源不平等。但数据隐私和临床效果验证仍是挑战。论文见:https://www.nature.com/articles/s41586-024-07894-z
169 101
|
1天前
|
存储 人工智能 API
ChatGPT-on-WeChat:Star32.4k, DeepSeek加持!这款开源神器秒变AI助手,聊天体验直接起飞!
嗨,大家好,我是小华同学。今天为大家介绍一款结合DeepSeek引擎的开源项目——ChatGPT-on-WeChat,由开发者zhayujie打造。它将微信变成智能AI助手,支持文本、图片、语音对话,具备定时提醒、天气查询等扩展功能,完全开源且易于定制。项目地址:https://github.com/zhayujie/chatgpt-on-wechat。关注我们,获取更多优质开源项目和高效学习方法。
55 11
|
3天前
|
人工智能 自然语言处理 算法
DeepSeek vs ChatGPT:AI对决中的赢家是……人类吗?
DeepSeek VS ChatGPT:DeepSeek以开源黑马姿态崛起,凭借低成本、高性能的「DeepSeek-V3」和专为深度推理设计的「DeepSeek-R1」,成为中小开发者的首选。而ChatGPT则较贵。 然而,AI依赖也带来隐忧,长期使用可能导致记忆衰退和“脑雾”现象。为此,推荐Neuriva解决方案,专注力提升30%,记忆留存率提升2.1倍,助力人类在AI时代保持脑力巅峰。 DeepSeek赢在技术普惠,ChatGPT胜于生态构建,人类的关键在于平衡AI与脑力健康,实现“双核驱动”突破极限!
108 7
|
2月前
|
人工智能 搜索推荐 iOS开发
OpenAI推出适用于iPhone的ChatGPT,与Apple实现具有里程碑意义的AI整合
OpenAI推出适用于iPhone的ChatGPT,与Apple实现具有里程碑意义的AI整合
|
6月前
|
人工智能 自然语言处理 搜索推荐
chatgpt这么火,现在AI搜索引擎有哪些呢?
国外AI搜索引擎包括ChatGPT,擅长自然语言处理与内容生成;Google Bard,提供智能个性化搜索体验;Microsoft Bing集成GPT模型增强智能检索;Perplexity AI以简洁答案及文献引用著称;Neeva强调隐私保护与无广告服务。国内方面,天工AI支持多种功能如知识问答与代码编程;腾讯元宝基于混元模型助力内容创造与学习;360AI搜索以精准全面的信息搜索见长;秘塔AI专注提升写作质量和效率;开搜AI搜索提供个性化智能搜索服务。以上引擎均利用先进AI技术提升用户体验。更多详情参阅[AI搜索合集](zhangfeidezhu.com/?page_id=651)。
178 8
chatgpt这么火,现在AI搜索引擎有哪些呢?
|
6月前
|
机器学习/深度学习 人工智能 自然语言处理
HuggingGPT解析:使用 ChatGPT及HuggingFace上的族系解决AI问题
HuggingGPT是一个框架,它使用大型语言模型(如ChatGPT)作为控制器来管理和协调Hugging Face上的AI模型,以语言作为通用接口解决多模态和领域的复杂AI任务。
114 0
HuggingGPT解析:使用 ChatGPT及HuggingFace上的族系解决AI问题
|
6月前
|
机器学习/深度学习 人工智能 算法
为什么ChatGPT等AI大模型都是基于Python开发?
为什么ChatGPT等AI大模型都是基于Python开发?
141 0