前面的部分请看基于SpringBoot的哈士奇博客项目(一)-CSDN博客
2、前台博客
2.8、前台登录功能实现
2.8.6、登录接口代码实现
BlogLoginController
import com.hashiqi.domain.ResponseResult;
import com.hashiqi.domain.entity.User;
import com.hashiqi.service.BlogLoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
* 前台登录接口
*/
@RestController
public class BlogLoginController {
@Autowired
private BlogLoginService blogLoginService;
// 登录
@PostMapping("/login")
public ResponseResult login(@RequestBody User user) {
return blogLoginService.login(user);
}
}
service中BlogLoginService
import com.hashiqi.domain.ResponseResult;
import com.hashiqi.domain.entity.User;
public interface BlogLoginService {
// 登录
ResponseResult login(User user);
}
serviceImpl中BlogLoginServiceImpl
import com.hashiqi.constants.RedisKeyConstants;
import com.hashiqi.domain.ResponseResult;
import com.hashiqi.domain.entity.LoginUser;
import com.hashiqi.domain.entity.User;
import com.hashiqi.domain.vo.BlogUserLoginVO;
import com.hashiqi.domain.vo.UserInfoVO;
import com.hashiqi.service.BlogLoginService;
import com.hashiqi.utils.BeanCopyUtils;
import com.hashiqi.utils.JwtUtil;
import com.hashiqi.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.stereotype.Service;
import java.util.Objects;
@Service
public class BlogLoginServiceImpl implements BlogLoginService {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisCache redisCache;
/**
* 登录
* @param user 用户
* @return 返回结果集
*/
@Override
public ResponseResult login(User user) {
UsernamePasswordAuthenticationToken authenticationToken
= new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
// 判断是否认证通过
if (Objects.isNull(authenticate)) {
throw new RuntimeException("用户名或密码错误");
}
// 获取userId生成token
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userId = loginUser.getUser().getId().toString();
String jwt = JwtUtil.createJWT(userId);
// 把用户信息存入redis中
redisCache.setCacheObject(RedisKeyConstants.BLOG_LOGIN_ID + userId, loginUser);
// 把User转换成UserInfoVO
UserInfoVO userInfoVO = BeanCopyUtils.copyBean(loginUser.getUser(), UserInfoVO.class);
// 把token和userInfo封装并返回
BlogUserLoginVO vo = new BlogUserLoginVO(jwt, userInfoVO);
return ResponseResult.okResult(vo);
}
}
serviceImpl中UserDetailServiceImpl
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.hashiqi.domain.entity.LoginUser;
import com.hashiqi.domain.entity.User;
import com.hashiqi.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Objects;
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 根据用户名查询用户信息
User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUserName, username));
// 判断是否存在该用户信息,如果否,则抛出异常
if (Objects.isNull(user)) {
throw new RuntimeException("用户不存在");
}
// 如果是,返回该用户信息
//TODO 查询权限信息封装
return new LoginUser(user);
}
}
entity包中LoginUser
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
/**
* 用户登录实体
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser implements UserDetails {
private User user;
// 权限集合
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUserName();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
vo包中的BlogUserLoginVO
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BlogUserLoginVO {
private String token;
private UserInfoVO userInfo;
}
vo包中的UserInfoVO
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class UserInfoVO {
/**
* 主键
*/
private Long id;
/**
* 昵称
*/
private String nickName;
/**
* 头像
*/
private String avatar;
private String sex;
private String email;
}
constants包中的RedisKeyConstants
/**
* redis的key前缀
*/
public class RedisKeyConstants {
/**
* 用户登录key前缀
*/
public static final String BLOG_LOGIN_ID = "bloglogin:";
}
前台博客模块中的config包
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf,因为如果是分布式系统,则不适合session
.csrf().disable()
// 不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口,允许匿名访问【即未登录状态下可以访问 .permitAll()代表都可以访问】
.antMatchers("/login").anonymous()
// 出上面外的所有请求全部不需要认证即可访问
.anyRequest().permitAll();
http.logout().disable();
// 允许跨域
http.cors();
}
}
2.8.7、登录校验过滤器代码实现
思路
-
定义jwt认证过滤器
-
获取token
-
解析token,获取其中的userId
-
从redis中获取登录用户信息
-
存入SecurityContextHolder中
-
前台博客模块中的filter包
import com.alibaba.fastjson.JSON;
import com.hashiqi.constants.RedisKeyConstants;
import com.hashiqi.constants.SystemConstants;
import com.hashiqi.domain.ResponseResult;
import com.hashiqi.domain.entity.LoginUser;
import com.hashiqi.enums.AppHttpCodeEnum;
import com.hashiqi.utils.JwtUtil;
import com.hashiqi.utils.RedisCache;
import com.hashiqi.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 {
@Autowired
private RedisCache redisCache;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 获取请求头中的token
String token = request.getHeader(SystemConstants.TOKEN);
if (!StringUtils.hasText(token)) {
// 说明该接口无需token,直接放行即可
filterChain.doFilter(request, response);
return;
}
// 解析获取userId
Claims claims = null;
try {
claims = JwtUtil.parseJWT(token);
} catch (Exception e) {
// token超时
// token非法请求
e.printStackTrace();
// 响应前端,需重新进行登录
ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
WebUtils.renderString(response, JSON.toJSONString(result));
return;
}
String userId = claims.getSubject();
// 从redis中获取用户信息
LoginUser loginUser = redisCache.getCacheObject(RedisKeyConstants.BLOG_LOGIN_ID + userId);
// redis过期
if (Objects.isNull(loginUser)) {
// 说明redis过期,提示重新登录
ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
WebUtils.renderString(response, JSON.toJSONString(result));
return;
}
// 存入SecurityContextHolder
// TODO 现阶段未有权限信息
UsernamePasswordAuthenticationToken authenticationToken
= new UsernamePasswordAuthenticationToken(loginUser, null, null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
}
}
import com.hashiqi.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf,因为如果是分布式系统,则不适合session
.csrf().disable()
// 不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口,允许匿名访问【即未登录状态下可以访问 .permitAll()代表都可以访问】
.antMatchers("/login").anonymous()
.antMatchers("/link/getAllLink").authenticated()
// 出上面外的所有请求全部不需要认证即可访问
.anyRequest().permitAll();
http.logout().disable();
// 允许跨域
http.cors();
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
}
2.9、认证授权失败处理
目前项目在认证失败或者权限不足的时候返回前端的响应体是Security进行处理的,但是可能不会符合该项目的接口规范,所以需要自定义异常处理。
这两个处理器放在公共子模块中的handler包中。
AuthenticationEntryPoint 认证失败处理器
import com.alibaba.fastjson.JSON;
import com.hashiqi.domain.ResponseResult;
import com.hashiqi.enums.AppHttpCodeEnum;
import com.hashiqi.utils.WebUtils;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
// 认证失败处理
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException, ServletException {
// 打印异常信息
authenticationException.printStackTrace();
// InsufficientAuthenticationException
ResponseResult result = null;
// BadCredentialsException
if (authenticationException instanceof BadCredentialsException) {
result = ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_ERROR.getCode(), authenticationException.getMessage());
} else if (authenticationException instanceof InsufficientAuthenticationException) {
result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
} else {
result = ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR, "认证或授权失败");
}
// 响应回前端
WebUtils.renderString(response, JSON.toJSONString(result));
}
}
AccessDeniedHandler 授权失败处理器
import com.alibaba.fastjson.JSON;
import com.hashiqi.domain.ResponseResult;
import com.hashiqi.enums.AppHttpCodeEnum;
import com.hashiqi.utils.WebUtils;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
// 授权失败处理
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException authenticationException) throws IOException, ServletException {
// 打印异常信息
authenticationException.printStackTrace();
// 响应回前端
ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NO_OPERATOR_AUTH);
WebUtils.renderString(response, JSON.toJSONString(result));
}
}
将这两个处理器注入到前台博客模块里的SecurityConfig配置类中
import com.hashiqi.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private AccessDeniedHandler accessDeniedHandler;
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf,因为如果是分布式系统,则不适合session
.csrf().disable()
// 不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口,允许匿名访问【即未登录状态下可以访问 .permitAll()代表都可以访问】
.antMatchers("/login").anonymous()
.antMatchers("/link/getAllLink").authenticated()
// 出上面外的所有请求全部不需要认证即可访问
.anyRequest().permitAll();
http.logout().disable();
// 允许跨域
http.cors();
// 将token检验过滤器放在用户名和密码校验之前
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 配置认证和授权失败处理器
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
}
}
2.10、统一异常处理
实际开发过程中需要做很多的判断校验,如果出现了非法情况是期望响应对应的提示的。但是如果每次都是手动处理就显得十分麻烦,所以可以选择直接抛出异常的方式,接着对异常进行统一处理,换言之,把异常中的信息封装成统一返回结果集响应给前端。
在公共子模块中的exception包
import com.hashiqi.enums.AppHttpCodeEnum;
/**
* 统一异常处理
*/
public class SystemException extends RuntimeException {
private int code;
private String msg;
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
public SystemException(AppHttpCodeEnum httpCodeEnum) {
super(httpCodeEnum.getMsg());
this.code = httpCodeEnum.getCode();
this.msg = httpCodeEnum.getMsg();
}
}
在公共子模块中的handler包的handler包
import com.hashiqi.domain.ResponseResult;
import com.hashiqi.enums.AppHttpCodeEnum;
import com.hashiqi.exception.SystemException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(SystemException.class)
public ResponseResult systemExceptionHandler(SystemException e){
//打印异常信息
log.error("出现了异常! {}",e.toString());
//从异常对象中获取提示信息封装返回
return ResponseResult.errorResult(e.getCode(),e.getMsg());
}
@ExceptionHandler(Exception.class)
public ResponseResult exceptionHandler(Exception e){
//打印异常信息
log.error("出现了异常! {}",e.toString());
//从异常对象中获取提示信息封装返回
return ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR.getCode(),e.getMessage());
}
}
2.11、退出登录接口
2.11.1、接口设计
请求方式 | 请求地址 | 请求头 |
---|---|---|
POST | /logout | 需要token请求头 |
响应格式:
{
"code": 200,
"msg": "操作成功"
}
2.11.2、代码实现
要实现的操作:删除redis中的用户信息
BlogLoginController
// 退出登录
@PostMapping("/logout")
public ResponseResult logout() {
return blogLoginService.logout();
}
BlogLoginService
// 退出登录
ResponseResult logout();
BlogLoginServiceImpl
/**
* 退出登录
* @return 返回结果集
*/
@Override
public ResponseResult logout() {
// 获取token,解析获取userId
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
// 获取userId
Long userId = loginUser.getUser().getId();
// 删除redis中的用户信息
redisCache.deleteObject(RedisKeyConstants.BLOG_LOGIN_ID + userId);
return ResponseResult.okResult();
}
在SecurityConfig需要再次操作
要关闭默认的退出登录功能。并且要配置我们的退出登录接口需要认证才能访问
import com.hashiqi.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private AccessDeniedHandler accessDeniedHandler;
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf,因为如果是分布式系统,则不适合session
.csrf().disable()
// 不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口,允许匿名访问【即未登录状态下可以访问 .permitAll()代表都可以访问】
.antMatchers("/login").anonymous()
// 注销接口需要认证才能访问
.antMatchers("/logout").authenticated()
// 出上面外的所有请求全部不需要认证即可访问
.anyRequest().permitAll();
// 关闭默认的注销功能
http.logout().disable();
// 允许跨域
http.cors();
// 将token检验过滤器放在用户名和密码校验之前
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 配置认证和授权失败处理器
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
}
}
2.12、查询评论列表接口
2.12.1、需求分析
文章详情页面要展示这篇文章下的评论列表。
2.12.2、评论表分析
通过需求去分析需要有哪些字段。
DROP TABLE IF EXISTS `tb_comment`;
CREATE TABLE `tb_comment` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '评论类型(0代表文章评论,1代表友链评论)',
`article_id` bigint(20) NULL DEFAULT NULL COMMENT '文章id',
`root_id` bigint(20) NULL DEFAULT -1 COMMENT '根评论id',
`content` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '评论内容',
`to_comment_user_id` bigint(20) NULL DEFAULT -1 COMMENT '所回复的目标评论的userid',
`to_comment_id` bigint(20) NULL DEFAULT -1 COMMENT '回复目标评论id',
`create_by` bigint(20) NULL DEFAULT NULL,
`create_time` datetime(0) NULL DEFAULT NULL,
`update_by` bigint(20) NULL DEFAULT NULL,
`update_time` datetime(0) NULL DEFAULT NULL,
`is_deleted` int(1) NULL DEFAULT 0 COMMENT '删除标志(0代表未删除,1代表已删除)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 34 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '评论表' ROW_FORMAT = Dynamic;
2.12.3、接口设计
请求方式 | 请求地址 | 请求头 |
---|---|---|
GET | /comment/commentList | 不需要token请求头 |
Query格式请求参数:
articleId:文章id
pageNum: 页码
pageSize: 每页条数
响应格式:
{
"code": 200,
"data": {
"rows": [
{
"articleId": "1",
"children": [
{
"articleId": "1",
"content": "hhhashiqi",
"createBy": "1",
"createTime": "2023-09-30 10:06:21",
"id": "20",
"rootId": "1",
"toCommentId": "1",
"toCommentUserId": "1",
"toCommentUserName": "xxx",
"username": "xxx"
}
],
"content": "hashiqi",
"createBy": "1",
"createTime": "2023-09-29 07:59:22",
"id": "1",
"rootId": "-1",
"toCommentId": "-1",
"toCommentUserId": "-1",
"username": "xxx"
}
],
"total": "15"
},
"msg": "操作成功"
}
2.12.4、代码实现
Comment实体类
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("tb_comment")
public class Comment implements Serializable {
private static final long serialVersionUID=1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 评论类型(0代表文章评论,1代表友链评论)
*/
private String type;
/**
* 文章id
*/
private Long articleId;
/**
* 根评论id
*/
private Long rootId;
/**
* 评论内容
*/
private String content;
/**
* 所回复的目标评论的userid
*/
private Long toCommentUserId;
/**
* 回复目标评论id
*/
private Long toCommentId;
private Long createBy;
private Date createTime;
private Long updateBy;
private Date updateTime;
/**
* 删除标志(0代表未删除,1代表已删除)
*/
private Integer isDeleted;
}
2.12.4.1、暂时省略子评论
CommentController
import com.hashiqi.domain.ResponseResult;
import com.hashiqi.service.CommentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/comment")
public class CommentController {
@Autowired
private CommentService commentService;
// 获取所有文章相关的评论
@GetMapping("/commentList")
public ResponseResult commentList(
Long articleId,
Integer pageNum,
Integer pageSize) {
return commentService.commentList(articleId, pageNum, pageSize);
}
}
CommentService
import com.hashiqi.domain.ResponseResult;
import com.hashiqi.domain.entity.Comment;
import com.baomidou.mybatisplus.extension.service.IService;
public interface CommentService extends IService<Comment> {
// 获取所有文章相关的评论
ResponseResult commentList(Long articleId, Integer pageNum, Integer pageSize);
}
CommentServiceImpl
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hashiqi.constants.SystemConstants;
import com.hashiqi.domain.ResponseResult;
import com.hashiqi.domain.entity.Comment;
import com.hashiqi.domain.vo.CommentVO;
import com.hashiqi.domain.vo.PageVO;
import com.hashiqi.mapper.CommentMapper;
import com.hashiqi.service.CommentService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hashiqi.service.UserService;
import com.hashiqi.utils.BeanCopyUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> implements CommentService {
@Autowired
private UserService userService;
/**
* 获取所有文章相关的评论
* @param articleId 文章id
* @param pageNum 当前页
* @param pageSize 页大小
* @return 返回所有评论
*/
@Override
public ResponseResult commentList(Long articleId, Integer pageNum, Integer pageSize) {
// 查询对应文章id的根评论
Page<Comment> page = this.page(new Page<>(pageNum, pageSize),
new LambdaQueryWrapper<Comment>().eq(Comment::getArticleId, articleId).eq(Comment::getRootId, SystemConstants.ARTICLE_COMMENT_ROOT));
// 将Comment转换成CommentVO
List<CommentVO> commentVOs = toCommentList(page.getRecords());
return ResponseResult.okResult(new PageVO(commentVOs, page.getTotal()));
}
/**
* 对获取的评论VO进行属性赋值封装
* @param list 要封装的评论对象集合
* @return 返回封装后的评论对象集合
*/
private List<CommentVO> toCommentList(List<Comment> list) {
List<CommentVO> commentVOs = BeanCopyUtils.copyBeanList(list, CommentVO.class);
commentVOs.forEach(commentVO -> {
// 通过creatBy查询用户的昵称并赋值
commentVO.setUsername(userService.getById(commentVO.getCreateBy()).getNickName());
// 通过toCommentId查询用户的昵称并赋值
if (!commentVO.getToCommentUserId().equals(SystemConstants.ARTICLE_TO_COMMENT_USER_ID)) {
commentVO.setToCommentUserName(userService.getById(commentVO.getToCommentUserId()).getNickName());
}
});
return commentVOs;
}
}
CommentVO
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommentVO {
private Long id;
/**
* 文章id
*/
private Long articleId;
/**
* 根评论id
*/
private Long rootId;
/**
* 评论内容
*/
private String content;
/**
* 所回复的目标评论的userid
*/
private Long toCommentUserId;
private String toCommentUserName;
/**
* 回复目标评论id
*/
private Long toCommentId;
/**
* 评论人
*/
private String username;
private Long createBy;
private Date createTime;
}
SystemConstants
/**
* 文章的根评论
*/
public static final String ARTICLE_COMMENT_ROOT = "-1";
/**
* 根评论回复ID为-1
*/
public static final Long ARTICLE_TO_COMMENT_USER_ID = -1L;
2.12.4.2、查询子评论
CommentVO在之前的基础上增加了 private List<CommentVO> children;
CommentServiceImpl
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hashiqi.constants.SystemConstants;
import com.hashiqi.domain.ResponseResult;
import com.hashiqi.domain.entity.Comment;
import com.hashiqi.domain.vo.CommentVO;
import com.hashiqi.domain.vo.PageVO;
import com.hashiqi.mapper.CommentMapper;
import com.hashiqi.service.CommentService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hashiqi.service.UserService;
import com.hashiqi.utils.BeanCopyUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> implements CommentService {
@Autowired
private UserService userService;
/**
* 获取所有文章相关的评论
* @param articleId 文章id
* @param pageNum 当前页
* @param pageSize 页大小
* @return 返回所有评论
*/
@Override
public ResponseResult commentList(Long articleId, Integer pageNum, Integer pageSize) {
// 查询对应文章id的根评论
Page<Comment> page = this.page(new Page<>(pageNum, pageSize),
new LambdaQueryWrapper<Comment>().eq(Comment::getArticleId, articleId).eq(Comment::getRootId, SystemConstants.ARTICLE_COMMENT_ROOT));
// 将Comment转换成CommentVO
List<CommentVO> commentVOs = toCommentList(page.getRecords());
// 查询所有根评论对应的子评论集合,并且赋值给对应的属性
commentVOs.forEach(commentVO -> {
// 查询对应的子评论集合
List<CommentVO> children = getChildren(commentVO.getId());
// 赋值
commentVO.setChildren(children);
});
return ResponseResult.okResult(new PageVO(commentVOs, page.getTotal()));
}
/**
* 根据根评论id查询所对应的子评论集合
* @param id 根评论id
* @return 返回CommentVO集合
*/
private List<CommentVO> getChildren(Long id) {
List<Comment> comments = baseMapper.selectList(new LambdaQueryWrapper<Comment>()
.eq(Comment::getRootId, id)
.orderByDesc(Comment::getCreateTime));
return toCommentList(comments);
}
/**
* 对获取的评论VO进行属性赋值封装
* @param list 要封装的评论对象集合
* @return 返回封装后的评论对象集合
*/
private List<CommentVO> toCommentList(List<Comment> list) {
List<CommentVO> commentVOs = BeanCopyUtils.copyBeanList(list, CommentVO.class);
commentVOs.forEach(commentVO -> {
// 通过creatBy查询用户的昵称并赋值
commentVO.setUsername(userService.getById(commentVO.getCreateBy()).getNickName());
// 通过toCommentId查询用户的昵称并赋值
if (!commentVO.getToCommentUserId().equals(SystemConstants.ARTICLE_TO_COMMENT_USER_ID)) {
commentVO.setToCommentUserName(userService.getById(commentVO.getToCommentUserId()).getNickName());
}
});
return commentVOs;
}
}
2.13、发表评论接口
2.13.1、需求分析
用户登录后可以对文章发表评论,也可以对评论进行回复。
用户登录后也可以在友链页面进行评论。
2.13.2、接口设计
请求方式 | 请求地址 | 请求头 |
---|---|---|
POST | /comment | 需要token头 |
请求体:
回复了文章:
{
"articleId":1,
"type":0,
"rootId":-1,
"toCommentId":-1,
"toCommentUserId":-1,
"content":"评论了文章"
}
回复了某条评论:
{
"articleId":1,
"type":0,
"rootId":"3",
"toCommentId":"3",
"toCommentUserId":"1",
"content":"回复了某条评论"
}
回复网链评论:
{
"articleId":1,
"type":1,
"rootId":"3",
"toCommentId":"3",
"toCommentUserId":"1",
"content":"回复了某条网链评论"
}
响应格式:
{
"code":200,
"msg":"操作成功"
}
2.13.3、代码实现
CommentController
// 添加评论
@PostMapping
public ResponseResult addComment(@RequestBody Comment comment) {
return commentService.addComment(comment);
}
CommentService
// 添加评论
ResponseResult addComment(Comment comment);
CommentServiceImpl
/**
* 添加评论
* @param comment 评论对象
* @return 返回结果集
*/
@Override
public ResponseResult addComment(Comment comment) {
// 评论内容不能为空
if (!StringUtils.hasText(comment.getContent())) {
throw new SystemException(AppHttpCodeEnum.CONTENT_NOT_NULL);
}
save(comment);
return ResponseResult.okResult();
}
公共子模块中utils包
import com.hashiqi.domain.entity.LoginUser;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
public class SecurityUtils {
/**
* 获取用户
**/
public static LoginUser getLoginUser() {
return (LoginUser) getAuthentication().getPrincipal();
}
/**
* 获取Authentication
*/
public static Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
public static Boolean isAdmin(){
Long id = getLoginUser().getUser().getId();
return id != null && 1L == id;
}
public static Long getUserId() {
return getLoginUser().getUser().getId();
}
}
公共子模块中的handler包
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.hashiqi.utils.SecurityUtils;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* 配置MP字段自动填充
*/
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
Long userId = null;
try {
userId = SecurityUtils.getUserId();
} catch (Exception e) {
e.printStackTrace();
userId = -1L;//表示是自己创建
}
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("createBy",userId , metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
this.setFieldValByName("updateBy", userId, metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", new Date(), metaObject);
this.setFieldValByName(" ", SecurityUtils.getUserId(), metaObject);
}
}
在entity包中的实体类进行添加,或者嫌添加每一个实体类太麻烦,可以创建一个实体基类,将这些字段写入其中,再让其他实体类去继承该实体基类。
/**
* 创建人的用户id
*/
@TableField(fill = FieldFill.INSERT)
private Long createBy;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
* 更新人
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateBy;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
2.14、网链评论列表
2.14.1、需求分析
网链页面也需要查询对应的评论列表。
2.14.2、接口设计
请求方式 | 请求地址 | 请求头 |
---|---|---|
GET | /comment/linkCommentList | 不需要token请求头 |
Query格式请求参数:
pageNum: 页码
pageSize: 每页条数
响应格式:
{
"code": 200,
"data": {
"rows": [
{
"articleId": "1",
"children": [
{
"articleId": "1",
"content": "回复友链评论3",
"createBy": "1",
"createTime": "2023-09-29 10:08:50",
"id": "23",
"rootId": "22",
"toCommentId": "22",
"toCommentUserId": "1",
"toCommentUserName": "xxx",
"username": "xxx"
}
],
"content": "友链评论2",
"createBy": "1",
"createTime": "2023-09-29 10:08:28",
"id": "22",
"rootId": "-1",
"toCommentId": "-1",
"toCommentUserId": "-1",
"username": "xxx"
}
],
"total": "1"
},
"msg": "操作成功"
}
2.14.3、代码实现
SystemConstants增加两个常量
/**
* 评论类型为:文章评论
*/
public static final String ARTICLE_COMMENT = "0";
/**
* 评论类型为:网链评论
*/
public static final String LINK_COMMENT = "1";
CommentController
对之前文章评论列表接口进行修改,并添加新的网链评论接口。
@Autowired
private CommentService commentService;
// 获取所有文章相关的评论
@GetMapping("/commentList")
public ResponseResult commentList(
Long articleId,
Integer pageNum,
Integer pageSize) {
return commentService.commentList(SystemConstants.ARTICLE_COMMENT, articleId, pageNum, pageSize);
}
// 获取所有网链相关的评论
@GetMapping("/linkCommentList")
public ResponseResult linkCommentList(
Integer pageNum,
Integer pageSize) {
return commentService.commentList(SystemConstants.LINK_COMMENT, null, pageNum, pageSize);
}
CommentService
同样,既然控制层方法里参数进行改动,那么就要对业务层及业务实现层的方法参数进行改动。
在CommentService中修改commentList方法,新增一个参数为字符串类型的commentType。
// 获取所有文章/网链相关的评论
ResponseResult commentList(String commentType, Long articleId, Integer pageNum, Integer pageSize);
CommentServiceImpl
/**
* 获取所有文章相关的评论
* @param commentType 评论类型
* @param articleId 文章id
* @param pageNum 当前页
* @param pageSize 页大小
* @return 返回所有评论
*/
@Override
public ResponseResult commentList(String commentType, Long articleId, Integer pageNum, Integer pageSize) {
// 查询对应文章id的根评论
Page<Comment> page = this.page(new Page<>(pageNum, pageSize),
new LambdaQueryWrapper<Comment>()
.eq(SystemConstants.ARTICLE_COMMENT.equals(commentType), Comment::getArticleId, articleId)
.eq(Comment::getRootId, SystemConstants.ARTICLE_COMMENT_ROOT)
.eq(Comment::getType, commentType)
.orderByDesc(Comment::getCreateTime));
// 将Comment转换成CommentVO
List<CommentVO> commentVOs = toCommentList(page.getRecords());
// 查询所有根评论对应的子评论集合,并且赋值给对应的属性
commentVOs.forEach(commentVO -> {
// 查询对应的子评论集合
List<CommentVO> children = getChildren(commentVO.getId());
// 赋值
commentVO.setChildren(children);
});
return ResponseResult.okResult(new PageVO(commentVOs, page.getTotal()));
}
2.15、个人信息查询接口
2.15.1、需求分析
进入个人中心的时候需要能够查看当前用户信息。
2.15.2、接口设计
请求方式 | 请求地址 | 请求头 |
---|---|---|
GET | /user/userInfo | 需要token请求头 |
不需要参数
响应格式:
{
"code":200,
"data":{
"avatar":"https://xxx/image_search/src=http%3A%2F%2Fi0.hdslb.com%2Fbfs%2Farticle%2F3bf9c263bc0f2ac5c3a7feb9e218d07475573ec8.gi",
"email":"23412332@qq.com",
"id":"1",
"nickName":"xxx",
"sex":"1"
},
"msg":"操作成功"
}
2.15.3、代码实现
UserController
import com.hashiqi.domain.ResponseResult;
import com.hashiqi.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
// 获取个人用户信息
@GetMapping("/userInfo")
public ResponseResult userInfo() {
return userService.userInfo();
}
}
UserService
import com.hashiqi.domain.ResponseResult;
import com.hashiqi.domain.entity.User;
import com.baomidou.mybatisplus.extension.service.IService;
public interface UserService extends IService<User> {
// 获取个人用户信息
ResponseResult userInfo();
}
UserServiceImpl
import com.hashiqi.domain.ResponseResult;
import com.hashiqi.domain.entity.User;
import com.hashiqi.domain.vo.UserInfoVO;
import com.hashiqi.mapper.UserMapper;
import com.hashiqi.service.UserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hashiqi.utils.BeanCopyUtils;
import com.hashiqi.utils.SecurityUtils;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
/**
* 获取个人用户信息
* @return 返回用户信息
*/
@Override
public ResponseResult userInfo() {
// 根据用户id查询用户信息
User user = this.getById(SecurityUtils.getUserId());
// User封装成UserInfoVO并返回
return ResponseResult.okResult(BeanCopyUtils.copyBean(user, UserInfoVO.class));
}
}
SecurityConfig
配置该接口必须认证后才能访问。
// 个人信息接口必须登录后才可访问
.antMatchers("/user/userInfo").authenticated()
2.16、头像上传接口
2.16.1、需求
在个人中心点击编辑的时候可以上传头像图片。上传完头像后,可以用于更新个人信息接口。
2.16.2、OSS
2.16.2.1、OSS的作用
因为如果把图片视频等文件上传到自己的应用的Web服务器,在读取图片的时候会占用比较多的资源。影响应用服务器的性能。
所以一般使用OSS(Object Storage Service对象存储服务)存储图片或视频。
2.16.2.2、七牛云代码测试
当然也可以使用腾讯云和阿里云的,或者去创建minio。
引入依赖
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>[7.13.0, 7.13.99]</version>
</dependency>
在前台博客模块中配置yml文件
这里需要自己去申请放入钥匙和秘钥以及创建桶进行对象存储。
oss:
accessKey: "xxx"
secretKey: "xxx"
bucket: "yourname-bucket"
复制参考文档的代码进行修改
import com.google.gson.Gson;
import com.qiniu.common.QiniuException;
import com.qiniu.http.Response;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.Region;
import com.qiniu.storage.UploadManager;
import com.qiniu.storage.model.DefaultPutRet;
import com.qiniu.util.Auth;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
@SpringBootTest
@ConfigurationProperties(prefix = "oss")
public class OSSTest {
@Value("${oss.accessKey}")
private String accessKey;
@Value("${oss.secretKey}")
private String secretKey;
@Value("${oss.bucket}")
private String bucket;
@Test
void test() {
//构造一个带指定 Region 对象的配置类
Configuration cfg = new Configuration(Region.autoRegion());
cfg.resumableUploadAPIVersion = Configuration.ResumableUploadAPIVersion.V2;// 指定分片上传版本
//...其他参数参考类注释
UploadManager uploadManager = new UploadManager(cfg);
//...生成上传凭证,然后准备上传
// String accessKey = "xxx";
// String secretKey = "xxx";
// String bucket = "yourname-bucket";
//默认不指定key的情况下,以文件内容的hash值作为文件名
String key = null;
try {
// 默认文档
// byte[] uploadBytes = "hello qiniu cloud".getBytes("utf-8");
// ByteArrayInputStream byteInputStream=new ByteArrayInputStream(uploadBytes);
// 自定义更改
InputStream is = new FileInputStream("F:\\yezhu.png");
Auth auth = Auth.create(accessKey, secretKey);
String upToken = auth.uploadToken(bucket);
try {
Response response = uploadManager.put(is,key,upToken,null, null);
//解析上传成功的结果
DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
System.out.println(putRet.key);
System.out.println(putRet.hash);
} catch (QiniuException ex) {
ex.printStackTrace();
if (ex.response != null) {
System.err.println(ex.response);
try {
String body = ex.response.toString();
System.err.println(body);
} catch (Exception ignored) {
}
}
}
} catch (Exception ex) {
//ignore
ex.printStackTrace();
}
}
}
2.16.3、接口设计
请求方式 | 请求地址 | 请求头 |
---|---|---|
POST | /upload | 需要token |
参数:img,值为要上传的文件
请求头:Content-Type :multipart/form-data;
响应格式:
{
"code": 200,
"data": "文件访问链接",
"msg": "操作成功"
}
2.16.4、代码实现
UploadController
import com.hashiqi.domain.ResponseResult;
import com.hashiqi.service.UploadService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
public class UploadController {
@Autowired
private UploadService uploadService;
// 上传文件
@PostMapping("/upload")
public ResponseResult uploadImg(MultipartFile img) {
return uploadService.uploadImg(img);
}
}
UploadService
import com.hashiqi.domain.ResponseResult;
import org.springframework.web.multipart.MultipartFile;
public interface UploadService {
// 上传文件
ResponseResult uploadImg(MultipartFile img);
}
OssUploadService
import com.google.gson.Gson;
import com.hashiqi.domain.ResponseResult;
import com.hashiqi.enums.AppHttpCodeEnum;
import com.hashiqi.exception.SystemException;
import com.hashiqi.service.UploadService;
import com.hashiqi.utils.PathUtils;
import com.qiniu.common.QiniuException;
import com.qiniu.http.Response;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.Region;
import com.qiniu.storage.UploadManager;
import com.qiniu.storage.model.DefaultPutRet;
import com.qiniu.util.Auth;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
@Service
@Data
@ConfigurationProperties(prefix = "oss")
public class OssUploadService implements UploadService {
@Value("${oss.accessKey}")
private String accessKey;
@Value("${oss.secretKey}")
private String secretKey;
@Value("${oss.bucket}")
private String bucket;
/**
* 上传文件
* @param img 文件
* @return 返回结果集
*/
@Override
public ResponseResult uploadImg(MultipartFile img) {
// 判断文件类型或者文件大小
// 获取原始文件名
String originalFilename = img.getOriginalFilename();
// 对原始名进行判断
if (!originalFilename.endsWith(".png")) {
throw new SystemException(AppHttpCodeEnum.FILE_TYPE_ERROR);
}
// 判断通过,设置文件路径规则【xxxx/xx/xx】,然后上传文件到OSS
String url = uploadOss(img, PathUtils.generateFilePath(originalFilename));
return ResponseResult.okResult(url);
}
/**
* 七牛云OSS
*/
private String uploadOss(MultipartFile imgFile, String filePath) {
//构造一个带指定 Region 对象的配置类
Configuration cfg = new Configuration(Region.autoRegion());
cfg.resumableUploadAPIVersion = Configuration.ResumableUploadAPIVersion.V2;// 指定分片上传版本
//...其他参数参考类注释
UploadManager uploadManager = new UploadManager(cfg);
//...生成上传凭证,然后准备上传
//默认不指定key的情况下,以文件内容的hash值作为文件名
// String key = null;
try {
// 自定义更改
InputStream is = imgFile.getInputStream();
Auth auth = Auth.create(accessKey, secretKey);
String upToken = auth.uploadToken(bucket);
try {
Response response = uploadManager.put(is,filePath,upToken,null, null);
//解析上传成功的结果
DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
System.out.println(putRet.key);
System.out.println(putRet.hash);
return "http://xxxxxxx.bkt.clouddn.com/" + filePath;
} catch (QiniuException ex) {
ex.printStackTrace();
if (ex.response != null) {
System.err.println(ex.response);
try {
String body = ex.response.toString();
System.err.println(body);
} catch (Exception ignored) {
}
}
}
} catch (Exception ex) {
//ignore
ex.printStackTrace();
}
return "操作成功";
}
}
公共子模块的utils包中的PathUtils
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
/**
* 规定文件路径
*/
public class PathUtils {
public static String generateFilePath(String fileName) {
// 根据日期生成路径【xxxx/xx/xx】
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd");
String datePath = simpleDateFormat.format(new Date());
// uuid作为文件名
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
// 后缀和文件后缀一致
int index = fileName.lastIndexOf(".");
String fileType = fileName.substring(index);
// 这里为什么不使用直接拼接,是因为这样处理性能比较好【但是不是安全线程】
return new StringBuilder().append(datePath).append(uuid).append(fileType).toString();
}
}
2.17、更新个人信息接口
2.17.1、需求分析
在编辑完个人资料后点击保存会对个人资料进行更新。
2.17.2、接口设计
请求方式 | 请求地址 | 请求头 |
---|---|---|
PUT | /user/userInfo | 需要token请求头 |
请求体中json格式数据:
{
"avatar":"https://xxx.aliyuncs.com/2023/09/29/948597e164614902ab1662ba8452e106.png",
"email":"548415168@qq.com",
"id":"1",
"nickName":"xxx",
"sex":"1"
}
响应格式:
{
"code":200,
"msg":"操作成功"
}
2.17.3、代码实现
UserController
// 更新个人用户信息
@PutMapping("userInfo")
public ResponseResult updateUserInfo(@RequestBody User user) {
return userService.updateUserInfo(user);
}
UserServiceImpl
/**
* 更新个人用户信息【指定字段】
* @param user 要更新的信息
* @return 返回结果集
*/
@Override
public ResponseResult updateUserInfo(User user) {
this.update(user, new LambdaUpdateWrapper<User>()
.eq(User::getId, user.getId())
.set(User::getAvatar, user.getAvatar())
.set(User::getNickName, user.getNickName())
.set(User::getSex, user.getSex()));
return ResponseResult.okResult();
}
2.18、用户注册
2.18.1、需求分析
要求用户能够在注册界面完成用户的注册。要求用户名,昵称,邮箱不能和数据库中原有的数据重复。如果某项重复了注册失败并且要有对应的提示。并且要求用户名,密码,昵称,邮箱都不能为空。
注意:密码必须密文存储到数据库中。
2.18.2、接口设计
请求方式 | 请求地址 | 请求头 |
---|---|---|
POST | /user/register | 不需要token请求头 |
请求体中json格式数据:
{
"email": "string",
"nickName": "string",
"password": "string",
"userName": "string"
}
响应格式:
{
"code":200,
"msg":"操作成功"
}
2.18.3、代码实现
UserController
// 注册个人用户信息
@PostMapping("/register")
public ResponseResult register(@RequestBody User user) {
return userService.register(user);
}
UserService
// 注册个人用户信息
ResponseResult register(User user);
UserServiceImpl
/**
* 注册个人用户信息
* @param user 用户信息
* @return 返回结果集
*/
@Override
public ResponseResult register(User user) {
// 对从前端传回来的数据进行非空判断
String userName = user.getUserName();
String password = user.getPassword();
String nickName = user.getNickName();
String email = user.getEmail();
if (!StringUtils.hasText(userName)) {
throw new SystemException(AppHttpCodeEnum.USERNAME_NOT_NULL);
}
if (!StringUtils.hasText(password)) {
throw new SystemException(AppHttpCodeEnum.PASSWORD_NOT_NULL);
}
if (!StringUtils.hasText(email)) {
throw new SystemException(AppHttpCodeEnum.EMAIL_NOT_NULL);
}
if(!StringUtils.hasText(nickName)){
throw new SystemException(AppHttpCodeEnum.NICKNAME_NOT_NULL);
}
// 是否存在数据库同样数据
if(userNameExist(userName)){
throw new SystemException(AppHttpCodeEnum.USERNAME_EXIST);
}
if(nickNameExist(nickName)){
throw new SystemException(AppHttpCodeEnum.NICKNAME_EXIST);
}
if(emailExist(email)) {
throw new SystemException(AppHttpCodeEnum.EMAIL_EXIST);
}
// 对密码进行加密
String encodePassword = passwordEncoder.encode(user.getPassword());
user.setPassword(encodePassword);
// 将数据存入数据库中
save(user);
return ResponseResult.okResult();
}
/**
* 判断邮箱是否已存在
* @param email 注册的邮箱
* @return true:存在;false:不存在
*/
private boolean emailExist(String email) {
return this.count(new LambdaQueryWrapper<User>().eq(User::getEmail, email)) > 0;
}
/**
* 判断昵称是否已存在
* @param nickName 注册或要更新的昵称
* @return true:存在;false:不存在
*/
private boolean nickNameExist(String nickName) {
return this.count(new LambdaQueryWrapper<User>().eq(User::getNickName, nickName)) > 0;
}
/**
* 判断用户名是否已存在
* @param userName 注册或要更新的用户名
* @return true:存在;false:不存在
*/
private boolean userNameExist(String userName) {
return this.count(new LambdaQueryWrapper<User>().eq(User::getUserName, userName)) > 0;
}
AppHttpCodeEnum
USERNAME_NOT_NULL(508, "用户名不能为空"),
NICKNAME_NOT_NULL(509, "昵称不能为空"),
PASSWORD_NOT_NULL(510, "密码不能为空"),
EMAIL_NOT_NULL(511, "邮箱不能为空"),
NICKNAME_EXIST(512, "昵称已存在");
这部分到这里就结束了,后续还会继续更新~~