从文件存储到数据库迁移:个人博客系统二次开发实战
标签:JavaWeb、JSP\Servlet、二次开发、数据库、性能优化
来源:取自空间信息与数字技术专业的周同学的期末大作业。
一、引言:为什么要进行二次开发?
在阅读此基于JSP+Servlet+JavaBean的个人博客系统后,我深入分析了该系统的运行情况与架构。虽然系统功能完整,符合大作业要求,但在实际使用过程中,我发现了若干性能瓶颈和架构缺陷,比如,该系统的数据存储使用的是文件存储,这种存储方式导致其数据的共享性和大大降低,提高了代码冗余度。
本文将记录我对这个系统进行的二次开发过程,重点解决原有缺陷,并将数据存储方式从文件系统迁移到MySQL数据库,同时保持原有功能不变。
二、运行环境与原始系统截图
2.1原始运行环境
- 操作系统:Windows 11
- JDK版本:Java 21.0.8 LTS
- Web服务器:Apache Tomcat 10.1
- 开发工具:IntelliJ IDEA 2025.3
实体类:
点击查看代码Article.java
package com.blog.bean;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 文章实体类(用于五个板块)
*/
public class Article implements Serializable {
private String id; // 文章ID
private String category; // 板块分类
private String title; // 标题
private String content; // 内容
private String author; // 作者(用户名)
private String createTime; // 创建时间
private String updateTime; // 更新时间
// 分类常量
public static final String CATEGORY_NEWS = "news"; // 生活新闻
public static final String CATEGORY_STUDY = "study"; // 学习记录
public static final String CATEGORY_SCHOOL = "school"; // 学校生活
public static final String CATEGORY_PLAN = "plan"; // 规划人生
public static final String CATEGORY_TRAVEL = "travel"; // 旅行打卡
// 无参构造方法
public Article() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
this.createTime = sdf.format(new Date());
this.updateTime = this.createTime;
this.id = generateId();
}
// 带参构造方法
public Article(String category, String title, String content, String author) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
this.category = category;
this.title = title;
this.content = content;
this.author = author;
this.createTime = sdf.format(new Date());
this.updateTime = this.createTime;
this.id = generateId();
}
// 生成唯一ID
private String generateId() {
return "ART_" + System.currentTimeMillis() + "_" + (int) (Math.random() * 1000);
}
// Getter和Setter方法
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getCreateTime() {
return createTime;
}
public void setCreateTime(String createTime) {
this.createTime = createTime;
}
public String getUpdateTime() {
return updateTime;
}
public void setUpdateTime(String updateTime) {
this.updateTime = updateTime;
}
// 获取分类中文名
public String getCategoryName() {
switch (category) {
case CATEGORY_NEWS:
return "生活新闻";
case CATEGORY_STUDY:
return "学习记录";
case CATEGORY_SCHOOL:
return "学校生活";
case CATEGORY_PLAN:
return "规划人生";
case CATEGORY_TRAVEL:
return "旅行打卡";
default:
return "未知分类";
}
}
@Override
public String toString() {
return id + "::" + category + "::" + title + "::" + content + "::"
+ author + "::" + createTime + "::" + updateTime;
}
}
点击查看代码Message.java
package com.blog.bean;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 留言实体类
*/
public class Message implements Serializable {
private String id; // 留言ID
private String visitor; // 访客昵称
private String email; // 邮箱(可选)
private String content; // 留言内容
private String createTime; // 留言时间
private String ipAddress; // IP地址
// 无参构造方法
public Message() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 格式化时间
this.createTime = sdf.format(new Date());// 格式化当前时间
this.id = generateId();// 生成唯一ID
}
// 带参构造方法
public Message(String visitor, String email, String content, String ipAddress) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
this.visitor = visitor;
this.email = email;
this.content = content;
this.createTime = sdf.format(new Date());
this.ipAddress = ipAddress;
this.id = generateId();
}
// 生成唯一ID(简单实现)
private String generateId() {
return "MSG_" + System.currentTimeMillis() + "_" + (int) (Math.random() * 1000);
}
// Getter和Setter方法
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getVisitor() {
return visitor;
}
public void setVisitor(String visitor) {
this.visitor = visitor;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getCreateTime() {
return createTime;
}
public void setCreateTime(String createTime) {
this.createTime = createTime;
}
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
@Override
public String toString() {
return id + "::" + visitor + "::" + (email != null ? email : "") + "::"
+ content + "::" + createTime + "::" + ipAddress;
}
}
点击查看代码User.java
package com.blog.bean;
import java.io.Serializable;
/**
* 用户实体类 - JavaBean
* 实现Serializable接口以便序列化存储
*/
public class User implements Serializable {
private String username; // 用户名
private String password; // 密码
private String nickname; // 昵称
private String gender; // 性别
private int age; // 年龄
private String zodiac; // 星座
// 无参构造方法(JavaBean规范要求)
public User() {
}
// 带参构造方法
public User(String username, String password, String nickname,
String gender, int age, String zodiac) {
this.username = username;
this.password = password;
this.nickname = nickname;
this.gender = gender;
this.age = age;
this.zodiac = zodiac;
}
// Getter和Setter方法
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 getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getZodiac() {
return zodiac;
}
public void setZodiac(String zodiac) {
this.zodiac = zodiac;
}
@Override
public String toString() {
return username + "," + password + "," + nickname + ","
+ gender + "," + age + "," + zodiac;
}
}
数据访问类:
点击查看代码ArticleDAO.java
package com.blog.dao;
import com.blog.bean.Article;
import java.io.*;// 导入文件操作类
import java.text.SimpleDateFormat;// 导入日期格式化类
import java.util.ArrayList;// 导入数组列表类
import java.util.Date;// 导入日期类
import java.util.List;// 导入列表类
/**
* 文章数据访问对象
* 负责文章的存储和读取
*/
public class ArticleDAO {
private static final String FILE_PATH = "D:\\JavaTools\\JavaCode\\MyWeb\\web\\data\\articles.txt";
/**
* 保存文章
*
* @param article 文章对象
* @return 保存是否成功
*/
public boolean saveArticle(Article article) {
try {
File file = new File(FILE_PATH);
// 如果目录不存在,创建目录
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
// 使用追加模式写入文件
FileWriter writer = new FileWriter(file, true);
writer.write(article.toString() + "\n");
writer.close();
System.out.println("文章保存成功: " + article.getId());
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
/**
* 更新文章
*
* @param updatedArticle 更新后的文章对象
* @return 更新是否成功
*/
public boolean updateArticle(Article updatedArticle) {
List<Article> articles = getAllArticles();
boolean found = false;
// 更新更新时间
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
updatedArticle.setUpdateTime(sdf.format(new Date()));
// 查找并更新文章
for (int i = 0; i < articles.size(); i++) {
Article article = articles.get(i);
if (article.getId().equals(updatedArticle.getId())) {
articles.set(i, updatedArticle);
found = true;
break;
}
}
if (!found) {
return false;
}
// 将更新后的文章列表写回文件
return writeAllArticles(articles);
}
/**
* 删除文章
*
* @param articleId 文章ID
* @return 删除是否成功
*/
public boolean deleteArticle(String articleId) {
List<Article> articles = getAllArticles();
boolean removed = articles.removeIf(article -> article.getId().equals(articleId));
if (!removed) {
return false;
}
// 重新写入文件
return writeAllArticles(articles);
}
/**
* 获取所有文章
*
* @return 文章列表(按创建时间倒序)
*/
public List<Article> getAllArticles() {
List<Article> articles = new ArrayList<>();
try {
File file = new File(FILE_PATH);
// 如果文件不存在,创建空文件
if (!file.exists()) {
file.getParentFile().mkdirs();
file.createNewFile();
return articles;
}
// 读取文件内容
BufferedReader reader = new BufferedReader(new FileReader(file));
String line;
while ((line = reader.readLine()) != null) {
String[] parts = line.split("::");
if (parts.length >= 7) {
Article article = new Article();
article.setId(parts[0]);
article.setCategory(parts[1]);
article.setTitle(parts[2]);
article.setContent(parts[3]);
article.setAuthor(parts[4]);
article.setCreateTime(parts[5]);
article.setUpdateTime(parts[6]);
articles.add(article);
}
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
// 按创建时间倒序排序(最新的在前)
articles.sort((a1, a2) -> a2.getCreateTime().compareTo(a1.getCreateTime()));
return articles;
}
/**
* 根据分类获取文章
*
* @param category 分类
* @return 该分类的文章列表
*/
public List<Article> getArticlesByCategory(String category) {
List<Article> allArticles = getAllArticles();
List<Article> filteredArticles = new ArrayList<>();
for (Article article : allArticles) {
if (article.getCategory().equals(category)) {
filteredArticles.add(article);
}
}
return filteredArticles;
}
/**
* 根据ID获取文章
*
* @param articleId 文章ID
* @return 文章对象,不存在返回null
*/
public Article getArticleById(String articleId) {
List<Article> articles = getAllArticles();
for (Article article : articles) {
if (article.getId().equals(articleId)) {
return article;
}
}
return null;
}
/**
* 获取最新的N篇文章
*
* @param limit 数量限制
* @return 最新的文章列表
*/
public List<Article> getLatestArticles(int limit) {
List<Article> allArticles = getAllArticles();
int count = Math.min(allArticles.size(), limit);
return allArticles.subList(0, count);
}
/**
* 获取分类名称
*
* @param category 分类代码
* @return 分类中文名
*/
public static String getCategoryName(String category) {
switch (category) {
case Article.CATEGORY_NEWS:
return "生活新闻";
case Article.CATEGORY_STUDY:
return "学习记录";
case Article.CATEGORY_SCHOOL:
return "学校生活";
case Article.CATEGORY_PLAN:
return "规划人生";
case Article.CATEGORY_TRAVEL:
return "旅行打卡";
default:
return "未知分类";
}
}
/**
* 获取所有分类
*
* @return 分类数组
*/
public static String[][] getAllCategories() {
return new String[][]{
{Article.CATEGORY_NEWS, "生活新闻"},
{Article.CATEGORY_STUDY, "学习记录"},
{Article.CATEGORY_SCHOOL, "学校生活"},
{Article.CATEGORY_PLAN, "规划人生"},
{Article.CATEGORY_TRAVEL, "旅行打卡"}
};
}
/**
* 将文章列表写入文件
*
* @param articles 文章列表
* @return 写入是否成功
*/
private boolean writeAllArticles(List<Article> articles) {
try {
File file = new File(FILE_PATH);
FileWriter writer = new FileWriter(file);
for (Article article : articles) {
writer.write(article.toString() + "\n");
}
writer.close();
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
}
点击查看代码MessageDAO.java
package com.blog.dao;
import com.blog.bean.Message;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* 留言数据访问对象
* 负责留言数据的存储和读取
*/
public class MessageDAO {
private static final String FILE_PATH = "D:\\JavaTools\\JavaCode\\MyWeb\\web\\data\\message.txt";
/**
* 保存留言
* @param message 留言对象
* @return 保存是否成功
*/
public boolean saveMessage(Message message) {
try {
File file = new File(FILE_PATH);
// 如果目录不存在,创建目录
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
// 使用追加模式写入文件
FileWriter writer = new FileWriter(file, true);
writer.write(message.toString() + "\n");
writer.close();
System.out.println("留言保存成功: " + message.getId());
System.out.println("保存到文件: " + file.getAbsolutePath());
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
/**
* 获取所有留言
* @return 留言列表(按时间倒序)
*/
public List<Message> getAllMessages() {
List<Message> messages = new ArrayList<>();
try {
File file = new File(FILE_PATH);
// 如果文件不存在,创建空文件
if (!file.exists()) {
file.getParentFile().mkdirs();
file.createNewFile();
return messages;
}
// 读取文件内容
BufferedReader reader = new BufferedReader(new FileReader(file));
String line;
while ((line = reader.readLine()) != null) {
String[] parts = line.split("::");
if (parts.length >= 6) {
Message message = new Message();
message.setId(parts[0]);
message.setVisitor(parts[1]);
message.setEmail(parts[2].isEmpty() ? null : parts[2]);
message.setContent(parts[3]);
message.setCreateTime(parts[4]);
message.setIpAddress(parts[5]);
messages.add(message);
}
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
// 按时间倒序排序(最新的在前)
messages.sort((m1, m2) -> m2.getCreateTime().compareTo(m1.getCreateTime()));
return messages;
}
/**
* 根据ID删除留言
* @param messageId 留言ID
* @return 删除是否成功
*/
public boolean deleteMessage(String messageId) {
List<Message> messages = getAllMessages();
boolean removed = messages.removeIf(message -> message.getId().equals(messageId));
if (!removed) {
return false;
}
// 重新写入文件
try {
File file = new File(FILE_PATH);
FileWriter writer = new FileWriter(file);
for (Message message : messages) {
writer.write(message.toString() + "\n");
}
writer.close();
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
/**
* 获取留言数量
* @return 留言总数
*/
public int getMessageCount() {
return getAllMessages().size();
}
/**
* 获取文件路径(用于查看)
* @return 文件绝对路径
*/
public String getFilePath() {
return FILE_PATH;
}
}
点击查看代码UserDAO.java
package com.blog.dao;
import com.blog.bean.User;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* 用户数据访问对象
* 负责user.txt文件的读写操作
*/
public class UserDAO {
private static String FILE_PATH = "D:\\JavaTools\\JavaCode\\MyWeb\\web\\data\\user.txt";
/**
* 设置文件路径
* @param filePath 文件路径
*/
public void setFilePath(String filePath) {
FILE_PATH = filePath;
}
/**
* 用户注册 - 保存用户信息到文件
* @param user 用户对象
* @return 注册是否成功
*/
public boolean register(User user) {
try {
// 检查用户名是否已存在
if (findByUsername(user.getUsername()) != null) {
return false;
}
File file = new File(FILE_PATH);
// 如果目录不存在,创建目录
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
// 使用追加模式写入文件
FileWriter writer = new FileWriter(file, true);
writer.write(user.toString() + "\n");
writer.close();
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
/**
* 用户登录验证
* @param username 用户名
* @param password 密码
* @return 验证通过的用户对象,失败返回null
*/
public User login(String username, String password) {
List<User> users = getAllUsers();
for (User user : users) {
if (user.getUsername().equals(username) &&
user.getPassword().equals(password)) {
return user;
}
}
return null;
}
/**
* 根据用户名查找用户
* @param username 用户名
* @return 用户对象,不存在返回null
*/
public User findByUsername(String username) {
List<User> users = getAllUsers();
for (User user : users) {
if (user.getUsername().equals(username)) {
return user;
}
}
return null;
}
/**
* 更新用户信息
* @param updatedUser 更新后的用户对象
* @return 更新是否成功
*/
public boolean updateUser(User updatedUser) {
List<User> users = getAllUsers();
boolean found = false;
// 查找并更新用户
for (int i = 0; i < users.size(); i++) {
User user = users.get(i);
if (user.getUsername().equals(updatedUser.getUsername())) {
// 保留原来的密码
updatedUser.setPassword(user.getPassword());
users.set(i, updatedUser);
found = true;
break;
}
}
if (!found) {
return false;
}
// 将更新后的用户列表写回文件
try {
File file = new File(FILE_PATH);
FileWriter writer = new FileWriter(file);
for (User user : users) {
writer.write(user.toString() + "\n");
}
writer.close();
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
/**
* 更新用户密码
* @param username 用户名
* @param newPassword 新密码
* @return 更新是否成功
*/
public boolean updatePassword(String username, String newPassword) {
List<User> users = getAllUsers();
boolean found = false;
// 查找并更新用户密码
for (int i = 0; i < users.size(); i++) {
User user = users.get(i);
if (user.getUsername().equals(username)) {
user.setPassword(newPassword);
users.set(i, user);
found = true;
break;
}
}
if (!found) {
return false;
}
// 将更新后的用户列表写回文件
try {
File file = new File(FILE_PATH);
FileWriter writer = new FileWriter(file);
for (User user : users) {
writer.write(user.toString() + "\n");
}
writer.close();
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
/**
* 获取所有用户列表
* @return 用户列表
*/
public List<User> getAllUsers() {
List<User> users = new ArrayList<>();
try {
File file = new File(FILE_PATH);
// 如果文件不存在,创建空文件
if (!file.exists()) {
file.getParentFile().mkdirs(); // 创建目录
file.createNewFile(); // 创建文件
return users;
}
// 读取文件内容
BufferedReader reader = new BufferedReader(new FileReader(file));
String line;
while ((line = reader.readLine()) != null) {
String[] parts = line.split(",");
if (parts.length >= 6) {
User user = new User(
parts[0], parts[1], parts[2],
parts[3], Integer.parseInt(parts[4]), parts[5]
);
users.add(user);
}
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
return users;
}
}
控制器类:
点击查看代码LoginServlet.java
package com.blog.servlet;
import com.blog.bean.User;
import com.blog.dao.UserDAO;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;
import java.io.IOException;
/**
* 登录Servlet
* 处理用户登录请求
*/
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
private UserDAO userDAO = new UserDAO();
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取表单参数
String username = request.getParameter("username");
String password = request.getParameter("password");
// 验证登录
User user = userDAO.login(username, password);
if (user != null) {
// 登录成功,将用户信息存入Session
HttpSession session = request.getSession();
session.setAttribute("user", user);
session.setAttribute("loginStatus", "success");
// 重定向到主页
response.sendRedirect("home.jsp");
} else {
// 登录失败,设置错误信息
request.setAttribute("errorMsg", "用户名或密码错误!");
request.getRequestDispatcher("login.jsp").forward(request, response);
}
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// GET请求直接转发到登录页面
request.getRequestDispatcher("login.jsp").forward(request, response);
}
}
点击查看代码RegisterServlet.java
package com.blog.servlet;
import com.blog.bean.User;
import com.blog.dao.UserDAO;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;
import java.io.IOException;
/**
* 注册Servlet
* 处理用户注册请求
*/
@WebServlet("/register")
public class RegisterServlet extends HttpServlet {
private UserDAO userDAO = new UserDAO();
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取表单参数
String username = request.getParameter("username");
String password = request.getParameter("password");
String nickname = request.getParameter("nickname");
String gender = request.getParameter("gender");
String ageStr = request.getParameter("age");
String zodiac = request.getParameter("zodiac");
// 验证参数
if (username == null || username.trim().isEmpty() ||
password == null || password.trim().isEmpty()) {
request.setAttribute("errorMsg", "用户名和密码不能为空!");
request.getRequestDispatcher("register.jsp").forward(request, response);
return;
}
int age = 20; // 默认年龄
try {
age = Integer.parseInt(ageStr);
} catch (NumberFormatException e) {
// 保持默认值
}
// 创建用户对象
User user = new User(username, password, nickname, gender, age, zodiac);
// 注册用户
boolean success = userDAO.register(user);
if (success) {
// 注册成功,跳转到登录页面
request.setAttribute("successMsg", "注册成功,请登录!");
request.getRequestDispatcher("login.jsp").forward(request, response);
} else {
// 注册失败(用户名已存在)
request.setAttribute("errorMsg", "用户名已存在!");
request.getRequestDispatcher("register.jsp").forward(request, response);
}
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// GET请求直接转发到注册页面
request.getRequestDispatcher("register.jsp").forward(request, response);
}
}
点击查看代码LogoutServlet.java
package com.blog.servlet;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;
import java.io.IOException;
/**
* 注销Servlet
* 处理用户注销请求
*/
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 使当前Session失效
HttpSession session = request.getSession();
session.invalidate();
// 重定向到首页
response.sendRedirect("index.jsp");
}
}
点击查看代码ArticleServlet.java
package com.blog.servlet;
import com.blog.bean.Article;
import com.blog.bean.User;
import com.blog.dao.ArticleDAO;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;
import java.io.IOException;
import java.util.List;
/**
* 文章管理Servlet
* 处理文章的增删查改
*/
@WebServlet("/article")
public class ArticleServlet extends HttpServlet {
private ArticleDAO articleDAO = new ArticleDAO();
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 检查用户是否已登录
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("user") == null) {
response.sendRedirect("login.jsp");
return;
}
User user = (User) session.getAttribute("user");
String action = request.getParameter("action");
if ("create".equals(action)) {
doCreate(request, response, user);
} else if ("update".equals(action)) {
doUpdate(request, response, user);
}
}
/**
* 处理GET请求
* 处理文章列表、查看、编辑、删除请求
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String action = request.getParameter("action");
String category = request.getParameter("category");
if ("delete".equals(action)) {
doDelete(request, response);
} else if ("edit".equals(action)) {
doEdit(request, response);
} else if ("list".equals(action)) {
doList(request, response, category);
} else if ("view".equals(action)) {
doView(request, response);
} else {
// 默认显示所有分类的文章列表
request.setAttribute("categories", ArticleDAO.getAllCategories());
request.getRequestDispatcher("articleList.jsp").forward(request, response);
}
}
/**
* 创建文章
*/
private void doCreate(HttpServletRequest request, HttpServletResponse response, User user)
throws ServletException, IOException {
String category = request.getParameter("category");
String title = request.getParameter("title");
String content = request.getParameter("content");
// 验证参数
if (category == null || category.trim().isEmpty() ||
title == null || title.trim().isEmpty() ||
content == null || content.trim().isEmpty()) {
request.setAttribute("errorMsg", "请填写完整信息!");
request.setAttribute("categories", ArticleDAO.getAllCategories());
request.getRequestDispatcher("articleEdit.jsp").forward(request, response);
return;
}
// 创建文章对象
Article article = new Article(category, title.trim(), content.trim(), user.getUsername());
// 保存文章
boolean success = articleDAO.saveArticle(article);
if (success) {
request.setAttribute("successMsg", "文章发布成功!");
response.sendRedirect("article?action=list&category=" + category);
} else {
request.setAttribute("errorMsg", "发布失败,请重试!");
request.setAttribute("categories", ArticleDAO.getAllCategories());
request.getRequestDispatcher("articleEdit.jsp").forward(request, response);
}
}
/**
* 更新文章
*/
private void doUpdate(HttpServletRequest request, HttpServletResponse response, User user)
throws ServletException, IOException {
String articleId = request.getParameter("id");
String category = request.getParameter("category");
String title = request.getParameter("title");
String content = request.getParameter("content");
// 验证参数
if (articleId == null || articleId.trim().isEmpty() ||
category == null || category.trim().isEmpty() ||
title == null || title.trim().isEmpty() ||
content == null || content.trim().isEmpty()) {
request.setAttribute("errorMsg", "请填写完整信息!");
request.setAttribute("categories", ArticleDAO.getAllCategories());
request.setAttribute("article", articleDAO.getArticleById(articleId));
request.getRequestDispatcher("articleEdit.jsp").forward(request, response);
return;
}
// 获取原文章并检查权限
Article article = articleDAO.getArticleById(articleId);
if (article == null) {
request.setAttribute("errorMsg", "文章不存在!");
request.getRequestDispatcher("articleList.jsp").forward(request, response);
return;
}
// 检查是否是作者本人或管理员(这里简单实现,只检查作者)
if (!article.getAuthor().equals(user.getUsername())) {
request.setAttribute("errorMsg", "无权修改此文章!");
request.getRequestDispatcher("articleList.jsp").forward(request, response);
return;
}
// 更新文章信息
article.setCategory(category);
article.setTitle(title.trim());
article.setContent(content.trim());
// 更新文章
boolean success = articleDAO.updateArticle(article);
if (success) {
request.setAttribute("successMsg", "文章更新成功!");
response.sendRedirect("article?action=list&category=" + category);
} else {
request.setAttribute("errorMsg", "更新失败,请重试!");
request.setAttribute("categories", ArticleDAO.getAllCategories());
request.setAttribute("article", article);
request.getRequestDispatcher("articleEdit.jsp").forward(request, response);
}
}
/**
* 删除文章
*/
public void doDelete(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 检查用户是否已登录
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("user") == null) {
response.sendRedirect("login.jsp");
return;
}
User user = (User) session.getAttribute("user");
String articleId = request.getParameter("id");
String category = request.getParameter("category");
if (articleId != null && !articleId.isEmpty()) {
// 获取文章并检查权限
Article article = articleDAO.getArticleById(articleId);
if (article != null && article.getAuthor().equals(user.getUsername())) {
boolean success = articleDAO.deleteArticle(articleId);
if (success) {
request.setAttribute("successMsg", "文章删除成功!");
} else {
request.setAttribute("errorMsg", "删除失败,请重试!");
}
} else {
request.setAttribute("errorMsg", "无权删除此文章!");
}
}
// 重定向到文章列表
String redirectUrl = "article?action=list";
if (category != null && !category.isEmpty()) {
redirectUrl += "&category=" + category;
}
response.sendRedirect(redirectUrl);
}
/**
* 编辑文章页面
*/
private void doEdit(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 检查用户是否已登录
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("user") == null) {
response.sendRedirect("login.jsp");
return;
}
String articleId = request.getParameter("id");
if (articleId != null && !articleId.isEmpty()) {
// 编辑现有文章
Article article = articleDAO.getArticleById(articleId);
if (article != null) {
request.setAttribute("article", article);
}
}
request.setAttribute("categories", ArticleDAO.getAllCategories());
request.getRequestDispatcher("articleEdit.jsp").forward(request, response);
}
/**
* 查看文章详情
*/
private void doView(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String articleId = request.getParameter("id");
if (articleId != null && !articleId.isEmpty()) {
Article article = articleDAO.getArticleById(articleId);
if (article != null) {
request.setAttribute("article", article);
request.getRequestDispatcher("articleView.jsp").forward(request, response);
return;
}
}
request.setAttribute("errorMsg", "文章不存在!");
request.getRequestDispatcher("articleList.jsp").forward(request, response);
}
/**
* 显示文章列表
*/
private void doList(HttpServletRequest request, HttpServletResponse response, String category)
throws ServletException, IOException {
if (category != null && !category.isEmpty()) {
// 显示指定分类的文章
List<Article> articles = articleDAO.getArticlesByCategory(category);
request.setAttribute("articles", articles);
request.setAttribute("currentCategory", category);
request.setAttribute("categoryName", ArticleDAO.getCategoryName(category));
} else {
// 显示所有文章
List<Article> articles = articleDAO.getAllArticles();
request.setAttribute("articles", articles);
}
request.setAttribute("categories", ArticleDAO.getAllCategories());
request.getRequestDispatcher("articleList.jsp").forward(request, response);
}
}
点击查看代码MessageServlet.java
package com.blog.servlet;
import com.blog.bean.Message;
import com.blog.dao.MessageDAO;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;
import java.io.IOException;
/**
* 留言处理Servlet
* 处理留言的提交、查看和删除
*/
@WebServlet("/message")
public class MessageServlet extends HttpServlet {
private MessageDAO messageDAO = new MessageDAO();
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取表单参数
String visitor = request.getParameter("visitor");
String email = request.getParameter("email");
String content = request.getParameter("content");
// 验证参数
if (visitor == null || visitor.trim().isEmpty() ||
content == null || content.trim().isEmpty()) {
request.setAttribute("errorMsg", "昵称和留言内容不能为空!");
forwardToMessagePage(request, response);
return;
}
// 获取客户端IP地址
String ipAddress = getClientIpAddress(request);
// 创建留言对象
Message message = new Message(visitor.trim(),
email != null ? email.trim() : null,
content.trim(),
ipAddress);
// 保存留言
boolean success = messageDAO.saveMessage(message);
if (success) {
request.setAttribute("successMsg", "留言成功!感谢您的反馈。");
} else {
request.setAttribute("errorMsg", "留言失败,请稍后重试!");
}
// 重新加载留言列表
request.setAttribute("messages", messageDAO.getAllMessages());
forwardToMessagePage(request, response);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String action = request.getParameter("action");
if ("delete".equals(action)) {
// 删除留言
doDelete(request, response);
} else {
// 查看留言列表
request.setAttribute("messages", messageDAO.getAllMessages());
forwardToMessagePage(request, response);
}
}
/**
* 处理删除请求
*/
public void doDelete(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String messageId = request.getParameter("id");
if (messageId != null && !messageId.isEmpty()) {
boolean success = messageDAO.deleteMessage(messageId);
if (success) {
request.setAttribute("successMsg", "留言删除成功!");
} else {
request.setAttribute("errorMsg", "删除失败,留言不存在!");
}
}
// 重新加载留言列表
request.setAttribute("messages", messageDAO.getAllMessages());
forwardToMessagePage(request, response);
}
/**
* 转发到留言页面
*/
private void forwardToMessagePage(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 检查是否是AJAX请求
if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
// AJAX请求,返回JSON响应
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
boolean success = request.getAttribute("errorMsg") == null;
String message = success ?
(String) request.getAttribute("successMsg") :
(String) request.getAttribute("errorMsg");
response.getWriter().write("{\"success\":" + success + ",\"message\":\"" + message + "\"}");
} else {
// 普通请求,转发到JSP页面
request.getRequestDispatcher("message.jsp").forward(request, response);
}
}
/**
* 获取客户端IP地址
*/
private String getClientIpAddress(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
点击查看代码UpdateProfileServlet.java
package com.blog.servlet;
import com.blog.bean.User;
import com.blog.dao.UserDAO;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;
import java.io.IOException;
/**
* 更新个人信息Servlet
* 处理用户信息修改请求
*/
@WebServlet("/updateProfile")
public class UpdateProfileServlet extends HttpServlet {
private UserDAO userDAO = new UserDAO();
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 检查用户是否已登录
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("user") == null) {
response.sendRedirect("login.jsp");
return;
}
User currentUser = (User) session.getAttribute("user");
// 获取表单参数
String nickname = request.getParameter("nickname");
String gender = request.getParameter("gender");
String ageStr = request.getParameter("age");
String zodiac = request.getParameter("zodiac");
String password = request.getParameter("password");
String confirmPassword = request.getParameter("confirmPassword");
// 验证密码(如果需要修改密码)
if (password != null && !password.trim().isEmpty()) {
if (!password.equals(confirmPassword)) {
request.setAttribute("errorMsg", "两次输入的密码不一致!");
request.getRequestDispatcher("editProfile.jsp").forward(request, response);
return;
}
// 更新密码
boolean passwordUpdated = userDAO.updatePassword(currentUser.getUsername(), password);
if (passwordUpdated) {
// 更新session中的用户对象密码
currentUser.setPassword(password);
}
}
int age = currentUser.getAge(); // 默认保持原年龄
try {
age = Integer.parseInt(ageStr);
} catch (NumberFormatException e) {
// 保持原值
}
// 创建更新后的用户对象
User updatedUser = new User(
currentUser.getUsername(),
currentUser.getPassword(), // 密码可能已更新
nickname,
gender,
age,
zodiac
);
// 更新用户信息
boolean success = userDAO.updateUser(updatedUser);
if (success) {
// 更新session中的用户对象
session.setAttribute("user", updatedUser);
// 设置成功消息
request.setAttribute("successMsg", "个人信息更新成功!");
request.getRequestDispatcher("home.jsp").forward(request, response);
} else {
request.setAttribute("errorMsg", "更新失败,请重试!");
request.getRequestDispatcher("editProfile.jsp").forward(request, response);
}
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// GET请求直接转发到编辑页面
request.getRequestDispatcher("editProfile.jsp").forward(request, response);
}
}
视图层:
点击查看代码index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
// 检查用户是否已登录
HttpSession userSession = request.getSession(false);
boolean isLoggedIn = (userSession != null && userSession.getAttribute("user") != null);
%>
<!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" href="css/style.css">
</head>
<body>
<div class="container">
<!-- 导航栏 -->
<header class="header">
<nav class="nav">
<div class="logo">
<a href="index.jsp">个人博客</a>
</div>
<ul class="nav-list">
<li><a href="home.jsp">个人首页</a></li>
<li><a href="article?action=list&category=news">生活新闻</a></li>
<li><a href="article?action=list&category=study">学习记录</a></li>
<li><a href="article?action=list&category=school">学校生活</a></li>
<li><a href="article?action=list&category=plan">规划人生</a></li>
<li><a href="article?action=list&category=travel">旅行打卡</a></li>
<li><a href="message.jsp">访客留言</a></li>
<% if (isLoggedIn) { %>
<li class="user-info-nav">
<a href="#" class="user-name">
<%= ((com.blog.bean.User)userSession.getAttribute("user")).getNickname() %>
</a>
<div class="user-menu">
<a href="home.jsp">个人主页</a>
<a href="article?action=list">文章管理</a>
<a href="editProfile.jsp">编辑资料</a>
<a href="logout">退出</a>
</div>
</li>
<% } else { %>
<li><a href="login.jsp">登录</a></li>
<li><a href="register.jsp">注册</a></li>
<% } %>
</ul>
</nav>
</header>
<!-- 主要内容 -->
<main class="main-content">
<div class="welcome-section">
<h1>欢迎来到我的个人博客</h1>
<p>记录生活,分享思考,规划未来</p>
<% if (!isLoggedIn) { %>
<div class="auth-buttons">
<a href="login.jsp" class="btn btn-login">登录</a>
<a href="register.jsp" class="btn btn-register">注册</a>
</div>
<p class="auth-tip">请先登录以访问完整内容</p>
<% } else { %>
<div class="welcome-user">
<p>欢迎回来,<strong><%= ((com.blog.bean.User)userSession.getAttribute("user")).getNickname() %></strong>!</p>
<a href="home.jsp" class="btn btn-home">进入个人主页</a>
</div>
<% } %>
</div>
<div class="preview">
<h2>博客内容预览</h2>
<div class="preview-content">
<div class="preview-item">
<h3>生活新闻</h3>
<p>只要追求幸福和自我的完美,选择适合我们的生活。你会发现即使在每句可以表示爱,对生活的经历也会更加清晰。只要能有希望就会有完美的行为。</p>
</div>
<div class="preview-item">
<h3>学习记录</h3>
<p>有时候,我们只是需要换一扇窗户看风景,生活有微笑有害羞,不想说不等于无言,只有心里明了,也许永远远离沉默,选择享受...</p>
</div>
<div class="preview-item">
<h3>旅行打卡</h3>
<p>越过江河,越晚就不难,越现在还年轻。还可以走很长很长的路,还能听说很深很深的思念。去寻找那些曾出现在梦境中的路径,山口与田野。</p>
</div>
</div>
</div>
</main>
<footer class="footer">
<p>© 2025 个人博客系统 | 基于JSP+Servlet+JavaBean开发</p>
</footer>
</div>
</body>
</html>
点击查看代码home.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
// 检查用户是否已登录
HttpSession userSession = request.getSession(false);
com.blog.bean.User user = null;
if (userSession != null) {
user = (com.blog.bean.User) userSession.getAttribute("user");
}
// 如果未登录,重定向到登录页面
if (user == null) {
response.sendRedirect("login.jsp");
return;
}
%>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= user.getNickname() %>的个人博客</title>
<link rel="stylesheet" href="css/style.css">
<style>
/* 用户个人信息卡片 */
.user-profile {
display: flex;
align-items: center;
margin-bottom: 20px;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 10px;
}
/* 用户头像 */
.user-avatar {
width: 100px;
height: 100px;
border-radius: 50%;
background-color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 40px;
margin-right: 20px;
color: #764ba2;
}
/* 用户个人信息 */
.user-info h2 {
margin: 0 0 10px 0;
font-size: 1.2em;
}
/* 用户详细信息 */
.user-details {
display: flex;
gap: 15px;
flex-wrap: wrap;
}
/* 用户详细信息项 */
.user-details span {
background: rgba(255, 255, 255, 0.2);
padding: 5px 10px;
border-radius: 15px;
}
/* 博客内容区域 */
.blog-content {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
margin-top: 20px;
}
/* 博客分类区域 */
.blog-section {
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
/* 博客分类标题 */
.blog-section h3 {
color: #333;
border-bottom: 2px solid #667eea;
padding-bottom: 10px;
margin-bottom: 15px;
}
.blog-section p {
line-height: 1.6;
color: #666;
}
.article-meta {
color: #999;
font-size: 0.85em;
margin: 10px 0;
}
.article-actions {
display: flex;
gap: 10px;
margin-top: 15px;
}
.btn-small {
padding: 6px 12px;
font-size: 0.9em;
}
.no-content {
color: #999;
font-style: italic;
}
.view-all-link {
display: block;
text-align: right;
margin-top: 10px;
color: #667eea;
text-decoration: none;
}
.view-all-link:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="container">
<!-- 导航栏 -->
<header class="header">
<nav class="nav">
<div class="logo">
<a href="index.jsp">个人博客</a>
</div>
<ul class="nav-list">
<li><a href="#home" class="active">个人首页</a></li>
<li><a href="article?action=list&category=news">生活新闻</a></li>
<li><a href="article?action=list&category=study">学习记录</a></li>
<li><a href="article?action=list&category=school">学校生活</a></li>
<li><a href="article?action=list&category=plan">规划人生</a></li>
<li><a href="article?action=list&category=travel">旅行打卡</a></li>
<li><a href="message.jsp">访客留言</a></li>
<li class="user-info-nav">
<a href="#" class="user-name">
<%= user.getNickname() %>
</a>
<div class="user-menu">
<a href="home.jsp">个人主页</a>
<a href="article?action=list">文章管理</a>
<a href="editProfile.jsp">编辑资料</a>
<a href="logout">退出登录</a>
</div>
</li>
</ul>
</nav>
</header>
<!-- 主要内容 -->
<main class="main-content">
<!-- 用户信息区域 -->
<section id="home" class="user-profile">
<div class="user-avatar">
<%= user.getNickname().charAt(0) %>
</div>
<div class="user-info">
<h2><%= user.getNickname() %></h2>
<p>Nothing is impossible for a determined person.</p>
<div class="user-details">
<span><%= user.getGender() %></span>
<span><%= user.getAge() %>岁</span>
<span><%= user.getZodiac() %></span>
</div>
</div>
</section>
<!-- 博客内容区域 -->
<div class="blog-content">
<!-- 生活新闻 -->
<section id="news" class="blog-section">
<h3>生活新闻</h3>
<%
com.blog.dao.ArticleDAO articleDAO = new com.blog.dao.ArticleDAO();
java.util.List<com.blog.bean.Article> newsArticles = articleDAO.getArticlesByCategory("news");
if (!newsArticles.isEmpty()) {
com.blog.bean.Article latestNews = newsArticles.get(0);
%>
<p><strong><%= latestNews.getTitle() %></strong></p>
<p><%= latestNews.getContent().length() > 150 ?
latestNews.getContent().substring(0, 150) + "..." : latestNews.getContent() %></p>
<div class="article-meta">
发布于:<%= latestNews.getCreateTime() %>
<% if (!latestNews.getCreateTime().equals(latestNews.getUpdateTime())) { %>
<br>更新于:<%= latestNews.getUpdateTime() %>
<% } %>
</div>
<div class="article-actions">
<a href="article?action=view&id=<%= latestNews.getId() %>" class="btn btn-small">阅读更多</a>
<a href="article?action=list&category=news" class="btn btn-small">查看全部</a>
</div>
<%
} else {
%>
<p class="no-content">只要追求幸福和自我的完美,选择适合我们的生活。你会发现即使在每句可以表示爱,对生活的经历也会更加清晰。只要能有希望就会有完美的行为。</p>
<div class="article-actions">
<a href="article?action=list&category=news" class="btn btn-small">查看生活新闻</a>
<% if (user != null) { %>
<a href="article?action=edit" class="btn btn-small">发布新闻</a>
<% } %>
</div>
<%
}
%>
</section>
<!-- 学习记录 -->
<section id="study" class="blog-section">
<h3>学习记录</h3>
<%
java.util.List<com.blog.bean.Article> studyArticles = articleDAO.getArticlesByCategory("study");
if (!studyArticles.isEmpty()) {
com.blog.bean.Article latestStudy = studyArticles.get(0);
%>
<p><strong><%= latestStudy.getTitle() %></strong></p>
<p><%= latestStudy.getContent().length() > 150 ?
latestStudy.getContent().substring(0, 150) + "..." : latestStudy.getContent() %></p>
<div class="article-meta">
发布于:<%= latestStudy.getCreateTime() %>
<% if (!latestStudy.getCreateTime().equals(latestStudy.getUpdateTime())) { %>
<br>更新于:<%= latestStudy.getUpdateTime() %>
<% } %>
</div>
<div class="article-actions">
<a href="article?action=view&id=<%= latestStudy.getId() %>" class="btn btn-small">阅读更多</a>
<a href="article?action=list&category=study" class="btn btn-small">查看全部</a>
</div>
<%
} else {
%>
<p class="no-content">有时候,我们只是需要换一扇窗户看风景,生活有微笑有害羞,不想说不等于无言,只有心里明了,也许永远远离沉默,选择享受。沉默多了,承受也就越多,不知道已经打开另一扇窗户,放宽心情。也许就是另一个轻松美丽的世界,从一扇窗户到另一扇窗户,从一个世界到另一个世界,需要的是一个勇气、一抹心境。只有长生活本着理想的生活,我们才能成为真正充满的人。人生就是一场漫长时光,就看你能够迅速多久,坚持多久。</p>
<div class="article-actions">
<a href="article?action=list&category=study" class="btn btn-small">查看学习记录</a>
<% if (user != null) { %>
<a href="article?action=edit" class="btn btn-small">发布记录</a>
<% } %>
</div>
<%
}
%>
</section>
<!-- 学校生活 -->
<section id="school" class="blog-section">
<h3>学校生活</h3>
<%
java.util.List<com.blog.bean.Article> schoolArticles = articleDAO.getArticlesByCategory("school");
if (!schoolArticles.isEmpty()) {
com.blog.bean.Article latestSchool = schoolArticles.get(0);
%>
<p><strong><%= latestSchool.getTitle() %></strong></p>
<p><%= latestSchool.getContent().length() > 150 ?
latestSchool.getContent().substring(0, 150) + "..." : latestSchool.getContent() %></p>
<div class="article-meta">
发布于:<%= latestSchool.getCreateTime() %>
<% if (!latestSchool.getCreateTime().equals(latestSchool.getUpdateTime())) { %>
<br>更新于:<%= latestSchool.getUpdateTime() %>
<% } %>
</div>
<div class="article-actions">
<a href="article?action=view&id=<%= latestSchool.getId() %>" class="btn btn-small">阅读更多</a>
<a href="article?action=list&category=school" class="btn btn-small">查看全部</a>
</div>
<%
} else {
%>
<p class="no-content">有时候,我们只是需要换一扇窗户看风景,生活有微笑有害羞,不想说不等于无言,只有心里明了,也许永远远离沉默,选择享受。</p>
<div class="article-actions">
<a href="article?action=list&category=school" class="btn btn-small">查看学校生活</a>
<% if (user != null) { %>
<a href="article?action=edit" class="btn btn-small">发布内容</a>
<% } %>
</div>
<%
}
%>
</section>
<!-- 规划人生 -->
<section id="plan" class="blog-section">
<h3>规划人生</h3>
<%
java.util.List<com.blog.bean.Article> planArticles = articleDAO.getArticlesByCategory("plan");
if (!planArticles.isEmpty()) {
com.blog.bean.Article latestPlan = planArticles.get(0);
%>
<p><strong><%= latestPlan.getTitle() %></strong></p>
<p><%= latestPlan.getContent().length() > 150 ?
latestPlan.getContent().substring(0, 150) + "..." : latestPlan.getContent() %></p>
<div class="article-meta">
发布于:<%= latestPlan.getCreateTime() %>
<% if (!latestPlan.getCreateTime().equals(latestPlan.getUpdateTime())) { %>
<br>更新于:<%= latestPlan.getUpdateTime() %>
<% } %>
</div>
<div class="article-actions">
<a href="article?action=view&id=<%= latestPlan.getId() %>" class="btn btn-small">阅读更多</a>
<a href="article?action=list&category=plan" class="btn btn-small">查看全部</a>
</div>
<%
} else {
%>
<p class="no-content">人生规划是自我成长的重要部分。我们需要设定目标,制定计划,并坚持执行。无论是短期的小目标,还是长期的人生理想,每一步都值得认真对待。</p>
<div class="article-actions">
<a href="article?action=list&category=plan" class="btn btn-small">查看人生规划</a>
<% if (user != null) { %>
<a href="article?action=edit" class="btn btn-small">发布规划</a>
<% } %>
</div>
<%
}
%>
</section>
<!-- 旅行打卡 -->
<section id="travel" class="blog-section">
<h3>旅行打卡</h3>
<%
java.util.List<com.blog.bean.Article> travelArticles = articleDAO.getArticlesByCategory("travel");
if (!travelArticles.isEmpty()) {
com.blog.bean.Article latestTravel = travelArticles.get(0);
%>
<p><strong><%= latestTravel.getTitle() %></strong></p>
<p><%= latestTravel.getContent().length() > 150 ?
latestTravel.getContent().substring(0, 150) + "..." : latestTravel.getContent() %></p>
<div class="article-meta">
发布于:<%= latestTravel.getCreateTime() %>
<% if (!latestTravel.getCreateTime().equals(latestTravel.getUpdateTime())) { %>
<br>更新于:<%= latestTravel.getUpdateTime() %>
<% } %>
</div>
<div class="article-actions">
<a href="article?action=view&id=<%= latestTravel.getId() %>" class="btn btn-small">阅读更多</a>
<a href="article?action=list&category=travel" class="btn btn-small">查看全部</a>
</div>
<%
} else {
%>
<p class="no-content">越过江河,越现在还年轻。还可以走很长很长的路,还能听说很深很深的思念。去寻找那些曾出现在梦境中的路径,山口与田野。</p>
<div class="article-actions">
<a href="article?action=list&category=travel" class="btn btn-small">查看旅行打卡</a>
<% if (user != null) { %>
<a href="article?action=edit" class="btn btn-small">发布游记</a>
<% } %>
</div>
<%
}
%>
</section>
<!-- 访客留言 -->
<section id="message" class="blog-section">
<h3>访客留言</h3>
<p>欢迎留下你的足迹和想法,我会认真阅读每一条留言。</p>
<div class="message-preview">
<p><strong>最新留言:</strong></p>
<%
// 获取最新的3条留言
com.blog.dao.MessageDAO messageDAO = new com.blog.dao.MessageDAO();
java.util.List<com.blog.bean.Message> recentMessages = messageDAO.getAllMessages();
int count = Math.min(recentMessages.size(), 3);
if (count > 0) {
for (int i = 0; i < count; i++) {
com.blog.bean.Message msg = recentMessages.get(i);
%>
<div class="recent-message">
<strong><%= msg.getVisitor() %>:</strong>
<%= msg.getContent().length() > 50 ?
msg.getContent().substring(0, 50) + "..." : msg.getContent() %>
<span class="message-time"><%= msg.getCreateTime() %></span>
</div>
<%
}
} else {
%>
<p>还没有留言,快来第一个留言吧!</p>
<%
}
%>
</div>
<div class="article-actions">
<a href="message.jsp" class="btn btn-small">查看所有留言</a>
<a href="message.jsp" class="btn btn-small">我要留言</a>
</div>
</section>
</div>
</main>
<footer class="footer">
<p>© 2025 <%= user.getNickname() %>的个人博客 | 基于JSP+Servlet+JavaBean开发</p>
</footer>
</div>
<script>
// 导航栏滚动效果
document.querySelectorAll('.nav-list a').forEach(anchor => {
anchor.addEventListener('click', function(e) {
if (this.getAttribute('href').startsWith('#')) {
e.preventDefault();
const targetId = this.getAttribute('href').substring(1);
const targetElement = document.getElementById(targetId);
if (targetElement) {
window.scrollTo({
top: targetElement.offsetTop - 80,
behavior: 'smooth'
});
}
}
});
});
</script>
</body>
</html>
点击查看代码login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!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" href="css/style.css">
</head>
<body>
<div class="container">
<header class="header">
<nav class="nav">
<div class="logo">
<a href="index.jsp">个人博客</a>
</div>
<ul class="nav-list">
<li><a href="index.jsp">首页</a></li>
<li><a href="article?action=list&category=news">生活新闻</a></li>
<li><a href="article?action=list&category=study">学习记录</a></li>
<li><a href="article?action=list&category=school">学校生活</a></li>
<li><a href="article?action=list&category=plan">规划人生</a></li>
<li><a href="article?action=list&category=travel">旅行打卡</a></li>
<li><a href="message.jsp">访客留言</a></li>
<li><a href="register.jsp">注册</a></li>
</ul>
</nav>
</header>
<main class="main-content">
<div class="auth-container">
<div class="auth-form">
<h2>用户登录</h2>
<%-- 显示错误信息 --%>
<% if (request.getAttribute("errorMsg") != null) { %>
<div class="error-message">
<%= request.getAttribute("errorMsg") %>
</div>
<% } %>
<%-- 显示成功信息(注册成功后跳转到登录页面) --%>
<% if (request.getAttribute("successMsg") != null) { %>
<div class="success-message">
<%= request.getAttribute("successMsg") %>
</div>
<% } %>
<form action="login" method="post">
<div class="form-group">
<label for="username">用户名:</label>
<input type="text" id="username" name="username"
required placeholder="请输入用户名">
</div>
<div class="form-group">
<label for="password">密码:</label>
<input type="password" id="password" name="password"
required placeholder="请输入密码">
</div>
<div class="form-group">
<button type="submit" class="btn btn-submit">登录</button>
</div>
<div class="form-links">
<p>还没有账号?<a href="register.jsp">立即注册</a></p>
</div>
</form>
</div>
</div>
</main>
<footer class="footer">
<p>© 2025 个人博客系统 | 基于JSP+Servlet+JavaBean开发</p>
</footer>
</div>
</body>
</html>
点击查看代码register.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!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" href="css/style.css">
</head>
<body>
<div class="container">
<header class="header">
<nav class="nav">
<div class="logo">
<a href="index.jsp">个人博客</a>
</div>
<ul class="nav-list">
<li><a href="index.jsp">首页</a></li>
<li><a href="article?action=list&category=news">生活新闻</a></li>
<li><a href="article?action=list&category=study">学习记录</a></li>
<li><a href="article?action=list&category=school">学校生活</a></li>
<li><a href="article?action=list&category=plan">规划人生</a></li>
<li><a href="article?action=list&category=travel">旅行打卡</a></li>
<li><a href="message.jsp">访客留言</a></li>
<li><a href="login.jsp">登录</a></li>
</ul>
</nav>
</header>
<main class="main-content">
<div class="auth-container">
<div class="auth-form">
<h2>用户注册</h2>
<%-- 显示错误信息 --%>
<% if (request.getAttribute("errorMsg") != null) { %>
<div class="error-message">
<%= request.getAttribute("errorMsg") %>
</div>
<% } %>
<form action="register" method="post">
<div class="form-group">
<label for="username">用户名:</label>
<input type="text" id="username" name="username"
required placeholder="请输入用户名(登录使用)">
</div>
<div class="form-group">
<label for="password">密码:</label>
<input type="password" id="password" name="password"
required placeholder="请输入密码">
</div>
<div class="form-group">
<label for="nickname">昵称:</label>
<input type="text" id="nickname" name="nickname"
placeholder="例如:小猪猪">
</div>
<div class="form-group">
<label>性别:</label>
<div class="radio-group">
<input type="radio" id="male" name="gender" value="男" checked>
<label for="male">男</label>
<input type="radio" id="female" name="gender" value="女">
<label for="female">女</label>
</div>
</div>
<div class="form-group">
<label for="age">年龄:</label>
<input type="number" id="age" name="age"
value="20" min="1" max="100">
</div>
<div class="form-group">
<label for="zodiac">星座:</label>
<select id="zodiac" name="zodiac">
<option value="狮子座" selected>狮子座</option>
<option value="白羊座">白羊座</option>
<option value="金牛座">金牛座</option>
<option value="双子座">双子座</option>
<option value="巨蟹座">巨蟹座</option>
<option value="处女座">处女座</option>
<option value="天秤座">天秤座</option>
<option value="天蝎座">天蝎座</option>
<option value="射手座">射手座</option>
<option value="摩羯座">摩羯座</option>
<option value="水瓶座">水瓶座</option>
<option value="双鱼座">双鱼座</option>
</select>
</div>
<div class="form-group">
<button type="submit" class="btn btn-submit">注册</button>
</div>
<div class="form-links">
<p>已有账号?<a href="login.jsp">立即登录</a></p>
</div>
</form>
</div>
</div>
</main>
<footer class="footer">
<p>© 2025 个人博客系统 | 基于JSP+Servlet+JavaBean开发</p>
</footer>
</div>
</body>
</html>
点击查看代码editProfile.jsp
<%--
Created by IntelliJ IDEA.
User: Felix
Date: 2026/1/7
Time: 10:35
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
// 检查用户是否已登录
HttpSession userSession = request.getSession(false);
com.blog.bean.User user = null;
if (userSession != null) {
user = (com.blog.bean.User) userSession.getAttribute("user");
}
// 如果未登录,重定向到登录页面
if (user == null) {
response.sendRedirect("login.jsp");
return;
}
%>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>编辑个人信息 - <%= user.getNickname() %>的个人博客</title>
<link rel="stylesheet" href="css/style.css">
<style>
.profile-container {
max-width: 600px;
margin: 0 auto;
padding: 40px 20px;
}
.profile-form {
background: white;
padding: 40px;
border-radius: 10px;
box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1);
}
.profile-header {
text-align: center;
margin-bottom: 30px;
}
.profile-header h2 {
color: #333;
margin-bottom: 10px;
}
.profile-header p {
color: #666;
}
.form-section {
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 1px solid #eee;
}
.form-section h3 {
color: #667eea;
margin-bottom: 20px;
font-size: 1.2em;
}
.password-note {
color: #999;
font-size: 0.9em;
margin-top: 5px;
}
.form-actions {
display: flex;
justify-content: space-between;
margin-top: 30px;
}
.btn-cancel {
background-color: #e2e8f0;
color: #4a5568;
}
.btn-cancel:hover {
background-color: #cbd5e0;
}
.current-info {
background-color: #f7fafc;
padding: 15px;
border-radius: 5px;
margin-bottom: 20px;
border-left: 4px solid #667eea;
}
.current-info p {
margin: 5px 0;
color: #4a5568;
}
</style>
</head>
<body>
<div class="container">
<!-- 导航栏 -->
<header class="header">
<nav class="nav">
<div class="logo">
<a href="index.jsp">个人博客</a>
</div>
<ul class="nav-list">
<li><a href="home.jsp">个人首页</a></li>
<li><a href="article?action=list&category=news">生活新闻</a></li>
<li><a href="article?action=list&category=study">学习记录</a></li>
<li><a href="article?action=list&category=school">学校生活</a></li>
<li><a href="article?action=list&category=plan">规划人生</a></li>
<li><a href="article?action=list&category=travel">旅行打卡</a></li>
<li><a href="message.jsp">访客留言</a></li>
<% if (user != null) { %>
<li class="user-info-nav">
<a href="#" class="user-name"><%= user.getNickname() %></a>
<div class="user-menu">
<a href="home.jsp">个人主页</a>
<a href="article?action=list">文章管理</a>
<a href="logout">退出登录</a>
</div>
</li>
<% } %>
</ul>
</nav>
</header>
<!-- 主要内容 -->
<main class="main-content">
<div class="profile-container">
<div class="profile-form">
<div class="profile-header">
<h2>编辑个人信息</h2>
<p>修改你的个人资料和密码</p>
</div>
<!-- 显示当前信息 -->
<div class="current-info">
<p><strong>当前信息:</strong></p>
<p>用户名:<strong><%= user.getUsername() %></strong>(不可修改)</p>
<p>昵称:<%= user.getNickname() %></p>
<p>性别:<%= user.getGender() %></p>
<p>年龄:<%= user.getAge() %>岁</p>
<p>星座:<%= user.getZodiac() %></p>
</div>
<%-- 显示错误信息 --%>
<% if (request.getAttribute("errorMsg") != null) { %>
<div class="error-message">
<%= request.getAttribute("errorMsg") %>
</div>
<% } %>
<%-- 显示成功信息 --%>
<% if (request.getAttribute("successMsg") != null) { %>
<div class="success-message">
<%= request.getAttribute("successMsg") %>
</div>
<% } %>
<form action="updateProfile" method="post">
<!-- 基本信息部分 -->
<div class="form-section">
<h3>基本信息</h3>
<div class="form-group">
<label for="nickname">昵称:</label>
<input type="text" id="nickname" name="nickname"
value="<%= user.getNickname() %>"
placeholder="请输入昵称">
</div>
<div class="form-group">
<label>性别:</label>
<div class="radio-group">
<input type="radio" id="male" name="gender" value="男"
<%= "男".equals(user.getGender()) ? "checked" : "" %>>
<label for="male">男</label>
<input type="radio" id="female" name="gender" value="女"
<%= "女".equals(user.getGender()) ? "checked" : "" %>>
<label for="female">女</label>
</div>
</div>
<div class="form-group">
<label for="age">年龄:</label>
<input type="number" id="age" name="age"
value="<%= user.getAge() %>" min="1" max="100">
</div>
<div class="form-group">
<label for="zodiac">星座:</label>
<select id="zodiac" name="zodiac">
<option value="狮子座" <%= "狮子座".equals(user.getZodiac()) ? "selected" : "" %>>狮子座</option>
<option value="白羊座" <%= "白羊座".equals(user.getZodiac()) ? "selected" : "" %>>白羊座</option>
<option value="金牛座" <%= "金牛座".equals(user.getZodiac()) ? "selected" : "" %>>金牛座</option>
<option value="双子座" <%= "双子座".equals(user.getZodiac()) ? "selected" : "" %>>双子座</option>
<option value="巨蟹座" <%= "巨蟹座".equals(user.getZodiac()) ? "selected" : "" %>>巨蟹座</option>
<option value="处女座" <%= "处女座".equals(user.getZodiac()) ? "selected" : "" %>>处女座</option>
<option value="天秤座" <%= "天秤座".equals(user.getZodiac()) ? "selected" : "" %>>天秤座</option>
<option value="天蝎座" <%= "天蝎座".equals(user.getZodiac()) ? "selected" : "" %>>天蝎座</option>
<option value="射手座" <%= "射手座".equals(user.getZodiac()) ? "selected" : "" %>>射手座</option>
<option value="摩羯座" <%= "摩羯座".equals(user.getZodiac()) ? "selected" : "" %>>摩羯座</option>
<option value="水瓶座" <%= "水瓶座".equals(user.getZodiac()) ? "selected" : "" %>>水瓶座</option>
<option value="双鱼座" <%= "双鱼座".equals(user.getZodiac()) ? "selected" : "" %>>双鱼座</option>
</select>
</div>
</div>
<!-- 密码修改部分 -->
<div class="form-section">
<h3>修改密码(可选)</h3>
<p class="password-note">如果不需要修改密码,请留空以下字段</p>
<div class="form-group">
<label for="password">新密码:</label>
<input type="password" id="password" name="password"
placeholder="如需修改密码,请输入新密码">
</div>
<div class="form-group">
<label for="confirmPassword">确认新密码:</label>
<input type="password" id="confirmPassword" name="confirmPassword"
placeholder="请再次输入新密码">
</div>
</div>
<div class="form-actions">
<a href="home.jsp" class="btn btn-cancel">取消</a>
<button type="submit" class="btn btn-submit">保存更改</button>
</div>
</form>
</div>
</div>
</main>
<footer class="footer">
<p>© 2025 <%= user.getNickname() %>的个人博客 | 基于JSP+Servlet+JavaBean开发</p>
</footer>
</div>
</body>
</html>
点击查看代码message.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="com.blog.bean.Message" %>
<%@ page import="java.util.List" %>
<%@ page import="com.blog.bean.User" %>
<%
// 检查是否传递了消息列表
List<Message> messages = (List<Message>) request.getAttribute("messages");
if (messages == null) {
response.sendRedirect("message?action=view");
return;
}
// 检查用户是否已登录(用于显示管理功能)
HttpSession userSession = request.getSession(false);
User user = null;
boolean isLoggedIn = false;
if (userSession != null) {
user = (User) userSession.getAttribute("user");
isLoggedIn = (user != null);
}
%>
<!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" href="css/style.css">
<style>
.message-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.message-form-container {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1);
margin-bottom: 30px;
}
.message-list-container {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1);
}
.message-item {
border-bottom: 1px solid #eee;
padding: 20px 0;
position: relative;
}
.message-item:last-child {
border-bottom: none;
}
.message-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.message-visitor {
font-weight: bold;
color: #333;
font-size: 1.1em;
}
.message-time {
color: #999;
font-size: 0.9em;
}
.message-email {
color: #667eea;
font-size: 0.9em;
margin-left: 10px;
}
.message-content {
color: #555;
line-height: 1.6;
margin: 15px 0;
padding: 15px;
background-color: #f9f9f9;
border-radius: 5px;
border-left: 4px solid #667eea;
}
.message-footer {
display: flex;
justify-content: space-between;
align-items: center;
color: #999;
font-size: 0.85em;
}
.message-ip {
font-family: monospace;
}
.delete-btn {
background-color: #fc8181;
color: white;
border: none;
padding: 5px 10px;
border-radius: 3px;
cursor: pointer;
font-size: 0.8em;
}
.delete-btn:hover {
background-color: #f56565;
}
.no-messages {
text-align: center;
padding: 40px;
color: #999;
}
.message-count {
text-align: center;
margin-bottom: 20px;
color: #667eea;
font-size: 1.1em;
}
.form-row {
display: flex;
gap: 20px;
margin-bottom: 20px;
}
.form-row .form-group {
flex: 1;
}
.message-preview {
background-color: #f0f9ff;
border: 1px dashed #667eea;
padding: 15px;
border-radius: 5px;
margin-top: 20px;
display: none;
}
.preview-header {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
.char-count {
text-align: right;
color: #999;
font-size: 0.9em;
margin-top: 5px;
}
.btn-secondary {
background-color: #e2e8f0;
color: #4a5568;
}
.btn-secondary:hover {
background-color: #cbd5e0;
}
</style>
</head>
<body>
<div class="container">
<!-- 导航栏 -->
<header class="header">
<nav class="nav">
<div class="logo">
<a href="index.jsp">个人博客</a>
</div>
<ul class="nav-list">
<li><a href="home.jsp">个人首页</a></li>
<li><a href="article?action=list&category=news">生活新闻</a></li>
<li><a href="article?action=list&category=study">学习记录</a></li>
<li><a href="article?action=list&category=school">学校生活</a></li>
<li><a href="article?action=list&category=plan">规划人生</a></li>
<li><a href="article?action=list&category=travel">旅行打卡</a></li>
<li><a href="message.jsp" class="active">访客留言</a></li>
<% if (isLoggedIn) { %>
<li class="user-info-nav">
<a href="#" class="user-name"><%= user.getNickname() %></a>
<div class="user-menu">
<a href="home.jsp">个人主页</a>
<a href="article?action=list">文章管理</a>
<a href="editProfile.jsp">编辑资料</a>
<a href="logout">退出登录</a>
</div>
</li>
<% } else { %>
<li><a href="login.jsp">登录</a></li>
<li><a href="register.jsp">注册</a></li>
<% } %>
</ul>
</nav>
</header>
<!-- 主要内容 -->
<main class="main-content">
<div class="message-container">
<!-- 留言表单 -->
<div class="message-form-container">
<h2>留下你的足迹</h2>
<%-- 显示错误信息 --%>
<% if (request.getAttribute("errorMsg") != null) { %>
<div class="error-message">
<%= request.getAttribute("errorMsg") %>
</div>
<% } %>
<%-- 显示成功信息 --%>
<% if (request.getAttribute("successMsg") != null) { %>
<div class="success-message">
<%= request.getAttribute("successMsg") %>
</div>
<% } %>
<form id="messageForm" action="message" method="post">
<div class="form-row">
<div class="form-group">
<label for="visitor">你的昵称:</label>
<input type="text" id="visitor" name="visitor"
required placeholder="请输入昵称" maxlength="20">
</div>
<div class="form-group">
<label for="email">邮箱地址:</label>
<input type="email" id="email" name="email"
placeholder="请输入邮箱(可选)">
</div>
</div>
<div class="form-group">
<label for="content">留言内容:</label>
<textarea id="content" name="content" rows="5"
required placeholder="写下你想说的话..." maxlength="500"></textarea>
<div class="char-count">
<span id="charCount">0</span>/500
</div>
</div>
<div class="form-actions">
<button type="button" id="previewBtn" class="btn btn-secondary">预览</button>
<button type="submit" class="btn btn-submit">提交留言</button>
</div>
</form>
<!-- 预览区域 -->
<div id="messagePreview" class="message-preview">
<div class="preview-header">
<strong>留言预览</strong>
<button type="button" id="closePreview" style="background:none;border:none;color:#666;cursor:pointer;">×</button>
</div>
<div id="previewContent"></div>
</div>
</div>
<!-- 留言列表 -->
<div class="message-list-container">
<h2>留言列表</h2>
<div class="message-count">
共 <%= messages.size() %> 条留言
</div>
<% if (messages.isEmpty()) { %>
<div class="no-messages">
<p>还没有留言,快来第一个留言吧!</p>
</div>
<% } else { %>
<% for (Message message : messages) { %>
<div class="message-item" id="message-<%= message.getId() %>">
<div class="message-header">
<div>
<span class="message-visitor"><%= message.getVisitor() %></span>
<% if (message.getEmail() != null && !message.getEmail().isEmpty()) { %>
<span class="message-email">(<%= message.getEmail() %>)</span>
<% } %>
</div>
<span class="message-time"><%= message.getCreateTime() %></span>
</div>
<div class="message-content">
<%= message.getContent().replace("\n", "<br>") %>
</div>
<div class="message-footer">
<span class="message-ip">IP: <%= message.getIpAddress() %></span>
<% if (isLoggedIn) { %>
<form action="message" method="get" style="display:inline;">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="<%= message.getId() %>">
<button type="submit" class="delete-btn"
onclick="return confirm('确定要删除这条留言吗?')">删除</button>
</form>
<% } %>
</div>
</div>
<% } %>
<% } %>
</div>
</div>
</main>
<footer class="footer">
<p>© 2025 个人博客系统 | 留言数据保存在: data/messages.txt</p>
</footer>
</div>
<script>
// 字符计数
const contentTextarea = document.getElementById('content');
const charCount = document.getElementById('charCount');
contentTextarea.addEventListener('input', function() {
charCount.textContent = this.value.length;
});
// 留言预览功能
const previewBtn = document.getElementById('previewBtn');
const closePreview = document.getElementById('closePreview');
const messagePreview = document.getElementById('messagePreview');
const previewContent = document.getElementById('previewContent');
previewBtn.addEventListener('click', function() {
const visitor = document.getElementById('visitor').value;
const email = document.getElementById('email').value;
const content = document.getElementById('content').value;
if (!visitor || !content) {
alert('请填写昵称和留言内容!');
return;
}
let previewHtml = '';
previewHtml += '<div class="message-header">';
previewHtml += '<div>';
previewHtml += '<span class="message-visitor">' + visitor + '</span>';
if (email) {
previewHtml += '<span class="message-email">(' + email + ')</span>';
}
previewHtml += '</div>';
previewHtml += '<span class="message-time">刚刚</span>';
previewHtml += '</div>';
previewHtml += '<div class="message-content">';
previewHtml += content.replace(/\n/g, '<br>');
previewHtml += '</div>';
previewContent.innerHTML = previewHtml;
messagePreview.style.display = 'block';
});
closePreview.addEventListener('click', function() {
messagePreview.style.display = 'none';
});
// 表单提交前的验证
document.getElementById('messageForm').addEventListener('submit', function(e) {
const visitor = document.getElementById('visitor').value.trim();
const content = document.getElementById('content').value.trim();
if (!visitor) {
e.preventDefault();
alert('请填写昵称!');
document.getElementById('visitor').focus();
return;
}
if (!content) {
e.preventDefault();
alert('请填写留言内容!');
document.getElementById('content').focus();
return;
}
if (content.length > 500) {
e.preventDefault();
alert('留言内容不能超过500字!');
return;
}
});
</script>
</body>
</html>
点击查看代码articleList.jsp
<%--
Created by IntelliJ IDEA.
User: Felix
Date: 2026/1/7
Time: 16:57
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="com.blog.bean.Article" %>
<%@ page import="java.util.List" %>
<%@ page import="com.blog.bean.User" %>
<%
// 检查用户是否已登录
HttpSession userSession = request.getSession(false);
User user = null;
boolean isLoggedIn = false;
if (userSession != null) {
user = (User) userSession.getAttribute("user");
isLoggedIn = (user != null);
}
List<Article> articles = (List<Article>) request.getAttribute("articles");
String[][] categories = (String[][]) request.getAttribute("categories");
String currentCategory = (String) request.getAttribute("currentCategory");
String categoryName = (String) request.getAttribute("categoryName");
%>
<!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" href="css/style.css">
<style>
.article-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.article-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #667eea;
}
.category-tabs {
display: flex;
gap: 10px;
margin-bottom: 30px;
flex-wrap: wrap;
}
.category-tab {
padding: 10px 20px;
background-color: #f1f5f9;
border-radius: 20px;
text-decoration: none;
color: #475569;
transition: all 0.3s;
}
.category-tab:hover {
background-color: #e2e8f0;
}
.category-tab.active {
background-color: #667eea;
color: white;
}
.article-list {
display: grid;
gap: 20px;
}
.article-item {
background: white;
padding: 25px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
transition: transform 0.3s;
}
.article-item:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.article-title {
font-size: 1.3em;
color: #333;
margin-bottom: 10px;
text-decoration: none;
display: block;
}
.article-title:hover {
color: #667eea;
}
.article-meta {
display: flex;
justify-content: space-between;
color: #666;
font-size: 0.9em;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.article-content {
color: #555;
line-height: 1.6;
margin-bottom: 20px;
}
.article-content-preview {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.article-actions {
display: flex;
gap: 10px;
justify-content: flex-end;
}
.btn-small {
padding: 6px 12px;
font-size: 0.9em;
}
.btn-view {
background-color: #48bb78;
color: white;
}
.btn-edit {
background-color: #4299e1;
color: white;
}
.btn-delete {
background-color: #fc8181;
color: white;
}
.empty-message {
text-align: center;
padding: 60px;
color: #999;
background: white;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.category-badge {
display: inline-block;
padding: 4px 12px;
background-color: #667eea;
color: white;
border-radius: 12px;
font-size: 0.85em;
margin-right: 10px;
}
.time-info {
color: #94a3b8;
}
</style>
</head>
<body>
<div class="container">
<!-- 导航栏 -->
<header class="header">
<nav class="nav">
<div class="logo">
<a href="index.jsp">个人博客</a>
</div>
<ul class="nav-list">
<li><a href="home.jsp">个人首页</a></li>
<li><a href="article?action=list&category=news">生活新闻</a></li>
<li><a href="article?action=list&category=study">学习记录</a></li>
<li><a href="article?action=list&category=school">学校生活</a></li>
<li><a href="article?action=list&category=plan">规划人生</a></li>
<li><a href="article?action=list&category=travel">旅行打卡</a></li>
<li><a href="message.jsp">访客留言</a></li>
<% if (isLoggedIn) { %>
<li class="user-info-nav">
<a href="#" class="user-name"><%= user.getNickname() %></a>
<div class="user-menu">
<a href="home.jsp">个人主页</a>
<a href="article?action=list">文章管理</a>
<a href="editProfile.jsp">编辑资料</a>
<a href="logout">退出登录</a>
</div>
</li>
<% } else { %>
<li><a href="login.jsp">登录</a></li>
<li><a href="register.jsp">注册</a></li>
<% } %>
</ul>
</nav>
</header>
<!-- 主要内容 -->
<main class="main-content">
<div class="article-container">
<div class="article-header">
<h1><%= categoryName != null ? categoryName : "所有文章" %></h1>
<% if (isLoggedIn) { %>
<a href="article?action=edit" class="btn btn-submit">+ 发布新文章</a>
<% } %>
</div>
<%-- 显示错误信息 --%>
<% if (request.getAttribute("errorMsg") != null) { %>
<div class="error-message">
<%= request.getAttribute("errorMsg") %>
</div>
<% } %>
<%-- 显示成功信息 --%>
<% if (request.getAttribute("successMsg") != null) { %>
<div class="success-message">
<%= request.getAttribute("successMsg") %>
</div>
<% } %>
<!-- 分类标签 -->
<div class="category-tabs">
<a href="article?action=list"
class="category-tab <%= currentCategory == null ? "active" : "" %>">
全部
</a>
<% for (String[] category : categories) { %>
<a href="article?action=list&category=<%= category[0] %>"
class="category-tab <%= category[0].equals(currentCategory) ? "active" : "" %>">
<%= category[1] %>
</a>
<% } %>
</div>
<!-- 文章列表 -->
<div class="article-list">
<% if (articles == null || articles.isEmpty()) { %>
<div class="empty-message">
<h3>暂无文章</h3>
<p>还没有发布任何文章,快来第一个发布吧!</p>
<% if (isLoggedIn) { %>
<a href="article?action=edit" class="btn btn-submit" style="margin-top: 20px;">发布文章</a>
<% } else { %>
<p>请先<a href="login.jsp">登录</a>后发布文章</p>
<% } %>
</div>
<% } else { %>
<% for (Article article : articles) { %>
<div class="article-item">
<a href="article?action=view&id=<%= article.getId() %>" class="article-title">
<span class="category-badge"><%= article.getCategoryName() %></span>
<%= article.getTitle() %>
</a>
<div class="article-meta">
<span>作者:<%= article.getAuthor() %></span>
<span class="time-info">
发布于:<%= article.getCreateTime() %>
<% if (!article.getCreateTime().equals(article.getUpdateTime())) { %>
(更新于:<%= article.getUpdateTime() %>)
<% } %>
</span>
</div>
<div class="article-content article-content-preview">
<%= article.getContent().length() > 200 ?
article.getContent().substring(0, 200) + "..." : article.getContent() %>
</div>
<div class="article-actions">
<a href="article?action=view&id=<%= article.getId() %>"
class="btn btn-small btn-view">查看详情</a>
<% if (isLoggedIn && article.getAuthor().equals(user.getUsername())) { %>
<a href="article?action=edit&id=<%= article.getId() %>"
class="btn btn-small btn-edit">编辑</a>
<a href="article?action=delete&id=<%= article.getId() %>&category=<%= article.getCategory() %>"
class="btn btn-small btn-delete"
onclick="return confirm('确定要删除这篇文章吗?')">删除</a>
<% } %>
</div>
</div>
<% } %>
<% } %>
</div>
</div>
</main>
<footer class="footer">
<p>© 2025 个人博客系统 | 文章数据保存在: data/articles.txt</p>
</footer>
</div>
</body>
</html>
点击查看代码articleEdit.jsp
<%--
Created by IntelliJ IDEA.
User: Felix
Date: 2026/1/7
Time: 16:57
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="com.blog.bean.Article" %>
<%@ page import="com.blog.bean.User" %>
<%
// 检查用户是否已登录
HttpSession userSession = request.getSession(false);
User user = null;
if (userSession != null) {
user = (User) userSession.getAttribute("user");
}
// 如果未登录,重定向到登录页面
if (user == null) {
response.sendRedirect("login.jsp");
return;
}
Article article = (Article) request.getAttribute("article");
String[][] categories = (String[][]) request.getAttribute("categories");
boolean isEditMode = (article != null);
String pageTitle = isEditMode ? "编辑文章" : "发布新文章";
String formAction = isEditMode ? "article?action=update" : "article?action=create";
%>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= pageTitle %> - 个人博客</title>
<link rel="stylesheet" href="css/style.css">
<style>
.edit-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.edit-form {
background: white;
padding: 40px;
border-radius: 10px;
box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1);
}
.edit-header {
text-align: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #667eea;
}
.edit-header h2 {
color: #333;
margin-bottom: 10px;
}
.form-actions {
display: flex;
justify-content: space-between;
margin-top: 30px;
}
.char-count {
text-align: right;
color: #999;
font-size: 0.9em;
margin-top: 5px;
}
.preview-area {
background-color: #f9f9f9;
border: 1px solid #ddd;
border-radius: 5px;
padding: 15px;
margin-top: 20px;
display: none;
}
.preview-title {
font-size: 1.4em;
color: #333;
margin-bottom: 10px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.preview-content {
line-height: 1.6;
color: #555;
}
</style>
</head>
<body>
<div class="container">
<!-- 导航栏 -->
<header class="header">
<nav class="nav">
<div class="logo">
<a href="index.jsp">个人博客</a>
</div>
<ul class="nav-list">
<li><a href="home.jsp">个人首页</a></li>
<li><a href="article?action=list&category=news">生活新闻</a></li>
<li><a href="article?action=list&category=study">学习记录</a></li>
<li><a href="article?action=list&category=school">学校生活</a></li>
<li><a href="article?action=list&category=plan">规划人生</a></li>
<li><a href="article?action=list&category=travel">旅行打卡</a></li>
<li><a href="message.jsp">访客留言</a></li>
<% if (user != null) { %>
<li class="user-info-nav">
<a href="#" class="user-name"><%= user.getNickname() %></a>
<div class="user-menu">
<a href="home.jsp">个人主页</a>
<a href="article?action=list">文章管理</a>
<a href="editProfile.jsp">编辑资料</a>
<a href="logout">退出登录</a>
</div>
</li>
<% } %>
</ul>
</nav>
</header>
<!-- 主要内容 -->
<main class="main-content">
<div class="edit-container">
<div class="edit-form">
<div class="edit-header">
<h2><%= pageTitle %></h2>
<p>在这里撰写和编辑你的文章内容</p>
</div>
<%-- 显示错误信息 --%>
<% if (request.getAttribute("errorMsg") != null) { %>
<div class="error-message">
<%= request.getAttribute("errorMsg") %>
</div>
<% } %>
<form action="<%= formAction %>" method="post">
<% if (isEditMode) { %>
<input type="hidden" name="id" value="<%= article.getId() %>">
<% } %>
<div class="form-group">
<label for="category">选择分类:</label>
<select id="category" name="category" required>
<option value="">-- 请选择分类 --</option>
<% for (String[] category : categories) { %>
<option value="<%= category[0] %>"
<% if (isEditMode && article.getCategory().equals(category[0])) { %>
selected
<% } %>
>
<%= category[1] %>
</option>
<% } %>
</select>
</div>
<div class="form-group">
<label for="title">文章标题:</label>
<input type="text" id="title" name="title"
value="<%= isEditMode ? article.getTitle() : "" %>"
required placeholder="请输入文章标题" maxlength="100">
</div>
<div class="form-group">
<label for="content">文章内容:</label>
<textarea id="content" name="content" rows="15"
required placeholder="请输入文章内容..."><%= isEditMode ? article.getContent() : "" %></textarea>
<div class="char-count">
<span id="charCount">0</span>/5000
</div>
</div>
<div class="form-actions">
<button type="button" id="previewBtn" class="btn btn-secondary">预览</button>
<div>
<a href="article?action=list" class="btn btn-cancel">取消</a>
<button type="submit" class="btn btn-submit">
<%= isEditMode ? "更新文章" : "发布文章" %>
</button>
</div>
</div>
</form>
<!-- 预览区域 -->
<div id="previewArea" class="preview-area">
<div class="preview-header">
<strong>文章预览</strong>
<button type="button" id="closePreview" style="background:none;border:none;color:#666;cursor:pointer;">×</button>
</div>
<div id="previewTitle" class="preview-title"></div>
<div id="previewContent" class="preview-content"></div>
</div>
</div>
</div>
</main>
<footer class="footer">
<p>© 2025 个人博客系统</p>
</footer>
</div>
<script>
// 字符计数
const contentTextarea = document.getElementById('content');
const charCount = document.getElementById('charCount');
// 初始化字符计数
charCount.textContent = contentTextarea.value.length;
contentTextarea.addEventListener('input', function() {
charCount.textContent = this.value.length;
});
// 文章预览功能
const previewBtn = document.getElementById('previewBtn');
const closePreview = document.getElementById('closePreview');
const previewArea = document.getElementById('previewArea');
const previewTitle = document.getElementById('previewTitle');
const previewContent = document.getElementById('previewContent');
previewBtn.addEventListener('click', function() {
const categorySelect = document.getElementById('category');
const categoryText = categorySelect.options[categorySelect.selectedIndex].text;
const title = document.getElementById('title').value;
const content = document.getElementById('content').value;
if (!categorySelect.value || !title || !content) {
alert('请先填写完整的文章信息!');
return;
}
previewTitle.innerHTML = `<span style="background:#667eea;color:white;padding:2px 8px;border-radius:4px;font-size:0.8em;margin-right:10px;">${categoryText}</span>${title}`;
previewContent.innerHTML = content.replace(/\n/g, '<br>');
previewArea.style.display = 'block';
// 滚动到预览区域
previewArea.scrollIntoView({ behavior: 'smooth' });
});
closePreview.addEventListener('click', function() {
previewArea.style.display = 'none';
});
// 表单提交验证
document.querySelector('form').addEventListener('submit', function(e) {
const category = document.getElementById('category').value;
const title = document.getElementById('title').value.trim();
const content = document.getElementById('content').value.trim();
if (!category) {
e.preventDefault();
alert('请选择文章分类!');
return;
}
if (!title) {
e.preventDefault();
alert('请输入文章标题!');
document.getElementById('title').focus();
return;
}
if (!content) {
e.preventDefault();
alert('请输入文章内容!');
document.getElementById('content').focus();
return;
}
if (content.length > 5000) {
e.preventDefault();
alert('文章内容不能超过5000字!');
return;
}
});
</script>
</body>
</html>
点击查看代码articleView.jsp
<%--
Created by IntelliJ IDEA.
User: Felix
Date: 2026/1/7
Time: 16:58
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="com.blog.bean.Article" %>
<%@ page import="com.blog.bean.User" %>
<%
Article article = (Article) request.getAttribute("article");
if (article == null) {
response.sendRedirect("article?action=list");
return;
}
// 检查用户是否已登录
HttpSession userSession = request.getSession(false);
User user = null;
boolean isLoggedIn = false;
boolean isAuthor = false;
if (userSession != null) {
user = (User) userSession.getAttribute("user");
isLoggedIn = (user != null);
if (isLoggedIn) {
isAuthor = article.getAuthor().equals(user.getUsername());
}
}
%>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= article.getTitle() %> - 个人博客</title>
<link rel="stylesheet" href="css/style.css">
<style>
.view-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.article-view {
background: white;
padding: 40px;
border-radius: 10px;
box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1);
}
.article-header {
text-align: center;
margin-bottom: 40px;
padding-bottom: 30px;
border-bottom: 2px solid #667eea;
}
.article-category {
display: inline-block;
padding: 6px 15px;
background-color: #667eea;
color: white;
border-radius: 15px;
font-size: 0.9em;
margin-bottom: 20px;
}
.article-title {
font-size: 2em;
color: #333;
margin-bottom: 20px;
line-height: 1.3;
}
.article-meta {
color: #666;
font-size: 0.95em;
}
.article-meta span {
margin-right: 20px;
}
.article-content {
line-height: 1.8;
color: #444;
font-size: 1.1em;
margin-bottom: 40px;
}
.article-footer {
padding-top: 30px;
border-top: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
}
.article-actions {
display: flex;
gap: 10px;
}
.time-info {
color: #94a3b8;
font-size: 0.9em;
}
.content-section {
white-space: pre-wrap;
word-wrap: break-word;
}
</style>
</head>
<body>
<div class="container">
<!-- 导航栏 -->
<header class="header">
<nav class="nav">
<div class="logo">
<a href="index.jsp">个人博客</a>
</div>
<ul class="nav-list">
<li><a href="home.jsp">个人首页</a></li>
<li><a href="article?action=list&category=news">生活新闻</a></li>
<li><a href="article?action=list&category=study">学习记录</a></li>
<li><a href="article?action=list&category=school">学校生活</a></li>
<li><a href="article?action=list&category=plan">规划人生</a></li>
<li><a href="article?action=list&category=travel">旅行打卡</a></li>
<li><a href="message.jsp">访客留言</a></li>
<% if (isLoggedIn) { %>
<li class="user-info-nav">
<a href="#" class="user-name"><%= user.getNickname() %></a>
<div class="user-menu">
<a href="home.jsp">个人主页</a>
<a href="article?action=list">文章管理</a>
<a href="editProfile.jsp">编辑资料</a>
<a href="logout">退出登录</a>
</div>
</li>
<% } else { %>
<li><a href="login.jsp">登录</a></li>
<li><a href="register.jsp">注册</a></li>
<% } %>
</ul>
</nav>
</header>
<!-- 主要内容 -->
<main class="main-content">
<div class="view-container">
<div class="article-view">
<div class="article-header">
<div class="article-category"><%= article.getCategoryName() %></div>
<h1 class="article-title"><%= article.getTitle() %></h1>
<div class="article-meta">
<span>作者:<%= article.getAuthor() %></span>
<span>发布时间:<%= article.getCreateTime() %></span>
</div>
</div>
<div class="article-content content-section"><%= article.getContent() %></div>
<div class="article-footer">
<div class="time-info">
最后更新:<%= article.getUpdateTime() %>
</div>
<div class="article-actions">
<a href="article?action=list&category=<%= article.getCategory() %>"
class="btn btn-small">返回列表</a>
<% if (isAuthor) { %>
<a href="article?action=edit&id=<%= article.getId() %>"
class="btn btn-small btn-edit">编辑文章</a>
<a href="article?action=delete&id=<%= article.getId() %>&category=<%= article.getCategory() %>"
class="btn btn-small btn-delete"
onclick="return confirm('确定要删除这篇文章吗?')">删除文章</a>
<% } %>
</div>
</div>
</div>
</div>
</main>
<footer class="footer">
<p> 2025 个人博客系统</p>
</footer>
</div>
</body>
</html>
页面排版
点击查看代码style.css
/* 全局样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Arial', 'Microsoft YaHei', sans-serif;
line-height: 1.6;
color: #333;
background-color: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
/* 导航栏样式 */
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
position: sticky;
top: 0;
z-index: 1000;
}
.nav {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 0;
}
.logo a {
color: white;
font-size: 24px;
font-weight: bold;
text-decoration: none;
}
.nav-list {
display: flex;
list-style: none;
gap: 20px;
}
.nav-list li a {
color: white;
text-decoration: none;
padding: 8px 16px;
border-radius: 4px;
transition: background-color 0.3s;
}
.nav-list li a:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.nav-list li a.active {
background-color: rgba(255, 255, 255, 0.2);
}
.user-info-nav {
position: relative;
}
.user-name {
background-color: rgba(255, 255, 255, 0.2);
padding: 8px 16px;
border-radius: 20px;
}
.user-menu {
display: none;
position: absolute;
top: 100%;
right: 0;
background: white;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
border-radius: 4px;
min-width: 120px;
}
.user-info-nav:hover .user-menu {
display: block;
}
.user-menu a {
color: #333 !important;
display: block;
padding: 10px;
text-decoration: none;
}
.user-menu a:hover {
background-color: #f5f5f5;
}
/* 主要内容区域 */
.main-content {
padding: 40px 0;
}
.welcome-section {
text-align: center;
padding: 60px 20px;
background: white;
border-radius: 10px;
box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1);
margin-bottom: 40px;
}
.welcome-section h1 {
font-size: 2.5em;
color: #333;
margin-bottom: 20px;
}
.welcome-section p {
font-size: 1.2em;
color: #666;
margin-bottom: 30px;
}
.auth-buttons {
display: flex;
justify-content: center;
gap: 20px;
margin-bottom: 20px;
}
.btn {
display: inline-block;
padding: 12px 30px;
border-radius: 25px;
text-decoration: none;
font-weight: bold;
transition: all 0.3s;
border: none;
cursor: pointer;
font-size: 16px;
}
.btn-login {
background-color: #667eea;
color: white;
}
.btn-register {
background-color: #764ba2;
color: white;
}
.btn-home {
background-color: #48bb78;
color: white;
margin-top: 20px;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.auth-tip {
color: #999;
font-size: 0.9em;
}
/* 预览区域 */
.preview {
background: white;
padding: 40px;
border-radius: 10px;
box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1);
}
.preview h2 {
text-align: center;
margin-bottom: 30px;
color: #333;
}
.preview-content {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 30px;
}
.preview-item {
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
transition: transform 0.3s;
}
.preview-item:hover {
transform: translateY(-5px);
}
.preview-item h3 {
color: #667eea;
margin-bottom: 15px;
border-bottom: 2px solid #667eea;
padding-bottom: 10px;
}
/* 认证表单样式 */
.auth-container {
max-width: 500px;
margin: 0 auto;
padding: 40px 20px;
}
.auth-form {
background: white;
padding: 40px;
border-radius: 10px;
box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1);
}
.auth-form h2 {
text-align: center;
margin-bottom: 30px;
color: #333;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: #555;
font-weight: bold;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
transition: border-color 0.3s;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
border-color: #667eea;
outline: none;
}
.radio-group {
display: flex;
gap: 20px;
margin-top: 8px;
}
.radio-group input[type="radio"] {
width: auto;
margin-right: 5px;
}
.btn-submit {
width: 100%;
background-color: #667eea;
color: white;
padding: 14px;
font-size: 16px;
}
.btn-submit:hover {
background-color: #5a67d8;
}
.form-links {
text-align: center;
margin-top: 20px;
}
.form-links a {
color: #667eea;
text-decoration: none;
}
.form-links a:hover {
text-decoration: underline;
}
.error-message {
background-color: #fed7d7;
color: #c53030;
padding: 12px;
border-radius: 4px;
margin-bottom: 20px;
text-align: center;
}
.success-message {
background-color: #c6f6d5;
color: #22543d;
padding: 12px;
border-radius: 4px;
margin-bottom: 20px;
text-align: center;
}
/* 页脚样式 */
.footer {
text-align: center;
padding: 30px 0;
color: #666;
border-top: 1px solid #eee;
margin-top: 40px;
}
/* 响应式设计 */
/*弹性布局*/
@media (max-width: 768px) {
.nav {
flex-direction: column;
gap: 15px;
}
.nav-list {
flex-wrap: wrap;
justify-content: center;
}
.preview-content {
grid-template-columns: 1fr;
}
.welcome-section h1 {
font-size: 2em;
}
.auth-buttons {
flex-direction: column;
align-items: center;
}
.btn {
width: 200px;
text-align: center;
}
}
/* 用户菜单样式 */
.user-info-nav {
position: relative;
}
.user-name {
background-color: rgba(255, 255, 255, 0.2);
padding: 8px 16px;
border-radius: 20px;
display: inline-block;
}
.user-menu {
display: none;
position: absolute;
top: 100%;
right: 0;
background: white;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
border-radius: 4px;
min-width: 150px;
z-index: 1000;
}
.user-info-nav:hover .user-menu {
display: block;
}
.user-menu a {
color: #333 !important;
display: block;
padding: 10px 15px;
text-decoration: none;
border-bottom: 1px solid #eee;
}
.user-menu a:last-child {
border-bottom: none;
}
.user-menu a:hover {
background-color: #f5f5f5;
color: #667eea !important;
}
/* 留言相关样式 */
.message-preview {
background-color: #f8f9fa;
padding: 15px;
border-radius: 5px;
margin: 15px 0;
}
.recent-message {
border-bottom: 1px solid #eee;
padding: 10px 0;
color: #555;
}
.recent-message:last-child {
border-bottom: none;
}
.message-time {
color: #999;
font-size: 0.85em;
float: right;
}
.message-actions {
display: flex;
gap: 10px;
margin-top: 20px;
}
.btn-message {
background-color: #48bb78;
color: white;
}
.char-count {
text-align: right;
color: #999;
font-size: 0.9em;
margin-top: 5px;
}
.btn-secondary {
background-color: #e2e8f0;
color: #4a5568;
}
.btn-secondary:hover {
background-color: #cbd5e0;
}
/* 文章相关样式 */
.article-meta {
color: #999;
font-size: 0.85em;
margin: 10px 0;
}
.article-actions {
display: flex;
gap: 10px;
margin-top: 15px;
}
.btn-small {
padding: 6px 12px;
font-size: 0.9em;
}
.no-content {
color: #999;
font-style: italic;
}
.view-all-link {
display: block;
text-align: right;
margin-top: 10px;
color: #667eea;
text-decoration: none;
}
.view-all-link:hover {
text-decoration: underline;
}
.message-preview {
background-color: #f8f9fa;
padding: 15px;
border-radius: 5px;
margin: 15px 0;
}
.recent-message {
border-bottom: 1px solid #eee;
padding: 10px 0;
color: #555;
}
.recent-message:last-child {
border-bottom: none;
}
.message-time {
color: #999;
font-size: 0.85em;
float: right;
}
2.2原始系统运行截图
- 图一:原始系统首页,展示博客预览和登录入口
![image]()
- 图二:文章列表页面,显示所有文章并编辑文章等功能
![image]()
- 图三:txt文件存储方式
![image]()
三、原始系统缺陷分析
3.1文件并发访问问题
当多个用户同时访问或修改数据时,文件操作可能产生冲突,导致数据丢失或损坏。
3.2性能瓶颈
随着数据量增长,每次操作都需要读取整个文件,性能急剧下降。
3.3数据一致性问题
在更新或删除操作中,如果程序中途崩溃,可能导致数据文件损坏。
3.4数据冗余
相同的用户信息在多个文件中重复存储(文章文件中有作者名,但没有用户详细信息)。
四、重构方案设计
4.1重构目标:将文件存储迁移到MySQL数据库,保证数据一致性和完整性
4.2数据库设计:
表结构设计
点击查看代码sql
-- 创建数据库
CREATE DATABASE personal_blog CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE personal_blog;
-- 用户表
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(100) NOT NULL,
nickname VARCHAR(50) NOT NULL,
gender ENUM('男', '女') DEFAULT '男',
age INT DEFAULT 20,
zodiac VARCHAR(20),
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_username (username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 文章表
CREATE TABLE articles (
id INT PRIMARY KEY AUTO_INCREMENT,
article_id VARCHAR(50) UNIQUE NOT NULL COMMENT '兼容原有ID格式',
category ENUM('news', 'study', 'school', 'plan', 'travel') NOT NULL,
title VARCHAR(200) NOT NULL,
content TEXT NOT NULL,
author_id INT NOT NULL,
author_name VARCHAR(50) NOT NULL,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
view_count INT DEFAULT 0,
FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE CASCADE,
FULLTEXT INDEX ft_content (title, content) WITH PARSER ngram,
INDEX idx_category (category),
INDEX idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 留言表
CREATE TABLE messages (
id INT PRIMARY KEY AUTO_INCREMENT,
message_id VARCHAR(50) UNIQUE NOT NULL COMMENT '兼容原有ID格式',
visitor_name VARCHAR(50) NOT NULL,
visitor_email VARCHAR(100),
content TEXT NOT NULL,
ip_address VARCHAR(50),
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 系统日志表
CREATE TABLE system_logs (
id INT PRIMARY KEY AUTO_INCREMENT,
log_level VARCHAR(20),
message TEXT,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
五、核心代码重构
5.1数据库连接工具类
点击查看代码DBUtil.java
package com.blog.util;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* 数据库连接工具类 - 使用HikariCP连接池
*/
public class DBUtil {
private static HikariDataSource dataSource;
static {
try {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/personal_blog?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8mb4");
config.setUsername("root");
config.setPassword("123456");
config.setDriverClassName("com.mysql.cj.jdbc.Driver");
// 连接池配置
config.setMaximumPoolSize(20); // 最大连接数
config.setMinimumIdle(5); // 最小空闲连接
config.setConnectionTimeout(30000); // 连接超时时间(ms)
config.setIdleTimeout(600000); // 空闲超时时间(ms)
config.setMaxLifetime(1800000); // 连接最大生命周期(ms)
// 连接测试
config.setConnectionTestQuery("SELECT 1");
config.setValidationTimeout(5000);
dataSource = new HikariDataSource(config);
// 测试连接
try (Connection conn = dataSource.getConnection()) {
System.out.println("数据库连接成功!");
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("数据库连接池初始化失败!", e);
}
}
/**
* 获取数据库连接
*/
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
/**
* 关闭连接池
*/
public static void close() {
if (dataSource != null && !dataSource.isClosed()) {
dataSource.close();
}
}
}
点击查看代码
package com.blog.dao;
import com.blog.bean.User;
import com.blog.util.DBUtil;
import org.apache.commons.dbutils.*;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import java.sql.*;
import java.util.List;
/**
* 重构后的UserDAO - 使用数据库存储
*/
public class UserDAO {
private QueryRunner runner = new QueryRunner();
private RowProcessor processor = new BasicRowProcessor(new GenerousBeanProcessor());
/**
* 用户注册
*/
public boolean register(User user) {
String sql = "INSERT INTO users (username, password, nickname, gender, age, zodiac) " +
"VALUES (?, ?, ?, ?, ?, ?)";
try (Connection conn = DBUtil.getConnection()) {
int result = runner.update(conn, sql,
user.getUsername(),
user.getPassword(), // 实际项目中应该加密存储
user.getNickname(),
user.getGender(),
user.getAge(),
user.getZodiac()
);
// 记录日志
logOperation("用户注册", user.getUsername(), result > 0);
return result > 0;
} catch (SQLException e) {
e.printStackTrace();
logError("用户注册失败", e);
return false;
}
}
/**
* 用户登录验证
*/
public User login(String username, String password) {
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
try (Connection conn = DBUtil.getConnection()) {
User user = runner.query(conn, sql,
new BeanHandler<>(User.class, processor),
username, password);
logOperation("用户登录", username, user != null);
return user;
} catch (SQLException e) {
e.printStackTrace();
logError("用户登录失败", e);
return null;
}
}
/**
* 根据用户名查找用户
*/
public User findByUsername(String username) {
String sql = "SELECT * FROM users WHERE username = ?";
try (Connection conn = DBUtil.getConnection()) {
return runner.query(conn, sql,
new BeanHandler<>(User.class, processor),
username);
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
/**
* 更新用户信息
*/
public boolean updateUser(User updatedUser) {
String sql = "UPDATE users SET nickname = ?, gender = ?, age = ?, zodiac = ? " +
"WHERE username = ?";
try (Connection conn = DBUtil.getConnection()) {
int result = runner.update(conn, sql,
updatedUser.getNickname(),
updatedUser.getGender(),
updatedUser.getAge(),
updatedUser.getZodiac(),
updatedUser.getUsername()
);
logOperation("更新用户信息", updatedUser.getUsername(), result > 0);
return result > 0;
} catch (SQLException e) {
e.printStackTrace();
logError("更新用户信息失败", e);
return false;
}
}
/**
* 更新密码
*/
public boolean updatePassword(String username, String newPassword) {
String sql = "UPDATE users SET password = ? WHERE username = ?";
try (Connection conn = DBUtil.getConnection()) {
int result = runner.update(conn, sql, newPassword, username);
logOperation("更新密码", username, result > 0);
return result > 0;
} catch (SQLException e) {
e.printStackTrace();
logError("更新密码失败", e);
return false;
}
}
/**
* 获取所有用户
*/
public List<User> getAllUsers() {
String sql = "SELECT * FROM users ORDER BY create_time DESC";
try (Connection conn = DBUtil.getConnection()) {
return runner.query(conn, sql,
new BeanListHandler<>(User.class, processor));
} catch (SQLException e) {
e.printStackTrace();
logError("获取用户列表失败", e);
return new ArrayList<>();
}
}
/**
* 记录操作日志
*/
private void logOperation(String operation, String detail, boolean success) {
String sql = "INSERT INTO system_logs (log_level, message) VALUES (?, ?)";
String level = success ? "INFO" : "WARN";
String message = operation + " - " + detail + " - " + (success ? "成功" : "失败");
try (Connection conn = DBUtil.getConnection()) {
runner.update(conn, sql, level, message);
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 记录错误日志
*/
private void logError(String message, Exception e) {
String sql = "INSERT INTO system_logs (log_level, message) VALUES ('ERROR', ?)";
String fullMessage = message + ": " + e.getMessage();
try (Connection conn = DBUtil.getConnection()) {
runner.update(conn, sql, fullMessage);
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}
五、难点总结与体会
数据库连接池的配置、数据库的设计以及数据的迁移。在最开始数据总是丢失,经过反复修改调试才成功,当然还有修改原始代码中读取文件数据的代码也耗费了大量的时间,这让我意识到原始系统采用文件存储,虽然简单易实现,但扩展性差。这次经历让我学到,好的架构设计不能只看当下,更要为未来程序的扩展考虑。




浙公网安备 33010602011771号