利用国庆期间做了一个基于springboot+vue的前后端分离的个人博客网站,今天在这里将开发过程和大家分享一下,手把手教你搭建一个自己专属的个人博客。
一、个人博客网站项目整体思路整个项目的设计是前后端分离的,后端使用的是SpringBoot+MybatisPlus设计,前端使用Vue+ElementUI搭建页面。安全验证等 *** 作由shiro安全框架完成,在进行前后端数据交互的时候采用路由传输,同时在前后端解决了跨域问题。博客实现登录功能,在未登录的情况下只能访问博客主页,在登录的状态下可以实现博客的发布与编辑功能。
整个博客主页的博客采用时间线的方式布局,先发布的文章会在最前面展示;博客编辑功能同时支持Markdown编辑器编辑。具体的功能实现小伙伴们继续往下看!
二、Java后端接口开发 (1)数据库设计在数据库设计上主要就是两个表,一个用户信息表和一个博客信息表,
博客信息表中的数据ID会和用户ID相对应。详细的表结构如下:
(2)整合MybatisPlus平常我们使用的都是mybatis来做数据库 *** 作,MybatisPlus是在Mybatis的基础上兴起的,我个人的理解是它在Mybatis和逆向工程的结合,可以直接读取我们的数据库,并且自动的生成*Mapper.xml、Dao、Service中的代码,提高我们的开发效率。
整合MybatisPlus的步骤如下:
第一步,导入所需jar包在这里我们需要导入MybatisPlus所依赖的jar包,同时因为MybatisPlus需要涉及到代码的自动生成,所以还需要引入freemarker的页面模版引擎。
第二步、写入配置文件com.baomidou mybatis-plus-boot-starter3.2.0 org.springframework.boot spring-boot-starter-freemarkermysql mysql-connector-java5.1.37 runtime com.baomidou mybatis-plus-generator3.2.0
因为我们需要连接数据库嘛,所以当然需要用到数据库连接驱动,同时还需要在配置文件中进行配置,指定好我们的数据库驱动、用户名、密码、数据库名称这些。
同时还需要指定好MybatisPlus扫描的xml文件,
第三步、开启mapper接口扫描,添加分页插件在这里需要实现一个分页插件PaginationInterceptor,使用该分页插件的目的很简单,就是让我们在每次查询到的结果以分页的形式展示出来。该插件是写在MybatisPlusConfig类下的,
**同时还有一点需要注意的是,**在添加该配置文件的时候我们需要在类上增加@MapperScan("")注解,在其中传入我们想要将接口写入到的包名,该接口的目的就是执行想要变成实现类的接口所在的包,如@MapperScan("com.gyg.mapper")
@Configuration @EnableTransactionManagement @MapperScan("com.gyg.mapper") //指定变成实现类的接口所在的包 public class MybatisPlusConfig { @Bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); return paginationInterceptor; } }第四步、生成相关代码
想要通过mybatisplus生成代码,官方是给了我们一个工具类的,通过该工具类,我们可以写入自己的参数,然后就可以自动的生成相关的代码了。
工具类名叫:CodeGenerator ,使用时我们需要将其和springboot的启动类放置在同级目录下。启动运行之后,输入我们想要生成对应代码的表名即可。
工具类的代码比较长,我放置在了gitee上,
运行这个代码生成器我们就可以自动的生成相关数据表的mapper、dao、service等内容了!
现在数据库相关的代码已经是基本完成了,
(3)统一结果封装由于我们的数据都是需要通过json串的形式返回给我们的前端页面的,所以我们就需要对返回的结果进行一个统一的封装。在这里我们可以自定义一个封装类Result,方便我们将数据以统一的格式返回出去。
该封装类中一般需要返回的信息有三个:
- 状态码code(如200表示 *** 作正确,400表示异常)
- 结果消息msg
- 结果数据data
同时在封装类中定义全局方法,用于在不同的状态下返回不同的数据。封装类的代码如下:
import lombok.Data; import java.io.Serializable; @Data public class Result implements Serializable { private int code; //200正常、非200异常 private String msg; //提示信息 private Object data; //返回数据 public static Result success(Object data) { return success(200," *** 作成功",data); } public static Result success(int code, String msg, Object data) { Result r = new Result(); r.setCode(code); r.setMsg(msg); r.setData(data); return r; } public static Result fail(String msg) { return fail(400,msg,null); } public static Result fail(String msg, Object data) { return fail(400,msg,data); } public static Result fail(int code, String msg, Object data) { Result r = new Result(); r.setCode(code); r.setMsg(msg); r.setData(data); return r; } }(4)整合shiro+jwt实现安全验证
在进行安全验证的时候我采用的是shiro+jwt结合的方式,大概验证思路是这样的:
前端将登陆信息传送过来之后,通过shiro的Realm进行安全验证,如果验证不通过,那么直接将错误信息返回到前端。如果登录信息验证通过,就将用户信息存储到服务器端,然后通过jwtUtils工具类根据用户的ID生成一个token,并且将该token放入返回请求的请求头中,携带给浏览器,浏览器在接收到服务器的返回的请求的时候,就会解析并获取到该token,并将该token存储到本地;
这样在浏览器每次向服务器发送请求的时候都会从本地携带上该token,服务器也会对每次浏览器发送的请求进行验证,验证浏览器返回的token和服务器端保存的token是否相同。如果相同就放行进行处理;如果不相同就将错误信息返回到浏览器。
附上一个请求过程的图示:
安全验证所用到的类有:
- ShiroConfig:用于配置shiro的验证信息
- AccountRealm:用于对浏览器返回的登录信息进行验证
- JwtToken:封装和获取token中的数据
- AccountProfile:登录之后返回的用户信息的一个载体
- JwtFilter:jwt过滤器,用于过滤浏览器的请求
无论我们平常在进行什么样的项目开发,进行全局异常处理都是一个非常好的习惯,进行全局异常处理,它可以将我们的错误信息用最简单的方式表示出来,并不会出现大量的报错信息。方便我们查阅,在这里我声明了几个在项目中经常会遇到的报错信息。
@Slf4j @RestControllerAdvice public class GlobalExceptionHandler { @ResponseStatus(HttpStatus.BAD_REQUEST) //判断返回消息是否正常 @ExceptionHandler(value = RuntimeException.class) public Result handler(RuntimeException e){ log.error("运行时异常---------->>>" + e); return Result.fail(e.getMessage()); } @ResponseStatus(HttpStatus.UNAUTHORIZED) //判断返回消息是否正常,没有权限异常 @ExceptionHandler(value = ShiroException.class) public Result handler(ShiroException e){ log.error("shiro异常---------->>>" + e); return Result.fail(401,e.getMessage(),null); } @ResponseStatus(HttpStatus.BAD_REQUEST) //判断返回消息是否正常,没有权限异常 @ExceptionHandler(value = MethodArgumentNotValidException.class) public Result handler(MethodArgumentNotValidException e){ log.error("实体检验异常异常---------->>>" + e); BindingResult bindingResult = e.getBindingResult(); ObjectError objectError = bindingResult.getAllErrors().stream().findFirst().get(); return Result.fail(objectError.getDefaultMessage()); } @ResponseStatus(HttpStatus.BAD_REQUEST) //判断返回消息是否正常,没有权限异常 @ExceptionHandler(value = IllegalArgumentException.class) public Result handler(IllegalArgumentException e){ log.error("断言异常异常---------->>>" + e); return Result.fail(e.getMessage()); } }(6)实体校验
在表单数据提交的时候,我们通常会对数据进行校验,比如不能为空,或长度不能小于指定值等,在前端我们可以通过js插件来完成,但是如果在后端的话,我们可以通过使用Hibernate validatior的方式来进行校验。
在springboot中已经自动集成了Hibernate validatior的校验,我们只需要在代码中直接使用就可以了。
所以我们只需要在实体的属性上添加相应的校验规则就可以了,比如在user实例类中:
@Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @TableName("m_user") public class User implements Serializable { private static final long serialVersionUID = 1L; @TableId(value = "id", type = IdType.AUTO) private Long id; @NotBlank(message = "用户名不能为空") private String username; private String avatar; @NotBlank(message = "邮箱不能为空") @Email(message = "邮箱格式不正确") private String email; private String password; private Integer status; private LocalDateTime created; private LocalDateTime lastLogin; }(7)跨域问题
由于我们做的是前后端分离的项目,所以在请求发送上一定会出现同源策略的相关问题,这就需要我们解决跨域问题了,
在springboot的后端解决跨域问题的策略比较简单,只需要添加一个类CorsConfig,并且让它实现WebMvcConfigurer接口, 其中代码如下,一般在开发的时候直接将代码复制过去就可以了。
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(" @RequiresAuthentication @GetMapping("/logout") public Result logout() { Subject subject = SecurityUtils.getSubject(); // AccountProfile profile = (AccountProfile) subject.getPrincipal(); // System.out.println(profile.getId()); // 会请求到logout subject.logout(); return Result.success("退出成功"); } @RequiresAuthentication @GetMapping("/testlogin") public Result testlogin() { User user = userService.getById(1L); return Result.success(user); } }(9)博客接口开发
博客接口中主要实现的功能有:返回主页信息,返回指定博客信息,编辑和发布博客、删除博客的功能,其中编辑和删除博客只有在登录状态下才能请求成功,其他两个请求无需进行登录。
代码如下:
@RestController //@RequestMapping("/blog") public class BlogController { @Autowired BlogService blogService; @GetMapping("/blogs") public Result list(@RequestParam(defaultValue = "1") Integer currentPage) { Page page = new Page(currentPage, 5); AccountProfile accountProfile = (AccountProfile) SecurityUtils.getSubject().getPrincipal(); System.out.println(accountProfile); IPagepageDate = blogService.page(page, new QueryWrapper ().orderByDesc("created")); return Result.success(pageDate); } @GetMapping("/blog/{id}") public Result detail(@PathVariable(name = "id") long id) { Blog blog = blogService.getById(id); // 用断言来来判断文章是否找不到 Assert.notNull(blog, "该博客已经被删除!"); // 返回该博客数据 return Result.success(blog); } // 只有登录之后才能编辑 @RequiresAuthentication @PostMapping("/blog/edit") public Result edit(@Validated @RequestBody Blog blog) { System.out.println("编辑测试11111111111111111"); System.out.println(blog.toString()); System.out.println("当前用户ID:" + ShiroUtil.getProfile().getId()); System.out.println(blog.toString()); // System.out.println("当前用户id:" + ShiroUtil.getSubjectID()); Blog temp = null; // 如果博客id不为空,就是编辑 if (blog.getId() != null) { temp = blogService.getById(blog.getId()); // 每一个用户只能编辑自己的文章 Assert.isTrue(temp.getUserId().equals(ShiroUtil.getProfile().getId()), "你没有权限编辑"); } else { // 如果id为空,就是添加 temp = new Blog(); // 将这篇文章添加给当前用户的id temp.setUserId(ShiroUtil.getProfile().getId()); // 博客创建时间 temp.setCreated(LocalDateTime.now()); temp.setStatus(0); } // 将两个对象进行复制,指定那些字段不复制 //BeanUtil.copyProperties("转换前的类","转换后的类"); BeanUtil.copyProperties(blog, temp, "id", "userId", "created", "status"); //保存或者更新这一篇文章 blogService.saveOrUpdate(temp); return Result.success(" *** 作成功"); } @RequiresAuthentication @PostMapping("/blog/delete/{id}") public Result deleteBlog(@PathVariable("id") long id){ System.out.println(id); System.out.println("------------"); // int bid = Integer.parseInt(id); boolean isRemove = blogService.removeById(id); if (!isRemove){ return Result.fail("删除失败!"); } return Result.success("删除成功!"); } }
以上就是我们后台接口开发的全部过程,在开发完成之后需要进行相关的接口测试,测试完成无误之后就可以进行前台页面的开发了。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)