日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

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

javascript

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

發布時間:2024/1/18 javascript 92 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《黑马头条》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等企业级微服务架构项目的全部內容,希望文章能夠幫你解決所遇到的問題。

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

    国产98色在线 | 日韩 | 国产精品久久久久一区二区三区 | 中文字幕一区二区三区久久蜜桃 | 国产主播大尺度精品福利免费 | 久久96国产精品久久99漫画 | 欧美一级专区免费大片 | 国产美女搞久久 | 成人黄色电影在线播放 | 国产成年人av | 亚洲影院一区 | 久久激五月天综合精品 | 久久爱影视i | 国产亚洲精品美女久久 | 99视频在线观看免费 | 婷婷丁香狠狠爱 | 成人久久综合 | 欧美午夜激情网 | 久久在线视频在线 | 人人爽人人爽人人爽人人爽 | 99re久久精品国产 | 久久草草热国产精品直播 | 97超碰在 | 免费十分钟 | 久久久国内精品 | 亚洲欧美成人网 | 国产精品免费在线播放 | 久久久久久久久久久免费av | 日本精品久久久一区二区三区 | 国产精品麻 | 亚洲视频在线视频 | 久久综合影视 | 丁香六月网 | 2019中文字幕网站 | 91九色在线视频 | 久操操 | 人人精品久久 | 99精品在线看 | 久久国产视频网站 | 九九热99视频 | 国产日韩欧美在线看 | a电影免费看| 天天操天天干天天插 | 国产亚洲精品免费 | 在线视频欧美精品 | 欧美91av | 欧美性猛片, | 亚洲伦理一区二区 | 国产黄色片久久久 | 成人免费视频网站 | 97夜夜澡人人爽人人免费 | 中文字幕丝袜制服 | 亚洲一区二区视频在线播放 | 国产精品xxxx18a99 | av九九九| 狠狠狠狠狠狠干 | 人人草在线视频 | 国产传媒中文字幕 | 欧美日韩高清一区 | 欧美精品天堂 | 911精品美国片911久久久 | 国产精品初高中精品久久 | 国产夫妻av在线 | 亚洲v欧美v国产v在线观看 | 国产视频中文字幕在线观看 | 亚洲高清视频在线观看 | 国产精品一区二区三区免费看 | 成人av片免费观看app下载 | 中文字幕在线视频一区 | 五月天堂网 | 亚洲女人天堂成人av在线 | 免费视频二区 | 九七视频在线观看 | 精品国产伦一区二区三区观看体验 | 国产最新在线视频 | 国际精品久久 | 最近中文字幕免费 | 日韩videos | 国产在线免费av | 午夜在线观看一区 | 91av在线视频免费观看 | 成人久久精品视频 | 欧美精品日韩 | 手机av片 | 激情视频亚洲 | 欧美另类性 | 国产成人精品一二三区 | 天天操天天色天天 | 一级黄色片在线 | 97韩国电影 | 免费成人在线网站 | 国产精品久久久久永久免费观看 | 婷婷激情久久 | 国产亚洲综合性久久久影院 | 国产最新视频在线观看 | 夜夜爽88888免费视频4848 | 欧美日韩视频在线观看免费 | 久久精品视频4 | 婷婷久久一区二区三区 | 91九色成人蝌蚪首页 | 天天做夜夜做 | 黄色a大片 | 成人久久综合 | 激情网婷婷 | 亚洲一二三久久 | 中文字幕av在线免费 | 人人干人人添 | 国产精品久久久久影视 | 国产亚洲精品bv在线观看 | 天天操天天干天天操天天干 | 久草在线视频网 | 深夜视频久久 | av三区在线 | av超碰免费在线 | 欧美狠狠操 | 久久久久免费视频 | 欧美日韩中文在线观看 | 久久1区 | 天天操天天干天天玩 | 久久一区二区三区日韩 | 欧美日韩在线观看一区 | 干综合网 | 久久呀 | 久草在线资源视频 | 91在线免费播放 | 天天操天天干天天综合网 | 国产一级片一区二区三区 | 天天操人人要 | 激情丁香月 | 精品在线观看视频 | 不卡视频国产 | 天天综合导航 | 97av.com| 亚洲最大av网 | 色狠狠综合天天综合综合 | 婷婷 中文字幕 | 9久久精品 | 亚洲欧美视频一区二区三区 | 天堂av网址 | 91在线视频在线 | 欧美一区二区三区特黄 | 久久草网站 | 国产精品黑丝在线观看 | 夜夜嗨av色一区二区不卡 | 草久中文字幕 | 久久99免费| av电影在线观看 | 国产97视频在线 | 免费a v观看 | 精品视频久久 | 99视频偷窥在线精品国自产拍 | 91精品国产92久久久久 | a在线观看国产 | 开心婷婷色 | 欧美成人精品三级在线观看播放 | 久草电影在线观看 | 91免费观看国产 | 日韩精品免费一线在线观看 | 久久色网站 | 国产无套一区二区三区久久 | 九九视频免费 | 国产精品成人一区二区 | 国产精品第二十页 | 国产中的精品av小宝探花 | 日韩免费成人av | 丁香婷婷综合激情五月色 | 免费av视屏 | 天天色综合久久 | 亚洲午夜久久久综合37日本 | 日本公妇在线观看高清 | 国产网站av | 欧美福利片在线观看 | 天天色.com| 国产69精品久久久久久久久久 | 亚洲国产中文字幕在线观看 | 日产乱码一二三区别免费 | 99色国产 | www.神马久久 | 超碰激情在线 | 日韩高清在线一区 | 在线小视频 | 中文字幕 国产 一区 | 伊人影院得得 | 激情欧美丁香 | 精品在线视频播放 | 中文字幕中文字幕在线中文字幕三区 | 91久久人澡人人添人人爽欧美 | 国产亚洲午夜高清国产拍精品 | 国产 一区二区三区 在线 | 国产美女视频黄a视频免费 久久综合九色欧美综合狠狠 | 久久久五月天 | 久草精品视频 | 日韩精品欧美一区 | 中文伊人 | 精品久久一区二区 | 在线欧美中文字幕 | 97品白浆高清久久久久久 | 五月婷婷毛片 | 亚洲精品国产成人av在线 | 国产一级在线看 | 国产高清视频在线播放 | 国产伦理久久精品久久久久_ | 亚洲一级电影 | 二区三区在线视频 | 亚洲国产成人在线 | 男女激情片在线观看 | 美女视频国产 | 午夜精品99久久免费 | 国产精品永久在线观看 | 人人射人人插 | 色婷婷一 | 手机看片 | 国产视频一区二区三区在线 | 亚洲综合在线一区二区三区 | 在线观看亚洲视频 | 国产精品 美女 | 国产免码va在线观看免费 | 91热精品 | 在线亚洲天堂网 | 日韩在线首页 | 免费日韩高清 | 青青河边草免费观看完整版高清 | 一区二区三区在线视频观看58 | 69性欧美| 午夜久久久久久久久 | 国产精品18久久久久vr手机版特色 | 欧美性性网| 国产精品18久久久久久不卡孕妇 | 国产成人精品一区二区三区在线 | 日韩专区一区二区 | 日韩久久精品一区二区 | 亚洲一区二区观看 | 午夜免费电影院 | 久久久久久久久久久高潮一区二区 | 国产乱老熟视频网88av | 日韩av一卡二卡三卡 | 免费av在线播放 | www黄在线| 日本婷婷色 | 99视频黄| 又黄又色又爽 | 天天爱天天操天天射 | 亚洲国产精品久久久久 | 国产黄av| 91精品视频免费看 | 青青草国产成人99久久 | 在线一二三四区 | 日韩专区av| 中文字幕色在线视频 | 日日麻批40分钟视频免费观看 | 精品国产一区二区三区不卡 | 精品视频成人 | 久久国产精品色av免费看 | 日韩久久电影 | 日韩亚洲国产精品 | 激情综合一区 | 亚洲国产资源 | 国产乱老熟视频网88av | 天天天射 | 成人国产一区二区 | 免费一级片久久 | 日韩精品一区二区三区免费视频观看 | 国产高清不卡一区二区三区 | 日韩av片在线 | 一区二区电影在线观看 | 亚洲黄色av| www.天天射.com | 欧美精品久久久久久久久久 | 欧美日韩精品综合 | 欧亚日韩精品一区二区在线 | 国产成a人亚洲精v品在线观看 | 黄色软件在线观看 | 夜色在线资源 | 国产精品一区专区欧美日韩 | 午夜精品一区二区三区在线视频 | 欧洲精品亚洲精品 | 91在线公开视频 | 国产精品aⅴ | 狠狠操狠狠插 | 亚洲免费高清视频 | 亚洲国产一二三 | 国产1级视频 | 99久久婷婷国产综合精品 | 免费一级片观看 | 久久综合婷婷 | 99re久久资源最新地址 | 丁香婷婷色| 97国产情侣爱久久免费观看 | 日韩免费大片 | av一区二区三区在线 | 狠狠狠操 | 美国三级黄色大片 | 色播激情五月 | 久草视频播放 | 亚洲视频观看 | 综合久色| 九九热精品视频在线观看 | 极品美女被弄高潮视频网站 | 最近免费中文字幕大全高清10 | 婷婷五综合| 丁香九月婷婷 | 久久免费视频在线观看30 | 91福利视频免费 | 91精品蜜桃 | 六月激情婷婷 | 中文字幕久久亚洲 | 青春草视频在线播放 | 久久精品91久久久久久再现 | 综合五月 | 国产精品剧情在线亚洲 | 2022久久国产露脸精品国产 | 成人一级片视频 | 青青啪 | 亚洲在线日韩 | 亚洲婷婷综合色高清在线 | 久久激情五月丁香伊人 | 欧洲一区二区在线观看 | 亚洲精品国偷拍自产在线观看蜜桃 | 久久国产高清 | 成人av教育| 成人免费观看网址 | 成 人 免费 黄 色 视频 | 在线精品亚洲一区二区 | 青春草视频 | 国产精品久久久久久久午夜片 | 99爱在线观看| 手机av资源 | 中文字幕欧美日韩va免费视频 | 日韩欧美亚州 | 日韩精品一区二区三区丰满 | 国产成人精品av | 久久综合狠狠综合 | 精品国偷自产在线 | 97电院网手机版 | 精品国产乱码久久久久 | 久草在线91 | 国产美女免费观看 | 亚洲美女精品 | 亚洲精品小视频在线观看 | 亚洲精品美女久久17c | 国产一级免费电影 | 人人干狠狠操 | 欧美高清成人 | 国产一区在线视频 | 免费看一及片 | 韩国视频一区二区三区 | 成人超碰在线 | 国产91免费观看 | 婷婷日日| 久久99九九99精品 | 狠狠五月天 | 中国一 片免费观看 | 天天综合网天天 | 五月天丁香视频 | 免费成人av在线看 | 日韩av专区| 欧美亚洲一级片 | 亚洲综合网站在线观看 | 2020天天干天天操 | 中文字幕欧美激情 | www.天天操.com| 欧美日韩精品免费观看视频 | 国产午夜精品一区二区三区欧美 | 偷拍视频一区 | 天天干天天拍天天操 | 国产成人免费观看久久久 | 在线看片91 | 91成人免费在线视频 | 五月天色中色 | 黄色资源在线观看 | 午夜av网站| 青春草视频 | 国产精品一区二区在线免费观看 | av电影在线不卡 | 精品久久久成人 | 狠狠色丁香婷综合久久 | 国产精品日韩欧美 | 久久久久亚洲精品男人的天堂 | av黄色国产 | 成人丝袜 | 操高跟美女 | 国产淫片| 日韩精品久久久免费观看夜色 | 在线草 | 波多野结衣一区三区 | 96精品视频 | 少妇18xxxx性xxxx片 | 成人一区电影 | 国内精品福利视频 | 精品一区电影国产 | 色综合久久久久久久 | 国产精品伦一区二区三区视频 | 精品成人网 | 免费视频一二三区 | 亚洲精品午夜久久久 | 免费日韩电影 | 一级片视频在线 | 欧美一级在线观看视频 | 黄色在线成人 | 国产五月色婷婷六月丁香视频 | a级国产片| 久久99精品久久只有精品 | 粉嫩av一区二区三区免费 | wwwww.国产| 日韩欧美视频二区 | 在线观看日韩精品 | 一区二区丝袜 | 午夜精品久久久久久久99水蜜桃 | 91九色视频网站 | 免费亚洲一区二区 | 欧美激情视频免费看 | 国产黄色免费看 | 国产精品久久久久久久妇 | 欧美日韩国产综合一区二区 | 99热在线精品观看 | 欧美日韩二区在线 | 成全免费观看视频 | 精品一区二区久久久久久久网站 | 色吊丝在线永久观看最新版本 | av手机在线播放 | 婷婷丁香色 | 国产xxxx做受性欧美88 | 青草视频在线播放 | 国产精品96久久久久久吹潮 | 免费高清在线观看成人 | 日韩免费三级 | www.超碰97.com| 香蕉视频在线免费看 | 韩国av一区二区 | 中文字幕一区二区三区精华液 | 日韩欧美高清在线 | 国内揄拍国内精品 | 91最新地址永久入口 | 狠狠操精品 | 激情综合国产 | 国产精品毛片久久蜜 | www免费在线观看 | 综合色婷婷 | 免费在线观看一区二区三区 | 日韩最新理论电影 | 成 人 免费 黄 色 视频 | 97国产在线 | 天天插天天狠天天透 | 99国产在线观看 | 国产精品欧美久久久久无广告 | 青青河边草免费直播 | 成人在线播放免费观看 | 成人免费观看av | 天天操天天干天天综合网 | 极品久久久 | 久久96国产精品久久99漫画 | 精品一区 在线 | 久久不卡视频 | 波多野结衣亚洲一区二区 | 中文字幕2021 | 黄色小网站在线 | 成年人视频在线观看免费 | 久久精品欧美一区二区三区麻豆 | 成人h动漫在线看 | 亚洲激情小视频 | 免费看黄在线观看 | 豆豆色资源网xfplay | 久草在线视频国产 | 精品日韩av | 免费久久久 | 国产精品久久久久毛片大屁完整版 | 在线免费观看黄色小说 | 91国内在线 | 97理论片 | 国产小视频国产精品 | 国产色婷婷在线 | 日韩艹| 欧美一级免费在线 | 手机在线看片日韩 | 欧美综合在线视频 | 97超碰资源总站 | 日韩高清二区 | 激情综合五月 | 中文在线资源 | 国产婷婷视频在线 | 日韩va在线观看 | 精品国自产在线观看 | 亚洲码国产日韩欧美高潮在线播放 | 97人人看 | 久久亚洲私人国产精品 | 国产视频1区2区 | 国产精品久久麻豆 | 在线激情av电影 | 国产精品成人久久久久 | 91免费高清 | 在线91播放| 国产成人精品亚洲日本在线观看 | 欧美另类xxxxx | 国产在线精品福利 | av在线影片 | 91片网 | 国产又粗又猛又黄又爽的视频 | 天天操天天操天天操天天操 | av电影免费在线 | 夜夜爽夜夜操 | 激情五月av| 久久婷婷精品 | 国产一级h | 久草干| 国产男女无遮挡猛进猛出在线观看 | 久久久婷 | av黄色大片 | 久久99网 | 玖玖在线资源 | 99久久激情 | 亚洲精品自拍 | 麻豆国产精品永久免费视频 | 国产福利一区二区三区视频 | 丁香婷婷综合激情 | 国产一区欧美在线 | 国产亚洲欧美日韩高清 | 欧美一二三区播放 | 97人人模人人爽人人喊网 | 成人动漫精品一区二区 | 欧洲精品一区二区 | 激情久久伊人 | 麻豆一精品传二传媒短视频 | 亚洲第一伊人 | 天天曰天天干 | 日韩网站在线看片你懂的 | 天天操夜夜曰 | 深夜免费福利 | 久久久久中文 | 欧美日韩精品影院 | 亚洲91网站| 国产一区二区中文字幕 | 一级黄色片在线免费观看 | 精品一区欧美 | 四虎亚洲精品 | 综合视频在线 | 99精品99 | 亚洲天天看 | 黄色电影小说 | 91久久人澡人人添人人爽欧美 | 中国精品少妇 | 国产精品1区 | 五月天网站在线 | 亚洲在线免费视频 | 九九视频这里只有精品 | 久草剧场| 午夜久久电影网 | 麻豆久久一区二区 | 天天色天天操天天爽 | 精品在线观看一区二区 | 国产黄网站在线观看 | www.久久久久| 国产一区二区三区免费视频 | 午夜精品久久久久久久99无限制 | 国产亚洲精品久久久久秋 | 天堂v中文 | 亚洲欧美日本一区二区三区 | 国产精品久久嫩一区二区免费 | 亚洲另类在线视频 | 久久综合影院 | 91色偷偷 | 亚洲va综合va国产va中文 | 久久深夜福利免费观看 | 国产视频在线播放 | 婷婷激情av | 91天堂在线观看 | 中文字幕免费高 | 国产精品久久久久久久久久东京 | 免费视频一二三区 | 久久久亚洲精华液 | 国产免费久久精品 | 日韩电影中文字幕在线观看 | 四虎影视av | 天天干天天射天天爽 | 成在线播放 | 免费进去里的视频 | 国产色网站 | 精品亚洲在线 | 久久激情婷婷 | 天天搞天天 | 国产一区二区在线播放 | 又黄又刺激视频 | 99色婷婷 | 91九色在线观看视频 | 欧美在线99 | 久久成人高清 | 日本中文字幕免费观看 | 在线观看av中文字幕 | 日韩aa视频 | 精品国产乱码久久久久久三级人 | 二区三区在线 | 久久伊人精品天天 | 四虎在线影视 | www.狠狠插.com | 亚州精品在线视频 | 蜜臀aⅴ精品一区二区三区 久久视屏网 | 亚洲精品一区二区久 | 成人精品在线 | 亚洲黄色网络 | 99亚洲精品在线 | 亚洲综合欧美激情 | 国产69精品久久99不卡的观看体验 | 97综合在线 | 夜夜澡人模人人添人人看 | 91伊人| 丁香六月婷婷开心 | 久久精品免费观看 | 国产色一区 | 国产成人久久精品一区二区三区 | 韩国av一区二区三区在线观看 | 韩国av一区二区三区在线观看 | 亚洲精品视频大全 | 91字幕| av久久久| 中日韩欧美精彩视频 | 精品国产三级 | 久久不卡国产精品一区二区 | 99精品视频在线免费观看 | 亚洲一级片在线观看 | 久久综合综合久久综合 | 一级免费观看 | 国产婷婷vvvv激情久 | 人人要人人澡人人爽人人dvd | 欧美一区二区三区在线观看 | 美女视频黄在线 | 久久久久麻豆 | 玖玖在线免费视频 | 国产精品美乳一区二区免费 | 97国产在线观看 | 久久久国产精品一区二区中文 | 伊人婷婷久久 | 91九色国产蝌蚪 | 欧美日韩午夜爽爽 | 天天射天天干天天插 | 黄色一级大片免费看 | 欧美日韩69| 五月天综合色 | 日本午夜在线观看 | 精品国产视频在线 | 国产va饥渴难耐女保洁员在线观看 | 久久字幕| a成人v在线 | 性色av一区二区 | 日本中文字幕观看 | 国产福利91精品一区二区三区 | 欧美日韩一级久久久久久免费看 | 高清色免费 | 午夜电影久久久 | 欧美日韩视频一区二区三区 | jizzjizzjizz亚洲 | 中日韩男男gay无套 日韩精品一区二区三区高清免费 | 婷婷久久亚洲 | 狠狠躁18三区二区一区ai明星 | 免费视频97 | 久久久久区 | 国产最新视频在线观看 | 在线免费看黄网站 | 久久婷亚洲五月一区天天躁 | 国产xxxx | 中文免费在线观看 | 久久黄网站 | 亚州欧美精品 | 国产精品国产三级国产 | 欧美日韩二区在线 | 国产成人av在线 | 人人插人人 | 国产小视频精品 | 久久成人麻豆午夜电影 | 国产无套精品久久久久久 | 看毛片网站 | 亚洲老妇xxxxxx | 国产精品va视频 | 精品一区电影国产 | 91在线免费视频观看 | 五月天色丁香 | 日韩成人欧美 | 欧美精品乱码99久久影院 | 国产区网址 | 91精品国产麻豆国产自产影视 | 一区二区 不卡 | 黄色三级免费 | 天天天操操操 | 国产一级大片在线观看 | 国产一级做a爱片久久毛片a | 亚洲乱码精品久久久久 | 日韩丝袜 | 日韩电影一区二区在线 | 国产在线播放一区二区三区 | 中文字幕国语官网在线视频 | 免费国产一区二区视频 | 久久久免费观看视频 | 色欧美88888久久久久久影院 | 日本中文在线观看 | 国产成视频在线观看 | 在线有码中文字幕 | 91激情视频在线播放 | 国产精品一区二区三区99 | 精品国产区在线 | 久久久免费观看 | 一级一片免费视频 | 免费av电影网站 | 91精品免费看| 天天综合天天综合 | 国产一级在线视频 | a久久久久久 | 免费看一级特黄a大片 | 999久久久欧美日韩黑人 | 国产91影院 | 麻花传媒mv免费观看 | 色国产精品一区在线观看 | 97国产情侣爱久久免费观看 | 国产玖玖在线 | jizz999| 中文字幕免费高清av | 伊人天堂av | 看片黄网站| 亚洲午夜久久久影院 | 亚洲精品高清在线观看 | 欧美色图p | 亚洲精品www| 久久99在线视频 | 国产精品96久久久久久吹潮 | 精品免费视频 | 亚洲婷婷综合色高清在线 | 99久久久国产精品 | 人人草网站| 欧美日韩三级 | 国产免费又粗又猛又爽 | 精品国产乱码一区二 | 91色吧| 最近久乱中文字幕 | 欧美在线91 | 青青河边草免费观看 | 欧美日韩国产一区二区三区在线观看 | 性色av免费在线观看 | 久久在线 | 亚洲激情综合 | 啪啪资源 | 色视频在线免费观看 | 2023年中文无字幕文字 | 91av在线视频播放 | 亚洲在线a | 美女福利视频 | 中文区中文字幕免费看 | 在线观看色网站 | 亚洲人人av | 国产精品白丝jk白祙 | 久久天天躁夜夜躁狠狠85麻豆 | 狠狠狠狠狠狠狠 | 97视频网址 | 亚洲成a人片在线www | 婷婷综合五月 | 日日夜夜人人天天 | 九九热在线精品 | 色视频网站免费观看 | 欧美日韩综合在线观看 | 91桃色免费视频 | 在线观看av麻豆 | 免费看三级网站 | 成人免费在线观看入口 | 久久国产精品免费一区 | 中文字幕丰满人伦在线 | 激情婷婷久久 | 狠狠色噜噜狠狠狠狠2021天天 | 亚洲男男gaygayxxxgv | 黄色小说免费在线观看 | 国产91电影在线观看 | 日韩久久一区二区 | 黄色片视频免费 | 丁香婷婷综合激情 | 午夜久久久久久久久久久 | 91精品国产自产在线观看 | 五月天婷婷在线观看视频 | 国产精品videossex国产高清 | 国产中出在线观看 | 日韩在线电影一区二区 | 日韩一区二区在线免费观看 | 国产精品一区二区三区在线免费观看 | 黄色福利视频网站 | 久久天堂精品视频 | 国产精品久久亚洲 | 不卡的av电影在线观看 | 最近最新mv字幕免费观看 | 久操视频在线免费看 | 久久免费电影网 | 国产精品一区二区电影 | 久久精品国产一区二区三 | 成人网页在线免费观看 | 欧美一级日韩三级 | 天天干夜夜爽 | 久久资源在线 | 成人国产精品免费 | 在线看免费 | 免费一级片在线观看 | 韩国一区二区三区在线观看 | 中文字幕色站 | 天天碰天天操 | 亚洲美女精品区人人人人 | 欧美日韩国产伦理 | 欧美视频xxx| 日韩中文字幕网站 | 久久综合久久综合九色 | 日韩久久电影 | 91精品国自产拍天天拍 | 亚洲欧美成人网 | 奇米影音四色 | 婷婷草 | 欧美综合在线视频 | 婷婷开心久久网 | 亚洲在线网址 | 免费观看福利视频 | 久久免费视频网站 | 超碰在线观看av | 久久久久久黄 | 97超碰资源网| 国产精品久久久久久久99 | 中文国产在线观看 | 国产91精品一区二区绿帽 | 手机在线欧美 | 日韩视频免费 | 91色九色 | 99精品视频一区二区 | 久久福利在线 | 国产精品区免费视频 | av中文字幕亚洲 | 友田真希av | 人人爱人人做人人爽 | 一区二区三区电影 | 久久不卡免费视频 | 国产亚洲成人网 | 少妇bbw揉bbb欧美 | 中文字幕在线免费 | 久久小视频 | 伊人婷婷激情 | 日韩av一区二区三区四区 | 综合精品久久久 | 黄色一级免费电影 | 日韩精品电影在线播放 | 高清视频一区 | 国产女人免费看a级丨片 | 在线精品视频免费观看 | 81精品国产乱码久久久久久 | 国产一区二区三区久久久 | 香蕉视频色 | 91一区二区在线 | 缴情综合网五月天 | www.国产在线观看 | 日本超碰在线 | 伊人看片 | 91探花在线视频 | 亚洲午夜精品一区 | 97精品国产| 国产色综合| 国内久久久久 | 久久免费电影网 | 婷婷五天天在线视频 | 国产一区二区三区免费在线 | 五月婷婷视频 | 成年人免费在线看 | 午夜在线看| 久久成熟| 婷婷亚洲最大 | 黄色在线看网站 | 午夜精品一区二区三区在线观看 | 国产婷婷在线观看 | 精品久久久久久久久久久院品网 | 久久久久草 | 久久国产精品小视频 | 日韩精品免费一线在线观看 | 午夜精品久久久久久久99无限制 | 伊人电影在线观看 | 伊人久久在线观看 | 亚洲综合黄色 | 亚洲高清精品在线 | 超碰97人人爱 | 激情综合五月婷婷 | 91精品天码美女少妇 | 麻豆视频一区 | 国产精品国产三级国产不产一地 | 久久福利 | 中文字幕资源网 国产 | 久久国产精品99精国产 | 91自拍视频在线 | av动态图片 | 国产精品 999 | 亚洲精品理论 | 国产免费亚洲高清 | 国产手机av在线 | 在线中文字幕电影 | 国产又粗又猛又黄 | 国产在线不卡精品 | 亚洲精品午夜久久久久久久 | 日韩欧美一区二区不卡 | www.天天操.com | 婷婷色5月 | av综合网址 | 精品婷婷| 国产精品福利午夜在线观看 | 欧美a在线免费观看 | 亚洲国产精彩中文乱码av | av电影中文字幕 | 成年人视频在线免费观看 | 亚洲每日更新 | wwwav视频 | 欧美最新另类人妖 | 91天天操 | 久久影院午夜论 | 久久综合色天天久久综合图片 | 午夜色场| 天天摸日日摸人人看 | 久久这里只有精品首页 | 亚洲v欧美v国产v在线观看 | 亚洲激情网站免费观看 | 国产精品一区二区美女视频免费看 | 伊人午夜视频 | 男女视频国产 | 中文字幕丝袜 | 美女搞黄国产视频网站 | 天天曰夜夜操 | 免费视频一二三区 | 久久精品屋 | 高清国产午夜精品久久久久久 | 精品久久1 | 九九精品久久久 | 美女免费电影 | 一级a性色生活片久久毛片波多野 | 国产精品99久久久久久久久久久久 | 豆豆色资源网xfplay | 日韩欧美xxxx| 天天射网| 久久综合中文色婷婷 | 国产片免费在线观看视频 | 成人毛片在线观看视频 | 日韩高清在线不卡 | 三级黄色免费片 | 91精品国产九九九久久久亚洲 | 欧美日韩久久不卡 | 亚洲精品自拍 | 视频福利在线观看 | 国产国语在线 | 黄色小说视频在线 | 欧美中文字幕久久 | 久久五月天色综合 | 日韩中文字幕电影 | 在线a视频 | 久草99| 狠狠的干 | 91成品人影院 | 亚洲精品在线网站 | 91久久偷偷做嫩草影院 | 亚洲九九影院 | 免费观看黄色12片一级视频 | 色老板在线视频 | 黄a在线| www激情久久 | 国产精品大尺度 | 色婷婷免费视频 | 色com网| 久久综合五月天 | 成人a v视频 | 黄色片网站 | 2023av在线| 看v片| 91插插插免费视频 | 亚州精品一二三区 | 黄色特级片 | 99热这里| 成人黄色电影在线播放 | 久久男人免费视频 | 国产精品久久久久久五月尺 | 精品久久久免费 | 国产一区电影在线观看 | 久久精品欧美一 | 日韩欧美精品在线 | 久久在视频 | 欧美精品亚洲二区 | 日韩在线不卡视频 | 日韩精品一区二区在线 | 狠色在线| 97成人免费视频 | a黄色 | 亚洲欧洲在线视频 | 亚洲精品乱码久久久久久按摩 | 最近能播放的中文字幕 | 亚洲最大激情中文字幕 | 中文综合在线 | 久久久国产精品一区二区三区 | 久久免费精彩视频 | 日韩国产高清在线 | 91看片淫黄大片在线播放 | 成人精品999 | 国产精品99蜜臀久久不卡二区 | 中文av在线免费观看 | 国产亚洲综合精品 | 三级免费黄色 | 色丁香综合 | 国产精品黄色av | 国语精品视频 | 国产福利一区二区三区在线观看 | 免费日韩 精品中文字幕视频在线 |