国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

《黑馬頭條》SpringBoot+SpringCloud+ Nacos等企業(yè)級微服務(wù)架構(gòu)項(xiàng)目

這篇具有很好參考價(jià)值的文章主要介紹了《黑馬頭條》SpringBoot+SpringCloud+ Nacos等企業(yè)級微服務(wù)架構(gòu)項(xiàng)目。希望對大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

各位爺,完整項(xiàng)目gitee如下,求star

heima-leadnews-master: 《黑馬頭條》項(xiàng)目采用的是SpringBoot+springcloud當(dāng)下最流行的微服務(wù)為項(xiàng)目架構(gòu),配合spring cloud alibaba nacos作為項(xiàng)目的注冊和配置中心。新課程采用快速開發(fā)的模式,主要解決真實(shí)企業(yè)開發(fā)的一些應(yīng)用場景。詳情請看博客:https://blog.csdn.net/m0_67184231/article/details/131439819

01環(huán)境搭建、SpringCloud微服務(wù)(注冊發(fā)現(xiàn)、服務(wù)調(diào)用、網(wǎng)關(guān))

1)課程對比

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

2)項(xiàng)目概述

2.1)能讓你收獲什么

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

2.2)項(xiàng)目課程大綱

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

2.3)項(xiàng)目概述

隨著智能手機(jī)的普及,人們更加習(xí)慣于通過手機(jī)來看新聞。由于生活節(jié)奏的加快,很多人只能利用碎片時(shí)間來獲取信息,因此,對于移動(dòng)資訊客戶端的需求也越來越高。

黑馬頭條項(xiàng)目正是在這樣背景下開發(fā)出來。黑馬頭條項(xiàng)目采用當(dāng)下火熱的微服務(wù)+大數(shù)據(jù)技術(shù)架構(gòu)實(shí)現(xiàn)。本項(xiàng)目主要著手于獲取最新最熱新聞資訊,通過大數(shù)據(jù)分析用戶喜好精確推送咨詢新聞

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

2.4)項(xiàng)目術(shù)語

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

2.5)業(yè)務(wù)說明

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

項(xiàng)目演示地址:

  • 平臺管理:http://heima-admin-java.research.itcast.cn

  • 自媒體:http://heime-media-java.research.itcast.cn

  • app端:http://heima-app-java.research.itcast.cn

平臺管理與自媒體為PC端,用電腦瀏覽器打開即可。

其中app端為移動(dòng)端,打開方式有兩種:

  • 谷歌瀏覽器打開,調(diào)成移動(dòng)端模式

  • 手機(jī)瀏覽器打開或掃描右側(cè)二維碼

3)技術(shù)棧

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot
  • Spring-Cloud-Gateway : 微服務(wù)之前架設(shè)的網(wǎng)關(guān)服務(wù),實(shí)現(xiàn)服務(wù)注冊中的API請求路由,以及控制流速控制和熔斷處理都是常用的架構(gòu)手段,而這些功能Gateway天然支持

  • 運(yùn)用Spring Boot快速開發(fā)框架,構(gòu)建項(xiàng)目工程;并結(jié)合Spring Cloud全家桶技術(shù),實(shí)現(xiàn)后端個(gè)人中心、自媒體、管理中心等微服務(wù)。

  • 運(yùn)用Spring Cloud Alibaba Nacos作為項(xiàng)目中的注冊中心和配置中心

  • 運(yùn)用mybatis-plus作為持久層提升開發(fā)效率

  • 運(yùn)用Kafka完成內(nèi)部系統(tǒng)消息通知;與客戶端系統(tǒng)消息通知;以及實(shí)時(shí)數(shù)據(jù)計(jì)算

  • 運(yùn)用Redis緩存技術(shù),實(shí)現(xiàn)熱數(shù)據(jù)的計(jì)算,提升系統(tǒng)性能指標(biāo)

  • 使用Mysql存儲用戶數(shù)據(jù),以保證上層數(shù)據(jù)查詢的高性

  • 使用Mongo存儲用戶熱數(shù)據(jù),以保證用戶熱數(shù)據(jù)高擴(kuò)展和高性能指標(biāo)

  • 使用FastDFS作為靜態(tài)資源存儲器,在其上實(shí)現(xiàn)熱靜態(tài)資源緩存、淘汰等功能

  • 運(yùn)用Hbase技術(shù),存儲系統(tǒng)中的冷數(shù)據(jù),保證系統(tǒng)數(shù)據(jù)的可靠性

  • 運(yùn)用ES搜索技術(shù),對冷數(shù)據(jù)、文章數(shù)據(jù)建立索引,以保證冷數(shù)據(jù)、文章查詢性能

  • 運(yùn)用AI技術(shù),來完成系統(tǒng)自動(dòng)化功能,以提升效率及節(jié)省成本。比如實(shí)名認(rèn)證自動(dòng)化

  • PMD&P3C : 靜態(tài)代碼掃描工具,在項(xiàng)目中掃描項(xiàng)目代碼,檢查異常點(diǎn)、優(yōu)化點(diǎn)、代碼規(guī)范等,為開發(fā)團(tuán)隊(duì)提供規(guī)范統(tǒng)一,提升項(xiàng)目代碼質(zhì)量

4)nacos環(huán)境搭建

4.1)虛擬機(jī)鏡像準(zhǔn)備

1)打開當(dāng)天資料文件中的鏡像,拷貝到一個(gè)地方,然后解壓

2)解壓后,雙擊ContOS7-hmtt.vmx文件,前提是電腦上已經(jīng)安裝了VMware

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot
  1. 修改虛擬網(wǎng)絡(luò)地址(NAT)

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

①,選中VMware中的編輯

②,選擇虛擬網(wǎng)絡(luò)編輯器

③,找到NAT網(wǎng)卡,把網(wǎng)段改為200(當(dāng)前掛載的虛擬機(jī)已固定ip地址)

4)修改虛擬機(jī)的網(wǎng)絡(luò)模式為NAT

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

5)啟動(dòng)虛擬機(jī),用戶名:root 密碼:itcast,當(dāng)前虛擬機(jī)的ip已手動(dòng)固定(靜態(tài)IP), 地址為:192.168.200.130

6)使用FinalShell客戶端鏈接

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

4.2)nacos安裝

①:docker拉取鏡像

docker pull nacos/nacos-server:1.2.0

②:創(chuàng)建容器

docker run --env MODE=standalone --name nacos --restart=always  -d -p 8848:8848 nacos/nacos-server:1.2.0
  • MODE=standalone 單機(jī)版

  • --restart=always 開機(jī)啟動(dòng)

  • -p 8848:8848 映射端口

  • -d 創(chuàng)建一個(gè)守護(hù)式容器在后臺運(yùn)行

③:訪問地址:http://192.168.200.130:8848/nacos

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

5)初始工程搭建

5.1)環(huán)境準(zhǔn)備

①:項(xiàng)目依賴環(huán)境(需提前安裝好)

  • JDK1.8

  • Intellij Idea

  • maven-3.6.1

  • Git

②:在當(dāng)天資料中解壓heima-leadnews.zip文件,拷貝到 沒有中文和空格的目錄,使用idea打開即可

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

③:IDEA開發(fā)工具配置

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

設(shè)置本地倉庫,建議使用資料中提供好的倉庫

④:設(shè)置項(xiàng)目編碼格式

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

5.2)主體結(jié)構(gòu)

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

6)登錄

6.1)需求分析

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot
  • 戶點(diǎn)擊開始使用

登錄后的用戶權(quán)限較大,可以查 看,也可以操作(點(diǎn)贊,關(guān)注,評論)

  • 用戶點(diǎn)擊不登錄,先看看

游客只有查看的權(quán)限

6.2)表結(jié)構(gòu)分析

關(guān)于app端用戶相關(guān)的內(nèi)容較多,可以單獨(dú)設(shè)置一個(gè)庫leadnews_user

表名稱

說明

ap_user

APP用戶信息表

ap_user_fan

APP用戶粉絲信息表

ap_user_follow

APP用戶關(guān)注信息表

ap_user_realname

APP實(shí)名認(rèn)證信息表

從當(dāng)前資料中找到對應(yīng)數(shù)據(jù)庫并導(dǎo)入到mysql中

登錄需要用到的是ap_user表,表結(jié)構(gòu)如下:

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot
黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

項(xiàng)目中的持久層使用的mybatis-plus,一般都使用mybais-plus逆向生成對應(yīng)的實(shí)體類

app_user表對應(yīng)的實(shí)體類如下:


package com.heima.model.user.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>
 * APP用戶信息表
 * </p>
 *
 * @author itheima
 */
@Data
@TableName("ap_user")
public class ApUser implements Serializable {

    private static final long serialVersionUID = 1L;

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

    /**
     * 密碼、通信等加密鹽
     */
    @TableField("salt")
    private String salt;

    /**
     * 用戶名
     */
    @TableField("name")
    private String name;

    /**
     * 密碼,md5加密
     */
    @TableField("password")
    private String password;

    /**
     * 手機(jī)號
     */
    @TableField("phone")
    private String phone;

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

    /**
     * 0 男
            1 女
            2 未知
     */
    @TableField("sex")
    private Boolean sex;

    /**
     * 0 未
            1 是
     */
    @TableField("is_certification")
    private Boolean certification;

    /**
     * 是否身份認(rèn)證
     */
    @TableField("is_identity_authentication")
    private Boolean identityAuthentication;

    /**
     * 0正常
            1鎖定
     */
    @TableField("status")
    private Boolean status;

    /**
     * 0 普通用戶
            1 自媒體人
            2 大V
     */
    @TableField("flag")
    private Short flag;

    /**
     * 注冊時(shí)間
     */
    @TableField("created_time")
    private Date createdTime;

}

手動(dòng)加密(md5+隨機(jī)字符串)

md5是不可逆加密,md5相同的密碼每次加密都一樣,不太安全。在md5的基礎(chǔ)上手動(dòng)加鹽(salt)處理

注冊->生成鹽

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

登錄->使用鹽來配合驗(yàn)證

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

6.3)思路分析

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

1,用戶輸入了用戶名和密碼進(jìn)行登錄,校驗(yàn)成功后返回jwt(基于當(dāng)前用戶的id生成)

2,用戶游客登錄,生成jwt返回(基于默認(rèn)值0生成)

6.4)運(yùn)營端微服務(wù)搭建

在heima-leadnews-service下創(chuàng)建工程heima-leadnews-user

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

引導(dǎo)類


package com.heima.user;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient//集成當(dāng)前注冊中心
@MapperScan("com.heima.user.mapper")//掃描mapper
public class UserApplication {

    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class,args);
    }
}

bootstrap.yml

server:
  port: 51801
spring:
  application:
    name: leadnews-user
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.200.130:8848
      config:
        server-addr: 192.168.200.130:8848
        file-extension: yml

在nacos中創(chuàng)建配置文件

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/leadnews_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: root
# 設(shè)置Mapper接口所對應(yīng)的XML文件位置,如果你在Mapper接口中有自定義方法,需要進(jìn)行該配置
mybatis-plus:
  mapper-locations: classpath*:mapper/*.xml
  # 設(shè)置別名包掃描路徑,通過該屬性可以給包中的類注冊別名
  type-aliases-package: com.heima.model.user.pojos
報(bào)錯(cuò):
The last packet successfully received from the server was 523 milliseconds ago. The last packet sent successfully to the server was 518 milliseconds ago.
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)

可以把nacos的mysqlurl改成下面

url: jdbc:mysql://localhost:3306/leadnews_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false

logback.xml

<?xml version="1.0" encoding="UTF-8"?>

<configuration>
    <!--定義日志文件的存儲地址,使用絕對路徑-->
    <property name="LOG_HOME" value="e:/logs"/>

    <!-- Console 輸出設(shè)置 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!--格式化輸出:%d表示日期,%thread表示線程名,%-5level:級別從左顯示5個(gè)字符寬度%msg:日志消息,%n是換行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

    <!-- 按照每天生成日志文件 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件輸出的文件名-->
            <fileNamePattern>${LOG_HOME}/leadnews.%d{yyyy-MM-dd}.log</fileNamePattern>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 異步輸出 -->
    <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 不丟失日志.默認(rèn)的,如果隊(duì)列的80%已滿,則會(huì)丟棄TRACT、DEBUG、INFO級別的日志 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 更改默認(rèn)的隊(duì)列的深度,該值會(huì)影響性能.默認(rèn)值為256 -->
        <queueSize>512</queueSize>
        <!-- 添加附加的appender,最多只能添加一個(gè) -->
        <appender-ref ref="FILE"/>
    </appender>

    <logger name="org.apache.ibatis.cache.decorators.LoggingCache" level="DEBUG" additivity="false">
        <appender-ref ref="CONSOLE"/>
    </logger>
    <logger name="org.springframework.boot" level="debug"/>
    <root level="info">
        <!--<appender-ref ref="ASYNC"/>-->
        <appender-ref ref="FILE"/>
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

6.4)登錄功能實(shí)現(xiàn)

①:接口定義


@RestController
@RequestMapping("/api/v1/login")
public class ApUserLoginController {

    @PostMapping("/login_auth")
    public ResponseResult login(@RequestBody LoginDto dto) {
        return null;
    }
}

②:持久層mapper


package com.heima.user.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.user.pojos.ApUser;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface ApUserMapper extends BaseMapper<ApUser> {
}

③:業(yè)務(wù)層service


package com.heima.user.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.user.dtos.LoginDto;
import com.heima.model.user.pojos.ApUser;

public interface ApUserService extends IService<ApUser>{

    /**
     * app端登錄
     * @param dto
     * @return
     */
    public ResponseResult login(LoginDto dto);
    
}

實(shí)現(xiàn)類:


package com.heima.user.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.user.dtos.LoginDto;
import com.heima.model.user.pojos.ApUser;
import com.heima.user.mapper.ApUserMapper;
import com.heima.user.service.ApUserService;
import com.heima.utils.common.AppJwtUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.util.HashMap;
import java.util.Map;


@Service
public class ApUserServiceImpl extends ServiceImpl<ApUserMapper, ApUser> implements ApUserService {

    @Override
    public ResponseResult login(LoginDto dto) {

        //1.正常登錄(手機(jī)號+密碼登錄)
        if (!StringUtils.isBlank(dto.getPhone()) && !StringUtils.isBlank(dto.getPassword())) {
            //1.1查詢用戶
            ApUser apUser = getOne(Wrappers.<ApUser>lambdaQuery().eq(ApUser::getPhone, dto.getPhone()));
            if (apUser == null) {
                return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST,"用戶不存在");
            }

            //1.2 比對密碼
            String salt = apUser.getSalt();
            String pswd = dto.getPassword();
            pswd = DigestUtils.md5DigestAsHex((pswd + salt).getBytes());
            if (!pswd.equals(apUser.getPassword())) {
                return ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_PASSWORD_ERROR);
            }
            //1.3 返回?cái)?shù)據(jù)  jwt
            Map<String, Object> map = new HashMap<>();
            map.put("token", AppJwtUtil.getToken(apUser.getId().longValue()));
? ? ? ? //這兩個(gè)值 置空 再返回對象
            apUser.setSalt("");
            apUser.setPassword("");
            map.put("user", apUser);
            return ResponseResult.okResult(map);
        } else {
            //2.游客  同樣返回token  id = 0
            Map<String, Object> map = new HashMap<>();
            map.put("token", AppJwtUtil.getToken(0l));
            return ResponseResult.okResult(map);
        }
    }
}

④:控制層controller


package com.heima.user.controller.v1;

import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.user.dtos.LoginDto;
import com.heima.user.service.ApUserService;
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/login")
public class ApUserLoginController {

    @Autowired
    private ApUserService apUserService;

    @PostMapping("/login_auth")
    public ResponseResult login(@RequestBody LoginDto dto) {
        return apUserService.login(dto);
    }
}

docker exec -it redis redis-cli這個(gè)進(jìn)入服務(wù)器得redis界面 你再輸入docker ps就能看到服務(wù)起來了;docker 中的redis沒密碼,要把nacos的密碼去掉;

報(bào)錯(cuò)的兄弟們先創(chuàng)建redis容器a然后把nacos配置中redis密碼那一行注掉就行了

docker run --name redis -p 6379:6379 -d redis

7)接口工具postman、swagger、knife4j

7.1)postman

Postman是一款功能強(qiáng)大的網(wǎng)頁調(diào)試與發(fā)送網(wǎng)頁HTTP請求的Chrome插件。postman被500萬開發(fā)者和超100,000家公司用于每月訪問1.3億個(gè)API。

官方網(wǎng)址:Postman

解壓資料文件夾中的軟件,安裝即可

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

通常的接口測試查看請求和響應(yīng),下面是登錄請求的測試

http://localhost:51801/api/v1/login/login_auth

{"phone":"13511223456","password":"admin"}

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

7.2)swagger

(1)簡介

Swagger 是一個(gè)規(guī)范和完整的框架,用于生成、描述、調(diào)用和可視化 RESTful 風(fēng)格的 Web 服務(wù)(API Documentation & Design Tools for Teams | Swagger)。 它的主要作用是:

  1. 使得前后端分離開發(fā)更加方便,有利于團(tuán)隊(duì)協(xié)作

  1. 接口的文檔在線自動(dòng)生成,降低后端開發(fā)人員編寫接口文檔的負(fù)擔(dān)

  1. 功能測試

Spring已經(jīng)將Swagger納入自身的標(biāo)準(zhǔn),建立了Spring-swagger項(xiàng)目,現(xiàn)在叫Springfox。

通過在項(xiàng)目中引入Springfox ,即可非常簡單快捷的使用Swagger。

(2)SpringBoot集成Swagger

  • 引入依賴,在heima-leadnews-model和heima-leadnews-common模塊中引入該依賴

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
</dependency>

只需要在heima-leadnews-common中進(jìn)行配置即可,因?yàn)槠渌⒎?wù)工程都直接或間接依賴即可。

  • 在heima-leadnews-common工程中添加一個(gè)配置類

新增:com.heima.common.swagger.SwaggerConfiguration


package com.heima.common.swagger;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfiguration {

   @Bean
   public Docket buildDocket() {
      return new Docket(DocumentationType.SWAGGER_2)
              .apiInfo(buildApiInfo())
              .select()
              // 要掃描的API(Controller)基礎(chǔ)包
              .apis(RequestHandlerSelectors.basePackage("com.heima"))
              .paths(PathSelectors.any())
              .build();
   }

   private ApiInfo buildApiInfo() {
      Contact contact = new Contact("黑馬程序員","","");
      return new ApiInfoBuilder()
              .title("黑馬頭條-平臺管理API文檔")
              .description("黑馬頭條后臺api")
              .contact(contact)
              .version("1.0.0").build();
   }
}

在heima-leadnews-common模塊中的resources目錄中新增以下目錄和文件

文件:resources/META-INF/Spring.factories


org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.heima.common.swagger.SwaggerConfiguration

(3)Swagger常用注解

在Java類中添加Swagger的注解即可生成Swagger接口文檔,常用Swagger注解如下:

@Api:修飾整個(gè)類,描述Controller的作用

@ApiOperation:描述一個(gè)類的一個(gè)方法,或者說一個(gè)接口

@ApiParam:單個(gè)參數(shù)的描述信息

@ApiModel:用對象來接收參數(shù)

@ApiModelProperty:用對象接收參數(shù)時(shí),描述對象的一個(gè)字段

@ApiResponse:HTTP響應(yīng)其中1個(gè)描述

@ApiResponses:HTTP響應(yīng)整體描述

@ApiIgnore:使用該注解忽略這個(gè)API

@ApiError :發(fā)生錯(cuò)誤返回的信息

@ApiImplicitParam:一個(gè)請求參數(shù)

@ApiImplicitParams:多個(gè)請求參數(shù)的描述信息

@ApiImplicitParam屬性:

屬性

取值

作用

paramType

查詢參數(shù)類型

path

以地址的形式提交數(shù)據(jù)

query

直接跟參數(shù)完成自動(dòng)映射賦值

body

以流的形式提交 僅支持POST

header

參數(shù)在request headers 里邊提交

form

以form表單的形式提交 僅支持POST

dataType

參數(shù)的數(shù)據(jù)類型 只作為標(biāo)志說明,并沒有實(shí)際驗(yàn)證

Long

String

name

接收參數(shù)名

value

接收參數(shù)的意義描述

required

參數(shù)是否必填

true

必填

false

非必填

defaultValue

默認(rèn)值

我們在ApUserLoginController中添加Swagger注解,代碼如下所示:


@RestController
@RequestMapping("/api/v1/login")
@Api(value = "app端用戶登錄", tags = "ap_user", description = "app端用戶登錄API")
public class ApUserLoginController {

    @Autowired
    private ApUserService apUserService;

    @PostMapping("/login_auth")
    @ApiOperation("用戶登錄")
    public ResponseResult login(@RequestBody LoginDto dto){
        return apUserService.login(dto);
    }
}

LoginDto


@Data
public class LoginDto {

    /**
     * 手機(jī)號
     */
    @ApiModelProperty(value="手機(jī)號",required = true)
    private String phone;

    /**
     * 密碼
     */
    @ApiModelProperty(value="密碼",required = true)
    private String password;
}

啟動(dòng)user微服務(wù),訪問地址:http://localhost:51801/swagger-ui.html

7.3)knife4j

(1)簡介

knife4j是為Java MVC框架集成Swagger生成Api文檔的增強(qiáng)解決方案,前身是swagger-bootstrap-ui,取名kni4j是希望它能像一把匕首一樣小巧,輕量,并且功能強(qiáng)悍!

gitee地址:knife4j: Knife4j是一個(gè)集Swagger2 和 OpenAPI3為一體的增強(qiáng)解決方案

官方文檔:Knife4j · 集Swagger2及OpenAPI3為一體的增強(qiáng)解決方案. | Knife4j

效果演示:http://knife4j.xiaominfo.com/doc.html

(2)核心功能

該UI增強(qiáng)包主要包括兩大核心功能:文檔說明 和 在線調(diào)試

  • 文檔說明:根據(jù)Swagger的規(guī)范說明,詳細(xì)列出接口文檔的說明,包括接口地址、類型、請求示例、請求參數(shù)、響應(yīng)示例、響應(yīng)參數(shù)、響應(yīng)碼等信息,使用swagger-bootstrap-ui能根據(jù)該文檔說明,對該接口的使用情況一目了然。

  • 在線調(diào)試:提供在線接口聯(lián)調(diào)的強(qiáng)大功能,自動(dòng)解析當(dāng)前接口參數(shù),同時(shí)包含表單驗(yàn)證,調(diào)用參數(shù)可返回接口響應(yīng)內(nèi)容、headers、Curl請求命令實(shí)例、響應(yīng)時(shí)間、響應(yīng)狀態(tài)碼等信息,幫助開發(fā)者在線調(diào)試,而不必通過其他測試工具測試接口是否正確,簡介、強(qiáng)大。

  • 個(gè)性化配置(Swaager無):通過個(gè)性化ui配置項(xiàng),可自定義UI的相關(guān)顯示信息

  • 離線文檔(Swaager無):根據(jù)標(biāo)準(zhǔn)規(guī)范,生成的在線markdown離線文檔,開發(fā)者可以進(jìn)行拷貝生成markdown接口文檔,通過其他第三方markdown轉(zhuǎn)換工具轉(zhuǎn)換成html或pdf,這樣也可以放棄swagger2markdown組件

  • 接口排序:自1.8.5后,ui支持了接口排序功能,

  • 例如一個(gè)注冊功能主要包含了多個(gè)步驟,可以根據(jù)swagger-bootstrap-ui提供的接口排序規(guī)則實(shí)現(xiàn)接口的排序,step化接口操作,方便其他開發(fā)者進(jìn)行接口對接

(3)快速集成

  • 在heima-leadnews-common模塊中的pom.xml文件中引入knife4j的依賴,如下:

<dependency>
     <groupId>com.github.xiaoymin</groupId>
     <artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
  • 創(chuàng)建Swagger配置文件

在heima-leadnews-common模塊中新建配置類

新建Swagger的配置文件SwaggerConfiguration.java文件,創(chuàng)建springfox提供的Docket分組對象,代碼如下:


package com.heima.common.knife4j;

import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
@EnableKnife4j
@Import(BeanValidatorPluginsConfiguration.class)
public class Swagger2Configuration {

    @Bean(value = "defaultApi2")
    public Docket defaultApi2() {
        Docket docket=new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                //分組名稱
                .groupName("1.0")
                .select()
                //這里指定Controller掃描包路徑
                .apis(RequestHandlerSelectors.basePackage("com.heima"))
                .paths(PathSelectors.any())
                .build();
        return docket;
    }
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("黑馬頭條API文檔")
                .description("黑馬頭條API文檔")
                .version("1.0")
                .build();
    }
}

以上有兩個(gè)注解需要特別說明,如下表:

注解

說明

@EnableSwagger2

該注解是Springfox-swagger框架提供的使用Swagger注解,該注解必須加

@EnableKnife4j

該注解是knife4j提供的增強(qiáng)注解,Ui提供了例如動(dòng)態(tài)參數(shù)、參數(shù)過濾、接口排序等增強(qiáng)功能,如果你想使用這些增強(qiáng)功能就必須加該注解,否則可以不用加

  • 添加配置

在Spring.factories中新增配置


org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.heima.common.swagger.Swagger2Configuration, \
  com.heima.common.swagger.SwaggerConfiguration
  • 訪問

在瀏覽器輸入地址:http://host:port/doc.html;http://localhost:51801/doc.html

8)網(wǎng)關(guān)

(1)在heima-leadnews-gateway導(dǎo)入以下依賴

pom文件

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
     <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
    </dependency>
</dependencies>

(2)在heima-leadnews-gateway下創(chuàng)建heima-leadnews-app-gateway微服務(wù)

引導(dǎo)類:


package com.heima.app.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient  //開啟注冊中心
public class AppGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(AppGatewayApplication.class,args);
    }
}

bootstrap.yml

server:
  port: 51601
spring:
  application:
    name: leadnews-app-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.200.130:8848
      config:
        server-addr: 192.168.200.130:8848
        file-extension: yml

在nacos的配置中心創(chuàng)建dataid為leadnews-app-gateway的yml配置

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot
spring:
  cloud:
    gateway:
      globalcors:
        add-to-simple-url-handler-mapping: true
        corsConfigurations:
          '[/**]':
            allowedHeaders: "*"
            allowedOrigins: "*"
            allowedMethods:
              - GET
              - POST
              - DELETE
              - PUT
              - OPTION
      routes:
        # 平臺管理
        - id: user
          uri: lb://leadnews-user
          predicates:
            - Path=/user/**
          filters:
            - StripPrefix= 1

環(huán)境搭建完成以后,啟動(dòng)項(xiàng)目網(wǎng)關(guān)和用戶兩個(gè)服務(wù),使用postman進(jìn)行測試

請求地址:http://localhost:51601/user/api/v1/login/login_auth

1.3 全局過濾器實(shí)現(xiàn)jwt校驗(yàn)

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

思路分析:

  1. 用戶進(jìn)入網(wǎng)關(guān)開始登陸,網(wǎng)關(guān)過濾器進(jìn)行判斷,如果是登錄,則路由到后臺管理微服務(wù)進(jìn)行登錄

  1. 用戶登錄成功,后臺管理微服務(wù)簽發(fā)JWT TOKEN信息返回給用戶

  1. 用戶再次進(jìn)入網(wǎng)關(guān)開始訪問,網(wǎng)關(guān)過濾器接收用戶攜帶的TOKEN

  1. 網(wǎng)關(guān)過濾器解析TOKEN ,判斷是否有權(quán)限,如果有,則放行,如果沒有則返回未認(rèn)證錯(cuò)誤

具體實(shí)現(xiàn):

第一:

在認(rèn)證過濾器中需要用到j(luò)wt的解析,所以需要把工具類拷貝一份到網(wǎng)關(guān)微服務(wù)

第二:

在網(wǎng)關(guān)微服務(wù)中新建全局過濾器:


package com.heima.app.gateway.filter;


import com.heima.app.gateway.util.AppJwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
@Slf4j
public class AuthorizeFilter implements Ordered, GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.獲取request和response對象
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        //2.判斷是否是登錄
        if(request.getURI().getPath().contains("/login")){
            //放行
            return chain.filter(exchange);
        }


        //3.獲取token
        String token = request.getHeaders().getFirst("token");

        //4.判斷token是否存在
        if(StringUtils.isBlank(token)){
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

        //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();
            }
        }catch (Exception e){
            e.printStackTrace();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

        //6.放行
        return chain.filter(exchange);
    }

    /**
     * 優(yōu)先級設(shè)置  值越小  優(yōu)先級越高
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

測試:

啟動(dòng)user服務(wù),繼續(xù)訪問其他微服務(wù),會(huì)提示需要認(rèn)證才能訪問,這個(gè)時(shí)候需要在heads中設(shè)置設(shè)置token才能正常訪問。

9)前端集成

9.1)前端項(xiàng)目部署思路

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

通過nginx來進(jìn)行配置,功能如下

  • 通過nginx的反向代理功能訪問后臺的網(wǎng)關(guān)資源

  • 通過nginx的靜態(tài)服務(wù)器功能訪問前端靜態(tài)頁面

9.2)配置nginx

①:解壓資料文件夾中的壓縮包nginx-1.18.0.zip

路徑cmd,鍵入nginx,啟動(dòng)

②:解壓資料文件夾中的前端項(xiàng)目app-web.zip

③:配置nginx.conf文件

在nginx安裝的conf目錄下新建一個(gè)文件夾leadnews.conf,在當(dāng)前文件夾中新建heima-leadnews-app.conf文件

heima-leadnews-app.conf配置如下:


upstream  heima-app-gateway{
    server localhost:51601;
}

server {
	listen 8801;
	location / {
		root D:/workspace/app-web/;
		index index.html;
	}
	
	location ~/app/(.*) {
		proxy_pass http://heima-app-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;   # 記錄真實(shí)發(fā)出請求的客戶端IP
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  #記錄代理信息
	}
}

nginx.conf 把里面注釋的內(nèi)容和靜態(tài)資源配置相關(guān)刪除,引入heima-leadnews-app.conf文件加載



#user  nobody;
worker_processes  1;

events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
	# 引入自定義配置文件
	include leadnews.conf/*.conf;
}

④ :啟動(dòng)nginx

在nginx安裝包中使用命令提示符打開,輸入命令nginx啟動(dòng)項(xiàng)目

查看進(jìn)程,檢查nginx是否啟動(dòng)

重新加載配置文件:nginx -s reload

報(bào)錯(cuò)
使用windows版本的nginx啟動(dòng)時(shí)遇到(1113: No mapping for the Unicode character exists in the target multi-byte code page)這個(gè)錯(cuò)誤

把nginx的版本升高了,依舊報(bào)錯(cuò)
后來查閱發(fā)現(xiàn)是因?yàn)榻鈮旱穆窂嚼锩姘兄形牡木壒剩灰呀鈮汉蟮奈募羟械經(jīng)]有包含中文的目錄即可解決問題

在運(yùn)行reload的時(shí)候報(bào)錯(cuò):CreateFile() "D:\DevelopCode\JavaCode\Springcloud\leadnews\nginx-1.18.0/logs/nginx.pid" failed (2: The system cannot find the file specified)

加pid的文件,然后里面沒東西運(yùn)行失敗,就start nginx,計(jì)算機(jī)自己在pid加了東西,成功reload!

打開8801 報(bào)錯(cuò):500 Internal Server Error

這個(gè)錯(cuò)誤通常表示在Windows操作系統(tǒng)上,Nginx無法識別路徑中含有非英文字符的文件或文件夾。這可能是因?yàn)镹ginx默認(rèn)將其配置文件和日志文件等保存在UTF-8編碼下,而Windows默認(rèn)的編碼方式是ANSI。
要解決這個(gè)問題,可以嘗試以下方法:
將Nginx配置文件及相關(guān)路徑修改為英文字符,避免包含非英文字符的路徑。
修改Nginx的配置文件編碼為 ANSI :在Nginx的配置文件中,找到 http 塊,并在該塊中添加以下指令:
plaintext復(fù)制代碼http {
# ...
charset utf-8;
charset_types text/plain text/css application/javascript application/json text/javascript;
charset utf-8 off;
# ...
}
保存并重新啟動(dòng)Nginx服務(wù)。

⑤:打開前端項(xiàng)目進(jìn)行測試 -- > http://localhost:8801

用谷歌瀏覽器打開,調(diào)試移動(dòng)端模式進(jìn)行訪

02app端文章查看,靜態(tài)化freemarker,分布式文件系統(tǒng)minIO

1)文章列表加載

1.1)需求分析

文章布局展示

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

1.2)表結(jié)構(gòu)分析

ap_article 文章基本信息表

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

ap_article_config 文章配置表

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

ap_article_content 文章內(nèi)容表

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

三張表關(guān)系分析

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

表的拆分—垂直分表

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

1.3)導(dǎo)入文章數(shù)據(jù)庫

1.3.1)導(dǎo)入數(shù)據(jù)庫

查看當(dāng)天資料文件夾,在數(shù)據(jù)庫連接工具中執(zhí)行l(wèi)eadnews_article.sql

1.3.2)導(dǎo)入對應(yīng)的實(shí)體類

ap_article文章表對應(yīng)實(shí)體


package com.heima.model.article.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>
 * 文章信息表,存儲已發(fā)布的文章
 * </p>
 *
 * @author itheima
 */

@Data
@TableName("ap_article")
public class ApArticle implements Serializable {

    @TableId(value = "id",type = IdType.ID_WORKER)
    private Long id;


    /**
     * 標(biāo)題
     */
    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;

    /**
     * 文章標(biāo)記  0 普通文章   1 熱點(diǎn)文章   2 置頂文章   3 精品文章   4 大V 文章
     */
    private Byte flag;

    /**
     * 文章封面圖片 多張逗號分隔
     */
    private String images;

    /**
     * 標(biāo)簽
     */
    private String labels;

    /**
     * 點(diǎn)贊數(shù)量
     */
    private Integer likes;

    /**
     * 收藏?cái)?shù)量
     */
    private Integer collection;

    /**
     * 評論數(shù)量
     */
    private Integer comment;

    /**
     * 閱讀數(shù)量
     */
    private Integer views;

    /**
     * 省市
     */
    @TableField("province_id")
    private Integer provinceId;

    /**
     * 市區(qū)
     */
    @TableField("city_id")
    private Integer cityId;

    /**
     * 區(qū)縣
     */
    @TableField("county_id")
    private Integer countyId;

    /**
     * 創(chuàng)建時(shí)間
     */
    @TableField("created_time")
    private Date createdTime;

    /**
     * 發(fā)布時(shí)間
     */
    @TableField("publish_time")
    private Date publishTime;

    /**
     * 同步狀態(tài)
     */
    @TableField("sync_status")
    private Boolean syncStatus;

    /**
     * 來源
     */
    private Boolean origin;

    /**
     * 靜態(tài)頁面地址
     */
    @TableField("static_url")
    private String staticUrl;
}

ap_article_config文章配置對應(yīng)實(shí)體類


package com.heima.model.article.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;

/**
 * <p>
 * APP已發(fā)布文章配置表
 * </p>
 *
 * @author itheima
 */

@Data
@TableName("ap_article_config")
public class ApArticleConfig implements Serializable {

    @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;

    /**
     * 是否轉(zhuǎn)發(fā)
     * true: 可以轉(zhuǎn)發(fā)   1
     * false: 不可轉(zhuǎn)發(fā)  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 文章內(nèi)容對應(yīng)的實(shí)體類


package com.heima.model.article.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;

@Data
@TableName("ap_article_content")
public class ApArticleContent implements Serializable {

    @TableId(value = "id",type = IdType.ID_WORKER)
    private Long id;

    /**
     * 文章id
     */
    @TableField("article_id")
    private Long articleId;

    /**
     * 文章內(nèi)容
     */
    private String content;
}

1.4)實(shí)現(xiàn)思路

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

1,在默認(rèn)頻道展示10條文章信息

2,可以切換頻道查看不同種類文章

3,當(dāng)用戶下拉可以加載最新的文章(分頁)本頁文章列表中發(fā)布時(shí)間為最大的時(shí)間為依據(jù)

4,當(dāng)用戶上拉可以加載更多的文章信息(按照發(fā)布時(shí)間)本頁文章列表中發(fā)布時(shí)間最小的時(shí)間為依據(jù)

5,如果是當(dāng)前頻道的首頁,前端傳遞默認(rèn)參數(shù)

  • maxBehotTime:0(毫秒)

  • minBehotTime:20000000000000(毫秒)--->2063年

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot
黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

1.5)接口定義

加載首頁

加載更多

加載最新

接口路徑

/api/v1/article/load

/api/v1/article/loadmore

/api/v1/article/loadnew

請求方式

POST

POST

POST

參數(shù)

ArticleHomeDto

ArticleHomeDto

ArticleHomeDto

響應(yīng)結(jié)果

ResponseResult

ResponseResult

ResponseResult

ArticleHomeDto


package com.heima.model.article.dtos;

import lombok.Data;
import java.util.Date;

@Data
public class ArticleHomeDto {
    // 最大時(shí)間
    Date maxBehotTime;
    // 最小時(shí)間
    Date minBehotTime;
    // 分頁size
    Integer size;
    // 頻道ID
    String tag;
}

1.6)功能實(shí)現(xiàn)

1.6.1):導(dǎo)入heima-leadnews-article微服務(wù),資料在當(dāng)天的文件夾中
黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

注意:需要在heima-leadnews-service的pom文件夾中添加子模塊信息,如下:

<modules>
    <module>heima-leadnews-user</module>
    <module>heima-leadnews-article</module>
</modules>

在idea中的maven中更新一下,如果工程還是灰色的,需要在重新添加文章微服務(wù)的pom文件,操作步驟如下:

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

需要在nacos中添加對應(yīng)的配置

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/leadnews_article?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: root
# 設(shè)置Mapper接口所對應(yīng)的XML文件位置,如果你在Mapper接口中有自定義方法,需要進(jìn)行該配置
mybatis-plus:
  mapper-locations: classpath*:mapper/*.xml
  # 設(shè)置別名包掃描路徑,通過該屬性可以給包中的類注冊別名
  type-aliases-package: com.heima.model.article.pojos
1.6.2):定義接口

package com.heima.article.controller.v1;

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;
    }
}
1.6.3):編寫mapper文件

mybatisPlus對多表查詢不太友好,所以用mybatis自定義mapper查詢


package com.heima.article.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);

}

對應(yīng)的映射文件

在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">
        SELECT
        aa.*
        FROM
        `ap_article` aa
        LEFT JOIN ap_article_config aac ON aa.id = aac.article_id
        <where>
            and aac.is_delete != 1
            and 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 desc
        limit #{dto.size}
    </select>

</mapper>
1.6.4):編寫業(yè)務(wù)層代碼

package com.heima.article.service;

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> {

    /**
     * 根據(jù)參數(shù)加載文章列表
     * @param loadtype 1為加載更多  2為加載最新
     * @param dto
     * @return
     */
    ResponseResult load(Short loadtype, ArticleHomeDto dto);

}

實(shí)現(xiàn)類:


package com.heima.article.service.impl;

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 {

    // 單頁最大加載的數(shù)字
    private final static short MAX_PAGE_SIZE = 50;

    @Autowired
    private ApArticleMapper apArticleMapper;

    /**
     * 根據(jù)參數(shù)加載文章列表
     * @param loadtype 1為加載更多  2為加載最新
     * @param dto
     * @return
     */
    @Override
    public ResponseResult load(Short loadtype, ArticleHomeDto dto) {
        //1.校驗(yàn)參數(shù)
        Integer size = dto.getSize();
        if(size == null || size == 0){
            size = 10;
        }
        size = Math.min(size,MAX_PAGE_SIZE);
        dto.setSize(size);

        //類型參數(shù)檢驗(yàn)
        if(!loadtype.equals(ArticleConstants.LOADTYPE_LOAD_MORE)&&!loadtype.equals(ArticleConstants.LOADTYPE_LOAD_NEW)){
            loadtype = ArticleConstants.LOADTYPE_LOAD_MORE;
        }
        //文章頻道校驗(yàn)
        if(StringUtils.isEmpty(dto.getTag())){
            dto.setTag(ArticleConstants.DEFAULT_TAG);
        }

        //時(shí)間校驗(yàn)
        if(dto.getMaxBehotTime() == null) dto.setMaxBehotTime(new Date());
        if(dto.getMinBehotTime() == null) dto.setMinBehotTime(new Date());
        //2.查詢數(shù)據(jù)
        List<ApArticle> apArticles = apArticleMapper.loadArticleList(dto, loadtype);

        //3.結(jié)果封裝
        ResponseResult responseResult = ResponseResult.okResult(apArticles);
        return responseResult;
    }
    
}

定義常量類


package com.heima.common.constants;

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__";

}
1.6.5):編寫控制器代碼

package com.heima.article.controller.v1;

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 {


    @Autowired
    private 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);
    }
}
1.6.6):swagger測試或前后端聯(lián)調(diào)測試

第一:在app網(wǎng)關(guān)的微服務(wù)的nacos的配置中心添加文章微服務(wù)的路由,完整配置如下:

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]': # 匹配所有請求
            allowedOrigins: "*" #跨域處理 允許所有的域
            allowedMethods: # 支持的方法
              - GET
              - POST
              - PUT
              - DELETE
      routes:
        # 用戶微服務(wù)
        - id: user
          uri: lb://leadnews-user
          predicates:
            - Path=/user/**
          filters:
            - StripPrefix= 1
        # 文章微服務(wù)
        - id: article
          uri: lb://leadnews-article
          predicates:
            - Path=/article/**
          filters:
            - StripPrefix= 1

第二:啟動(dòng)nginx,直接使用前端項(xiàng)目測試,啟動(dòng)文章微服務(wù),用戶微服務(wù)、app網(wǎng)關(guān)微服務(wù)

2)freemarker

2.1) freemarker 介紹

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

FreeMarker 是一款 模板引擎: 即一種基于模板和要改變的數(shù)據(jù), 并用來生成輸出文本(HTML網(wǎng)頁,電子郵件,配置文件,源代碼等)的通用工具。 它不是面向最終用戶的,而是一個(gè)Java類庫,是一款程序員可以嵌入他們所開發(fā)產(chǎn)品的組件。

模板編寫為FreeMarker Template Language (FTL)。它是簡單的,專用的語言, 不是 像PHP那樣成熟的編程語言。 那就意味著要準(zhǔn)備數(shù)據(jù)在真實(shí)編程語言中來顯示,比如數(shù)據(jù)庫查詢和業(yè)務(wù)運(yùn)算, 之后模板顯示已經(jīng)準(zhǔn)備好的數(shù)據(jù)。在模板中,你可以專注于如何展現(xiàn)數(shù)據(jù), 而在模板之外可以專注于要展示什么數(shù)據(jù)

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

常用的java模板引擎還有哪些?

Jsp、Freemarker、Thymeleaf 、Velocity 等。

1.Jsp 為 Servlet 專用,不能單獨(dú)進(jìn)行使用。 已淘汰

2.Thymeleaf 為新技術(shù),功能較為強(qiáng)大,但是執(zhí)行的效率比較低。

3.Velocity從2010年更新完 2.0 版本后,便沒有在更新。Spring Boot 官方在 1.4 版本后對此也不在支持,雖然 Velocity 在 2017 年版本得到迭代,但為時(shí)已晚。

2.2) 環(huán)境搭建&&快速入門

freemarker作為springmvc一種視圖格式,默認(rèn)情況下SpringMVC支持freemarker視圖格式。

需要?jiǎng)?chuàng)建Spring Boot+Freemarker工程用于測試模板。

2.2.1) 創(chuàng)建測試工程

創(chuàng)建一個(gè)freemarker-demo 的測試工程專門用于freemarker的功能測試與模板的測試。

pom.xml如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>heima-leadnews-test</artifactId>
        <groupId>com.heima</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>freemarker-demo</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!-- apache 對 java io 的封裝工具庫 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.3.2</version>
        </dependency>
    </dependencies>

</project>
2.2.2) 配置文件

配置application.yml

server:
  port: 8881 #服務(wù)端口
spring:
  application:
    name: freemarker-demo #指定服務(wù)名
  freemarker:
    cache: false  #關(guān)閉模板緩存,方便測試
    settings:
      template_update_delay: 0 #檢查模板更新延遲時(shí)間,設(shè)置為0表示立即檢查,如果時(shí)間大于0會(huì)有緩存不方便進(jìn)行模板測試
    suffix: .ftl               #指定Freemarker模板文件的后綴名
2.2.3) 創(chuàng)建模型類

在freemarker的測試工程下創(chuàng)建模型類型用于測試


package com.heima.freemarker.entity;

import lombok.Data;

import java.util.Date;

@Data
public class Student {
    private String name;//姓名
    private int age;//年齡
    private Date birthday;//生日
    private Float money;//錢包
}
2.2.4) 創(chuàng)建模板

在resources下創(chuàng)建templates,此目錄為freemarker的默認(rèn)模板存放目錄。

在templates下創(chuàng)建模板文件 01-basic.ftl ,模板中的插值表達(dá)式最終會(huì)被freemarker替換成具體的數(shù)據(jù)。


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Hello World!</title>
</head>
<body>
<b>普通文本 String 展示:</b><br><br>
Hello ${name} <br>
<hr>
<b>對象Student中的數(shù)據(jù)展示:</b><br/>
姓名:${stu.name}<br/>
年齡:${stu.age}
<hr>
</body>
</html>
2.2.5) 創(chuàng)建controller

創(chuàng)建Controller類,向Map中添加name,最后返回模板文件。


package com.xuecheng.test.freemarker.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;
import java.util.Map;

@Controller
public class HelloController {

    @GetMapping("/basic")
    public String test(Model model) {

        //1.純文本形式的參數(shù)
        model.addAttribute("name", "freemarker");
        //2.實(shí)體類相關(guān)的參數(shù)
        
        Student student = new Student();
        student.setName("小明");
        student.setAge(18);
        model.addAttribute("stu", student);

        return "01-basic";
    }
}

01-basic.ftl,使用插值表達(dá)式填充數(shù)據(jù)


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Hello World!</title>
</head>
<body>
<b>普通文本 String 展示:</b><br><br>
Hello ${name} <br>
<hr>
<b>對象Student中的數(shù)據(jù)展示:</b><br/>
姓名:${stu.name}<br/>
年齡:${stu.age}
<hr>
</body>
</html>
2.2.6) 創(chuàng)建啟動(dòng)類

package com.heima.freemarker;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class FreemarkerDemotApplication {
    public static void main(String[] args) {
        SpringApplication.run(FreemarkerDemotApplication.class,args);
    }
}
2.2.7) 測試

請求:http://localhost:8881/basic

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

2.3) freemarker基礎(chǔ)

2.3.1) 基礎(chǔ)語法種類
1、注釋,即<#-- -->,介于其之間的內(nèi)容會(huì)被freemarker忽略

<#--我是一個(gè)freemarker注釋-->
2、插值(Interpolation):即 ${..} 部分,freemarker會(huì)用真實(shí)的值代替**${..}**

Hello ${name}

3、FTL指令:和HTML標(biāo)記類似,名字前加#予以區(qū)分,F(xiàn)reemarker會(huì)解析標(biāo)簽中的表達(dá)式或邏輯。


<# >FTL指令</#> 

4、文本,僅文本信息,這些不是freemarker的注釋、插值、FTL指令的內(nèi)容會(huì)被freemarker忽略解析,直接輸出內(nèi)容。


<#--freemarker中的普通文本-->
我是一個(gè)普通的文本
2.3.2) 集合指令(List和Map)

1、數(shù)據(jù)模型:

在HelloController中新增如下方法:


@GetMapping("/list")
public String list(Model model){

    //------------------------------------
    Student stu1 = new Student();
    stu1.setName("小強(qiáng)");
    stu1.setAge(18);
    stu1.setMoney(1000.86f);
    stu1.setBirthday(new Date());

    //小紅對象模型數(shù)據(jù)
    Student stu2 = new Student();
    stu2.setName("小紅");
    stu2.setMoney(200.1f);
    stu2.setAge(19);

    //將兩個(gè)對象模型數(shù)據(jù)存放到List集合中
    List<Student> stus = new ArrayList<>();
    stus.add(stu1);
    stus.add(stu2);

    //向model中存放List集合數(shù)據(jù)
    model.addAttribute("stus",stus);

    //------------------------------------

    //創(chuàng)建Map數(shù)據(jù)
    HashMap<String,Student> stuMap = new HashMap<>();
    stuMap.put("stu1",stu1);
    stuMap.put("stu2",stu2);
    // 3.1 向model中存放Map數(shù)據(jù)
    model.addAttribute("stuMap", stuMap);

    return "02-list";
}

2、模板:

在templates中新增02-list.ftl文件


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Hello World!</title>
</head>
<body>
    
<#-- list 數(shù)據(jù)的展示 -->
<b>展示list中的stu數(shù)據(jù):</b>
<br>
<br>
<table>
    <tr>
        <td>序號</td>
        <td>姓名</td>
        <td>年齡</td>
        <td>錢包</td>
    </tr>
</table>
<hr>
    
<#-- Map 數(shù)據(jù)的展示 -->
<b>map數(shù)據(jù)的展示:</b>
<br/><br/>
<a href="###">方式一:通過map['keyname'].property</a><br/>
輸出stu1的學(xué)生信息:<br/>
姓名:<br/>
年齡:<br/>
<br/>
<a href="###">方式二:通過map.keyname.property</a><br/>
輸出stu2的學(xué)生信息:<br/>
姓名:<br/>
年齡:<br/>

<br/>
<a href="###">遍歷map中兩個(gè)學(xué)生信息:</a><br/>
<table>
    <tr>
        <td>序號</td>
        <td>姓名</td>
        <td>年齡</td>
        <td>錢包</td> 
    </tr>
</table>
<hr>
 
</body>
</html>

實(shí)例代碼:


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Hello World!</title>
</head>
<body>
    
<#-- list 數(shù)據(jù)的展示 -->
<b>展示list中的stu數(shù)據(jù):</b>
<br>
<br>
<table>
    <tr>
        <td>序號</td>
        <td>姓名</td>
        <td>年齡</td>
        <td>錢包</td>
    </tr>
    <#list stus as stu>
        <tr>
            <td>${stu_index+1}</td>
            <td>${stu.name}</td>
            <td>${stu.age}</td>
            <td>${stu.money}</td>
        </tr>
    </#list>

</table>
<hr>
    
<#-- Map 數(shù)據(jù)的展示 -->
<b>map數(shù)據(jù)的展示:</b>
<br/><br/>
<a href="###">方式一:通過map['keyname'].property</a><br/>
輸出stu1的學(xué)生信息:<br/>
姓名:${stuMap['stu1'].name}<br/>
年齡:${stuMap['stu1'].age}<br/>
<br/>
<a href="###">方式二:通過map.keyname.property</a><br/>
輸出stu2的學(xué)生信息:<br/>
姓名:${stuMap.stu2.name}<br/>
年齡:${stuMap.stu2.age}<br/>

<br/>
<a href="###">遍歷map中兩個(gè)學(xué)生信息:</a><br/>
<table>
    <tr>
        <td>序號</td>
        <td>姓名</td>
        <td>年齡</td>
        <td>錢包</td>
    </tr>
    <#list stuMap?keys as key >
        <tr>
            <td>${key_index}</td>
            <td>${stuMap[key].name}</td>
            <td>${stuMap[key].age}</td>
            <td>${stuMap[key].money}</td>
        </tr>
    </#list>
</table>
<hr>
 
</body>
</html>

??上面代碼解釋:

${k_index}:

index:得到循環(huán)的下標(biāo),使用方法是在stu后邊加"_index",它的值是從0開始

2.3.3) if指令

if 指令即判斷指令,是常用的FTL指令,freemarker在解析時(shí)遇到if會(huì)進(jìn)行判斷,條件為真則輸出if中間的內(nèi)容,否則跳過內(nèi)容不再輸出。

  • 指令格式


<#if ></if>

1、數(shù)據(jù)模型:

使用list指令中測試數(shù)據(jù)模型,判斷名稱為小紅的數(shù)據(jù)字體顯示為紅色。

2、模板:


<table>
    <tr>
        <td>姓名</td>
        <td>年齡</td>
        <td>錢包</td>
    </tr>
    <#list stus as stu>
        <tr>
            <td >${stu.name}</td>
            <td>${stu.age}</td>
            <td >${stu.mondy}</td>
        </tr>
    </#list>

</table>

實(shí)例代碼:

<table>
    <tr>
        <td>姓名</td>
        <td>年齡</td>
        <td>錢包</td>
    </tr>
    <#list stus as stu >
        <#if stu.name='小紅'>
            <tr style="color: red">
                <td>${stu_index}</td>
                <td>${stu.name}</td>
                <td>${stu.age}</td>
                <td>${stu.money}</td>
            </tr>
            <#else >
            <tr>
                <td>${stu_index}</td>
                <td>${stu.name}</td>
                <td>${stu.age}</td>
                <td>${stu.money}</td>
            </tr>
        </#if>
    </#list>
</table>

3、輸出:

姓名為“小強(qiáng)”則字體顏色顯示為紅色。

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot
2.3.4) 運(yùn)算符

1、算數(shù)運(yùn)算符

FreeMarker表達(dá)式中完全支持算術(shù)運(yùn)算,FreeMarker支持的算術(shù)運(yùn)算符包括:

  • 加法: +

  • 減法: -

  • 乘法: *

  • 除法: /

  • 求模 (求余): %

模板代碼


<b>算數(shù)運(yùn)算符</b>
<br/><br/>
    100+5 運(yùn)算:  ${100 + 5 }<br/>
    100 - 5 * 5運(yùn)算:${100 - 5 * 5}<br/>
    5 / 2運(yùn)算:${5 / 2}<br/>
    12 % 10運(yùn)算:${12 % 10}<br/>
<hr>

除了 + 運(yùn)算以外,其他的運(yùn)算只能和 number 數(shù)字類型的計(jì)算。

2、比較運(yùn)算符

  • =或者==:判斷兩個(gè)值是否相等.

  • !=:判斷兩個(gè)值是否不等.

  • >或者gt:判斷左邊值是否大于右邊值

  • >=或者gte:判斷左邊值是否大于等于右邊值

  • <或者lt:判斷左邊值是否小于右邊值

  • <=或者lte:判斷左邊值是否小于等于右邊值

= 和 == 模板代碼


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Hello World!</title>
</head>
<body>

    <b>比較運(yùn)算符</b>
    <br/>
    <br/>

    <dl>
        <dt> =/== 和 != 比較:</dt>
        <dd>
            <#if "xiaoming" == "xiaoming">
                字符串的比較 "xiaoming" == "xiaoming"
            </#if>
        </dd>
        <dd>
            <#if 10 != 100>
                數(shù)值的比較 10 != 100
            </#if>
        </dd>
    </dl>



    <dl>
        <dt>其他比較</dt>
        <dd>
            <#if 10 gt 5 >
                形式一:使用特殊字符比較數(shù)值 10 gt 5
            </#if>
        </dd>
        <dd>
            <#-- 日期的比較需要通過?date將屬性轉(zhuǎn)為data類型才能進(jìn)行比較 -->
            <#if (date1?date >= date2?date)>
                形式二:使用括號形式比較時(shí)間 date1?date >= date2?date
            </#if>
        </dd>
    </dl>

    <br/>
<hr>
</body>
</html>

Controller 的 數(shù)據(jù)模型代碼


@GetMapping("operation")
public String testOperation(Model model) {
    //構(gòu)建 Date 數(shù)據(jù)
    Date now = new Date();
    model.addAttribute("date1", now);
    model.addAttribute("date2", now);
    
    return "03-operation";
}

比較運(yùn)算符注意

  • =!=可以用于字符串、數(shù)值和日期來比較是否相等

  • =!=兩邊必須是相同類型的值,否則會(huì)產(chǎn)生錯(cuò)誤

  • 字符串 "x" 、"x " 、"X"比較是不等的.因?yàn)镕reeMarker是精確比較

  • 其它的運(yùn)行符可以作用于數(shù)字和日期,但不能作用于字符串

  • 使用gt字母運(yùn)算符代替>會(huì)有更好的效果,因?yàn)?FreeMarker會(huì)把> 解釋成FTL標(biāo)簽的結(jié)束字符

  • 可以使用括號來避免這種情況,如:<#if (x>y)>

3、邏輯運(yùn)算符

  • 邏輯與:&&

  • 邏輯或:||

  • 邏輯非:!

邏輯運(yùn)算符只能作用于布爾值,否則將產(chǎn)生錯(cuò)誤 。

模板代碼


<b>邏輯運(yùn)算符</b>
    <br/>
    <br/>
    <#if (10 lt 12 )&&( 10  gt  5 )  >
        (10 lt 12 )&&( 10  gt  5 )  顯示為 true
    </#if>
    <br/>
    <br/>
    <#if !false>
        false 取反為true
    </#if>
<hr>
2.3.5) 空值處理

1、判斷某變量是否存在使用 “??”

用法為:variable??,如果該變量存在,返回true,否則返回false

例:為防止stus為空報(bào)錯(cuò)可以加上判斷如下:


    <#if stus??>
    <#list stus as stu>
    	......
    </#list>
    </#if>

2、缺失變量默認(rèn)值使用 “!”

  • 使用!要以指定一個(gè)默認(rèn)值,當(dāng)變量為空時(shí)顯示默認(rèn)值

例: ${name!''}表示如果name為空顯示空字符串。

  • 如果是嵌套對象則建議使用()括起來

例: ${(stu.bestFriend.name)!''}表示,如果stu或bestFriend或name為空默認(rèn)顯示空字符串。

2.3.6) 內(nèi)建函數(shù)

內(nèi)建函數(shù)語法格式: 變量+?+函數(shù)名稱

1、和到某個(gè)集合的大小

${集合名?size}

2、日期格式化

顯示年月日: ${today?date}

顯示時(shí)分秒:${today?time}

顯示日期+時(shí)間:${today?datetime}

自定義格式化: ${today?string("yyyy年MM月")}

3、內(nèi)建函數(shù)c

model.addAttribute("point", 102920122);

point是數(shù)字型,使用${point}會(huì)顯示這個(gè)數(shù)字的值,每三位使用逗號分隔。

如果不想顯示為每三位分隔的數(shù)字,可以使用c函數(shù)將數(shù)字型轉(zhuǎn)成字符串輸出

${point?c}

4、將json字符串轉(zhuǎn)成對象

一個(gè)例子:

其中用到了 assign標(biāo)簽,assign的作用是定義一個(gè)變量。


<#assign text="{'bank':'工商銀行','account':'10101920201920212'}" />
<#assign data=text?eval />
開戶行:${data.bank}  賬號:${data.account}

模板代碼:


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>inner Function</title>
</head>
<body>

    <b>獲得集合大小</b><br>

    集合大小:
    <hr>


    <b>獲得日期</b><br>

    顯示年月日:      <br>

    顯示時(shí)分秒:<br>

    顯示日期+時(shí)間:<br>

    自定義格式化:  <br>

    <hr>

    <b>內(nèi)建函數(shù)C</b><br>
    沒有C函數(shù)顯示的數(shù)值: <br>

    有C函數(shù)顯示的數(shù)值:

    <hr>

    <b>聲明變量assign</b><br>


<hr>
</body>
</html>

內(nèi)建函數(shù)模板頁面:


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>inner Function</title>
</head>
<body>

    <b>獲得集合大小</b><br>

    集合大?。?{stus?size}
    <hr>


    <b>獲得日期</b><br>

    顯示年月日: ${today?date}       <br>

    顯示時(shí)分秒:${today?time}<br>

    顯示日期+時(shí)間:${today?datetime}<br>

    自定義格式化:  ${today?string("yyyy年MM月")}<br>

    <hr>

    <b>內(nèi)建函數(shù)C</b><br>
    沒有C函數(shù)顯示的數(shù)值:${point} <br>

    有C函數(shù)顯示的數(shù)值:${point?c}

    <hr>

    <b>聲明變量assign</b><br>
    <#assign text="{'bank':'工商銀行','account':'10101920201920212'}" />
    <#assign data=text?eval />
    開戶行:${data.bank}  賬號:${data.account}

<hr>
</body>
</html>

內(nèi)建函數(shù)Controller數(shù)據(jù)模型:


@GetMapping("innerFunc")
public String testInnerFunc(Model model) {
    //1.1 小強(qiáng)對象模型數(shù)據(jù)
    Student stu1 = new Student();
    stu1.setName("小強(qiáng)");
    stu1.setAge(18);
    stu1.setMoney(1000.86f);
    stu1.setBirthday(new Date());
    //1.2 小紅對象模型數(shù)據(jù)
    Student stu2 = new Student();
    stu2.setName("小紅");
    stu2.setMoney(200.1f);
    stu2.setAge(19);
    //1.3 將兩個(gè)對象模型數(shù)據(jù)存放到List集合中
    List<Student> stus = new ArrayList<>();
    stus.add(stu1);
    stus.add(stu2);
    model.addAttribute("stus", stus);
    // 2.1 添加日期
    Date date = new Date();
    model.addAttribute("today", date);
    // 3.1 添加數(shù)值
    model.addAttribute("point", 102920122);
    return "04-innerFunc";
}

2.4) 靜態(tài)化測試

之前的測試都是SpringMVC將Freemarker作為視圖解析器(ViewReporter)來集成到項(xiàng)目中,工作中,有的時(shí)候需要使用Freemarker原生Api來生成靜態(tài)內(nèi)容,下面一起來學(xué)習(xí)下原生Api生成文本文件

2.4.1) 需求分析

使用freemarker原生Api將頁面生成html文件,本節(jié)測試html文件生成的方法:

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot
2.4.2) 靜態(tài)化測試

根據(jù)模板文件生成html文件

①:修改application.yml文件,添加以下模板存放位置的配置信息,完整配置如下:

server:
  port: 8881 #服務(wù)端口
spring:
  application:
    name: freemarker-demo #指定服務(wù)名
  freemarker:
    cache: false  #關(guān)閉模板緩存,方便測試
    settings:
      template_update_delay: 0 #檢查模板更新延遲時(shí)間,設(shè)置為0表示立即檢查,如果時(shí)間大于0會(huì)有緩存不方便進(jìn)行模板測試
    suffix: .ftl               #指定Freemarker模板文件的后綴名
    template-loader-path: classpath:/templates   #模板存放位置

②:在test下創(chuàng)建測試類


package com.heima.freemarker.test;

import com.heima.freemarker.FreemarkerDemoApplication;
import com.heima.freemarker.entity.Student;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;

@SpringBootTest(classes = FreemarkerDemoApplication.class)
@RunWith(SpringRunner.class)
public class FreemarkerTest {

    @Autowired
    private Configuration configuration;

    @Test
    public void test() throws IOException, TemplateException {
        //freemarker的模板對象,獲取模板
        Template template = configuration.getTemplate("02-list.ftl");
        Map params = getData();
        //合成
        //第一個(gè)參數(shù) 數(shù)據(jù)模型
        //第二個(gè)參數(shù)  輸出流
        template.process(params, new FileWriter("d:/list.html"));
    }

    private Map getData() {
        Map<String, Object> map = new HashMap<>();

        //小強(qiáng)對象模型數(shù)據(jù)
        Student stu1 = new Student();
        stu1.setName("小強(qiáng)");
        stu1.setAge(18);
        stu1.setMoney(1000.86f);
        stu1.setBirthday(new Date());

        //小紅對象模型數(shù)據(jù)
        Student stu2 = new Student();
        stu2.setName("小紅");
        stu2.setMoney(200.1f);
        stu2.setAge(19);

        //將兩個(gè)對象模型數(shù)據(jù)存放到List集合中
        List<Student> stus = new ArrayList<>();
        stus.add(stu1);
        stus.add(stu2);

        //向map中存放List集合數(shù)據(jù)
        map.put("stus", stus);


        //創(chuàng)建Map數(shù)據(jù)
        HashMap<String, Student> stuMap = new HashMap<>();
        stuMap.put("stu1", stu1);
        stuMap.put("stu2", stu2);
        //向map中存放Map數(shù)據(jù)
        map.put("stuMap", stuMap);

        //返回Map
        return map;
    }
}

3) 對象存儲服務(wù)MinIO

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot
黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

3.1 MinIO簡介

MinIO基于Apache License v2.0開源協(xié)議的對象存儲服務(wù),可以做為云存儲的解決方案用來保存海量的圖片,視頻,文檔。由于采用Golang實(shí)現(xiàn),服務(wù)端可以工作在Windows,Linux, OS X和FreeBSD上。配置簡單,基本是復(fù)制可執(zhí)行程序,單行命令可以運(yùn)行起來。

MinIO兼容亞馬遜S3云存儲服務(wù)接口,非常適合于存儲大容量非結(jié)構(gòu)化的數(shù)據(jù),例如圖片、視頻、日志文件、備份數(shù)據(jù)和容器/虛擬機(jī)鏡像等,而一個(gè)對象文件可以是任意大小,從幾kb到最大5T不等。

S3 ( Simple Storage Service簡單存儲服務(wù))

基本概念

  • bucket – 類比于文件系統(tǒng)的目錄

  • Object – 類比文件系統(tǒng)的文件

  • Keys – 類比文件名

官網(wǎng)文檔:MinIO Object Storage for Kubernetes — MinIO Object Storage for Kubernetes

3.2 MinIO特點(diǎn)

  • 數(shù)據(jù)保護(hù)

Minio使用Minio Erasure Code(糾刪碼)來防止硬件故障。即便損壞一半以上的driver,但是仍然可以從中恢復(fù)。

  • 高性能

作為高性能對象存儲,在標(biāo)準(zhǔn)硬件條件下它能達(dá)到55GB/s的讀、35GB/s的寫速率

  • 可擴(kuò)容

不同MinIO集群可以組成聯(lián)邦,并形成一個(gè)全局的命名空間,并跨越多個(gè)數(shù)據(jù)中心

  • SDK支持

基于Minio輕量的特點(diǎn),它得到類似Java、Python或Go等語言的sdk支持

  • 有操作頁面

面向用戶友好的簡單操作界面,非常方便的管理Bucket及里面的文件資源

  • 功能簡單

這一設(shè)計(jì)原則讓MinIO不容易出錯(cuò)、更快啟動(dòng)

  • 豐富的API

支持文件資源的分享連接及分享鏈接的過期策略、存儲桶操作、文件列表訪問及文件上傳下載的基本功能等

  • 文件變化主動(dòng)通知

存儲桶(Bucket)如果發(fā)生改變,比如上傳對象和刪除對象,可以使用存儲桶事件通知機(jī)制進(jìn)行監(jiān)控,并通過以下方式發(fā)布出去:AMQP、MQTT、Elasticsearch、Redis、NATS、MySQL、Kafka、Webhooks等。

3.3 開箱使用

3.3.1 安裝啟動(dòng)

我們提供的鏡像中已經(jīng)有minio的環(huán)境; 我們可以使用docker進(jìn)行環(huán)境部署和啟動(dòng)

docker run -p 9000:9000 --name minio -d --restart=always -e "MINIO_ACCESS_KEY=minio" -e "MINIO_SECRET_KEY=minio123" -v /home/data:/data -v /home/config:/root/.minio minio/minio server /data
3.3.2 管理控制臺

假設(shè)我們的服務(wù)器地址為http://192.168.200.130:9000,我們在地址欄輸入:http://192.168.200.130:9000/ 即可進(jìn)入登錄界面。

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

Access Key為minio Secret_key 為minio123 進(jìn)入系統(tǒng)后可以看到主界面

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

點(diǎn)擊右下角的“+”號 ,點(diǎn)擊下面的圖標(biāo),創(chuàng)建一個(gè)桶

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

3.4 快速入門

3.4.1 創(chuàng)建工程,導(dǎo)入pom依賴

創(chuàng)建minio-demo,對應(yīng)pom如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>heima-leadnews-test</artifactId>
        <groupId>com.heima</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>minio-demo</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>

        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>7.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>

</project>

引導(dǎo)類:


package com.heima.minio;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


@SpringBootApplication
public class MinIOApplication {

    public static void main(String[] args) {
        SpringApplication.run(MinIOApplication.class,args);
    }
}

創(chuàng)建測試類,上傳html文件


package com.heima.minio.test;

import io.minio.MinioClient;
import io.minio.PutObjectArgs;

import java.io.FileInputStream;

public class MinIOTest {


    public static void main(String[] args) {

        FileInputStream fileInputStream = new FileInputStream("C:\\Users\\yuhon\\Downloads\\index.js");
        try {

            fileInputStream =  new FileInputStream("D:\\list.html");;

            //1.創(chuàng)建minio鏈接客戶端  
            MinioClient minioClient = MinioClient.builder().credentials("minio", "minio123").endpoint("http://192.168.200.130:9000").build();
            //2.上傳 對象
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .object("list.html")//文件名
                    .contentType("text/html")//文件類型
                    .bucket("leadnews")//桶名詞  與minio創(chuàng)建的桶名稱 一致
                    .stream(fileInputStream, fileInputStream.available(), -1) //文件流(流stream,大小,傳到哪)
//fileInputStream.available()代表有值就一直傳遞;-1代表傳完所有文件
                    .build();
            minioClient.putObject(putObjectArgs);
? ?? ?  //上傳完成

? ? ? ? //訪問
            System.out.println("http://192.168.200.130:9000/leadnews/ak47.jpg");

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

}
黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

3.5 封裝MinIO為starter

封裝為了其它微服務(wù)使用

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot
黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot
3.5.1 創(chuàng)建模塊heima-file-starter

導(dǎo)入依賴

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-autoconfigure</artifactId>
    </dependency>
    <dependency>
        <groupId>io.minio</groupId>
        <artifactId>minio</artifactId>
        <version>7.1.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
</dependencies>
3.5.2 配置類

MinIOConfigProperties


package com.heima.file.config;


import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.io.Serializable;

@Data
@ConfigurationProperties(prefix = "minio")  // 文件上傳 配置前綴file.oss
public class MinIOConfigProperties implements Serializable {

    private String accessKey;
    private String secretKey;
    private String bucket;
    private String endpoint;
    private String readPath;
}

MinIOConfig


package com.heima.file.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Data
@Configuration
@EnableConfigurationProperties({MinIOConfigProperties.class})
//當(dāng)引入FileStorageService接口時(shí)
@ConditionalOnClass(FileStorageService.class)
public class MinIOConfig {

   @Autowired
   private MinIOConfigProperties minIOConfigProperties;

    @Bean
    public MinioClient buildMinioClient(){
        return MinioClient
                .builder()
                .credentials(minIOConfigProperties.getAccessKey(), minIOConfigProperties.getSecretKey())
                .endpoint(minIOConfigProperties.getEndpoint())
                .build();
    }
}
3.5.3 封裝操作minIO類

FileStorageService


package com.heima.file.service;

import java.io.InputStream;

/**
 * @author itheima
 */
public interface FileStorageService {


    /**
     *  上傳圖片文件
     * @param prefix  文件前綴
     * @param filename  文件名
     * @param inputStream 文件流
     * @return  文件全路徑
     */
    public String uploadImgFile(String prefix, String filename,InputStream inputStream);

    /**
     *  上傳html文件
     * @param prefix  文件前綴
     * @param filename   文件名
     * @param inputStream  文件流
     * @return  文件全路徑
     */
    public String uploadHtmlFile(String prefix, String filename,InputStream inputStream);

    /**
     * 刪除文件
     * @param pathUrl  文件全路徑
     */
    public void delete(String pathUrl);

    /**
     * 下載文件
     * @param pathUrl  文件全路徑
     * @return
     *
     */
    public byte[]  downLoadFile(String pathUrl);

}

MinIOFileStorageService


package com.heima.file.service.impl;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;

@Slf4j
@EnableConfigurationProperties(MinIOConfigProperties.class)
@Import(MinIOConfig.class)
public class MinIOFileStorageService implements FileStorageService {

    @Autowired
    private MinioClient minioClient;

    @Autowired
    private MinIOConfigProperties minIOConfigProperties;

    private final static String separator = "/";

    /**
     * @param dirPath
     * @param filename  yyyy/mm/dd/file.jpg
     * @return
     */
    public String builderFilePath(String dirPath,String filename) {
        StringBuilder stringBuilder = new StringBuilder(50);
        if(!StringUtils.isEmpty(dirPath)){
            stringBuilder.append(dirPath).append(separator);
        }
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
        String todayStr = sdf.format(new Date());
        stringBuilder.append(todayStr).append(separator);
        stringBuilder.append(filename);
        return stringBuilder.toString();
    }

    /**
     *  上傳圖片文件
     * @param prefix  文件前綴
     * @param filename  文件名
     * @param inputStream 文件流
     * @return  文件全路徑
     */
    @Override
    public String uploadImgFile(String prefix, String filename,InputStream inputStream) {
        String filePath = builderFilePath(prefix, filename);
        try {
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .object(filePath)
                    .contentType("image/jpg")
                    .bucket(minIOConfigProperties.getBucket()).stream(inputStream,inputStream.available(),-1)
                    .build();
            minioClient.putObject(putObjectArgs);
            StringBuilder urlPath = new StringBuilder(minIOConfigProperties.getReadPath());
            urlPath.append(separator+minIOConfigProperties.getBucket());
            urlPath.append(separator);
            urlPath.append(filePath);
            return urlPath.toString();
        }catch (Exception ex){
            log.error("minio put file error.",ex);
            throw new RuntimeException("上傳文件失敗");
        }
    }

    /**
     *  上傳html文件
     * @param prefix  文件前綴
     * @param filename   文件名
     * @param inputStream  文件流
     * @return  文件全路徑
     */
    @Override
    public String uploadHtmlFile(String prefix, String filename,InputStream inputStream) {
        String filePath = builderFilePath(prefix, filename);
        try {
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .object(filePath)
                    .contentType("text/html")
                    .bucket(minIOConfigProperties.getBucket()).stream(inputStream,inputStream.available(),-1)
                    .build();
            minioClient.putObject(putObjectArgs);
            StringBuilder urlPath = new StringBuilder(minIOConfigProperties.getReadPath());
            urlPath.append(separator+minIOConfigProperties.getBucket());
            urlPath.append(separator);
            urlPath.append(filePath);
            return urlPath.toString();
        }catch (Exception ex){
            log.error("minio put file error.",ex);
            ex.printStackTrace();
            throw new RuntimeException("上傳文件失敗");
        }
    }

    /**
     * 刪除文件
     * @param pathUrl  文件全路徑
     */
    @Override
    public void delete(String pathUrl) {
        String key = pathUrl.replace(minIOConfigProperties.getEndpoint()+"/","");
        int index = key.indexOf(separator);
        String bucket = key.substring(0,index);
        String filePath = key.substring(index+1);
        // 刪除Objects
        RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket(bucket).object(filePath).build();
        try {
            minioClient.removeObject(removeObjectArgs);
        } catch (Exception e) {
            log.error("minio remove file error.  pathUrl:{}",pathUrl);
            e.printStackTrace();
        }
    }


    /**
     * 下載文件
     * @param pathUrl  文件全路徑
     * @return  文件流
     *
     */
    @Override
    public byte[] downLoadFile(String pathUrl)  {
        String key = pathUrl.replace(minIOConfigProperties.getEndpoint()+"/","");
        int index = key.indexOf(separator);
        String bucket = key.substring(0,index);
        String filePath = key.substring(index+1);
        InputStream inputStream = null;
        try {
            inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(minIOConfigProperties.getBucket()).object(filePath).build());
        } catch (Exception e) {
            log.error("minio down file error.  pathUrl:{}",pathUrl);
            e.printStackTrace();
        }

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] buff = new byte[100];
        int rc = 0;
        while (true) {
            try {
                if (!((rc = inputStream.read(buff, 0, 100)) > 0)) break;
            } catch (IOException e) {
                e.printStackTrace();
            }
            byteArrayOutputStream.write(buff, 0, rc);
        }
        return byteArrayOutputStream.toByteArray();
    }
}
3.5.4 對外加入自動(dòng)配置

在resources中新建META-INF/spring.factories


org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.heima.file.service.impl.MinIOFileStorageService
3.5.5 其他微服務(wù)使用

第一,導(dǎo)入heima-file-starter的依賴

第二,在微服務(wù)中添加minio所需要的配置

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

第三,在對應(yīng)使用的業(yè)務(wù)類中注入FileStorageService,樣例如下:


package com.heima.minio.test;

import java.io.FileInputStream;
import java.io.FileNotFoundException;

@SpringBootTest(classes = MinioApplication.class)
@RunWith(SpringRunner.class)
public class MinioTest {

    @Autowired
    private FileStorageService fileStorageService;

    @Test
    public void testUpdateImgFile() {
        try {
            FileInputStream fileInputStream = new FileInputStream("E:\\tmp\\ak47.jpg");
            String filePath = fileStorageService.uploadImgFile("", "ak47.jpg", fileInputStream);
            System.out.println(filePath);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}

4)文章詳情

4.1)需求分析

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

4.2)實(shí)現(xiàn)方案

方案一

用戶某一條文章,根據(jù)文章的id去查詢文章內(nèi)容表,返回渲染頁面

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

方案二 效率高

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

報(bào)錯(cuò):

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

這個(gè)要在nacos里面配 minio;

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot
黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

?allowPublicKeyRetrieval=true 成功解決;

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

4.3)實(shí)現(xiàn)步驟

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot
黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

4.在文章微服務(wù)中導(dǎo)入依賴

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-freemarker</artifactId>
    </dependency>
    <dependency>
        <groupId>com.heima</groupId>
        <artifactId>heima-file-starter</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

5.新建ApArticleContentMapper


package com.heima.article.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.article.pojos.ApArticleContent;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface ApArticleContentMapper extends BaseMapper<ApArticleContent> {
}

6.在artile微服務(wù)中新增測試類(后期新增文章的時(shí)候創(chuàng)建詳情靜態(tài)頁,目前暫時(shí)手動(dòng)生成)


package com.heima.article.test;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

@SpringBootTest(classes = ArticleApplication.class)
@RunWith(SpringRunner.class)
public class ArticleFreemarkerTest {

    @Autowired
    private Configuration configuration;

    @Autowired
    private FileStorageService fileStorageService;


    @Autowired
    private ApArticleMapper apArticleMapper;

    @Autowired
    private ApArticleContentMapper apArticleContentMapper;

    @Test
    public void createStaticUrlTest() throws Exception {
        //1.獲取文章內(nèi)容
        ApArticleContent apArticleContent = apArticleContentMapper.selectOne(Wrappers.<ApArticleContent>lambdaQuery().eq(ApArticleContent::getArticleId, 1390536764510310401L));
        if(apArticleContent != null && StringUtils.isNotBlank(apArticleContent.getContent())){
           //2.文章內(nèi)容通過freemarker生成html文件
            StringWriter out = new StringWriter();
            Template template = configuration.getTemplate("article.ftl");

            //第一個(gè)參數(shù) 數(shù)據(jù)模型
            Map<String, Object> params = new HashMap<>();
            //JSONArray.parseArray 字符串轉(zhuǎn)成對象
            params.put("content", JSONArray.parseArray(apArticleContent.getContent()));
            //合成
            template.process(params, out);
            //輸入流
            InputStream is = new ByteArrayInputStream(out.toString().getBytes());

            //3.把html文件上傳到minio中    (前綴,文件名稱,輸入流)
            String path = fileStorageService.uploadHtmlFile("",
                    apArticleContent.getArticleId() + ".html", is);

            //4.修改ap_article表,保存static_url字段
            ApArticle article = new ApArticle();
            article.setId(apArticleContent.getArticleId());
            article.setStaticUrl(path);
            apArticleMapper.updateById(article);

        }
    }
}

大佬彈幕:人家現(xiàn)在發(fā)布文章,都是直接用富文本 編輯器,發(fā)布后數(shù)據(jù)庫存的是富文本,然后直接把富文本丟給前端人家就可以直接顯示了,搞這么麻煩干嘛,

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot
手機(jī)端適宜的屏幕大小
黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

03自媒體文章發(fā)布

1)自媒體前后端搭建

1.1)后臺搭建
黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

①:資料中找到heima-leadnews-wemedia.zip解壓

拷貝到heima-leadnews-service工程下,并指定子模塊

執(zhí)行l(wèi)eadnews-wemedia.sql腳本

添加對應(yīng)的nacos配置

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: root
# 設(shè)置Mapper接口所對應(yīng)的XML文件位置,如果你在Mapper接口中有自定義方法,需要進(jìn)行該配置
mybatis-plus:
  mapper-locations: classpath*:mapper/*.xml
  # 設(shè)置別名包掃描路徑,通過該屬性可以給包中的類注冊別名
  type-aliases-package: com.heima.model.media.pojos

②:資料中找到heima-leadnews-wemedia-gateway.zip解壓

拷貝到heima-leadnews-gateway工程下,并指定子模塊

添加對應(yīng)的nacos配置

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

③:在資料中找到類文件夾

拷貝wemedia文件夾到heima-leadnews-model模塊下的com.heima.model

1.2)前臺搭建
黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

通過nginx的虛擬主機(jī)功能,使用同一個(gè)nginx訪問多個(gè)項(xiàng)目

搭建步驟:

①:資料中找到wemedia-web.zip解壓

②:在nginx中l(wèi)eadnews.conf目錄中新增heima-leadnews-wemedia.conf文件

  • 網(wǎng)關(guān)地址修改(localhost:51602)

  • 前端項(xiàng)目目錄修改(wemedia-web解壓的目錄)

  • 訪問端口修改(8802)


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

server {
	listen 8802;
	location / {
		root D:/workspace/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;   # 記錄真實(shí)發(fā)出請求的客戶端IP
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  #記錄代理信息
	}
}

③:啟動(dòng)nginx,啟動(dòng)自媒體微服務(wù)和對應(yīng)網(wǎng)關(guān)

④:聯(lián)調(diào)測試登錄功能 測試成功

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

2)自媒體素材管理

2.1)素材上傳
2.2.1)需求分析
黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

圖片上傳的頁面,首先是展示素材信息,可以點(diǎn)擊圖片上傳,彈窗后可以上傳圖片

2.2.2)素材管理-圖片上傳-表結(jié)構(gòu)

媒體圖文素材信息表wm_material

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

對應(yīng)實(shí)體類:


package com.heima.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_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;

    /**
     * 創(chuàng)建時(shí)間
     */
    @TableField("created_time")
    private Date createdTime;

}
2.2.3)實(shí)現(xiàn)思路
黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

①:前端發(fā)送上傳圖片請求,類型為MultipartFile

②:網(wǎng)關(guān)進(jìn)行token解析后,把解析后的用戶信息存儲到header


//獲得token解析后中的用戶信息
Object userId = claimsBody.get("id");
//在header中添加新的信息
ServerHttpRequest serverHttpRequest = request.mutate().headers(httpHeaders -> {
    httpHeaders.add("userId", userId + "");
}).build();
//重置header
exchange.mutate().request(serverHttpRequest).build();

③:自媒體微服務(wù)使用攔截器獲取到header中的的用戶信息,并放入到threadlocal

在heima-leadnews-utils中新增工具類

注意:需要從資料中找出WmUser實(shí)體類拷貝到model工程下


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<>();

    /**
     * 添加用戶
     * @param 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();
    }
}

在heima-leadnews-wemedia中新增攔截器


package com.heima.wemedia.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Optional;

@Slf4j
public class WmTokenInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //得到header中的信息
        String userId = request.getHeader("userId");
        Optional<String> optional = Optional.ofNullable(userId);
        if(optional.isPresent()){
            //把用戶id存入threadloacl中
            WmUser wmUser = new WmUser();
            wmUser.setId(Integer.valueOf(userId));
            WmThreadLocalUtils.setUser(wmUser);
            log.info("wmTokenFilter設(shè)置用戶信息到threadlocal中...");
        }

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("清理threadlocal...");
        WmThreadLocalUtils.clear();
    }
}

配置使攔截器生效,攔截所有的請求


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 {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new WmTokenInterceptor()).addPathPatterns("/**");
    }
}

④:先把圖片上傳到minIO中,獲取到圖片請求的路徑——(2.2.5查看具體功能實(shí)現(xiàn))

⑤:把用戶id和圖片上的路徑保存到素材表中——(2.2.5查看具體功能實(shí)現(xiàn))

2.2.4)接口定義

說明

接口路徑

/api/v1/material/upload_picture

請求方式

POST

參數(shù)

MultipartFile

響應(yīng)結(jié)果

ResponseResult

MultipartFile :Springmvc指定的文件接收類型

ResponseResult :

成功需要回顯圖片,返回素材對象

{
  "host":null,
  "code":200,
  "errorMessage":"操作成功",
  "data":{
    "id":52,
    "userId":1102,
    "url":"http://192.168.200.130:9000/leadnews/2021/04/26/a73f5b60c0d84c32bfe175055aaaac40.jpg",
    "type":0,
    "isCollection":0,
    "createdTime":"2021-01-20T16:49:48.443+0000"
  }
}

失?。?/p>

  • 參數(shù)失效

  • 文章上傳失敗

2.2.5)自媒體微服務(wù)集成heima-file-starter

①:導(dǎo)入heima-file-starter

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

②:在自媒體微服務(wù)的配置中心添加以下配置:

minio:
  accessKey: minio
  secretKey: minio123
  bucket: leadnews
  endpoint: http://192.168.200.130:9000
  readPath: http://192.168.200.130:9000
2.2.6)具體實(shí)現(xiàn)

①:創(chuàng)建WmMaterialController


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


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

}

②:mapper


package com.heima.wemedia.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.wemedia.pojos.WmMaterial;
import org.apache.ibatis.annotations.Mapper;

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

③:業(yè)務(wù)層:


package com.heima.wemedia.service;

public interface WmMaterialService extends IService<WmMaterial> {

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

業(yè)務(wù)層實(shí)現(xiàn)類:


package com.heima.wemedia.service.impl;
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.檢查參數(shù)
        if(multipartFile == null || multipartFile.getSize() == 0){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }

        //2.上傳圖片到minIO中
        String fileName = UUID.randomUUID().toString().replace("-", "");
        //aa.jpg
        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.保存到數(shù)據(jù)庫中
        WmMaterial wmMaterial = new WmMaterial();
        wmMaterial.setUserId(WmThreadLocalUtil.getUser().getId());
        wmMaterial.setUrl(fileId);
        wmMaterial.setIsCollection((short)0);
        wmMaterial.setType((short)0);
        wmMaterial.setCreatedTime(new Date());
        save(wmMaterial);

        //4.返回結(jié)果

        return ResponseResult.okResult(wmMaterial);
    }
}

④:控制器


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

    @Autowired
    private WmMaterialService wmMaterialService;

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

⑤:測試

啟動(dòng)自媒體微服務(wù)和自媒體網(wǎng)關(guān),使用前端項(xiàng)目進(jìn)行測試

2.2)素材列表查詢
2.2.1)接口定義

說明

接口路徑

/api/v1/material/list

請求方式

POST

參數(shù)

WmMaterialDto

響應(yīng)結(jié)果

ResponseResult

WmMaterialDto :


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

ResponseResult :

{
  "host":null,
  "code":200,
  "errorMessage":"操作成功",
  "data":[
    {
    "id":52,
      "userId":1102,
      "url":"http://192.168.200.130:9000/leadnews/2021/04/26/ec893175f18c4261af14df14b83cb25f.jpg",
      "type":0,
      "isCollection":0,
      "createdTime":"2021-01-20T16:49:48.000+0000"
    },
    ....
  ],
  "currentPage":1,
  "size":20,
  "total":0
}
2.2.2)功能實(shí)現(xiàn)

①:在WmMaterialController類中新增方法


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

②:mapper已定義

③:業(yè)務(wù)層

在WmMaterialService中新增方法


/**
     * 素材列表查詢
     * @param dto
     * @return
     */
public ResponseResult findList( WmMaterialDto dto);

實(shí)現(xiàn)方法:


/**
     * 素材列表查詢
     * @param dto
     * @return
     */
@Override
public ResponseResult findList(WmMaterialDto dto) {

    //1.檢查參數(shù)
    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());

    //按照時(shí)間倒序
    lambdaQueryWrapper.orderByDesc(WmMaterial::getCreatedTime);  
    page = page(page,lambdaQueryWrapper);

    //3.結(jié)果返回
    ResponseResult responseResult = new PageResponseResult(dto.getPage(),dto.getSize(),(int)page.getTotal());
    responseResult.setData(page.getRecords());
    return responseResult;
}

④:控制器:


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

⑤:在自媒體引導(dǎo)類中 添加 mybatis-plus的分頁攔截器


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

3)自媒體文章管理

3.1)查詢所有頻道
3.1.1)需求分析
黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot
3.1.2)表結(jié)構(gòu)

wm_channel 頻道信息表

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

對應(yīng)實(shí)體類:


package com.heima.model.wemedia.pojos;

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;

    /**
     * 是否默認(rèn)頻道
     * 1:默認(rèn)     true
     * 0:非默認(rèn)   false
     */
    @TableField("is_default")
    private Boolean isDefault;

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

    /**
     * 默認(rèn)排序
     */
    @TableField("ord")
    private Integer ord;

    /**
     * 創(chuàng)建時(shí)間
     */
    @TableField("created_time")
    private Date createdTime;

}
3.1.3)接口定義

說明

接口路徑

/api/v1/channel/channels

請求方式

POST

參數(shù)

響應(yīng)結(jié)果

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 {  ... }
  ]
}
3.1.4)功能實(shí)現(xiàn)

接口定義:


package com.heima.wemedia.controller.v1;

import org.springframework.web.bind.annotation.RestController;

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

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

mapper


package com.heima.wemedia.mapper;

import org.apache.ibatis.annotations.Mapper;

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

service


package com.heima.wemedia.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.wemedia.pojos.WmChannel;

public interface WmChannelService extends IService<WmChannel> {

    /**
     * 查詢所有頻道
     * @return
     */
    public ResponseResult findAll();


}

實(shí)現(xiàn)類


package com.heima.wemedia.service.impl;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
@Slf4j
public class WmChannelServiceImpl extends ServiceImpl<WmChannelMapper, WmChannel> implements WmChannelService {
    /**
     * 查詢所有頻道
     * @return
     */
    @Override
    public ResponseResult findAll() {
        return ResponseResult.okResult(list());
    }
}

控制層


package com.heima.wemedia.controller.v1;

import com.heima.model.common.dtos.ResponseResult;
import org.springframework.web.bind.annotation.RestController;

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

    @Autowired
    private WmChannelService wmChannelService;

    @GetMapping("/channels")
    public ResponseResult findAll(){
        return wmChannelService.findAll();
    }
}
3.1.5)測試
3.2)查詢自媒體文章
3.2.1)需求說明
黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot
3.2.2)表結(jié)構(gòu)分析

wm_news 自媒體文章表

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

對應(yīng)實(shí)體類:


package com.heima.model.wemedia.pojos;

import java.io.Serializable;
import java.util.Date;

/**
 * <p>
 * 自媒體圖文內(nèi)容信息表
 * </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;

    /**
     * 標(biāo)題
     */
    @TableField("title")
    private String title;

    /**
     * 圖文內(nèi)容
     */
    @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;

    /**
     * 創(chuàng)建時(shí)間
     */
    @TableField("created_time")
    private Date createdTime;

    /**
     * 提交時(shí)間
     */
    @TableField("submited_time")
    private Date submitedTime;

    /**
     * 當(dāng)前狀態(tài)
            0 草稿
            1 提交(待審核)
            2 審核失敗
            3 人工審核
            4 人工審核通過
            8 審核通過(待發(fā)布)
            9 已發(fā)布
     */
    @TableField("status")
    private Short status;

    /**
     * 定時(shí)發(fā)布時(shí)間,不定時(shí)則為空
     */
    @TableField("publish_time")
    private Date publishTime;

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

    /**
     * 發(fā)布庫文章ID
     */
    @TableField("article_id")
    private Long articleId;

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

    @TableField("enable")
    private Short enable;
    
     //狀態(tài)枚舉類
    @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.2.3)接口定義

說明

接口路徑

/api/v1/news/list

請求方式

POST

參數(shù)

WmNewsPageReqDto

響應(yīng)結(jié)果

ResponseResult

WmNewsPageReqDto :


package com.heima.model.wemedia.dtos;

import com.heima.model.common.dtos.PageRequestDto;
import lombok.Data;

import java.util.Date;

@Data
public class WmNewsPageReqDto extends PageRequestDto {

    /**
     * 狀態(tài)
     */
    private Short status;
    /**
     * 開始時(shí)間
     */
    private Date beginPubDate;
    /**
     * 結(jié)束時(shí)間
     */
    private Date endPubDate;
    /**
     * 所屬頻道ID
     */
    private Integer channelId;
    /**
     * 關(guān)鍵字
     */
    private String keyword;
}

ResponseResult :

{
  "host": "null",
  "code": 0,
  "errorMessage": "操作成功",
  "data": [
    Object { ... },
    Object { ... },
    Object { ... }
    
  ],
  "currentPage":1,
  "size":10,
  "total":21
}
3.2.4)功能實(shí)現(xiàn)

①:新增WmNewsController


package com.heima.wemedia.controller.v1;

import com.heima.model.common.dtos.ResponseResult;

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

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

}

②:新增WmNewsMapper


package com.heima.wemedia.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.wemedia.pojos.WmNews;
import org.apache.ibatis.annotations.Mapper;

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

③:新增WmNewsService


package com.heima.wemedia.service;

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

public interface WmNewsService extends IService<WmNews> {

    /**
     * 查詢文章
     * @param dto
     * @return
     */
    public ResponseResult findAll(WmNewsPageReqDto dto);

}

實(shí)現(xiàn)類:


package com.heima.wemedia.service.impl;
import org.springframework.transaction.annotation.Transactional;

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

    /**
     * 查詢文章
     * @param dto
     * @return
     */
    @Override
    public ResponseResult findAll(WmNewsPageReqDto dto) {

        //1.檢查參數(shù)
        if(dto == null){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        //分頁參數(shù)檢查
        dto.checkParam();
        //獲取當(dāng)前登錄人的信息
        WmUser user = WmThreadLocalUtil.getUser();
        if(user == null){
            return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
        }

        //2.分頁條件查詢
        IPage page = new Page(dto.getPage(),dto.getSize());
        LambdaQueryWrapper<WmNews> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //狀態(tài)精確查詢
        if(dto.getStatus() != null){
            lambdaQueryWrapper.eq(WmNews::getStatus,dto.getStatus());
        }

        //頻道精確查詢
        if(dto.getChannelId() != null){
            lambdaQueryWrapper.eq(WmNews::getChannelId,dto.getChannelId());
        }

        //時(shí)間范圍查詢
        if(dto.getBeginPubDate()!=null && dto.getEndPubDate()!=null){
            lambdaQueryWrapper.between(WmNews::getPublishTime,dto.getBeginPubDate(),dto.getEndPubDate());
        }

        //關(guān)鍵字模糊查詢
        if(StringUtils.isNotBlank(dto.getKeyword())){
            lambdaQueryWrapper.like(WmNews::getTitle,dto.getKeyword());
        }

        //查詢當(dāng)前登錄用戶的文章
        lambdaQueryWrapper.eq(WmNews::getUserId,user.getId());

        //發(fā)布時(shí)間倒序查詢
        lambdaQueryWrapper.orderByDesc(WmNews::getCreatedTime);

        page = page(page,lambdaQueryWrapper);

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

        return responseResult;
    }
   
}

④:控制器


package com.heima.wemedia.controller.v1;
import org.springframework.web.bind.annotation.RestController;

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


    @Autowired
    private WmNewsService wmNewsService;

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

}
3.2.5)測試

啟動(dòng)后端自媒體微服務(wù)和自媒體網(wǎng)關(guān)微服務(wù),測試文章列表查詢

3.3)文章發(fā)布
3.3.1)需求分析
黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot
3.3.2)表結(jié)構(gòu)分析

保存文章,除了需要wm_news表以外,還需要另外兩張表;

保存了這個(gè)關(guān)系表,就可以保存素材,素材有被引用的標(biāo)記,就不能被修改或刪除

黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

其中wm_material和wm_news表的實(shí)體類已經(jīng)導(dǎo)入到了項(xiàng)目中,下面是wm_news_material表對應(yīng)的實(shí)體類:


package com.heima.model.wemedia.pojos;
import java.io.Serializable;

/**
 * <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 內(nèi)容引用
            1 主圖引用
     */
    @TableField("type")
    private Short type;

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

}
3.3.3)實(shí)現(xiàn)思路分析
黑馬頭條項(xiàng)目,SpringCloud微服務(wù)技術(shù)棧,架構(gòu),微服務(wù),spring boot

1.前端提交發(fā)布或保存為草稿

2.后臺判斷請求中是否包含了文章id

3.如果不包含id,則為新增

3.1 執(zhí)行新增文章的操作

3.2 關(guān)聯(lián)文章內(nèi)容圖片與素材的關(guān)系

3.3 關(guān)聯(lián)文章封面圖片與素材的關(guān)系

4.如果包含了id,則為修改請求

4.1 刪除該文章與素材的所有關(guān)系

4.2 執(zhí)行修改操作

4.3 關(guān)聯(lián)文章內(nèi)容圖片與素材的關(guān)系

4.4 關(guān)聯(lián)文章封面圖片與素材的關(guān)系

3.3.4)接口定義

說明

接口路徑

/api/v1/channel/submit

請求方式

POST

參數(shù)

WmNewsDto

響應(yīng)結(jié)果

ResponseResult

WmNewsDto


package com.heima.model.wemedia.dtos;

import lombok.Data;

import java.util.Date;
import java.util.List;

@Data
public class WmNewsDto {
    
    private Integer id;
     /**
     * 標(biāo)題
     */
    private String title;
     /**
     * 頻道id
     */
    private Integer channelId;
     /**
     * 標(biāo)簽
     */
    private String labels;
     /**
     * 發(fā)布時(shí)間
     */
    private Date publishTime;
     /**
     * 文章內(nèi)容
     */
    private String content;
     /**
     * 文章封面類型  0 無圖 1 單圖 3 多圖 -1 自動(dòng)
     */
    private Short type;
     /**
     * 提交時(shí)間
     */
    private Date submitedTime; 
     /**
     * 狀態(tài) 提交為1  草稿為0
     */
    private Short status;
     
     /**
     * 封面圖片列表 多張圖以逗號隔開
     */
    private List<String> images;
}

前端給傳遞過來的json數(shù)據(jù)格式為:

{
    "title":"黑馬頭條項(xiàng)目背景",
    "type":"1",//這個(gè) 0 是無圖  1 是單圖  3 是多圖  -1 是自動(dòng)
    "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":"隨著智能手機(jī)的普及,人們更加習(xí)慣于通過手機(jī)來看新聞。由于生活節(jié)奏的加快,很多人只能利用碎片時(shí)間來獲取信息,因此,對于移動(dòng)資訊客戶端的需求也越來越高。黑馬頭條項(xiàng)目正是在這樣背景下開發(fā)出來。黑馬頭條項(xiàng)目采用當(dāng)下火熱的微服務(wù)+大數(shù)據(jù)技術(shù)架構(gòu)實(shí)現(xiàn)。本項(xiàng)目主要著手于獲取最新最熱新聞資訊,通過大數(shù)據(jù)分析用戶喜好精確推送咨詢新聞"
    },
    {
        "type":"image",
        "value":"http://192.168.200.130/group1/M00/00/00/wKjIgl5swbGATaSAAAEPfZfx6Iw790.png"
    }
]"
}

ResponseResult:

{
    “code”:501,
    “errorMessage”:“參數(shù)失效"
}
{
    “code”:200,
    “errorMessage”:“操作成功"
}
{
    “code”:501,
    “errorMessage”:“素材引用失效"
}
3.3.5)功能實(shí)現(xiàn)

①:在新增WmNewsController中新增方法

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

②:新增WmNewsMaterialMapper類,文章與素材的關(guān)聯(lián)關(guān)系需要批量保存,索引需要定義mapper文件和對應(yīng)的映射文件


package com.heima.wemedia.mapper;
import java.util.List;

@Mapper
public interface WmNewsMaterialMapper extends BaseMapper<WmNewsMaterial> {

     void saveRelations(@Param("materialIds") List<Integer> materialIds,@Param("newsId") Integer newsId, @Param("type")Short type);
}

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>

③:常量類準(zhǔn)備


package com.heima.common.constants;

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;
}

④:在WmNewsService中新增方法


/**
 *  發(fā)布文章或保存草稿
 * @param dto
 * @return
 */
public ResponseResult submitNews(WmNewsDto dto);

實(shí)現(xiàn)方法:


/**
     * 發(fā)布修改文章或保存為草稿
     * @param dto
     * @return
     */
@Override
public ResponseResult submitNews(WmNewsDto dto) {

    //0.條件判斷
    if(dto == null || dto.getContent() == null){
        return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
    }

    //1.保存或修改文章

    WmNews wmNews = new WmNews();
    //屬性拷貝 屬性名詞和類型相同才能拷貝
    BeanUtils.copyProperties(dto,wmNews);
    //封面圖片  list---> string
    if(dto.getImages() != null && dto.getImages().size() > 0){
        //[1dddfsd.jpg,sdlfjldk.jpg]-->   1dddfsd.jpg,sdlfjldk.jpg
        String imageStr = StringUtils.join(dto.getImages(), ",");
        wmNews.setImages(imageStr);
    }
    //如果當(dāng)前封面類型為自動(dòng) -1
    if(dto.getType().equals(WemediaConstants.WM_NEWS_TYPE_AUTO)){
        wmNews.setType(null);
    }

    saveOrUpdateWmNews(wmNews);

    //2.判斷是否為草稿  如果為草稿結(jié)束當(dāng)前方法
    if(dto.getStatus().equals(WmNews.Status.NORMAL.getCode())){
        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
    }

    //3.不是草稿,保存文章內(nèi)容圖片與素材的關(guān)系
    //獲取到文章內(nèi)容中的圖片信息
    List<String> materials =  ectractUrlInfo(dto.getContent());
    saveRelativeInfoForContent(materials,wmNews.getId());

    //4.不是草稿,保存文章封面圖片與素材的關(guān)系,如果當(dāng)前布局是自動(dòng),需要匹配封面圖片
    saveRelativeInfoForCover(dto,wmNews,materials);

    return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);

}

/**
     * 第一個(gè)功能:如果當(dāng)前封面類型為自動(dòng),則設(shè)置封面類型的數(shù)據(jù)
     * 匹配規(guī)則:
     * 1,如果內(nèi)容圖片大于等于1,小于3  單圖  type 1
     * 2,如果內(nèi)容圖片大于等于3  多圖  type 3
     * 3,如果內(nèi)容沒有圖片,無圖  type 0
     *
     * 第二個(gè)功能:保存封面圖片與素材的關(guān)系
     * @param dto
     * @param wmNews
     * @param materials
     */
private void saveRelativeInfoForCover(WmNewsDto dto, WmNews wmNews, List<String> materials) {

    List<String> images = dto.getImages();

    //如果當(dāng)前封面類型為自動(dòng),則設(shè)置封面類型的數(shù)據(jù)
    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);
    }

}


/**
     * 處理文章內(nèi)容圖片與素材的關(guān)系
     * @param materials
     * @param newsId
     */
private void saveRelativeInfoForContent(List<String> materials, Integer newsId) {
    saveRelativeInfo(materials,newsId,WemediaConstants.WM_CONTENT_REFERENCE);
}

@Autowired
private WmMaterialMapper wmMaterialMapper;

/**
     * 保存文章圖片與素材的關(guān)系到數(shù)據(jù)庫中
     * @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){
            //手動(dòng)拋出異常   第一個(gè)功能:能夠提示調(diào)用者素材失效了,第二個(gè)功能,進(jìn)行數(shù)據(jù)的回滾
            throw new CustomException(AppHttpCodeEnum.MATERIASL_REFERENCE_FAIL);
        }

        if(materials.size() != dbMaterials.size()){
            throw new CustomException(AppHttpCodeEnum.MATERIASL_REFERENCE_FAIL);
        }

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

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

}


/**
     * 提取文章內(nèi)容中的圖片信息
     * @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;
}

@Autowired
private WmNewsMaterialMapper wmNewsMaterialMapper;

/**
     * 保存或修改文章
     * @param wmNews
     */
private void saveOrUpdateWmNews(WmNews wmNews) {
    //補(bǔ)全屬性
    wmNews.setUserId(WmThreadLocalUtil.getUser().getId());
    wmNews.setCreatedTime(new Date());
    wmNews.setSubmitedTime(new Date());
    wmNews.setEnable((short)1);//默認(rèn)上架

    if(wmNews.getId() == null){
        //保存
        save(wmNews);
    }else {
        //修改
        //刪除文章圖片與素材的關(guān)系
        wmNewsMaterialMapper.delete(Wrappers.<WmNewsMaterial>lambdaQuery().eq(WmNewsMaterial::getNewsId,wmNews.getId()));
        updateById(wmNews);
    }

}

④:控制器


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

《黑馬頭條》 內(nèi)容安全 自動(dòng)審核 feign 延遲任務(wù)精準(zhǔn)發(fā)布 kafka_軟工菜雞的博客-CSDN博客文章來源地址http://www.zghlxwxcb.cn/news/detail-608079.html

到了這里,關(guān)于《黑馬頭條》SpringBoot+SpringCloud+ Nacos等企業(yè)級微服務(wù)架構(gòu)項(xiàng)目的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包