顾老师新作“带着ChatGPT玩转软件开发”将于今年底明年初出版
2.1.2 ChatGPT生成用户注册后端代码
1.ChatGPT申请生成用户注册后Java代码
提示词
数据库结构为。
id int 自动加 1
username varchar(50)
password varchar(100)
phone varchar(50)
email varchar(50)请实现
1.数据库结构。
数据库名:ebusiness。
表名:user。
请提供用于创建数据库和表的SQL语句。
2.JavaBean和JSP代码。
请实现相应的JavaBean类和JSP代码,以处理用户注册。
3.将register.html修改为RegisterPage.jsp。
请更新RegisterPage.jsp,使其能够调用JavaBean代码。
2.生成用户注册的Java后端代码的回复
ChatGPT回答
1.首先,开始展示创建数据库和表的SQL语句,然后再实现JavaBean代码,最后将register.html修改为RegisterPage.jsp以调用JavaBean代码。
2.创建数据库和用户表的SQL语句。
--创建数据库
CREATE DATABASE IF NOT EXISTS ebusiness;
--使用数据库
USE ebusiness;
--创建用户表
CREATE TABLE IF NOT EXISTS user(
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50)NOT NULL,
password VARCHAR(100)NOT NULL,
phone VARCHAR(50)NOT NULL,
email VARCHAR(50)NOT NULL
);3.创建User.java
User类将输入数据形成一个类,此类与数据库进行交互。
代码2-6User.java
public class User{
private int id;
private String username;
private String password;
private String phone;
private String email;
//获得和设置
//获得ID
public int getId(){
return id;
}
//设置ID
public void setId(int id){
this.id=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;
}
//获得Email
public String getEmail(){
return email;
}
//设置Email
public void setEmail(String email){
this.email=email;
}
}注解:这是一个POJO(Plain Ordinary Java Object:简单的Java对象)类。
4.创建RegisterPage.jsp
将register.html转为RegisterPage.jsp,处理注册请求,并将用户数据存储到MySQL数据库中。
代码2-7 RegisterPage.jsp
<%@page import="java.sql.*,javax.sql.*"%>
<%@page import="your.package.User"%><!--Replace 'your.package' with your actual package name-->
<%
String username=request.getParameter("username");
String password=request.getParameter("password");//Assuming password is already hashed from client-side
String phone=request.getParameter("phone");
String email=request.getParameter("email");
User user=new User();
user.setUsername(username);
user.setPassword(password);
user.setPhone(phone);
user.setEmail(email);
try{
Class.forName("com.mysql.jdbc.Driver");
Connection con=DriverManager.getConnection("jdbc:mysql://localhost:3306/ebusiness","root","password");
String query="INSERT INTO user(username,password,phone,email)VALUES(?,?,?,?)";
PreparedStatement stmt=con.prepareStatement(query);
stmt.setString(1,user.getUsername());
stmt.setString(2,user.getPassword());
stmt.setString(3,user.getPhone());
stmt.setString(4,user.getEmail());
stmt.executeUpdate();
out.println("注册成功!");
}catch(Exception e){
out.println("数据库连接失败:"+e.getMessage());
}
%>5.总结
•注意事项
Ø安全性:在上面的示例中,密码假定已经在客户端用JavaScript进行了哈希处理。然而,在实际应用中,应该在服务器端再次进行散列处理以确保安全。
Ø数据库连接信息:在RegisterPage.jsp中,数据库账号和密码被硬编码为root和password。在实际部署时,应该使用更安全的认证方式,并可能需要根据实际环境调整这些值。
Ø错误处理:应该增加更详细的错误处理逻辑,以便在出现问题时提供更多的上下文信息。
Ø驱动加载:确保已经将MySQL JDBC驱动添加到项目的类路径中。
注解。
•客户端已使用JavaScript对密码进行了哈希处理,因此在服务器端再次进行散列处理是多余的。
•后续将把数据库的账号和密码配置在配置文件中,以实现数据与代码的分离,从而增强代码的安全性。
•后续会优化错误处理逻辑。
•MySQL JDBC驱动已加载,本产品使用的是mysql-connector-java-9.0.0.jar。
3.调整生成的产品代码
下面调整RegisterPage.jsp,并且增加UserRepository.java文件,专门负责与数据库的交互。
1)修改RegisterPage.jsp
第1步:将your.package.User
<%@page import="your.package.User"%>替换为ebusiness.User。
<%@page import="ebusiness.User"%>第2步:由于数据库的root密码为123456,请将相应的配置从"password"
Connection con=DriverManager.getConnection("jdbc:mysql://localhost:3306/ebusiness","root","password");修改为"123456"。
Connection con=DriverManager.getConnection("jdbc:mysql://localhost:3306/ebusiness","root","123456");2)创建UserRepository.java
将RegisterPage.jsp中对数据库的操作封装到UserRepository.java中。
代码2-8 UserRepository.java
package ebusiness;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class UserRepository{
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{
//新版本改为"com.mysql.cj.jdbc.Driver"
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 驱动未找到:"+e.getMessage());
}catch(SQLException e){
System.out.println("数据库连接失败:"+e.getMessage());
}
return null;
}
//断开与数据库的连接。
public void disconnect(Connection conn){
try{
if(rs!=null){
rs.close();
}
stmt.close();
conn.close();
}catch(Exception e){
System.out.println(e.getMessage());
}
}
//将用户数据插入数据库,插入成功返回true,失败时返回false或抛出异常。
public boolean insert(Connection conn,User user)throws SQLException{
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();
if(affectedRows==1){
return true;
}else{
return false;
}
}catch(SQLException e){
System.out.println(e.getMessage());
}
return false;
}
}3)再次调整RegisterPage.jsp
由于创建了UserRepository.java文件,需要对RegisterPage.jsp进行相应的调整,将数据库的操作删除,移到封装UserRepository.java中。
代码2-9 RegisterPage.jsp
<%@page contentType="text/html;charset=utf-8"%>
<%@page import="java.sql.*,javax.sql.*"%>
<%@page import="ebusiness.User"%>
<%@page import="ebusiness.UserRepository"%>
<%
//从界面获取数据
String username=request.getParameter("username");
String password=request.getParameter("password");
String phone=request.getParameter("phone");
String email=request.getParameter("email");
//将数据封装在User类中
User user=new User();
user.setUsername(username);
user.setPassword(password);
user.setPhone(phone);
user.setEmail(email);
//将数据插入数据库中
try{
UserRepository mysql=new UserRepository();
Connection conn=mysql.connect();
if(mysql.insert(conn,user))
out.println("注册成功!");
mysql.disconnect(conn);
}catch(Exception e){
out.println("数据库连接失败:"+e.getMessage());
}
%>4.添加校验类ValidateUser.java
尽管前端已经对输入数据进行了格式校验,但为了增强系统安全性,尤其是防止CSRF攻击(后面介绍的CSRF Token可以有效减轻部分CSRF攻击风险,但目前尚无任何方法能够实现100%的防护CSRF攻击)。另外为了防止侵入者不通过前端,直接通过程序访问后端的行为,所以后端需对输入数据进行二次校验。创建ValidateUser.java以验证前端输入的数据。
代码2-10 ValidateUser.java
package ebusiness;
import java.util.regex.Pattern;
public class ValidateUser{
//判断给定字符串是否经过SHA256散列处理。
public boolean isHash(String str){
if(str!=null&&str.length()==64&&str.matches("[a-fA-F0-9]{64}")){
return true;//可能是SHA256哈希值
}
return false;
}
//通过正则表达式验证数据用户数据格式是否正确。
public String validate(User user){
String usernamePattern="^[a-zA-Z0-9]{5,20}$";
String phonePattern="^1[3-9]\\d{9}$";
String emailPattern="^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$";
if(!Pattern.matches(usernamePattern,user.getUsername())){
return "账号必须是5-20位字母或数字";
}
if(!Pattern.matches(phonePattern,user.getPhone())){
return "手机号必须符合中国手机号码格式";
}
if(!Pattern.matches(emailPattern,user.getEmail())){
return "Email格式不正确";
}
if(!isHash(user.getPassword())){
return "密码应该哈希进行存储";
}
return "验证成功";
}
}在RegisterPage.jsp中调用ValidateUser.java中的ValidateUser.validate(user)方法,对插入数据库的用户数据进行格式校验。
代码2-11 RegisterPage.jsp
…
ValidateUser vu=new ValidateUser();
if(vu.validate(user).equals("验证成功")){
…5.加入CSRF Token
在RegisterPage.jsp文件中加入CSRF Token,以防止CSRF攻击,增强系统的安全性。
提示词
如何利用Java产生100位随机字符串。
ChatGPT回答
import java.security.SecureRandom;
public class RandomStringGenerator{
//定义要使用的字符集
private static final String CHARACTERS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
private static final int LENGTH=100;//目标字符串长度
public static void main(String[] args){
String randomString=generateRandomString(LENGTH);
System.out.println("生成的随机字符串:"+randomString);
}
public static String generateRandomString(int length){
SecureRandom random=new SecureRandom();
StringBuilder sb=new StringBuilder(length);
for(int i=0;i<length;i++){
int index=random.nextInt(CHARACTERS.length());
sb.append(CHARACTERS.charAt(index));
}
return sb.toString();
}
}在项目中创建Util.java文件,加入如下代码。
代码2-12 Util.java
package ebusiness;
import java.security.SecureRandom;
public class Util{
//定义要使用的字符集
private static final String CHARACTERS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
private static final int LENGTH=100;//目标字符串长度
//生成一个指定长度的随机字符串。
public static String generateRandomString(int length){
SecureRandom random=new SecureRandom();
StringBuilder sb=new StringBuilder(length);
for(int i=0;i<length;i++){
int index=random.nextInt(CHARACTERS.length());
sb.append(CHARACTERS.charAt(index));
}
return sb.toString();
}
}修改RegisterPage.jsp。
代码2-13 RegisterPage.jsp
<%@page import="java.sql.*,ebusiness.User,ebusiness.UserRepository,ebusiness.ValidateUser,ebusiness.Util"%>
…
<%
Util util=new Util();
String csrftoken=util.generateRandomString(100);
%>
…
<input type="hidden" id="csrftoken" name="csrftoken" value="<%=csrftoken%>">
…
if("get".equalsIgnoreCase(method)){
Cookie myCookie=new Cookie("csrftoken",csrftoken);
myCookie.setMaxAge(60*60*24);//设置为1天,单位是秒
myCookie.setPath("/");
response.addCookie(myCookie);
}else if("post".equalsIgnoreCase(method)){
String csrftoken_post=request.getParameter("csrftoken");
String username=request.getParameter("username");
String password=request.getParameter("password");
String phone=request.getParameter("phone");
String email=request.getParameter("email");
Cookie[] cookies=request.getCookies();//获取所有的cookies
String csrftoken_cookies="";
if(cookies!=null){
for(Cookie cookie:cookies){
String name=cookie.getName();//获取cookie的名称
String value=cookie.getValue();//获取cookie的值
if(name.equals("csrftoken")&&(value!=null)||(value.equals(""))){
csrftoken_cookies=value;
}
}
Cookie myCookie=new Cookie("csrftoken",csrftoken);
myCookie.setMaxAge(60*60*24);//设置为1天,单位是秒
myCookie.setPath("/");
response.addCookie(myCookie);
} …
Cookie myCookie=new Cookie("csrftoken",csrftoken);
myCookie.setMaxAge(60*60*24);//设置为1天,单位是秒
myCookie.setPath("/");
response.addCookie(myCookie);
…
%>
</div>
</div>
</body>
</html>通过以下代码生成一个长度为100的随机字符串csrftoken,并将其存储在名为“csrftoken”的Cookie中。
String csrftoken=util.generateRandomString(100);
Cookie myCookie=new Cookie("csrftoken",csrftoken);
myCookie.setMaxAge(60*60*24);//设置有效期为1天(单位:秒)
myCookie.setPath("/");
response.addCookie(myCookie);在处理POST请求的时候,将csrftoken作为名为“csrftoken”的Cookie值传递给下一个页面。
<input type="hidden" id="csrftoken" name="csrftoken" value="<%=csrftoken%>">当下一个页面接收到POST参数和名为“csrftoken”的Cookie值时,比较这两个值,如果两个值相同,则系统认为可能不存在CSRF攻击风险;否则,认为肯定存在XSRF攻击的风险。
通常情况下,攻击者在伪造POST请求时容易模拟POST参数,但伪造“csrftoken”的Cookie值是相对困难。然而,这仍然不是不可能的,例如某些API测试工具可以轻易地伪造“csrftoken”的Cookie值。因此,尽管csrftoken方法在一定程度上可以防止CSRF攻击,但并不能完全消除风险。为了更有效地防范CSRF攻击,建议结合使用二次认证、错误地输入多次禁止操作和验证码等其他安全措施,以对系统提供更全面的安全保护。
6.实现账号、手机、Email必须唯一的需求
在上述代码中,没有考虑到账号、手机号码和电子邮件地址必须唯一的需求。接下来,将实现这一功能。
1)申请添加需求后的产品代码
提示词
在以下文件中实现注册功能的相关代码register.html。
参见代码2-4 register.html
RegisterPage.jsp。
参见代码2-13 RegisterPage.jsp
User.java。
参见代码2-12 Util.java
ValidateUser.java。
参见代码2-10 ValidateUser.java
UserRepository.java。
参见代码2-8 UserRepository.java
禁止同名用户重复注册,并确保不同用户的电子邮件和手机号码也不重复。
2)添加需求后的产品代码回复
ChatGPT回答
为了满足新的需求,即同名用户不允许注册多次,以及不同用户的Email和手机号也不可以相同,我们需要在UserRepository类中增加一些逻辑来检查数据库中是否已存在相同的账号、邮箱或手机号。
以下是修改后的UserRepository.java文件,添加了检查账号、邮箱和手机号是否已存在的方法。
代码2-14 UserRepository.java
package ebusiness;
import java.sql.*;
public class UserRepository{
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 boolean insert(Connection conn,User user)throws SQLException{
if(userExists(conn,user.getUsername(),user.getEmail(),user.getPhone())){
return false;//如果存在重复记录,返回false
}
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 affectedRows==1;//否则插入数据库,返回1,表示操作成功1条记录
}catch(SQLException e){
System.out.println(e.getMessage());
}
return false;
}
//检查指定的账号、手机号码和电子邮件是否已被其他用户注册。
//查询到至少有一个结果,则返回true,表示有重复,否则返回false
public boolean userExists(Connection conn,String username,String email,String phone)throws SQLException{
String queryString="SELECT * FROM user WHERE username=? OR email=? OR phone=?";
try{
PreparedStatement stmt=conn.prepareStatement(queryString);
stmt.setString(1,username);
stmt.setString(2,email);
stmt.setString(3,phone);
ResultSet rs=stmt.executeQuery();
return rs.next();//如果至少有一个结果,则返回true
}catch(SQLException e){
System.out.println(e.getMessage());
}
return false;
}
}这个版本的UserRepository类添加了一个boolean userExists(Connection conn,String username,String email,String phone)方法,它查询数据库以检查是否已存在具有给定账号、邮箱或手机号的用户。如果存在,boolean userExists(Connection conn,String username,String email,String phone)方法将返回true,这将阻止insert方法插入重复的用户。
确保在实际部署中处理好所有异常和错误,以保护用户数据和系统安全。同时,确保数据库的连接信息(如账号和密码)在生产环境中得到妥善保护。
7.调整代码
下面对代码进行调整。
1)调整UserRepository.java
修改UserRepository.java类中的String insert(Connection conn,User user)和String userExists(Connection conn,String username,String email,String phone)方法,确保能够准确定位到具体问题所在。
代码2-15 UserRepository.java
//将user信息插入到数据库中
//返回改为String,用于显示具体的错误信息
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();
affectedRows=1;
return String.valueOf(affectedRows);
}catch(SQLException e){
System.out.println(e.getMessage());
}
return "-1";
}
//查询数据库以检查是否已存在具有给定账号、邮箱或手机号的用户
public String userExists(Connection conn,String username,String email,String phone)throws SQLException{
String queryString="SELECT * FROM user WHERE username=?";
try{
PreparedStatement stmt=conn.prepareStatement(queryString);
stmt.setString(1,username);
ResultSet rs=stmt.executeQuery();
if(rs.next())
return "username";//返回"username",账号已经注册
}catch(SQLException e){
System.out.println(e.getMessage());
}
queryString="SELECT * FROM user WHERE email=?";
try{
PreparedStatement stmt=conn.prepareStatement(queryString);
stmt.setString(1,email);
ResultSet rs=stmt.executeQuery();
if(rs.next())
return "email";//返回"email",邮箱已经注册
}catch(SQLException e){
System.out.println (e.getMessage());
}
queryString="SELECT * FROM user WHERE phone=?";
try{
PreparedStatement stmt=conn.prepareStatement(queryString);
stmt.setString(1,phone);
ResultSet rs=stmt.executeQuery();
if(rs.next())
return "phone";//返回"phone",手机号已经注册
}catch(SQLException e){
System.out.println(e.getMessage());
}
return "0";
}2)调整RegisterPage.jsp
根据之前的代码调整,更新RegisterPage.jsp文件,以确保与后端逻辑保持一致。
代码2-16 RegisterPage.jsp
<%@page contentType="text/html;charset=utf-8"%>
<%@page import="java.sql.*,javax.sql.*"%>
<%@page import="ebusiness.*"%>
<%
String username=request.getParameter("username");
String password=request.getParameter("password");
String phone=request.getParameter("phone");
String email=request.getParameter("email");
User user=new User();
user.setUsername(username);
user.setPassword(password);
user.setPhone(phone);
user.setEmail(email);
ValidateUser vu=new ValidateUser();
if(vu.validate(user).equals("验证成功")){
try{
UserRepository mysql=new UserRepository();
Connection conn=mysql.connect();
String info=mysql.insert(conn,user);
if(info.equals("1"))
out.println("注册成功");
%>
<!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/showerror.js" type="text/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/crypto-js@4.1.1/core.js"></script>
<script src="https://cdn.jsdelivr.net/npm/crypto-js@4.1.1/sha256.js"></script>
</head>
<body onload='showerror("<%=info%>");'>
<div class="container">
<h2>用户注册</h2>
<form name="myForm" method="POST" action="jsp/RegisterPage.jsp" onsubmit="return validateForm();">
<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" required>
<div id="confirmPasswordError" class="error"></div>
<label for="phone">手机号(中国):</label>
<input type="tel" id="phone" name="phone" required>
<div id="phoneError" class="error"></div>
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required>
<div id="emailError" class="error"></div>
<button type="submit">注册</button>
</form>
</div>
</body>
</html>
<%
mysql.disconnect(conn);
}catch(Exception e){
out.println(e.getMessage());
}
}else{
out.print(vu.validate(user));
}
%>在代码中看到
<script src="../js/showerror.js" type="text/javascript"></script>
…
<body onload='showerror("<%=info%>");'>页面通过调用
onload='showerror("<%=info%>");'对后端错误的校验,并在前端把错误信息输出给用户。
3)添加后端错误的校验函数showerror.js
showerror.js文件用于显示UserRepository类中的String userExists(Connection conn,String username,String email,String phone)方法返回的错误信息,确保前端能够清晰地显示注册过程中的错误。
代码2-17 showerror.js
function showerror(info){
switch(info){
case("username"):
document.getElementById('usernameError').innerText='账号已经被注册过!';
break;
case("email"):
document.getElementById('emailError').innerText='电子邮件已经被注册过!';
break;
case("phone"):
document.getElementById('phoneError').innerText='手机已经被注册过!';
break;
case("1"):
document.writeln("注册成功!");
break;
default:
document.writeln('注册失败!');
}
}至此,电子商务注册模块的代码已完成。接下来,将进行单元测试,基于API和GUI的端到端测试以及代码评审,以验证程序是否存在缺陷。
顾翔凡言:人工智能未来的发展瓶颈在于对知识的更新。唯一不变的是变化,知识发生了变化,人工智能软件能否及时跟进变化,可能阻碍人工智能的使用。