SpringCloud微服务项目实战 - 4.自媒体平台(博主后台)

“我读过很多书,但后来大部分都忘记了,你说这样的阅读究竟有什么意义?”
“当我还是个孩子时,我吃过很多食物,现在已经记不起来吃过什么了。但可以肯定的是,它们中的一部分已经长成我的骨头和肉。”

在这里插入图片描述



系列文章目录

  1. 项目搭建
  2. App登录及网关
  3. App文章
  4. 自媒体平台(博主后台)
  5. 自媒体文章审核
  6. 延迟任务
  7. kafka及文章上下架
  8. App端文章搜索
  9. 后台系统管理
  10. Long类型精度丢失问题
  11. 定时计算热点文章(xxl-Job)
  12. 热点文章-实时计算(kafkaStream)
  13. 项目部署_持续集成(Jenkins)

文章目录


一、前后端搭建

1. 后台搭建

⑴. 导入文章数据库

sql链接: https://pan.baidu.com/s/18xmuhipX1EKuPLa3clC47g?pwd=abcd
在这里插入图片描述

创建同名空数据库 => localhost右键 => 运行SQL文件 => 刷新
在这里插入图片描述


⑵. 实体类

①. 自媒体用户信息

新建 heima-leadnews-model/src/main/java/com/heima/model/wemedia/pojos/WmUser.java 文件:

/**
 * <p>
 * 自媒体用户信息表
 * </p>
 *
 * @author itheima
 */
@Data
@TableName("wm_user")
public class WmUser implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    @TableField("ap_user_id")
    private Integer apUserId;

    @TableField("ap_author_id")
    private Integer apAuthorId;

    /**
     * 登录用户名
     */
    @TableField("name")
    private String name;

    /**
     * 登录密码
     */
    @TableField("password")
    private String password;

    /**
     * 盐
     */
    @TableField("salt")
    private String salt;

    /**
     * 昵称
     */
    @TableField("nickname")
    private String nickname;

    /**
     * 头像
     */
    @TableField("image")
    private String image;

    /**
     * 归属地
     */
    @TableField("location")
    private String location;

    /**
     * 手机号
     */
    @TableField("phone")
    private String phone;

    /**
     * 状态
            0 暂时不可用
            1 永久不可用
            9 正常可用
     */
    @TableField("status")
    private Integer status;

    /**
     * 邮箱
     */
    @TableField("email")
    private String email;

    /**
     * 账号类型
            0 个人 
            1 企业
            2 子账号
     */
    @TableField("type")
    private Integer type;

    /**
     * 运营评分
     */
    @TableField("score")
    private Integer score;

    /**
     * 最后一次登录时间
     */
    @TableField("login_time")
    private Date loginTime;

    /**
     * 创建时间
     */
    @TableField("created_time")
    private Date createdTime;

}

②. 自媒体登录

新建 heima-leadnews-model/src/main/java/com/heima/model/wemedia/dtos/WmLoginDto.java 文件:

@Data
public class WmLoginDto {

    /**
     * 用户名
     */
    private String name;
    /**
     * 密码
     */
    private String password;
}

⑶. 导入媒体微服务

微服务资源: https://pan.baidu.com/s/1JfRzXiawYo1Iixf8b2gaTw?pwd=abcd
在这里插入图片描述

解压至 leadnews-heima-service 目录下

编辑 heima-leadnews-service/pom.xml pom依赖:

    <modules>
        <module>heima-leadnews-user</module>
        <!--文章模块-->
        <module>heima-leadnews-article</module>
        <!--自媒体模块-->
        <module>heima-leadnews-wemedia</module>
    </modules>

刷新Maven

⑷. Nacos配置

新建 leadnews-wemedia 配置:

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/leadnews_wemedia?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: 123456
# 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置
mybatis-plus:
  mapper-locations: classpath*:mapper/*.xml
  # 设置别名包扫描路径,通过该属性可以给包中的类注册别名
  type-aliases-package: com.heima.model.media.pojos

⑸. 导入媒体网关

微服务资源: https://pan.baidu.com/s/1PusVjATzWk-Z299PTcQAAQ?pwd=abcd
在这里插入图片描述
解压至 heima-leadnews-gateway 目录下

编辑 heima-leadnews-gateway/pom.xml pom依赖:

    <modules>
        <module>heima-leadnews-app-gateway</module>
        <module>heima-leadnews-wemedia-gateway</module>
    </modules>

刷新Maven

⑹. Nacos配置

新建 leadnews-wemedia-gateway 配置:

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]': # 匹配所有请求
            allowedOrigins: "*" #跨域处理 允许所有的域
            allowedMethods: # 支持的方法
              - GET
              - POST
              - PUT
              - DELETE
      routes:
        # 平台管理
        - id: wemedia
          uri: lb://leadnews-wemedia
          predicates:
            - Path=/wemedia/**
          filters:
            - StripPrefix= 1

2. 前台搭建

⑴. 导入nginx

资源链接: https://pan.baidu.com/s/1TAwwtNcO7gWOUQOIvpgU3w?pwd=abcd

解压至 nginx 目录下(同APP端nginx)


⑵. 配置nginx.conf文件

(nginx包中)新建 D:\code\hm\leadnews\config\nginx-1.18.0\conf\leadnews.conf\heima-leadnews-wemedia.conf 文件:

upstream  heima-wemedia-gateway{
    server localhost:51602; # 根据网关去做的请求
}

server {
	listen 8802;
	location / {
		root D:/code/hm/leadnews/config/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;  #记录代理信息
	}
}

⑶. 测试

启动nginx,启动自媒体微服务和自媒体网关,自媒体地址: http://localhost:8802/#/login
在这里插入图片描述




二、自媒体素材管理

1. 图片上传

⑴. 实体类

新建 heima-leadnews-model/src/main/java/com/heima/model/wemedia/pojos/WmMaterial.java 文件:

/**
 * <p>
 * 自媒体图文素材信息表
 * </p>
 *
 * @author itheima
 */
@Data
@TableName("wm_material")
public class WmMaterial implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 自媒体用户ID
     */
    @TableField("user_id")
    private Integer userId;

    /**
     * 图片地址
     */
    @TableField("url")
    private String url;

    /**
     * 素材类型
            0 图片
            1 视频
     */
    @TableField("type")
    private Short type;

    /**
     * 是否收藏
     */
    @TableField("is_collection")
    private Short isCollection;

    /**
     * 创建时间
     */
    @TableField("created_time")
    private Date createdTime;

}

⑵. 网关

编辑 heima-leadnews-gateway/heima-leadnews-wemedia-gateway/src/main/java/com/heima/wemedia/gateway/filter/AuthorizeFilter.java 文件:

        //5.判断token是否有效
        try {
            Claims claimsBody = AppJwtUtil.getClaimsBody(token);
            //是否是过期
            int result = AppJwtUtil.verifyToken(claimsBody);
            if(result == 1 || result  == 2){
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            }

            // 网关: 进行token解析后,把解析后的用户信息存储到header中
            // 获取用户信息
            Object userId = claimsBody.get("id");

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

            // 重置请求
            exchange.mutate().request(serverHttpRequest);

        } catch (Exception e) {
            e.printStackTrace();
        }

⑶. 拦截器

①. 线程公共方法

新建 heima-leadnews-utils/src/main/java/com/heima/utils/thread/WmThreadLocalUtils.java 文件:

public class WmThreadLocalUtils {

    private final static ThreadLocal<WmUser> WM_USER_THREAD_LOCAL = new ThreadLocal<>();

    // 存入线程中
    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();
    }
}

②. 过滤器

新建 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/interceptor/WmTokenInterceptor.java 文件:

public class WmTokenInterceptor implements HandlerInterceptor {

    /**
     * 前置处理器: 得到hedaer中的用户信息, 并且存入到当前线程中
     * @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("userId");
        if(userId != null) {
            // 存入到当前线程中
            WmUser wmUser = new WmUser();
            wmUser.setId(Integer.valueOf(userId));
            WmThreadLocalUtils.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 {
        WmThreadLocalUtils.clear();
    }
}

③. 自定义拦截器

新建 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/config/WebMvcConfig.java 文件:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

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

⑷. 接口定义

说明
接口路径/api/v1/material/upload_picture
请求方式POST
参数MultipartFile
响应结果ResponseResult
①. 基础搭建
Ⅰ. Controller

新建 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/controller/v1/WmMaterialController.java 文件:

@RestController
@RequestMapping("/api/v1/material")
public class WmMaterialController {

    @PostMapping("upload_picture")
    public ResponseResult uploadPicture(MultipartFile multipartFile) {
        return null;
    }
}
Ⅱ. Mapper

新建 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/mapper/WmMaterialMapper.java 文件:

@Mapper
public interface WmMaterialMapper extends BaseMapper<WmMaterial> {
}
Ⅲ. Service

新建 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/service/WmMaterialService.java 文件:

public interface WmMaterialService extends IService<WmMaterial> {
}
Ⅳ. Impl

新建 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/service/impl/WmMaterialServiceImpl.java 文件:

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

②. 业务层

编辑 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/service/WmMaterialService.java 文件:

public interface WmMaterialService extends IService<WmMaterial> {

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

③. 配置
Ⅰ. 引入minio

编辑 heima-leadnews-service/heima-leadnews-wemedia/pom.xml 配置文件:

    <dependencies>
        <dependency>
            <groupId>com.heima</groupId>
            <artifactId>heima-file-starter</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
Ⅱ. Nacos配置

编辑 leadnews-wemedia 配置:

minio:
  accessKey: minio
  secretKey: minio123
  bucket: leadnews
  endpoint: http://192.168.200.130:9000
  readPath: http://192.168.200.130:9000

④. 业务层实现类

编辑 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/service/impl/WmMaterialServiceImpl.java 文件:

@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("-", "");
        // aa.kpg
        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();
        wmMaterial.setUserId(WmThreadLocalUtils.getUser().getId());
        wmMaterial.setUrl(fileId);
        wmMaterial.setIsCollection((short)0);
        wmMaterial.setType((short)0);
        wmMaterial.setCreatedTime(new Date());
        save(wmMaterial);

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


⑤. Controller

编辑 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/service/impl/WmMaterialServiceImpl.java 文件:

@RestController
@RequestMapping("/api/v1/material")
public class WmMaterialController {

    @Autowired
    private WmMaterialService wmMaterialService;

    @PostMapping("upload_picture")
    public ResponseResult uploadPicture(MultipartFile multipartFile) {
        return wmMaterialService.uploadPicture(multipartFile);
    }
}

⑸. 测试

启动nginx,启动自媒体微服务和自媒体网关,自媒体地址: http://localhost:8802/#/login
在这里插入图片描述

查看 数据库,新增了上传的素材图片
在这里插入图片描述



2. 图片列表

说明
接口路径/api/v1/material/list
请求方式POST
参数WmMaterialDto
响应结果ResponseResult

⑴. Dto

新建 heima-leadnews-model/src/main/java/com/heima/model/wemedia/dtos/WmMaterialDto.java 文件:

@Data
public class WmMaterialDto extends PageRequestDto {

    /**
     * 0 未收藏 1 已收藏
     */
    private Short isCollection;
}

⑵. Controller

编辑 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/controller/v1/WmMaterialController.java 文件:

    @PostMapping("/list")
    public ResponseResult findList(@RequestBody WmMaterialDto dto) {
        return null;
    }

⑶. Service

编辑 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/service/WmMaterialService.java 文件:

    /**
     * 图片列表
     * @param dto
     * @return
     */
    public ResponseResult findList(WmMaterialDto dto);

⑷. ServiceImpl

编辑 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/service/impl/WmMaterialServiceImpl.java 文件:

    /**
     * 素材列表查询
     * @param dto
     * @return
     */
    @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, WmThreadLocalUtils.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;
    }

⑸. 引入 MyBatisPlus 插件

编辑 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/WemediaApplication.java 文件:

    // myBatisPlus插件
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }

⑹. Controller

编辑 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/controller/v1/WmMaterialController.java 文件:

    @PostMapping("/list")
    public ResponseResult findList(@RequestBody WmMaterialDto dto) {
        return wmMaterialService.findList(dto);
    }

⑺. 测试

启动nginx,启动自媒体微服务和自媒体网关,自媒体地址: http://localhost:8802/#/login
在这里插入图片描述
在这里插入图片描述




三、文章管理

1. 频道列表查询

⑴. 接口定义

说明
接口路径/api/v1/channel/channels
请求方式POST
参数
响应结果ResponseResult

ResponseResult :

{
  "host": "null",
  "code": 0,
  "errorMessage": "操作成功",
  "data": [
    {
      "id": 4,
      "name": "java",
      "description": "java",
      "isDefault": true,
      "status": false,
      "ord": 3,
      "createdTime": "2019-08-16T10:55:41.000+0000"
    },
    Object {  ... },
    Object {  ... }
  ]
}

⑵. 实体类

新建 heima-leadnews-model/src/main/java/com/heima/model/wemedia/pojos/WmChannel.java 文件:

/**
 * <p>
 * 频道信息表
 * </p>
 *
 * @author itheima
 */
@Data
@TableName("wm_channel")
public class WmChannel implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 频道名称
     */
    @TableField("name")
    private String name;

    /**
     * 频道描述
     */
    @TableField("description")
    private String description;

    /**
     * 是否默认频道
     * 1:默认     true
     * 0:非默认   false
     */
    @TableField("is_default")
    private Boolean isDefault;

    /**
     * 是否启用
     * 1:启用   true
     * 0:禁用   false
     */
    @TableField("status")
    private Boolean status;

    /**
     * 默认排序
     */
    @TableField("ord")
    private Integer ord;

    /**
     * 创建时间
     */
    @TableField("created_time")
    private Date createdTime;

}

⑶. 基础搭建

①. Controller

新建 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/controller/v1/WmChannelController.java 文件:

@RestController
@RequestMapping("/api/v1/channel")
public class WmChannelController {

    @GetMapping("/channels")
    public ResponseResult findAll() {
        return null;
    }
}

②. Mapper

新建 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/mapper/WmChannelMapper.java 文件:

@Mapper
public interface WmChannelMapper extends BaseMapper<WmChannel> {
}

③. Service

新建 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/service/WmChannelService.java 文件:

public interface WmChannelService extends IService<WmChannel> {
}

④. ServiceImpl

新建 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/service/impl/WmChannelServiceImpl.java 文件:

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

⑷. Service

编辑 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/service/WmChannelService.java 文件:

public interface WmChannelService extends IService<WmChannel> {

    /**
     * 查询所有频道
     * @return
     */
    public ResponseResult findAll();
}

⑸. ServiceImpl

编辑 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/service/impl/WmChannelServiceImpl.java 文件:

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

    /**
     * 查询所有频道
     * @return
     */
    @Override
    public ResponseResult findAll() {
        return ResponseResult.okResult(list());
    }
}

⑹. Controller

编辑 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/controller/v1/WmChannelController.java 文件:

@RestController
@RequestMapping("/api/v1/channel")
public class WmChannelController {

    @Autowired
    private WmChannelService wmChannelService;

    @GetMapping("/channels")
    public ResponseResult findAll() {
        return wmChannelService.findAll();
    }
}

⑺. 测试

启动nginx,启动自媒体微服务和自媒体网关,自媒体地址: http://localhost:8802/#/login
在这里插入图片描述



2. 文章列表查询

⑴. 表结构分析

在这里插入图片描述

新建 heima-leadnews-model/src/main/java/com/heima/model/wemedia/pojos/WmNews.java 对应实体类:

/**
 * <p>
 * 自媒体图文内容信息表
 * </p>
 *
 * @author itheima
 */
@Data
@TableName("wm_news")
public class WmNews implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 自媒体用户ID
     */
    @TableField("user_id")
    private Integer userId;

    /**
     * 标题
     */
    @TableField("title")
    private String title;

    /**
     * 图文内容
     */
    @TableField("content")
    private String content;

    /**
     * 文章布局
            0 无图文章
            1 单图文章
            3 多图文章
     */
    @TableField("type")
    private Short type;

    /**
     * 图文频道ID
     */
    @TableField("channel_id")
    private Integer channelId;

    @TableField("labels")
    private String labels;

    /**
     * 创建时间
     */
    @TableField("created_time")
    private Date createdTime;

    /**
     * 提交时间
     */
    @TableField("submited_time")
    private Date submitedTime;

    /**
     * 当前状态
            0 草稿
            1 提交(待审核)
            2 审核失败
            3 人工审核
            4 人工审核通过
            8 审核通过(待发布)
            9 已发布
     */
    @TableField("status")
    private Short status;

    /**
     * 定时发布时间,不定时则为空
     */
    @TableField("publish_time")
    private Date publishTime;

    /**
     * 拒绝理由
     */
    @TableField("reason")
    private String reason;

    /**
     * 发布库文章ID
     */
    @TableField("article_id")
    private Long articleId;

    /**
     * //图片用逗号分隔
     */
    @TableField("images")
    private String images;

    @TableField("enable")
    private Short enable;
    
     //状态枚举类
    @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;
        }
    }

}

⑵. 接口定义

①. 接口说明
说明
接口路径/api/v1/news/list
请求方式POST
参数WmNewsPageReqDto
响应结果ResponseResult

ResponseResult :

{
  "host": "null",
  "code": 0,
  "errorMessage": "操作成功",
  "data": [
    Object { ... },
    Object { ... },
    Object { ... }
    
  ],
  "currentPage":1,
  "size":10,
  "total":21
}
②. Dto

新建 heima-leadnews-model/src/main/java/com/heima/model/wemedia/dtos/WmNewsPageReqDto.java 文件:

@Data
public class WmNewsPageReqDto extends PageRequestDto {

    /**
     * 状态
     */
    private Short status;
    /**
     * 开始时间
     */
    private Date beginPubDate;
    /**
     * 结束时间
     */
    private Date endPubDate;
    /**
     * 所属频道ID
     */
    private Integer channelId;
    /**
     * 关键字
     */
    private String keyword;
}

⑶. 基础搭建

①. Controller

新建 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/controller/v1/WmNewsController.java 文件:

@RestController
@RequestMapping("/api/v1/news")
public class WmNewsController {

    @PostMapping("/list")
    public ResponseResult findList(@RequestBody WmNewsPageReqDto dto) {
        return null;
    }
}

②. Mapper

新建 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/mapper/WmNewsMapper.java 文件:

@Mapper
public interface WmNewsMapper extends BaseMapper<WmNews> {
}

③. Service

新建 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/service/WmNewsService.java 文件:

public interface WmNewsService extends IService<WmNews> {
}

④. ServiceImpl

新建 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/service/impl/WmNewsServiceImpl.java 文件:

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

⑷. Service

编辑 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/service/WmNewsService.java 文件:

    /**
     * 条件查询文章列表
     * @param dto
     * @return
     */
    public ResponseResult findList(WmNewsPageReqDto dto);

⑸. ServiceImpl

编辑 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/service/impl/WmNewsServiceImpl.java 文件:

@Service
@Slf4j
@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();

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

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

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

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

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

        // 2.6 发布时间倒序查询
        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;
    }
}

⑹. Controller

编辑 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/controller/v1/WmNewsController.java 文件:

@RestController
@RequestMapping("/api/v1/news")
public class WmNewsController {

    @Autowired
    private WmNewsService wmNewsService;

    @PostMapping("/list")
    public ResponseResult findList(@RequestBody WmNewsPageReqDto dto) {
        return wmNewsService.findList(dto);
    }
}

⑺. 测试

启动nginx,启动自媒体微服务和自媒体网关,自媒体地址: http://localhost:8802/#/login
在这里插入图片描述



3. 文章发布

⑴. 需求分析

在这里插入图片描述


⑵. 表结构分析

①. 表关系

wm_material 素材表
在这里插入图片描述

wm_news_material 文章素材关系表
在这里插入图片描述

②. 实体类

新建 heima-leadnews-model/src/main/java/com/heima/model/wemedia/pojos/WmNewsMaterial.java 文件:

/**
 * <p>
 * 自媒体图文引用素材信息表
 * </p>
 *
 * @author itheima
 */
@Data
@TableName("wm_news_material")
public class WmNewsMaterial implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 素材ID
     */
    @TableField("material_id")
    private Integer materialId;

    /**
     * 图文ID
     */
    @TableField("news_id")
    private Integer newsId;

    /**
     * 引用类型
            0 内容引用
            1 主图引用
     */
    @TableField("type")
    private Short type;

    /**
     * 引用排序
     */
    @TableField("ord")
    private Short ord;

}

⑶. 思路分析

该功能为保存、修改(是否有id)、保存草稿的共有方法
在这里插入图片描述

  1. 前端提交发布或保存为草稿
  2. 后台判断请求中是否包含了文章id
  3. 如果不包含id,则为新增
    3.1. 执行新增文章的操作
    3.2 关联文章内容图片与素材的关系
    3.3 关联文章封面图片与素材的关系
  4. 如果包含了id,则为修改请求
    4.1 删除该文章与素材的所有关系
    4.2 执行修改操作
    4.3 关联文章内容图片与素材的关系
    4.4 关联文章封面图片与素材的关系

⑷. 接口定义

①. 接口说明
说明
接口路径/api/v1/channel/submit
请求方式POST
参数WmNewsDto
响应结果ResponseResult

ResponseResult:

{
    “code”:501,
    “errorMessage”:“参数失效"
}

{
    “code”:200,
    “errorMessage”:“操作成功"
}

{
    “code”:501,
    “errorMessage”:“素材引用失效"
}

②. Dto

新建 heima-leadnews-model/src/main/java/com/heima/model/wemedia/dtos/WmNewsDto.java 文件:

@Data
public class WmNewsDto {
    
    private Integer id;
     /**
     * 标题
     */
    private String title;
     /**
     * 频道id
     */
    private Integer channelId;
     /**
     * 标签
     */
    private String labels;
     /**
     * 发布时间
     */
    private Date publishTime;
     /**
     * 文章内容
     */
    private String content;
     /**
     * 文章封面类型  0 无图 1 单图 3 多图 -1 自动
     */
    private Short type;
     /**
     * 提交时间
     */
    private Date submitedTime; 
     /**
     * 状态 提交为1  草稿为0
     */
    private Short status;
     
     /**
     * 封面图片列表 多张图以逗号隔开
     */
    private List<String> images;
}

⑸. 基础搭建

①. Controller

编辑 heima-leadnews-model/src/main/java/com/heima/model/wemedia/dtos/WmNewsPageReqDto.java 文件:

    @PostMapping("/submit")
    public ResponseResult submitNews(@RequestBody WmNewsDto dto) {
        return null;
    }

②. Mapper

新建 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/mapper/WmNewsMaterialMapper.java 文件:

@Mapper
public interface WmNewsMaterialMapper extends BaseMapper<WmNewsMaterial> {

    // 批量保存文章素材的方法
    void saveRelations(@Param("materialIds") List<Integer> materialIds, @Param("newsId") Integer newsId, @Param("type") Short type);
}

③. Mapper配置文件

新建 heima-leadnews-service/heima-leadnews-wemedia/src/main/resources/mapper/WmNewsMaterialMapper.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.heima.wemedia.mapper.WmNewsMaterialMapper">

    <insert id="saveRelations">
        insert into wm_news_material (material_id,news_id,type,ord)
        values
        <foreach collection="materialIds" index="ord" item="mid" separator=",">
            (#{mid},#{newsId},#{type},#{ord})
        </foreach>
    </insert>

</mapper>

④. Service

编辑 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/service/WmNewsService.java 文件:

    /**
     * 发布/修改文章或保存为草稿
     * @param dto
     * @return
     */
    public ResponseResult submitNews(WmNewsDto dto);

⑤. ServiceImpl

编辑 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/service/impl/WmNewsServiceImpl.java 文件:

    /**
     * 发布/修改文章或发布为草稿
     * @param dto
     * @return
     */
    @Override
    public ResponseResult submitNews(WmNewsDto dto) {
        return null;
    }

⑹. 定义常量

新建 heima-leadnews-common/src/main/java/com/heima/common/constants/WemediaConstants.java 文件:

public class WemediaConstants {

    public static final Short COLLECT_MATERIAL = 1;//收藏

    public static final Short CANCEL_COLLECT_MATERIAL = 0;//取消收藏

    public static final String WM_NEWS_TYPE_IMAGE = "image";

    public static final Short WM_NEWS_NONE_IMAGE = 0;
    public static final Short WM_NEWS_SINGLE_IMAGE = 1;
    public static final Short WM_NEWS_MANY_IMAGE = 3;
    public static final Short WM_NEWS_TYPE_AUTO = -1;

    public static final Short WM_CONTENT_REFERENCE = 0;
    public static final Short WM_COVER_REFERENCE = 1;
}

⑺. 自定义提示信息

编辑 heima-leadnews-model/src/main/java/com/heima/model/common/enums/AppHttpCodeEnum.java 文件:

    NEED_ADMIND(3001,"需要管理员权限"),

    // 自媒体文章错误 3501~3600
    MATERIAL_REFERENCE_FAIL(3501, "素材引用失效");

⑻. ServiceImpl(内容图片与素材)

编辑 heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/service/impl/WmNewsServiceImpl.java 文件:

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

    @Autowired
    private WmNewsMaterialMapper wmNewsMaterialMapper;

    @Autowired
    private WmMaterialMapper wmMaterialMapper;

    /**
     * 条件查询文章列表
     * @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();

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

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

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

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

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

        // 2.6 发布时间倒序查询
        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;
    }

    /**
     * 发布/修改文章或发布为草稿
     * @param dto
     * @return
     */
    @Override
    public ResponseResult submitNews(WmNewsDto dto) {

        // 1. 参数校验
        if(dto == null || dto.getContent() == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }

        // 2. 保存或修改文章

        WmNews wmNews = new WmNews();
        // 属性拷贝 属性名词和类型相同才能拷贝
        BeanUtils.copyProperties(dto, wmNews);
        // 封面图片  list---> string
        if(dto.getImages() != null && dto.getImages().size() > 0) {
            String imageStr = StringUtils.join(dto.getImages(), ",");
            wmNews.setImages(imageStr);
        }
        // 如果当前封面为自动类型 -1
        if(dto.getType().equals(WemediaConstants.WM_NEWS_TYPE_AUTO)) {
            wmNews.setType(null);
        }
        saveOrUpdateWmNews(wmNews);

        // 3. 判断是否为草稿, 如果是草稿, 结束当前方法
        if(dto.getStatus().equals(WmNews.Status.NORMAL.getCode())) {
            return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
        }

        // 4. 保存文章内容图片和素材的关系
        // 提取文章内容中的图片信息
        List<String> materials = ectractUrlInfo(dto.getContent());

        saveRelativeInfoForContent(materials, wmNews.getId());

        // 5. 保存文章封面图片和素材的关系, 如果当然布局是自动, 需要匹配封面图片
        saveRelativeInfoForCover(dto, wmNews, materials);

        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
    }

    /**
     * 第一个功能:如果当前封面类型为自动,则设置封面类型的数据
     * 匹配规则:
     * 1,如果内容图片大于等于1,小于3  单图  type 1
     * 2,如果内容图片大于等于3  多图  type 3
     * 3,如果内容没有图片,无图  type 0
     *
     * 第二个功能:保存封面图片与素材的关系
     * @param dto
     * @param wmNews
     * @param materials
     */
    private void saveRelativeInfoForCover(WmNewsDto dto, WmNews wmNews, List<String> materials) {
        // 如果当前封面类型为自动,则设置封面类型的数据
        List<String> images = dto.getImages();

        if(dto.getType().equals(WemediaConstants.WM_NEWS_TYPE_AUTO)) {
            // 多图
            if(materials.size() >= 3) {
                wmNews.setType(WemediaConstants.WM_NEWS_MANY_IMAGE);
                images = materials.stream().limit(3).collect(Collectors.toList());
            } else if(materials.size() >= 1 && materials.size() < 3) {
                // 单图
                wmNews.setType(WemediaConstants.WM_NEWS_SINGLE_IMAGE);
                images = materials.stream().limit(1).collect(Collectors.toList());
            } else {
                // 无图
                wmNews.setType(WemediaConstants.WM_NEWS_NONE_IMAGE);
            }

            // 修改文章
            if(images != null && images.size() > 0) {
                wmNews.setImages(StringUtils.join(images, ","));
            }
            updateById(wmNews);

            // 保存封面图片与素材的关系
            if(images != null && images.size() > 0) {
                saveRelativeInfo(images, wmNews.getId(), WemediaConstants.WM_COVER_REFERENCE);
            }
        }
    }

    /**
     * 处理文章内容图片与素材的关系
     * @param materials
     * @param newsId
     */
    private void saveRelativeInfoForContent(List<String> materials, Integer newsId) {
        saveRelativeInfo(materials, newsId, WemediaConstants.WM_CONTENT_REFERENCE);
    }

    /**
     * 保存文章图片与素材的关系到数据库中
     * @param materials
     * @param newsId
     * @param type
     */
    private void saveRelativeInfo(List<String> materials, Integer newsId, Short type) {
        // 参数校验
        if(materials != null && !materials.isEmpty()) {
            // 通过图片的url查询素材的id
            List<WmMaterial> dbMaterials = wmMaterialMapper.selectList(Wrappers.<WmMaterial>lambdaQuery().in(WmMaterial::getUrl, materials));

            // 判断素材是否有效
            if(dbMaterials == null ||dbMaterials.size() == 0) {
                // 提示调用者素材失效(还可以进行数据回滚)
                throw new CustomException(AppHttpCodeEnum.MATERIAL_REFERENCE_FAIL);
            }

            // 判断素材和数据库素材是否一致
            if(dbMaterials.size() != materials.size()) {
                // 提示调用者素材失效(还可以进行数据回滚)
                throw new CustomException(AppHttpCodeEnum.MATERIAL_REFERENCE_FAIL);
            }

            List<Integer> idList = dbMaterials.stream().map(WmMaterial::getId).collect(Collectors.toList());

            // 批量保存
            wmNewsMaterialMapper.saveRelations(idList, newsId, type);
        }
    }

    /**
     * 提取文章内容中的图片信息
     * @param content
     * @return
     */
    private List<String> ectractUrlInfo(String content) {
        List<String> materials = new ArrayList<>();

        List<Map> maps = JSON.parseArray(content, Map.class);
        for(Map map : maps) {
            if(map.get("type").equals("image")) {
                String imgUrl = (String) map.get("value");
                materials.add(imgUrl);
            }
        }

        return materials;
    }

    /**
     * 保存或修改文章
     * @param wmNews
     */
    private void saveOrUpdateWmNews(WmNews wmNews) {
        // 属性补全
        wmNews.setUserId(WmThreadLocalUtils.getUser().getId());
        wmNews.setCreatedTime(new Date());
        wmNews.setSubmitedTime(new Date());
        wmNews.setEnable((short) 0); // 默认上架

        if(wmNews.getId() == null) {
            // 保存文章
            save(wmNews);
        } else {
            // 修改文章
            wmNewsMaterialMapper.delete(Wrappers.<WmNewsMaterial>lambdaQuery().eq(WmNewsMaterial::getNewsId, wmNews.getId()));
            updateById(wmNews);
        }
    }
}

⑼. Controller

编辑 heima-leadnews-model/src/main/java/com/heima/model/wemedia/dtos/WmNewsPageReqDto.java 文件:

    @PostMapping("/submit")
    public ResponseResult submitNews(@RequestBody WmNewsDto dto) {
        return wmNewsService.submitNews(dto);
    }

⑽. 测试

启动nginx,启动自媒体微服务和自媒体网关,自媒体地址: http://localhost:8802/#/login

①. 单图
Ⅰ. 发布文章

在这里插入图片描述
在这里插入图片描述

Ⅱ. 内容列表

在这里插入图片描述

Ⅲ. 数据库

在这里插入图片描述

②. 草稿
Ⅰ. 发布文章

在这里插入图片描述

Ⅱ. 内容列表

在这里插入图片描述

Ⅲ. 数据库

数据库有文章,但是图片和素材未关联
在这里插入图片描述
在这里插入图片描述


③. 自动
Ⅰ. 发布文章

在这里插入图片描述

Ⅱ. 内容列表

在这里插入图片描述

Ⅲ. 数据库

数据库有文章,内容、封面图片和素材已关联
在这里插入图片描述
在这里插入图片描述



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

后海 0_o

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值