文章目录
1. 项目设计
前端使用 HTML+CSS+JavaScript+JQuery
后端使用 Spring MVC+Spring Boot+MyBatis
2. 效果展示
3. 创建项目并配置文件
1.1 创建 Spring 项目
1.2 配置文件
application.properties 配置内容
- spring.profiles.active=dev
复制代码 application-dev.properties 配置内容
- spring.datasource.url=jdbc:mysql://localhost:3306/MyBlogSystem?characterEncoding=utf8&useSSL=true
- spring.datasource.username=root
- spring.datasource.password=0000
- spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
- mybatis.mapper-locations=classpath:mapper/**Mapper.xml
复制代码 4. 数据库实现用户和博客管理
4.1 设计数据库
这里博客系统, 是一个用户表和博客表,
用户一般分为:
- 用户Id (每个人一个且互不相同)
- 用户名 (每个人的用户名不相同)
- 密码
- 用户头像(暂不实现)
- 用户马云地址(暂不实现)
博客一般分为:
- 博客Id (每篇博客一个且互不相同)
- 标题
- 正文
- 创建时间
- 修改时间 (这里没用到, 自己想加也可以)
- 用户Id (可以设置一个外键,这里没设置)
- create database if not exists MyBlogSystem;
- use MyBlogSystem;
- drop table if exists blog;
- -- 创建一个博客表
- create table blog (
- blogId int primary key auto_increment,
- title varchar(1024),
- content mediumtext,
- postTime datetime,
- userId int
- );
- drop table if exists user;
- -- 创建一个用户信息表
- create table user (
- userId int primary key auto_increment,
- username varchar(128) unique,
- password varchar(128)
- );
复制代码 4.2 使用 MyBatis 操作数据库
在 resources 下创建一个 mapper 包. 包下创建 UserMapper.xml 和
BlogMapper.xml
- BlogMapper.xml 里是对博客表的数据库操作
- UserMapper.xml 里是对用户表的数据库操作
UserMapper.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
- <mapper namespace="com.example.demo.mapper.UserMapper">
-
- <insert id="addUser" keyProperty="userId" keyColumn="userId">
- insert into user(username password) values (#{username}, #{password})
- </insert>
-
- <select id="selectByName" resultType="com.example.demo.model.User">
- select * from user where username = #{username}
- </select>
-
- <select id="selectById" resultType="com.example.demo.model.User">
- select * from user where userId = #{userId}
- </select>
- </mapper>
复制代码 BlogMapper.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
- <mapper namespace="com.example.demo.mapper.BlogMapper">
-
- <select id="getAllBlog" resultType="com.example.demo.model.Blog">
- select * from blog
- </select>
-
- <select id="getBlogByBid" resultType="com.example.demo.model.Blog">
- select * from blog where blogId = #{blogId}
- </select>
-
- <insert id="postBlog" keyColumn="userId" keyProperty="userId">
- insert into blog(title,content,postTime,userId) values(#{title},#{content},#{postTime},#{userId})
- </insert>
-
- <delete id="deleteBlog">
- delete from blog where blogId = #{blogId}
- </delete>
-
- <update id="updateBlog">
- update blog set content = #{content},title = #{title} where blogId = #{blogId}
- </update>
-
- <select id="getAllBlogById" resultType="com.example.demo.model.Blog">
- select * from blog where userId = #{userId}
- </select>
- </mapper>
复制代码 User 实体类 和 Blog 实体类
在 model 包下 创建 User 类 和 Blog 类
User 类
- @Data
- public class User {
- public int userId;
- public String username;
- public String password;
- }
复制代码 Blog 类
- @Data
- public class Blog {
- public int blogId;
- public String title;
- public String content;
- public Timestamp postTime;
- public int userId;
- }
复制代码 UserMapper 接口 和 BlogMapper 接口
在 mapper 包下创建 UserMapper 和 BlogMapper 接口
UserMapper
- @Mapper
- public interface UserMapper {
- void addUser(User user);
- User selectByName(String username);
- User selectById(Integer userId);
- }
复制代码 BlogMapper
- @Mapper
- public interface BlogMapper {
- List<Blog> getAllBlog();
-
- Blog getBlogByBid(Integer blogId);
-
- void postBlog(Blog blog);
-
- void deleteBlog(Integer blogId);
-
- void updateBlog(Blog blog);
-
- List<Blog> getAllBlogById();
- }
复制代码 UserService 类 和 BlogService 类
service 包下创建 UserService类 和 BlogService类
UserService
- @Service
- public class UserService {
- @Resource
- private UserMapper userMapper;
- public void addUser(User user){
- userMapper.addUser(user);
- }
- public User selectByName(String username){
- return userMapper.selectByName(username);
- }
- public User selectById(Integer userId){
- return userMapper.selectById(userId);
- }
- }
复制代码 BlogService
- @Service
- public class BlogService {
- @Resource
- private BlogMapper blogMapper;
- public List<Blog> getAllBlog(){
- return blogMapper.getAllBlog();
- }
- public Blog getBlogByBid(Integer blogId){
- return blogMapper.getBlogByBid(blogId);
- }
- public void postBlog(Blog blog) {
- blogMapper.postBlog(blog);
- }
- public void deleteBlog(Integer blogId){
- blogMapper.deleteBlog(blogId);
- }
- public void updateBlog(Blog blog){
- blogMapper.updateBlog(blog);
- }
- public List<Blog> getAllBlogById(){
- return blogMapper.getAllBlogById();
- }
- }
复制代码 5. 前后端交互接口设计
交互1
交互2
交互3
交互4
交互5
交互6
交互7
交互8
交互9
交互10
交互11
6. 导入前端代码
导入前端代码到 resources 的 static下
具体代码查看 文章 博客系统前端界面
https://wangzhi430.blog.csdn.net/article/details/124649884
7. 实现博客主页
这里的交互接口是 交互6
7.1 实现后端代码
- @RestController
- public class IndexController {
- @Autowired
- private BlogService blogService;
- @RequestMapping("/index")
- public List<Blog> getAllBlog() {
- return blogService.getAllBlog();
- }
- }
复制代码 7.2 实现前端代码
- $.ajax({
- url: "index",
- method: "GET",
- success: function(data,status) {
- buildBlogs(data);
- }
- })
-
- function buildBlogs(blogs){
- let rightDiv = document.querySelector('.right');
- for(let blog of blogs){
- let blogDiv = document.createElement('div');
- blogDiv.className = 'article';
- // 创建 title
- let h2 = document.createElement('h2');
- h2.className = 'title';
- h2.innerHTML = blog.title;
- blogDiv.appendChild(h2);
- // 创建 postTime
- let postTime = document.createElement('span');
- postTime.className = 'date';
- postTime.innerHTML = DateFormat(blog.postTime);
- blogDiv.appendChild(postTime);
- // 创建 content
- let content = document.createElement('div');
- content.className = 'desc';
- content.innerHTML = blog.content;
- blogDiv.appendChild(content);
- // 创建 详情页的超链接
- let detailA = document.createElement('a');
- detailA.className = 'more';
- detailA.href = 'art.html?blogId=' + blog.blogId;
- detailA.innerHTML = '查看全文>>';
- blogDiv.appendChild(detailA);
- // 加入到 right 中
- rightDiv.appendChild(blogDiv);
- }
- }
- // 把毫秒级时间戳转化成格式化日期
- function DateFormat(timeStampMS) {
- var date = new Date(timeStampMS);
-
- var year = date.getFullYear(),
- month = date.getMonth()+1,//月份是从0开始的
- day = date.getDate(),
- hour = date.getHours(),
- min = date.getMinutes(),
- sec = date.getSeconds();
- var newTime = year + '-' +
- (month < 10? '0' + month : month) + '-' +
- (day < 10? '0' + day : day) + ' ' +
- (hour < 10? '0' + hour : hour) + ':' +
- (min < 10? '0' + min : min) + ':' +
- (sec < 10? '0' + sec : sec);
-
- return newTime;
- }
复制代码 7.3 测试代码
7.4 解决页面内容太多超出当前浏览器
7.5 解决页面顺序不是按最新时间排序
在BlogMapper.xml中修改当前sql语句.
7.6 解决内容太多, 导致显示的时候占位太多.
7.7 再次测试代码
8. 实现博客详情页
这里的交互是 交互5
8.1 实现后端代码
根据当前blogId来获取文章, 要判断blogId是否合法, 是否存在当前blogId的文章
这里使用, message来判断, 返回的message不为空就表示异常.
- @RestController
- public class DetailsController {
- @Autowired
- private BlogService blogService;
- @RequestMapping("/details")
- public Object ShowBlog(Integer blogId) {
- HashMap<String,Object> map = new HashMap<>();
- if (blogId == null || blogId < 1) {
- map.put("message","blogId异常!");
- return map;
- }
- Blog blog = blogService.getBlogByBid(blogId);
- if (blog == null) {
- map.put("message","不存在当前blogId的文章");
- return map;
- }
- return blog;
- }
- }
复制代码 8.2 实现前端代码
- $.ajax({
- url: "details"+location.search,
- method: "GET",
- success: function(data,status) {
- if(data.message == null) {
- buildBlog(data);
- } else {
- alert(data.message);
- location.assign("home.html");
- }
- }
- })
- function buildBlog(blog){
- // 1. 更新 title
- let titleDiv = document.querySelector('.title');
- titleDiv.innerHTML = blog.title;
- // 2. 更新 postTime
- let postTime = document.querySelector('.date');
- postTime.innerHTML = DateFormat(blog.postTime);
- editormd.markdownToHTML('content', {markdown: blog.content});
- }
- // 把毫秒级时间戳转化成格式化日期
- function DateFormat(timeStampMS) {
- var date = new Date(timeStampMS);
- var year = date.getFullYear(),
- month = date.getMonth()+1,//月份是从0开始的
- day = date.getDate(),
- hour = date.getHours(),
- min = date.getMinutes(),
- sec = date.getSeconds();
- var newTime = year + '-' +
- (month < 10? '0' + month : month) + '-' +
- (day < 10? '0' + day : day) + ' ' +
- (hour < 10? '0' + hour : hour) + ':' +
- (min < 10? '0' + min : min) + ':' +
- (sec < 10? '0' + sec : sec);
- return newTime;
- }
复制代码 8.3 测试代码
8.4 这里展示为markdown语法的正文
展示为 markdown 语法的正文.
注意这里的几段代码
这里还需要导入依赖包
9. 实现博客登录界面
这里的交互是 交互8
9.1 实现后端代码
这里要根据前端穿过来的 json 格式数据进行判断
判断当前是否存在用户, 以及当前用户密码是否正确
登录成功之后, 要创建一个session
- @RestController
- public class LoginController {
- @Autowired
- private UserService userService;
- @RequestMapping("/login")
- public Object userLogin(@RequestBody User user, HttpServletRequest request) {
- HashMap<String,Object> map = new HashMap<>();
- if (user == null) {
- map.put("message","当前还没有输入用户名和密码,登录失败!");
- return map;
- }
- User user1 = userService.selectByName(user.getUsername());
- if (user1 == null) {
- map.put("message","当前用户名不存在!");
- return map;
- }
- if (!user.getPassword().equals(user1.getPassword())) {
- map.put("message","当前用户名密码错误!");
- return map;
- }
- user.setUserId(user1.getUserId());
- HttpSession session = request.getSession(true);
- if (session != null) {
- session.setAttribute("user",user);
- }
- return map;
- }
- }
复制代码 9.2 实现前端代码
这里前端去除了前后空格,以及为空的情况
- let submit = document.querySelector('.button');
- submit.onclick = function() {
- let username = document.querySelector('.user');
- let password = document.querySelector('.password');
-
- if (username.value.trim() == ""){
- alert('请先输入用户名!');
- username.focus();
- return;
- }
- if (password.value.trim() == ""){
- alert('请先输入密码!');
- password.focus();
- return;
- }
- $.ajax({
- url: "login",
- method: "POST",
- data: JSON.stringify({username: username.value.trim(), password: password.value.trim()}),
- contentType: "application/json;charset=utf-8",
- success: function(data, status) {
- if(data.message == null) {
- location.assign("home.html");
- }else{
- alert(data.message);
- username.value="";
- password.value="";
- username.focus();
- }
- }
- })
- }
复制代码 10. 实现登录判断 — 拦截器
10.1 实现自定义拦截器
- public class LoginInterceptor implements HandlerInterceptor {
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
- HttpSession session = request.getSession(false);
- if (session != null && session.getAttribute("user") != null) {
- return true;
- }
- response.setStatus(401);
- response.sendRedirect("/login.html");
- return false;
- }
- }
复制代码 10.2 将自定义拦截器加入到系统配置
- @Configuration
- public class AppConfig implements WebMvcConfigurer {
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- registry.addInterceptor(new LoginInterceptor())
- .addPathPatterns("/**")
- .excludePathPatterns("/**/*.js")
- .excludePathPatterns("/**/*.jpg")
- .excludePathPatterns("/**/*.css")
- .excludePathPatterns("/**/*.png")
- .excludePathPatterns("/**/login.html")
- .excludePathPatterns("/**/register.html")
- .excludePathPatterns("/**/login")
- .excludePathPatterns("/**/register");
- }
- }
复制代码 11. 实现注册功能
这里的交互是 交互9
11.1 实现后端代码
实现一个 类 Register 来接收前端返回来的数据
- class Register {
- public String username;
- public String password1;
- public String password2;
- }
复制代码 这里后端需要判断当前用户名是否已经被使用.
- @RestController
- public class RegisterController {
- @Autowired
- private UserService userService;
- @RequestMapping("/register")
- public Object userRegister(@RequestBody Register register) {
- HashMap<String,Object> map = new HashMap<>();
- User user = userService.selectByName(register.username);
- if (user != null) {
- map.put("message","当前用户名已经存在了, 请更换!");
- return map;
- }
- User user1 = new User();
- user1.setUsername(register.username);
- user1.setPassword(register.password1);
- userService.addUser(user1);
- return map;
- }
- }
复制代码 11.2 实现前端代码
要对输入内容去除前后空格,并且判空
- let submit = document.querySelector('.button');
- submit.onclick = function() {
- let username = document.querySelector('.user');
- let password1 = document.querySelector('.password1');
- let password2 = document.querySelector('.password2');
- if(username.value.trim() == ""){
- alert("请先输入用户名!");
- username.focus();
- return;
- }
- if(password1.value.trim() == ""){
- alert('请先输入密码!');
- password1.focus();
- return;
- }
- if(password2.value.trim() == ""){
- alert('请再次输入密码!');
- password2.focus();
- return;
- }
- if(password1.value.trim() != password2.value.trim()) {
- alert('两次输入的密码不同!');
- passwrod1.value="";
- password2.value="";
- return;
- }
- $.ajax({
- url: "register",
- method: "POST",
- data: JSON.stringify({username: username.value.trim(), password1: password1.value.trim(),password2: password2.value.trim()}),
- contentType: "application/json;charset=utf-8",
- success: function(data,status){
- if(data.message != null){
- alert(data.message);
- username.value="";
- password1.value="";
- password2.value="";
- username.focus();
- }else{
- location.assign('login.html');
- }
- }
- })
- }
复制代码 12. 实现注销功能
这里的交互是 交互10
12.1 实现后端代码
因为 注销功能是点击注销的时候, 触发一个logout的url, 然后发送一个请求.
这里只需要实现后端代码既可
- @Controller
- public class LogoutController {
- @RequestMapping("/logout")
- public void userLogout(HttpServletRequest request, HttpServletResponse response) throws IOException {
- HttpSession session = request.getSession(false);
- // 拦截器的拦截, 所以不可能出现session为空的情况
- session.removeAttribute("user");
- response.sendRedirect("login.html");
- }
- }
复制代码 13. 实现博客编辑页
这里的交互是 交互1
13.1 实现后端代码
- @RestController
- public class EditController {
- @Autowired
- private BlogService blogService;
- @RequestMapping("/edit")
- public void postBlog(@RequestBody Blog blog, @SessionAttribute(value = "user",required = false)User user){
- blog.setPostTime(new Timestamp(System.currentTimeMillis()));
- blog.setUserId(user.getUserId());
- blogService.postBlog(blog);
- }
- }
复制代码 13.2 实现前端代码
- let submit = document.querySelector('.publish');
- submit.onclick = function() {
- let title = document.querySelector('.title');
- let content = document.querySelector('.content');
- if(title.value.trim() == ""){
- alert('当前文章标题为空,请输入!');
- title.focus();
- return;
- }
- if(content.value.trim() == ""){
- alert('当前文章内容为空,请输入!');
- content.focus();
- return;
- }
- $.ajax({
- url: "edit",
- method: "POST",
- data: JSON.stringify({title: title.value.trim(), content: content.value.trim()}),
- contentType: "application/json;charset=utf-8",
- success: function(data,status) {
- location.assign('home.html');
- }
- })
- }
复制代码 14. 实现博客个人主页
这里的交互是 交互7
这里的前端页面主要就是主页页面的改进
14.1 实现后端代码
- @RestController
- public class PersonController {
- @Autowired
- private BlogService blogService;
- @RequestMapping("/person")
- public List<Blog> getMyBlog(@SessionAttribute(value = "user",required = false)User user) {
- List<Blog> blogs = blogService.getAllBlogById(user.getUserId());
- for (Blog blog : blogs) {
- if (blog.getContent().length() > 80) {
- blog.setContent(blog.getContent().substring(0,80) + " ...");
- }
- }
- return blogs;
- }
- }
复制代码 14.2 实现前端代码
- $.ajax({
- url: "person",
- method: "GET",
- success: function(data,status) {
- buildBlogs(data);
- }
- })
- function buildBlogs(blogs){
- let rightDiv = document.querySelector('.right');
- for(let blog of blogs){
- let blogDiv = document.createElement('div');
- blogDiv.className = 'article';
- // 创建 title
- let h2 = document.createElement('h2');
- h2.className = 'title';
- h2.innerHTML = blog.title;
- blogDiv.appendChild(h2);
- // 创建 postTime
- let postTime = document.createElement('span');
- postTime.className = 'date';
- postTime.innerHTML = DateFormat(blog.postTime);
- blogDiv.appendChild(postTime);
- // 创建 content
- let content = document.createElement('div');
- content.className = 'desc';
- content.innerHTML = blog.content;
- blogDiv.appendChild(content);
- // 创建 详情页的超链接
- let detailA = document.createElement('a');
- detailA.className = 'more';
- detailA.href = 'art.html?blogId=' + blog.blogId;
- detailA.innerHTML = '查看全文>>';
- blogDiv.appendChild(detailA);
- // 加入到 right 中
- rightDiv.appendChild(blogDiv);
- }
- }
- // 把毫秒级时间戳转化成格式化日期
- function DateFormat(timeStampMS) {
- var date = new Date(timeStampMS);
-
- var year = date.getFullYear(),
- month = date.getMonth()+1,//月份是从0开始的
- day = date.getDate(),
- hour = date.getHours(),
- min = date.getMinutes(),
- sec = date.getSeconds();
- var newTime = year + '-' +
- (month < 10? '0' + month : month) + '-' +
- (day < 10? '0' + day : day) + ' ' +
- (hour < 10? '0' + hour : hour) + ':' +
- (min < 10? '0' + min : min) + ':' +
- (sec < 10? '0' + sec : sec);
-
- return newTime;
- }
复制代码 15. 实现展示用户信息的功能
这里的交互是 交互11
这里需要分情况考虑, 展示个人信息主要是 主页页面, 详情页面, 个人主页页面.
以带不带blogId来区分
15.1 实现后端代码
这里判断了 blogId丢失的情况以及,文章作者丢失情况(数据库表数据被删除的时候会出现这种错误)
- @RestController
- public class UserController {
- @Autowired
- private UserService userService;
- @Autowired
- private BlogService blogService;
- @RequestMapping("/user")
- public Object getUser(Integer blogId, @SessionAttribute(value = "user",required = false)User user){
- if(blogId == null) {
- return user;
- }else {
- HashMap<String,Object> map = new HashMap<>();
- Blog blog = blogService.getBlogByBid(blogId);
- if(blog == null) {
- map.put("message","不存在当前blogId的文章");
- return map;
- }
- User author = userService.selectById(blog.getUserId());
- if(author == null){
- map.put("message","当前文章作者出错");
- return map;
- }
- return author;
- }
- }
- }
复制代码 15.2 实现前端代码
详情页的情况:
- $.ajax({
- url: "user"+location.search,
- method: "GET",
- success: function(data,status) {
- if(data.message == null){
- let username = document.querySelector('.name');
- username.innerHTML = data.username;
- }else{
- alert(data.message);
- location.assign('home.html');
- }
- }
- })
复制代码 个人主页和主页的情况
- $.ajax({
- url: "user",
- method: "GET",
- success: function(data,status){
- let username = document.querySelector('.name');
- username.innerHTML = data.username;
- }
- })
复制代码 16. 实现博客的删除功能
这里需要用到 交互2
这里在详情页的时候进行构建, 在Blog实体类中加一项 isAuthor, 为1的时候就是当前文章就是作者.
前端接收到这个的时候, 进行判断, 如果为1就显示删除的按钮.
16.1 改进代码
16.2 实现后端代码
- @Controller
- public class DeleteController {
- @Autowired
- private BlogService blogService;
- @RequestMapping("/delete")
- public Object deleteBlog(Integer blogId) {
- blogService.deleteBlog(blogId);
- return "/home.html";
- }
- }
复制代码 17. 实现博客的修改功能
这里的交互是 交互3 和 交互4
交互3是在新的页面进行加载
17.1 实现后端代码
- @RestController
- public class UpdateController {
- @Autowired
- private BlogService blogService;
- @RequestMapping("/updateLoad")
- public Object updateLoad(Integer blogId){
- HashMap<String, Object> map = new HashMap<>();
- if(blogId == null) {
- map.put("message","blogId丢失!");
- return map;
- }
- Blog blog = blogService.getBlogByBid(blogId);
- if(blog == null) {
- map.put("blog","不存在当前blog的文章!");
- return map;
- }
- return blog;
- }
- @RequestMapping("/update")
- public Object Update(Integer blogId, @RequestBody Blog blog, @SessionAttribute(value = "user",required = false)User user) {
- HashMap<String, Object> map = new HashMap<>();
- if(blogId == null) {
- map.put("message","blogId丢失!");
- return map;
- }
- blog.setBlogId(blogId);
- blog.setUserId(user.getUserId());
- blogService.updateBlog(blog);
- return map;
- }
- }
复制代码 17.2 实现前端代码
- $.ajax({
- url: "updateLoad"+location.search,
- method: "GET",
- success: function(data,status) {
- if(data.message == null) {
- let title = document.querySelector('.title');
- title.value=data.title;
- let content = document.querySelector('.content');
- content.value=data.content;
- }else{
- alert(data.message);
- location.assign('home.html');
- }
- }
- })
- // 初始化编辑器
- var editor = editormd("editor", {
- // 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉.
- width: "100%",
- // 高度 100% 意思是和父元素一样高. 要在父元素的基础上去掉标题编辑区的高度
- height: "calc(100% - 60px)",
- // 指定 editor.md 依赖的插件路径
- path: "editor.md/lib/",
- // 放到 textarea中
- saveHTMLToTextArea: true
- });
- let submit = document.querySelector('.publish');
- submit.onclick = function() {
- let title = document.querySelector('.title');
- let content = document.querySelector('.content');
- if(title.value.trim() == ""){
- alert('当前文章标题为空,请输入!');
- title.focus();
- return;
- }
- if(content.value.trim() == ""){
- alert('当前文章内容为空,请输入!');
- content.focus();
- return;
- }
- $.ajax({
- url: "update"+location.search,
- method: "POST",
- data: JSON.stringify({title: title.value.trim(), content: content.value.trim()}),
- contentType: "application/json;charset=utf-8",
- success: function(data,status) {
- if(data.message == null){
- location.assign('home.html');
- }else{
- alert(data.message);
- location.assign('home.html');
- }
- }
- })
- }
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |