# 第五章 自媒體文章發布
目標
- 完成自媒體文章列表查詢功能
- 完成自媒體文章的發布功能
- 完成自媒體文章的查詢
- 完成自媒體文章的刪除功能
- 完成自媒體文章的上下架功能功能
1 自媒體文章列表查詢
1.1 需求分析

如圖所示:
需要展示自媒體發布的文章列表,并實現分頁查詢展示,而且需要根據關鍵字(文章的標題)、頻道列表 和發布日期范圍來進行分頁查詢
1.2 表結構
wm_news 自媒體文章表

頻道表:

1.3 功能實現
1.3.1 自媒體文章分頁查詢
1.3.1.1 思路分析
頁面點擊搜索按鈕之后 將選中的條件作為請求體傳遞給后臺,后臺根據條件進行分頁查詢即可
請求路徑:/search
參數:分頁條件封裝對象
返回值:分頁結果返回對象
1.3.1.2 功能實現
(1)創建dto來接收頁面傳遞的數值
@Data
@Getter
@Setter
public class WmNewsDto extends WmNews {
private LocalDateTime startTime;
private LocalDateTime endTime;
}

(2)修改controller進行分頁查詢
@PostMapping("/searchPage")
public Result<PageInfo<WmNews>> findByPageDto(@RequestBody PageRequestDto<WmNewsDto> pageRequestDto){
PageInfo<WmNews> pageInfo = wmNewsService.findByPageDto(pageRequestDto);
return Result.ok(pageInfo);
}

service:
@Service
public class WmNewsServiceImpl extends ServiceImpl<WmNewsMapper, WmNews> implements WmNewsService {
@Override
public PageInfo<WmNews> findByPageDto(PageRequestDto<WmNewsDto> pageRequestDto) {
//select * from wm_news where status=? and title=? and user_id=當前的用戶的ID and channl_id=? and publishedTime between ? and ? limit 0,10
//1.獲取請求當前的頁碼 和每頁顯示的行
Long page = pageRequestDto.getPage();
Long size = pageRequestDto.getSize();
//2.獲取請求體對象
WmNewsDto body = pageRequestDto.getBody();
QueryWrapper<WmNews> queryWrapper = new QueryWrapper<>();
//添加查詢當前用戶的文章的列表
String userInfo = RequestContextUtil.getUserInfo();
queryWrapper.eq("user_id",Integer.valueOf(userInfo));
//3.判斷 是否為空 如果不為空 則拼接查詢條件
if(body!=null){
if(!StringUtils.isEmpty(body.getStatus())){
queryWrapper.eq("status",body.getStatus());
}
if(!StringUtils.isEmpty(body.getTitle())){
queryWrapper.like("title",body.getTitle());
}
if(!StringUtils.isEmpty(body.getChannelId())){
queryWrapper.eq("channel_id",body.getChannelId());
}
if(!StringUtils.isEmpty(body.getStartTime()) && !StringUtils.isEmpty(body.getEndTime())){
queryWrapper.between("publish_time", body.getStartTime(),body.getEndTime());
}
}
//4.執行分頁查詢
IPage<WmNews> page1 = new Page<WmNews>(page,size);
IPage<WmNews> page2 = page(page1, queryWrapper);
//5.封裝結果
return new PageInfo<WmNews>(page2.getCurrent(),page2.getSize(), page2.getTotal(),page2.getPages(),page2.getRecords());
}
}
1.3.2.3 測試

1.3.2 頻道列表查詢
1.3.2.1 思路分析
? 實際上頻道列表 需要在頁面展示下拉框我們可以直接使用現有的admin微服務中的查詢所有的頻道列表即可,因為數據量不大,可以直接列出來即可,然后通過自媒體網關路由轉發到admin微服務即可。
1.3.2.2 功能實現
目前已經實現了在抽象類中,所以直接關聯網關即可。


網關配置:

1.3.2.3 測試
為了測試方便直接在本地訪問路徑,(當然也可以結合網關進行測試)

1.3.3 網關路由規則配置

結合網關測試:
(1)先啟動微服務 和網關 (自媒體微服務 admin微服務 自媒體網關)登錄

(2)測試獲取頻道列表: token從上一節登錄之后獲取

(3)測試文章分頁列表查詢:

3 自媒體文章-發布、修改,保存草稿
3.1 需求分析
總體上的需求:

原型:需求可以參考原型,但是會有稍微的變化
項目原型-HTML(使用火狐瀏覽器打開)/[原型圖]_前臺_黑馬頭條_V1.0/index.html#g=1

彈出窗口的圖如下:有兩種:1 選擇現有素材作為 2 重新上傳圖片
(圖3)

流程說明如下:
1.點擊發布文章,添加標題 添加內容
2.添加內容有兩種 一種是 純文本 一種是是圖片
3.點擊文本的時候 彈出窗口直接添加文本 點擊確定即可
4.點擊圖片的時候 彈出窗口 如上圖片 圖三表示 可以從素材庫里面選擇一張圖,或者自己直接上傳一張圖 作為內容的一張圖片
5.選擇標簽 頻道 和發布定時時間
6.選擇封面 選擇單圖 或者 多圖 或者無圖或者自動 需要彈出窗口圖三那里進行 選擇或者上傳 多圖需要上傳3張
7.點擊保存草稿或者直接提交
3.2 思路分析
涉及到的表如下:


思路分析:
這個其實也很簡單。
發布文章的本質就是像wm_news文章表進行插入一條記錄而已。
這里比較特殊的點在于 可以選擇素材 所以需要彈出窗口查詢素材 并選中之后將素材對應的圖片的路徑作為參數請求 傳遞給后臺保存到mw_news文章表中存儲起來
還有就是封面的無圖 單圖 自動 需要進行判斷,自動的時候,需要判斷當前的內容中是否有圖片,如圖有圖片,則判斷有幾張,如果小于2 則作為單圖 如果小于1 則作為無圖,如果大于2 則作為多圖即可。
實現步驟:
實現彈窗 獲取素材列表--》 【已經實現】 直接調用分頁搜索的請求路徑即可
實現上傳圖片---》【已經實現】 直接調用dfs的請求路徑即可
封面的單圖 多圖 選擇圖片上傳--》就是彈窗功能已經實現
實現發表文章功能:
請求:/wmNews/save/{isSubmit} POST
參數:2個
是否為提交 isSubitm 值為 1 或者 0 1標識為提交 0 標識為保存草稿
請求體對象
返回值:result 成功與否即可
前端應當傳遞的請求體對象數據為如下案例:
{
"title": "黑馬頭條項目背景",
"type": "1",
"labels": "黑馬頭條",
"publishTime": "2020-03-14T11:35:49.000Z",
"channelId": 1,
"images": [
"http://192.168.200.130/group1/M00/00/00/wKjIgl5swbGATaSAAAEPfZfx6Iw790.png"
],
"status": 1,
"content": [
{
"type": "text",
"value": "隨著智能手機的普及,人們更加習慣于通過手機來看新聞。由于生活節奏的加快,很多人只能利用碎片時間來獲取信息,因此,對于移動資訊客戶端的需求也越來越高。黑馬頭條項目正是在這樣背景下開發出來。黑馬頭條項目采用當下火熱的微服務+大數據技術架構實現。本項目主要著手于獲取最新最熱新聞資訊,通過大數據分析用戶喜好精確推送咨詢新聞"
},
{
"type": "image",
"value": "http://192.168.200.130/group1/M00/00/00/wKjIgl5swbGATaSAAAEPfZfx6Iw790.png"
}
]
}
解釋 :
type : 指定為封面類型 0 是無圖 1 是單圖 3 是多圖 -1 是自動
images: 指定為封面圖片 以逗號分隔的圖片路徑
status: 自媒體文章的狀態 0 保存草稿 1 提交(待審核)..... 這個字段前端不必傳遞
3.3 功能實現
(1)創建dto 用來接收頁面傳遞過來的請求體
@Data
@Getter
@Setter
public class ContentNode {
//type 指定類型 text 標識文本 image 標識 圖片
private String type;
//value 指定內容
private String value;
}
@Data
@Getter
@Setter
public class WmNewsDtoSave {
//主鍵ID
private Integer id;
//文章標題
private String title;
//圖文內容
private List<ContentNode> content;
//指定為封面類型 0 是無圖 1 是單圖 3 是多圖 -1 是自動
private Integer type;
//指定選中的頻道ID
private Integer channelId;
//指定標簽
private String labels;
//狀態 0 草稿 1 提交 待審核 (該字段可以不用設置,前端不必傳遞)
private Integer status;
//定時發布時間
private LocalDateTime publishTime;
//封面圖片
private List<String> images;
}

(2)編寫controller
//保存自媒體文章 保存草稿 和 添加 或者修改
@PostMapping("/save/{isSubmit}")
public Result save(@PathVariable(name="isSubmit") Integer isSubmit,@RequestBody WmNewsDtoSave wmNewsDtoSave){
if(StringUtils.isEmpty(isSubmit) || wmNewsDtoSave==null){
return Result.errorMessage("數據不能為空");
}
if(isSubmit>1 || isSubmit<0){
return Result.errorMessage("isSubmit的值有誤");
}
wmNewsService.save(wmNewsDtoSave,isSubmit);
return Result.ok();
}

(3)編寫service 實現類
@Autowired
private WmNewsMapper wmNewsMapper;
//保存自媒體文章信息
@Override
public void save(WmNewsDtoSave wmNewsDtoSave, Integer isSubmit) {
WmNews wmNews = new WmNews();
//copy數據
BeanUtils.copyProperties(wmNewsDtoSave, wmNews);
//補充設置數據
//設置登錄的用戶ID
wmNews.setUserId(Integer.valueOf(RequestContextUtil.getUserInfo()));
//設置成JSON 字符串到數據庫中
wmNews.setContent(JSON.toJSONString(wmNewsDtoSave.getContent()));
//設置封面圖片 將list 轉成一個以逗號分隔的字符串
if (wmNewsDtoSave.getImages() != null && wmNewsDtoSave.getImages().size() > 0) {
wmNews.setImages(String.join(",", wmNewsDtoSave.getImages()));
}
//如果是自動圖 則判斷 圖文內容中的圖片有多少張,如果是>2 則為多圖 如果是1 則為單圖 如果是小于1 則為 無圖
if (wmNewsDtoSave.getType() == -1) {
List<String> imagesFromContent = getImagesFromContent(wmNewsDtoSave);
//說明是多圖
if (imagesFromContent.size() > 2) {
//設置為多圖
wmNews.setType(3);
//并設置圖片 因為頁面沒有傳遞了
wmNews.setImages(String.join(",", imagesFromContent));
} else if (imagesFromContent.size() > 0 && imagesFromContent.size() <= 2) {
//設置為單圖
wmNews.setType(1);
//設置圖片為一張
wmNews.setImages(imagesFromContent.get(0));
} else {
//無圖
wmNews.setType(0);
//空字符串
wmNews.setImages("");
}
}
//保存草稿或者提交審核
wmNews.setStatus(isSubmit);
if (isSubmit == 1) {
wmNews.setSubmitedTime(LocalDateTime.now());
}
//修改數據
if (wmNewsDtoSave.getId() != null) {
wmNewsMapper.updateById(wmNews);
} else {
//添加數據
wmNews.setCreatedTime(LocalDateTime.now());
wmNewsMapper.insert(wmNews);
}
}
//獲取圖片路徑列表
private List<String> getImagesFromContent(WmNewsDtoSave wmNewsDtoSave) {
List<ContentNode> content = wmNewsDtoSave.getContent();
List<String> images = new ArrayList<String>();
for (ContentNode contentNode : content) {
//圖片
if (contentNode.getType().equals("image")) {
String value = contentNode.getValue();
images.add(value);
}
}
return images;
}
(4)測試:
先啟動網關和自媒體微服務 并先登錄

登錄好了之后進行添加保存的操作:


測試數據參考思路分析里面的請求體數據。
3.4 優化
當保存之后需要將生成的主鍵返回
操作如下:
實現類中 修改如下:

接口修改:

controller修改如下:

4 自媒體文章-根據id查詢
4.1 需求分析

點擊修改的時候,就是根據文章id查詢,跳轉至編輯頁面進行展示

4.2 思路分析
點擊編輯 先根據文章的ID 獲取到文章的數據,要注意的是,需要返回的數據不是數據庫對應的實體對象,而是剛才我們定義的dto的對象。因為編輯的時候需要用到該數據
4.3 功能實現
(1)修改controller
@GetMapping("/one/{id}")
public Result<WmNewsDtoSave> getById(@PathVariable(name="id")Integer id){
WmNewsDtoSave wmNewsDtoSave = wmNewsService.getDtoById(id);
return Result.ok(wmNewsDtoSave);
}

(2)service實現類
@Override
public WmNewsDtoSave getDtoById(Integer id) {
WmNews wmNews = wmNewsMapper.selectById(id);
if(wmNews!=null){
WmNewsDtoSave wmNewsDtoSave = new WmNewsDtoSave();
BeanUtils.copyProperties(wmNews,wmNewsDtoSave);
//設置內容
String content = wmNews.getContent();
List<ContentNode> contentNodes = JSON.parseArray(content, ContentNode.class);
wmNewsDtoSave.setContent(contentNodes);
//設置圖片
String images = wmNews.getImages();
if(!StringUtils.isEmpty(images)){
//設置圖片列表
wmNewsDtoSave.setImages(Arrays.asList(images.split(",")));
}
return wmNewsDtoSave;
}
return null;
}
(3)測試 通過網關測試(注意:測試也可以不用通過網關)

5 自媒體文章-刪除
5.1 需求分析

5.2 思路分析
當文章狀態為9 并且已上架的數據 不能刪除。 如果是其他的狀態可以刪除。
刪除之后需要同步數據到 APP文章中,該狀態待做。
前端發送請求到后臺 后臺做邏輯判斷處理即可

5.3 功能實現
controller 實現即可:
@Override
@DeleteMapping("/{id}")
public Result deleteById(@PathVariable(name = "id") Serializable id) {
WmNews wmNews = wmNewsService.getById(id);
if (wmNews == null) {
return Result.errorMessage("不存在的文章");
}
Integer enable = wmNews.getEnable();
Integer status = wmNews.getStatus();
//已發布 且上架
if (status == 9 && enable == 1) {
return Result.errorMessage("已發布 且上架 不能刪除");
}
wmNewsService.removeById(id);
return Result.ok();
}

6 自媒體文章-上架、下架
6.1 需求分析


6.2 思路分析
當前已經發布(狀態為9)的文章可以上架(enable = 1),也可以下架(enable = 0)
在上架和下架操作的同時,需要同步app端的文章配置信息,暫時不做,后期講到審核文章的時候再優化
6.3 功能實現
修改controller 添加一個方法進行上架和下架。
@PutMapping("/upOrDown/{id}/{enable}")
public Result updateUpDown(@PathVariable(name = "id") Serializable id,@PathVariable(name="enable")Integer enable) {
WmNews wmNews = wmNewsService.getById(id);
if (wmNews == null) {
return Result.errorMessage("不存在的文章");
}
Integer status = wmNews.getStatus();
//已發布 且上架
if (status != 9) {
return Result.errorMessage("文章沒發布,不能上下架");
}
if(enable>1 || enable<0){
return Result.errorMessage("錯誤的數字范圍 只能是0,1");
}
wmNews.setEnable(enable);
wmNewsService.updateById(wmNews);
return Result.ok();
}

7 優化登錄和續約(擴展)
7.1 思路
以自媒體微服務為例

1.先登錄。產生兩個令牌 一個令牌為訪問令牌 一個為刷新令牌
2.訪問令牌 訪問時 當過期之后,返回狀態碼403 表示需要刷新令牌
3.用戶再發送請求,將之前產生的刷新令牌 傳遞到后臺,后臺校驗通過之后,返回新的兩個令牌
7.2 實現
1 創建如下類,參考資料中的類直接copy過來

參考類如下:

2 修改登錄方法
controller:

@PostMapping("/login")
public Result<TokenJsonVo> login(@RequestBody LoginVo loginVo) throws LeadNewsException {
TokenJsonVo tokenJsonVo = wmUserService.login(loginVo);
return Result.ok(tokenJsonVo);
}
VO所在位置:
@Data
public class LoginVo {
//登錄用的用戶名
private String username;
//登錄用的密碼
private String password;
}

service接口及實現類:
public TokenJsonVo login(LoginVo loginVo) throws LeadNewsException;
@Override
public TokenJsonVo login(LoginVo loginVo) throws LeadNewsException {
//1.校驗數據是否為空 判斷 如果為空直接報錯
if (StringUtils.isEmpty(loginVo.getUsername()) || StringUtils.isEmpty(loginVo.getPassword())) {
throw new LeadNewsException("用戶名和密碼不能為null");
}
//2.要根據 用戶名 獲取 數據庫中的用戶的信息 判斷 如果沒有數據 直接報錯 select * from ad_user where name=?
QueryWrapper<WmUser> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", loginVo.getUsername());
WmUser wmUserFromDb = wmUserMapper.selectOne(queryWrapper);
if (wmUserFromDb == null) {
throw new LeadNewsException("用戶名或密碼錯誤");
}
//3.根據 數據庫中的密碼(密文) 和 【頁面傳遞過來的密碼(明文)+salt--->md5加密之后的密文】 對比 如果不成功 報錯
String passwordFromWeb = DigestUtils.md5DigestAsHex((loginVo.getPassword() + wmUserFromDb.getSalt()).getBytes());
String passwordFromDb = wmUserFromDb.getPassword();
if (!passwordFromDb.equals(passwordFromWeb)) {
throw new LeadNewsException("用戶名或密碼錯誤");
}
//4.生成令牌 組裝數據返回
UserTokenInfo userTokenInfo = new UserTokenInfo(Long.valueOf(wmUserFromDb.getId()),
wmUserFromDb.getImage(),
wmUserFromDb.getNickname(),
wmUserFromDb.getName(),
TokenRole.ROLE_MEDIA);
TokenJsonVo token = JwtUtil.createToken(userTokenInfo);
return token;
}
3 刷新令牌controller:

@PostMapping("/refreshToken")
public Result refreshToken(@RequestBody Map<String, String> map) {
String refreshToken = map.get("refreshToken");
UserTokenInfoExp userTokenInfoExp = null;
try {
userTokenInfoExp = JwtUtil.parseJwtUserToken(refreshToken);
Long exp = userTokenInfoExp.getExp();
long now = System.currentTimeMillis();
long chazhi = exp - now;
//續約的要求是: 必須在 訪問令牌的過期時間點 到 刷新令牌的過期時間點 之間 防止 出現過久的令牌來惡意刷新令牌
if(chazhi>(JwtUtil.TOKEN_TIME_OUT*1000)){
return Result.errorMessage("令牌續約時間不在有效范圍之內");
}
} catch (Exception e) {
return Result.errorMessage("令牌錯誤");
}
if (JwtUtil.isExpire(userTokenInfoExp)) {
return Result.errorMessage("令牌錯誤");
}
TokenJsonVo token = JwtUtil.createToken(userTokenInfoExp);
return Result.ok(token);
}
4 網關過濾器實現
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
//獲取用戶攜帶的token令牌 解析校驗 校驗通過放行 不通過 返回錯誤
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.獲取請求對象 和 響應對象
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
//1.5 如果請求的路徑是 自媒體登錄【白名單】 放行即可
String path = request.getURI().getPath();
if(path.startsWith("/media/wmUser/login") ||path.startsWith("/media/wmUser/refreshToken") || path.endsWith("v2/api-docs")){
return chain.filter(exchange);
}
//2.從請求頭中獲取訪問令牌數據
String token = request.getHeaders().getFirst("token");
//3.判斷 是否為空 如果為空 返回錯誤 401
if(StringUtils.isEmpty(token)){
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//4.校驗令牌是否正確了 如果不是 返回錯誤 401
try {
UserTokenInfoExp userTokenInfoExp = JwtUtil.parseJwtUserToken(token);
if(!JwtUtil.isValidRole(userTokenInfoExp, TokenRole.ROLE_MEDIA)){
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//直接返回 表示需要續約
if(JwtUtil.isExpire(userTokenInfoExp)){
//403
response.setStatusCode(HttpStatus.FORBIDDEN);
return response.setComplete();
}
//URL編碼 否則有亂碼產生
String encode = URLEncoder.encode(JSON.toJSONString(userTokenInfoExp), "UTF-8");
//將信息傳遞給下游微服務
request.mutate().header(SystemConstants.USER_HEADER_NAME, encode);
} catch (Exception e) {
e.printStackTrace();
//直接錯誤
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
return chain.filter(exchange);
}
//值越低 優先級越高 優先被執行
@Override
public int getOrder() {
return -10;
}
}
5 工具類中修改原來的請求頭獲取用戶ID的方法:
public class RequestContextUtil {
/**
* 獲取訪問令牌信息解析之后的信息
*
* @return
*/
public static UserTokenInfoExp getRequestUserTokenInfo() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
String json = null;
try {
//解碼
json = URLDecoder.decode(request.getHeader(SystemConstants.USER_HEADER_NAME), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
UserTokenInfoExp userTokenInfoExp = JSON.parseObject(json, UserTokenInfoExp.class);
return userTokenInfoExp;
}
//判斷是否為匿名用戶
public static boolean isAnonymous() {
return TokenRole.ROLE_ANONYMOUS == getRequestUserTokenInfo().getRole();
}
//獲取用戶ID值
public static Integer getUserId() {
return getRequestUserTokenInfo().getUserId().intValue();
}
}
修改原來添加素材的代碼:

@Override
@PostMapping
public Result<WmMaterial> insert(@RequestBody WmMaterial record) {//該方法 叫:處理器handler
Integer userId = RequestContextUtil.getUserId();
//設置User_id的值為當前登錄的自媒體的用戶的ID
record.setType(0);//圖片
record.setIsCollection(0);
record.setCreatedTime(LocalDateTime.now());
//一定是當前登錄自媒體的用戶的ID
record.setUserId(userId);
wmMaterialService.save(record);
return Result.ok();
}
修改 發布文章的代碼:

7.3 測試
測試雙令牌登錄:
(1)為了測試過期 我們修改過期時間為30S

(2)測試登錄

測試攜帶令牌訪問:

(3)等過30S的時候再刷新令牌:

浙公網安備 33010602011771號