在controller层需要调用到service层的代码,做相关业务的处理。
需要统一返回相同的响应格式,不管在前端还是后端,我们在项目中需要统一返回响应的格式。
我们可以定义一个统一返回响应格式的类,这个类前后台都可以用得到,我们定义在公共模块中

统一响应类,响应给前端。
package com.jierlung.domain;import com.fasterxml.jackson.annotation.JsonInclude;
import com.jierlung.enums.AppHttpCodeEnum;import java.io.Serializable;@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseResult implements Serializable {private Integer code;private String msg;private T data;public ResponseResult() {this.code = AppHttpCodeEnum.SUCCESS.getCode();this.msg = AppHttpCodeEnum.SUCCESS.getMsg();}public ResponseResult(Integer code, T data) {this.code = code;this.data = data;}public ResponseResult(Integer code, String msg, T data) {this.code = code;this.msg = msg;this.data = data;}public ResponseResult(Integer code, String msg) {this.code = code;this.msg = msg;}public static ResponseResult errorResult(int code, String msg) {ResponseResult result = new ResponseResult();return result.error(code, msg);}public static ResponseResult okResult() {ResponseResult result = new ResponseResult();return result;}public static ResponseResult okResult(int code, String msg) {ResponseResult result = new ResponseResult();return result.ok(code, null, msg);}public static ResponseResult okResult(Object data) {ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS, AppHttpCodeEnum.SUCCESS.getMsg());if(data!=null) {result.setData(data);}return result;}public static ResponseResult errorResult(AppHttpCodeEnum enums){return setAppHttpCodeEnum(enums,enums.getMsg());}public static ResponseResult errorResult(AppHttpCodeEnum enums, String msg){return setAppHttpCodeEnum(enums,msg);}public static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums){return okResult(enums.getCode(),enums.getMsg());}private static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums, String msg){return okResult(enums.getCode(),msg);}public ResponseResult> error(Integer code, String msg) {this.code = code;this.msg = msg;return this;}public ResponseResult> ok(Integer code, T data) {this.code = code;this.data = data;return this;}public ResponseResult> ok(Integer code, T data, String msg) {this.code = code;this.data = data;this.msg = msg;return this;}public ResponseResult> ok(T data) {this.data = data;return this;}public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}public T getData() {return data;}public void setData(T data) {this.data = data;}
}

我们在Service接口中去继承IService方法

在ServiceImpl写具体的业务逻辑。

我们在Mapper接口中去继承Mybatis-plus的BaseMapper的里面的方法。

java开发规范手册解释

其实Vo就是返回给前端展示的数据,比如说我现在表里面查询出来10个字段,而需要前端展示的字段只有5个,难道全部返回给前端吗,显然不合理。
我们需要定义一个Vo对应表的实体类,类中就是展示给前端的字段数据。
例如:
实体类Article(文章表)
package com.jierlung.domain.entity;import java.util.Date;
import java.io.Serializable;import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;/*** 文章表(Article)表实体类** @author makejava* @since 2022-10-10 15:27:26*/
@SuppressWarnings("serial")
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("sg_article")
@Accessors(chain = true)
public class Article {@TableIdprivate Long id;//标题private String title;@TableField(exist = false)//分类的标题private String categoryName;//文章内容private String content;//文章摘要private String summary;//所属分类idprivate Long categoryId;//缩略图private String thumbnail;//是否置顶(0否,1是)private String isTop;//状态(0已发布,1草稿)private String status;//访问量private Long viewCount;//是否允许评论 1是,0否private String isComment;private Long createBy;private Date createTime;private Long updateBy;private Date updateTime;//删除标志(0代表未删除,1代表已删除)private Integer delFlag;}
而根据展示的内容我们可以去掉不必要的字段创建对应的Vo:articleListVo
package com.jierlung.domain.vo;import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.Date;@Data
@AllArgsConstructor
@NoArgsConstructor
public class articleListVo {private Long id;//标题private String title;//文章摘要private String summary;//所属分类idprivate String categoryName;//缩略图private String thumbnail;//访问量private Long viewCount;private Date createTime;}
然后可以通过Bean拷贝的形式对Vo进行赋值,比如我查询出来一个List集合的article,然后通过拷贝的形式,去给Vo赋值,然后将Vo对象返回给前端。
后面的接口实现中,不在重复对Vo的编写,读者可以根据返还给前端的数据展示相应的字段,编写Vo

可以根据对应表自动生成对应的实体类和mappe、Service、ServiceImpl、Controller。
可以设置一个类,表示字面量,比如说文章的状态是0,查询的条件如果直接写0,不太方便理解,写字面量的形式更加方便。

ApiPost7或者postman都可以,操作过程差不多。
例如:查询热门文章列表


热门文章接口肯定是前台展示的数据,应该在blog的前台模块中

前端代码的接口路径:

后端:
@RestController
@RequestMapping("/article")
public class ArticleController {@AutowiredArticleService articleService;@GetMapping("/articleList")public ResponseResult articleList(Integer pageNum,Integer pageSize,Long categoryId){return articleService.articleList(pageNum,pageSize,categoryId);}
}

可以看到需要返回一个code和msg,和一个data

需求:
文章表

分析:
public ResponseResult hotArticleList() {//查询热门文章,封装成ResponseResult返回LambdaQueryWrapper queryWrapper=new LambdaQueryWrapper();//必须是正式文章queryWrapper.eq(Article::getStatus, SystemConstants.ARTICLE_STATUS_NORMAL);//按照浏览量进行排序queryWrapper.orderByDesc(Article::getViewCount);//最多只查询10条Page page = new Page<>(1, 10);page(page, queryWrapper);List articles = page.getRecords();//bean拷贝ArrayList articleVos = new ArrayList<>();for (Article article:articles){HotArticleVo vo = new HotArticleVo();BeanUtils.copyProperties(article,vo);articleVos.add(vo);}return ResponseResult.okResult(articleVos);}
步骤解析:
第一步:构建article的查询对象,封装一系列的查询条件,例如:降序,必须是正式文章…
第二步:构建分页查询对象,只展示前十条数据
第三步:得到查询后并且经过条件过滤后的的分页对象,将其转化为list集合
第四步:封装vo(因为返回给前端的数据没有那么多,其他的数据没必要返回)
因为:


前端只需要这些数据,我们可以定义vo的方式去对应返回给前端的字段,定义一定要从本来实体类出发,简而言之就是去掉不需要的字段。
第五步:通过bean拷贝的方式去从article实体类去拷贝到articleVo实体类上,最后面统一封装返回。
第六步测试:

总结一下:首先看对应的涉及到的表,例如这是对应这article表,看需要什么字段,你就给我返回什么字段,在编写相应的逻辑就可以了。
文章分类的接口肯定是前台展示的数据,应该在blog的前台模块中
前端返回的接口路径

后端:
@RestController
@RequestMapping("/category")
public class CategoryController {@Autowiredprivate CategoryService categoryService;@GetMapping("/getCategoryList")public ResponseResult getCategoryList(){return categoryService.getCategoryList();}
}


需求:
数据表:

分析:
CategoryServiceImpl
@Service
public class CategoryServiceImpl extends ServiceImpl implements CategoryService {@Autowiredprivate ArticleService articleService;@Overridepublic ResponseResult getCategoryList() {//查询文章表,状态为已发布LambdaQueryWrapper articleWrapper = new LambdaQueryWrapper();articleWrapper.eq(Article::getStatus, SystemConstants.ARTICLE_STATUS_NORMAL);List articleList = articleService.list(articleWrapper);//获取文章分类的id,并且去重Set categoryIds = articleList.stream().map(article -> article.getCategoryId()).collect(Collectors.toSet());//查询分列表List categories = listByIds(categoryIds);categories = categories.stream().filter(category -> SystemConstants.STATUS_NORMAL.equals(category.getStatus())).collect(Collectors.toList());//封装VoList categoryVoList = BeanCopyUtils.copyBeanList(categories, CategoryVo.class);return ResponseResult.okResult(categoryVoList);}
}
步骤分析:
第一步:我拿到正常的article集合,然后通过stream流的形式,我得到一个文章的id的list

第二步:调用mybatis-plus的自带的listByIds方法得到category的集合,通过stream流的方式去过滤一些条件,最后面封装vo,拷贝到vo返回即可。
第三步:测试

也就是说方法中可以进行单个对象和集合对象的拷贝
public class BeanCopyUtils {private BeanCopyUtils() {}public static V copyBean(Object source,Class clazz) {//创建目标对象V result = null;try {result = clazz.newInstance();//实现属性copyBeanUtils.copyProperties(source, result);} catch (Exception e) {e.printStackTrace();}//返回结果return result;}public static List copyBeanList(List list,Class clazz){return list.stream().map(o -> copyBean(o, clazz)).collect(Collectors.toList());}
}
文章列表接口肯定是前台展示的数据,应该在blog的前台模块中。

参数解释:


分析:在这我需要查询到article的信息,而json数据里面有categoryName,我需要从category表中去查到名字去赋值到article表中,而关联信息就是传入的categoryId。
也就是说:我查到的文章列表的数据,我通过比较如果我的categoryId的aritlce表和category表,如果相同的话就是一篇文章,我就在article实体类赋值我的categoryName
首先分析:

在article实体类上添加字段:

public ResponseResult articleList(Integer pageNum, Integer pageSize, Long categoryId) {//查询条件LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();// 如果有categoryId 就要 查询时和传入的相同lambdaQueryWrapper.eq(Objects.nonNull(categoryId)&&categoryId>0, Article::getCategoryId,categoryId);//状态是正式发布的lambdaQueryWrapper.eq(Article::getStatus, SystemConstants.ARTICLE_STATUS_NORMAL);//对isTop进行排序lambdaQueryWrapper.orderByDesc(Article::getIsTop);//分页查询Page page = new Page<>(pageNum, pageSize);page(page, lambdaQueryWrapper);//查询categoryName方式一/* List articles = page.getRecords();for (Article article : articles) {Category category = categoryService.getById(article.getCategoryId());article.setCategoryName(category.getName());}*///查询categoryName方式二List articles = page.getRecords();articles = articles.stream().map(article ->article.setCategoryName(categoryService.getById(article.getCategoryId()).getName())).collect(Collectors.toList());//封装查询结果List articleListVos = BeanCopyUtils.copyBeanList(page.getRecords(), Article.class);PageVo pageVo = new PageVo(articleListVos,page.getTotal());return ResponseResult.okResult(pageVo);}
点击阅读全文即可跳转到文章详情界面


参数说明:
需要文章的id

public ResponseResult getArticleDetail(Long id) {//根据id查询文章Article article = getById(id);//转换成VOArticleDetailVo articleDetailVo = BeanCopyUtils.copyBean(article, ArticleDetailVo.class);//根据分类id去查询分类名Long categoryId = articleDetailVo.getCategoryId();Category category = categoryService.getById(categoryId);if(category != null){articleDetailVo.setCategoryName(category.getName());}//封装相应返回return ResponseResult.okResult(articleDetailVo);}
步骤分析:
第一步:首先根据文章id得到相应的文章
第二步:转化为vo对象,根据分类的id得到category
第三步:判断如果不为空,设置vo的categoryName,封装返回
引入依赖,创建配置类,统一处理日期。
@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {// 设置允许跨域的路径registry.addMapping("/**")// 设置允许跨域请求的域名.allowedOriginPatterns("*")// 是否允许cookie.allowCredentials(true)// 设置允许的请求方式.allowedMethods("GET", "POST", "DELETE", "PUT")// 设置允许的header属性.allowedHeaders("*")// 跨域允许时间.maxAge(3600);}@Bean//使用@Bean注入fastJsonHttpMessageConvertpublic HttpMessageConverter fastJsonHttpMessageConverters() {//1.需要定义一个Convert转换消息的对象FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();FastJsonConfig fastJsonConfig = new FastJsonConfig();fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");SerializeConfig.globalInstance.put(Long.class, ToStringSerializer.instance);fastJsonConfig.setSerializeConfig(SerializeConfig.globalInstance);fastConverter.setFastJsonConfig(fastJsonConfig);HttpMessageConverter> converter = fastConverter;return converter;}@Overridepublic void configureMessageConverters(List> converters) {converters.add(fastJsonHttpMessageConverters());}}


对应的数据表

返回的数据格式


@Service("linkService")
public class LinkServiceImpl extends ServiceImpl implements LinkService {@Overridepublic ResponseResult getAllLink() {//查询所有审核通过的LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(Link::getStatus, SystemConstants.LINK_STATUS_NORMAL);List links = list(queryWrapper);//转换成voList linkVos = BeanCopyUtils.copyBeanList(links, LinkVo.class);//封装返回return ResponseResult.okResult(linkVos);}
}
步骤分析:
第一步:因为查询的字段刚好数据库表相对应,构建查询对象。
第二步:封装Vo返回即可。

对应的数据表

返回的数据格式
{"code": 200,"data": {"token": "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxZjhmYWVjM2U3YzI0YTE4OGUyMjQyMzNjMDNlMmQyZiIsInN1YiI6IjEiLCJpc3MiOiJzZyIsImlhdCI6MTY2ODQ2ODQwOSwiZXhwIjoxNjY4NDcyMDA5fQ.gbmWqXLKv0LoRI5yv5qEy8jQwdyay1tDLzQbmvAU1Mk","userInfoVo": {"avatar": "https://11111","email": "22222@qq.com","id": "1","nickName": "sg888","sex": "1"}},"msg": "操作成功"
}


UserDetailServiceImpl
@Service
public class UserDetailServiceImpl implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//根据用户名查询用户信息LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();lambdaQueryWrapper.eq(User::getUserName, username);User user = userMapper.selectOne(lambdaQueryWrapper);if(Objects.isNull(user)){throw new RuntimeException("用户不存在!");}//返回用户信息//TODO 查询权限信息封装return new LoginUser(user);}
}
自定义登录的实现类,去实现UserDetailsService的接口里面的loadUserByUsername方法,再次登录的时候,Spring Security会自动调用覆写的方法,方法里面自定义从数据库查询用户的相关信息,返回一个UserDetails的对象。
LoginUser
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser implements UserDetails {private User user;@Overridepublic Collection extends GrantedAuthority> getAuthorities() {return null;}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUserName();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

SecurityConfig
我们一般使用SpringSecurity为我们提供的BCryptPasswordEncoder。
我们只需要使用把BCryptPasswordEncoder对象注入Spring容器中,SpringSecurity就会使用该PasswordEncoder来进行密码校验。
我们可以定义一个SpringSecurity的配置类,SpringSecurity要求这个配置类要继承WebSecurityConfigurerAdapter。
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}}
BlogLoginServiceImpl
package com.jierlung.service.Impl;import com.jierlung.domain.ResponseResult;
import com.jierlung.domain.entity.LoginUser;
import com.jierlung.domain.entity.User;
import com.jierlung.domain.vo.BlogUserLoginVo;
import com.jierlung.domain.vo.UserInfoVo;
import com.jierlung.service.BlogLoginService;
import com.jierlung.utils.BeanCopyUtils;
import com.jierlung.utils.JwtUtil;
import com.jierlung.utils.RedisCache;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;import java.util.Objects;@Service
public class BlogLoginServiceImpl implements BlogLoginService {@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate RedisCache redisCache;@Overridepublic ResponseResult login(User user) {UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());Authentication authenticate = authenticationManager.authenticate(authenticationToken);if (Objects.isNull(authenticate)) {throw new RuntimeException("用户名或密码错误");}//获取用户id,生成tokenLoginUser loginUser = (LoginUser) authenticate.getPrincipal();String userId = loginUser.getUser().getId().toString();String jwt = JwtUtil.createJWT(userId);//把用户信息存入redis当中redisCache.setCacheObject("bloglogin:" + userId, loginUser);//Bean拷贝UserInfoVo userInfoVo = BeanCopyUtils.copyBean(loginUser.getUser(), UserInfoVo.class);BlogUserLoginVo blogUserLoginVo = new BlogUserLoginVo(jwt, userInfoVo);return ResponseResult.okResult(blogUserLoginVo);}@Overridepublic ResponseResult logout() {//获取token 解析获取useridAuthentication authentication = SecurityContextHolder.getContext().getAuthentication();LoginUser loginUser = (LoginUser) authentication.getPrincipal();//获取useridLong userId = loginUser.getUser().getId();//删除redis中的用户信息redisCache.deleteObject("bloglogin:"+userId);return ResponseResult.okResult();}
}
JwtAuthenticationTokenFilter
package com.jierlung.filter;import com.alibaba.fastjson.JSON;
import com.jierlung.domain.ResponseResult;
import com.jierlung.domain.entity.LoginUser;
import com.jierlung.enums.AppHttpCodeEnum;import com.jierlung.utils.JwtUtil;
import com.jierlung.utils.RedisCache;
import com.jierlung.utils.WebUtils;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Autowiredprivate RedisCache redisCache;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, IOException {//获取tokenString token = request.getHeader("token");if (!StringUtils.hasText(token)) {//放行filterChain.doFilter(request, response);return;}//解析获取useridClaims claims=null;try {claims = JwtUtil.parseJWT(token);} catch (Exception e) {ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);WebUtils.renderString(response, JSON.toJSONString(result));return;}String userId = claims.getSubject();LoginUser loginUser = redisCache.getCacheObject("bloglogin:" + userId);//存入securityContextHolderUsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,null);SecurityContextHolder.getContext().setAuthentication(authenticationToken);//放行filterChain.doFilter(request, response);}
}
流程:首先会进入过滤器,会检查是否携带了token,如果携带就解析放行,如果没有token就会去到自定义实现类去查数据库,生成token返回,数据存入到redis中。

登录认证需要有spring security的知识,如果还没学到,请去学习相关内容,而具体流程可以看我这篇文章:Spring Security安全认证流程



相关数据表

@Service("CommentService")
public class CommentServiceImpl extends ServiceImpl implements CommentService {@Autowiredprivate UserService userService;@Overridepublic ResponseResult commentList(String commentType, Long articleId, Integer pageNum, Integer pageSize) {//查询所有文章列表//查询条件LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();//对文章id进行判断lambdaQueryWrapper.eq(SystemConstants.ARTICLE_COMMENT.equals(commentType),Comment::getArticleId, articleId);//根评论的id为-1lambdaQueryWrapper.eq(Comment::getRootId, -1);//评论类型lambdaQueryWrapper.eq(Comment::getType, commentType);//分页查询Page page = new Page<>(pageNum, pageSize);page(page, lambdaQueryWrapper);List commentVoList = toCommentVoList(page.getRecords());//查询所有根评论对应的子评论for (CommentVo commentVo : commentVoList) {//查询对应的子评论List children=getChildren(commentVo.getId());commentVo.setChilden(children);}return ResponseResult.okResult(new PageVo(commentVoList, page.getTotal()));}@Overridepublic ResponseResult addComment(Comment comment) {if(!StringUtils.hasText(comment.getContent())){throw new SystemException(AppHttpCodeEnum.CONTENT_NOT_NULL);}save(comment);return ResponseResult.okResult();}private List getChildren(Long id) {LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();lambdaQueryWrapper.eq(Comment::getRootId, id);lambdaQueryWrapper.orderByAsc(Comment::getCreateTime);List comments = list(lambdaQueryWrapper);List commentVos = toCommentVoList(comments);return commentVos;}private List toCommentVoList(List list) {List commentVos = BeanCopyUtils.copyBeanList(list, CommentVo.class);//遍历commentVofor (CommentVo commentVo : commentVos) {//查询getCreateBy,对应的用户String nickName = userService.getById(commentVo.getCreateBy()).getNickName();commentVo.setUsername(nickName);//通过对toCommentUserId查询用户的昵称并赋值//如果toCommentUserId不为-1才进行查询if(commentVo.getToCommentUserId()!=-1){String toCommentUserName = userService.getById(commentVo.getToCommentUserId()).getNickName();commentVo.setToCommentUserName(toCommentUserName);}}return commentVos;}
}
关键步骤解析:
第一步:首先我拿到所以评论的集合(暂时不考虑子评论情况),注意判断条件中,友链中也有评论,所以需要去判断类型。
第二步:拿到分页的评论的对象,转化为list集合,通过Bean拷贝的方式,将其转化为Vo对象,注意这里有三个字段是Vo中没有的,MP在赋值这里为空,我们需要手动去加上这三个字段,分别是

username字段和toCommentUserName字段我们可以在Bean拷贝时,手动赋值,因为username我们可以根据查出来的评论的createby查到该用户,在调用该用户的方法去为评论的Vo赋值。
toCommentUserName字段我们可以通过toCommentUserId不等于-1,就证明不是根评论,我们就可以查到评论该评论的评论者的用户(子评论人)然后在为Vo赋值。
第三步:我已经拿到完整的评论Vo对象,我该查询子评论,遍历评论Vo对象,编写方法,如果子评论的rootId一样的话,收集到该集合,在重复步骤二。
第四步:封装返回对象。
方法和上面一模一样,在这复用之前的方法,在方法里面加上评论的类型。




相关数据表

@Overridepublic ResponseResult userInfo() {//获取用户当前的idLong userId = SecurityUtils.getUserId();//根据用户id查询用用户信息User user = getById(userId);//封装拷贝UserInfoVo vo = BeanCopyUtils.copyBean(user, UserInfoVo.class);return ResponseResult.okResult(vo);}
步骤解析:
因为登录了,就产生了token,直接拿到token,去查询用户信息即可,最后面封装返回。
在这使用七牛云来保存图片

也就说,上传到七牛云,前端通过读取千牛云的图片,不通过Web服务器来存储。

响应数据

配置在yml文件中配置oss

@ConfigurationProperties(prefix = "oss")public class UploadServiceImpl implements UploadService {@Overridepublic ResponseResult uploadImg(MultipartFile img) {String originalFilename = img.getOriginalFilename();//判断类型if(!originalFilename.endsWith(".png")){throw new SystemException(AppHttpCodeEnum.FILE_TYPE_ERROR);}//如果判断通过上传文件到ossString filePath = PathUtils.generateFilePath(originalFilename);String url = uploadOss(img,filePath);return ResponseResult.okResult(url);}private String accessKey;private String secretKey;private String bucket;private String uploadOss(MultipartFile imgFile, String filePath){//构造一个带指定 Region 对象的配置类Configuration cfg = new Configuration(Region.autoRegion());//...其他参数参考类注释UploadManager uploadManager = new UploadManager(cfg);//...生成上传凭证,然后准备上传
// String accessKey = "your access key";
// String secretKey = "your secret key";
// String bucket = "sg-blog";//默认不指定key的情况下,以文件内容的hash值作为文件名String key = filePath;try {InputStream inputStream = imgFile.getInputStream();Auth auth = Auth.create(accessKey, secretKey);String upToken = auth.uploadToken(bucket);try {Response response = uploadManager.put(inputStream,key,upToken,null, null);//解析上传成功的结果DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);System.out.println(putRet.key);System.out.println(putRet.hash);return "http://rl4fyr0mn.hn-bkt.clouddn.com/"+key;} catch (QiniuException ex) {Response r = ex.response;System.err.println(r.toString());try {System.err.println(r.bodyString());} catch (QiniuException ex2) {//ignore}}} catch (Exception ex) {//ignore}return "www";}
}

@Override
public ResponseResult updateUserInfo(User user) {updateById(user);return ResponseResult.okResult();
}


@Overridepublic ResponseResult register(User user) {//对数据进行非空判断if(!StringUtils.hasText(user.getUserName())){throw new SystemException(AppHttpCodeEnum.USERNAME_NOT_NULL);}if(!StringUtils.hasText(user.getPassword())){throw new SystemException(AppHttpCodeEnum.PASSWORD_NOT_NULL);}if(!StringUtils.hasText(user.getEmail())){throw new SystemException(AppHttpCodeEnum.EMAIL_NOT_NULL);}if(!StringUtils.hasText(user.getNickName())){throw new SystemException(AppHttpCodeEnum.NICKNAME_NOT_NULL);}//对数据进行是否存在的判断if(userNameExist(user.getUserName())){throw new SystemException(AppHttpCodeEnum.USERNAME_EXIST);}if(nickNameExist(user.getNickName())){throw new SystemException(AppHttpCodeEnum.NICKNAME_EXIST);}//...//对密码进行加密String encodePassword = passwordEncoder.encode(user.getPassword());user.setPassword(encodePassword);//存入数据库save(user);return ResponseResult.okResult();}
相关注解
@Component 将当前类注入到Spring容器内
@Aspect :表明是一个切面类
@Before :前置通知,在方法执行之前执行
@After :后置通知,在方法执行之后执行
@AfterRuturning :返回通知,在方法返回结果之后执行
@AfterThrowing :异常通知,在方法抛出异常之后执行
@Around :环绕通知,围绕着方法执行
@Pointcut :切入点,PointCut(切入点)表达式有很多种,其中execution用于使用切面的连接点。
上面所提到的五种通知方法中,除了环绕通知外,其他的四个通知注解中,加或者不加参数JoinPoint都可以,如果有用到JoinPoint的地方就加,用不到就可以不加。
JoinPoint:里包含了类名、被切面的方法名,参数等属性。
环绕通知:参数必须为ProceedingJoinPoint,pjp.proceed相应于执行被切面的方法。
返回通知:可以加returning = “XXX”,XXX即为被切入方法的返回值,本例中是controller类中方法的返回值。
异常通知:可以加throwing = “XXX”,供读取异常信息。
返回通知和异常通知只会执行一个,如果产生异常,返回通知就不执行,后置通知一定会执行
环绕通知一般单独使用,环绕通知可以替代上面的四种通知,后面单独介绍。
相关概念
Joinpoint(连接点):所谓连接点是指那些被拦截到的点,在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点,通俗的说就是被增强类中的所有方法
PointCut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义,通俗的说就是被增强类中的被增强的方法,因为被增强类中并不是所有的方法都被代理了
Advice(通知/增强):所谓通知是指拦截到 Joinpoint (被增强的方法)之后所要做的事情就是通知,通俗的说就是对被增强的方法进行增强的代码
通知的类型:前置通知,返回通知,异常通知,后置通知,环绕通知
前置通知:在被代理方法执行之前执行
返回通知:在被代理方法执行之后执行
异常通知:在被代理方法执行出错的时候执行
后置通知:无论怎样都会执行
注意:返回通知和异常通知只能有一个会被执行,因为发生异常执行异常通知,然后就不会继续向下执行,自然后置通知也就不会被执行,反之亦然。
Aspect(切面):是切入点和通知(引介)的结合,通俗的说就是建立切入点和通知方法在创建时的对应关系
需要通过日志的接口信息,便于后期的调试排查,并可能有很多接口都需要进行日志的记录。

第一步:编写一个自定义注解

第二步:在目标的Controller类方法中添加一个方法

第三步:定义切面类

SystemLog
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SystemLog {String businessName();
}
LogAspect
@Component
@Aspect
@Slf4j
public class LogAspect {@Pointcut("@annotation(com.sangeng.annotation.SystemLog)")public void pt(){}@Around("pt()")public Object printLog(ProceedingJoinPoint joinPoint) throws Throwable {Object ret;try {handleBefore(joinPoint);ret = joinPoint.proceed();handleAfter(ret);} finally {// 结束后换行log.info("=======End=======" + System.lineSeparator());}return ret;}private void handleAfter(Object ret) {// 打印出参log.info("Response : {}", JSON.toJSONString(ret));}private void handleBefore(ProceedingJoinPoint joinPoint) {ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = requestAttributes.getRequest();//获取被增强方法上的注解对象SystemLog systemLog = getSystemLog(joinPoint);log.info("=======Start=======");// 打印请求 URLlog.info("URL : {}",request.getRequestURL());// 打印描述信息log.info("BusinessName : {}",systemLog.businessName());// 打印 Http methodlog.info("HTTP Method : {}",request.getMethod());// 打印调用 controller 的全路径以及执行方法log.info("Class Method : {}.{}",joinPoint.getSignature().getDeclaringTypeName(),((MethodSignature) joinPoint.getSignature()).getName());// 打印请求的 IPlog.info("IP : {}",request.getRemoteHost());// 打印请求入参log.info("Request Args : {}", JSON.toJSONString(joinPoint.getArgs()) );}private SystemLog getSystemLog(ProceedingJoinPoint joinPoint) {MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();SystemLog systemLog = methodSignature.getMethod().getAnnotation(SystemLog.class);return systemLog;}
}

首先在应用启动时,把mysql的博客浏览量存储到redis中
在应用启动时,需要去继承CommandLineRunner,启动时执行的代码
@Component
public class ViewCountRunner implements CommandLineRunner {@Resourceprivate ArticleMapper articleMapper;@Autowiredprivate RedisCache redisCache;@Overridepublic void run(String... args) throws Exception {//查询博客信息 id viewCountList articles = articleMapper.selectList(null);Map viewCountMap = articles.stream().collect(Collectors.toMap(article -> article.getId().toString(), article -> {return article.getViewCount().intValue();//}));//存储到redis中redisCache.setCacheMap("article:viewCount",viewCountMap);}
}
定义接口,点击浏览文章时,该文章的浏览器+1


添加定时任务,给定的时间,同步数据到mysql中去,使得数据保持一致
关于crone表达式,可以自行去网上查找相关内容学习

@Component
public class UpdateViewCountJob {@Autowiredprivate RedisCache redisCache;@Autowiredprivate ArticleService articleService;@Scheduled(cron = "0/55 * * * * ?")public void updateViewCount(){//获取redis中的浏览量Map viewCountMap = redisCache.getCacheMap("article:viewCount");List articles = viewCountMap.entrySet().stream().map(entry -> new Article(Long.valueOf(entry.getKey()), entry.getValue().longValue())).collect(Collectors.toList());//更新到数据库中articleService.updateBatchById(articles);}
}
修改查询文章的代码,冲redis中去查询数据,获得实时的数据。
@Overridepublic ResponseResult getArticleDetail(Long id) {//根据id查询文章Article article = getById(id);//从redis中获取viewCountInteger viewCount = redisCache.getCacheMapValue("article:viewCount", id.toString());article.setViewCount(viewCount.longValue());//转换成VOArticleDetailVo articleDetailVo = BeanCopyUtils.copyBean(article, ArticleDetailVo.class);//根据分类id查询分类名Long categoryId = articleDetailVo.getCategoryId();Category category = categoryService.getById(categoryId);if(category!=null){articleDetailVo.setCategoryName(category.getName());}//封装响应返回return ResponseResult.okResult(articleDetailVo);}

前台部分到此结束,学会自行去分析,根据接口去写代码,一步步去分析业务逻辑。
下一篇:[前端框架]-VUE(下篇)