java入门笔记六(springboot框架博客系统开发3)
继续完善这个博客的后端部分。
1 全局异常处理。新建com.blog.common.exception.GlobalExceptionHandler类。添加异常处理
package com.blog.common.exception; import com.blog.common.lang.Result; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.ShiroException; import org.springframework.http.HttpStatus; import org.springframework.validation.BindingResult; import org.springframework.validation.ObjectError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import java.io.IOException; /** * 全局异常处理 */ @Slf4j @RestControllerAdvice //定义全局控制器异常处理, public class GlobalExceptionHandler { // 处理shiro的异常 @ResponseStatus(HttpStatus.UNAUTHORIZED) @ExceptionHandler(ShiroException.class) public Result handle401(ShiroException e){ return Result.fail(401,e.getMessage(),null); } //处理Assert的异常 @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(value = IllegalArgumentException.class) //参数格式化异常 public Result handler(IllegalArgumentException e) throws IOException{ log.error("Assert异常:-------------->{}",e.getMessage()); return Result.fail(e.getMessage()); } //校验错误异常处理@validated @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(value = MethodArgumentNotValidException.class) public Result handler(MethodArgumentNotValidException e) throws IOException{ 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 = RuntimeException.class) public Result handler(RuntimeException e) throws IOException{ log.error("运行时异常:-------------->",e); return Result.fail(e.getMessage()); } }
2 添加数据实体处理校验,以user类为例,修改com.blog.entity.User类
package com.blog.entity; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import java.time.LocalDateTime; import java.io.Serializable; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; import javax.validation.constraints.Email; import javax.validation.constraints.NotBlank; /** * <p> * * </p> * * @author 关注公众号:MarkerHub * @since 2021-02-15 */ @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; }
在com.blog.controller.UserController类中加一个测试方法
/** * 测试实体校验 * @param user * @return */ @PostMapping("/testsave") public Object testUser(@Validated @RequestBody User user) { return user.toString(); }
使用postman测试一下,提示类校验错误
3 后台加上全局处理跨域代码,新增com.blog.config.CorsConfig类
package com.blog.config; 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("/**") .allowedOrigins("*") .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS") .allowCredentials(true) .maxAge(3600) .allowedHeaders("*"); } }
4 登录的接口,新增一个LoginDto类,新增com.blog.common.dto.LoginDto类
package com.blog.common.dto; import lombok.Data; import javax.validation.constraints.NotBlank; import java.io.Serializable; @Data public class LoginDto implements Serializable { @NotBlank(message = "昵称不能为空") public String username; @NotBlank(message = "密码不能为空") private String password; }
新增登录接口类com.blog.controller.AccountController类
package com.blog.controller; import cn.hutool.core.map.MapUtil; import cn.hutool.crypto.SecureUtil; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.blog.common.dto.LoginDto; import com.blog.common.lang.Result; import com.blog.entity.User; import com.blog.service.UserService; import com.blog.utils.JwtUtils; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authz.annotation.RequiresAuthentication; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.Assert; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletResponse; @RestController public class AccountController { @Autowired JwtUtils jwtUtils; @Autowired UserService userService; @CrossOrigin @PostMapping("/login") public Result login(@Validated @RequestBody LoginDto loginDto, HttpServletResponse response) { User user = userService.getOne(new QueryWrapper<User>().eq("username", loginDto.getUsername())); Assert.notNull(user, "用户不存在"); if (!user.getPassword().equals(SecureUtil.md5(loginDto.getPassword()))) { return Result.fail("密码错误!"); } String jwt = jwtUtils.generateToken(user.getId()); response.setHeader("Authorization", jwt); response.setHeader("Access-Control-Expose-Headers", "Authorization"); return Result.succ(MapUtil.builder() .put("id", user.getId()) .put("username", user.getUsername()) .put("avatar", user.getAvatar()) .put("email", user.getEmail()) .map() ); } // 退出 @GetMapping("/logout") @RequiresAuthentication public Result logout() { SecurityUtils.getSubject().logout(); return Result.succ(null); } }
然后登录测试
错误1:登录接口报错java.lang.ClassNotFoundException: javax.xml.bind.DatatypeConverter
解决方法,去pom.xml添加依赖
<dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.0</version> </dependency>
maven更新依赖然后继续测试能够正常生成jwt
此时jwt已经生成,但是尚未存储到redis中
当header带着jwt访问其它接口时,如果接口验证权限注解@RequiresAuthentication
会走JwtFilter的onAccessDenied方法,将jwt写入redis
5 继续博客列表接口开发,新增BlogController
package com.blog.controller; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.lang.Assert; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.blog.common.lang.Result; import com.blog.entity.Blog; import com.blog.service.BlogService; import com.blog.utils.JwtUtils; import com.blog.utils.ShiroUtils; import com.fasterxml.jackson.annotation.JsonTypeInfo; import org.apache.shiro.authz.annotation.RequiresAuthentication; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.time.LocalDateTime; /** * <p> * 前端控制器 * </p> * * @author 关注公众号:MarkerHub * @since 2021-02-15 */ @RestController @RequestMapping("/blog") public class BlogController { @Autowired BlogService blogService; //查询博客列表 @GetMapping("/blogs") public Result blogs(Integer currentPage){ if(currentPage == null ||currentPage <1) currentPage=1; Page page=new Page(currentPage,5); IPage pagedata=blogService.page(page,new QueryWrapper<Blog>().orderByDesc("created")); return Result.succ(pagedata); } //获取博客详情 @GetMapping("/blog/{id}") public Result detail(@PathVariable(name="id") long id){ Blog blog=blogService.getById(id); Assert.notNull(blog,"不存在"); return Result.succ(blog); } //编辑或者新增 @RequiresAuthentication @PostMapping("blog/edit") public Result edit(@Validated @RequestBody Blog blog){ Blog b=null; if(blog.getId()!=null){ b=blogService.getById(blog.getId()); Assert.isTrue(b.getUserId()== ShiroUtils.getProfile().getId(),"没有权限"); }else { b=new Blog(); b.setUserId(ShiroUtils.getProfile().getId()); b.setCreated(LocalDateTime.now()); b.setStatus(0); } BeanUtil.copyProperties(blog,b,"id","userId","created","status"); blogService.saveOrUpdate(b); return Result.succ("成功"); } }
后台的代码基本上完成了,剩下前端的部分就不详细描写了,因为前端已经比较熟练了
前端和后端的代码一起放到了github上地址是:
下一篇写一下部署的过程。