文章目录
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 (可以设置一个外键,这里没设置)
createdatabaseifnotexists MyBlogSystem;use MyBlogSystem;droptableifexists blog;-- 创建一个博客表createtable blog (
blogId intprimarykeyauto_increment,
title varchar(1024),
content mediumtext,
postTime datetime,
userId int);droptableifexistsuser;-- 创建一个用户信息表createtableuser(
userId intprimarykeyauto_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"?><!DOCTYPEmapperPUBLIC"-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="com.example.demo.mapper.UserMapper"><!-- 注册一个用户 --><insertid="addUser"keyProperty="userId"keyColumn="userId">
insert into user(username password) values (#{username}, #{password})
</insert><!-- 通过用户名查找用户信息 --><selectid="selectByName"resultType="com.example.demo.model.User">
select * from user where username = #{username}
</select><!-- 通过用户Id查找用户信息 --><selectid="selectById"resultType="com.example.demo.model.User">
select * from user where userId = #{userId}
</select></mapper>
BlogMapper.xml
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPEmapperPUBLIC"-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="com.example.demo.mapper.BlogMapper"><!-- 查找当前所有的文章 --><selectid="getAllBlog"resultType="com.example.demo.model.Blog">
select * from blog
</select><!-- 通过博客id查找所有的文章 --><selectid="getBlogByBid"resultType="com.example.demo.model.Blog">
select * from blog where blogId = #{blogId}
</select><!-- 发布一篇博客 --><insertid="postBlog"keyColumn="userId"keyProperty="userId">
insert into blog(title,content,postTime,userId) values(#{title},#{content},#{postTime},#{userId})
</insert><!-- 删除一篇博客 --><deleteid="deleteBlog">
delete from blog where blogId = #{blogId}
</delete><!-- 更新一篇博客 --><updateid="updateBlog">
update blog set content = #{content},title = #{title} where blogId = #{blogId}
</update><!-- 根据当前用户Id获取所有的博客 --><selectid="getAllBlogById"resultType="com.example.demo.model.Blog">
select * from blog where userId = #{userId}
</select></mapper>
User 实体类 和 Blog 实体类
在 model 包下 创建 User 类 和 Blog 类
User 类
@DatapublicclassUser{publicint userId;publicString username;publicString password;}
Blog 类
@DatapublicclassBlog{publicint blogId;publicString title;publicString content;publicTimestamp postTime;publicint userId;}
UserMapper 接口 和 BlogMapper 接口
在 mapper 包下创建 UserMapper 和 BlogMapper 接口
UserMapper
@MapperpublicinterfaceUserMapper{voidaddUser(User user);UserselectByName(String username);UserselectById(Integer userId);}
BlogMapper
@MapperpublicinterfaceBlogMapper{List<Blog>getAllBlog();BloggetBlogByBid(Integer blogId);voidpostBlog(Blog blog);voiddeleteBlog(Integer blogId);voidupdateBlog(Blog blog);List<Blog>getAllBlogById();}
UserService 类 和 BlogService 类
service
包下创建 UserService类 和 BlogService类
UserService
@ServicepublicclassUserService{@ResourceprivateUserMapper userMapper;publicvoidaddUser(User user){
userMapper.addUser(user);}publicUserselectByName(String username){return userMapper.selectByName(username);}publicUserselectById(Integer userId){return userMapper.selectById(userId);}}
BlogService
@ServicepublicclassBlogService{@ResourceprivateBlogMapper blogMapper;publicList<Blog>getAllBlog(){return blogMapper.getAllBlog();}publicBloggetBlogByBid(Integer blogId){return blogMapper.getBlogByBid(blogId);}publicvoidpostBlog(Blog blog){
blogMapper.postBlog(blog);}publicvoiddeleteBlog(Integer blogId){
blogMapper.deleteBlog(blogId);}publicvoidupdateBlog(Blog blog){
blogMapper.updateBlog(blog);}publicList<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 实现后端代码
@RestControllerpublicclassIndexController{@AutowiredprivateBlogService blogService;@RequestMapping("/index")publicList<Blog>getAllBlog(){return blogService.getAllBlog();}}
7.2 实现前端代码
$.ajax({url:"index",method:"GET",success:function(data,status){buildBlogs(data);}})functionbuildBlogs(blogs){let rightDiv = document.querySelector('.right');for(let blog of blogs){let blogDiv = document.createElement('div');
blogDiv.className ='article';// 创建 titlelet h2 = document.createElement('h2');
h2.className ='title';
h2.innerHTML = blog.title;
blogDiv.appendChild(h2);// 创建 postTimelet postTime = document.createElement('span');
postTime.className ='date';
postTime.innerHTML =DateFormat(blog.postTime);
blogDiv.appendChild(postTime);// 创建 contentlet 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);}}// 把毫秒级时间戳转化成格式化日期functionDateFormat(timeStampMS){var date =newDate(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不为空就表示异常.
@RestControllerpublicclassDetailsController{@AutowiredprivateBlogService blogService;@RequestMapping("/details")publicObjectShowBlog(Integer blogId){HashMap<String,Object> map =newHashMap<>();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");}}})functionbuildBlog(blog){// 1. 更新 titlelet titleDiv = document.querySelector('.title');
titleDiv.innerHTML = blog.title;// 2. 更新 postTimelet postTime = document.querySelector('.date');
postTime.innerHTML =DateFormat(blog.postTime);
editormd.markdownToHTML('content',{markdown: blog.content});}// 把毫秒级时间戳转化成格式化日期functionDateFormat(timeStampMS){var date =newDate(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
@RestControllerpublicclassLoginController{@AutowiredprivateUserService userService;@RequestMapping("/login")publicObjectuserLogin(@RequestBodyUser user,HttpServletRequest request){HashMap<String,Object> map =newHashMap<>();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 实现自定义拦截器
publicclassLoginInterceptorimplementsHandlerInterceptor{@OverridepublicbooleanpreHandle(HttpServletRequest request,HttpServletResponse response,Object handler)throwsException{HttpSession session = request.getSession(false);if(session !=null&& session.getAttribute("user")!=null){returntrue;}
response.setStatus(401);
response.sendRedirect("/login.html");returnfalse;}}
10.2 将自定义拦截器加入到系统配置
@ConfigurationpublicclassAppConfigimplementsWebMvcConfigurer{@OverridepublicvoidaddInterceptors(InterceptorRegistry registry){
registry.addInterceptor(newLoginInterceptor()).addPathPatterns("/**").excludePathPatterns("/**/*.js").excludePathPatterns("/**/*.jpg").excludePathPatterns("/**/*.css").excludePathPatterns("/**/*.png").excludePathPatterns("/**/login.html").excludePathPatterns("/**/register.html").excludePathPatterns("/**/login").excludePathPatterns("/**/register");}}
11. 实现注册功能
这里的交互是
交互9
11.1 实现后端代码
实现一个 类 Register 来接收前端返回来的数据
classRegister{publicString username;publicString password1;publicString password2;}
这里后端需要判断当前用户名是否已经被使用.
@RestControllerpublicclassRegisterController{@AutowiredprivateUserService userService;@RequestMapping("/register")publicObjectuserRegister(@RequestBodyRegister register){HashMap<String,Object> map =newHashMap<>();User user = userService.selectByName(register.username);if(user !=null){
map.put("message","当前用户名已经存在了, 请更换!");return map;}User user1 =newUser();
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, 然后发送一个请求.
这里只需要实现后端代码既可
@ControllerpublicclassLogoutController{@RequestMapping("/logout")publicvoiduserLogout(HttpServletRequest request,HttpServletResponse response)throwsIOException{HttpSession session = request.getSession(false);// 拦截器的拦截, 所以不可能出现session为空的情况
session.removeAttribute("user");
response.sendRedirect("login.html");}}
13. 实现博客编辑页
这里的交互是
交互1
13.1 实现后端代码
@RestControllerpublicclassEditController{@AutowiredprivateBlogService blogService;@RequestMapping("/edit")publicvoidpostBlog(@RequestBodyBlog blog,@SessionAttribute(value ="user",required =false)User user){
blog.setPostTime(newTimestamp(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 实现后端代码
@RestControllerpublicclassPersonController{@AutowiredprivateBlogService blogService;@RequestMapping("/person")publicList<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);}})functionbuildBlogs(blogs){let rightDiv = document.querySelector('.right');for(let blog of blogs){let blogDiv = document.createElement('div');
blogDiv.className ='article';// 创建 titlelet h2 = document.createElement('h2');
h2.className ='title';
h2.innerHTML = blog.title;
blogDiv.appendChild(h2);// 创建 postTimelet postTime = document.createElement('span');
postTime.className ='date';
postTime.innerHTML =DateFormat(blog.postTime);
blogDiv.appendChild(postTime);// 创建 contentlet 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);}}// 把毫秒级时间戳转化成格式化日期functionDateFormat(timeStampMS){var date =newDate(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丢失的情况以及,文章作者丢失情况(数据库表数据被删除的时候会出现这种错误)
@RestControllerpublicclassUserController{@AutowiredprivateUserService userService;@AutowiredprivateBlogService blogService;@RequestMapping("/user")publicObjectgetUser(Integer blogId,@SessionAttribute(value ="user",required =false)User user){if(blogId ==null){return user;}else{HashMap<String,Object> map =newHashMap<>();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 实现后端代码
@ControllerpublicclassDeleteController{@AutowiredprivateBlogService blogService;@RequestMapping("/delete")publicObjectdeleteBlog(Integer blogId){
blogService.deleteBlog(blogId);return"/home.html";}}
17. 实现博客的修改功能
这里的交互是
交互3
和
交互4
交互3是在新的页面进行加载
17.1 实现后端代码
@RestControllerpublicclassUpdateController{@AutowiredprivateBlogService blogService;@RequestMapping("/updateLoad")publicObjectupdateLoad(Integer blogId){HashMap<String,Object> map =newHashMap<>();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")publicObjectUpdate(Integer blogId,@RequestBodyBlog blog,@SessionAttribute(value ="user",required =false)User user){HashMap<String,Object> map =newHashMap<>();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');}}})}
版权归原作者 独一无二的哈密瓜 所有, 如有侵权,请联系我们删除。