目录
一、项目背景
二、项目功能
1. 登录功能:
2. 列表页面:
3. 详情页面:
4. 写博客:
三、技术实现
四、功能页面展示
1. 用户登录
2. 博客列表页
3. 博客编辑更新页
4.博客发表页
5. 博客详情页
五.系统亮点
1.强制要求登陆
2.令牌技术
网站: 博客登陆页
账号: zhangsan 密码: 123456 或者 账号: lisi 密码: 123456
(欢迎大家登录测试,如果无法响应,请联系我重新部署,谢谢!)
一、项目背景
1. 星辰博客系统采用前后端分离的方法来实现,同时使用了数据库来存储相关的数据,同时将其部署到云服务器上。前端主要有四个页面构成:登录页、列表页、详情页以及编辑页,以上模拟实现了最简单的个人博客系统。其结合后端实现了以下的主要功能:登录、编辑博客、注销、删除博客、以及强制登录等功能。
2. 但是该项目没有设计用户注册功能,只能提前在数据库中存储用户信息后经过校验登录;并且用户头像不能自己设定,在进行前端页面的书写过程中已经将头像的图片写为静态了;而用户信息中的文章数以及分类数也没有在后端中具体实现,直接在前端页面中写为了静态的。
3. 该博客系统可以实现个人用户简单的博客记录,时间、标题、内容以及发布者等都可以进行详细地查看。
二、项目功能
该博客系统主要实现了以下几个功能:登录、注销、写博客以及删除博客等功能。
1. 登录功能:
用户名以及密码已经在后端写入了数据库,没有实现账户注册功能,即:用户名以及密码是已经存在的。登录成功后就会跳转到列表页面。在右上角存在主页和写博客两个按钮,但是在未登录情况下按下均只会跳转到登录页面。
2. 列表页面:
可以在列表页查看有限数量的博客简介,其包括博客标题、发布时间以及内容概要。在左侧可以看到登录的用户以及文章数、分类数等的模块。在右上角有主页、写博客和注销三个功能:主页即列表页,写博客即博客编辑页,注销即注销用户,回到登录页面。
3. 详情页面:
在列表页面点击“查看全文”按钮就会跳转到详情页,此时就可以看到该篇博客的完整内容。在右上角同样有主页、写博客、删除和注销四个功能:删除即删除该篇博客,删除之后就会跳转到列表页面,该篇博客就被成功删除。
4. 写博客:
在登录之后的任意界面点击“写博客”之后就会进入博客编辑页面,此时就可以进行博客的编写,点击“发布文章”后就可以成功发布文章,此时就会跳转到列表页。
三、技术实现
星辰博客系统采用Spring Boot作为核心框架,同时集成了SpringMVC、MyBatis等相关技术组件,构建了一个功能完备、性能优异的博客系统。具体技术实现如下:
- 基于Spring Boot框架构建Web应用程序,实现快速开发和部署。
- 采用MyBatis作为数据访问层框架,实现对数据库的CRUD操作。
- 使用Thymeleaf作为视图模板引擎,生成动态的HTML页面。
- 利用Markdown解析器实现博客文章的富文本编辑和展示。
- 采用Maven进行项目管理和构建,实现依赖管理和自动化构建部署。
四、功能页面展示
1. 用户登录
2. 博客列表页
3. 博客编辑更新页
4.博客发表页
5. 博客详情页
五.系统亮点
1.强制要求登陆
当⽤⼾访问博客列表⻚和博客详情⻚时,如果⽤⼾当前尚未登陆,就⾃动跳转到登陆⻚⾯. 采⽤拦截器来完成,token通常由前端放在header中,我们从header中获取token,并校验 token是否合法
添加拦截器
@Slf4j @Component public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1. 从header中获取token//2. 校验token//3. 成功, 放行String userToken = request.getHeader(Constant.USER_TOKEN_HEADER);log.info("获得token, token:"+userToken);boolean result = JwtUtils.checkToken(userToken);if (result){return true;}else {response.setStatus(401);return false;}} }
@Configuration public class WebConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/**/*.html","/pic/**","/js/**","/css/**","/blog-editormd/**","/user/login"); //这里要排除登录页面} }
1. 前端请求时,header中统⼀添加token,写在common.js中
$(document).ajaxSend(function (e, xhr, opt) { var user_token = localStorage.getItem("user_token"); xhr.setRequestHeader("user_token", user_token); });
效果展示
强制要求登陆展示
2.令牌技术
在我们实现登陆的时候
传统思路:
• 登陆⻚⾯把⽤⼾名密码提交给服务器.
• 服务器端验证⽤⼾名密码是否正确,并返回校验结果给后端
• 如果密码正确,则在服务器端创建Session.通过Cookie把sessionId返回给浏览器.
有可能出现问题: 集群环境下无法直接使用Session.
如下场景:
1. 用户登录
用户登录请求,经过负载均衡,把请求转给了第⼀台服务器,第⼀台服务器进⾏账号密码 验证,验证成功后,把Session存在了第⼀台服务器上
2. 查询操作
用户登录成功之后,携带Cookie(里面有SessionId)继续执⾏查询操作,比如查询博客列表.此时请求转发到了第⼆台机器,第⼆台机器会先进⾏权限验证操作(通过SessionId验证⽤⼾是否 登录),此时第⼆台机器上没有该⽤⼾的Session,就会出现问题,提示用户登录,这是我们不能忍受的
问题出现的原因:
我们开发的项⽬,在企业中很少会部署在⼀台机器上,容易发⽣单点故障.(单点故障:⼀旦这台服务器挂 了,整个应⽤都没法访问了).所以通常情况下,⼀个Web应⽤会部署在多个服务器上,通过Nginx等进⾏ 负载均衡.此时,来⾃⼀个⽤⼾的请求就会被分发到不同的服务器上.
令牌技术可以解决如上场景:
1. 用户登录 用户登录请求,经过负载均衡,把请求转给了第⼀台服务器,第⼀台服务器进⾏账号密码 验证,验证成功后,⽣成⼀个令牌,并返回给客⼾端.
2. 客⼾端收到令牌之后,把令牌存储起来.可以存储在Cookie中,也可以存储在其他的存储空间.
3. 查询操作 用户登录成功之后,携带令牌继续执⾏查询操作,⽐如查询博客列表.此时请求转发到了 第⼆台机器,第⼆台机器会先进⾏权限验证操作.服务器验证令牌是否有效,如果有效,就说明用户已 经执行了登录操作,如果令牌是⽆效的,就说明用户之前未执⾏登录操作.
下面是JWT令牌⽣成和校验的部分源码:
1. 引⼊JWT令牌的依赖
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.5</version><scope>runtime</scope></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred --><version>0.11.5</version><scope>runtime</scope></dependency>
public static String genToken(Map<String, Object> claim){return Jwts.builder().setClaims(claim).setExpiration(new Date(System.currentTimeMillis()+EXPIRATION_DATE)).signWith(key).compact(); }//生成keypublic static Claims parseToken(String token) {JwtParser build = Jwts.parserBuilder().setSigningKey(key).build();Claims body = null;try {body = build.parseClaimsJws(token).getBody();}catch (ExpiredJwtException e){log.error("token过期,校验失败,token:",token);} catch (Exception e) {log.error("token校验失败,token:",token);}return body; } public static boolean checkToken(String token) {Claims body = parseToken(token);if (body == null){return false;}return true; }