顾老师新作“带着ChatGPT玩转软件开发”将于今年底明年初出版
4.4使用ChatGPT关于找回密码的代码评审
到目前为止,存在以下12个文件需要代码评审。
•前端代码
Ø提炼出的HTML文件
◆VeriCodePage.jsp。
◆RecoverPage.jsp。
ØJavaScript文件
◆index.js。
•后端代码
ØJSP文件
◆VeriCodePage.jsp。
◆RecoverPage.jsp。
ØJava文件
◆UserRepository.java。
◆PasswordRecoveryTest.java。
◆RecoverTest.java。
◆LoginController.java。
◆RegisterController.java。
•测试文件
ØAPI测试代码:Test_Recover.py。
ØGUI测试代码:Test_Recover_GUI.py。
4.4.1前端找回密码代码评审
1.申请前端找回密码代码评审
提示词
请对下面前端代码进行代码评审
1)提炼出的HTML文件
①VeriCodePage.jsp
参看代码4-56 VeriCodePage.jsp
②RecoverPage.jsp
参看代码4-56 RecoverPage.jsp
2)index.js
参看代码4-7 index.js
2.前端找回密码代码评审回复
ChatGPT回答
对您提供的前端代码进行评审时,我将从代码结构、可读性、性能和安全性等方面进行分析。
1)提炼出的HTML文件
①VeriCodePage.jsp
1.优点
•结构清晰:HTML结构简洁明了,包含必要的表单元素和标签。
•使用了CSRF令牌:通过<input type="hidden">传递CSRF令牌,有助于提高。
2.改进建议
•表单验证:建议在<form>标签中添加novalidate属性,以便使用JavaScript自定义验证,而不是浏览器的默认验证。
•错误信息的可访问性:可以考虑为错误信息添加aria-live属性,以便屏幕阅读器能够及时通知用户。
•CSS和JavaScript文件的相对路径:建议使用绝对路径或基于根的路径,以避免在不同目录中访问时出现问题。
注解。
•暂不考虑在<form>标签中添加novalidate属性。
•考虑到旧版本浏览器的兼容性,暂不添加aria-live属性。
•如前所述,使用相对路径便于移植。
②RecoverPage.jsp
1.优点
•表单设计:设计合理,包含必要的输入字段和错误提示区域。
•重用代码:与VeriCodePage.jsp使用相同的CSS和JavaScript文件,避免重复代码。
2.改进建议
•表单验证:同样可以考虑在<form>标签中添加novalidate属性。
•错误信息的可访问性:同样建议为错误信息添加aria-live属性。
注解。
•暂不考虑在<form>标签中添加novalidate属性。
•考虑到旧版本浏览器的兼容性,暂不添加aria-live属性。
2)index.js
1.优点
•模块化函数:将验证逻辑分散到多个函数中,使得代码更易于维护和理解。
•正则表达式使用:使用正则表达式进行输入验证是一个良好的实践。
2.改进建议
•全局变量:在JavaScript中,避免使用全局变量(如phone),应使用let或const声明变量以确保其作用域。
•函数命名:函数命名可以更具描述性,例如CheckField()可以改为validateField()以提高可读性。
•错误处理:在hash256()和其他函数中,建议增加错误处理逻辑,以便捕获和处理可能的异常。
•性能优化:在check和checkRecover函数中,重复调用。
•document.getElementById可以通过将其结果存储在变量中来优化性能。
•代码注释:增加代码注释,特别是在复杂的逻辑部分,以便其他开发者更容易理解代码的意图。
注解。
•修改
代码4-58index.js
phone=CheckFieldByRecover(phonePattern,contact,isValid);
email=CheckFieldByRecover(emailPattern,contact,isValid);为
let phone=CheckFieldByRecover(phonePattern,contact,isValid);
let email=CheckFieldByRecover(emailPattern,contact,isValid);•将CheckField()方法改为validateField()。
•下一个版本将考虑在hash256()和其他函数中增加错误处理逻辑。
•暂时不考虑在check和checkRecover函数中,重复调用。
•下一个版本将考虑通过将document.getElementById的结果存储在变量中来优化性能。
•下一个版本将考虑优化代码注释。
3.安全性
•密码哈希:在hash256()函数中,密码在客户端进行哈希处理,虽然可以增加一定的安全性,但仍然建议在服务器端进行密码哈希,以确保安全性。
•输入验证:确保所有用户输入在服务器端也进行验证,以防止恶意输入。
•HTTPS:确保所有数据传输通过HTTPS进行,以保护用户数据的安全。
注解。
•正如前面提到的,客户端和服务器端都对密码进行了哈希处理,因此不进行修改。
•正如前面提到的,客户端和服务器端都对用户输入数据进行了验证。
•下一版本将考虑确保所有数据传输通过HTTPS进行。
4.总结
整体而言,您的代码结构清晰,功能实现合理,但在可读性、性能和安全性方面还有进一步优化的空间。通过遵循上述建议,您可以提高代码的可维护性和安全性。如果您有任何具体问题或需要进一步的帮助,请随时告知!
4.4.2找回密码后端代码评审
1.JSP文件代码评审
1)申请JSP文件进行代码评审
提示词
请对以下Java后端代码进行评审。
VeriCodePage.jsp
参看代码4-56 VeriCodePage.jsp
RecoverPage.jsp
参看代码4-56 RecoverPage.jsp
2)JSP文件代码评审回复
ChatGPT回复
代码评审-VeriCodePage.jsp和RecoverPage.jsp
1.优点
•CSRF Token机制:两段代码均实现了CSRF Token的生成与验证,增强了安全性。
•表单验证:通过JavaScript的onsubmit()事件,增加了客户端的表单验证。
•结构清晰:代码结构相对清晰,易于理解和维护。
2.改进建议。
•CSRF Token生成与处理。
•当前的CSRF Token是在JSP页面中生成的,建议将其生成逻辑封装到后端控制器中,以确保更高的安全性和一致性。
•在CookiesManager类中,建议提供一个方法用于生成Token,并在构造函数中仅传递必要的参数。
注解。
•下一版本将在JavaBean中生成CSRF Token。
•下一版本将提供一个方法用于生成Token。
3.Cookie设置。
•CookiesManager的构造函数中传入csrftoken,但实际设置Cookie的逻辑在setCookie()方法中。建议将Cookie的设置逻辑集中在CookiesManager中,避免在JSP中重复代码。
•setSecure(true)应根据实际部署环境动态设置,确保Cookie只在HTTPS环境下使用。
注解。
•下一版本将把Cookie的设置逻辑集中到CookiesManager中。
•下一版本将根据实际部署环境动态设置setSecure(true),并使用HTTPS。
4.错误处理。
•目前的错误处理仅通过out.println输出错误信息,建议使用更健壮的错误处理机制,例如抛出异常并在用户界面上显示友好的错误信息。
•使用日志框架(如SLF4J或Log4J)记录错误信息,而不是直接在页面输出。
注解。
•下一版本改进错误处理机制。
•下一版本考虑在JSP页面采用Log4J日志框架。
5.SQL注入和XSS防护。
•在处理用户输入(如request.getParameter())时,应进行适当的验证和清理,以防止SQL注入和XSS攻击。
•例如,对于用户输入的contact和identifyingCode,应使用合适的验证逻辑。
注解:前端和后端均已对用户输入进行了验证,以防止SQL注入和XSS攻击。
6.代码重复。
•VeriCodePage.jsp和RecoverPage.jsp中有大量重复代码(如CSRF Token处理),建议将其提取到一个公共的JSP文件或方法中,以减少代码重复,提高可维护性。
注解:下一版本将提取VeriCodePage.jsp和RecoverPage.jsp中的重复代码到一个公共JSP文件或JavaBean方法中。
7.JavaScript代码的安全性。
•在客户端进行表单验证时,确保JavaScript代码的安全性,避免潜在的XSS攻击。
•可以考虑使用CSP(内容安全策略)来进一步保护页面。
注解。
•JavaScript中已进行验证,以避免XSS攻击。
•下一版本将改进使用内容安全策略(CSP)。
8.页面编码。
•在输出用户输入时,建议使用out.print()或out.write()的HTML编码,以防止XSS攻击。例如,可以使用StringEscapeUtils.escapeHtml4()。
注解:下一版本将使用StringEscapeUtils.escapeHtml4()。
9.使用JSP EL 和 JSTL。
•考虑使用JSP EL和JSTL来简化JSP页面中的Java代码,使页面更清晰,逻辑更分离。
注解:下一版本将使用JSP EL和JSTL。
10示例改进(部分)。
<%@page contentType="text/html;charset=utf-8"%>
<%@page import="ebusiness.Util,ebusiness.CookiesManager,ebusiness.PasswordRecoveryController"%>
<%@page import="org.apache.commons.text.StringEscapeUtils"%>
<%
CookiesManager ct=new CookiesManager(response,request);
String csrftoken=ct.generateCsrfToken();//假设在 CookiesManager 中实现
ct.setCookie(csrftoken);
%>
<div id="recoverError" class="error">
<%
String method=request.getMethod();
if("post".equalsIgnoreCase(method)){
String csrftoken_post=request.getParameter("csrftoken");
String csrftoken_cookies=ct.getCookie();
if(csrftoken_cookies!=null&&csrftoken_cookies.equals(csrftoken_post)){
PasswordRecoveryController recover=new PasswordRecoveryController(response,request);
recover.recoverPassword();
}else{
out.println("可能存在CSRF攻击风险");
}
}
%>
</div>总结
•代码实现了基本的CSRF防护机制,但在安全性和可维护性方面存在改进空间。
•通过提取公共逻辑、增强错误处理和输入验证、动态设置Cookie 属性等方式,可以有效提升整体安全性和代码质量。
2.Java文件代码评审
1)申请Java文件进行代码评审
评审前先做如下改动。
第1步:UserRepository.java的Connection connect()方法中改为。
代码4-59 UserRepository.java
…
Util util=new Util();
Properties prop=util.getPropertiesInfo("jdbc.properties");
String driver=prop.getProperty("driver");
String url=prop.getProperty("url");
String user=prop.getProperty("user");
String password=prop.getProperty("password");
Class.forName(driver);
Connection conn=DriverManager.getConnection(url,user,password);
…第2步:Util.java的SendMail(String contact,String message)方法中改为。
代码4-60 Util.java
public boolean SendMail(String contact,String message){
try{
Properties prop=util.getPropertiesInfo("config.properties");
//获取数据
String SNMPTServer=prop.getProperty("SNMPTServer");
String from=prop.getProperty("from");
String to=contact;
String title=prop.getProperty("title");
String content=message;
String username=prop.getProperty("username");
String password=prop.getProperty("password");
String port=prop.getProperty("port");
List list=new ArrayList();
//发送Emial验证码
EmailSender sender=new EmailSender(SMTPServer,from,to,title,content,list,username,password,port);
String [] result=sender.sendMail();
if (result[1].equals("发送成功")){
return true;
}else{
return false;
}
}catch (IOException ex){
logger.error("发Email验证码信息发生错误" + ex.getMessage());
}
return false;
}第3步:使用try-with-resources格式修改所有SQL语句,比如。
String insertString="INSERT INTO user(username,password,phone,email)VALUES(?,?,?,?)";
try (Connection conn=dataSource.getConnection();
PreparedStatement stmt=conn.prepareStatement(insertString)){
…
try (ResultSet rs=stmt.executeQuery()){
…提示词
请对以下Java后端代码进行评审。
① UserRepository.java
参看代码4-59 UserRepository.java
② PasswordRecovery.java
参看代码4-26 PasswordRecovery.java
③ PasswordRecoveryController.java
参看代码4-32 PasswordRecoveryController.java
④ LoginController.java
参看代码3-58 LoginController.java
⑤ RegisterController.java
参看代码3-54 RegisterController.java
2)Java文件代码评审回复
ChatGPT回答
对您提供的Java后端代码进行评审时,我将从代码结构、可读性、性能、安全性等方面进行分析。
①UserRepository.java
这段代码实现了一个用户存储库类UserRepository,用于处理用户的各种操作,包括注册、登录、密码管理等。以下是针对该代码的一些评审和建议,涵盖了可读性、健壮性、安全性和性能等方面。
1.可读性和结构
•方法命名:方法名称清晰地表达了其意图,例如insert,userExists,login等,使代码可读性较强。
•日志记录:日志记录很详细,能帮助开发者追踪问题,尤其是SQL操作方面的错误。
2.异常处理
•目前的异常处理可以提高。尽量不要捕捉SQLException后继续返回错误代码,考虑利用自定义异常来处理错误,这样能更好地向调用者传达错误信息,同时也能避免隐藏错误。
3.事务管理
•事务的使用有些不一致。例如在执行queryForEmailOrPhone()和deleteCodeInDB()等方法时,如果开启了setAutoCommit(false),但在try-with-resources的上下文中。实际上,许多操作可以在不控制事务的情况下进行,因为它们是单一的查询或插入操作。
注解。
•下一版本考虑利用自定义异常来处理错误。
•改造String getPassword(int uid)。
代码4-61 UserRepository.java
//通过User ID获得user表的密码
public String getPassword(int uid){
String password=null;
String query="SELECT password FROM user WHERE id=?";
try(Connection conn=dataSource.getConnection();
PreparedStatement stmt=conn.prepareStatement(query)){
stmt.setInt(1,uid);
try(ResultSet rs=stmt.executeQuery()){
if(!rs.next()){
logger.error("通过id获得user表的密码失败");
return "0";//不存在
}else{
password=rs.getString("password");//存在
logger.info("通过id获得user表的密码成功");
}
}
}catch(SQLException e){
logger.error("getPassword发生数据库错误:"+e.getMessage());
}
return password!=null?password:"-1";//返回密码或-1
}4.SQL注入安全
•尽管使用了PreparedStatement,这已经很好地防止了SQL注入,但在某些地方,依然存在对用户输入的的不必要的信任(如在judgeInfo()里使用field直接拼接成SQL语句),需要严密检查field参数的合法性,避免SQL注入的风险。
注解:在
String queryString="SELECT * FROM user WHERE "+field+"=?";前面加入判断。
if(!field.equals("username")||!field.equals("phone")||!field.equals("email")
{
logger.info("user表"+field+"字段不存在"+value+"值");
return "0";
}else{
String queryString="SELECT * FROM user WHERE "+field+"=?";
}5.字段唯一性验证
•userExists()方法会多次调用judgeInfo(),可以考虑使用一个方法来接收一个字段列表以及对应的值,来减少数据库的访问,提高性能。
注解:下一版本考虑使用一个方法来接收一个字段列表以及对应的值。
6.密码管理
•登录过程中,密码是以明文存储和验证的,这是一个潜在的安全隐患。建议使用加密算法(例如BCrypt、PBKDF2等)来securely storage密码,确保用户密码的安全性。
注解:正如前述,数据库中密码使用SHA256存储。
7.返回值设计
•方法返回值的设计也可以做一定改善。例如,返回字符串表示的结果并不直观,尤其是多个方法对于成功和失败的返回值会造成调用者理解的难度,可以考虑使用自定义的响应对象,例如Result或者ServiceResponse。
注解:下一版本考虑方法返回值的设计优化。
8.代码复用
•某些逻辑(例如查询操作)可以提取为通用方法,以减少代码重复。例如,重复的连接获取和PreparedStatemen的创建,可以考虑封装成一个方法。
注解:下一版本考虑提取为通用方法。
9.关闭资源
•使用try-with-resources保证PreparedStatement和ResultSet的关闭很重要。但是在某些捕捉块外仍需确认连接被正确关闭。
注解:确认后不存在连接没有被正确关闭。
10.代码注释
•尽量保持注释的更新,确保注释与代码逻辑保持一致。同时,尽量减少注释的使用,通过清晰的代码结构和命名提升代码自解释性。
注解:下一版本考虑代码注释的优化。
11.Magic Numbers
•代码中使用了一些数值,比如最大连接数、超时时间等,建议使用常量定义。
注解:下一版本考虑解决Magic Numbers。
12.改进后的示例
以下是部分改进示例。
代码4-62 UserRepository.java
public String userExists(String username,String email,String phone){
//页面的逻辑可能更易于理解
String result=judgeInfo("username",username);
if(!"0".equals(result))return "注册用户的账号必须唯一";
result=judgeInfo("phone",phone);
if(!"0".equals(result))return "注册用户的手机必须唯一";
result=judgeInfo("email",email);
if(!"0".equals(result))return "注册用户的邮箱必须唯一";
return "0";
}注解:按上述方法修改,测试通过。
13.总结
这个UserRepository类实现了基本的用户管理功能,整体结构清晰,但在安全性、健壮性及性能方面还有机会改进。重点关注异常处理、事务管理,确保用户数据安全,加密存储敏感信息,以及提高代码复用率来优化代码结构。
②PasswordRecovery.java
以下是对你提供的Java后端代码的评审以及一些改进建议。
1.代码结构和可读性
•清晰的命名
Ø此代码中变量名和方法名基本上是自解释的,但某些地方如info_i,info_s可以改为更具描述性的名称(如verificationResult,validationResult),被引用的地方也需要更改。
注解。
第1步:将
代码4-63 PasswordRecovery.java
//判断验证码是否正确
int info_i=mysql.verifyCode(identifyingCode,uid);
if(info_i!=1){
return "验证码错误,请重新输入!";
}修改为。
//重置密码
info_i=updatePassword(newPassword,oldPassword,mysql,uid);
if(info_i==-1){
return "-1";
}第2步:将
//重置密码
info_i=updatePassword(newPassword,oldPassword,mysql,uid);
if(info_i==-1){
return "-1";
}修改为。
//重置密码
updatePasswordInfo=updatePassword(newPassword,oldPassword,mysql,uid);
if(updatePasswordInfo==-1){
return "-1";
}第3步:将
//验证新密码是否使用过
boolean info_b=isPasswordUsed(newPassword,uid,mysql);
if(!info_b){
return "这个密码以前设置过,请用一个新密码!";
}修改为。
//验证新密码是否使用过
boolean isPasswordUsedInfo=isPasswordUsed(newPassword,uid,mysql);
if(!isPasswordUsedInfo){
return "这个密码以前设置过,请用一个新密码!";
}第4步:将
int info=mysql.deleteCodeInDB(uidi);
if(info==1 ||info==0){
//将新的验证码记录放入password修改为。
int deleteCodeInDBInfo=mysql.deleteCodeInDB(uidi);
if(deleteCodeInDBInfo==1||deleteCodeInDBInfo==0){
//将新的验证码记录放入password2.提取方法
•sendVerificationCode()方法较长且具有多个关注点。建议将其拆分成多个小方法,例如。
handleSmsVerification(String contact,int uid,String code)
handleEmailVerification(String contact,int uid,String code)•这样可以提高代码的可读性和可重用性。
注解:改写sendVerificationCode()。
代码4-63 PasswordRecovery.java
//通过手机号或Email发送验证码
public String sendVerificationCode(String contact)throws Exception{
String contactType=getContactType(contact);
if(contactType==null){
return "您输入的手机号或Email格式不正确!";
}
UserRepository userRepository=new UserRepository();
String uid=getUserIdByContact(contact,userRepository);
if(!uid.matches("\\d+")){
return uid;
}
int uidi=Integer.parseInt(uid);
String code=util.generateRandomString(6,"int");
UserRepository mysql=new UserRepository();
HikariDataSource dataSource=mysql.dataSource;
if(contactType.equals("phone")){
return sendCodeForSMS(contact,uidi,code,userRepository,mysql,uid);
}else if(contactType.equals("email")){
return sendCodeForEmail(contact,code,uidi,uid,mysql);
}
util.closePool(dataSource);
return contactType;
}
//通过手机号发送验证码
private String sendCodeForSMS(String contact,int uidi,String code,UserRepository mysql,String uid)throws Exception{
if(sendSmsVerificationCode(contact,uidi,code,mysql).equals("验证码已发送至手机")){
//清除以前code表中的验证码记录
int info=mysql.deleteCodeinDB(uidi);
if(info==1 ||info==0){
//将新的验证码记录放入code表
mysql.putCodeInDB(code,uidi);
return uid;
}else{
logger.error("从code删除数据失败");
return "-1";
}
}else{
logger.error("短信发送失败!");
return "-1";
}
}
//通过Email发送验证码
private String sendCodeForEmial(String contact,String code,int uidi,String uid,UserRepository mysql)throws Exception{
if(sendEmailVerificationCode(contact,code).equals("验证码已发送至邮箱")){
int info=mysql.deleteCodeinDB(uidi);
if(info==1 ||info==0){
//将新的验证码记录放入password
mysql.putCodeInDB(code,uidi);
return uid;
}else{
logger.error("从code删除数据失败");
return "-1";
}
}else{
logger.error("邮件发送失败!");
return "-1";
}
}拆分为
sendCodeForSMS(String contact,int uidi,String code,UserRepository mysql,String uid)和
sendCodeForEmail(String contact,String code,int uidi,String uid,UserRepository mysql)提取sendCodeForEmail()和sendCodeForSMS()公共部分。
代码4-64 PasswordRecovery.java
private String sendCodeForSMS(String contact,int uidi,String code,UserRepository mysql,String uid)throws Exception{
return sendCode(contact,mysql,uidi,uid,code,"SMS");
}
private String sendCodeForEmail(String contact,String code,int uidi,String uid,UserRepository mysql)throws Exception{
return sendCode(contact,mysql,uidi,uid,code,"Email");
}
private String sendCode(String contact,UserRepository mysql,int uidi,String uid,String code,String sign)throws Exception{
boolean judge=false;
if(sign.equals("SMS")){
judge=sendSmsVerificationCode(contact,uidi,code,mysql).equals("验证码已发送至手机");
}else if(sign.equals("Email")){
judge=sendEmailVerificationCode(contact,code).equals("验证码已发送至邮箱");
}
if(judge){
int info=mysql.deleteCodeinDB(uidi);
if(info==1 ||info==0){
//将新的验证码记录放入password
mysql.putCodeInDB(code,uidi);
return uid;
}else{
logger.error("从code删除数据失败");
return "-1";
}
}else{
if(sign.equals("SMS")){
logger.error("短信发送失败!");
}else if(sign.equals("Email")){
logger.error("邮件发送失败!");
}
return "-1";
}
}3.更好的异常处理
•throws Exception不明确,建议使用具体的异常类型(如SQLException,RuntimeException)或进行更细致的异常处理。
•使用try-catch语句进行异常处理,并在发生异常时记录日志,避免程序因为未捕获的异常而崩溃。
注解。
•下一版本将优化异常处理机制。
•将sendVerificationCode(String contact)修改为。
代码4-65 PasswordRecovery.java
public String sendVerificationCode(String contact)throws Exception{
String contactType=getContactType(contact);
if(contactType==null){
return "您输入的手机号或Email格式不正确!";
}
UserRepository mysql=new UserRepository();
try(HikariDataSource dataSource=mysql.dataSource){
String uid=getUserIdByContact(contact,mysql);
…
}4.数据库连接池管理
•资源管理
Ø数据库连接池在多个地方被关闭,这看似是为了防止泄漏,但过于频繁地开关连接相对较浪费资源。考虑在类的构造函数或 sendVerificationCode()方法之外进行连接池的初始化和关闭。
•使用try-with-resources
Ø优化资源管理,可以使用try-with-resources来确保即使出现异常也能正确关闭数据库连接。
注解。
•下一版本将解决数据库连接池多次关闭的问题。
•下一版本将采用try-with-resources语法优化这段代码。
5.逻辑和业务验证
•重复代码
Ømysql.deleteCodeInDB(uidi)和之后的逻辑在两种发送方式(短信与邮件)中均出现,建议提取成一个单独方法以减少重复。
•返回值一致性
Ø方法返回多种不同格式(字符串、整数),不应使用类似"-1"或"0"这样的魔法值。建议使用标准的返回结构(如一个封装类)来提供更清晰的错误消息和状态。
注解。
•下一版本将合并多处出现的mysql.deleteCodeInDB(uidi)调用。
•下一版本将魔法值替换为具有实际意义的常量。
6.验证码生成
•在验证码生成方面,generateRandomString()接收的参数为"int",这种方式有些不清晰,可以考虑使用枚举或更具体的方法名以提高可读性。
注解:generateRandomString()的参数为"int",指示随机字符串的长度,暂不作优化。
7.安全性
•密码处理
Ø在当前输出中并未看到散列操作,当检查密码时是否确保已安全地散列存储。确保密码不可逆散列,并在验证密码时同步处理散列。
Ø在代码中增加对密码复杂度的检查,以避免简单或易猜测的密码。
注解:如前所述,密码在传输和存储过程中均进行了哈希处理。
8.测试
•单元测试
Ø建议为每个公共方法编写单元测试,以确保其功能在未来的修改中不被意外破坏。
注解:已为所有公共方法编写单元测试脚本,且均已通过测试。
9.总结
代码的逻辑和功能相对清晰,但在可维护性、异常处理和资源管理方面还有改进空间。通过对代码结构及逻辑进行重构可以提升代码的可读性和安全性。同时,确保编写单元测试以确保功能的一致性和可靠性是非常重要的。
③PasswordRecoveryController.java
这段Java后端代码是一种典型的控制器实现,用于处理用户的密码恢复请求。在进行代码评审时,以下是一些比较重要的关注点和建议。
1.代码结构和可读性
•类命名与职责:类名PasswordRecoveryController明确指出了控制器的目的,符合SRP原则,有助于理解类的功能。
•方法命名:方法sendVerificationCode()和recoverPassword()名称能清晰表明其用途,但可以考虑使用动词"send"和"recover"前缀以提高一致性,比如sendVerificationCode()和resetPassword()。
注解:将暂时保留recoverPassword()方法名称。
2.异常处理
•异常捕获:sendVerificationCode()和recoverPassword()内都仅捕获通用Exception,考虑具体化捕获异常类型并分别处理逻辑,比如SQLException、IOException,以便更好地理解和调试。
•异常记录:在捕捉异常后,仅调用e.printStackTrace(),建议增加对此异常的记录或者采用统一的错误响应格式返回,这样可以提高系统的信息化和可维护性。
注解。
•下一版本将改善异常处理机制。
•去掉
代码4-66 PasswordRecoveryController.java
logger.error("发送验证码的时候发生错误:"+e.getMessage());保留
logger.error("XXXX的时候发生错误:"+e.getMessage());3.资源管理
•数据库连接池的关闭:在recoverPassword()方法中,调用util.closePool(dataSource);可能引发问题,尤其是在方法执行过程中出现异常的情况下,连接池未必能够被正确关闭。应考虑使用try-with-resources语句或在finally代码块中确保连接池关闭。
注解:修改
代码4-67 PasswordRecoveryController.java
//重置密码
public String recoverPassword()throws SQLException,IOException{
…
String info=passwordrecovery.recoverPassword(identifyingCode,newPassword,mysql,uidi);
if(info.equals("0")){
userId.clearCookie();
response.sendRedirect("LoginPage.jsp");
}else{
String encodedMessage=URLEncoder.encode(info,"UTF-8");
String url="RecoverPage.jsp?error="+encodedMessage;
response.sendRedirect(url);
}
Util util=new Util();
util.closePool(dataSource);修改为。
//重置密码
public String recoverPassword()throws SQLException,IOException{
…
//更新新密码
try(HikariDataSource dataSource=mysql.dataSource){
String info=passwordrecovery.recoverPassword(identifyingCode,newPassword,mysql,uidi);
if(info.equals("0")){
userId.clearCookie();
response.sendRedirect("LoginPage.jsp");
}else{
String encodedMessage=URLEncoder.encode(info,"UTF-8");
String url="RecoverPage.jsp?error="+encodedMessage;
response.sendRedirect(url);
}
}catch(Exception e){
logger.error("userInfoIsInviald()数据库发生错误:"+e.getMessage());
}
}
return "0";
}这样当语句结束dataSource被自动关闭。
4.安全性
•敏感信息处理:在记录密码重置信息时,确定不记录敏感信息,如密码明文等。
•验证码的安全性:若用于验证码的用户标识(uid)使用cookie,考虑对cookie设置HttpOnly和Secure标志,以增加安全性。
•输入验证:没有对参数contact、identifyingCode和newPassword进行充分的输入验证和过滤。考虑添加输入验证以防止出现SQL注入、跨站脚本等安全问题。
注解。
•敏感信息不被记录。
•当前不考虑使用cookie存储验证码的用户标识(uid)。
•前后端均已对输入参数进行验证,确保不发生SQL注入和跨站脚本等安全问题。
5.逻辑处理
•重定向处理:每次sendRedirect()调用后,方法就会返回,可能导致部分代码未被执行。确保逻辑简洁,避免多余逻辑执行。
•引入常量:在代码中使用的字符串,如"uid""error"和不同的页面URL等,建议定义成常量,以便于维护和避免硬编码字符串错误。
注解。
•下一版本将处理重定向相关的问题。
•暂时不考虑将"uid""error"和不同页面URL定义为常量。
6.依赖管理
•依赖注入:考虑依赖注入的方式来传递HttpServletResponse和HttpServletRequest,这可以增强可测试性和灵活性。
•分页逻辑:在调用数据库相关的方法前,考虑提前实例化数据库访问组件,而不是在每个请求中都实例化。
注解。
•暂时不考虑通过依赖注入方式传递HttpServletResponse和HttpServletRequest。
•目前不考虑实现分页逻辑。
7.代码重复
•URLEncoder.encode(info,"UTF-8")和response.sendRedirect(url)的这种重复逻辑可以考虑提取为单独的方法,以避免代码重复。
注解:下一版本将解决URLEncoder.encode(info,"UTF-8")和response.sendRedirect(url)重复逻辑的问题。
8.总结
该代码实现了基本的密码恢复功能,结构清晰,但在资源管理、安全性、异常处理和性能优化等方面可以进一步增强。如果能整合上述建议,将使代码更具可读性、安全性和维护性
④LoginController.java
这段Java后端代码实现了用户登录功能,整体结构清晰,但仍存在一些可改进之处。以下是对代码的评审及建议。
1.代码结构与可读性
•方法命名:方法名performLogin()清晰表明其功能,但可以考虑更简化的命名,比如login(),让它更加简洁。
•变量命名:变量validationResult和info的命名比较模糊,可以考虑使用更加具描述性的名字,比如validationMessage和errorMessage,以提高可读性。
注解。
•保留performLogin()方法名。
•将validationResult修改为validationMessage;将info修改为errorMessage。
2.异常处理
•异常捕获:在performLogin()方法中,捕获了所有异常并只记录错误信息。应考虑细化异常类型(如SQLException或与登录逻辑相关的具体异常),并相应地处理以提高系统的健壮性。
•异常响应:不仅记录异常,同时可以通过返回给用户友好的错误消息来提升用户体验。
注解。
•在下一版本中添加异常捕获。
•在下一版本中完善异常响应处理。
3.资源管理
•数据库连接管理:在UserRepository类的login()方法中进行数据库操作时,应确保连接在使用后适当地关闭,避免连接泄漏。当前代码可通过try-with-resources语句增加代码的安全性。
注解:优化一下代码,将performLogin()拆分为void validatePass(User user,String errorMessage,String username)和void validatefail(String errorMessage)。
代码4-68 LoginController.java
public String performLogin()throws SQLException,IOException{
String username=request.getParameter("username");
String password=request.getParameter("password");
User user=new User(username,password);
ValidateUser ValidateUser=new ValidateUser();
String validationMessage=ValidateUser.validate(user);
String errorMessage="";
if(validationMessage.equals("验证成功")){
validatePass(user,errorMessage,username);
}else{
errorMessage=validationMessage;
validatefail(errorMessage);
}
return "0";
}
private void validatePass(User user,String errorMessage,String username)throws SQLException,IOException{
Util util=new Util();
UserRepository mysql=new UserRepository();
HikariDataSource dataSource=mysql.dataSource;
try{
if(mysql.login(user).equals("0")){
String url="/jsp/LoginPage.jsp";
errorMessage="账号或密码错误";
util.sendRedirect(response,request.getContextPath()+url,"error",errorMessage);
}else{
CookiesManager myusername=new CookiesManager("username",username,response,request);
myusername.setCookie();
String url="/jsp/WelcomePage.jsp";
util.sendRedirect(response,request.getContextPath()+url);
}
}catch(Exception e){
logger.error("validatePass()发生错误:"+e.getMessage());
}finally{
util.closePool(dataSource);
}
}
private void validatefail(String errorMessage)throws IOException{
Util util=new Util();
String url="/jsp/LoginPage.jsp";
util.sendRedirect(response,request.getContextPath()+url,"error",errorMessage);
}将void validatePass(User user,String errorMessage,String username)修改为。
代码4-69 LoginController.java
private void validatePass(User user,String errorMessage,String username)throws SQLException,IOException{
Util util=new Util();
UserRepository mysql=new UserRepository();
try(HikariDataSource dataSource=mysql.dataSource){
if(mysql.login(user).equals("0")){
…
}catch(Exception e){
logger.error("validatePass()发生错误:"+e.getMessage());
}
}4.输入验证与安全性
•输入验证:虽然注释提到前后端都有输入验证,但在此代码段中没有执行任何输入验证。建议至少检查账号和密码是否为空,以避免不必要的数据库调用。
•敏感信息处理:在错误处理时,输出的错误信息(如“账号或密码错误”)可能会泄露系统信息。考虑将其模糊化,例如,只提示“登录失败”。
注解。
•有专门的模块处理前后端验证输入问题。
前端。
代码4-70 index.js
function check(username,password,confirmPassword,phone,email){
…
}
function checkLogin(username,password){
…
}
function checkVeriCode(contact){
…
}后端。
代码4-71 ValidateUser.java
public boolean isHash(String str){
…
}
public String validate(User user){
…
}•在下一版本中考虑细化提示信息。
5.逻辑处理
•重定向处理:调用response.sendRedirect()后,方法可以提前返回,以防止后续的代码逻辑被执行。建议在重定向后直接返回,以保持逻辑简洁。
response.sendRedirect(url);
return;
注解:方法的返回类型设为void,无需使用return。
6.代码重复
•重复的URLEncoder编码:在多个地方使用了URLEncoder.encode(info,"UTF-8"),可以考虑提取为一个独立的私有方法来提高可维护性。
•字符串比较:使用equals方法时应考虑使用StringUtils等工具类,保证安全与简洁。
注解。
•在下一版本中考虑对重复的URLEncoder编码进行优化。
•暂不进行使用StringUtils等工具类处理字符串比较。
7.依赖管理
•限制直接实例化:在performLogin()方法中直接创建UserRepository和ValidateUser的实例,可考虑通过依赖注入来增强代码的灵活性和可测试性。
8.Cookies管理
•Cookie设置:在CookiesManager的使用中,未看到对Cookie的有效期设置,建议明确设置有效期、路径和其他必要的属性,以确保Cookie能有效地在需求范围内保持。
注解:在CookiesManager类中定义有效期、路径及其他必要属性。
代码4-72 CookiesManager.java
//设置cookies的方法
public void setCookie(){
if(this.cookies_value!=null&&!this.cookies_value.isEmpty()){
try{
//创建一个 Cookie
Cookie cookie=new Cookie(this.cookies_name,this.cookies_value);
//设置 Cookie 的路径
cookie.setPath(request.getContextPath());//确保在应用的上下文中可用
//cookie.setDomain("192.168.31.184");//注意:这可能在某些浏览器中不被支持
//可选:设置 Cookie 的过期时间(单位:秒)
cookie.setMaxAge(60*60*24);//1天
//可选:设置 Cookie 的 HttpOnly 属性
cookie.setHttpOnly(true);
//将 Cookie 添加到响应中
response.addCookie(cookie);
}catch(Exception e){
logger.error("setCookie()发生错误:"+e.getMessage());
}
}else{
//处理cookies为空的情况
logger.info("cookie 为空.");
}
}9.总结
这段代码实现了基本的用户登录功能,结构清晰,但在异常处理、安全性、资源管理等方面还有提升空间。如果能考虑上述建议,将使代码更具可读性、安全性和可维护性。
⑤RegisterController.java
下面是对提供的Java后端代码RegisterController进行评审的结果,包括潜在问题、改进建议和最佳实践。
1.代码结构和可读性
•注释:代码缺乏适当的注释,为关键部分和算法提供注释会提高可读性和可维护性。
•方法长度:performRegister()方法较长,考虑将其拆分成多个小方法以提高可读性。
注解。
•在下一版本中优化注释。
•将performRegister()方法拆分出两个方法:User getUser()和void userInfoIsInviald(User user),具体实现如下。
代码4-73 RegisterController.java
public String performRegister()throws SQLException,IOException{
User user=getUser();
ValidateUser ValidateUser=new ValidateUser();
String validationResult=ValidateUser.validate(user);
Util util=new Util();
if(!validationResult.equals("验证成功")){
util.sendRedirect(response,request.getContextPath()+"/jsp/RegisterPage.jsp","error",validationResult);
}else{
userInfoIsInviald(user);
}
return "0";
}
private User getUser(){
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);
return user;
}
private void userInfoIsInviald(User user)throws SQLException,IOException{
Util util=new Util();
UserRepository mysql=new UserRepository();
HikariDataSource dataSource=mysql.dataSource;
try{
String info=mysql.insert(user);
if(info.equals("1")){
util.sendRedirect(response,request.getContextPath()+"/jsp/LoginPage.jsp");
}else{
util.sendRedirect(response,request.getContextPath()+"/jsp/RegisterPage.jsp","error",info);
}
}catch(Exception e){
logger.error("userInfoIsInviald()数据库发生错误:"+e.getMessage());
}finally{
util.closePool(dataSource);
}
}2.错误处理
•异常捕获:目前代码只记录了SQLException和IOException,并未给出具体的用户反馈。建议将异常信息返回给用户,或在日志中详细记录。
•具体异常捕获:不建议使用通用的Exception捕获,应该更具体化,例如使用SQLException。如果有必要,使用多个catch块来处理不同类型的异常。
注解。
•下一版本细化异常信息。
•下一版本优化具体的异常捕获。
3.表单数据处理
•参数验证:代码中没有对用户输入的有效性进行全面验证(例如空检查、格式检查等)。考虑使用更全面的验证逻辑。
•敏感信息处理:密码需要特别处理,比如加密存储而不是明文存储。
注解:如LoginController.java代码审核反馈,前后端都对用户输入进行了校验。
4.URL编码和重定向
•URL编码:URLEncoder.encode应该在try语句中单独处理以捕获可能的异常。
•重定向的最佳实践:使用HttpServletResponse的sendRedirect()方法时,建议确保URL不会引导用户到预期外的页面。
注解。
•重构代码,不用URLEncoder.encode。
代码4-74 RegisterController.java
package ebusiness;
import java.io.IOException;
import java.sql.SQLException;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import com.zaxxer.hikari.HikariDataSource;
import javax.servlet.http.HttpServletRequest;
public class RegisterController{
private static final Logger logger=Logger.getLogger(RegisterController.class);
private HttpServletResponse response;
private HttpServletRequest request;
public RegisterController(HttpServletResponse response,HttpServletRequest request){
this.response=response;
this.request=request;
}
public String performRegister()throws SQLException,IOException{
User user=getUser();
ValidateUser ValidateUser=new ValidateUser();
String validationResult=ValidateUser.validate(user);
Util util=new Util();
if(!validationResult.equals("验证成功")){
util.sendRedirect(response,request.getContextPath()+"/jsp/RegisterPage.jsp","error",validationResult);
}else{
userInfoIsInviald(user);
}
return "0";
}
private User getUser(){
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);
return user;
}
private void userInfoIsInviald(User user)throws SQLException,IOException{
Util util=new Util();
UserRepository mysql=new UserRepository();
HikariDataSource dataSource=mysql.dataSource;
try{
String info=mysql.insert(user);
if(info.equals("1")){
util.sendRedirect(response,request.getContextPath()+"/jsp/LoginPage.jsp");
}else{
util.sendRedirect(response,request.getContextPath()+"/jsp/RegisterPage.jsp","error",info);
}
}catch(Exception e){
logger.error("userInfoIsInviald()数据库发生错误:"+e.getMessage());
}finally{
util.closePool(dataSource);
}
}
}•确定重定向URL不会引导用户到预期外的页面。
5.资源管理
•数据库连接的管理:当前使用Util.closePool(dataSource)的实现方式不够清晰,应该清楚地管理数据库连接的生命周期。考虑使用try-with-resources 来自动关闭资源。
注解:在下一版本中考虑在这段代码中使用try-with-resources语句。
6.魔法字符串
•避免硬编码字符串:"验证成功"和其他字符串直接使用在代码中,建议使用常量或枚举来代替,以防止由于错别字带来的逻辑错误。
注解:在下一版本中优化魔法字符串。
7.代码示例
以下是改进建议的示范。
public String performRegister()throws SQLException,IOException{
String username=this.request.getParameter("username");
String password=this.request.getParameter("password");
String phone=this.request.getParameter("phone");
String email=this.request.getParameter("email");
//Validate user input
String validationMessage=validateInput(username,password,phone,email);
if(validationMessage!=null){
return validationMessage;//Return validation message
}
User user=new User(username,password,phone,email);
ValidateUser validateUser=new ValidateUser();
String validationResult=validateUser.validate(user);
if(!validationResult.equals("验证成功")){
return validationResult;
}
UserRepository mysql=new UserRepository();
try(HikariDataSource dataSource=mysql.getDataSource()){//Use try-with-resources
String info=mysql.insert(user);
if("1".equals(info)){
this.response.sendRedirect("LoginPage.jsp");
}else{
String encodedMessage=URLEncoder.encode(info,"UTF-8");
String url="RegisterPage.jsp?error="+encodedMessage;
this.response.sendRedirect(url);
}
}catch(SQLException e){
logger.error("performRegister()数据库发生错误:"+e.getMessage(),e);
//Optionally return a user-friendly message
}
return "0";//Consider returning meaningful status or message
}
private String validateInput(String username,String password,String phone,String email){
//Add input validation logic here
//Return null if valid,or return a validation message if invalid
}注解:修改void userInfoIsInviald(User user)方法为。
private void userInfoIsInviald(User user)throws SQLException,IOException{
Util util=new Util();
UserRepository mysql=new UserRepository();
try(HikariDataSource dataSource=mysql.dataSource){
String info=mysql.insert(user);
…
}catch(Exception e){
logger.error("userInfoIsInviald()数据库发生错误:"+e.getMessage());
}
}最后删除Util.java中的void closePool(HikariDataSource dataSource)方法。
8.总结
此代码的基本逻辑可行,但在错误处理、输入验证、资源管理等方面有改进空间。通过引入异常处理、注释和清晰的结构,您可以提升代码的可维护性和可读性。
4.4.3找回密码测试代码评审
1.申请找回密码测试代码评审
提示词
请对下面测试代码进行评审。
1)globals.py
代码4-75 globals.py
class Config:
REGISTER_URL="http://127.0.0.1:8080/CharGPTEbusiness/jsp/RegisterPage.jsp"
LOGIN_URL="http://127.0.0.1:8080/CharGPTEbusiness/jsp/LoginPage.jsp"
WELCOME_URL="http://127.0.0.1:8080/CharGPTEbusiness/jsp/WelcomePage.jsp"
VERICODE_URL="http://127.0.0.1:8080/CharGPTEbusiness/jsp/VeriCodePage.jsp"
RECOVER_URL="http://127.0.0.1:8080/CharGPTEbusiness/jsp/RecoverPage.jsp"
VALID_USERNAME="validUser123"
VALID_PASSWORD="Valid123!"
INVALID_USERNAME="validUser321"
INVALID_PASSWORD="Valid321!"
SHORT_USERNAME="usr2"
LONG_USERNAME="user12345678901234567"
SHORT_PLUS1_USERNAME="usr12"
LONG_REDUCE1_USERNAME="user1234567890123456"
OTHER_USERNAME="validUser456"
XSS_USERNAME="<script>alert('XSS')</script>"
SQL_USERNAME="admin'--"
DIFF_PASSWORD="Abc@123"
SHORT_PASSWORD="abc"
LONG_PASSWORD="iLongpassword12@Longpassword12@"
SIMPLE_PASSWORD="12345"
VALID_PHONE="13812345677"
OTHER_PHONE="13812345688"
INVALID_PHONE="12345"
CONTRACT_PHONE="13681732596"
VALID_EMAIL="valid@example.com"
INVALID_EMAIL="invalid@example"
OTHER_EMAIL="valid1@example.com"
CONTRACT_EMAIL="xianggu625@126.com"
CODE="123456"
ZERO_SIZE_STRING=""2)Test_Recover.py
参看代码4-52Test_Recover.py
3)Test_Recover_GUI.py
参看代码4-57Test_Recover_GUI.py
4)Util.py
参看代码2-36 Util.py
2.找回密码测试代码评审回复
ChatGPT回答
以下是对您提供的代码(globals.py、Test_Recover.py、Test_Recover_GUI.py和Util.py)的详细评审和改进建议。主要关注代码的可读性、可维护性、安全性和性能。
1)globals.py
1.优点
•全局变量集中管理,便于维护和修改。
•变量命名清晰,易于理解其用途。
2.改进建议
•数据安全
Ø敏感信息处理:如果有敏感信息(如数据库连接信息),应避免直接在代码中硬编码。
注解:数据库连接信息未在globals.py中体现,因此不存在敏感信息。
2)Test_Recover.py
1.优点
•使用unittest框架,结构清晰。
•使用parameterized进行多场景测试,减少重复。
2.改进建议
•异常处理
Ø异常信息格式化:在捕获异常时,使用f-string格式化字符串,确保异常信息能正确显示。
Ø请求失败处理:在捕获请求异常时,建议在失败时记录更多信息(如请求的URL和数据)。
•逻辑优化
Ø重复代码:在多个地方重复请求和响应处理逻辑,建议封装为一个方法,以减少代码重复。
•测试用例描述
Ø测试用例注释:可以使用更简洁的方式描述测试用例,例如使用pytest的@pytest.mark注释。
3.改进示例
class Test TestRecoverAPI(unittest.TestCase):
def setUp(self):
self.session=requests.Session()
response=self.session.get(Config.VERICODE_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 _post_request(self,url,data):
try:
response=requests.post(url,data=data,cookies=self.token)
response.raise_for_status()
return response
except requests.exceptions.RequestException as e:
self.fail(f"请求失败:{e},URL:{url},数据:{data}")
@parameterized.expand([
#参数化用例
])
def test_valid_contact(self,sig,contact,message):
"""验证有效和无效的联系方式"""
uid=str(self.db.getId(Config.CONTRACT_EMAIL))
data1={'csrftoken':self.csrf,'contact':contact}
self.token['uid']=uid
response=self._post_request(Config.VERICODE_URL,data1)
if sig==0:
self.assertEqual(response.status_code,200)
self.assertIn(message,response.text)
else:
self.assertEqual(response.status_code,200)
self.assertIn("找回密码",response.text)
#处理验证码逻辑
#...注解:修改Test_Recover.py。
代码4-76 Test_Recover.py
#-*-coding:utf-8-*-
import requests
import unittest
from Util import ClassDB
from Util import CSRF
from Util import HASH
from parameterized import parameterized
from globals import Config
MESSAGES={
"OK":"登录",
"NON_EXISTER_EMAIL_OR_PHONE":"您输入的手机号或Email不存在,请重新输入!",
"INCORRECT_VERIFICATION":"验证码错误,请重新输入!",
"HAVEN_USING_PASSWORD":"这个密码以前设置过,请用一个新密码!",
"PASSWORD_NEED_HASH":"密码需要HASH散列",
}
class TestRecoverAPI(unittest.TestCase):
def setUp(self):
self.session=requests.Session()
response=self.session.get(Config.VERICODE_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 _post_request(self,url,data):
try:
response=requests.post(url,data=data,cookies=self.token)
response.raise_for_status()
return response
except requests.exceptions.RequestException as e:
self.fail(f"请求失败:{e},URL:{url},数据:{data}")
@parameterized.expand([
(1,Config.CONTRACT_PHONE,MESSAGES["OK"]),
(1,Config.CONTRACT_EMAIL,MESSAGES["OK"],),
(0,Config.OTHER_EMAIL,MESSAGES["NON_EXISTER_EMAIL_OR_PHONE"]),
(0,Config.OTHER_PHONE,MESSAGES["NON_EXISTER_EMAIL_OR_PHONE"]),
(1,Config.CONTRACT_EMAIL,MESSAGES["INCORRECT_VERIFICATION"]),
(1,Config.CONTRACT_EMAIL,MESSAGES["HAVEN_USING_PASSWORD"]),
])
def test_valid_contact(self,sig,contact,message):
"""TC-001-输入有效的注册手机号进行找回密码"""
"""TC-002-输入有效的注册邮箱进行找回密码"""
"""TC-003-输入未注册的邮箱"""
"""TC-004-输入未注册的手机"""
"""TC-005-验证码输入错误"""
"""TC-009-新密码已使用过"""
uid=str(self.db.getId(Config.CONTRACT_EMAIL))
data1={'csrftoken':self.csrf,'contact':contact}
self.token['uid']=uid
response=self._post_request(Config.VERICODE_URL,data1)
if sig==0:
self.assertEqual(response.status_code,200)
self.assertIn(message,response.text)
else:
self.assertEqual(response.status_code,200)
self.assertIn("找回密码",response.text)
if message=="验证码错误,请重新输入!":
vricode=Config.INVALID_CODE
else:
vricode=self.db.geveriCode(uid)
if message=="这个密码以前设置过,请用一个新密码!":
newpassword=Config.VALID_PASSWORD
else:
newpassword=Config.DIFF_PASSWORD
data2={
'csrftoken':self.csrf,
'identifyingCode':vricode,
'newPassword':HASH.hash_password(newpassword),
'confirmPassword':HASH.hash_password(newpassword)
}
#进入输入新密码页面
self.csrf=CSRF.get_csrf(response.text)
response=self._post_request(Config.RECOVER_URL,data2)
self.assertEqual(response.status_code,200)
self.assertIn(message,response.text)
@parameterized.expand([
(Config.VALID_PASSWORD),
(Config.ZERO_SIZE_STRING),
])
def test_recover_password_by_password_format_no_hash256(self,newpassword):
"""TC-010-密码正确使用SHA256散列传输"""
"""TC-011-空字段提交"""
#进入输入手机或Email页面
uid=str(self.db.getId(Config.CONTRACT_EMAIL))
data1={
'csrftoken':self.csrf,
'contact':Config.CONTRACT_EMAIL
}
#获取Uid
self.token['uid']=uid
response=self._post_request(Config.VERICODE_URL,data1)
self.assertEqual(response.status_code,200)
self.assertIn("找回密码",response.text)
#获取验证码
vricode=self.db.geveriCode(uid)
data2={
'csrftoken':self.csrf,
'identifyingCode':vricode,
'newPassword':newpassword,
'confirmPassword':newpassword
}
#进入输入新密码页面
self.csrf=CSRF.get_csrf(response.text)
response=self._post_request(Config.RECOVER_URL,data2)
self.assertEqual(response.status_code,200)
self.assertIn(MESSAGES["PASSWORD_NEED_HASH"],response.text)
if __name__=="__main__":
unittest.main()3)Test_Recover_GUI.py
1.优点
•使用pytest框架,支持参数化和fixture,结构更灵活。
•GUI测试使用playwright,能够模拟用户交互。
2.改进建议
•结构优化
Ø使用pytest的fixture:可以在setup_class()中进行数据库初始化,确保每个测试用例前后的状态一致。
•错误处理
Ø页面加载失败处理:在页面加载失败时,建议使用pytest.fail()记录详细信息。
•测试用例描述
Ø参数化注释:可以在参数化注释中加入用例编号,便于追踪。
3.改进示例
class TestRecover:
@pytest.fixture(autouse=True)
def setup_and_teardown(self):
self.db=ClassDB()
self.db.init_login(username="",password="")
yield
self.db.init_db()
def _fill_new_password_form(self,page:Page,code:str,password:str,repassword:str):
#填充新密码表单
page.get_by_placeholder("输入验证码").fill(code)
page.get_by_placeholder("输入新密码").fill(password)
page.get_by_placeholder("确认新密码").fill(repassword)
page.get_by_role("button",name="确定").click()
@pytest.mark.parametrize("code,password,repassword,expected_message",[
#参数化用例
])
def test_reset_password(self,page:Page,code,password,repassword,expected_message):
try:
page.goto(Config.RECOVER_URL)
except TimeoutError:
pytest.fail(f"加载页面失败:{Config.RECOVER_URL}")
page.reload()
self._fill_new_password_form(page,code,password,repassword)
error_message=page.inner_text("#confirmPasswordError" if expected_message=="密码确认不一致!" else "#newPasswordError")
assert expected_message in error_message修改Test_Recover_GUI.py。
代码4-77 Test_Recover_GUI.py
import pytest
from playwright.sync_APIimport Page
from Util import ClassDB
import time
from globals import Config
class TestRecover:
@pytest.fixture(autouse=True)
def setup_and_teardown(self):
self.db=ClassDB()
self.db.init_login(username="",password="")
yield
self.db.init_db()
def _fill_new_password_form(self,page:Page,code:str,password:str,repassword:str):
#填充新密码表单
page.get_by_placeholder("输入验证码").fill(code)
page.get_by_placeholder("输入新密码").fill(password)
page.get_by_placeholder("确认新密码").fill(repassword)
page.get_by_role("button",name="确定").click()
def _fill_phone_mail_form(self,page:Page,contact:str):
page.get_by_placeholder("输入手机号码或邮箱").fill(contact)
page.get_by_role("button",name="发送验证码").click()
@pytest.mark.parametrize("code,password,repassword,expected_message",[
(Config.CODE,Config.VALID_PASSWORD,Config.DIFF_PASSWORD,"密码确认不一致!"),#TC-006:新密码和确认密码不一致
(Config.CODE,Config.SIMPLE_PASSWORD,Config.SIMPLE_PASSWORD,"密码必须包含大小写字母、数字和特殊字符,长度在5-30之间"),#TC-007:新密码不符合复杂度要求
(Config.CODE,Config.SHORT_PASSWORD,Config.SHORT_PASSWORD,"密码必须包含大小写字母、数字和特殊字符,长度在5-30之间"),#TC-008:新密码长度不符合要求
])
def test_reset_password(self,page:Page,code,password,repassword,expected_message):
try:
page.goto(Config.RECOVER_URL)
except TimeoutError:
pytest.fail(f"加载页面失败:{Config.RECOVER_URL}")
page.reload()
self._fill_new_password_form(page,code,password,repassword)
error_message=page.inner_text("#confirmPasswordError" if expected_message=="密码确认不一致!" else "#newPasswordError")
assert expected_message in error_message
@pytest.mark.parametrize("contact,expected_message",[
(Config.INVALID_PHONE,"请输入有效的中国手机号或Email"),#TC-012:用户输入的邮箱格式不正确
(Config.INVALID_EMAIL,"请输入有效的中国手机号或Email"),#TC-013:用户输入的邮箱格式不正确
])
def test_invalid_phone_format(self,page:Page,contact,expected_message):
try:
page.goto(Config.VERICODE_URL)
except TimeoutError:
pytest.fail("加载页面失败:{"+Config.VERICODE_URL+"}")
page.reload()
self._fill_phone_mail_form(page,contact)
#检查错误提示
error_message=page.inner_text("#contactError")
assert expected_message in error_message
if __name__=='__main__':
pytest.main(["-sv","Test_Recover_GUI.py"])4)Util.py
1.优点
•ClassDB和CSRF的实现结构清晰,功能分明。
•使用pymysql进行数据库操作,支持多种功能。
2.改进建议
•数据库操作
ØSQL注入风险:在SQL语句中直接拼接用户输入,存在SQL注入风险。建议使用参数化查询。
Ø连接管理:在initDB()方法中,建议使用上下文管理器(with)来自动管理连接的打开和关闭。
•代码重复
Ø重复代码:init_db()和init_login()中有重复的数据库连接和关闭逻辑,建议提取为一个通用方法。
3.改进示例
class ClassDB:
def initDB(self):
host='localhost'
user='root'
password='123456'
database='ebusiness'
return pymysql.connect(host=host,user=user,password=password,database=database,charset='utf8')
def execute_query(self,query,params=None):
connection=self.initDB()
try:
with connection.cursor()as cursor:
cursor.execute(query,params)
connection.commit()
return cursor.fetchall()
finally:
connection.close()
def init_db(self):
self.execute_query("DELETE FROM code")
self.execute_query("DELETE FROM password")
self.execute_query("DELETE FROM user")
def init_login(self,username,password):
username=username or "validUser"
password=HASH.hash_password(password or "ValidPassword123!")
self.execute_query("INSERT INTO user(username,password,phone,email)VALUES(%s,%s,%s,%s)",
(username,password,"13681732596","xianggu625@126.com"))修改Util.py前半部分为。
代码4-78 Util.py
#-*-coding:utf-8-*-
import pymysql
import re
import hashlib
from globals import Config
class ClassDB:
def initDB(self):
host='localhost'
user='root'
password='123456'
database='ebusiness'
return pymysql.connect(host=host,user=user,password=password,database=database,charset='utf8')
def execute_query(self,query,params=None):
connection=self.initDB()
try:
with connection.cursor()as cursor:
cursor.execute(query,params)
connection.commit()
return cursor.fetchall()
finally:
connection.close()
def init_db(self):
self.execute_query("DELETE FROM code")
self.execute_query("DELETE FROM password")
self.execute_query("DELETE FROM user")
def init_login(self,username,password):
username=username or Config.VALID_USERNAME
password=HASH.hash_password(password or Config.VALID_PASSWORD)
self.execute_query("INSERT INTO user(username,password,phone,email)VALUES(%s,%s,%s,%s)",
(username,password,"13681732596","xianggu625@126.com"))
#关闭数据库连接
def closedb(self,connection):
try:
connection.close()
except MySQLError as e:
print(f"数据库关闭失败:{e}")4.总结
通过以上评审和建议,您可以提升代码的可读性、可维护性和安全性。关键点包括。
•采用更安全的数据库操作方式,避免SQL注入。
•使用类和方法封装全局变量,提升代码组织性。
•通过异常处理和日志记录提高代码的健壮性。
如果您有任何进一步的问题或需要更具体的帮助,请随时告诉我。
顾翔凡言:人工智能未来的发展瓶颈在于对知识的更新。唯一不变的是变化,知识发生了变化,人工智能软件能否及时跟进变化,可能阻碍人工智能的使用。