一:获取所有频道
1.需求分析
当我们点击内容管理时候,页面会自动发送请求获取频道列表(Java、MySql、大数据、推荐等),这时候用户可以进行频道的选择以过滤其他频道的文章。
编辑
2.表结构
编辑数据库表字段有频道名称、频道描述、是否默认频道、频道状态、默认排序、创建时间,其对应的实体类为:
package com.my.model.wemedia.pojos; 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("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; }
3.接口定义
说明 | |
接口路径 | /api/v1/channel/channels |
请求方式 | POST |
参数 | 无 |
响应结果 | ResponseResult |
4.功能实现
实现代码不难,就是简单地从数据库中获取所有频道的信息并返回,为了节省篇幅我这里就不将代码放上来了,可以自己动手实现一下。
二:查询文章
1.需求说明
在内容列表页面,我们可以通过特定条件筛选文章,比如按照文章的状态、频道、发布时间等筛选出自己想要的文章信息。
编辑
2.表结构
编辑 自媒体文章表字段比较多,主要包括用户id、标题、图文内容等一些文章信息,这时候你可能会有这样的疑问,为什么前面移动端是将表格拆分成三份这里不进行拆分。我们首先要明确的是拆分的目的及意义是什么,前面说过拆分是为了减轻数据库压力,减少IO操作,因为移动端用户量是相当大的,而且大多数时候用户只是刷新列表并不用查看文章详情。但是在自媒体创作端则不同,首先用户量不大,其次一般创作者在进行文章管理时候都会对文章进行修改,这时候就需要获取文章详细信息,把这些信息封装成一个表比较好操作,同时数据库压力不会很大。
package com.my.model.wemedia.pojos; 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 org.apache.ibatis.type.Alias; import java.io.Serializable; import java.time.LocalDateTime; import java.util.Date; /** * <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; } } }
3.接口定义
说明 | |
接口路径 | /api/v1/news/list |
请求方式 | POST |
参数 | WmNewsPageReqDto |
响应结果 | ResponseResult |
WmNewsPageReqDto :
package com.my.model.wemedia.dtos; import com.my.model.common.dtos.PageRequestDto; import lombok.Data; import java.util.Date; @Data public class WmNewsPageReqDto extends PageRequestDto { /** * 状态 */ private Short status; /** * 开始时间 */ private Date beginPubDate; /** * 结束时间 */ private Date endPubDate; /** * 所属频道ID */ private Integer channelId; /** * 关键字 */ private String keyword; }
4.功能实现
package com.my.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.my.model.common.dtos.PageResponseResult; import com.my.model.common.dtos.ResponseResult; import com.my.model.wemedia.dtos.WmNewsPageReqDto; import com.my.model.wemedia.pojos.WmNews; import com.my.utils.thread.WmThreadLocalUtils; import lombok.extern.slf4j.Slf4j; 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 findContentList(WmNewsPageReqDto dto) { //1.参数检查 dto.checkParam(); //2.分页条件查询 IPage<WmNews> page = new Page<>(dto.getPage(),dto.getSize()); LambdaQueryWrapper<WmNews> lqw = new LambdaQueryWrapper<>(); //状态查询 lqw.eq(dto.getStatus() != null,WmNews::getStatus,dto.getStatus()); //频道精确查询 lqw.eq(dto.getChannelId() != null,WmNews::getChannelId,dto.getChannelId()); //时间范围查询 if(dto.getBeginPubDate() != null && dto.getEndPubDate() != null) { lqw.between(WmNews::getPublishTime,dto.getBeginPubDate(),dto.getEndPubDate()); } //关键字模糊查询 lqw.eq(dto.getKeyword() != null,WmNews::getContent,dto.getKeyword()); //查询当前登录人的文章 lqw.eq(WmNews::getUserId, WmThreadLocalUtils.getUser().getId()); //按照发布时间倒序排序 lqw.orderByDesc(WmNews::getPublishTime); page = page(page, lqw); //3.结果返回 ResponseResult responseResult = new PageResponseResult(dto.getPage(), dto.getSize(), (int) page.getTotal()); responseResult.setData(page.getRecords()); return responseResult; } }
三:文章发布
1.需求分析
编辑
文章的发布是这个项目的难点之一,因为涉及到文章内容和素材的关系,这种关系又分为内容引用和封面引用两种。当用户选择的是自动设置封面时候,我们需要根据情况选择是设置无封面、单图封面、双图封面、多图封面。在提交部分,创作者可以选择保存为草稿,也可以选择提交审核,审核通过即可发表,此外,创作者还可以选择定时发布文章,不过审核部分和定时发布部分留到后面再说。
2.表结构
除了文章表之外,我们还需要另外两张表,即素材表和素材关系表:
wm_material 素材表
编辑wm_news_material 文章素材关系表
编辑这三张表的关系见下图:
编辑可以看到文章表、素材表和素材关系表之间的关系都是一对多的关系,因为一篇文章可能包含多张素材,一张素材也可能被多次引用。
3.实现思路
编辑
当创作者点击保存草稿或者提交审核之后,首先应根据文章有无id来判断这是修改还是新增文章,假如有id则说明为修改文章,执行修改操作;若无id表明为新增操作,执行新增操作。然后判断是否为草稿,若为草稿则不需要保存素材和文章图片的关系,因为草稿是不用发布到移动端的,素材关系表是移动端使用到的。
4.代码实现
package com.my.wemedia.service.impl; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.my.common.constans.WemediaConstants; import com.my.common.exception.CustomException; import com.my.model.common.dtos.ResponseResult; import com.my.model.common.enums.AppHttpCodeEnum; import com.my.model.wemedia.dtos.WmNewsDto; import com.my.model.wemedia.pojos.WmMaterial; import com.my.model.wemedia.pojos.WmNews; import com.my.model.wemedia.pojos.WmNewsMaterial; import com.my.utils.thread.WmThreadLocalUtils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; import java.util.stream.Collectors; @Slf4j @Service @Transactional public class WmNewsServiceImpl extends ServiceImpl<WmNewsMapper, WmNews> implements WmNewsService { /** * 提交文章 * @param dto * @return */ @Override public ResponseResult submitNews(WmNewsDto dto) { //1.参数校验 if(dto == null || dto.getContent().length() == 0) { return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID); } //2.保存或修改文章 //2.1属性拷贝 WmNews wmNews = new WmNews(); BeanUtils.copyProperties(dto,wmNews); //2.2设置封面图片 if(dto.getImages() != null && dto.getImages().size() != 0) { String images = StringUtils.join(dto.getImages(), ","); wmNews.setImages(images); } //2.3封面类型为自动 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.不是草稿 //4.1保存文章图片素材与文章关系 //4.1.1提取图片素材列表 List<String> imagesList = getImagesList(dto); //4.1.2保存 saveRelatedImages(imagesList,wmNews.getId(),WemediaConstants.WM_CONTENT_REFERENCE); //4.2保存封面图片和文章关系 saveRelatedCover(dto,imagesList,wmNews); return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS); } @Autowired private WmNewsMaterialMapper wmNewsMaterialMapper; private void saveOrUpdateWmNews(WmNews wmNews) { wmNews.setUserId(WmThreadLocalUtils.getUser().getId()); wmNews.setCreatedTime(new Date()); wmNews.setSubmitedTime(new Date()); wmNews.setEnable((short) 1); if(wmNews.getId() == null) { //保存 save(wmNews); } else { //修改 //删除文章和素材的关系 wmNewsMaterialMapper.delete(Wrappers.<WmNewsMaterial>lambdaQuery().eq(WmNewsMaterial::getNewsId,wmNews.getId())); updateById(wmNews); } } /** * 获取文章图片素材列表 * @param dto * @return */ private List<String> getImagesList(WmNewsDto dto) { List<String> imagesUrlList = new ArrayList<>(); String content = dto.getContent(); List<Map> maps = JSON.parseArray(content, Map.class); for(Map map : maps) { if(map.get("type").equals("image")) { String imageUrl = (String) map.get("value"); imagesUrlList.add(imageUrl); } } return imagesUrlList; } @Autowired private WmMaterialMapper wmMaterialMapper; /** * 保存图片素材与文章的关系 * @param imagesList * @param id */ private void saveRelatedImages(List<String> imagesList, Integer id,Short type) { //参数校验 if(imagesList != null && !imagesList.isEmpty()) { //通过图片url获取素材id List<WmMaterial> materials = wmMaterialMapper.selectList(Wrappers.<WmMaterial>lambdaQuery().in(WmMaterial::getUrl, imagesList)); //判断素材是否有效 if(materials == null || materials.isEmpty()) { //手动抛出异常 一方面提醒开发者,另一方面做数据回滚 throw new CustomException(AppHttpCodeEnum.MATERIASL_REFERENCE_FAIL); } //素材部分失效 if(materials.size() != imagesList.size()) { throw new CustomException(AppHttpCodeEnum.MATERIASL_REFERENCE_FAIL); } //获取素材id List<Integer> materialsId = materials.stream().map(WmMaterial::getId).collect(Collectors.toList()); //批量保存 wmNewsMaterialMapper.saveRelations(materialsId,id,type); } } /** * 保存封面图片与文章之间关系 * @param dto * @param imagesList * @param wmNews */ private void saveRelatedCover(WmNewsDto dto, List<String> imagesList, WmNews wmNews) { List<String> images = dto.getImages(); //自动设置封面 if(dto.getType().equals(WemediaConstants.WM_NEWS_TYPE_AUTO)) { //多图 if(imagesList.size() >= 3) { //设置文章封面属性 wmNews.setType(WemediaConstants.WM_NEWS_MANY_IMAGE); images = imagesList.stream().limit(3).collect(Collectors.toList()); } //单图 else if(imagesList.size() >= 1) { //设置文章封面属性 wmNews.setType(WemediaConstants.WM_NEWS_SINGLE_IMAGE); images = imagesList.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) { saveRelatedImages(images,wmNews.getId(),WemediaConstants.WM_COVER_REFERENCE); } } }
5.代码说明
前端传过来的数据格式如下:
{ "title":"", "type":"1",//这个 0 是无图 1 是单图 3 是多图 -1 是自动 "labels":"", "publishTime":"2022-03-14T11:35:49.000Z", "channelId":1, "images":[ "http://192.10/group1/M00/00/00/wKjIgl5swbGATaSAAAEPfZfx6Iw790.png" ], "status":1, "content":"[ { "type":"text", "value":"随着智能手机的普及,人们更加习惯于通过手机来看新闻。" }, { "type":"image", "value":"http://19.130/group1/M00/00/00/wKjIgl790.png" } ]" }
这是JSON格式的字符串,里面的images表示文章的封面信息,是一个数组类型,但是自媒体文章实体类WmNews中的封面属性iamges是一个字符串类型,若有多个封面则用","隔开,所以在保存封面之前需要对前端传过来的数据进行处理。需要注意的是,content包含两个部分,一个是文本内容,一个是图片内容。因此在获取文章图片素材列表时候我们使用的是Map来接收,并且key值为"image"。
下篇预告:自媒体文章自动审核