当前位置:   article > 正文

《黑马点评》项目学习3:自媒体素材与文章管理_黑马点评源码

黑马点评源码


前言


一、前后端搭建

前端部分:
使用nginx反向代理服务器,具体的html文件直接使用资料中的文件

upstream  heima-wemedia-gateway{
    server localhost:51602;
}

server {
	listen 8802;
	location / {
		root E:/java/project/heima-leadnews/wemedia-web/; #填写自己的地址
		index index.html;
	}
	
	location ~/wemedia/MEDIA/(.*) {
		proxy_pass http://heima-wemedia-gateway/$1;
		proxy_set_header HOST $host;  # 不改变源请求头的值
		proxy_pass_request_body on;  #开启获取请求体
		proxy_pass_request_headers on;  #开启获取请求头
		proxy_set_header X-Real-IP $remote_addr;   # 记录真实发出请求的客户端IP
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  #记录代理信息
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

后端部分:
需要在项目中添加文章数据库及实体类、文章微服务、文章网关,并对每个模块添加对应的配置文件,主要包括nacos配置中心中的与数据库连接的配置、路由到自媒体的微服务网关。同时网关模块主要包括filter过滤器与jwt工具类,其中过滤器用来接收response和request,一次判断token是否有效,jwt工具类则是帮助过滤器实现基本功能的方法函数。整体结构和app结构相同,实现逻辑也相似。

二、素材管理

1.图片上传

实现思路:
在这里插入图片描述
token解析为用户存入header实现:

修改AuthorizeFilter中的代码:

//获取用户信息
Object userId = claimsBody.get("id");

//存入header中
ServerHttpRequest serverHttpRequest =  request.mutate().headers(httpHeaders ->
{httpHeaders.add("userId", userId + "");}).build();

//重置请求
exchange.mutate().request(serverHttpRequest);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

拦截器实现:
需要在自媒体微服务中加入拦截器类,具体类实现如下:

package com.heima.wemedia.interceptor;

import com.heima.model.wemedia.pojos.WmUser;
import com.heima.utils.thread.WmThreadLocalUtil;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


public class WmTokenInterceptor implements HandlerInterceptor {
    /**
     * 得到header中的用户信息,并存入到当前线程中
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String userId = request.getHeader("uesrId");
        if(userId != null) {
            //存入到当前线程中
            WmUser wmUser = new WmUser();
            wmUser.setId(Integer.valueOf(userId));
            WmThreadLocalUtil.setUser(wmUser);
        }

        return true;
    }
    /**
     * 清理线程中的数据
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        WmThreadLocalUtil.clear();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

拦截器中使用了线程的ThreadLocal工具类,将变量存放在线程每部空间中,这样,在后续的请求处理过程中,不需要每次都传递用户身份信息,而是可以直接从WmThreadLocalUtil中获取。同时,在使用线程池处理任务时,可能需要将一些线程本地的变量传递给任务进行处理。这时可以利用WmThreadLocalUtil在每个线程中存储变量,任务可以通过WmThreadLocalUtil获取到所需的线程本地变量。
工具类的实现:

package com.heima.utils.thread;

import com.heima.model.wemedia.pojos.WmUser;

public class WmThreadLocalUtil {
    private final static ThreadLocal<WmUser> WM_USER_THREAD_LOCAL = new ThreadLocal<WmUser>();

    //存入线程中
    public static void setUser(WmUser wmUser){
        WM_USER_THREAD_LOCAL.set(wmUser);
    }

    //从线程中获取
    public static WmUser getUser(){
        return WM_USER_THREAD_LOCAL.get();
    }

    //清理
    public static void clear(){
        WM_USER_THREAD_LOCAL.remove();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

拦截器使用:

package com.heima.wemedia.config;


import com.heima.wemedia.interceptor.WmTokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    /**
     * 添加自定义的拦截器
     * @param registry 注册
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //拦截所有请求
        registry.addInterceptor(new WmTokenInterceptor()).addPathPatterns("/**");
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

2.图片上传接口

在这里插入图片描述
其中MultipartFile是Springmvc指定的文件接受类型
ResponseResult就是200、501等

代码实现:

package com.heima.wemedia.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.file.service.FileStorageService;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.wemedia.pojos.WmMaterial;
import com.heima.model.wemedia.pojos.WmUser;
import com.heima.utils.thread.WmThreadLocalUtil;
import com.heima.wemedia.mapper.WmMaterialMapper;
import com.heima.wemedia.service.WmMaterialService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.Date;
import java.util.UUID;

@Slf4j
@Service
@Transactional
public class WmMaterialServiceImpl extends ServiceImpl<WmMaterialMapper, WmMaterial> implements WmMaterialService {

    @Autowired
    private FileStorageService fileStorageService;

    /**
     * 素材图片上传
     * @param multipartFile
     * @return
     */
    @Override
    public ResponseResult uploadPicture(MultipartFile multipartFile) {
        //1、检查参数
        if(multipartFile == null || multipartFile.getSize() == 0) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }

        //2、上传图片到minIO中
        String fileName = UUID.randomUUID().toString().replace("-", "");
        String originalFilename = multipartFile.getOriginalFilename();
        String postfix = originalFilename.substring(originalFilename.lastIndexOf("."));
        String fileId = null;
        try {
            fileId = fileStorageService.uploadImgFile("", fileName + postfix, multipartFile.getInputStream());
            log.info("上传图片到minIO中,fileId:{}", fileId);
        } catch (IOException e) {
            e.printStackTrace();
            log.error("WmMaterialServiceImpl-上传文件失败");

        }

        //3、保存到数据库中
        WmMaterial wmMaterial = new WmMaterial();
        WmUser user = WmThreadLocalUtil.getUser();
        if (user != null) {
            wmMaterial.setUserId(user.getId());
            log.info("获取用户成功,userId:{}", user.getId());
        } else {
            // 处理用户信息为空的情况,例如记录日志或者抛出异常
            log.error("WmThreadLocalUtil-空指针异常");
        }
        wmMaterial.setUrl(fileId);
        wmMaterial.setIsCollection((short)0);
        wmMaterial.setType((short)0);
        wmMaterial.setCreatedTime(new Date());
        Boolean isSave = save(wmMaterial);
        log.info("数据与数据的交互结果为:{}", isSave);
        log.info("数据库存入的dto数据为:{}", wmMaterial);

        //4、返回结果
        return ResponseResult.okResult(wmMaterial);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78

3、素材列表-接口定义

在这里插入图片描述
其中参数WmMaterialDto要有分页信息,所以需要继承common里已有的PageReauestDto

首先在WmMaterialController中添加要实现的功能和路由:

    @PostMapping("/list")
    public ResponseResult findList(@RequestBody WmMaterialDto dto){
        return wmMaterialService.findList(dto);
    }
  • 1
  • 2
  • 3
  • 4

随后在接口中增加方法

public interface WmMaterialService extends IService<WmMaterial> {

    /**
     * 素材图片上传
     * @param multipartFile
     * @return
     */
    public ResponseResult uploadPicture(MultipartFile multipartFile);

    /**
     * 素材列表查询
     * @param dto
     * @return
     */
    public ResponseResult findList(WmMaterialDto dto);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

要实现查找,入参需要分页实体类WmMaterialDto继承PageRequestDto(此处还未理清楚)

@Data
public class WmMaterialDto extends PageRequestDto {
    /**
     * 1 收藏
     * 0 未收藏
     */
    private Short IsCollection;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

最后实现业务层:

@Override
    public ResponseResult findList(WmMaterialDto dto) {
        //1.检查参数
        dto.checkParam();

        //2.分页查询
        IPage page = new Page(dto.getPage(), dto.getSize());
        LambdaQueryWrapper<WmMaterial> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //是否收藏
        if(dto.getIsCollection() != null && dto.getIsCollection() == 1){
            lambdaQueryWrapper.eq(WmMaterial::getIsCollection, dto.getIsCollection());
        }

        //按照用户查询
        lambdaQueryWrapper.eq(WmMaterial::getUserId, WmThreadLocalUtil.getUser().getId());

        //按照时间查询
        lambdaQueryWrapper.orderByDesc( WmMaterial::getCreatedTime);

        page = page(page, lambdaQueryWrapper);

        //3.结果返回
        ResponseResult responseResult = new PageResponseResult(dto.getPage(), dto.getSize(), (int)page.getTotal());
        responseResult.setData(page.getRecords());
        return responseResult;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

三、文章管理

1、频道列表

1.1需求分析
需要将app端所有频道都展示在文章内容里,因为查询和发布文章的时候都要指定对应的频道信息,目前第一件要做的事就是将所有频道信息展示在内容列表的选项框里。

1.2定义频道实体类
在这里插入图片描述
根据频道信息表定义实体类pojo

1.3接口定义
在这里插入图片描述
响应结果需要返回频道实体类的data数据

1.4功能实现

@Slf4j
@Transactional
@Service
public class WmChannelServiceImpl extends ServiceImpl<WmChannelMapper, WmChannel> implements WmChannelService {

    /**
     * 查询所有频道
     * @return
     */
    @Override
    public ResponseResult findAll() {
        return ResponseResult.okResult(list());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

2、文章列表加载

2.1需求分析
1)可以对文章状态进行查询
2)可以根据关键字进行模糊查询
3)基于频道进行查询
4)根据发布时间段进行查询

2.2表结构分析
在这里插入图片描述
该表设置了枚举类来表示状态:

//状态枚举类
    @Alias("WmNewsStatus")
    public enum Status{
        NORMAL((short)0),SUBMIT((short)1),FAIL((short)2),ADMIN_AUTH((short)3),ADMIN_SUCCESS((short)4),SUCCESS((short)8),PUBLISHED((short)9);
        short code;
        Status(short code){
            this.code = code;
        }
        public short getCode(){
            return this.code;
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

2.3接口定义
在这里插入图片描述
参数:考虑请求可能会用到的参数,在需求分析中能够得到以下参数,又因为要显示页面,所有dto继承了pagedto
在这里插入图片描述
响应返回分页的列表信息即可:
在这里插入图片描述
2.4功能实现

package com.heima.wemedia.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.model.common.dtos.PageResponseResult;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.wemedia.dtos.WmNewsPageReqDto;
import com.heima.model.wemedia.pojos.WmNews;
import com.heima.utils.thread.WmThreadLocalUtil;
import com.heima.wemedia.mapper.WmNewsMapper;
import com.heima.wemedia.service.WmNewsService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.jsoup.helper.StringUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Service
@Transactional
public class WmNewsServiceImpl extends ServiceImpl<WmNewsMapper, WmNews> implements WmNewsService {

    /**
     * 条件查询文章列表
     * @param dto
     * @return
     */
    @Override
    public ResponseResult findList(WmNewsPageReqDto dto) {
        //1.检查参数
        //分页检查
        dto.checkParam();

        //2.分页条件查询
        IPage page = new Page(dto.getPage(), dto.getSize());
        LambdaQueryWrapper<WmNews> lambdaQueryWrapper = new LambdaQueryWrapper<>();

        //状态精确查询
        if(dto.getStatus() != null){
            lambdaQueryWrapper.eq(WmNews::getStatus, dto.getStatus());
        }

        //频道精确查询
        if(dto.getChannelId() != null) {
            lambdaQueryWrapper.eq(WmNews::getChannelId, dto.getChannelId());
        }

        //时间范围查询
        if(dto.getBeginPubDate() != null && dto.getEndPubDate() != null){
            lambdaQueryWrapper.between(WmNews::getPublishTime, dto.getBeginPubDate(), dto.getEndPubDate());
        }

        //关键字的模糊查询
        if(StringUtils.isNotBlank(dto.getKeyword())){
            lambdaQueryWrapper.like(WmNews::getTitle, dto.getKeyword());
        }

        //查询当前登陆人的文章
        lambdaQueryWrapper.eq(WmNews::getUserId, WmThreadLocalUtil.getUser().getId());

        //按照发布时间倒序查询
        lambdaQueryWrapper.orderByDesc(WmNews::getPublishTime);

        page = page(page, lambdaQueryWrapper);

        //3.结果返回
        ResponseResult responseResult = new PageResponseResult(dto.getPage(), dto.getSize(), (int) page.getTotal());
        responseResult.setData(page.getRecords());

        return responseResult;
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75

踩雷与debug

1、修改nginx的conf文件后运行,请求无法到达代理的服务器
使用postman对服务器和代理服务器进行post请求,服务器在保证运行状态的情况下返回200,但是代理服务器返回503/404。原因是cmd同时打开了多个nginx应用,导致代理服务器地址出错,打开任务管理器后将nginx全部关闭重新运行,问题得到解决
2、上传图片时404问题
使用postman重新对上传图片到服务器,发现问题是401问题。
在这里插入图片描述
查看idea,报错是

JWT expired at 2024-05-05T14:55:18Z. Current time: 2024-05-05T15:58:59Z, a difference of 3821453 milliseconds. Allowed clock skew: 0 milliseconds.
经过打印日志文件debug发现,postman中文问题是没有发送token,因此返回值为401;而线程内存ThreadLocal内变量不存在的问题是因为每次刷新在这里插入图片描述

界面默认是登录状态的,导致发送的请求是没有userId的,重新打开网页登录即可。
目前还剩的问题是上传的数据无法保存到数据库中,经过测试,上传的数据是可以正常上传minIO中的。
编写测试类获取数据库数据,发现上传的数据其实已经保存下来了,只是本地的Navicat无法显示,原因是使用的Navicat连接中并不显示已有的数据库,重新开启一个连接即可。

拓展学习与思考

在javaweb模块里开发一个需求的主要流程:
1.需求分析:了解掌握具体要是实现什么功能
2.表接口设计:分析该功能需要用到什么数据
3.接口定义:要有接口路径、请求方式、参数和响应结果
4.具体功能实现:实现方法的具体步骤。
其中功能实现又分为以下几个部分:
1)controller类实现函数涉及到mapper,mapper是数据库映射器是一种用于将数据库中的数据与Java对象互相映射的组件
2)完成接口定义,主要描述含有的功能
3)接口实现:具体功能实现

DTO和POJO的区别:
DTO(Data Transfer Object):
DTO通常用于在不同层之间传输数据,例如在前端和后端之间传递数据。
DTO对象通常包含了应用程序中某些特定业务场景所需的数据。
DTO对象通常是扁平的,只包含数据字段和对应的getter和setter方法,不包含业务逻辑。
DTO对象的属性通常与数据库表的字段或者服务端返回的数据结构相对应,但并不是一一对应的关系。
POJO(Plain Old Java Object):
POJO是一个简单的Java对象,通常用于表示应用程序中的实体或数据模型。
POJO对象通常包含了应用程序中某个实体的所有属性,并提供了对应的getter和setter方法。
POJO对象不包含业务逻辑,它们的主要作用是作为数据的载体。
POJO对象的属性通常与数据库表的字段或者数据模型的属性一一对应,用于在应用程序中表示和操作实体数据。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/693035
推荐阅读
相关标签
  

闽ICP备14008679号