日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

《黑马头条》SpringBoot+SpringCloud+ Nacos等企业级微服务架构项目

發布時間:2024/1/18 javascript 83 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《黑马头条》SpringBoot+SpringCloud+ Nacos等企业级微服务架构项目 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

01環境搭建、SpringCloud微服務(注冊發現、服務調用、網關)

1)課程對比

2)項目概述

2.1)能讓你收獲什么

2.2)項目課程大綱

2.3)項目概述

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

黑馬頭條項目正是在這樣背景下開發出來。黑馬頭條項目采用當下火熱的微服務+大數據技術架構實現。本項目主要著手于獲取最新最熱新聞資訊,通過大數據分析用戶喜好精確推送咨詢新聞

2.4)項目術語

2.5)業務說明

項目演示地址:

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

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

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

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

其中app端為移動端,打開方式有兩種:

  • 谷歌瀏覽器打開,調成移動端模式

  • 手機瀏覽器打開或掃描右側二維碼

3)技術棧

  • Spring-Cloud-Gateway : 微服務之前架設的網關服務,實現服務注冊中的API請求路由,以及控制流速控制和熔斷處理都是常用的架構手段,而這些功能Gateway天然支持

  • 運用Spring Boot快速開發框架,構建項目工程;并結合Spring Cloud全家桶技術,實現后端個人中心、自媒體、管理中心等微服務。

  • 運用Spring Cloud Alibaba Nacos作為項目中的注冊中心和配置中心

  • 運用mybatis-plus作為持久層提升開發效率

  • 運用Kafka完成內部系統消息通知;與客戶端系統消息通知;以及實時數據計算

  • 運用Redis緩存技術,實現熱數據的計算,提升系統性能指標

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

  • 使用Mongo存儲用戶熱數據,以保證用戶熱數據高擴展和高性能指標

  • 使用FastDFS作為靜態資源存儲器,在其上實現熱靜態資源緩存、淘汰等功能

  • 運用Hbase技術,存儲系統中的冷數據,保證系統數據的可靠性

  • 運用ES搜索技術,對冷數據、文章數據建立索引,以保證冷數據、文章查詢性能

  • 運用AI技術,來完成系統自動化功能,以提升效率及節省成本。比如實名認證自動化

  • PMD&P3C : 靜態代碼掃描工具,在項目中掃描項目代碼,檢查異常點、優化點、代碼規范等,為開發團隊提供規范統一,提升項目代碼質量

4)nacos環境搭建

4.1)虛擬機鏡像準備

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

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

  • 修改虛擬網絡地址(NAT)

  • ①,選中VMware中的編輯

    ②,選擇虛擬網絡編輯器

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

    4)修改虛擬機的網絡模式為NAT

    5)啟動虛擬機,用戶名:root 密碼:itcast,當前虛擬機的ip已手動固定(靜態IP), 地址為:192.168.200.130

    6)使用FinalShell客戶端鏈接

    4.2)nacos安裝

    ①:docker拉取鏡像

    docker pull nacos/nacos-server:1.2.0

    ②:創建容器

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

    • --restart=always 開機啟動

    • -p 8848:8848 映射端口

    • -d 創建一個守護式容器在后臺運行

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

    5)初始工程搭建

    5.1)環境準備

    ①:項目依賴環境(需提前安裝好)

    • JDK1.8

    • Intellij Idea

    • maven-3.6.1

    • Git

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

    ③:IDEA開發工具配置

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

    ④:設置項目編碼格式

    5.2)主體結構

    6)登錄

    6.1)需求分析

    • 戶點擊開始使用

    登錄后的用戶權限較大,可以查 看,也可以操作(點贊,關注,評論)

    • 用戶點擊不登錄,先看看

    游客只有查看的權限

    6.2)表結構分析

    關于app端用戶相關的內容較多,可以單獨設置一個庫leadnews_user

    表名稱

    說明

    ap_user

    APP用戶信息表

    ap_user_fan

    APP用戶粉絲信息表

    ap_user_follow

    APP用戶關注信息表

    ap_user_realname

    APP實名認證信息表

    從當前資料中找到對應數據庫并導入到mysql中

    登錄需要用到的是ap_user表,表結構如下:

    項目中的持久層使用的mybatis-plus,一般都使用mybais-plus逆向生成對應的實體類

    app_user表對應的實體類如下:

    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;/*** 手機號*/@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;/*** 是否身份認證*/@TableField("is_identity_authentication")private Boolean identityAuthentication;/*** 0正常1鎖定*/@TableField("status")private Boolean status;/*** 0 普通用戶1 自媒體人2 大V*/@TableField("flag")private Short flag;/*** 注冊時間*/@TableField("created_time")private Date createdTime;}

    手動加密(md5+隨機字符串)

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

    注冊->生成鹽

    登錄->使用鹽來配合驗證

    6.3)思路分析

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

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

    6.4)運營端微服務搭建

    在heima-leadnews-service下創建工程heima-leadnews-user

    引導類

    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//集成當前注冊中心 @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-usercloud:nacos:discovery:server-addr: 192.168.200.130:8848config:server-addr: 192.168.200.130:8848file-extension: yml

    在nacos中創建配置文件

    spring:datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/leadnews_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTCusername: rootpassword: root # 設置Mapper接口所對應的XML文件位置,如果你在Mapper接口中有自定義方法,需要進行該配置 mybatis-plus:mapper-locations: classpath*:mapper/*.xml# 設置別名包掃描路徑,通過該屬性可以給包中的類注冊別名type-aliases-package: com.heima.model.user.pojos

    報錯:

    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 輸出設置 --><appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><encoder><!--格式化輸出:%d表示日期,%thread表示線程名,%-5level:級別從左顯示5個字符寬度%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"><!-- 不丟失日志.默認的,如果隊列的80%已滿,則會丟棄TRACT、DEBUG、INFO級別的日志 --><discardingThreshold>0</discardingThreshold><!-- 更改默認的隊列的深度,該值會影響性能.默認值為256 --><queueSize>512</queueSize><!-- 添加附加的appender,最多只能添加一個 --><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)登錄功能實現

    ①:接口定義

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

    ③:業務層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);}

    實現類:

    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 {@Overridepublic ResponseResult login(LoginDto dto) {//1.正常登錄(手機號+密碼登錄)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 返回數據 jwtMap<String, Object> map = new HashMap<>();map.put("token", AppJwtUtil.getToken(apUser.getId().longValue()));//這兩個值 置空 再返回對象apUser.setSalt("");apUser.setPassword("");map.put("user", apUser);return ResponseResult.okResult(map);} else {//2.游客 同樣返回token id = 0Map<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 {@Autowiredprivate ApUserService apUserService;@PostMapping("/login_auth")public ResponseResult login(@RequestBody LoginDto dto) {return apUserService.login(dto);} }

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

    報錯的兄弟們先創建redis容器a然后把nacos配置中redis密碼那一行注掉就行了

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

    7)接口工具postman、swagger、knife4j

    7.1)postman

    Postman是一款功能強大的網頁調試與發送網頁HTTP請求的Chrome插件。postman被500萬開發者和超100,000家公司用于每月訪問1.3億個API。

    官方網址:https://www.postman.com/

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

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

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

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

    7.2)swagger

    (1)簡介

    Swagger 是一個規范和完整的框架,用于生成、描述、調用和可視化 RESTful 風格的 Web 服務(https://swagger.io/)。 它的主要作用是:

  • 使得前后端分離開發更加方便,有利于團隊協作

  • 接口的文檔在線自動生成,降低后端開發人員編寫接口文檔的負擔

  • 功能測試

  • Spring已經將Swagger納入自身的標準,建立了Spring-swagger項目,現在叫Springfox

    通過在項目中引入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中進行配置即可,因為其他微服務工程都直接或間接依賴即可。

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

    新增: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 {@Beanpublic Docket buildDocket() {return new Docket(DocumentationType.SWAGGER_2).apiInfo(buildApiInfo()).select()// 要掃描的API(Controller)基礎包.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:修飾整個類,描述Controller的作用

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

    @ApiParam:單個參數的描述信息

    @ApiModel:用對象來接收參數

    @ApiModelProperty:用對象接收參數時,描述對象的一個字段

    @ApiResponse:HTTP響應其中1個描述

    @ApiResponses:HTTP響應整體描述

    @ApiIgnore:使用該注解忽略這個API

    @ApiError :發生錯誤返回的信息

    @ApiImplicitParam:一個請求參數

    @ApiImplicitParams:多個請求參數的描述信息

    @ApiImplicitParam屬性:

    屬性

    取值

    作用

    paramType

    查詢參數類型

    path

    以地址的形式提交數據

    query

    直接跟參數完成自動映射賦值

    body

    以流的形式提交 僅支持POST

    header

    參數在request headers 里邊提交

    form

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

    dataType

    參數的數據類型 只作為標志說明,并沒有實際驗證

    Long

    String

    name

    接收參數名

    value

    接收參數的意義描述

    required

    參數是否必填

    true

    必填

    false

    非必填

    defaultValue

    默認值

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

    @RestController @RequestMapping("/api/v1/login") @Api(value = "app端用戶登錄", tags = "ap_user", description = "app端用戶登錄API") public class ApUserLoginController {@Autowiredprivate ApUserService apUserService;@PostMapping("/login_auth")@ApiOperation("用戶登錄")public ResponseResult login(@RequestBody LoginDto dto){return apUserService.login(dto);} }

    LoginDto

    @Data public class LoginDto {/*** 手機號*/@ApiModelProperty(value="手機號",required = true)private String phone;/*** 密碼*/@ApiModelProperty(value="密碼",required = true)private String password; }

    啟動user微服務,訪問地址:http://localhost:51801/swagger-ui.html

    7.3)knife4j

    (1)簡介

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

    gitee地址:https://gitee.com/xiaoym/knife4j

    官方文檔:https://doc.xiaominfo.com/

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

    (2)核心功能

    該UI增強包主要包括兩大核心功能:文檔說明 和 在線調試

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

    • 在線調試:提供在線接口聯調的強大功能,自動解析當前接口參數,同時包含表單驗證,調用參數可返回接口響應內容、headers、Curl請求命令實例、響應時間、響應狀態碼等信息,幫助開發者在線調試,而不必通過其他測試工具測試接口是否正確,簡介、強大。

    • 個性化配置(Swaager無):通過個性化ui配置項,可自定義UI的相關顯示信息

    • 離線文檔(Swaager無):根據標準規范,生成的在線markdown離線文檔,開發者可以進行拷貝生成markdown接口文檔,通過其他第三方markdown轉換工具轉換成html或pdf,這樣也可以放棄swagger2markdown組件

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

    • 例如一個注冊功能主要包含了多個步驟,可以根據swagger-bootstrap-ui提供的接口排序規則實現接口的排序,step化接口操作,方便其他開發者進行接口對接

    (3)快速集成

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

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

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

    新建Swagger的配置文件SwaggerConfiguration.java文件,創建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();} }

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

    注解

    說明

    @EnableSwagger2

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

    @EnableKnife4j

    該注解是knife4j提供的增強注解,Ui提供了例如動態參數、參數過濾、接口排序等增強功能,如果你想使用這些增強功能就必須加該注解,否則可以不用加

    • 添加配置

    在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)網關

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

    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下創建heima-leadnews-app-gateway微服務

    引導類:

    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-gatewaycloud:nacos:discovery:server-addr: 192.168.200.130:8848config:server-addr: 192.168.200.130:8848file-extension: yml

    在nacos的配置中心創建dataid為leadnews-app-gateway的yml配置

    spring:cloud:gateway:globalcors:add-to-simple-url-handler-mapping: truecorsConfigurations:'[/**]':allowedHeaders: "*"allowedOrigins: "*"allowedMethods:- GET- POST- DELETE- PUT- OPTIONroutes:# 平臺管理- id: useruri: lb://leadnews-userpredicates:- Path=/user/**filters:- StripPrefix= 1

    環境搭建完成以后,啟動項目網關和用戶兩個服務,使用postman進行測試

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

    1.3 全局過濾器實現jwt校驗

    思路分析:

  • 用戶進入網關開始登陸,網關過濾器進行判斷,如果是登錄,則路由到后臺管理微服務進行登錄

  • 用戶登錄成功,后臺管理微服務簽發JWT TOKEN信息返回給用戶

  • 用戶再次進入網關開始訪問,網關過濾器接收用戶攜帶的TOKEN

  • 網關過濾器解析TOKEN ,判斷是否有權限,如果有,則放行,如果沒有則返回未認證錯誤

  • 具體實現:

    第一:

    在認證過濾器中需要用到jwt的解析,所以需要把工具類拷貝一份到網關微服務

    第二:

    在網關微服務中新建全局過濾器:

    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 {@Overridepublic 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.獲取tokenString 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);}/*** 優先級設置 值越小 優先級越高* @return*/@Overridepublic int getOrder() {return 0;} }

    測試:

    啟動user服務,繼續訪問其他微服務,會提示需要認證才能訪問,這個時候需要在heads中設置設置token才能正常訪問。

    9)前端集成

    9.1)前端項目部署思路

    通過nginx來進行配置,功能如下

    • 通過nginx的反向代理功能訪問后臺的網關資源

    • 通過nginx的靜態服務器功能訪問前端靜態頁面

    9.2)配置nginx

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

    路徑cmd,鍵入nginx,啟動

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

    ③:配置nginx.conf文件

    在nginx安裝的conf目錄下新建一個文件夾leadnews.conf,在當前文件夾中新建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; # 記錄真實發出請求的客戶端IPproxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; #記錄代理信息} }

    nginx.conf 把里面注釋的內容和靜態資源配置相關刪除引入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; }

    ④ :啟動nginx

    在nginx安裝包中使用命令提示符打開,輸入命令nginx啟動項目

    查看進程,檢查nginx是否啟動

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

    報錯

    使用windows版本的nginx啟動時遇到(1113: No mapping for the Unicode character exists in the target multi-byte code page)這個錯誤

    把nginx的版本升高了,依舊報錯
    后來查閱發現是因為解壓的路徑里面包含有中文的緣故,只要把解壓后的文件剪切到沒有包含中文的目錄即可解決問題

    在運行reload的時候報錯:CreateFile() "D:\DevelopCode\JavaCode\Springcloud\leadnews\nginx-1.18.0/logs/nginx.pid" failed (2: The system cannot find the file specified)

    加pid的文件,然后里面沒東西運行失敗,就start nginx,計算機自己在pid加了東西,成功reload!

    打開8801 報錯:500 Internal Server Error

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

    ⑤:打開前端項目進行測試 -- > http://localhost:8801

    用谷歌瀏覽器打開,調試移動端模式進行訪

    02app端文章查看,靜態化freemarker,分布式文件系統minIO

    1)文章列表加載

    1.1)需求分析

    文章布局展示

    1.2)表結構分析

    ap_article 文章基本信息表

    ap_article_config 文章配置表

    ap_article_content 文章內容表

    三張表關系分析

    表的拆分—垂直分表

    1.3)導入文章數據庫

    1.3.1)導入數據庫

    查看當天資料文件夾,在數據庫連接工具中執行leadnews_article.sql

    1.3.2)導入對應的實體類

    ap_article文章表對應實體

    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>* 文章信息表,存儲已發布的文章* </p>** @author itheima*/@Data @TableName("ap_article") public class ApArticle implements Serializable {@TableId(value = "id",type = IdType.ID_WORKER)private Long id;/*** 標題*/private String title;/*** 作者id*/@TableField("author_id")private Long authorId;/*** 作者名稱*/@TableField("author_name")private String authorName;/*** 頻道id*/@TableField("channel_id")private Integer channelId;/*** 頻道名稱*/@TableField("channel_name")private String channelName;/*** 文章布局 0 無圖文章 1 單圖文章 2 多圖文章*/private Short layout;/*** 文章標記 0 普通文章 1 熱點文章 2 置頂文章 3 精品文章 4 大V 文章*/private Byte flag;/*** 文章封面圖片 多張逗號分隔*/private String images;/*** 標簽*/private String labels;/*** 點贊數量*/private Integer likes;/*** 收藏數量*/private Integer collection;/*** 評論數量*/private Integer comment;/*** 閱讀數量*/private Integer views;/*** 省市*/@TableField("province_id")private Integer provinceId;/*** 市區*/@TableField("city_id")private Integer cityId;/*** 區縣*/@TableField("county_id")private Integer countyId;/*** 創建時間*/@TableField("created_time")private Date createdTime;/*** 發布時間*/@TableField("publish_time")private Date publishTime;/*** 同步狀態*/@TableField("sync_status")private Boolean syncStatus;/*** 來源*/private Boolean origin;/*** 靜態頁面地址*/@TableField("static_url")private String staticUrl; }

    ap_article_config文章配置對應實體類

    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已發布文章配置表* </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;/*** 是否轉發* true: 可以轉發 1* false: 不可轉發 0*/@TableField("is_forward")private Boolean isForward;/*** 是否下架* true: 下架 1* false: 沒有下架 0*/@TableField("is_down")private Boolean isDown;/*** 是否已刪除* true: 刪除 1* false: 沒有刪除 0*/@TableField("is_delete")private Boolean isDelete; }

    ap_article_content 文章內容對應的實體類

    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;/*** 文章內容*/private String content; }

    1.4)實現思路

    1,在默認頻道展示10條文章信息

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

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

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

    5,如果是當前頻道的首頁,前端傳遞默認參數

    • maxBehotTime:0(毫秒)

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

    1.5)接口定義

    加載首頁

    加載更多

    加載最新

    接口路徑

    /api/v1/article/load

    /api/v1/article/loadmore

    /api/v1/article/loadnew

    請求方式

    POST

    POST

    POST

    參數

    ArticleHomeDto

    ArticleHomeDto

    ArticleHomeDto

    響應結果

    ResponseResult

    ResponseResult

    ResponseResult

    ArticleHomeDto

    package com.heima.model.article.dtos;import lombok.Data; import java.util.Date;@Data public class ArticleHomeDto {// 最大時間Date maxBehotTime;// 最小時間Date minBehotTime;// 分頁sizeInteger size;// 頻道IDString tag; }

    1.6)功能實現

    1.6.1):導入heima-leadnews-article微服務,資料在當天的文件夾中

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

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

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

    需要在nacos中添加對應的配置

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

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

    對應的映射文件

    在resources中新建mapper/ApArticleMapper.xml 如下配置:

    <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.heima.article.mapper.ApArticleMapper"><resultMap id="resultMap" type="com.heima.model.article.pojos.ApArticle"><id column="id" property="id"/><result column="title" property="title"/><result column="author_id" property="authorId"/><result column="author_name" property="authorName"/><result column="channel_id" property="channelId"/><result column="channel_name" property="channelName"/><result column="layout" property="layout"/><result column="flag" property="flag"/><result column="images" property="images"/><result column="labels" property="labels"/><result column="likes" property="likes"/><result column="collection" property="collection"/><result column="comment" property="comment"/><result column="views" property="views"/><result column="province_id" property="provinceId"/><result column="city_id" property="cityId"/><result column="county_id" property="countyId"/><result column="created_time" property="createdTime"/><result column="publish_time" property="publishTime"/><result column="sync_status" property="syncStatus"/><result column="static_url" property="staticUrl"/></resultMap><select id="loadArticleList" resultMap="resultMap">SELECTaa.*FROM`ap_article` aaLEFT JOIN ap_article_config aac ON aa.id = aac.article_id<where>and aac.is_delete != 1and aac.is_down != 1<!-- loadmore --><if test="type != null and type == 1">and aa.publish_time <![CDATA[<]]> #{dto.minBehotTime}</if><if test="type != null and type == 2">and aa.publish_time <![CDATA[>]]> #{dto.maxBehotTime}</if><if test="dto.tag != '__all__'">and aa.channel_id = #{dto.tag}</if></where>order by aa.publish_time desclimit #{dto.size}</select></mapper>

    1.6.4):編寫業務層代碼

    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> {/*** 根據參數加載文章列表* @param loadtype 1為加載更多 2為加載最新* @param dto* @return*/ResponseResult load(Short loadtype, ArticleHomeDto dto);}

    實現類:

    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 {// 單頁最大加載的數字private final static short MAX_PAGE_SIZE = 50;@Autowiredprivate ApArticleMapper apArticleMapper;/*** 根據參數加載文章列表* @param loadtype 1為加載更多 2為加載最新* @param dto* @return*/@Overridepublic ResponseResult load(Short loadtype, ArticleHomeDto dto) {//1.校驗參數Integer size = dto.getSize();if(size == null || size == 0){size = 10;}size = Math.min(size,MAX_PAGE_SIZE);dto.setSize(size);//類型參數檢驗if(!loadtype.equals(ArticleConstants.LOADTYPE_LOAD_MORE)&&!loadtype.equals(ArticleConstants.LOADTYPE_LOAD_NEW)){loadtype = ArticleConstants.LOADTYPE_LOAD_MORE;}//文章頻道校驗if(StringUtils.isEmpty(dto.getTag())){dto.setTag(ArticleConstants.DEFAULT_TAG);}//時間校驗if(dto.getMaxBehotTime() == null) dto.setMaxBehotTime(new Date());if(dto.getMinBehotTime() == null) dto.setMinBehotTime(new Date());//2.查詢數據List<ApArticle> apArticles = apArticleMapper.loadArticleList(dto, loadtype);//3.結果封裝ResponseResult responseResult = ResponseResult.okResult(apArticles);return responseResult;}}

    定義常量類

    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 {@Autowiredprivate ApArticleService apArticleService;@PostMapping("/load")public ResponseResult load(@RequestBody ArticleHomeDto dto) {return apArticleService.load(ArticleConstants.LOADTYPE_LOAD_MORE,dto);}@PostMapping("/loadmore")public ResponseResult loadMore(@RequestBody ArticleHomeDto dto) {return apArticleService.load(ArticleConstants.LOADTYPE_LOAD_MORE,dto);}@PostMapping("/loadnew")public ResponseResult loadNew(@RequestBody ArticleHomeDto dto) {return apArticleService.load(ArticleConstants.LOADTYPE_LOAD_NEW,dto);} }

    1.6.6):swagger測試或前后端聯調測試

    第一:在app網關的微服務的nacos的配置中心添加文章微服務的路由,完整配置如下:

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

    第二:啟動nginx,直接使用前端項目測試,啟動文章微服務,用戶微服務、app網關微服務

    2)freemarker

    2.1) freemarker 介紹

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

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

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

    Jsp、Freemarker、Thymeleaf 、Velocity 等。

    1.Jsp 為 Servlet 專用,不能單獨進行使用。 已淘汰

    2.Thymeleaf 為新技術,功能較為強大,但是執行的效率比較低

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

    2.2) 環境搭建&&快速入門

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

    需要創建Spring Boot+Freemarker工程用于測試模板。

    2.2.1) 創建測試工程

    創建一個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 #服務端口 spring:application:name: freemarker-demo #指定服務名freemarker:cache: false #關閉模板緩存,方便測試settings:template_update_delay: 0 #檢查模板更新延遲時間,設置為0表示立即檢查,如果時間大于0會有緩存不方便進行模板測試suffix: .ftl #指定Freemarker模板文件的后綴名

    2.2.3) 創建模型類

    在freemarker的測試工程下創建模型類型用于測試

    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) 創建模板

    在resources下創建templates,此目錄為freemarker的默認模板存放目錄

    在templates下創建模板文件 01-basic.ftl ,模板中的插值表達式最終會被freemarker替換成具體的數據。

    <!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中的數據展示:</b><br/> 姓名:${stu.name}<br/> 年齡:${stu.age} <hr> </body> </html>

    2.2.5) 創建controller

    創建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.純文本形式的參數model.addAttribute("name", "freemarker");//2.實體類相關的參數Student student = new Student();student.setName("小明");student.setAge(18);model.addAttribute("stu", student);return "01-basic";} }

    01-basic.ftl,使用插值表達式填充數據

    <!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中的數據展示:</b><br/> 姓名:${stu.name}<br/> 年齡:${stu.age} <hr> </body> </html>

    2.2.6) 創建啟動類

    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

    2.3) freemarker基礎

    2.3.1) 基礎語法種類

    1、注釋,即<#-- -->,介于其之間的內容會被freemarker忽略
    <#--我是一個freemarker注釋-->
    2、插值(Interpolation):即 ${..} 部分,freemarker會用真實的值代替**${..}**
    Hello ${name}

    3、FTL指令:和HTML標記類似,名字前加#予以區分,Freemarker會解析標簽中的表達式或邏輯

    <# >FTL指令</#>

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

    <#--freemarker中的普通文本--> 我是一個普通的文本

    2.3.2) 集合指令(List和Map)

    1、數據模型:

    在HelloController中新增如下方法:

    @GetMapping("/list") public String list(Model model){//------------------------------------Student stu1 = new Student();stu1.setName("小強");stu1.setAge(18);stu1.setMoney(1000.86f);stu1.setBirthday(new Date());//小紅對象模型數據Student stu2 = new Student();stu2.setName("小紅");stu2.setMoney(200.1f);stu2.setAge(19);//將兩個對象模型數據存放到List集合中List<Student> stus = new ArrayList<>();stus.add(stu1);stus.add(stu2);//向model中存放List集合數據model.addAttribute("stus",stus);//------------------------------------//創建Map數據HashMap<String,Student> stuMap = new HashMap<>();stuMap.put("stu1",stu1);stuMap.put("stu2",stu2);// 3.1 向model中存放Map數據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 數據的展示 --> <b>展示list中的stu數據:</b> <br> <br> <table><tr><td>序號</td><td>姓名</td><td>年齡</td><td>錢包</td></tr> </table> <hr><#-- Map 數據的展示 --> <b>map數據的展示:</b> <br/><br/> <a href="###">方式一:通過map['keyname'].property</a><br/> 輸出stu1的學生信息:<br/> 姓名:<br/> 年齡:<br/> <br/> <a href="###">方式二:通過map.keyname.property</a><br/> 輸出stu2的學生信息:<br/> 姓名:<br/> 年齡:<br/><br/> <a href="###">遍歷map中兩個學生信息:</a><br/> <table><tr><td>序號</td><td>姓名</td><td>年齡</td><td>錢包</td> </tr> </table> <hr></body> </html>

    實例代碼:

    <!DOCTYPE html> <html> <head><meta charset="utf-8"><title>Hello World!</title> </head> <body><#-- list 數據的展示 --> <b>展示list中的stu數據:</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 數據的展示 --> <b>map數據的展示:</b> <br/><br/> <a href="###">方式一:通過map['keyname'].property</a><br/> 輸出stu1的學生信息:<br/> 姓名:${stuMap['stu1'].name}<br/> 年齡:${stuMap['stu1'].age}<br/> <br/> <a href="###">方式二:通過map.keyname.property</a><br/> 輸出stu2的學生信息:<br/> 姓名:${stuMap.stu2.name}<br/> 年齡:${stuMap.stu2.age}<br/><br/> <a href="###">遍歷map中兩個學生信息:</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:得到循環的下標,使用方法是在stu后邊加"_index",它的值是從0開始

    2.3.3) if指令

    if 指令即判斷指令,是常用的FTL指令,freemarker在解析時遇到if會進行判斷,條件為真則輸出if中間的內容,否則跳過內容不再輸出

    • 指令格式

    <#if ></if>

    1、數據模型:

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

    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>

    實例代碼:

    <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、輸出:

    姓名為“小強”則字體顏色顯示為紅色。

    2.3.4) 運算符

    1、算數運算符

    FreeMarker表達式中完全支持算術運算,FreeMarker支持的算術運算符包括:

    • 加法: +

    • 減法: -

    • 乘法: *

    • 除法: /

    • 求模 (求余): %

    模板代碼

    <b>算數運算符</b> <br/><br/>100+5 運算: ${100 + 5 }<br/>100 - 5 * 5運算:${100 - 5 * 5}<br/>5 / 2運算:${5 / 2}<br/>12 % 10運算:${12 % 10}<br/> <hr>

    除了 + 運算以外,其他的運算只能和 number 數字類型的計算

    2、比較運算符

    • =或者==:判斷兩個值是否相等.

    • !=:判斷兩個值是否不等.

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

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

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

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

    = 和 == 模板代碼

    <!DOCTYPE html> <html> <head><meta charset="utf-8"><title>Hello World!</title> </head> <body><b>比較運算符</b><br/><br/><dl><dt> =/== 和 != 比較:</dt><dd><#if "xiaoming" == "xiaoming">字符串的比較 "xiaoming" == "xiaoming"</#if></dd><dd><#if 10 != 100>數值的比較 10 != 100</#if></dd></dl><dl><dt>其他比較</dt><dd><#if 10 gt 5 >形式一:使用特殊字符比較數值 10 gt 5</#if></dd><dd><#-- 日期的比較需要通過?date將屬性轉為data類型才能進行比較 --><#if (date1?date >= date2?date)>形式二:使用括號形式比較時間 date1?date >= date2?date</#if></dd></dl><br/> <hr> </body> </html>

    Controller 的 數據模型代碼

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

    比較運算符注意

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

    • =!=兩邊必須是相同類型的值,否則會產生錯誤

    • 字符串 "x""x " 、"X"比較是不等的.因為FreeMarker是精確比較

    • 其它的運行符可以作用于數字和日期,但不能作用于字符串

    • 使用gt字母運算符代替>會有更好的效果,因為 FreeMarker會把> 解釋成FTL標簽的結束字符

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

    3、邏輯運算符

    • 邏輯與:&&

    • 邏輯或:||

    • 邏輯非:!

    邏輯運算符只能作用于布爾值,否則將產生錯誤 。

    模板代碼

    <b>邏輯運算符</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為空報錯可以加上判斷如下:

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

    2、缺失變量默認值使用 “!”

    • 使用!要以指定一個默認值,當變量為空時顯示默認值

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

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

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

    2.3.6) 內建函數

    內建函數語法格式: 變量+?+函數名稱

    1、和到某個集合的大小

    ${集合名?size}

    2、日期格式化

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

    顯示時分秒:${today?time}

    顯示日期+時間:${today?datetime}

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

    3、內建函數c

    model.addAttribute("point", 102920122);

    point是數字型,使用${point}會顯示這個數字的值,每三位使用逗號分隔。

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

    ${point?c}

    4、將json字符串轉成對象

    一個例子:

    其中用到了 assign標簽,assign的作用是定義一個變量

    <#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>顯示時分秒:<br>顯示日期+時間:<br>自定義格式化: <br><hr><b>內建函數C</b><br>沒有C函數顯示的數值: <br>有C函數顯示的數值:<hr><b>聲明變量assign</b><br><hr> </body> </html>

    內建函數模板頁面:

    <!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>顯示時分秒:${today?time}<br>顯示日期+時間:${today?datetime}<br>自定義格式化: ${today?string("yyyy年MM月")}<br><hr><b>內建函數C</b><br>沒有C函數顯示的數值:${point} <br>有C函數顯示的數值:${point?c}<hr><b>聲明變量assign</b><br><#assign text="{'bank':'工商銀行','account':'10101920201920212'}" /><#assign data=text?eval />開戶行:${data.bank} 賬號:${data.account}<hr> </body> </html>

    內建函數Controller數據模型:

    @GetMapping("innerFunc") public String testInnerFunc(Model model) {//1.1 小強對象模型數據Student stu1 = new Student();stu1.setName("小強");stu1.setAge(18);stu1.setMoney(1000.86f);stu1.setBirthday(new Date());//1.2 小紅對象模型數據Student stu2 = new Student();stu2.setName("小紅");stu2.setMoney(200.1f);stu2.setAge(19);//1.3 將兩個對象模型數據存放到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 添加數值model.addAttribute("point", 102920122);return "04-innerFunc"; }

    2.4) 靜態化測試

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

    2.4.1) 需求分析

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

    2.4.2) 靜態化測試

    根據模板文件生成html文件

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

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

    ②:在test下創建測試類

    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 {@Autowiredprivate Configuration configuration;@Testpublic void test() throws IOException, TemplateException {//freemarker的模板對象,獲取模板Template template = configuration.getTemplate("02-list.ftl");Map params = getData();//合成//第一個參數 數據模型//第二個參數 輸出流template.process(params, new FileWriter("d:/list.html"));}private Map getData() {Map<String, Object> map = new HashMap<>();//小強對象模型數據Student stu1 = new Student();stu1.setName("小強");stu1.setAge(18);stu1.setMoney(1000.86f);stu1.setBirthday(new Date());//小紅對象模型數據Student stu2 = new Student();stu2.setName("小紅");stu2.setMoney(200.1f);stu2.setAge(19);//將兩個對象模型數據存放到List集合中List<Student> stus = new ArrayList<>();stus.add(stu1);stus.add(stu2);//向map中存放List集合數據map.put("stus", stus);//創建Map數據HashMap<String, Student> stuMap = new HashMap<>();stuMap.put("stu1", stu1);stuMap.put("stu2", stu2);//向map中存放Map數據map.put("stuMap", stuMap);//返回Mapreturn map;} }

    3) 對象存儲服務MinIO

    3.1 MinIO簡介

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

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

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

    基本概念

    • bucket – 類比于文件系統的目錄

    • Object – 類比文件系統的文件

    • Keys – 類比文件名

    官網文檔:http://docs.minio.org.cn/docs/

    3.2 MinIO特點

    • 數據保護

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

    • 高性能

    作為高性能對象存儲,在標準硬件條件下它能達到55GB/s的讀、35GB/s的寫速率

    • 可擴容

    不同MinIO集群可以組成聯邦,并形成一個全局的命名空間,并跨越多個數據中心

    • SDK支持

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

    • 有操作頁面

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

    • 功能簡單

    這一設計原則讓MinIO不容易出錯、更快啟動

    • 豐富的API

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

    • 文件變化主動通知

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

    3.3 開箱使用

    3.3.1 安裝啟動

    我們提供的鏡像中已經有minio的環境; 我們可以使用docker進行環境部署和啟動

    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 管理控制臺

    假設我們的服務器地址為http://192.168.200.130:9000,我們在地址欄輸入:http://192.168.200.130:9000/ 即可進入登錄界面。

    Access Key為minio Secret_key 為minio123 進入系統后可以看到主界面

    點擊右下角的“+”號 ,點擊下面的圖標,創建一個桶

    3.4 快速入門

    3.4.1 創建工程,導入pom依賴

    創建minio-demo,對應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>

    引導類:

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

    創建測試類,上傳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.創建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創建的桶名稱 一致.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();}}}

    3.5 封裝MinIO為starter

    封裝為了其它微服務使用

    3.5.1 創建模塊heima-file-starter

    導入依賴

    <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}) //當引入FileStorageService接口時 @ConditionalOnClass(FileStorageService.class) public class MinIOConfig {@Autowiredprivate MinIOConfigProperties minIOConfigProperties;@Beanpublic 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 {@Autowiredprivate MinioClient minioClient;@Autowiredprivate 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 文件全路徑*/@Overridepublic 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 文件全路徑*/@Overridepublic 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 文件全路徑*/@Overridepublic 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);// 刪除ObjectsRemoveObjectArgs 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 文件流**/@Overridepublic 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 對外加入自動配置

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

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.heima.file.service.impl.MinIOFileStorageService

    3.5.5 其他微服務使用

    第一,導入heima-file-starter的依賴

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

    minio:accessKey: miniosecretKey: minio123bucket: leadnewsendpoint: http://192.168.200.130:9000readPath: http://192.168.200.130:9000

    第三,在對應使用的業務類中注入FileStorageService,樣例如下:

    package com.heima.minio.test;import java.io.FileInputStream; import java.io.FileNotFoundException;@SpringBootTest(classes = MinioApplication.class) @RunWith(SpringRunner.class) public class MinioTest {@Autowiredprivate FileStorageService fileStorageService;@Testpublic 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)需求分析

    4.2)實現方案

    方案一

    用戶某一條文章,根據文章的id去查詢文章內容表,返回渲染頁面

    方案二 效率高

    報錯:

    這個要在nacos里面配 minio;

    ?allowPublicKeyRetrieval=true 成功解決;

    4.3)實現步驟

    4.在文章微服務中導入依賴

    <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微服務中新增測試類(后期新增文章的時候創建詳情靜態頁,目前暫時手動生成)

    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 {@Autowiredprivate Configuration configuration;@Autowiredprivate FileStorageService fileStorageService;@Autowiredprivate ApArticleMapper apArticleMapper;@Autowiredprivate ApArticleContentMapper apArticleContentMapper;@Testpublic void createStaticUrlTest() throws Exception {//1.獲取文章內容ApArticleContent apArticleContent = apArticleContentMapper.selectOne(Wrappers.<ApArticleContent>lambdaQuery().eq(ApArticleContent::getArticleId, 1390536764510310401L));if(apArticleContent != null && StringUtils.isNotBlank(apArticleContent.getContent())){//2.文章內容通過freemarker生成html文件StringWriter out = new StringWriter();Template template = configuration.getTemplate("article.ftl");//第一個參數 數據模型Map<String, Object> params = new HashMap<>();//JSONArray.parseArray 字符串轉成對象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);}} }

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

    手機端適宜的屏幕大小

    03自媒體文章發布

    1)自媒體前后端搭建

    1.1)后臺搭建

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

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

    執行leadnews-wemedia.sql腳本

    添加對應的nacos配置

    spring:datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/leadnews_wemedia?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTCusername: rootpassword: root # 設置Mapper接口所對應的XML文件位置,如果你在Mapper接口中有自定義方法,需要進行該配置 mybatis-plus:mapper-locations: classpath*:mapper/*.xml# 設置別名包掃描路徑,通過該屬性可以給包中的類注冊別名type-aliases-package: com.heima.model.media.pojos

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

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

    添加對應的nacos配置

    spring:cloud:gateway:globalcors:cors-configurations:'[/**]': # 匹配所有請求allowedOrigins: "*" #跨域處理 允許所有的域allowedMethods: # 支持的方法- GET- POST- PUT- DELETEroutes:# 平臺管理- id: wemediauri: lb://leadnews-wemediapredicates:- Path=/wemedia/**filters:- StripPrefix= 1

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

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

    1.2)前臺搭建

    通過nginx的虛擬主機功能,使用同一個nginx訪問多個項目

    搭建步驟:

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

    ②:在nginx中leadnews.conf目錄中新增heima-leadnews-wemedia.conf文件

    • 網關地址修改(localhost:51602)

    • 前端項目目錄修改(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; # 記錄真實發出請求的客戶端IPproxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; #記錄代理信息} }

    ③:啟動nginx,啟動自媒體微服務和對應網關

    ④:聯調測試登錄功能 測試成功

    2)自媒體素材管理

    2.1)素材上傳

    2.2.1)需求分析

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

    2.2.2)素材管理-圖片上傳-表結構

    媒體圖文素材信息表wm_material

    對應實體類:

    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;/*** 創建時間*/@TableField("created_time")private Date createdTime;}
    2.2.3)實現思路

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

    ②:網關進行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();

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

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

    注意:需要從資料中找出WmUser實體類拷貝到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 {@Overridepublic 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設置用戶信息到threadlocal中...");}return true;}@Overridepublic 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 {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new WmTokenInterceptor()).addPathPatterns("/**");} }

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

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

    2.2.4)接口定義

    說明

    接口路徑

    /api/v1/material/upload_picture

    請求方式

    POST

    參數

    MultipartFile

    響應結果

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

    失敗:

    • 參數失效

    • 文章上傳失敗

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

    ①:導入heima-file-starter

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

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

    minio:accessKey: miniosecretKey: minio123bucket: leadnewsendpoint: http://192.168.200.130:9000readPath: http://192.168.200.130:9000
    2.2.6)具體實現

    ①:創建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> { }

    ③:業務層:

    package com.heima.wemedia.service;public interface WmMaterialService extends IService<WmMaterial> {/*** 圖片上傳* @param multipartFile* @return*/public ResponseResult uploadPicture(MultipartFile multipartFile); }

    業務層實現類:

    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 {@Autowiredprivate FileStorageService fileStorageService;/*** 圖片上傳* @param multipartFile* @return*/@Overridepublic ResponseResult uploadPicture(MultipartFile multipartFile) {//1.檢查參數if(multipartFile == null || multipartFile.getSize() == 0){return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);}//2.上傳圖片到minIO中String fileName = UUID.randomUUID().toString().replace("-", "");//aa.jpgString originalFilename = multipartFile.getOriginalFilename();String postfix = originalFilename.substring(originalFilename.lastIndexOf("."));String fileId = null;try {fileId = fileStorageService.uploadImgFile("", fileName + postfix, multipartFile.getInputStream());log.info("上傳圖片到MinIO中,fileId:{}",fileId);} catch (IOException e) {e.printStackTrace();log.error("WmMaterialServiceImpl-上傳文件失敗");}//3.保存到數據庫中WmMaterial wmMaterial = new WmMaterial();wmMaterial.setUserId(WmThreadLocalUtil.getUser().getId());wmMaterial.setUrl(fileId);wmMaterial.setIsCollection((short)0);wmMaterial.setType((short)0);wmMaterial.setCreatedTime(new Date());save(wmMaterial);//4.返回結果return ResponseResult.okResult(wmMaterial);} }

    ④:控制器

    @RestController @RequestMapping("/api/v1/material") public class WmMaterialController {@Autowiredprivate WmMaterialService wmMaterialService;@PostMapping("/upload_picture")public ResponseResult uploadPicture(MultipartFile multipartFile){return wmMaterialService.uploadPicture(multipartFile);} }

    ⑤:測試

    啟動自媒體微服務和自媒體網關,使用前端項目進行測試

    2.2)素材列表查詢

    2.2.1)接口定義

    說明

    接口路徑

    /api/v1/material/list

    請求方式

    POST

    參數

    WmMaterialDto

    響應結果

    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)功能實現

    ①:在WmMaterialController類中新增方法

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

    ②:mapper已定義

    ③:業務層

    在WmMaterialService中新增方法

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

    實現方法:

    /*** 素材列表查詢* @param dto* @return*/ @Override public ResponseResult findList(WmMaterialDto dto) {//1.檢查參數dto.checkParam();//2.分頁查詢IPage page = new Page(dto.getPage(),dto.getSize());LambdaQueryWrapper<WmMaterial> lambdaQueryWrapper = new LambdaQueryWrapper<>();//是否收藏if(dto.getIsCollection() != null && dto.getIsCollection() == 1){lambdaQueryWrapper.eq(WmMaterial::getIsCollection,dto.getIsCollection());}//按照用戶查詢lambdaQueryWrapper.eq(WmMaterial::getUserId,WmThreadLocalUtil.getUser().getId());//按照時間倒序lambdaQueryWrapper.orderByDesc(WmMaterial::getCreatedTime); page = page(page,lambdaQueryWrapper);//3.結果返回ResponseResult responseResult = new PageResponseResult(dto.getPage(),dto.getSize(),(int)page.getTotal());responseResult.setData(page.getRecords());return responseResult; }

    ④:控制器:

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

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

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

    3)自媒體文章管理

    3.1)查詢所有頻道

    3.1.1)需求分析
    3.1.2)表結構

    wm_channel 頻道信息表

    對應實體類:

    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;/*** 是否默認頻道* 1:默認 true* 0:非默認 false*/@TableField("is_default")private Boolean isDefault;/*** 是否啟用* 1:啟用 true* 0:禁用 false*/@TableField("status")private Boolean status;/*** 默認排序*/@TableField("ord")private Integer ord;/*** 創建時間*/@TableField("created_time")private Date createdTime;}
    3.1.3)接口定義

    說明

    接口路徑

    /api/v1/channel/channels

    請求方式

    POST

    參數

    響應結果

    ResponseResult

    ResponseResult :

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

    接口定義:

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

    實現類

    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*/@Overridepublic 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 {@Autowiredprivate WmChannelService wmChannelService;@GetMapping("/channels")public ResponseResult findAll(){return wmChannelService.findAll();} }
    3.1.5)測試

    3.2)查詢自媒體文章

    3.2.1)需求說明
    3.2.2)表結構分析

    wm_news 自媒體文章表

    對應實體類:

    package com.heima.model.wemedia.pojos;import java.io.Serializable; import java.util.Date;/*** <p>* 自媒體圖文內容信息表* </p>** @author itheima*/ @Data @TableName("wm_news") public class WmNews implements Serializable {private static final long serialVersionUID = 1L;/*** 主鍵*/@TableId(value = "id", type = IdType.AUTO)private Integer id;/*** 自媒體用戶ID*/@TableField("user_id")private Integer userId;/*** 標題*/@TableField("title")private String title;/*** 圖文內容*/@TableField("content")private String content;/*** 文章布局0 無圖文章1 單圖文章3 多圖文章*/@TableField("type")private Short type;/*** 圖文頻道ID*/@TableField("channel_id")private Integer channelId;@TableField("labels")private String labels;/*** 創建時間*/@TableField("created_time")private Date createdTime;/*** 提交時間*/@TableField("submited_time")private Date submitedTime;/*** 當前狀態0 草稿1 提交(待審核)2 審核失敗3 人工審核4 人工審核通過8 審核通過(待發布)9 已發布*/@TableField("status")private Short status;/*** 定時發布時間,不定時則為空*/@TableField("publish_time")private Date publishTime;/*** 拒絕理由*/@TableField("reason")private String reason;/*** 發布庫文章ID*/@TableField("article_id")private Long articleId;/*** //圖片用逗號分隔*/@TableField("images")private String images;@TableField("enable")private Short enable;//狀態枚舉類@Alias("WmNewsStatus")public enum Status{NORMAL((short)0),SUBMIT((short)1),FAIL((short)2),ADMIN_AUTH((short)3),ADMIN_SUCCESS((short)4),SUCCESS((short)8),PUBLISHED((short)9);short code;Status(short code){this.code = code;}public short getCode(){return this.code;}}}
    3.2.3)接口定義

    說明

    接口路徑

    /api/v1/news/list

    請求方式

    POST

    參數

    WmNewsPageReqDto

    響應結果

    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 {/*** 狀態*/private Short status;/*** 開始時間*/private Date beginPubDate;/*** 結束時間*/private Date endPubDate;/*** 所屬頻道ID*/private Integer channelId;/*** 關鍵字*/private String keyword; }

    ResponseResult :

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

    ①:新增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);}

    實現類:

    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*/@Overridepublic ResponseResult findAll(WmNewsPageReqDto dto) {//1.檢查參數if(dto == null){return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);}//分頁參數檢查dto.checkParam();//獲取當前登錄人的信息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<>();//狀態精確查詢if(dto.getStatus() != null){lambdaQueryWrapper.eq(WmNews::getStatus,dto.getStatus());}//頻道精確查詢if(dto.getChannelId() != null){lambdaQueryWrapper.eq(WmNews::getChannelId,dto.getChannelId());}//時間范圍查詢if(dto.getBeginPubDate()!=null && dto.getEndPubDate()!=null){lambdaQueryWrapper.between(WmNews::getPublishTime,dto.getBeginPubDate(),dto.getEndPubDate());}//關鍵字模糊查詢if(StringUtils.isNotBlank(dto.getKeyword())){lambdaQueryWrapper.like(WmNews::getTitle,dto.getKeyword());}//查詢當前登錄用戶的文章lambdaQueryWrapper.eq(WmNews::getUserId,user.getId());//發布時間倒序查詢lambdaQueryWrapper.orderByDesc(WmNews::getCreatedTime);page = page(page,lambdaQueryWrapper);//3.結果返回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 {@Autowiredprivate WmNewsService wmNewsService;@PostMapping("/list")public ResponseResult findAll(@RequestBody WmNewsPageReqDto dto){return wmNewsService.findAll(dto);}}
    3.2.5)測試

    啟動后端自媒體微服務和自媒體網關微服務,測試文章列表查詢

    3.3)文章發布

    3.3.1)需求分析
    3.3.2)表結構分析

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

    保存了這個關系表,就可以保存素材,素材有被引用的標記,就不能被修改或刪除

    其中wm_material和wm_news表的實體類已經導入到了項目中,下面是wm_news_material表對應的實體類:

    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 內容引用1 主圖引用*/@TableField("type")private Short type;/*** 引用排序*/@TableField("ord")private Short ord;}
    3.3.3)實現思路分析

    1.前端提交發布或保存為草稿

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

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

    3.1 執行新增文章的操作

    3.2 關聯文章內容圖片與素材的關系

    3.3 關聯文章封面圖片與素材的關系

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

    4.1 刪除該文章與素材的所有關系

    4.2 執行修改操作

    4.3 關聯文章內容圖片與素材的關系

    4.4 關聯文章封面圖片與素材的關系

    3.3.4)接口定義

    說明

    接口路徑

    /api/v1/channel/submit

    請求方式

    POST

    參數

    WmNewsDto

    響應結果

    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;/*** 標題*/private String title;/*** 頻道id*/private Integer channelId;/*** 標簽*/private String labels;/*** 發布時間*/private Date publishTime;/*** 文章內容*/private String content;/*** 文章封面類型 0 無圖 1 單圖 3 多圖 -1 自動*/private Short type;/*** 提交時間*/private Date submitedTime; /*** 狀態 提交為1 草稿為0*/private Short status;/*** 封面圖片列表 多張圖以逗號隔開*/private List<String> images; }

    前端給傳遞過來的json數據格式為:

    {"title":"黑馬頭條項目背景","type":"1",//這個 0 是無圖 1 是單圖 3 是多圖 -1 是自動"labels":"黑馬頭條","publishTime":"2020-03-14T11:35:49.000Z","channelId":1,"images":["http://192.168.200.130/group1/M00/00/00/wKjIgl5swbGATaSAAAEPfZfx6Iw790.png"],"status":1,"content":"[{"type":"text","value":"隨著智能手機的普及,人們更加習慣于通過手機來看新聞。由于生活節奏的加快,很多人只能利用碎片時間來獲取信息,因此,對于移動資訊客戶端的需求也越來越高。黑馬頭條項目正是在這樣背景下開發出來。黑馬頭條項目采用當下火熱的微服務+大數據技術架構實現。本項目主要著手于獲取最新最熱新聞資訊,通過大數據分析用戶喜好精確推送咨詢新聞"},{"type":"image","value":"http://192.168.200.130/group1/M00/00/00/wKjIgl5swbGATaSAAAEPfZfx6Iw790.png"} ]" }

    ResponseResult:

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

    ①:在新增WmNewsController中新增方法

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

    ②:新增WmNewsMaterialMapper類,文章與素材的關聯關系需要批量保存,索引需要定義mapper文件和對應的映射文件

    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>

    ③:常量類準備

    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中新增方法

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

    實現方法:

    /*** 發布修改文章或保存為草稿* @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---> stringif(dto.getImages() != null && dto.getImages().size() > 0){//[1dddfsd.jpg,sdlfjldk.jpg]--> 1dddfsd.jpg,sdlfjldk.jpgString imageStr = StringUtils.join(dto.getImages(), ",");wmNews.setImages(imageStr);}//如果當前封面類型為自動 -1if(dto.getType().equals(WemediaConstants.WM_NEWS_TYPE_AUTO)){wmNews.setType(null);}saveOrUpdateWmNews(wmNews);//2.判斷是否為草稿 如果為草稿結束當前方法if(dto.getStatus().equals(WmNews.Status.NORMAL.getCode())){return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);}//3.不是草稿,保存文章內容圖片與素材的關系//獲取到文章內容中的圖片信息List<String> materials = ectractUrlInfo(dto.getContent());saveRelativeInfoForContent(materials,wmNews.getId());//4.不是草稿,保存文章封面圖片與素材的關系,如果當前布局是自動,需要匹配封面圖片saveRelativeInfoForCover(dto,wmNews,materials);return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);}/*** 第一個功能:如果當前封面類型為自動,則設置封面類型的數據* 匹配規則:* 1,如果內容圖片大于等于1,小于3 單圖 type 1* 2,如果內容圖片大于等于3 多圖 type 3* 3,如果內容沒有圖片,無圖 type 0** 第二個功能:保存封面圖片與素材的關系* @param dto* @param wmNews* @param materials*/ private void saveRelativeInfoForCover(WmNewsDto dto, WmNews wmNews, List<String> materials) {List<String> images = dto.getImages();//如果當前封面類型為自動,則設置封面類型的數據if(dto.getType().equals(WemediaConstants.WM_NEWS_TYPE_AUTO)){//多圖if(materials.size() >= 3){wmNews.setType(WemediaConstants.WM_NEWS_MANY_IMAGE);images = materials.stream().limit(3).collect(Collectors.toList());}else if(materials.size() >= 1 && materials.size() < 3){//單圖wmNews.setType(WemediaConstants.WM_NEWS_SINGLE_IMAGE);images = materials.stream().limit(1).collect(Collectors.toList());}else {//無圖wmNews.setType(WemediaConstants.WM_NEWS_NONE_IMAGE);}//修改文章if(images != null && images.size() > 0){wmNews.setImages(StringUtils.join(images,","));}updateById(wmNews);}if(images != null && images.size() > 0){saveRelativeInfo(images,wmNews.getId(),WemediaConstants.WM_COVER_REFERENCE);}}/*** 處理文章內容圖片與素材的關系* @param materials* @param newsId*/ private void saveRelativeInfoForContent(List<String> materials, Integer newsId) {saveRelativeInfo(materials,newsId,WemediaConstants.WM_CONTENT_REFERENCE); }@Autowired private WmMaterialMapper wmMaterialMapper;/*** 保存文章圖片與素材的關系到數據庫中* @param materials* @param newsId* @param type*/ private void saveRelativeInfo(List<String> materials, Integer newsId, Short type) {if(materials!=null && !materials.isEmpty()){//通過圖片的url查詢素材的idList<WmMaterial> dbMaterials = wmMaterialMapper.selectList(Wrappers.<WmMaterial>lambdaQuery().in(WmMaterial::getUrl, materials));//判斷素材是否有效if(dbMaterials==null || dbMaterials.size() == 0){//手動拋出異常 第一個功能:能夠提示調用者素材失效了,第二個功能,進行數據的回滾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);}}/*** 提取文章內容中的圖片信息* @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) {//補全屬性wmNews.setUserId(WmThreadLocalUtil.getUser().getId());wmNews.setCreatedTime(new Date());wmNews.setSubmitedTime(new Date());wmNews.setEnable((short)1);//默認上架if(wmNews.getId() == null){//保存save(wmNews);}else {//修改//刪除文章圖片與素材的關系wmNewsMaterialMapper.delete(Wrappers.<WmNewsMaterial>lambdaQuery().eq(WmNewsMaterial::getNewsId,wmNews.getId()));updateById(wmNews);}}

    ④:控制器

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

    總結

    以上是生活随笔為你收集整理的《黑马头条》SpringBoot+SpringCloud+ Nacos等企业级微服务架构项目的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。