目录
1.1)需求分析
1.2)表结构分析
ap_article 文章基本信息表
ap_article_config 文章配置表
ap_article_content 文章内容表
导入文章数据库
实现思路
接口定义
功能实现
定义接口
编写mapper文件
编写业务层代码
实现类:
定义常量类
编写控制器代码
swagger测试
1.1)需求分析
文章布局展示:
所谓文章加载,指的是从数据库中将文章的数据查询出来,然后传递给前端页面进行展示。
根据图片可得,我们的文章对象可以是无图,单图,多图等多种形态。
1.2)表结构分析
在表结构设计上,采用了分库分表的思想,简而言之就是根据我们项目来创建库,然后将该库的表进行细分,如图所示,我们建立了一个名为leadnews_article库,专门用来存放黑马头条中跟文章有关的信息,其中有文章表,文章配置表,文章内容表。这样的好处就是将文章大宽表进行细分,冷热数据分离,大字段分离等。像我们的微服务项目,经常会这样设计表。
在这里,嵌入一个Java数据类型和MySQL数据类型的对照表,方便在开发和数据库设计时作为参考,直接跳转链接直达:
Java和MySQL数据类型对照表https://blog.csdn.net/weixin_43822632/article/details/144501788?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522af79b0f2b9c1bdd19e156c4a27fe22d7%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=af79b0f2b9c1bdd19e156c4a27fe22d7&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-5-144501788-null-null.142^v102^pc_search_result_base4&utm_term=java%E5%92%8Cmysql%E7%9A%84%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%AF%B9%E7%85%A7%E5%9B%BE&spm=1018.2226.3001.4187
ap_article 文章基本信息表
代码如下:
-- auto-generated definition
create table ap_article
(id bigint unsigned auto_incrementprimary key,title varchar(50) null comment '标题',author_id int unsigned null comment '文章作者的ID',author_name varchar(20) null comment '作者昵称',channel_id int unsigned null comment '文章所属频道ID',channel_name varchar(10) null comment '频道名称',layout tinyint unsigned null comment '文章布局0 无图文章1 单图文章2 多图文章',flag tinyint unsigned null comment '文章标记0 普通文章1 热点文章2 置顶文章3 精品文章4 大V 文章',images varchar(1000) null comment '文章图片多张逗号分隔',labels varchar(500) null comment '文章标签最多3个 逗号分隔',likes int unsigned null comment '点赞数量',collection int unsigned null comment '收藏数量',comment int unsigned null comment '评论数量',views int unsigned null comment '阅读数量',province_id int unsigned null comment '省市',city_id int unsigned null comment '市区',county_id int unsigned null comment '区县',created_time datetime null comment '创建时间',publish_time datetime null comment '发布时间',sync_status tinyint(1) default 0 null comment '同步状态',origin tinyint unsigned default '0' null comment '来源',static_url varchar(150) null
)comment '文章信息表,存储已发布的文章';
ap_article_config 文章配置表
代码如下:
-- auto-generated definition
create table ap_article_config
(id bigint unsigned auto_increment comment '主键'primary key,article_id bigint unsigned null comment '文章ID',is_comment tinyint unsigned null comment '是否可评论',is_forward tinyint unsigned null comment '是否转发',is_down tinyint unsigned null comment '是否下架',is_delete tinyint unsigned null comment '是否已删除'
)comment 'APP已发布文章配置表';create index idx_article_idon ap_article_config (article_id);
ap_article_content 文章内容表
代码如下:
-- auto-generated definition
create table ap_article_content
(id bigint unsigned auto_increment comment '主键'primary key,article_id bigint unsigned null comment '文章ID',content longtext null comment '文章内容'
)comment 'APP已发布文章内容表';create index idx_article_idon ap_article_content (article_id);
导入文章数据库
ap_article文章表对应实体
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;import java.io.Serializable;
import java.util.Date;/*** <p>* 文章信息表,存储已发布的文章* </p>** @author itheima*/@Data
@TableName("ap_article")//指定数据库中的名字
public class ApArticle implements Serializable {//IdType.ID_WORKER指雪花算法生成id@TableId(value = "id",type = IdType.ID_WORKER)private Long id;/*** 标题*/private String title;/*** 作者id*/@TableField("author_id")private Long authorId;/*** 作者名称*/@TableField("author_name")private String authorName;/*** 频道id*/@TableField("channel_id")private Integer channelId;/*** 频道名称*/@TableField("channel_name")private String channelName;/*** 文章布局 0 无图文章 1 单图文章 2 多图文章*/private Short layout;/*** 文章标记 0 普通文章 1 热点文章 2 置顶文章 3 精品文章 4 大V 文章*/private Byte flag;/*** 文章封面图片 多张逗号分隔*/private String images;/*** 标签*/private String labels;/*** 点赞数量*/private Integer likes;/*** 收藏数量*/private Integer collection;/*** 评论数量*/private Integer comment;/*** 阅读数量*/private Integer views;/*** 省市*/@TableField("province_id")private Integer provinceId;/*** 市区*/@TableField("city_id")private Integer cityId;/*** 区县*/@TableField("county_id")private Integer countyId;/*** 创建时间*/@TableField("created_time")private Date createdTime;/*** 发布时间*/@TableField("publish_time")private Date publishTime;/*** 同步状态*/@TableField("sync_status")private Boolean syncStatus;/*** 来源*/private Boolean origin;/*** 静态页面地址*/@TableField("static_url")private String staticUrl;
}
ap_article_config文章配置对应实体类
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;/*** <p>* APP已发布文章配置表* </p>** @author itheima*/@Data
@NoArgsConstructor//添加无参构造方法
@TableName("ap_article_config")
//文章配置类,是一个pojo类,和数据库中对应的表一一对应
public class ApArticleConfig implements Serializable {//添加有参构造方法,因为这个构造方法只有一个参数字段,所以需要手动声明//如果是需要全参构造方法,则可以直接在类上使用@AllArgsConstructor注解//这个构造方法是只给articleId赋值,其他字段采取默认值即可public ApArticleConfig(Long articleId){this.articleId = articleId;//文章idthis.isComment = true;//默认开启评论this.isForward = true;//默认开启转发this.isDelete = false;//默认没有删除this.isDown = false;//默认没有下架}@TableId(value = "id",type = IdType.ID_WORKER)private Long id;/*** 文章id*/@TableField("article_id")private Long articleId;/*** 是否可评论* true: 可以评论 1* false: 不可评论 0*/@TableField("is_comment")private Boolean isComment;/*** 是否转发* true: 可以转发 1* false: 不可转发 0*/@TableField("is_forward")private Boolean isForward;/*** 是否下架* true: 下架 1* false: 没有下架 0*/@TableField("is_down")private Boolean isDown;/*** 是否已删除* true: 删除 1* false: 没有删除 0*/@TableField("is_delete")private Boolean isDelete;
}
ap_article_content 文章内容对应的实体类
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;import java.io.Serializable;@Data
@TableName("ap_article_content")
public class ApArticleContent implements Serializable {//IdType.ID_WORKER指雪花算法生成id@TableId(value = "id",type = IdType.ID_WORKER)private Long id;/*** 文章id*/@TableField("article_id")private Long articleId;/*** 文章内容*/private String content;
}
实现思路
1,在默认频道展示10条文章信息
2,可以切换频道查看不同种类文章
3,当用户下拉可以加载最新的文章(分页)本页文章列表中发布时间为最大的时间为依据
4,当用户上拉可以加载更多的文章信息(按照发布时间)本页文章列表中发布时间最小的时间为依据
5,如果是当前频道的首页,前端传递默认参数:
-
maxBehotTime:0(毫秒)
-
minBehotTime:20000000000000(毫秒)--->2063年
接口定义
ArticleHomeDto
import lombok.Data;import java.util.Date;@Data//注解@Data是lombok提供的注解,它能自动生成get、set方法,还能自动生成equals、hashCode、toString方法。
//这是文章首页dto,用来封装从前端传递过来的请求参数
public class ArticleHomeDto {// 最大时间Date maxBehotTime;// 最小时间Date minBehotTime;// 分页sizeInteger size;// 频道IDString tag;
}
功能实现
导入heima-leadnews-article微服务:
注意:需要在heima-leadnews-service的pom文件夹中添加子模块信息,如下:
当我们在父工程下创建一个子工程时,需要在父工程中添加<module>标签,指名子工程的名字。
需要在nacos中添加对应的配置:
spring:datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/leadnews_article?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTCusername: rootpassword: root
# 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置
mybatis-plus:mapper-locations: classpath*:mapper/*.xml# 设置别名包扫描路径,通过该属性可以给包中的类注册别名type-aliases-package: com.heima.model.article.pojos
定义接口
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.common.dtos.ResponseResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/api/v1/article")
public class ArticleHomeController {@PostMapping("/load")public ResponseResult load(@RequestBody ArticleHomeDto dto) {return null;}@PostMapping("/loadmore")public ResponseResult loadMore(@RequestBody ArticleHomeDto dto) {return null;}@PostMapping("/loadnew")public ResponseResult loadNew(@RequestBody ArticleHomeDto dto) {return null;}
}
编写mapper文件
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.pojos.ApArticle;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;import java.util.List;@Mapper
public interface ApArticleMapper extends BaseMapper<ApArticle> {public List<ApArticle> loadArticleList(@Param("dto") ArticleHomeDto dto, @Param("type") Short type);}
对应的映射文件
在resources中新建mapper/ApArticleMapper.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.article.mapper.ApArticleMapper"><resultMap id="resultMap" type="com.heima.model.article.pojos.ApArticle"><id column="id" property="id"/><result column="title" property="title"/><result column="author_id" property="authorId"/><result column="author_name" property="authorName"/><result column="channel_id" property="channelId"/><result column="channel_name" property="channelName"/><result column="layout" property="layout"/><result column="flag" property="flag"/><result column="images" property="images"/><result column="labels" property="labels"/><result column="likes" property="likes"/><result column="collection" property="collection"/><result column="comment" property="comment"/><result column="views" property="views"/><result column="province_id" property="provinceId"/><result column="city_id" property="cityId"/><result column="county_id" property="countyId"/><result column="created_time" property="createdTime"/><result column="publish_time" property="publishTime"/><result column="sync_status" property="syncStatus"/><result column="static_url" property="staticUrl"/></resultMap><select id="loadArticleList" resultMap="resultMap">SELECTaa.*FROM`ap_article` aaLEFT JOIN ap_article_config aac ON aa.id = aac.article_id<where>and aac.is_delete != 1and aac.is_down != 1<!-- loadmore --><if test="type != null and type == 1">and aa.publish_time <![CDATA[<]]> #{dto.minBehotTime}</if><if test="type != null and type == 2">and aa.publish_time <![CDATA[>]]> #{dto.maxBehotTime}</if><if test="dto.tag != '__all__'">and aa.channel_id = #{dto.tag}</if></where>order by aa.publish_time desclimit #{dto.size}</select></mapper>
编写业务层代码
import com.baomidou.mybatisplus.extension.service.IService;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.common.dtos.ResponseResult;import java.io.IOException;public interface ApArticleService extends IService<ApArticle> {/*** 根据参数加载文章列表* @param loadtype 1为加载更多 2为加载最新* @param dto* @return*/ResponseResult load(Short loadtype, ArticleHomeDto dto);}
实现类:
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.ApArticleService;
import com.heima.common.constants.ArticleConstants;
import com.heima.model.article.dtos.ArticleHomeDto;import com.heima.model.article.pojos.ApArticle;
import com.heima.model.common.dtos.ResponseResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.util.Date;
import java.util.List;@Service
@Transactional
@Slf4j
public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements ApArticleService {// 单页最大加载的数字private final static short MAX_PAGE_SIZE = 50;@Autowiredprivate ApArticleMapper apArticleMapper;/*** 根据参数加载文章列表* @param loadtype 1为加载更多 2为加载最新* @param dto* @return*/@Overridepublic ResponseResult load(Short loadtype, ArticleHomeDto dto) {//1.校验参数Integer size = dto.getSize();if(size == null || size == 0){size = 10;}size = Math.min(size,MAX_PAGE_SIZE);dto.setSize(size);//类型参数检验if(!loadtype.equals(ArticleConstants.LOADTYPE_LOAD_MORE)&&!loadtype.equals(ArticleConstants.LOADTYPE_LOAD_NEW)){loadtype = ArticleConstants.LOADTYPE_LOAD_MORE;}//文章频道校验if(StringUtils.isEmpty(dto.getTag())){dto.setTag(ArticleConstants.DEFAULT_TAG);}//时间校验if(dto.getMaxBehotTime() == null) dto.setMaxBehotTime(new Date());if(dto.getMinBehotTime() == null) dto.setMinBehotTime(new Date());//2.查询数据List<ApArticle> apArticles = apArticleMapper.loadArticleList(dto, loadtype);//3.结果封装ResponseResult responseResult = ResponseResult.okResult(apArticles);return responseResult;}}
定义常量类
public class ArticleConstants {public static final Short LOADTYPE_LOAD_MORE = 1;public static final Short LOADTYPE_LOAD_NEW = 2;public static final String DEFAULT_TAG = "__all__";}
编写控制器代码
import com.heima.article.service.ApArticleService;
import com.heima.common.constants.ArticleConstants;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.common.dtos.ResponseResult;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/api/v1/article")
public class ArticleHomeController {@Autowiredprivate ApArticleService apArticleService;@PostMapping("/load")public ResponseResult load(@RequestBody ArticleHomeDto dto) {return apArticleService.load(ArticleConstants.LOADTYPE_LOAD_MORE,dto);}@PostMapping("/loadmore")public ResponseResult loadMore(@RequestBody ArticleHomeDto dto) {return apArticleService.load(ArticleConstants.LOADTYPE_LOAD_MORE,dto);}@PostMapping("/loadnew")public ResponseResult loadNew(@RequestBody ArticleHomeDto dto) {return apArticleService.load(ArticleConstants.LOADTYPE_LOAD_NEW,dto);}
}
swagger测试
第一:在app网关的微服务的nacos的配置中心添加文章微服务的路由,完整配置如下:
spring:cloud:gateway:globalcors:cors-configurations:'[/**]': # 匹配所有请求allowedOrigins: "*" #跨域处理 允许所有的域allowedMethods: # 支持的方法- GET- POST- PUT- DELETEroutes:# 用户微服务- id: useruri: lb://leadnews-userpredicates:- Path=/user/**filters:- StripPrefix= 1# 文章微服务- id: articleuri: lb://leadnews-articlepredicates:- Path=/article/**filters:- StripPrefix= 1
第二:启动nginx,直接使用前端项目测试,启动文章微服务,用户微服务、app网关微服务