
各位爺,完整項(xiàng)目gitee如下,求star
heima-leadnews-master: 《黑馬頭條》項(xiàng)目采用的是SpringBoot+springcloud當(dāng)下最流行的微服務(wù)為項(xiàng)目架構(gòu),配合spring cloud alibaba nacos作為項(xiàng)目的注冊和配置中心。新課程采用快速開發(fā)的模式,主要解決真實(shí)企業(yè)開發(fā)的一些應(yīng)用場景。詳情請看博客:https://blog.csdn.net/m0_67184231/article/details/131439819
01環(huán)境搭建、SpringCloud微服務(wù)(注冊發(fā)現(xiàn)、服務(wù)調(diào)用、網(wǎng)關(guān))
1)課程對比

2)項(xiàng)目概述
2.1)能讓你收獲什么

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

2.3)項(xiàng)目概述
隨著智能手機(jī)的普及,人們更加習(xí)慣于通過手機(jī)來看新聞。由于生活節(jié)奏的加快,很多人只能利用碎片時(shí)間來獲取信息,因此,對于移動(dòng)資訊客戶端的需求也越來越高。
黑馬頭條項(xiàng)目正是在這樣背景下開發(fā)出來。黑馬頭條項(xiàng)目采用當(dāng)下火熱的微服務(wù)+大數(shù)據(jù)技術(shù)架構(gòu)實(shí)現(xiàn)。本項(xiàng)目主要著手于獲取最新最熱新聞資訊,通過大數(shù)據(jù)分析用戶喜好精確推送咨詢新聞

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

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

項(xiàng)目演示地址:
-
平臺管理:http://heima-admin-java.research.itcast.cn
-
自媒體:http://heime-media-java.research.itcast.cn
-
app端:http://heima-app-java.research.itcast.cn
平臺管理與自媒體為PC端,用電腦瀏覽器打開即可。
其中app端為移動(dòng)端,打開方式有兩種:
-
谷歌瀏覽器打開,調(diào)成移動(dòng)端模式
-
手機(jī)瀏覽器打開或掃描右側(cè)二維碼
3)技術(shù)棧

-
Spring-Cloud-Gateway : 微服務(wù)之前架設(shè)的網(wǎng)關(guān)服務(wù),實(shí)現(xiàn)服務(wù)注冊中的API請求路由,以及控制流速控制和熔斷處理都是常用的架構(gòu)手段,而這些功能Gateway天然支持
-
運(yùn)用Spring Boot快速開發(fā)框架,構(gòu)建項(xiàng)目工程;并結(jié)合Spring Cloud全家桶技術(shù),實(shí)現(xiàn)后端個(gè)人中心、自媒體、管理中心等微服務(wù)。
-
運(yùn)用Spring Cloud Alibaba Nacos作為項(xiàng)目中的注冊中心和配置中心
-
運(yùn)用mybatis-plus作為持久層提升開發(fā)效率
-
運(yùn)用Kafka完成內(nèi)部系統(tǒng)消息通知;與客戶端系統(tǒng)消息通知;以及實(shí)時(shí)數(shù)據(jù)計(jì)算
-
運(yùn)用Redis緩存技術(shù),實(shí)現(xiàn)熱數(shù)據(jù)的計(jì)算,提升系統(tǒng)性能指標(biāo)
-
使用Mysql存儲用戶數(shù)據(jù),以保證上層數(shù)據(jù)查詢的高性能
-
使用Mongo存儲用戶熱數(shù)據(jù),以保證用戶熱數(shù)據(jù)高擴(kuò)展和高性能指標(biāo)
-
使用FastDFS作為靜態(tài)資源存儲器,在其上實(shí)現(xiàn)熱靜態(tài)資源緩存、淘汰等功能
-
運(yùn)用Hbase技術(shù),存儲系統(tǒng)中的冷數(shù)據(jù),保證系統(tǒng)數(shù)據(jù)的可靠性
-
運(yùn)用ES搜索技術(shù),對冷數(shù)據(jù)、文章數(shù)據(jù)建立索引,以保證冷數(shù)據(jù)、文章查詢性能
-
運(yùn)用AI技術(shù),來完成系統(tǒng)自動(dòng)化功能,以提升效率及節(jié)省成本。比如實(shí)名認(rèn)證自動(dòng)化
-
PMD&P3C : 靜態(tài)代碼掃描工具,在項(xiàng)目中掃描項(xiàng)目代碼,檢查異常點(diǎn)、優(yōu)化點(diǎn)、代碼規(guī)范等,為開發(fā)團(tuán)隊(duì)提供規(guī)范統(tǒng)一,提升項(xiàng)目代碼質(zhì)量
4)nacos環(huán)境搭建
4.1)虛擬機(jī)鏡像準(zhǔn)備
1)打開當(dāng)天資料文件中的鏡像,拷貝到一個(gè)地方,然后解壓
2)解壓后,雙擊ContOS7-hmtt.vmx文件,前提是電腦上已經(jīng)安裝了VMware

-
修改虛擬網(wǎng)絡(luò)地址(NAT)

①,選中VMware中的編輯
②,選擇虛擬網(wǎng)絡(luò)編輯器
③,找到NAT網(wǎng)卡,把網(wǎng)段改為200(當(dāng)前掛載的虛擬機(jī)已固定ip地址)
4)修改虛擬機(jī)的網(wǎng)絡(luò)模式為NAT

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

4.2)nacos安裝
①:docker拉取鏡像
docker pull nacos/nacos-server:1.2.0
②:創(chuàng)建容器
docker run --env MODE=standalone --name nacos --restart=always -d -p 8848:8848 nacos/nacos-server:1.2.0
-
MODE=standalone 單機(jī)版
-
--restart=always 開機(jī)啟動(dòng)
-
-p 8848:8848 映射端口
-
-d 創(chuàng)建一個(gè)守護(hù)式容器在后臺運(yùn)行
③:訪問地址:http://192.168.200.130:8848/nacos

5)初始工程搭建
5.1)環(huán)境準(zhǔn)備
①:項(xiàng)目依賴環(huán)境(需提前安裝好)
-
JDK1.8
-
Intellij Idea
-
maven-3.6.1
-
Git
②:在當(dāng)天資料中解壓heima-leadnews.zip文件,拷貝到 沒有中文和空格的目錄,使用idea打開即可

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

設(shè)置本地倉庫,建議使用資料中提供好的倉庫
④:設(shè)置項(xiàng)目編碼格式

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

6)登錄
6.1)需求分析

-
戶點(diǎn)擊開始使用
登錄后的用戶權(quán)限較大,可以查 看,也可以操作(點(diǎn)贊,關(guān)注,評論)
-
用戶點(diǎn)擊不登錄,先看看
游客只有查看的權(quán)限
6.2)表結(jié)構(gòu)分析
關(guān)于app端用戶相關(guān)的內(nèi)容較多,可以單獨(dú)設(shè)置一個(gè)庫leadnews_user
表名稱 |
說明 |
ap_user |
APP用戶信息表 |
ap_user_fan |
APP用戶粉絲信息表 |
ap_user_follow |
APP用戶關(guān)注信息表 |
ap_user_realname |
APP實(shí)名認(rèn)證信息表 |
從當(dāng)前資料中找到對應(yīng)數(shù)據(jù)庫并導(dǎo)入到mysql中
登錄需要用到的是ap_user表,表結(jié)構(gòu)如下:


項(xiàng)目中的持久層使用的mybatis-plus,一般都使用mybais-plus逆向生成對應(yīng)的實(shí)體類
app_user表對應(yīng)的實(shí)體類如下:
package com.heima.model.user.pojos;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* <p>
* APP用戶信息表
* </p>
*
* @author itheima
*/
@Data
@TableName("ap_user")
public class ApUser implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主鍵
*/
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 密碼、通信等加密鹽
*/
@TableField("salt")
private String salt;
/**
* 用戶名
*/
@TableField("name")
private String name;
/**
* 密碼,md5加密
*/
@TableField("password")
private String password;
/**
* 手機(jī)號
*/
@TableField("phone")
private String phone;
/**
* 頭像
*/
@TableField("image")
private String image;
/**
* 0 男
1 女
2 未知
*/
@TableField("sex")
private Boolean sex;
/**
* 0 未
1 是
*/
@TableField("is_certification")
private Boolean certification;
/**
* 是否身份認(rèn)證
*/
@TableField("is_identity_authentication")
private Boolean identityAuthentication;
/**
* 0正常
1鎖定
*/
@TableField("status")
private Boolean status;
/**
* 0 普通用戶
1 自媒體人
2 大V
*/
@TableField("flag")
private Short flag;
/**
* 注冊時(shí)間
*/
@TableField("created_time")
private Date createdTime;
}
手動(dòng)加密(md5+隨機(jī)字符串)
md5是不可逆加密,md5相同的密碼每次加密都一樣,不太安全。在md5的基礎(chǔ)上手動(dòng)加鹽(salt)處理
注冊->生成鹽

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

6.3)思路分析

1,用戶輸入了用戶名和密碼進(jìn)行登錄,校驗(yàn)成功后返回jwt(基于當(dāng)前用戶的id生成)
2,用戶游客登錄,生成jwt返回(基于默認(rèn)值0生成)‘
6.4)運(yùn)營端微服務(wù)搭建
在heima-leadnews-service下創(chuàng)建工程heima-leadnews-user

引導(dǎo)類
package com.heima.user;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient//集成當(dāng)前注冊中心
@MapperScan("com.heima.user.mapper")//掃描mapper
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class,args);
}
}
bootstrap.yml
server:
port: 51801
spring:
application:
name: leadnews-user
cloud:
nacos:
discovery:
server-addr: 192.168.200.130:8848
config:
server-addr: 192.168.200.130:8848
file-extension: yml
在nacos中創(chuàng)建配置文件

spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/leadnews_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: root
# 設(shè)置Mapper接口所對應(yīng)的XML文件位置,如果你在Mapper接口中有自定義方法,需要進(jìn)行該配置
mybatis-plus:
mapper-locations: classpath*:mapper/*.xml
# 設(shè)置別名包掃描路徑,通過該屬性可以給包中的類注冊別名
type-aliases-package: com.heima.model.user.pojos
報(bào)錯(cuò):
The last packet successfully received from the server was 523 milliseconds ago. The last packet sent successfully to the server was 518 milliseconds ago.
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
可以把nacos的mysqlurl改成下面
url: jdbc:mysql://localhost:3306/leadnews_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--定義日志文件的存儲地址,使用絕對路徑-->
<property name="LOG_HOME" value="e:/logs"/>
<!-- Console 輸出設(shè)置 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!--格式化輸出:%d表示日期,%thread表示線程名,%-5level:級別從左顯示5個(gè)字符寬度%msg:日志消息,%n是換行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件輸出的文件名-->
<fileNamePattern>${LOG_HOME}/leadnews.%d{yyyy-MM-dd}.log</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 異步輸出 -->
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丟失日志.默認(rèn)的,如果隊(duì)列的80%已滿,則會(huì)丟棄TRACT、DEBUG、INFO級別的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默認(rèn)的隊(duì)列的深度,該值會(huì)影響性能.默認(rèn)值為256 -->
<queueSize>512</queueSize>
<!-- 添加附加的appender,最多只能添加一個(gè) -->
<appender-ref ref="FILE"/>
</appender>
<logger name="org.apache.ibatis.cache.decorators.LoggingCache" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="org.springframework.boot" level="debug"/>
<root level="info">
<!--<appender-ref ref="ASYNC"/>-->
<appender-ref ref="FILE"/>
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
6.4)登錄功能實(shí)現(xiàn)
①:接口定義
@RestController
@RequestMapping("/api/v1/login")
public class ApUserLoginController {
@PostMapping("/login_auth")
public ResponseResult login(@RequestBody LoginDto dto) {
return null;
}
}
②:持久層mapper
package com.heima.user.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.user.pojos.ApUser;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ApUserMapper extends BaseMapper<ApUser> {
}
③:業(yè)務(wù)層service
package com.heima.user.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.user.dtos.LoginDto;
import com.heima.model.user.pojos.ApUser;
public interface ApUserService extends IService<ApUser>{
/**
* app端登錄
* @param dto
* @return
*/
public ResponseResult login(LoginDto dto);
}
實(shí)現(xiàn)類:
package com.heima.user.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.user.dtos.LoginDto;
import com.heima.model.user.pojos.ApUser;
import com.heima.user.mapper.ApUserMapper;
import com.heima.user.service.ApUserService;
import com.heima.utils.common.AppJwtUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import java.util.HashMap;
import java.util.Map;
@Service
public class ApUserServiceImpl extends ServiceImpl<ApUserMapper, ApUser> implements ApUserService {
@Override
public ResponseResult login(LoginDto dto) {
//1.正常登錄(手機(jī)號+密碼登錄)
if (!StringUtils.isBlank(dto.getPhone()) && !StringUtils.isBlank(dto.getPassword())) {
//1.1查詢用戶
ApUser apUser = getOne(Wrappers.<ApUser>lambdaQuery().eq(ApUser::getPhone, dto.getPhone()));
if (apUser == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST,"用戶不存在");
}
//1.2 比對密碼
String salt = apUser.getSalt();
String pswd = dto.getPassword();
pswd = DigestUtils.md5DigestAsHex((pswd + salt).getBytes());
if (!pswd.equals(apUser.getPassword())) {
return ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_PASSWORD_ERROR);
}
//1.3 返回?cái)?shù)據(jù) jwt
Map<String, Object> map = new HashMap<>();
map.put("token", AppJwtUtil.getToken(apUser.getId().longValue()));
? ? ? ? //這兩個(gè)值 置空 再返回對象
apUser.setSalt("");
apUser.setPassword("");
map.put("user", apUser);
return ResponseResult.okResult(map);
} else {
//2.游客 同樣返回token id = 0
Map<String, Object> map = new HashMap<>();
map.put("token", AppJwtUtil.getToken(0l));
return ResponseResult.okResult(map);
}
}
}
④:控制層controller
package com.heima.user.controller.v1;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.user.dtos.LoginDto;
import com.heima.user.service.ApUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/login")
public class ApUserLoginController {
@Autowired
private ApUserService apUserService;
@PostMapping("/login_auth")
public ResponseResult login(@RequestBody LoginDto dto) {
return apUserService.login(dto);
}
}
docker exec -it redis redis-cli這個(gè)進(jìn)入服務(wù)器得redis界面 你再輸入docker ps就能看到服務(wù)起來了;docker 中的redis沒密碼,要把nacos的密碼去掉;
報(bào)錯(cuò)的兄弟們先創(chuàng)建redis容器a然后把nacos配置中redis密碼那一行注掉就行了
docker run --name redis -p 6379:6379 -d redis
7)接口工具postman、swagger、knife4j
7.1)postman
Postman是一款功能強(qiáng)大的網(wǎng)頁調(diào)試與發(fā)送網(wǎng)頁HTTP請求的Chrome插件。postman被500萬開發(fā)者和超100,000家公司用于每月訪問1.3億個(gè)API。
官方網(wǎng)址:Postman
解壓資料文件夾中的軟件,安裝即可

通常的接口測試查看請求和響應(yīng),下面是登錄請求的測試
http://localhost:51801/api/v1/login/login_auth
{"phone":"13511223456","password":"admin"}

7.2)swagger
(1)簡介
Swagger 是一個(gè)規(guī)范和完整的框架,用于生成、描述、調(diào)用和可視化 RESTful 風(fēng)格的 Web 服務(wù)(API Documentation & Design Tools for Teams | Swagger)。 它的主要作用是:
-
使得前后端分離開發(fā)更加方便,有利于團(tuán)隊(duì)協(xié)作
-
接口的文檔在線自動(dòng)生成,降低后端開發(fā)人員編寫接口文檔的負(fù)擔(dān)
-
功能測試
Spring已經(jīng)將Swagger納入自身的標(biāo)準(zhǔn),建立了Spring-swagger項(xiàng)目,現(xiàn)在叫Springfox。
通過在項(xiàng)目中引入Springfox ,即可非常簡單快捷的使用Swagger。
(2)SpringBoot集成Swagger
-
引入依賴,在heima-leadnews-model和heima-leadnews-common模塊中引入該依賴
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
只需要在heima-leadnews-common中進(jìn)行配置即可,因?yàn)槠渌⒎?wù)工程都直接或間接依賴即可。
-
在heima-leadnews-common工程中添加一個(gè)配置類
新增:com.heima.common.swagger.SwaggerConfiguration
package com.heima.common.swagger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfiguration {
@Bean
public Docket buildDocket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(buildApiInfo())
.select()
// 要掃描的API(Controller)基礎(chǔ)包
.apis(RequestHandlerSelectors.basePackage("com.heima"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo buildApiInfo() {
Contact contact = new Contact("黑馬程序員","","");
return new ApiInfoBuilder()
.title("黑馬頭條-平臺管理API文檔")
.description("黑馬頭條后臺api")
.contact(contact)
.version("1.0.0").build();
}
}
在heima-leadnews-common模塊中的resources目錄中新增以下目錄和文件
文件:resources/META-INF/Spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.heima.common.swagger.SwaggerConfiguration
(3)Swagger常用注解
在Java類中添加Swagger的注解即可生成Swagger接口文檔,常用Swagger注解如下:
@Api:修飾整個(gè)類,描述Controller的作用
@ApiOperation:描述一個(gè)類的一個(gè)方法,或者說一個(gè)接口
@ApiParam:單個(gè)參數(shù)的描述信息
@ApiModel:用對象來接收參數(shù)
@ApiModelProperty:用對象接收參數(shù)時(shí),描述對象的一個(gè)字段
@ApiResponse:HTTP響應(yīng)其中1個(gè)描述
@ApiResponses:HTTP響應(yīng)整體描述
@ApiIgnore:使用該注解忽略這個(gè)API
@ApiError :發(fā)生錯(cuò)誤返回的信息
@ApiImplicitParam:一個(gè)請求參數(shù)
@ApiImplicitParams:多個(gè)請求參數(shù)的描述信息
@ApiImplicitParam屬性:
屬性 |
取值 |
作用 |
paramType |
查詢參數(shù)類型 |
|
path |
以地址的形式提交數(shù)據(jù) |
|
query |
直接跟參數(shù)完成自動(dòng)映射賦值 |
|
body |
以流的形式提交 僅支持POST |
|
header |
參數(shù)在request headers 里邊提交 |
|
form |
以form表單的形式提交 僅支持POST |
|
dataType |
參數(shù)的數(shù)據(jù)類型 只作為標(biāo)志說明,并沒有實(shí)際驗(yàn)證 |
|
Long |
||
String |
||
name |
接收參數(shù)名 |
|
value |
接收參數(shù)的意義描述 |
|
required |
參數(shù)是否必填 |
|
true |
必填 |
|
false |
非必填 |
|
defaultValue |
默認(rèn)值 |
我們在ApUserLoginController中添加Swagger注解,代碼如下所示:
@RestController
@RequestMapping("/api/v1/login")
@Api(value = "app端用戶登錄", tags = "ap_user", description = "app端用戶登錄API")
public class ApUserLoginController {
@Autowired
private ApUserService apUserService;
@PostMapping("/login_auth")
@ApiOperation("用戶登錄")
public ResponseResult login(@RequestBody LoginDto dto){
return apUserService.login(dto);
}
}
LoginDto
@Data
public class LoginDto {
/**
* 手機(jī)號
*/
@ApiModelProperty(value="手機(jī)號",required = true)
private String phone;
/**
* 密碼
*/
@ApiModelProperty(value="密碼",required = true)
private String password;
}
啟動(dòng)user微服務(wù),訪問地址:http://localhost:51801/swagger-ui.html
7.3)knife4j
(1)簡介
knife4j是為Java MVC框架集成Swagger生成Api文檔的增強(qiáng)解決方案,前身是swagger-bootstrap-ui,取名kni4j是希望它能像一把匕首一樣小巧,輕量,并且功能強(qiáng)悍!
gitee地址:knife4j: Knife4j是一個(gè)集Swagger2 和 OpenAPI3為一體的增強(qiáng)解決方案
官方文檔:Knife4j · 集Swagger2及OpenAPI3為一體的增強(qiáng)解決方案. | Knife4j
效果演示:http://knife4j.xiaominfo.com/doc.html
(2)核心功能
該UI增強(qiáng)包主要包括兩大核心功能:文檔說明 和 在線調(diào)試
-
文檔說明:根據(jù)Swagger的規(guī)范說明,詳細(xì)列出接口文檔的說明,包括接口地址、類型、請求示例、請求參數(shù)、響應(yīng)示例、響應(yīng)參數(shù)、響應(yīng)碼等信息,使用swagger-bootstrap-ui能根據(jù)該文檔說明,對該接口的使用情況一目了然。
-
在線調(diào)試:提供在線接口聯(lián)調(diào)的強(qiáng)大功能,自動(dòng)解析當(dāng)前接口參數(shù),同時(shí)包含表單驗(yàn)證,調(diào)用參數(shù)可返回接口響應(yīng)內(nèi)容、headers、Curl請求命令實(shí)例、響應(yīng)時(shí)間、響應(yīng)狀態(tài)碼等信息,幫助開發(fā)者在線調(diào)試,而不必通過其他測試工具測試接口是否正確,簡介、強(qiáng)大。
-
個(gè)性化配置(Swaager無):通過個(gè)性化ui配置項(xiàng),可自定義UI的相關(guān)顯示信息
-
離線文檔(Swaager無):根據(jù)標(biāo)準(zhǔn)規(guī)范,生成的在線markdown離線文檔,開發(fā)者可以進(jìn)行拷貝生成markdown接口文檔,通過其他第三方markdown轉(zhuǎn)換工具轉(zhuǎn)換成html或pdf,這樣也可以放棄swagger2markdown組件
-
接口排序:自1.8.5后,ui支持了接口排序功能,
-
例如一個(gè)注冊功能主要包含了多個(gè)步驟,可以根據(jù)swagger-bootstrap-ui提供的接口排序規(guī)則實(shí)現(xiàn)接口的排序,step化接口操作,方便其他開發(fā)者進(jìn)行接口對接
(3)快速集成
-
在heima-leadnews-common模塊中的pom.xml文件中引入knife4j的依賴,如下:
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
-
創(chuàng)建Swagger配置文件
在heima-leadnews-common模塊中新建配置類
新建Swagger的配置文件SwaggerConfiguration.java文件,創(chuàng)建springfox提供的Docket分組對象,代碼如下:
package com.heima.common.knife4j;
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
@EnableKnife4j
@Import(BeanValidatorPluginsConfiguration.class)
public class Swagger2Configuration {
@Bean(value = "defaultApi2")
public Docket defaultApi2() {
Docket docket=new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
//分組名稱
.groupName("1.0")
.select()
//這里指定Controller掃描包路徑
.apis(RequestHandlerSelectors.basePackage("com.heima"))
.paths(PathSelectors.any())
.build();
return docket;
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("黑馬頭條API文檔")
.description("黑馬頭條API文檔")
.version("1.0")
.build();
}
}
以上有兩個(gè)注解需要特別說明,如下表:
注解 |
說明 |
@EnableSwagger2 |
該注解是Springfox-swagger框架提供的使用Swagger注解,該注解必須加 |
@EnableKnife4j |
該注解是knife4j提供的增強(qiáng)注解,Ui提供了例如動(dòng)態(tài)參數(shù)、參數(shù)過濾、接口排序等增強(qiáng)功能,如果你想使用這些增強(qiáng)功能就必須加該注解,否則可以不用加 |
-
添加配置
在Spring.factories中新增配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.heima.common.swagger.Swagger2Configuration, \
com.heima.common.swagger.SwaggerConfiguration
-
訪問
在瀏覽器輸入地址:http://host:port/doc.html;http://localhost:51801/doc.html
8)網(wǎng)關(guān)
(1)在heima-leadnews-gateway導(dǎo)入以下依賴
pom文件
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
</dependencies>
(2)在heima-leadnews-gateway下創(chuàng)建heima-leadnews-app-gateway微服務(wù)
引導(dǎo)類:
package com.heima.app.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient //開啟注冊中心
public class AppGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(AppGatewayApplication.class,args);
}
}
bootstrap.yml
server:
port: 51601
spring:
application:
name: leadnews-app-gateway
cloud:
nacos:
discovery:
server-addr: 192.168.200.130:8848
config:
server-addr: 192.168.200.130:8848
file-extension: yml
在nacos的配置中心創(chuàng)建dataid為leadnews-app-gateway的yml配置

spring:
cloud:
gateway:
globalcors:
add-to-simple-url-handler-mapping: true
corsConfigurations:
'[/**]':
allowedHeaders: "*"
allowedOrigins: "*"
allowedMethods:
- GET
- POST
- DELETE
- PUT
- OPTION
routes:
# 平臺管理
- id: user
uri: lb://leadnews-user
predicates:
- Path=/user/**
filters:
- StripPrefix= 1
環(huán)境搭建完成以后,啟動(dòng)項(xiàng)目網(wǎng)關(guān)和用戶兩個(gè)服務(wù),使用postman進(jìn)行測試
請求地址:http://localhost:51601/user/api/v1/login/login_auth
1.3 全局過濾器實(shí)現(xiàn)jwt校驗(yàn)

思路分析:
-
用戶進(jìn)入網(wǎng)關(guān)開始登陸,網(wǎng)關(guān)過濾器進(jìn)行判斷,如果是登錄,則路由到后臺管理微服務(wù)進(jìn)行登錄
-
用戶登錄成功,后臺管理微服務(wù)簽發(fā)JWT TOKEN信息返回給用戶
-
用戶再次進(jìn)入網(wǎng)關(guān)開始訪問,網(wǎng)關(guān)過濾器接收用戶攜帶的TOKEN
-
網(wǎng)關(guān)過濾器解析TOKEN ,判斷是否有權(quán)限,如果有,則放行,如果沒有則返回未認(rèn)證錯(cuò)誤
具體實(shí)現(xiàn):
第一:
在認(rèn)證過濾器中需要用到j(luò)wt的解析,所以需要把工具類拷貝一份到網(wǎng)關(guān)微服務(wù)
第二:
在網(wǎng)關(guān)微服務(wù)中新建全局過濾器:
package com.heima.app.gateway.filter;
import com.heima.app.gateway.util.AppJwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
@Slf4j
public class AuthorizeFilter implements Ordered, GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.獲取request和response對象
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
//2.判斷是否是登錄
if(request.getURI().getPath().contains("/login")){
//放行
return chain.filter(exchange);
}
//3.獲取token
String token = request.getHeaders().getFirst("token");
//4.判斷token是否存在
if(StringUtils.isBlank(token)){
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//5.判斷token是否有效
try {
Claims claimsBody = AppJwtUtil.getClaimsBody(token);
//是否是過期
int result = AppJwtUtil.verifyToken(claimsBody);
if(result == 1 || result == 2){
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
}catch (Exception e){
e.printStackTrace();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//6.放行
return chain.filter(exchange);
}
/**
* 優(yōu)先級設(shè)置 值越小 優(yōu)先級越高
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
測試:
啟動(dòng)user服務(wù),繼續(xù)訪問其他微服務(wù),會(huì)提示需要認(rèn)證才能訪問,這個(gè)時(shí)候需要在heads中設(shè)置設(shè)置token才能正常訪問。
9)前端集成
9.1)前端項(xiàng)目部署思路

通過nginx來進(jìn)行配置,功能如下
-
通過nginx的反向代理功能訪問后臺的網(wǎng)關(guān)資源
-
通過nginx的靜態(tài)服務(wù)器功能訪問前端靜態(tài)頁面
9.2)配置nginx
①:解壓資料文件夾中的壓縮包nginx-1.18.0.zip
路徑cmd,鍵入nginx,啟動(dòng)
②:解壓資料文件夾中的前端項(xiàng)目app-web.zip
③:配置nginx.conf文件
在nginx安裝的conf目錄下新建一個(gè)文件夾leadnews.conf,在當(dāng)前文件夾中新建heima-leadnews-app.conf文件
heima-leadnews-app.conf配置如下:
upstream heima-app-gateway{
server localhost:51601;
}
server {
listen 8801;
location / {
root D:/workspace/app-web/;
index index.html;
}
location ~/app/(.*) {
proxy_pass http://heima-app-gateway/$1;
proxy_set_header HOST $host; # 不改變源請求頭的值
proxy_pass_request_body on; #開啟獲取請求體
proxy_pass_request_headers on; #開啟獲取請求頭
proxy_set_header X-Real-IP $remote_addr; # 記錄真實(shí)發(fā)出請求的客戶端IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; #記錄代理信息
}
}
nginx.conf 把里面注釋的內(nèi)容和靜態(tài)資源配置相關(guān)刪除,引入heima-leadnews-app.conf文件加載
#user nobody;
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
# 引入自定義配置文件
include leadnews.conf/*.conf;
}
④ :啟動(dòng)nginx
在nginx安裝包中使用命令提示符打開,輸入命令nginx啟動(dòng)項(xiàng)目
可查看進(jìn)程,檢查nginx是否啟動(dòng)
重新加載配置文件:nginx -s reload
報(bào)錯(cuò)
使用windows版本的nginx啟動(dòng)時(shí)遇到(1113: No mapping for the Unicode character exists in the target multi-byte code page)這個(gè)錯(cuò)誤
把nginx的版本升高了,依舊報(bào)錯(cuò)
后來查閱發(fā)現(xiàn)是因?yàn)榻鈮旱穆窂嚼锩姘兄形牡木壒剩灰呀鈮汉蟮奈募羟械經(jīng)]有包含中文的目錄即可解決問題
在運(yùn)行reload的時(shí)候報(bào)錯(cuò):CreateFile() "D:\DevelopCode\JavaCode\Springcloud\leadnews\nginx-1.18.0/logs/nginx.pid" failed (2: The system cannot find the file specified)
加pid的文件,然后里面沒東西運(yùn)行失敗,就start nginx,計(jì)算機(jī)自己在pid加了東西,成功reload!
打開8801 報(bào)錯(cuò):500 Internal Server Error
這個(gè)錯(cuò)誤通常表示在Windows操作系統(tǒng)上,Nginx無法識別路徑中含有非英文字符的文件或文件夾。這可能是因?yàn)镹ginx默認(rèn)將其配置文件和日志文件等保存在UTF-8編碼下,而Windows默認(rèn)的編碼方式是ANSI。
要解決這個(gè)問題,可以嘗試以下方法:
將Nginx配置文件及相關(guān)路徑修改為英文字符,避免包含非英文字符的路徑。
修改Nginx的配置文件編碼為 ANSI :在Nginx的配置文件中,找到 http 塊,并在該塊中添加以下指令:
plaintext復(fù)制代碼http {
# ...
charset utf-8;
charset_types text/plain text/css application/javascript application/json text/javascript;
charset utf-8 off;
# ...
}
保存并重新啟動(dòng)Nginx服務(wù)。
⑤:打開前端項(xiàng)目進(jìn)行測試 -- > http://localhost:8801
用谷歌瀏覽器打開,調(diào)試移動(dòng)端模式進(jìn)行訪問
02app端文章查看,靜態(tài)化freemarker,分布式文件系統(tǒng)minIO
1)文章列表加載
1.1)需求分析
文章布局展示

1.2)表結(jié)構(gòu)分析
ap_article 文章基本信息表

ap_article_config 文章配置表

ap_article_content 文章內(nèi)容表

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

表的拆分—垂直分表

1.3)導(dǎo)入文章數(shù)據(jù)庫
1.3.1)導(dǎo)入數(shù)據(jù)庫
查看當(dāng)天資料文件夾,在數(shù)據(jù)庫連接工具中執(zhí)行l(wèi)eadnews_article.sql
1.3.2)導(dǎo)入對應(yīng)的實(shí)體類
ap_article文章表對應(yīng)實(shí)體
package com.heima.model.article.pojos;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* <p>
* 文章信息表,存儲已發(fā)布的文章
* </p>
*
* @author itheima
*/
@Data
@TableName("ap_article")
public class ApArticle implements Serializable {
@TableId(value = "id",type = IdType.ID_WORKER)
private Long id;
/**
* 標(biāo)題
*/
private String title;
/**
* 作者id
*/
@TableField("author_id")
private Long authorId;
/**
* 作者名稱
*/
@TableField("author_name")
private String authorName;
/**
* 頻道id
*/
@TableField("channel_id")
private Integer channelId;
/**
* 頻道名稱
*/
@TableField("channel_name")
private String channelName;
/**
* 文章布局 0 無圖文章 1 單圖文章 2 多圖文章
*/
private Short layout;
/**
* 文章標(biāo)記 0 普通文章 1 熱點(diǎn)文章 2 置頂文章 3 精品文章 4 大V 文章
*/
private Byte flag;
/**
* 文章封面圖片 多張逗號分隔
*/
private String images;
/**
* 標(biāo)簽
*/
private String labels;
/**
* 點(diǎn)贊數(shù)量
*/
private Integer likes;
/**
* 收藏?cái)?shù)量
*/
private Integer collection;
/**
* 評論數(shù)量
*/
private Integer comment;
/**
* 閱讀數(shù)量
*/
private Integer views;
/**
* 省市
*/
@TableField("province_id")
private Integer provinceId;
/**
* 市區(qū)
*/
@TableField("city_id")
private Integer cityId;
/**
* 區(qū)縣
*/
@TableField("county_id")
private Integer countyId;
/**
* 創(chuàng)建時(shí)間
*/
@TableField("created_time")
private Date createdTime;
/**
* 發(fā)布時(shí)間
*/
@TableField("publish_time")
private Date publishTime;
/**
* 同步狀態(tài)
*/
@TableField("sync_status")
private Boolean syncStatus;
/**
* 來源
*/
private Boolean origin;
/**
* 靜態(tài)頁面地址
*/
@TableField("static_url")
private String staticUrl;
}
ap_article_config文章配置對應(yīng)實(shí)體類
package com.heima.model.article.pojos;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
/**
* <p>
* APP已發(fā)布文章配置表
* </p>
*
* @author itheima
*/
@Data
@TableName("ap_article_config")
public class ApArticleConfig implements Serializable {
@TableId(value = "id",type = IdType.ID_WORKER)
private Long id;
/**
* 文章id
*/
@TableField("article_id")
private Long articleId;
/**
* 是否可評論
* true: 可以評論 1
* false: 不可評論 0
*/
@TableField("is_comment")
private Boolean isComment;
/**
* 是否轉(zhuǎn)發(fā)
* true: 可以轉(zhuǎn)發(fā) 1
* false: 不可轉(zhuǎn)發(fā) 0
*/
@TableField("is_forward")
private Boolean isForward;
/**
* 是否下架
* true: 下架 1
* false: 沒有下架 0
*/
@TableField("is_down")
private Boolean isDown;
/**
* 是否已刪除
* true: 刪除 1
* false: 沒有刪除 0
*/
@TableField("is_delete")
private Boolean isDelete;
}
ap_article_content 文章內(nèi)容對應(yīng)的實(shí)體類
package com.heima.model.article.pojos;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
@Data
@TableName("ap_article_content")
public class ApArticleContent implements Serializable {
@TableId(value = "id",type = IdType.ID_WORKER)
private Long id;
/**
* 文章id
*/
@TableField("article_id")
private Long articleId;
/**
* 文章內(nèi)容
*/
private String content;
}
1.4)實(shí)現(xiàn)思路

1,在默認(rèn)頻道展示10條文章信息
2,可以切換頻道查看不同種類文章
3,當(dāng)用戶下拉可以加載最新的文章(分頁)本頁文章列表中發(fā)布時(shí)間為最大的時(shí)間為依據(jù)
4,當(dāng)用戶上拉可以加載更多的文章信息(按照發(fā)布時(shí)間)本頁文章列表中發(fā)布時(shí)間最小的時(shí)間為依據(jù)
5,如果是當(dāng)前頻道的首頁,前端傳遞默認(rèn)參數(shù):
-
maxBehotTime:0(毫秒)
-
minBehotTime:20000000000000(毫秒)--->2063年


1.5)接口定義
加載首頁 |
加載更多 |
加載最新 |
|
接口路徑 |
/api/v1/article/load |
/api/v1/article/loadmore |
/api/v1/article/loadnew |
請求方式 |
POST |
POST |
POST |
參數(shù) |
ArticleHomeDto |
ArticleHomeDto |
ArticleHomeDto |
響應(yīng)結(jié)果 |
ResponseResult |
ResponseResult |
ResponseResult |
ArticleHomeDto
package com.heima.model.article.dtos;
import lombok.Data;
import java.util.Date;
@Data
public class ArticleHomeDto {
// 最大時(shí)間
Date maxBehotTime;
// 最小時(shí)間
Date minBehotTime;
// 分頁size
Integer size;
// 頻道ID
String tag;
}
1.6)功能實(shí)現(xiàn)
1.6.1):導(dǎo)入heima-leadnews-article微服務(wù),資料在當(dāng)天的文件夾中

注意:需要在heima-leadnews-service的pom文件夾中添加子模塊信息,如下:
<modules>
<module>heima-leadnews-user</module>
<module>heima-leadnews-article</module>
</modules>
在idea中的maven中更新一下,如果工程還是灰色的,需要在重新添加文章微服務(wù)的pom文件,操作步驟如下:

需要在nacos中添加對應(yīng)的配置
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/leadnews_article?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: root
# 設(shè)置Mapper接口所對應(yīng)的XML文件位置,如果你在Mapper接口中有自定義方法,需要進(jìn)行該配置
mybatis-plus:
mapper-locations: classpath*:mapper/*.xml
# 設(shè)置別名包掃描路徑,通過該屬性可以給包中的類注冊別名
type-aliases-package: com.heima.model.article.pojos
1.6.2):定義接口
package com.heima.article.controller.v1;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.common.dtos.ResponseResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/article")
public class ArticleHomeController {
@PostMapping("/load")
public ResponseResult load(@RequestBody ArticleHomeDto dto) {
return null;
}
@PostMapping("/loadmore")
public ResponseResult loadMore(@RequestBody ArticleHomeDto dto) {
return null;
}
@PostMapping("/loadnew")
public ResponseResult loadNew(@RequestBody ArticleHomeDto dto) {
return null;
}
}
1.6.3):編寫mapper文件
mybatisPlus對多表查詢不太友好,所以用mybatis自定義mapper查詢
package com.heima.article.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.pojos.ApArticle;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface ApArticleMapper extends BaseMapper<ApArticle> {
public List<ApArticle> loadArticleList(@Param("dto") ArticleHomeDto dto, @Param("type") Short type);
}
對應(yīng)的映射文件
在resources中新建mapper/ApArticleMapper.xml 如下配置:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.heima.article.mapper.ApArticleMapper">
<resultMap id="resultMap" type="com.heima.model.article.pojos.ApArticle">
<id column="id" property="id"/>
<result column="title" property="title"/>
<result column="author_id" property="authorId"/>
<result column="author_name" property="authorName"/>
<result column="channel_id" property="channelId"/>
<result column="channel_name" property="channelName"/>
<result column="layout" property="layout"/>
<result column="flag" property="flag"/>
<result column="images" property="images"/>
<result column="labels" property="labels"/>
<result column="likes" property="likes"/>
<result column="collection" property="collection"/>
<result column="comment" property="comment"/>
<result column="views" property="views"/>
<result column="province_id" property="provinceId"/>
<result column="city_id" property="cityId"/>
<result column="county_id" property="countyId"/>
<result column="created_time" property="createdTime"/>
<result column="publish_time" property="publishTime"/>
<result column="sync_status" property="syncStatus"/>
<result column="static_url" property="staticUrl"/>
</resultMap>
<select id="loadArticleList" resultMap="resultMap">
SELECT
aa.*
FROM
`ap_article` aa
LEFT JOIN ap_article_config aac ON aa.id = aac.article_id
<where>
and aac.is_delete != 1
and aac.is_down != 1
<!-- loadmore -->
<if test="type != null and type == 1">
and aa.publish_time <![CDATA[<]]> #{dto.minBehotTime}
</if>
<if test="type != null and type == 2">
and aa.publish_time <![CDATA[>]]> #{dto.maxBehotTime}
</if>
<if test="dto.tag != '__all__'">
and aa.channel_id = #{dto.tag}
</if>
</where>
order by aa.publish_time desc
limit #{dto.size}
</select>
</mapper>
1.6.4):編寫業(yè)務(wù)層代碼
package com.heima.article.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.common.dtos.ResponseResult;
import java.io.IOException;
public interface ApArticleService extends IService<ApArticle> {
/**
* 根據(jù)參數(shù)加載文章列表
* @param loadtype 1為加載更多 2為加載最新
* @param dto
* @return
*/
ResponseResult load(Short loadtype, ArticleHomeDto dto);
}
實(shí)現(xiàn)類:
package com.heima.article.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.ApArticleService;
import com.heima.common.constants.ArticleConstants;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.common.dtos.ResponseResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
@Service
@Transactional
@Slf4j
public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements ApArticleService {
// 單頁最大加載的數(shù)字
private final static short MAX_PAGE_SIZE = 50;
@Autowired
private ApArticleMapper apArticleMapper;
/**
* 根據(jù)參數(shù)加載文章列表
* @param loadtype 1為加載更多 2為加載最新
* @param dto
* @return
*/
@Override
public ResponseResult load(Short loadtype, ArticleHomeDto dto) {
//1.校驗(yàn)參數(shù)
Integer size = dto.getSize();
if(size == null || size == 0){
size = 10;
}
size = Math.min(size,MAX_PAGE_SIZE);
dto.setSize(size);
//類型參數(shù)檢驗(yàn)
if(!loadtype.equals(ArticleConstants.LOADTYPE_LOAD_MORE)&&!loadtype.equals(ArticleConstants.LOADTYPE_LOAD_NEW)){
loadtype = ArticleConstants.LOADTYPE_LOAD_MORE;
}
//文章頻道校驗(yàn)
if(StringUtils.isEmpty(dto.getTag())){
dto.setTag(ArticleConstants.DEFAULT_TAG);
}
//時(shí)間校驗(yàn)
if(dto.getMaxBehotTime() == null) dto.setMaxBehotTime(new Date());
if(dto.getMinBehotTime() == null) dto.setMinBehotTime(new Date());
//2.查詢數(shù)據(jù)
List<ApArticle> apArticles = apArticleMapper.loadArticleList(dto, loadtype);
//3.結(jié)果封裝
ResponseResult responseResult = ResponseResult.okResult(apArticles);
return responseResult;
}
}
定義常量類
package com.heima.common.constants;
public class ArticleConstants {
public static final Short LOADTYPE_LOAD_MORE = 1;
public static final Short LOADTYPE_LOAD_NEW = 2;
public static final String DEFAULT_TAG = "__all__";
}
1.6.5):編寫控制器代碼
package com.heima.article.controller.v1;
import com.heima.article.service.ApArticleService;
import com.heima.common.constants.ArticleConstants;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.common.dtos.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/article")
public class ArticleHomeController {
@Autowired
private ApArticleService apArticleService;
@PostMapping("/load")
public ResponseResult load(@RequestBody ArticleHomeDto dto) {
return apArticleService.load(ArticleConstants.LOADTYPE_LOAD_MORE,dto);
}
@PostMapping("/loadmore")
public ResponseResult loadMore(@RequestBody ArticleHomeDto dto) {
return apArticleService.load(ArticleConstants.LOADTYPE_LOAD_MORE,dto);
}
@PostMapping("/loadnew")
public ResponseResult loadNew(@RequestBody ArticleHomeDto dto) {
return apArticleService.load(ArticleConstants.LOADTYPE_LOAD_NEW,dto);
}
}
1.6.6):swagger測試或前后端聯(lián)調(diào)測試
第一:在app網(wǎng)關(guān)的微服務(wù)的nacos的配置中心添加文章微服務(wù)的路由,完整配置如下:
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]': # 匹配所有請求
allowedOrigins: "*" #跨域處理 允許所有的域
allowedMethods: # 支持的方法
- GET
- POST
- PUT
- DELETE
routes:
# 用戶微服務(wù)
- id: user
uri: lb://leadnews-user
predicates:
- Path=/user/**
filters:
- StripPrefix= 1
# 文章微服務(wù)
- id: article
uri: lb://leadnews-article
predicates:
- Path=/article/**
filters:
- StripPrefix= 1
第二:啟動(dòng)nginx,直接使用前端項(xiàng)目測試,啟動(dòng)文章微服務(wù),用戶微服務(wù)、app網(wǎng)關(guān)微服務(wù)
2)freemarker
2.1) freemarker 介紹

FreeMarker 是一款 模板引擎: 即一種基于模板和要改變的數(shù)據(jù), 并用來生成輸出文本(HTML網(wǎng)頁,電子郵件,配置文件,源代碼等)的通用工具。 它不是面向最終用戶的,而是一個(gè)Java類庫,是一款程序員可以嵌入他們所開發(fā)產(chǎn)品的組件。
模板編寫為FreeMarker Template Language (FTL)。它是簡單的,專用的語言, 不是 像PHP那樣成熟的編程語言。 那就意味著要準(zhǔn)備數(shù)據(jù)在真實(shí)編程語言中來顯示,比如數(shù)據(jù)庫查詢和業(yè)務(wù)運(yùn)算, 之后模板顯示已經(jīng)準(zhǔn)備好的數(shù)據(jù)。在模板中,你可以專注于如何展現(xiàn)數(shù)據(jù), 而在模板之外可以專注于要展示什么數(shù)據(jù)。

常用的java模板引擎還有哪些?
Jsp、Freemarker、Thymeleaf 、Velocity 等。
1.Jsp 為 Servlet 專用,不能單獨(dú)進(jìn)行使用。 已淘汰
2.Thymeleaf 為新技術(shù),功能較為強(qiáng)大,但是執(zhí)行的效率比較低。
3.Velocity從2010年更新完 2.0 版本后,便沒有在更新。Spring Boot 官方在 1.4 版本后對此也不在支持,雖然 Velocity 在 2017 年版本得到迭代,但為時(shí)已晚。
2.2) 環(huán)境搭建&&快速入門
freemarker作為springmvc一種視圖格式,默認(rèn)情況下SpringMVC支持freemarker視圖格式。
需要?jiǎng)?chuàng)建Spring Boot+Freemarker工程用于測試模板。
2.2.1) 創(chuàng)建測試工程
創(chuàng)建一個(gè)freemarker-demo 的測試工程專門用于freemarker的功能測試與模板的測試。
pom.xml如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>heima-leadnews-test</artifactId>
<groupId>com.heima</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>freemarker-demo</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- apache 對 java io 的封裝工具庫 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
</project>
2.2.2) 配置文件
配置application.yml
server:
port: 8881 #服務(wù)端口
spring:
application:
name: freemarker-demo #指定服務(wù)名
freemarker:
cache: false #關(guān)閉模板緩存,方便測試
settings:
template_update_delay: 0 #檢查模板更新延遲時(shí)間,設(shè)置為0表示立即檢查,如果時(shí)間大于0會(huì)有緩存不方便進(jìn)行模板測試
suffix: .ftl #指定Freemarker模板文件的后綴名
2.2.3) 創(chuàng)建模型類
在freemarker的測試工程下創(chuàng)建模型類型用于測試
package com.heima.freemarker.entity;
import lombok.Data;
import java.util.Date;
@Data
public class Student {
private String name;//姓名
private int age;//年齡
private Date birthday;//生日
private Float money;//錢包
}
2.2.4) 創(chuàng)建模板
在resources下創(chuàng)建templates,此目錄為freemarker的默認(rèn)模板存放目錄。
在templates下創(chuàng)建模板文件 01-basic.ftl ,模板中的插值表達(dá)式最終會(huì)被freemarker替換成具體的數(shù)據(jù)。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World!</title>
</head>
<body>
<b>普通文本 String 展示:</b><br><br>
Hello ${name} <br>
<hr>
<b>對象Student中的數(shù)據(jù)展示:</b><br/>
姓名:${stu.name}<br/>
年齡:${stu.age}
<hr>
</body>
</html>
2.2.5) 創(chuàng)建controller
創(chuàng)建Controller類,向Map中添加name,最后返回模板文件。
package com.xuecheng.test.freemarker.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
@Controller
public class HelloController {
@GetMapping("/basic")
public String test(Model model) {
//1.純文本形式的參數(shù)
model.addAttribute("name", "freemarker");
//2.實(shí)體類相關(guān)的參數(shù)
Student student = new Student();
student.setName("小明");
student.setAge(18);
model.addAttribute("stu", student);
return "01-basic";
}
}
01-basic.ftl,使用插值表達(dá)式填充數(shù)據(jù)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World!</title>
</head>
<body>
<b>普通文本 String 展示:</b><br><br>
Hello ${name} <br>
<hr>
<b>對象Student中的數(shù)據(jù)展示:</b><br/>
姓名:${stu.name}<br/>
年齡:${stu.age}
<hr>
</body>
</html>
2.2.6) 創(chuàng)建啟動(dòng)類
package com.heima.freemarker;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class FreemarkerDemotApplication {
public static void main(String[] args) {
SpringApplication.run(FreemarkerDemotApplication.class,args);
}
}
2.2.7) 測試
請求:http://localhost:8881/basic

2.3) freemarker基礎(chǔ)
2.3.1) 基礎(chǔ)語法種類
1、注釋,即<#-- -->,介于其之間的內(nèi)容會(huì)被freemarker忽略
<#--我是一個(gè)freemarker注釋-->
2、插值(Interpolation):即 ${..} 部分,freemarker會(huì)用真實(shí)的值代替**${..}**
Hello ${name}
3、FTL指令:和HTML標(biāo)記類似,名字前加#予以區(qū)分,F(xiàn)reemarker會(huì)解析標(biāo)簽中的表達(dá)式或邏輯。
<# >FTL指令</#>
4、文本,僅文本信息,這些不是freemarker的注釋、插值、FTL指令的內(nèi)容會(huì)被freemarker忽略解析,直接輸出內(nèi)容。
<#--freemarker中的普通文本-->
我是一個(gè)普通的文本
2.3.2) 集合指令(List和Map)
1、數(shù)據(jù)模型:
在HelloController中新增如下方法:
@GetMapping("/list")
public String list(Model model){
//------------------------------------
Student stu1 = new Student();
stu1.setName("小強(qiáng)");
stu1.setAge(18);
stu1.setMoney(1000.86f);
stu1.setBirthday(new Date());
//小紅對象模型數(shù)據(jù)
Student stu2 = new Student();
stu2.setName("小紅");
stu2.setMoney(200.1f);
stu2.setAge(19);
//將兩個(gè)對象模型數(shù)據(jù)存放到List集合中
List<Student> stus = new ArrayList<>();
stus.add(stu1);
stus.add(stu2);
//向model中存放List集合數(shù)據(jù)
model.addAttribute("stus",stus);
//------------------------------------
//創(chuàng)建Map數(shù)據(jù)
HashMap<String,Student> stuMap = new HashMap<>();
stuMap.put("stu1",stu1);
stuMap.put("stu2",stu2);
// 3.1 向model中存放Map數(shù)據(jù)
model.addAttribute("stuMap", stuMap);
return "02-list";
}
2、模板:
在templates中新增02-list.ftl文件
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World!</title>
</head>
<body>
<#-- list 數(shù)據(jù)的展示 -->
<b>展示list中的stu數(shù)據(jù):</b>
<br>
<br>
<table>
<tr>
<td>序號</td>
<td>姓名</td>
<td>年齡</td>
<td>錢包</td>
</tr>
</table>
<hr>
<#-- Map 數(shù)據(jù)的展示 -->
<b>map數(shù)據(jù)的展示:</b>
<br/><br/>
<a href="###">方式一:通過map['keyname'].property</a><br/>
輸出stu1的學(xué)生信息:<br/>
姓名:<br/>
年齡:<br/>
<br/>
<a href="###">方式二:通過map.keyname.property</a><br/>
輸出stu2的學(xué)生信息:<br/>
姓名:<br/>
年齡:<br/>
<br/>
<a href="###">遍歷map中兩個(gè)學(xué)生信息:</a><br/>
<table>
<tr>
<td>序號</td>
<td>姓名</td>
<td>年齡</td>
<td>錢包</td>
</tr>
</table>
<hr>
</body>
</html>
實(shí)例代碼:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World!</title>
</head>
<body>
<#-- list 數(shù)據(jù)的展示 -->
<b>展示list中的stu數(shù)據(jù):</b>
<br>
<br>
<table>
<tr>
<td>序號</td>
<td>姓名</td>
<td>年齡</td>
<td>錢包</td>
</tr>
<#list stus as stu>
<tr>
<td>${stu_index+1}</td>
<td>${stu.name}</td>
<td>${stu.age}</td>
<td>${stu.money}</td>
</tr>
</#list>
</table>
<hr>
<#-- Map 數(shù)據(jù)的展示 -->
<b>map數(shù)據(jù)的展示:</b>
<br/><br/>
<a href="###">方式一:通過map['keyname'].property</a><br/>
輸出stu1的學(xué)生信息:<br/>
姓名:${stuMap['stu1'].name}<br/>
年齡:${stuMap['stu1'].age}<br/>
<br/>
<a href="###">方式二:通過map.keyname.property</a><br/>
輸出stu2的學(xué)生信息:<br/>
姓名:${stuMap.stu2.name}<br/>
年齡:${stuMap.stu2.age}<br/>
<br/>
<a href="###">遍歷map中兩個(gè)學(xué)生信息:</a><br/>
<table>
<tr>
<td>序號</td>
<td>姓名</td>
<td>年齡</td>
<td>錢包</td>
</tr>
<#list stuMap?keys as key >
<tr>
<td>${key_index}</td>
<td>${stuMap[key].name}</td>
<td>${stuMap[key].age}</td>
<td>${stuMap[key].money}</td>
</tr>
</#list>
</table>
<hr>
</body>
</html>
??上面代碼解釋:
${k_index}:
index:得到循環(huán)的下標(biāo),使用方法是在stu后邊加"_index",它的值是從0開始
2.3.3) if指令
if 指令即判斷指令,是常用的FTL指令,freemarker在解析時(shí)遇到if會(huì)進(jìn)行判斷,條件為真則輸出if中間的內(nèi)容,否則跳過內(nèi)容不再輸出。
-
指令格式
<#if ></if>
1、數(shù)據(jù)模型:
使用list指令中測試數(shù)據(jù)模型,判斷名稱為小紅的數(shù)據(jù)字體顯示為紅色。
2、模板:
<table>
<tr>
<td>姓名</td>
<td>年齡</td>
<td>錢包</td>
</tr>
<#list stus as stu>
<tr>
<td >${stu.name}</td>
<td>${stu.age}</td>
<td >${stu.mondy}</td>
</tr>
</#list>
</table>
實(shí)例代碼:
<table>
<tr>
<td>姓名</td>
<td>年齡</td>
<td>錢包</td>
</tr>
<#list stus as stu >
<#if stu.name='小紅'>
<tr style="color: red">
<td>${stu_index}</td>
<td>${stu.name}</td>
<td>${stu.age}</td>
<td>${stu.money}</td>
</tr>
<#else >
<tr>
<td>${stu_index}</td>
<td>${stu.name}</td>
<td>${stu.age}</td>
<td>${stu.money}</td>
</tr>
</#if>
</#list>
</table>
3、輸出:
姓名為“小強(qiáng)”則字體顏色顯示為紅色。

2.3.4) 運(yùn)算符
1、算數(shù)運(yùn)算符
FreeMarker表達(dá)式中完全支持算術(shù)運(yùn)算,FreeMarker支持的算術(shù)運(yùn)算符包括:
-
加法: +
-
減法: -
-
乘法: *
-
除法: /
-
求模 (求余): %
模板代碼
<b>算數(shù)運(yùn)算符</b>
<br/><br/>
100+5 運(yùn)算: ${100 + 5 }<br/>
100 - 5 * 5運(yùn)算:${100 - 5 * 5}<br/>
5 / 2運(yùn)算:${5 / 2}<br/>
12 % 10運(yùn)算:${12 % 10}<br/>
<hr>
除了 + 運(yùn)算以外,其他的運(yùn)算只能和 number 數(shù)字類型的計(jì)算。
2、比較運(yùn)算符
-
=或者==:判斷兩個(gè)值是否相等.
-
!=:判斷兩個(gè)值是否不等.
-
>或者gt:判斷左邊值是否大于右邊值
-
>=或者gte:判斷左邊值是否大于等于右邊值
-
<或者lt:判斷左邊值是否小于右邊值
-
<=或者lte:判斷左邊值是否小于等于右邊值
= 和 == 模板代碼
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World!</title>
</head>
<body>
<b>比較運(yùn)算符</b>
<br/>
<br/>
<dl>
<dt> =/== 和 != 比較:</dt>
<dd>
<#if "xiaoming" == "xiaoming">
字符串的比較 "xiaoming" == "xiaoming"
</#if>
</dd>
<dd>
<#if 10 != 100>
數(shù)值的比較 10 != 100
</#if>
</dd>
</dl>
<dl>
<dt>其他比較</dt>
<dd>
<#if 10 gt 5 >
形式一:使用特殊字符比較數(shù)值 10 gt 5
</#if>
</dd>
<dd>
<#-- 日期的比較需要通過?date將屬性轉(zhuǎn)為data類型才能進(jìn)行比較 -->
<#if (date1?date >= date2?date)>
形式二:使用括號形式比較時(shí)間 date1?date >= date2?date
</#if>
</dd>
</dl>
<br/>
<hr>
</body>
</html>
Controller 的 數(shù)據(jù)模型代碼
@GetMapping("operation")
public String testOperation(Model model) {
//構(gòu)建 Date 數(shù)據(jù)
Date now = new Date();
model.addAttribute("date1", now);
model.addAttribute("date2", now);
return "03-operation";
}
比較運(yùn)算符注意
-
=和!=可以用于字符串、數(shù)值和日期來比較是否相等
-
=和!=兩邊必須是相同類型的值,否則會(huì)產(chǎn)生錯(cuò)誤
-
字符串 "x" 、"x " 、"X"比較是不等的.因?yàn)镕reeMarker是精確比較
-
其它的運(yùn)行符可以作用于數(shù)字和日期,但不能作用于字符串
-
使用gt等字母運(yùn)算符代替>會(huì)有更好的效果,因?yàn)?FreeMarker會(huì)把> 解釋成FTL標(biāo)簽的結(jié)束字符
-
可以使用括號來避免這種情況,如:<#if (x>y)>
3、邏輯運(yùn)算符
-
邏輯與:&&
-
邏輯或:||
-
邏輯非:!
邏輯運(yùn)算符只能作用于布爾值,否則將產(chǎn)生錯(cuò)誤 。
模板代碼
<b>邏輯運(yùn)算符</b>
<br/>
<br/>
<#if (10 lt 12 )&&( 10 gt 5 ) >
(10 lt 12 )&&( 10 gt 5 ) 顯示為 true
</#if>
<br/>
<br/>
<#if !false>
false 取反為true
</#if>
<hr>
2.3.5) 空值處理
1、判斷某變量是否存在使用 “??”
用法為:variable??,如果該變量存在,返回true,否則返回false
例:為防止stus為空報(bào)錯(cuò)可以加上判斷如下:
<#if stus??>
<#list stus as stu>
......
</#list>
</#if>
2、缺失變量默認(rèn)值使用 “!”
-
使用!要以指定一個(gè)默認(rèn)值,當(dāng)變量為空時(shí)顯示默認(rèn)值
例: ${name!''}表示如果name為空顯示空字符串。
-
如果是嵌套對象則建議使用()括起來
例: ${(stu.bestFriend.name)!''}表示,如果stu或bestFriend或name為空默認(rèn)顯示空字符串。
2.3.6) 內(nèi)建函數(shù)
內(nèi)建函數(shù)語法格式: 變量+?+函數(shù)名稱
1、和到某個(gè)集合的大小
${集合名?size}
2、日期格式化
顯示年月日: ${today?date}
顯示時(shí)分秒:${today?time}
顯示日期+時(shí)間:${today?datetime}
自定義格式化: ${today?string("yyyy年MM月")}
3、內(nèi)建函數(shù)c
model.addAttribute("point", 102920122);
point是數(shù)字型,使用${point}會(huì)顯示這個(gè)數(shù)字的值,每三位使用逗號分隔。
如果不想顯示為每三位分隔的數(shù)字,可以使用c函數(shù)將數(shù)字型轉(zhuǎn)成字符串輸出
${point?c}
4、將json字符串轉(zhuǎn)成對象
一個(gè)例子:
其中用到了 assign標(biāo)簽,assign的作用是定義一個(gè)變量。
<#assign text="{'bank':'工商銀行','account':'10101920201920212'}" />
<#assign data=text?eval />
開戶行:${data.bank} 賬號:${data.account}
模板代碼:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>inner Function</title>
</head>
<body>
<b>獲得集合大小</b><br>
集合大小:
<hr>
<b>獲得日期</b><br>
顯示年月日: <br>
顯示時(shí)分秒:<br>
顯示日期+時(shí)間:<br>
自定義格式化: <br>
<hr>
<b>內(nèi)建函數(shù)C</b><br>
沒有C函數(shù)顯示的數(shù)值: <br>
有C函數(shù)顯示的數(shù)值:
<hr>
<b>聲明變量assign</b><br>
<hr>
</body>
</html>
內(nèi)建函數(shù)模板頁面:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>inner Function</title>
</head>
<body>
<b>獲得集合大小</b><br>
集合大?。?{stus?size}
<hr>
<b>獲得日期</b><br>
顯示年月日: ${today?date} <br>
顯示時(shí)分秒:${today?time}<br>
顯示日期+時(shí)間:${today?datetime}<br>
自定義格式化: ${today?string("yyyy年MM月")}<br>
<hr>
<b>內(nèi)建函數(shù)C</b><br>
沒有C函數(shù)顯示的數(shù)值:${point} <br>
有C函數(shù)顯示的數(shù)值:${point?c}
<hr>
<b>聲明變量assign</b><br>
<#assign text="{'bank':'工商銀行','account':'10101920201920212'}" />
<#assign data=text?eval />
開戶行:${data.bank} 賬號:${data.account}
<hr>
</body>
</html>
內(nèi)建函數(shù)Controller數(shù)據(jù)模型:
@GetMapping("innerFunc")
public String testInnerFunc(Model model) {
//1.1 小強(qiáng)對象模型數(shù)據(jù)
Student stu1 = new Student();
stu1.setName("小強(qiáng)");
stu1.setAge(18);
stu1.setMoney(1000.86f);
stu1.setBirthday(new Date());
//1.2 小紅對象模型數(shù)據(jù)
Student stu2 = new Student();
stu2.setName("小紅");
stu2.setMoney(200.1f);
stu2.setAge(19);
//1.3 將兩個(gè)對象模型數(shù)據(jù)存放到List集合中
List<Student> stus = new ArrayList<>();
stus.add(stu1);
stus.add(stu2);
model.addAttribute("stus", stus);
// 2.1 添加日期
Date date = new Date();
model.addAttribute("today", date);
// 3.1 添加數(shù)值
model.addAttribute("point", 102920122);
return "04-innerFunc";
}
2.4) 靜態(tài)化測試
之前的測試都是SpringMVC將Freemarker作為視圖解析器(ViewReporter)來集成到項(xiàng)目中,工作中,有的時(shí)候需要使用Freemarker原生Api來生成靜態(tài)內(nèi)容,下面一起來學(xué)習(xí)下原生Api生成文本文件
2.4.1) 需求分析
使用freemarker原生Api將頁面生成html文件,本節(jié)測試html文件生成的方法:

2.4.2) 靜態(tài)化測試
根據(jù)模板文件生成html文件
①:修改application.yml文件,添加以下模板存放位置的配置信息,完整配置如下:
server:
port: 8881 #服務(wù)端口
spring:
application:
name: freemarker-demo #指定服務(wù)名
freemarker:
cache: false #關(guān)閉模板緩存,方便測試
settings:
template_update_delay: 0 #檢查模板更新延遲時(shí)間,設(shè)置為0表示立即檢查,如果時(shí)間大于0會(huì)有緩存不方便進(jìn)行模板測試
suffix: .ftl #指定Freemarker模板文件的后綴名
template-loader-path: classpath:/templates #模板存放位置
②:在test下創(chuàng)建測試類
package com.heima.freemarker.test;
import com.heima.freemarker.FreemarkerDemoApplication;
import com.heima.freemarker.entity.Student;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;
@SpringBootTest(classes = FreemarkerDemoApplication.class)
@RunWith(SpringRunner.class)
public class FreemarkerTest {
@Autowired
private Configuration configuration;
@Test
public void test() throws IOException, TemplateException {
//freemarker的模板對象,獲取模板
Template template = configuration.getTemplate("02-list.ftl");
Map params = getData();
//合成
//第一個(gè)參數(shù) 數(shù)據(jù)模型
//第二個(gè)參數(shù) 輸出流
template.process(params, new FileWriter("d:/list.html"));
}
private Map getData() {
Map<String, Object> map = new HashMap<>();
//小強(qiáng)對象模型數(shù)據(jù)
Student stu1 = new Student();
stu1.setName("小強(qiáng)");
stu1.setAge(18);
stu1.setMoney(1000.86f);
stu1.setBirthday(new Date());
//小紅對象模型數(shù)據(jù)
Student stu2 = new Student();
stu2.setName("小紅");
stu2.setMoney(200.1f);
stu2.setAge(19);
//將兩個(gè)對象模型數(shù)據(jù)存放到List集合中
List<Student> stus = new ArrayList<>();
stus.add(stu1);
stus.add(stu2);
//向map中存放List集合數(shù)據(jù)
map.put("stus", stus);
//創(chuàng)建Map數(shù)據(jù)
HashMap<String, Student> stuMap = new HashMap<>();
stuMap.put("stu1", stu1);
stuMap.put("stu2", stu2);
//向map中存放Map數(shù)據(jù)
map.put("stuMap", stuMap);
//返回Map
return map;
}
}
3) 對象存儲服務(wù)MinIO


3.1 MinIO簡介
MinIO基于Apache License v2.0開源協(xié)議的對象存儲服務(wù),可以做為云存儲的解決方案用來保存海量的圖片,視頻,文檔。由于采用Golang實(shí)現(xiàn),服務(wù)端可以工作在Windows,Linux, OS X和FreeBSD上。配置簡單,基本是復(fù)制可執(zhí)行程序,單行命令可以運(yùn)行起來。
MinIO兼容亞馬遜S3云存儲服務(wù)接口,非常適合于存儲大容量非結(jié)構(gòu)化的數(shù)據(jù),例如圖片、視頻、日志文件、備份數(shù)據(jù)和容器/虛擬機(jī)鏡像等,而一個(gè)對象文件可以是任意大小,從幾kb到最大5T不等。
S3 ( Simple Storage Service簡單存儲服務(wù))
基本概念
-
bucket – 類比于文件系統(tǒng)的目錄
-
Object – 類比文件系統(tǒng)的文件
-
Keys – 類比文件名
官網(wǎng)文檔:MinIO Object Storage for Kubernetes — MinIO Object Storage for Kubernetes
3.2 MinIO特點(diǎn)
-
數(shù)據(jù)保護(hù)
Minio使用Minio Erasure Code(糾刪碼)來防止硬件故障。即便損壞一半以上的driver,但是仍然可以從中恢復(fù)。
-
高性能
作為高性能對象存儲,在標(biāo)準(zhǔn)硬件條件下它能達(dá)到55GB/s的讀、35GB/s的寫速率
-
可擴(kuò)容
不同MinIO集群可以組成聯(lián)邦,并形成一個(gè)全局的命名空間,并跨越多個(gè)數(shù)據(jù)中心
-
SDK支持
基于Minio輕量的特點(diǎn),它得到類似Java、Python或Go等語言的sdk支持
-
有操作頁面
面向用戶友好的簡單操作界面,非常方便的管理Bucket及里面的文件資源
-
功能簡單
這一設(shè)計(jì)原則讓MinIO不容易出錯(cuò)、更快啟動(dòng)
-
豐富的API
支持文件資源的分享連接及分享鏈接的過期策略、存儲桶操作、文件列表訪問及文件上傳下載的基本功能等
-
文件變化主動(dòng)通知
存儲桶(Bucket)如果發(fā)生改變,比如上傳對象和刪除對象,可以使用存儲桶事件通知機(jī)制進(jìn)行監(jiān)控,并通過以下方式發(fā)布出去:AMQP、MQTT、Elasticsearch、Redis、NATS、MySQL、Kafka、Webhooks等。
3.3 開箱使用
3.3.1 安裝啟動(dòng)
我們提供的鏡像中已經(jīng)有minio的環(huán)境; 我們可以使用docker進(jìn)行環(huán)境部署和啟動(dòng)
docker run -p 9000:9000 --name minio -d --restart=always -e "MINIO_ACCESS_KEY=minio" -e "MINIO_SECRET_KEY=minio123" -v /home/data:/data -v /home/config:/root/.minio minio/minio server /data
3.3.2 管理控制臺
假設(shè)我們的服務(wù)器地址為http://192.168.200.130:9000,我們在地址欄輸入:http://192.168.200.130:9000/ 即可進(jìn)入登錄界面。

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

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

3.4 快速入門
3.4.1 創(chuàng)建工程,導(dǎo)入pom依賴
創(chuàng)建minio-demo,對應(yīng)pom如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>heima-leadnews-test</artifactId>
<groupId>com.heima</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>minio-demo</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>7.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
</project>
引導(dǎo)類:
package com.heima.minio;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MinIOApplication {
public static void main(String[] args) {
SpringApplication.run(MinIOApplication.class,args);
}
}
創(chuàng)建測試類,上傳html文件
package com.heima.minio.test;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import java.io.FileInputStream;
public class MinIOTest {
public static void main(String[] args) {
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\yuhon\\Downloads\\index.js");
try {
fileInputStream = new FileInputStream("D:\\list.html");;
//1.創(chuàng)建minio鏈接客戶端
MinioClient minioClient = MinioClient.builder().credentials("minio", "minio123").endpoint("http://192.168.200.130:9000").build();
//2.上傳 對象
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.object("list.html")//文件名
.contentType("text/html")//文件類型
.bucket("leadnews")//桶名詞 與minio創(chuàng)建的桶名稱 一致
.stream(fileInputStream, fileInputStream.available(), -1) //文件流(流stream,大小,傳到哪)
//fileInputStream.available()代表有值就一直傳遞;-1代表傳完所有文件
.build();
minioClient.putObject(putObjectArgs);
? ?? ? //上傳完成
? ? ? ? //訪問
System.out.println("http://192.168.200.130:9000/leadnews/ak47.jpg");
} catch (Exception ex) {
ex.printStackTrace();
}
}
}

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


3.5.1 創(chuàng)建模塊heima-file-starter
導(dǎo)入依賴
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>7.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
3.5.2 配置類
MinIOConfigProperties
package com.heima.file.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.io.Serializable;
@Data
@ConfigurationProperties(prefix = "minio") // 文件上傳 配置前綴file.oss
public class MinIOConfigProperties implements Serializable {
private String accessKey;
private String secretKey;
private String bucket;
private String endpoint;
private String readPath;
}
MinIOConfig
package com.heima.file.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@EnableConfigurationProperties({MinIOConfigProperties.class})
//當(dāng)引入FileStorageService接口時(shí)
@ConditionalOnClass(FileStorageService.class)
public class MinIOConfig {
@Autowired
private MinIOConfigProperties minIOConfigProperties;
@Bean
public MinioClient buildMinioClient(){
return MinioClient
.builder()
.credentials(minIOConfigProperties.getAccessKey(), minIOConfigProperties.getSecretKey())
.endpoint(minIOConfigProperties.getEndpoint())
.build();
}
}
3.5.3 封裝操作minIO類
FileStorageService
package com.heima.file.service;
import java.io.InputStream;
/**
* @author itheima
*/
public interface FileStorageService {
/**
* 上傳圖片文件
* @param prefix 文件前綴
* @param filename 文件名
* @param inputStream 文件流
* @return 文件全路徑
*/
public String uploadImgFile(String prefix, String filename,InputStream inputStream);
/**
* 上傳html文件
* @param prefix 文件前綴
* @param filename 文件名
* @param inputStream 文件流
* @return 文件全路徑
*/
public String uploadHtmlFile(String prefix, String filename,InputStream inputStream);
/**
* 刪除文件
* @param pathUrl 文件全路徑
*/
public void delete(String pathUrl);
/**
* 下載文件
* @param pathUrl 文件全路徑
* @return
*
*/
public byte[] downLoadFile(String pathUrl);
}
MinIOFileStorageService
package com.heima.file.service.impl;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
@Slf4j
@EnableConfigurationProperties(MinIOConfigProperties.class)
@Import(MinIOConfig.class)
public class MinIOFileStorageService implements FileStorageService {
@Autowired
private MinioClient minioClient;
@Autowired
private MinIOConfigProperties minIOConfigProperties;
private final static String separator = "/";
/**
* @param dirPath
* @param filename yyyy/mm/dd/file.jpg
* @return
*/
public String builderFilePath(String dirPath,String filename) {
StringBuilder stringBuilder = new StringBuilder(50);
if(!StringUtils.isEmpty(dirPath)){
stringBuilder.append(dirPath).append(separator);
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
String todayStr = sdf.format(new Date());
stringBuilder.append(todayStr).append(separator);
stringBuilder.append(filename);
return stringBuilder.toString();
}
/**
* 上傳圖片文件
* @param prefix 文件前綴
* @param filename 文件名
* @param inputStream 文件流
* @return 文件全路徑
*/
@Override
public String uploadImgFile(String prefix, String filename,InputStream inputStream) {
String filePath = builderFilePath(prefix, filename);
try {
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.object(filePath)
.contentType("image/jpg")
.bucket(minIOConfigProperties.getBucket()).stream(inputStream,inputStream.available(),-1)
.build();
minioClient.putObject(putObjectArgs);
StringBuilder urlPath = new StringBuilder(minIOConfigProperties.getReadPath());
urlPath.append(separator+minIOConfigProperties.getBucket());
urlPath.append(separator);
urlPath.append(filePath);
return urlPath.toString();
}catch (Exception ex){
log.error("minio put file error.",ex);
throw new RuntimeException("上傳文件失敗");
}
}
/**
* 上傳html文件
* @param prefix 文件前綴
* @param filename 文件名
* @param inputStream 文件流
* @return 文件全路徑
*/
@Override
public String uploadHtmlFile(String prefix, String filename,InputStream inputStream) {
String filePath = builderFilePath(prefix, filename);
try {
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.object(filePath)
.contentType("text/html")
.bucket(minIOConfigProperties.getBucket()).stream(inputStream,inputStream.available(),-1)
.build();
minioClient.putObject(putObjectArgs);
StringBuilder urlPath = new StringBuilder(minIOConfigProperties.getReadPath());
urlPath.append(separator+minIOConfigProperties.getBucket());
urlPath.append(separator);
urlPath.append(filePath);
return urlPath.toString();
}catch (Exception ex){
log.error("minio put file error.",ex);
ex.printStackTrace();
throw new RuntimeException("上傳文件失敗");
}
}
/**
* 刪除文件
* @param pathUrl 文件全路徑
*/
@Override
public void delete(String pathUrl) {
String key = pathUrl.replace(minIOConfigProperties.getEndpoint()+"/","");
int index = key.indexOf(separator);
String bucket = key.substring(0,index);
String filePath = key.substring(index+1);
// 刪除Objects
RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket(bucket).object(filePath).build();
try {
minioClient.removeObject(removeObjectArgs);
} catch (Exception e) {
log.error("minio remove file error. pathUrl:{}",pathUrl);
e.printStackTrace();
}
}
/**
* 下載文件
* @param pathUrl 文件全路徑
* @return 文件流
*
*/
@Override
public byte[] downLoadFile(String pathUrl) {
String key = pathUrl.replace(minIOConfigProperties.getEndpoint()+"/","");
int index = key.indexOf(separator);
String bucket = key.substring(0,index);
String filePath = key.substring(index+1);
InputStream inputStream = null;
try {
inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(minIOConfigProperties.getBucket()).object(filePath).build());
} catch (Exception e) {
log.error("minio down file error. pathUrl:{}",pathUrl);
e.printStackTrace();
}
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buff = new byte[100];
int rc = 0;
while (true) {
try {
if (!((rc = inputStream.read(buff, 0, 100)) > 0)) break;
} catch (IOException e) {
e.printStackTrace();
}
byteArrayOutputStream.write(buff, 0, rc);
}
return byteArrayOutputStream.toByteArray();
}
}
3.5.4 對外加入自動(dòng)配置
在resources中新建META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.heima.file.service.impl.MinIOFileStorageService
3.5.5 其他微服務(wù)使用
第一,導(dǎo)入heima-file-starter的依賴
第二,在微服務(wù)中添加minio所需要的配置
minio:
accessKey: minio
secretKey: minio123
bucket: leadnews
endpoint: http://192.168.200.130:9000
readPath: http://192.168.200.130:9000
第三,在對應(yīng)使用的業(yè)務(wù)類中注入FileStorageService,樣例如下:
package com.heima.minio.test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@SpringBootTest(classes = MinioApplication.class)
@RunWith(SpringRunner.class)
public class MinioTest {
@Autowired
private FileStorageService fileStorageService;
@Test
public void testUpdateImgFile() {
try {
FileInputStream fileInputStream = new FileInputStream("E:\\tmp\\ak47.jpg");
String filePath = fileStorageService.uploadImgFile("", "ak47.jpg", fileInputStream);
System.out.println(filePath);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
4)文章詳情
4.1)需求分析

4.2)實(shí)現(xiàn)方案
方案一
用戶某一條文章,根據(jù)文章的id去查詢文章內(nèi)容表,返回渲染頁面

方案二 效率高

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

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


?allowPublicKeyRetrieval=true 成功解決;

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


4.在文章微服務(wù)中導(dǎo)入依賴
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>com.heima</groupId>
<artifactId>heima-file-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
5.新建ApArticleContentMapper
package com.heima.article.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.article.pojos.ApArticleContent;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ApArticleContentMapper extends BaseMapper<ApArticleContent> {
}
6.在artile微服務(wù)中新增測試類(后期新增文章的時(shí)候創(chuàng)建詳情靜態(tài)頁,目前暫時(shí)手動(dòng)生成)
package com.heima.article.test;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
@SpringBootTest(classes = ArticleApplication.class)
@RunWith(SpringRunner.class)
public class ArticleFreemarkerTest {
@Autowired
private Configuration configuration;
@Autowired
private FileStorageService fileStorageService;
@Autowired
private ApArticleMapper apArticleMapper;
@Autowired
private ApArticleContentMapper apArticleContentMapper;
@Test
public void createStaticUrlTest() throws Exception {
//1.獲取文章內(nèi)容
ApArticleContent apArticleContent = apArticleContentMapper.selectOne(Wrappers.<ApArticleContent>lambdaQuery().eq(ApArticleContent::getArticleId, 1390536764510310401L));
if(apArticleContent != null && StringUtils.isNotBlank(apArticleContent.getContent())){
//2.文章內(nèi)容通過freemarker生成html文件
StringWriter out = new StringWriter();
Template template = configuration.getTemplate("article.ftl");
//第一個(gè)參數(shù) 數(shù)據(jù)模型
Map<String, Object> params = new HashMap<>();
//JSONArray.parseArray 字符串轉(zhuǎn)成對象
params.put("content", JSONArray.parseArray(apArticleContent.getContent()));
//合成
template.process(params, out);
//輸入流
InputStream is = new ByteArrayInputStream(out.toString().getBytes());
//3.把html文件上傳到minio中 (前綴,文件名稱,輸入流)
String path = fileStorageService.uploadHtmlFile("",
apArticleContent.getArticleId() + ".html", is);
//4.修改ap_article表,保存static_url字段
ApArticle article = new ApArticle();
article.setId(apArticleContent.getArticleId());
article.setStaticUrl(path);
apArticleMapper.updateById(article);
}
}
}
大佬彈幕:人家現(xiàn)在發(fā)布文章,都是直接用富文本 編輯器,發(fā)布后數(shù)據(jù)庫存的是富文本,然后直接把富文本丟給前端人家就可以直接顯示了,搞這么麻煩干嘛,

手機(jī)端適宜的屏幕大小

03自媒體文章發(fā)布
1)自媒體前后端搭建
1.1)后臺搭建

①:資料中找到heima-leadnews-wemedia.zip解壓
拷貝到heima-leadnews-service工程下,并指定子模塊
執(zhí)行l(wèi)eadnews-wemedia.sql腳本
添加對應(yīng)的nacos配置
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/leadnews_wemedia?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: root
# 設(shè)置Mapper接口所對應(yīng)的XML文件位置,如果你在Mapper接口中有自定義方法,需要進(jìn)行該配置
mybatis-plus:
mapper-locations: classpath*:mapper/*.xml
# 設(shè)置別名包掃描路徑,通過該屬性可以給包中的類注冊別名
type-aliases-package: com.heima.model.media.pojos
②:資料中找到heima-leadnews-wemedia-gateway.zip解壓
拷貝到heima-leadnews-gateway工程下,并指定子模塊
添加對應(yīng)的nacos配置
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]': # 匹配所有請求
allowedOrigins: "*" #跨域處理 允許所有的域
allowedMethods: # 支持的方法
- GET
- POST
- PUT
- DELETE
routes:
# 平臺管理
- id: wemedia
uri: lb://leadnews-wemedia
predicates:
- Path=/wemedia/**
filters:
- StripPrefix= 1
③:在資料中找到類文件夾
拷貝wemedia文件夾到heima-leadnews-model模塊下的com.heima.model
1.2)前臺搭建

通過nginx的虛擬主機(jī)功能,使用同一個(gè)nginx訪問多個(gè)項(xiàng)目
搭建步驟:
①:資料中找到wemedia-web.zip解壓
②:在nginx中l(wèi)eadnews.conf目錄中新增heima-leadnews-wemedia.conf文件
-
網(wǎng)關(guān)地址修改(localhost:51602)
-
前端項(xiàng)目目錄修改(wemedia-web解壓的目錄)
-
訪問端口修改(8802)
upstream heima-wemedia-gateway{
server localhost:51602;
}
server {
listen 8802;
location / {
root D:/workspace/wemedia-web/;
index index.html;
}
location ~/wemedia/MEDIA/(.*) {
proxy_pass http://heima-wemedia-gateway/$1;
proxy_set_header HOST $host; # 不改變源請求頭的值
proxy_pass_request_body on; #開啟獲取請求體
proxy_pass_request_headers on; #開啟獲取請求頭
proxy_set_header X-Real-IP $remote_addr; # 記錄真實(shí)發(fā)出請求的客戶端IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; #記錄代理信息
}
}
③:啟動(dòng)nginx,啟動(dòng)自媒體微服務(wù)和對應(yīng)網(wǎng)關(guān)
④:聯(lián)調(diào)測試登錄功能 測試成功

2)自媒體素材管理
2.1)素材上傳
2.2.1)需求分析

圖片上傳的頁面,首先是展示素材信息,可以點(diǎn)擊圖片上傳,彈窗后可以上傳圖片
2.2.2)素材管理-圖片上傳-表結(jié)構(gòu)
媒體圖文素材信息表wm_material

對應(yīng)實(shí)體類:
package com.heima.model.wemedia.pojos;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* <p>
* 自媒體圖文素材信息表
* </p>
*
* @author itheima
*/
@Data
@TableName("wm_material")
public class WmMaterial implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主鍵
*/
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 自媒體用戶ID
*/
@TableField("user_id")
private Integer userId;
/**
* 圖片地址
*/
@TableField("url")
private String url;
/**
* 素材類型
0 圖片
1 視頻
*/
@TableField("type")
private Short type;
/**
* 是否收藏
*/
@TableField("is_collection")
private Short isCollection;
/**
* 創(chuàng)建時(shí)間
*/
@TableField("created_time")
private Date createdTime;
}
2.2.3)實(shí)現(xiàn)思路

①:前端發(fā)送上傳圖片請求,類型為MultipartFile
②:網(wǎng)關(guān)進(jìn)行token解析后,把解析后的用戶信息存儲到header中
//獲得token解析后中的用戶信息
Object userId = claimsBody.get("id");
//在header中添加新的信息
ServerHttpRequest serverHttpRequest = request.mutate().headers(httpHeaders -> {
httpHeaders.add("userId", userId + "");
}).build();
//重置header
exchange.mutate().request(serverHttpRequest).build();
③:自媒體微服務(wù)使用攔截器獲取到header中的的用戶信息,并放入到threadlocal中
在heima-leadnews-utils中新增工具類
注意:需要從資料中找出WmUser實(shí)體類拷貝到model工程下
package com.heima.utils.thread;
import com.heima.model.wemedia.pojos.WmUser;
public class WmThreadLocalUtil {
private final static ThreadLocal<WmUser> WM_USER_THREAD_LOCAL = new ThreadLocal<>();
/**
* 添加用戶
* @param wmUser
*/
public static void setUser(WmUser wmUser){
WM_USER_THREAD_LOCAL.set(wmUser);
}
/**
* 獲取用戶
*/
public static WmUser getUser(){
return WM_USER_THREAD_LOCAL.get();
}
/**
* 清理用戶
*/
public static void clear(){
WM_USER_THREAD_LOCAL.remove();
}
}
在heima-leadnews-wemedia中新增攔截器
package com.heima.wemedia.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Optional;
@Slf4j
public class WmTokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//得到header中的信息
String userId = request.getHeader("userId");
Optional<String> optional = Optional.ofNullable(userId);
if(optional.isPresent()){
//把用戶id存入threadloacl中
WmUser wmUser = new WmUser();
wmUser.setId(Integer.valueOf(userId));
WmThreadLocalUtils.setUser(wmUser);
log.info("wmTokenFilter設(shè)置用戶信息到threadlocal中...");
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("清理threadlocal...");
WmThreadLocalUtils.clear();
}
}
配置使攔截器生效,攔截所有的請求
package com.heima.wemedia.config;
import com.heima.wemedia.interceptor.WmTokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new WmTokenInterceptor()).addPathPatterns("/**");
}
}
④:先把圖片上傳到minIO中,獲取到圖片請求的路徑——(2.2.5查看具體功能實(shí)現(xiàn))
⑤:把用戶id和圖片上的路徑保存到素材表中——(2.2.5查看具體功能實(shí)現(xiàn))
2.2.4)接口定義
說明 |
|
接口路徑 |
/api/v1/material/upload_picture |
請求方式 |
POST |
參數(shù) |
MultipartFile |
響應(yīng)結(jié)果 |
ResponseResult |
MultipartFile :Springmvc指定的文件接收類型
ResponseResult :
成功需要回顯圖片,返回素材對象
{
"host":null,
"code":200,
"errorMessage":"操作成功",
"data":{
"id":52,
"userId":1102,
"url":"http://192.168.200.130:9000/leadnews/2021/04/26/a73f5b60c0d84c32bfe175055aaaac40.jpg",
"type":0,
"isCollection":0,
"createdTime":"2021-01-20T16:49:48.443+0000"
}
}
失?。?/p>
-
參數(shù)失效
-
文章上傳失敗
2.2.5)自媒體微服務(wù)集成heima-file-starter
①:導(dǎo)入heima-file-starter
<dependencies>
<dependency>
<groupId>com.heima</groupId>
<artifactId>heima-file-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
②:在自媒體微服務(wù)的配置中心添加以下配置:
minio:
accessKey: minio
secretKey: minio123
bucket: leadnews
endpoint: http://192.168.200.130:9000
readPath: http://192.168.200.130:9000
2.2.6)具體實(shí)現(xiàn)
①:創(chuàng)建WmMaterialController
@RestController
@RequestMapping("/api/v1/material")
public class WmMaterialController {
@PostMapping("/upload_picture")
public ResponseResult uploadPicture(MultipartFile multipartFile){
return null;
}
}
②:mapper
package com.heima.wemedia.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.wemedia.pojos.WmMaterial;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface WmMaterialMapper extends BaseMapper<WmMaterial> {
}
③:業(yè)務(wù)層:
package com.heima.wemedia.service;
public interface WmMaterialService extends IService<WmMaterial> {
/**
* 圖片上傳
* @param multipartFile
* @return
*/
public ResponseResult uploadPicture(MultipartFile multipartFile);
}
業(yè)務(wù)層實(shí)現(xiàn)類:
package com.heima.wemedia.service.impl;
import java.io.IOException;
import java.util.Date;
import java.util.UUID;
@Slf4j
@Service
@Transactional
public class WmMaterialServiceImpl extends ServiceImpl<WmMaterialMapper, WmMaterial> implements WmMaterialService {
@Autowired
private FileStorageService fileStorageService;
/**
* 圖片上傳
* @param multipartFile
* @return
*/
@Override
public ResponseResult uploadPicture(MultipartFile multipartFile) {
//1.檢查參數(shù)
if(multipartFile == null || multipartFile.getSize() == 0){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
//2.上傳圖片到minIO中
String fileName = UUID.randomUUID().toString().replace("-", "");
//aa.jpg
String originalFilename = multipartFile.getOriginalFilename();
String postfix = originalFilename.substring(originalFilename.lastIndexOf("."));
String fileId = null;
try {
fileId = fileStorageService.uploadImgFile("", fileName + postfix, multipartFile.getInputStream());
log.info("上傳圖片到MinIO中,fileId:{}",fileId);
} catch (IOException e) {
e.printStackTrace();
log.error("WmMaterialServiceImpl-上傳文件失敗");
}
//3.保存到數(shù)據(jù)庫中
WmMaterial wmMaterial = new WmMaterial();
wmMaterial.setUserId(WmThreadLocalUtil.getUser().getId());
wmMaterial.setUrl(fileId);
wmMaterial.setIsCollection((short)0);
wmMaterial.setType((short)0);
wmMaterial.setCreatedTime(new Date());
save(wmMaterial);
//4.返回結(jié)果
return ResponseResult.okResult(wmMaterial);
}
}
④:控制器
@RestController
@RequestMapping("/api/v1/material")
public class WmMaterialController {
@Autowired
private WmMaterialService wmMaterialService;
@PostMapping("/upload_picture")
public ResponseResult uploadPicture(MultipartFile multipartFile){
return wmMaterialService.uploadPicture(multipartFile);
}
}
⑤:測試
啟動(dòng)自媒體微服務(wù)和自媒體網(wǎng)關(guān),使用前端項(xiàng)目進(jìn)行測試
2.2)素材列表查詢
2.2.1)接口定義
說明 |
|
接口路徑 |
/api/v1/material/list |
請求方式 |
POST |
參數(shù) |
WmMaterialDto |
響應(yīng)結(jié)果 |
ResponseResult |
WmMaterialDto :
@Data
public class WmMaterialDto extends PageRequestDto {
/**
* 1 收藏
* 0 未收藏
*/
private Short isCollection;
}
ResponseResult :
{
"host":null,
"code":200,
"errorMessage":"操作成功",
"data":[
{
"id":52,
"userId":1102,
"url":"http://192.168.200.130:9000/leadnews/2021/04/26/ec893175f18c4261af14df14b83cb25f.jpg",
"type":0,
"isCollection":0,
"createdTime":"2021-01-20T16:49:48.000+0000"
},
....
],
"currentPage":1,
"size":20,
"total":0
}
2.2.2)功能實(shí)現(xiàn)
①:在WmMaterialController類中新增方法
@PostMapping("/list")
public ResponseResult findList(@RequestBody WmMaterialDto dto){
return null;
}
②:mapper已定義
③:業(yè)務(wù)層
在WmMaterialService中新增方法
/**
* 素材列表查詢
* @param dto
* @return
*/
public ResponseResult findList( WmMaterialDto dto);
實(shí)現(xiàn)方法:
/**
* 素材列表查詢
* @param dto
* @return
*/
@Override
public ResponseResult findList(WmMaterialDto dto) {
//1.檢查參數(shù)
dto.checkParam();
//2.分頁查詢
IPage page = new Page(dto.getPage(),dto.getSize());
LambdaQueryWrapper<WmMaterial> lambdaQueryWrapper = new LambdaQueryWrapper<>();
//是否收藏
if(dto.getIsCollection() != null && dto.getIsCollection() == 1){
lambdaQueryWrapper.eq(WmMaterial::getIsCollection,dto.getIsCollection());
}
//按照用戶查詢
lambdaQueryWrapper.eq(WmMaterial::getUserId,WmThreadLocalUtil.getUser().getId());
//按照時(shí)間倒序
lambdaQueryWrapper.orderByDesc(WmMaterial::getCreatedTime);
page = page(page,lambdaQueryWrapper);
//3.結(jié)果返回
ResponseResult responseResult = new PageResponseResult(dto.getPage(),dto.getSize(),(int)page.getTotal());
responseResult.setData(page.getRecords());
return responseResult;
}
④:控制器:
@PostMapping("/list")
public ResponseResult findList(@RequestBody WmMaterialDto dto){
return wmMaterialService.findList(dto);
}
⑤:在自媒體引導(dǎo)類中 添加 mybatis-plus的分頁攔截器
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
3)自媒體文章管理
3.1)查詢所有頻道
3.1.1)需求分析

3.1.2)表結(jié)構(gòu)
wm_channel 頻道信息表

對應(yīng)實(shí)體類:
package com.heima.model.wemedia.pojos;
import java.util.Date;
/**
* <p>
* 頻道信息表
* </p>
*
* @author itheima
*/
@Data
@TableName("wm_channel")
public class WmChannel implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 頻道名稱
*/
@TableField("name")
private String name;
/**
* 頻道描述
*/
@TableField("description")
private String description;
/**
* 是否默認(rèn)頻道
* 1:默認(rèn) true
* 0:非默認(rèn) false
*/
@TableField("is_default")
private Boolean isDefault;
/**
* 是否啟用
* 1:啟用 true
* 0:禁用 false
*/
@TableField("status")
private Boolean status;
/**
* 默認(rèn)排序
*/
@TableField("ord")
private Integer ord;
/**
* 創(chuàng)建時(shí)間
*/
@TableField("created_time")
private Date createdTime;
}
3.1.3)接口定義
說明 |
|
接口路徑 |
/api/v1/channel/channels |
請求方式 |
POST |
參數(shù) |
無 |
響應(yīng)結(jié)果 |
ResponseResult |
ResponseResult :
{
"host": "null",
"code": 0,
"errorMessage": "操作成功",
"data": [
{
"id": 4,
"name": "java",
"description": "java",
"isDefault": true,
"status": false,
"ord": 3,
"createdTime": "2019-08-16T10:55:41.000+0000"
},
Object { ... },
Object { ... }
]
}
3.1.4)功能實(shí)現(xiàn)
接口定義:
package com.heima.wemedia.controller.v1;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/channel")
public class WmchannelController {
@GetMapping("/channels")
public ResponseResult findAll(){
return null;
}
}
mapper
package com.heima.wemedia.mapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface WmChannelMapper extends BaseMapper<WmChannel> {
}
service
package com.heima.wemedia.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.wemedia.pojos.WmChannel;
public interface WmChannelService extends IService<WmChannel> {
/**
* 查詢所有頻道
* @return
*/
public ResponseResult findAll();
}
實(shí)現(xiàn)類
package com.heima.wemedia.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
@Slf4j
public class WmChannelServiceImpl extends ServiceImpl<WmChannelMapper, WmChannel> implements WmChannelService {
/**
* 查詢所有頻道
* @return
*/
@Override
public ResponseResult findAll() {
return ResponseResult.okResult(list());
}
}
控制層
package com.heima.wemedia.controller.v1;
import com.heima.model.common.dtos.ResponseResult;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/channel")
public class WmchannelController {
@Autowired
private WmChannelService wmChannelService;
@GetMapping("/channels")
public ResponseResult findAll(){
return wmChannelService.findAll();
}
}
3.1.5)測試
3.2)查詢自媒體文章
3.2.1)需求說明

3.2.2)表結(jié)構(gòu)分析
wm_news 自媒體文章表

對應(yīng)實(shí)體類:
package com.heima.model.wemedia.pojos;
import java.io.Serializable;
import java.util.Date;
/**
* <p>
* 自媒體圖文內(nèi)容信息表
* </p>
*
* @author itheima
*/
@Data
@TableName("wm_news")
public class WmNews implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主鍵
*/
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 自媒體用戶ID
*/
@TableField("user_id")
private Integer userId;
/**
* 標(biāo)題
*/
@TableField("title")
private String title;
/**
* 圖文內(nèi)容
*/
@TableField("content")
private String content;
/**
* 文章布局
0 無圖文章
1 單圖文章
3 多圖文章
*/
@TableField("type")
private Short type;
/**
* 圖文頻道ID
*/
@TableField("channel_id")
private Integer channelId;
@TableField("labels")
private String labels;
/**
* 創(chuàng)建時(shí)間
*/
@TableField("created_time")
private Date createdTime;
/**
* 提交時(shí)間
*/
@TableField("submited_time")
private Date submitedTime;
/**
* 當(dāng)前狀態(tài)
0 草稿
1 提交(待審核)
2 審核失敗
3 人工審核
4 人工審核通過
8 審核通過(待發(fā)布)
9 已發(fā)布
*/
@TableField("status")
private Short status;
/**
* 定時(shí)發(fā)布時(shí)間,不定時(shí)則為空
*/
@TableField("publish_time")
private Date publishTime;
/**
* 拒絕理由
*/
@TableField("reason")
private String reason;
/**
* 發(fā)布庫文章ID
*/
@TableField("article_id")
private Long articleId;
/**
* //圖片用逗號分隔
*/
@TableField("images")
private String images;
@TableField("enable")
private Short enable;
//狀態(tài)枚舉類
@Alias("WmNewsStatus")
public enum Status{
NORMAL((short)0),SUBMIT((short)1),FAIL((short)2),ADMIN_AUTH((short)3),ADMIN_SUCCESS((short)4),SUCCESS((short)8),PUBLISHED((short)9);
short code;
Status(short code){
this.code = code;
}
public short getCode(){
return this.code;
}
}
}
3.2.3)接口定義
說明 |
|
接口路徑 |
/api/v1/news/list |
請求方式 |
POST |
參數(shù) |
WmNewsPageReqDto |
響應(yīng)結(jié)果 |
ResponseResult |
WmNewsPageReqDto :
package com.heima.model.wemedia.dtos;
import com.heima.model.common.dtos.PageRequestDto;
import lombok.Data;
import java.util.Date;
@Data
public class WmNewsPageReqDto extends PageRequestDto {
/**
* 狀態(tài)
*/
private Short status;
/**
* 開始時(shí)間
*/
private Date beginPubDate;
/**
* 結(jié)束時(shí)間
*/
private Date endPubDate;
/**
* 所屬頻道ID
*/
private Integer channelId;
/**
* 關(guān)鍵字
*/
private String keyword;
}
ResponseResult :
{
"host": "null",
"code": 0,
"errorMessage": "操作成功",
"data": [
Object { ... },
Object { ... },
Object { ... }
],
"currentPage":1,
"size":10,
"total":21
}
3.2.4)功能實(shí)現(xiàn)
①:新增WmNewsController
package com.heima.wemedia.controller.v1;
import com.heima.model.common.dtos.ResponseResult;
@RestController
@RequestMapping("/api/v1/news")
public class WmNewsController {
@PostMapping("/list")
public ResponseResult findAll(@RequestBody WmNewsPageReqDto dto){
return null;
}
}
②:新增WmNewsMapper
package com.heima.wemedia.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.wemedia.pojos.WmNews;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface WmNewsMapper extends BaseMapper<WmNews> {
}
③:新增WmNewsService
package com.heima.wemedia.service;
import com.heima.model.wemedia.pojos.WmNews;
public interface WmNewsService extends IService<WmNews> {
/**
* 查詢文章
* @param dto
* @return
*/
public ResponseResult findAll(WmNewsPageReqDto dto);
}
實(shí)現(xiàn)類:
package com.heima.wemedia.service.impl;
import org.springframework.transaction.annotation.Transactional;
@Service
@Slf4j
@Transactional
public class WmNewsServiceImpl extends ServiceImpl<WmNewsMapper, WmNews> implements WmNewsService {
/**
* 查詢文章
* @param dto
* @return
*/
@Override
public ResponseResult findAll(WmNewsPageReqDto dto) {
//1.檢查參數(shù)
if(dto == null){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
//分頁參數(shù)檢查
dto.checkParam();
//獲取當(dāng)前登錄人的信息
WmUser user = WmThreadLocalUtil.getUser();
if(user == null){
return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
}
//2.分頁條件查詢
IPage page = new Page(dto.getPage(),dto.getSize());
LambdaQueryWrapper<WmNews> lambdaQueryWrapper = new LambdaQueryWrapper<>();
//狀態(tài)精確查詢
if(dto.getStatus() != null){
lambdaQueryWrapper.eq(WmNews::getStatus,dto.getStatus());
}
//頻道精確查詢
if(dto.getChannelId() != null){
lambdaQueryWrapper.eq(WmNews::getChannelId,dto.getChannelId());
}
//時(shí)間范圍查詢
if(dto.getBeginPubDate()!=null && dto.getEndPubDate()!=null){
lambdaQueryWrapper.between(WmNews::getPublishTime,dto.getBeginPubDate(),dto.getEndPubDate());
}
//關(guān)鍵字模糊查詢
if(StringUtils.isNotBlank(dto.getKeyword())){
lambdaQueryWrapper.like(WmNews::getTitle,dto.getKeyword());
}
//查詢當(dāng)前登錄用戶的文章
lambdaQueryWrapper.eq(WmNews::getUserId,user.getId());
//發(fā)布時(shí)間倒序查詢
lambdaQueryWrapper.orderByDesc(WmNews::getCreatedTime);
page = page(page,lambdaQueryWrapper);
//3.結(jié)果返回
ResponseResult responseResult = new PageResponseResult(dto.getPage(),dto.getSize(),(int)page.getTotal());
responseResult.setData(page.getRecords());
return responseResult;
}
}
④:控制器
package com.heima.wemedia.controller.v1;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/news")
public class WmNewsController {
@Autowired
private WmNewsService wmNewsService;
@PostMapping("/list")
public ResponseResult findAll(@RequestBody WmNewsPageReqDto dto){
return wmNewsService.findAll(dto);
}
}
3.2.5)測試
啟動(dòng)后端自媒體微服務(wù)和自媒體網(wǎng)關(guān)微服務(wù),測試文章列表查詢
3.3)文章發(fā)布
3.3.1)需求分析

3.3.2)表結(jié)構(gòu)分析
保存文章,除了需要wm_news表以外,還需要另外兩張表;
保存了這個(gè)關(guān)系表,就可以保存素材,素材有被引用的標(biāo)記,就不能被修改或刪除;

其中wm_material和wm_news表的實(shí)體類已經(jīng)導(dǎo)入到了項(xiàng)目中,下面是wm_news_material表對應(yīng)的實(shí)體類:
package com.heima.model.wemedia.pojos;
import java.io.Serializable;
/**
* <p>
* 自媒體圖文引用素材信息表
* </p>
*
* @author itheima
*/
@Data
@TableName("wm_news_material")
public class WmNewsMaterial implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主鍵
*/
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 素材ID
*/
@TableField("material_id")
private Integer materialId;
/**
* 圖文ID
*/
@TableField("news_id")
private Integer newsId;
/**
* 引用類型
0 內(nèi)容引用
1 主圖引用
*/
@TableField("type")
private Short type;
/**
* 引用排序
*/
@TableField("ord")
private Short ord;
}
3.3.3)實(shí)現(xiàn)思路分析

1.前端提交發(fā)布或保存為草稿
2.后臺判斷請求中是否包含了文章id
3.如果不包含id,則為新增
3.1 執(zhí)行新增文章的操作
3.2 關(guān)聯(lián)文章內(nèi)容圖片與素材的關(guān)系
3.3 關(guān)聯(lián)文章封面圖片與素材的關(guān)系
4.如果包含了id,則為修改請求
4.1 刪除該文章與素材的所有關(guān)系
4.2 執(zhí)行修改操作
4.3 關(guān)聯(lián)文章內(nèi)容圖片與素材的關(guān)系
4.4 關(guān)聯(lián)文章封面圖片與素材的關(guān)系
3.3.4)接口定義
說明 |
|
接口路徑 |
/api/v1/channel/submit |
請求方式 |
POST |
參數(shù) |
WmNewsDto |
響應(yīng)結(jié)果 |
ResponseResult |
WmNewsDto
package com.heima.model.wemedia.dtos;
import lombok.Data;
import java.util.Date;
import java.util.List;
@Data
public class WmNewsDto {
private Integer id;
/**
* 標(biāo)題
*/
private String title;
/**
* 頻道id
*/
private Integer channelId;
/**
* 標(biāo)簽
*/
private String labels;
/**
* 發(fā)布時(shí)間
*/
private Date publishTime;
/**
* 文章內(nèi)容
*/
private String content;
/**
* 文章封面類型 0 無圖 1 單圖 3 多圖 -1 自動(dòng)
*/
private Short type;
/**
* 提交時(shí)間
*/
private Date submitedTime;
/**
* 狀態(tài) 提交為1 草稿為0
*/
private Short status;
/**
* 封面圖片列表 多張圖以逗號隔開
*/
private List<String> images;
}
前端給傳遞過來的json數(shù)據(jù)格式為:
{
"title":"黑馬頭條項(xiàng)目背景",
"type":"1",//這個(gè) 0 是無圖 1 是單圖 3 是多圖 -1 是自動(dòng)
"labels":"黑馬頭條",
"publishTime":"2020-03-14T11:35:49.000Z",
"channelId":1,
"images":[
"http://192.168.200.130/group1/M00/00/00/wKjIgl5swbGATaSAAAEPfZfx6Iw790.png"
],
"status":1,
"content":"[
{
"type":"text",
"value":"隨著智能手機(jī)的普及,人們更加習(xí)慣于通過手機(jī)來看新聞。由于生活節(jié)奏的加快,很多人只能利用碎片時(shí)間來獲取信息,因此,對于移動(dòng)資訊客戶端的需求也越來越高。黑馬頭條項(xiàng)目正是在這樣背景下開發(fā)出來。黑馬頭條項(xiàng)目采用當(dāng)下火熱的微服務(wù)+大數(shù)據(jù)技術(shù)架構(gòu)實(shí)現(xiàn)。本項(xiàng)目主要著手于獲取最新最熱新聞資訊,通過大數(shù)據(jù)分析用戶喜好精確推送咨詢新聞"
},
{
"type":"image",
"value":"http://192.168.200.130/group1/M00/00/00/wKjIgl5swbGATaSAAAEPfZfx6Iw790.png"
}
]"
}
ResponseResult:
{
“code”:501,
“errorMessage”:“參數(shù)失效"
}
{
“code”:200,
“errorMessage”:“操作成功"
}
{
“code”:501,
“errorMessage”:“素材引用失效"
}
3.3.5)功能實(shí)現(xiàn)
①:在新增WmNewsController中新增方法
@PostMapping("/submit")
public ResponseResult submitNews(@RequestBody WmNewsDto dto){
return null;
}
②:新增WmNewsMaterialMapper類,文章與素材的關(guān)聯(lián)關(guān)系需要批量保存,索引需要定義mapper文件和對應(yīng)的映射文件
package com.heima.wemedia.mapper;
import java.util.List;
@Mapper
public interface WmNewsMaterialMapper extends BaseMapper<WmNewsMaterial> {
void saveRelations(@Param("materialIds") List<Integer> materialIds,@Param("newsId") Integer newsId, @Param("type")Short type);
}
WmNewsMaterialMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.heima.wemedia.mapper.WmNewsMaterialMapper">
<insert id="saveRelations">
insert into wm_news_material (material_id,news_id,type,ord)
values
<foreach collection="materialIds" index="ord" item="mid" separator=",">
(#{mid},#{newsId},#{type},#{ord})
</foreach>
</insert>
</mapper>
③:常量類準(zhǔn)備
package com.heima.common.constants;
public class WemediaConstants {
public static final Short COLLECT_MATERIAL = 1;//收藏
public static final Short CANCEL_COLLECT_MATERIAL = 0;//取消收藏
public static final String WM_NEWS_TYPE_IMAGE = "image";
public static final Short WM_NEWS_NONE_IMAGE = 0;
public static final Short WM_NEWS_SINGLE_IMAGE = 1;
public static final Short WM_NEWS_MANY_IMAGE = 3;
public static final Short WM_NEWS_TYPE_AUTO = -1;
public static final Short WM_CONTENT_REFERENCE = 0;
public static final Short WM_COVER_REFERENCE = 1;
}
④:在WmNewsService中新增方法
/**
* 發(fā)布文章或保存草稿
* @param dto
* @return
*/
public ResponseResult submitNews(WmNewsDto dto);
實(shí)現(xiàn)方法:
/**
* 發(fā)布修改文章或保存為草稿
* @param dto
* @return
*/
@Override
public ResponseResult submitNews(WmNewsDto dto) {
//0.條件判斷
if(dto == null || dto.getContent() == null){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
//1.保存或修改文章
WmNews wmNews = new WmNews();
//屬性拷貝 屬性名詞和類型相同才能拷貝
BeanUtils.copyProperties(dto,wmNews);
//封面圖片 list---> string
if(dto.getImages() != null && dto.getImages().size() > 0){
//[1dddfsd.jpg,sdlfjldk.jpg]--> 1dddfsd.jpg,sdlfjldk.jpg
String imageStr = StringUtils.join(dto.getImages(), ",");
wmNews.setImages(imageStr);
}
//如果當(dāng)前封面類型為自動(dòng) -1
if(dto.getType().equals(WemediaConstants.WM_NEWS_TYPE_AUTO)){
wmNews.setType(null);
}
saveOrUpdateWmNews(wmNews);
//2.判斷是否為草稿 如果為草稿結(jié)束當(dāng)前方法
if(dto.getStatus().equals(WmNews.Status.NORMAL.getCode())){
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
//3.不是草稿,保存文章內(nèi)容圖片與素材的關(guān)系
//獲取到文章內(nèi)容中的圖片信息
List<String> materials = ectractUrlInfo(dto.getContent());
saveRelativeInfoForContent(materials,wmNews.getId());
//4.不是草稿,保存文章封面圖片與素材的關(guān)系,如果當(dāng)前布局是自動(dòng),需要匹配封面圖片
saveRelativeInfoForCover(dto,wmNews,materials);
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
/**
* 第一個(gè)功能:如果當(dāng)前封面類型為自動(dòng),則設(shè)置封面類型的數(shù)據(jù)
* 匹配規(guī)則:
* 1,如果內(nèi)容圖片大于等于1,小于3 單圖 type 1
* 2,如果內(nèi)容圖片大于等于3 多圖 type 3
* 3,如果內(nèi)容沒有圖片,無圖 type 0
*
* 第二個(gè)功能:保存封面圖片與素材的關(guān)系
* @param dto
* @param wmNews
* @param materials
*/
private void saveRelativeInfoForCover(WmNewsDto dto, WmNews wmNews, List<String> materials) {
List<String> images = dto.getImages();
//如果當(dāng)前封面類型為自動(dòng),則設(shè)置封面類型的數(shù)據(jù)
if(dto.getType().equals(WemediaConstants.WM_NEWS_TYPE_AUTO)){
//多圖
if(materials.size() >= 3){
wmNews.setType(WemediaConstants.WM_NEWS_MANY_IMAGE);
images = materials.stream().limit(3).collect(Collectors.toList());
}else if(materials.size() >= 1 && materials.size() < 3){
//單圖
wmNews.setType(WemediaConstants.WM_NEWS_SINGLE_IMAGE);
images = materials.stream().limit(1).collect(Collectors.toList());
}else {
//無圖
wmNews.setType(WemediaConstants.WM_NEWS_NONE_IMAGE);
}
//修改文章
if(images != null && images.size() > 0){
wmNews.setImages(StringUtils.join(images,","));
}
updateById(wmNews);
}
if(images != null && images.size() > 0){
saveRelativeInfo(images,wmNews.getId(),WemediaConstants.WM_COVER_REFERENCE);
}
}
/**
* 處理文章內(nèi)容圖片與素材的關(guān)系
* @param materials
* @param newsId
*/
private void saveRelativeInfoForContent(List<String> materials, Integer newsId) {
saveRelativeInfo(materials,newsId,WemediaConstants.WM_CONTENT_REFERENCE);
}
@Autowired
private WmMaterialMapper wmMaterialMapper;
/**
* 保存文章圖片與素材的關(guān)系到數(shù)據(jù)庫中
* @param materials
* @param newsId
* @param type
*/
private void saveRelativeInfo(List<String> materials, Integer newsId, Short type) {
if(materials!=null && !materials.isEmpty()){
//通過圖片的url查詢素材的id
List<WmMaterial> dbMaterials = wmMaterialMapper.selectList(Wrappers.<WmMaterial>lambdaQuery().in(WmMaterial::getUrl, materials));
//判斷素材是否有效
if(dbMaterials==null || dbMaterials.size() == 0){
//手動(dòng)拋出異常 第一個(gè)功能:能夠提示調(diào)用者素材失效了,第二個(gè)功能,進(jìn)行數(shù)據(jù)的回滾
throw new CustomException(AppHttpCodeEnum.MATERIASL_REFERENCE_FAIL);
}
if(materials.size() != dbMaterials.size()){
throw new CustomException(AppHttpCodeEnum.MATERIASL_REFERENCE_FAIL);
}
List<Integer> idList = dbMaterials.stream().map(WmMaterial::getId).collect(Collectors.toList());
//批量保存
wmNewsMaterialMapper.saveRelations(idList,newsId,type);
}
}
/**
* 提取文章內(nèi)容中的圖片信息
* @param content
* @return
*/
private List<String> ectractUrlInfo(String content) {
List<String> materials = new ArrayList<>();
List<Map> maps = JSON.parseArray(content, Map.class);
for (Map map : maps) {
if(map.get("type").equals("image")){
String imgUrl = (String) map.get("value");
materials.add(imgUrl);
}
}
return materials;
}
@Autowired
private WmNewsMaterialMapper wmNewsMaterialMapper;
/**
* 保存或修改文章
* @param wmNews
*/
private void saveOrUpdateWmNews(WmNews wmNews) {
//補(bǔ)全屬性
wmNews.setUserId(WmThreadLocalUtil.getUser().getId());
wmNews.setCreatedTime(new Date());
wmNews.setSubmitedTime(new Date());
wmNews.setEnable((short)1);//默認(rèn)上架
if(wmNews.getId() == null){
//保存
save(wmNews);
}else {
//修改
//刪除文章圖片與素材的關(guān)系
wmNewsMaterialMapper.delete(Wrappers.<WmNewsMaterial>lambdaQuery().eq(WmNewsMaterial::getNewsId,wmNews.getId()));
updateById(wmNews);
}
}
④:控制器文章來源:http://www.zghlxwxcb.cn/news/detail-608079.html
@PostMapping("/submit")
public ResponseResult submitNews(@RequestBody WmNewsDto dto){
return wmNewsService.submitNews(dto);
}
3.3.6)測試
《黑馬頭條》 內(nèi)容安全 自動(dòng)審核 feign 延遲任務(wù)精準(zhǔn)發(fā)布 kafka_軟工菜雞的博客-CSDN博客文章來源地址http://www.zghlxwxcb.cn/news/detail-608079.html
到了這里,關(guān)于《黑馬頭條》SpringBoot+SpringCloud+ Nacos等企業(yè)級微服務(wù)架構(gòu)項(xiàng)目的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!