javascript
《黑马头条》SpringBoot+SpringCloud+ Nacos等企业级微服务架构项目
01環(huán)境搭建、SpringCloud微服務(wù)(注冊(cè)發(fā)現(xiàn)、服務(wù)調(diào)用、網(wǎng)關(guān))
1)課程對(duì)比
2)項(xiàng)目概述
2.1)能讓你收獲什么
2.2)項(xiàng)目課程大綱
2.3)項(xiàng)目概述
隨著智能手機(jī)的普及,人們更加習(xí)慣于通過(guò)手機(jī)來(lái)看新聞。由于生活節(jié)奏的加快,很多人只能利用碎片時(shí)間來(lái)獲取信息,因此,對(duì)于移動(dòng)資訊客戶端的需求也越來(lái)越高。
黑馬頭條項(xiàng)目正是在這樣背景下開發(fā)出來(lái)。黑馬頭條項(xiàng)目采用當(dāng)下火熱的微服務(wù)+大數(shù)據(jù)技術(shù)架構(gòu)實(shí)現(xiàn)。本項(xiàng)目主要著手于獲取最新最熱新聞資訊,通過(guò)大數(shù)據(jù)分析用戶喜好精確推送咨詢新聞
2.4)項(xiàng)目術(shù)語(yǔ)
2.5)業(yè)務(wù)說(shuō)明
項(xiàng)目演示地址:
平臺(tái)管理:http://heima-admin-java.research.itcast.cn
自媒體:http://heime-media-java.research.itcast.cn
app端:http://heima-app-java.research.itcast.cn
平臺(tái)管理與自媒體為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ù)注冊(cè)中的API請(qǐng)求路由,以及控制流速控制和熔斷處理都是常用的架構(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)目中的注冊(cè)中心和配置中心
運(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存儲(chǔ)用戶數(shù)據(jù),以保證上層數(shù)據(jù)查詢的高性能
使用Mongo存儲(chǔ)用戶熱數(shù)據(jù),以保證用戶熱數(shù)據(jù)高擴(kuò)展和高性能指標(biāo)
使用FastDFS作為靜態(tài)資源存儲(chǔ)器,在其上實(shí)現(xiàn)熱靜態(tài)資源緩存、淘汰等功能
運(yùn)用Hbase技術(shù),存儲(chǔ)系統(tǒng)中的冷數(shù)據(jù),保證系統(tǒng)數(shù)據(jù)的可靠性
運(yùn)用ES搜索技術(shù),對(duì)冷數(shù)據(jù)、文章數(shù)據(jù)建立索引,以保證冷數(shù)據(jù)、文章查詢性能
運(yùn)用AI技術(shù),來(lái)完成系統(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.0MODE=standalone 單機(jī)版
--restart=always 開機(jī)啟動(dòng)
-p 8848:8848 映射端口
-d 創(chuàng)建一個(gè)守護(hù)式容器在后臺(tái)運(yùn)行
③:訪問(wè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文件,拷貝到 沒(méi)有中文和空格的目錄,使用idea打開即可
③:IDEA開發(fā)工具配置
設(shè)置本地倉(cāng)庫(kù),建議使用資料中提供好的倉(cāng)庫(kù)
④:設(shè)置項(xiàng)目編碼格式
5.2)主體結(jié)構(gòu)
6)登錄
6.1)需求分析
戶點(diǎn)擊開始使用
登錄后的用戶權(quán)限較大,可以查 看,也可以操作(點(diǎn)贊,關(guān)注,評(píng)論)
用戶點(diǎn)擊不登錄,先看看
游客只有查看的權(quán)限
6.2)表結(jié)構(gòu)分析
關(guān)于app端用戶相關(guān)的內(nèi)容較多,可以單獨(dú)設(shè)置一個(gè)庫(kù)leadnews_user
表名稱 | 說(shuō)明 |
ap_user | APP用戶信息表 |
ap_user_fan | APP用戶粉絲信息表 |
ap_user_follow | APP用戶關(guān)注信息表 |
ap_user_realname | APP實(shí)名認(rèn)證信息表 |
從當(dāng)前資料中找到對(duì)應(yīng)數(shù)據(jù)庫(kù)并導(dǎo)入到mysql中
登錄需要用到的是ap_user表,表結(jié)構(gòu)如下:
項(xiàng)目中的持久層使用的mybatis-plus,一般都使用mybais-plus逆向生成對(duì)應(yīng)的實(shí)體類
app_user表對(duì)應(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ī)號(hào)*/@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;/*** 注冊(cè)時(shí)間*/@TableField("created_time")private Date createdTime;}手動(dòng)加密(md5+隨機(jī)字符串)
md5是不可逆加密,md5相同的密碼每次加密都一樣,不太安全。在md5的基礎(chǔ)上手動(dòng)加鹽(salt)處理
注冊(cè)->生成鹽
登錄->使用鹽來(lái)配合驗(yàn)證
6.3)思路分析
1,用戶輸入了用戶名和密碼進(jìn)行登錄,校驗(yàn)成功后返回jwt(基于當(dāng)前用戶的id生成)
2,用戶游客登錄,生成jwt返回(基于默認(rèn)值0生成)‘
6.4)運(yùn)營(yíng)端微服務(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)前注冊(cè)中心 @MapperScan("com.heima.user.mapper")//掃描mapper public class UserApplication {public static void main(String[] args) {SpringApplication.run(UserApplication.class,args);} }bootstrap.yml
server:port: 51801 spring:application:name: leadnews-usercloud:nacos:discovery:server-addr: 192.168.200.130:8848config:server-addr: 192.168.200.130:8848file-extension: yml在nacos中創(chuàng)建配置文件
spring:datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/leadnews_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTCusername: rootpassword: root # 設(shè)置Mapper接口所對(duì)應(yīng)的XML文件位置,如果你在Mapper接口中有自定義方法,需要進(jìn)行該配置 mybatis-plus:mapper-locations: classpath*:mapper/*.xml# 設(shè)置別名包掃描路徑,通過(guò)該屬性可以給包中的類注冊(cè)別名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><!--定義日志文件的存儲(chǔ)地址,使用絕對(duì)路徑--><property name="LOG_HOME" value="e:/logs"/><!-- Console 輸出設(shè)置 --><appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><encoder><!--格式化輸出:%d表示日期,%thread表示線程名,%-5level:級(jí)別從左顯示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級(jí)別的日志 --><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 {@Overridepublic ResponseResult login(LoginDto dto) {//1.正常登錄(手機(jī)號(hào)+密碼登錄)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 比對(duì)密碼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ù) jwtMap<String, Object> map = new HashMap<>();map.put("token", AppJwtUtil.getToken(apUser.getId().longValue()));//這兩個(gè)值 置空 再返回對(duì)象apUser.setSalt("");apUser.setPassword("");map.put("user", apUser);return ResponseResult.okResult(map);} else {//2.游客 同樣返回token id = 0Map<String, Object> map = new HashMap<>();map.put("token", AppJwtUtil.getToken(0l));return ResponseResult.okResult(map);}} }④:控制層controller
package com.heima.user.controller.v1;import com.heima.model.common.dtos.ResponseResult; import com.heima.model.user.dtos.LoginDto; import com.heima.user.service.ApUserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;@RestController @RequestMapping("/api/v1/login") public class ApUserLoginController {@Autowiredprivate ApUserService apUserService;@PostMapping("/login_auth")public ResponseResult login(@RequestBody LoginDto dto) {return apUserService.login(dto);} }docker exec -it redis redis-cli這個(gè)進(jìn)入服務(wù)器得redis界面 你再輸入docker ps就能看到服務(wù)起來(lái)了;docker 中的redis沒(méi)密碼,要把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)頁(yè)調(diào)試與發(fā)送網(wǎng)頁(yè)HTTP請(qǐng)求的Chrome插件。postman被500萬(wàn)開發(fā)者和超100,000家公司用于每月訪問(wèn)1.3億個(gè)API。
官方網(wǎng)址:https://www.postman.com/
解壓資料文件夾中的軟件,安裝即可
通常的接口測(cè)試查看請(qǐng)求和響應(yīng),下面是登錄請(qǐng)求的測(cè)試
http://localhost:51801/api/v1/login/login_auth
{"phone":"13511223456","password":"admin"}
7.2)swagger
(1)簡(jiǎn)介
Swagger 是一個(gè)規(guī)范和完整的框架,用于生成、描述、調(diào)用和可視化 RESTful 風(fēng)格的 Web 服務(wù)(https://swagger.io/)。 它的主要作用是:
使得前后端分離開發(fā)更加方便,有利于團(tuán)隊(duì)協(xié)作
接口的文檔在線自動(dòng)生成,降低后端開發(fā)人員編寫接口文檔的負(fù)擔(dān)
功能測(cè)試
Spring已經(jīng)將Swagger納入自身的標(biāo)準(zhǔn),建立了Spring-swagger項(xiàng)目,現(xiàn)在叫Springfox。
通過(guò)在項(xiàng)目中引入Springfox ,即可非常簡(jiǎn)單快捷的使用Swagger。
(2)SpringBoot集成Swagger
引入依賴,在heima-leadnews-model和heima-leadnews-common模塊中引入該依賴
只需要在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 {@Beanpublic 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("黑馬頭條-平臺(tái)管理API文檔").description("黑馬頭條后臺(tái)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è)方法,或者說(shuō)一個(gè)接口
@ApiParam:單個(gè)參數(shù)的描述信息
@ApiModel:用對(duì)象來(lái)接收參數(shù)
@ApiModelProperty:用對(duì)象接收參數(shù)時(shí),描述對(duì)象的一個(gè)字段
@ApiResponse:HTTP響應(yīng)其中1個(gè)描述
@ApiResponses:HTTP響應(yīng)整體描述
@ApiIgnore:使用該注解忽略這個(gè)API
@ApiError :發(fā)生錯(cuò)誤返回的信息
@ApiImplicitParam:一個(gè)請(qǐng)求參數(shù)
@ApiImplicitParams:多個(gè)請(qǐng)求參數(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)志說(shuō)明,并沒(méi)有實(shí)際驗(yàn)證 | |
Long | ||
String | ||
name | 接收參數(shù)名 | |
value | 接收參數(shù)的意義描述 | |
required | 參數(shù)是否必填 | |
true | 必填 | |
false | 非必填 | |
defaultValue | 默認(rèn)值 |
我們?cè)贏pUserLoginController中添加Swagger注解,代碼如下所示:
@RestController @RequestMapping("/api/v1/login") @Api(value = "app端用戶登錄", tags = "ap_user", description = "app端用戶登錄API") public class ApUserLoginController {@Autowiredprivate ApUserService apUserService;@PostMapping("/login_auth")@ApiOperation("用戶登錄")public ResponseResult login(@RequestBody LoginDto dto){return apUserService.login(dto);} }LoginDto
@Data public class LoginDto {/*** 手機(jī)號(hào)*/@ApiModelProperty(value="手機(jī)號(hào)",required = true)private String phone;/*** 密碼*/@ApiModelProperty(value="密碼",required = true)private String password; }啟動(dòng)user微服務(wù),訪問(wèn)地址:http://localhost:51801/swagger-ui.html
7.3)knife4j
(1)簡(jiǎn)介
knife4j是為Java MVC框架集成Swagger生成Api文檔的增強(qiáng)解決方案,前身是swagger-bootstrap-ui,取名kni4j是希望它能像一把匕首一樣小巧,輕量,并且功能強(qiáng)悍!
gitee地址:https://gitee.com/xiaoym/knife4j
官方文檔:https://doc.xiaominfo.com/
效果演示:http://knife4j.xiaominfo.com/doc.html
(2)核心功能
該UI增強(qiáng)包主要包括兩大核心功能:文檔說(shuō)明 和 在線調(diào)試
文檔說(shuō)明:根據(jù)Swagger的規(guī)范說(shuō)明,詳細(xì)列出接口文檔的說(shuō)明,包括接口地址、類型、請(qǐng)求示例、請(qǐng)求參數(shù)、響應(yīng)示例、響應(yīng)參數(shù)、響應(yīng)碼等信息,使用swagger-bootstrap-ui能根據(jù)該文檔說(shuō)明,對(duì)該接口的使用情況一目了然。
在線調(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請(qǐng)求命令實(shí)例、響應(yīng)時(shí)間、響應(yīng)狀態(tài)碼等信息,幫助開發(fā)者在線調(diào)試,而不必通過(guò)其他測(cè)試工具測(cè)試接口是否正確,簡(jiǎn)介、強(qiáng)大。
個(gè)性化配置(Swaager無(wú)):通過(guò)個(gè)性化ui配置項(xiàng),可自定義UI的相關(guān)顯示信息
離線文檔(Swaager無(wú)):根據(jù)標(biāo)準(zhǔn)規(guī)范,生成的在線markdown離線文檔,開發(fā)者可以進(jìn)行拷貝生成markdown接口文檔,通過(guò)其他第三方markdown轉(zhuǎn)換工具轉(zhuǎn)換成html或pdf,這樣也可以放棄swagger2markdown組件
接口排序:自1.8.5后,ui支持了接口排序功能,
例如一個(gè)注冊(cè)功能主要包含了多個(gè)步驟,可以根據(jù)swagger-bootstrap-ui提供的接口排序規(guī)則實(shí)現(xiàn)接口的排序,step化接口操作,方便其他開發(fā)者進(jìn)行接口對(duì)接
(3)快速集成
在heima-leadnews-common模塊中的pom.xml文件中引入knife4j的依賴,如下:
創(chuàng)建Swagger配置文件
在heima-leadnews-common模塊中新建配置類
新建Swagger的配置文件SwaggerConfiguration.java文件,創(chuàng)建springfox提供的Docket分組對(duì)象,代碼如下:
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è)注解需要特別說(shuō)明,如下表:
注解 | 說(shuō)明 |
@EnableSwagger2 | 該注解是Springfox-swagger框架提供的使用Swagger注解,該注解必須加 |
@EnableKnife4j | 該注解是knife4j提供的增強(qiáng)注解,Ui提供了例如動(dòng)態(tài)參數(shù)、參數(shù)過(guò)濾、接口排序等增強(qiáng)功能,如果你想使用這些增強(qiáng)功能就必須加該注解,否則可以不用加 |
添加配置
在Spring.factories中新增配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.heima.common.swagger.Swagger2Configuration, \com.heima.common.swagger.SwaggerConfiguration訪問(wèn)
在瀏覽器輸入地址: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 //開啟注冊(cè)中心 public class AppGatewayApplication {public static void main(String[] args) {SpringApplication.run(AppGatewayApplication.class,args);} }bootstrap.yml
server:port: 51601 spring:application:name: leadnews-app-gatewaycloud:nacos:discovery:server-addr: 192.168.200.130:8848config:server-addr: 192.168.200.130:8848file-extension: yml在nacos的配置中心創(chuàng)建dataid為leadnews-app-gateway的yml配置
spring:cloud:gateway:globalcors:add-to-simple-url-handler-mapping: truecorsConfigurations:'[/**]':allowedHeaders: "*"allowedOrigins: "*"allowedMethods:- GET- POST- DELETE- PUT- OPTIONroutes:# 平臺(tái)管理- id: useruri: lb://leadnews-userpredicates:- Path=/user/**filters:- StripPrefix= 1環(huán)境搭建完成以后,啟動(dòng)項(xiàng)目網(wǎng)關(guān)和用戶兩個(gè)服務(wù),使用postman進(jìn)行測(cè)試
請(qǐng)求地址:http://localhost:51601/user/api/v1/login/login_auth
1.3 全局過(guò)濾器實(shí)現(xiàn)jwt校驗(yàn)
思路分析:
用戶進(jìn)入網(wǎng)關(guān)開始登陸,網(wǎng)關(guān)過(guò)濾器進(jìn)行判斷,如果是登錄,則路由到后臺(tái)管理微服務(wù)進(jìn)行登錄
用戶登錄成功,后臺(tái)管理微服務(wù)簽發(fā)JWT TOKEN信息返回給用戶
用戶再次進(jìn)入網(wǎng)關(guān)開始訪問(wèn),網(wǎng)關(guān)過(guò)濾器接收用戶攜帶的TOKEN
網(wǎng)關(guān)過(guò)濾器解析TOKEN ,判斷是否有權(quán)限,如果有,則放行,如果沒(méi)有則返回未認(rèn)證錯(cuò)誤
具體實(shí)現(xiàn):
第一:
在認(rèn)證過(guò)濾器中需要用到j(luò)wt的解析,所以需要把工具類拷貝一份到網(wǎng)關(guān)微服務(wù)
第二:
在網(wǎng)關(guān)微服務(wù)中新建全局過(guò)濾器:
package com.heima.app.gateway.filter;import com.heima.app.gateway.util.AppJwtUtil; import io.jsonwebtoken.Claims; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono;@Component @Slf4j public class AuthorizeFilter implements Ordered, GlobalFilter {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//1.獲取request和response對(duì)象ServerHttpRequest request = exchange.getRequest();ServerHttpResponse response = exchange.getResponse();//2.判斷是否是登錄if(request.getURI().getPath().contains("/login")){//放行return chain.filter(exchange);}//3.獲取tokenString token = request.getHeaders().getFirst("token");//4.判斷token是否存在if(StringUtils.isBlank(token)){response.setStatusCode(HttpStatus.UNAUTHORIZED);return response.setComplete();}//5.判斷token是否有效try {Claims claimsBody = AppJwtUtil.getClaimsBody(token);//是否是過(guò)期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)先級(jí)設(shè)置 值越小 優(yōu)先級(jí)越高* @return*/@Overridepublic int getOrder() {return 0;} }測(cè)試:
啟動(dòng)user服務(wù),繼續(xù)訪問(wèn)其他微服務(wù),會(huì)提示需要認(rèn)證才能訪問(wèn),這個(gè)時(shí)候需要在heads中設(shè)置設(shè)置token才能正常訪問(wèn)。
9)前端集成
9.1)前端項(xiàng)目部署思路
通過(guò)nginx來(lái)進(jìn)行配置,功能如下
通過(guò)nginx的反向代理功能訪問(wèn)后臺(tái)的網(wǎng)關(guān)資源
通過(guò)nginx的靜態(tài)服務(wù)器功能訪問(wèn)前端靜態(tài)頁(yè)面
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; # 不改變?cè)凑?qǐng)求頭的值proxy_pass_request_body on; #開啟獲取請(qǐng)求體proxy_pass_request_headers on; #開啟獲取請(qǐng)求頭proxy_set_header X-Real-IP $remote_addr; # 記錄真實(shí)發(fā)出請(qǐng)求的客戶端IPproxy_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ò)
后來(lái)查閱發(fā)現(xiàn)是因?yàn)榻鈮旱穆窂嚼锩姘兄形牡木壒?#xff0c;只要把解壓后的文件剪切到?jīng)]有包含中文的目錄即可解決問(wèn)題
在運(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的文件,然后里面沒(méi)東西運(yùn)行失敗,就start nginx,計(jì)算機(jī)自己在pid加了東西,成功reload!
打開8801 報(bào)錯(cuò):500 Internal Server Error
這個(gè)錯(cuò)誤通常表示在Windows操作系統(tǒng)上,Nginx無(wú)法識(shí)別路徑中含有非英文字符的文件或文件夾。這可能是因?yàn)镹ginx默認(rèn)將其配置文件和日志文件等保存在UTF-8編碼下,而Windows默認(rèn)的編碼方式是ANSI。要解決這個(gè)問(wèn)題,可以嘗試以下方法:
將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)行測(cè)試 -- > http://localhost:8801
用谷歌瀏覽器打開,調(diào)試移動(dòng)端模式進(jìn)行訪問(wè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ù)庫(kù)
1.3.1)導(dǎo)入數(shù)據(jù)庫(kù)
查看當(dāng)天資料文件夾,在數(shù)據(jù)庫(kù)連接工具中執(zhí)行l(wèi)eadnews_article.sql
1.3.2)導(dǎo)入對(duì)應(yīng)的實(shí)體類
ap_article文章表對(duì)應(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>* 文章信息表,存儲(chǔ)已發(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 無(wú)圖文章 1 單圖文章 2 多圖文章*/private Short layout;/*** 文章標(biāo)記 0 普通文章 1 熱點(diǎn)文章 2 置頂文章 3 精品文章 4 大V 文章*/private Byte flag;/*** 文章封面圖片 多張逗號(hào)分隔*/private String images;/*** 標(biāo)簽*/private String labels;/*** 點(diǎn)贊數(shù)量*/private Integer likes;/*** 收藏?cái)?shù)量*/private Integer collection;/*** 評(píng)論數(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;/*** 來(lái)源*/private Boolean origin;/*** 靜態(tài)頁(yè)面地址*/@TableField("static_url")private String staticUrl; }ap_article_config文章配置對(duì)應(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;/*** 是否可評(píng)論* true: 可以評(píng)論 1* false: 不可評(píng)論 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: 沒(méi)有下架 0*/@TableField("is_down")private Boolean isDown;/*** 是否已刪除* true: 刪除 1* false: 沒(méi)有刪除 0*/@TableField("is_delete")private Boolean isDelete; }ap_article_content 文章內(nèi)容對(duì)應(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)用戶下拉可以加載最新的文章(分頁(yè))本頁(yè)文章列表中發(fā)布時(shí)間為最大的時(shí)間為依據(jù)
4,當(dāng)用戶上拉可以加載更多的文章信息(按照發(fā)布時(shí)間)本頁(yè)文章列表中發(fā)布時(shí)間最小的時(shí)間為依據(jù)
5,如果是當(dāng)前頻道的首頁(yè),前端傳遞默認(rèn)參數(shù):
maxBehotTime:0(毫秒)
minBehotTime:20000000000000(毫秒)--->2063年
1.5)接口定義
加載首頁(yè) | 加載更多 | 加載最新 | |
接口路徑 | /api/v1/article/load | /api/v1/article/loadmore | /api/v1/article/loadnew |
請(qǐng)求方式 | 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;// 分頁(yè)sizeInteger size;// 頻道IDString 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中添加對(duì)應(yīng)的配置
spring:datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/leadnews_article?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTCusername: rootpassword: root # 設(shè)置Mapper接口所對(duì)應(yīng)的XML文件位置,如果你在Mapper接口中有自定義方法,需要進(jìn)行該配置 mybatis-plus:mapper-locations: classpath*:mapper/*.xml# 設(shè)置別名包掃描路徑,通過(guò)該屬性可以給包中的類注冊(cè)別名type-aliases-package: com.heima.model.article.pojos1.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對(duì)多表查詢不太友好,所以用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);}對(duì)應(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">SELECTaa.*FROM`ap_article` aaLEFT JOIN ap_article_config aac ON aa.id = aac.article_id<where>and aac.is_delete != 1and aac.is_down != 1<!-- loadmore --><if test="type != null and type == 1">and aa.publish_time <![CDATA[<]]> #{dto.minBehotTime}</if><if test="type != null and type == 2">and aa.publish_time <![CDATA[>]]> #{dto.maxBehotTime}</if><if test="dto.tag != '__all__'">and aa.channel_id = #{dto.tag}</if></where>order by aa.publish_time desclimit #{dto.size}</select></mapper>1.6.4):編寫業(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 {// 單頁(yè)最大加載的數(shù)字private final static short MAX_PAGE_SIZE = 50;@Autowiredprivate ApArticleMapper apArticleMapper;/*** 根據(jù)參數(shù)加載文章列表* @param loadtype 1為加載更多 2為加載最新* @param dto* @return*/@Overridepublic 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 {@Autowiredprivate ApArticleService apArticleService;@PostMapping("/load")public ResponseResult load(@RequestBody ArticleHomeDto dto) {return apArticleService.load(ArticleConstants.LOADTYPE_LOAD_MORE,dto);}@PostMapping("/loadmore")public ResponseResult loadMore(@RequestBody ArticleHomeDto dto) {return apArticleService.load(ArticleConstants.LOADTYPE_LOAD_MORE,dto);}@PostMapping("/loadnew")public ResponseResult loadNew(@RequestBody ArticleHomeDto dto) {return apArticleService.load(ArticleConstants.LOADTYPE_LOAD_NEW,dto);} }1.6.6):swagger測(cè)試或前后端聯(lián)調(diào)測(cè)試
第一:在app網(wǎng)關(guān)的微服務(wù)的nacos的配置中心添加文章微服務(wù)的路由,完整配置如下:
spring:cloud:gateway:globalcors:cors-configurations:'[/**]': # 匹配所有請(qǐng)求allowedOrigins: "*" #跨域處理 允許所有的域allowedMethods: # 支持的方法- GET- POST- PUT- DELETEroutes:# 用戶微服務(wù)- id: useruri: lb://leadnews-userpredicates:- Path=/user/**filters:- StripPrefix= 1# 文章微服務(wù)- id: articleuri: lb://leadnews-articlepredicates:- Path=/article/**filters:- StripPrefix= 1第二:啟動(dòng)nginx,直接使用前端項(xiàng)目測(cè)試,啟動(dòng)文章微服務(wù),用戶微服務(wù)、app網(wǎng)關(guān)微服務(wù)
2)freemarker
2.1) freemarker 介紹
FreeMarker 是一款 模板引擎: 即一種基于模板和要改變的數(shù)據(jù), 并用來(lái)生成輸出文本(HTML網(wǎng)頁(yè),電子郵件,配置文件,源代碼等)的通用工具。 它不是面向最終用戶的,而是一個(gè)Java類庫(kù),是一款程序員可以嵌入他們所開發(fā)產(chǎn)品的組件。
模板編寫為FreeMarker Template Language (FTL)。它是簡(jiǎn)單的,專用的語(yǔ)言, 不是 像PHP那樣成熟的編程語(yǔ)言。 那就意味著要準(zhǔn)備數(shù)據(jù)在真實(shí)編程語(yǔ)言中來(lái)顯示,比如數(shù)據(jù)庫(kù)查詢和業(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 版本后,便沒(méi)有在更新。Spring Boot 官方在 1.4 版本后對(duì)此也不在支持,雖然 Velocity 在 2017 年版本得到迭代,但為時(shí)已晚。
2.2) 環(huán)境搭建&&快速入門
freemarker作為springmvc一種視圖格式,默認(rèn)情況下SpringMVC支持freemarker視圖格式。
需要?jiǎng)?chuàng)建Spring Boot+Freemarker工程用于測(cè)試模板。
2.2.1) 創(chuàng)建測(cè)試工程
創(chuàng)建一個(gè)freemarker-demo 的測(cè)試工程專門用于freemarker的功能測(cè)試與模板的測(cè)試。
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 對(duì) java io 的封裝工具庫(kù) --><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)閉模板緩存,方便測(cè)試settings:template_update_delay: 0 #檢查模板更新延遲時(shí)間,設(shè)置為0表示立即檢查,如果時(shí)間大于0會(huì)有緩存不方便進(jìn)行模板測(cè)試suffix: .ftl #指定Freemarker模板文件的后綴名2.2.3) 創(chuàng)建模型類
在freemarker的測(cè)試工程下創(chuàng)建模型類型用于測(cè)試
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>對(duì)象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>對(duì)象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) 測(cè)試
請(qǐng)求:http://localhost:8881/basic
2.3) freemarker基礎(chǔ)
2.3.1) 基礎(chǔ)語(yǔ)法種類
1、注釋,即<#-- -->,介于其之間的內(nèi)容會(huì)被freemarker忽略
<#--我是一個(gè)freemarker注釋-->2、插值(Interpolation):即 ${..} 部分,freemarker會(huì)用真實(shí)的值代替**${..}**
Hello ${name}3、FTL指令:和HTML標(biāo)記類似,名字前加#予以區(qū)分,Freemarker會(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());//小紅對(duì)象模型數(shù)據(jù)Student stu2 = new Student();stu2.setName("小紅");stu2.setMoney(200.1f);stu2.setAge(19);//將兩個(gè)對(duì)象模型數(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>序號(hào)</td><td>姓名</td><td>年齡</td><td>錢包</td></tr> </table> <hr><#-- Map 數(shù)據(jù)的展示 --> <b>map數(shù)據(jù)的展示:</b> <br/><br/> <a href="###">方式一:通過(guò)map['keyname'].property</a><br/> 輸出stu1的學(xué)生信息:<br/> 姓名:<br/> 年齡:<br/> <br/> <a href="###">方式二:通過(guò)map.keyname.property</a><br/> 輸出stu2的學(xué)生信息:<br/> 姓名:<br/> 年齡:<br/><br/> <a href="###">遍歷map中兩個(gè)學(xué)生信息:</a><br/> <table><tr><td>序號(hào)</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>序號(hào)</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="###">方式一:通過(guò)map['keyname'].property</a><br/> 輸出stu1的學(xué)生信息:<br/> 姓名:${stuMap['stu1'].name}<br/> 年齡:${stuMap['stu1'].age}<br/> <br/> <a href="###">方式二:通過(guò)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>序號(hào)</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)容,否則跳過(guò)內(nèi)容不再輸出。
指令格式
1、數(shù)據(jù)模型:
使用list指令中測(cè)試數(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><#-- 日期的比較需要通過(guò)?date將屬性轉(zhuǎn)為data類型才能進(jìn)行比較 --><#if (date1?date >= date2?date)>形式二:使用括號(hào)形式比較時(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ù)值和日期來(lái)比較是否相等
=和!=兩邊必須是相同類型的值,否則會(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é)束字符
可以使用括號(hào)來(lái)避免這種情況,如:<#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為空?qǐng)?bào)錯(cuò)可以加上判斷如下:
<#if stus??><#list stus as stu>......</#list></#if>2、缺失變量默認(rèn)值使用 “!”
使用!要以指定一個(gè)默認(rèn)值,當(dāng)變量為空時(shí)顯示默認(rèn)值
例: ${name!''}表示如果name為空顯示空字符串。
如果是嵌套對(duì)象則建議使用()括起來(lái)
例: ${(stu.bestFriend.name)!''}表示,如果stu或bestFriend或name為空默認(rèn)顯示空字符串。
2.3.6) 內(nèi)建函數(shù)
內(nèi)建函數(shù)語(yǔ)法格式: 變量+?+函數(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ù)字的值,每三位使用逗號(hào)分隔。
如果不想顯示為每三位分隔的數(shù)字,可以使用c函數(shù)將數(shù)字型轉(zhuǎn)成字符串輸出
${point?c}
4、將json字符串轉(zhuǎn)成對(duì)象
一個(gè)例子:
其中用到了 assign標(biāo)簽,assign的作用是定義一個(gè)變量。
<#assign text="{'bank':'工商銀行','account':'10101920201920212'}" /> <#assign data=text?eval /> 開戶行:${data.bank} 賬號(hào):${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>沒(méi)有C函數(shù)顯示的數(shù)值: <br>有C函數(shù)顯示的數(shù)值:<hr><b>聲明變量assign</b><br><hr> </body> </html>內(nèi)建函數(shù)模板頁(yè)面:
<!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>沒(méi)有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} 賬號(hào):${data.account}<hr> </body> </html>內(nèi)建函數(shù)Controller數(shù)據(jù)模型:
@GetMapping("innerFunc") public String testInnerFunc(Model model) {//1.1 小強(qiáng)對(duì)象模型數(shù)據(jù)Student stu1 = new Student();stu1.setName("小強(qiáng)");stu1.setAge(18);stu1.setMoney(1000.86f);stu1.setBirthday(new Date());//1.2 小紅對(duì)象模型數(shù)據(jù)Student stu2 = new Student();stu2.setName("小紅");stu2.setMoney(200.1f);stu2.setAge(19);//1.3 將兩個(gè)對(duì)象模型數(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)化測(cè)試
之前的測(cè)試都是SpringMVC將Freemarker作為視圖解析器(ViewReporter)來(lái)集成到項(xiàng)目中,工作中,有的時(shí)候需要使用Freemarker原生Api來(lái)生成靜態(tài)內(nèi)容,下面一起來(lái)學(xué)習(xí)下原生Api生成文本文件
2.4.1) 需求分析
使用freemarker原生Api將頁(yè)面生成html文件,本節(jié)測(cè)試html文件生成的方法:
2.4.2) 靜態(tài)化測(cè)試
根據(jù)模板文件生成html文件
①:修改application.yml文件,添加以下模板存放位置的配置信息,完整配置如下:
server:port: 8881 #服務(wù)端口 spring:application:name: freemarker-demo #指定服務(wù)名freemarker:cache: false #關(guān)閉模板緩存,方便測(cè)試settings:template_update_delay: 0 #檢查模板更新延遲時(shí)間,設(shè)置為0表示立即檢查,如果時(shí)間大于0會(huì)有緩存不方便進(jìn)行模板測(cè)試suffix: .ftl #指定Freemarker模板文件的后綴名template-loader-path: classpath:/templates #模板存放位置②:在test下創(chuàng)建測(cè)試類
package com.heima.freemarker.test;import com.heima.freemarker.FreemarkerDemoApplication; import com.heima.freemarker.entity.Student; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.io.FileWriter; import java.io.IOException; import java.util.*;@SpringBootTest(classes = FreemarkerDemoApplication.class) @RunWith(SpringRunner.class) public class FreemarkerTest {@Autowiredprivate Configuration configuration;@Testpublic void test() throws IOException, TemplateException {//freemarker的模板對(duì)象,獲取模板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)對(duì)象模型數(shù)據(jù)Student stu1 = new Student();stu1.setName("小強(qiáng)");stu1.setAge(18);stu1.setMoney(1000.86f);stu1.setBirthday(new Date());//小紅對(duì)象模型數(shù)據(jù)Student stu2 = new Student();stu2.setName("小紅");stu2.setMoney(200.1f);stu2.setAge(19);//將兩個(gè)對(duì)象模型數(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);//返回Mapreturn map;} }3) 對(duì)象存儲(chǔ)服務(wù)MinIO
3.1 MinIO簡(jiǎn)介
MinIO基于Apache License v2.0開源協(xié)議的對(duì)象存儲(chǔ)服務(wù),可以做為云存儲(chǔ)的解決方案用來(lái)保存海量的圖片,視頻,文檔。由于采用Golang實(shí)現(xiàn),服務(wù)端可以工作在Windows,Linux, OS X和FreeBSD上。配置簡(jiǎn)單,基本是復(fù)制可執(zhí)行程序,單行命令可以運(yùn)行起來(lái)。
MinIO兼容亞馬遜S3云存儲(chǔ)服務(wù)接口,非常適合于存儲(chǔ)大容量非結(jié)構(gòu)化的數(shù)據(jù),例如圖片、視頻、日志文件、備份數(shù)據(jù)和容器/虛擬機(jī)鏡像等,而一個(gè)對(duì)象文件可以是任意大小,從幾kb到最大5T不等。
S3 ( Simple Storage Service簡(jiǎn)單存儲(chǔ)服務(wù))
基本概念
bucket – 類比于文件系統(tǒng)的目錄
Object – 類比文件系統(tǒng)的文件
Keys – 類比文件名
官網(wǎng)文檔:http://docs.minio.org.cn/docs/
3.2 MinIO特點(diǎn)
數(shù)據(jù)保護(hù)
Minio使用Minio Erasure Code(糾刪碼)來(lái)防止硬件故障。即便損壞一半以上的driver,但是仍然可以從中恢復(fù)。
高性能
作為高性能對(duì)象存儲(chǔ),在標(biāo)準(zhǔn)硬件條件下它能達(dá)到55GB/s的讀、35GB/s的寫速率
可擴(kuò)容
不同MinIO集群可以組成聯(lián)邦,并形成一個(gè)全局的命名空間,并跨越多個(gè)數(shù)據(jù)中心
SDK支持
基于Minio輕量的特點(diǎn),它得到類似Java、Python或Go等語(yǔ)言的sdk支持
有操作頁(yè)面
面向用戶友好的簡(jiǎn)單操作界面,非常方便的管理Bucket及里面的文件資源
功能簡(jiǎn)單
這一設(shè)計(jì)原則讓MinIO不容易出錯(cuò)、更快啟動(dòng)
豐富的API
支持文件資源的分享連接及分享鏈接的過(guò)期策略、存儲(chǔ)桶操作、文件列表訪問(wèn)及文件上傳下載的基本功能等
文件變化主動(dòng)通知
存儲(chǔ)桶(Bucket)如果發(fā)生改變,比如上傳對(duì)象和刪除對(duì)象,可以使用存儲(chǔ)桶事件通知機(jī)制進(jìn)行監(jiān)控,并通過(guò)以下方式發(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 /data3.3.2 管理控制臺(tái)
假設(shè)我們的服務(wù)器地址為http://192.168.200.130:9000,我們?cè)诘刂窓谳斎?#xff1a;http://192.168.200.130:9000/ 即可進(jìn)入登錄界面。
Access Key為minio Secret_key 為minio123 進(jìn)入系統(tǒng)后可以看到主界面
點(diǎn)擊右下角的“+”號(hào) ,點(diǎn)擊下面的圖標(biāo),創(chuàng)建一個(gè)桶
3.4 快速入門
3.4.1 創(chuàng)建工程,導(dǎo)入pom依賴
創(chuàng)建minio-demo,對(duì)應(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)建測(cè)試類,上傳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.上傳 對(duì)象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);//上傳完成//訪問(wèn)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 {@Autowiredprivate MinIOConfigProperties minIOConfigProperties;@Beanpublic MinioClient buildMinioClient(){return MinioClient.builder().credentials(minIOConfigProperties.getAccessKey(), minIOConfigProperties.getSecretKey()).endpoint(minIOConfigProperties.getEndpoint()).build();} }3.5.3 封裝操作minIO類
FileStorageService
package com.heima.file.service;import java.io.InputStream;/*** @author itheima*/ public interface FileStorageService {/*** 上傳圖片文件* @param prefix 文件前綴* @param filename 文件名* @param inputStream 文件流* @return 文件全路徑*/public String uploadImgFile(String prefix, String filename,InputStream inputStream);/*** 上傳html文件* @param prefix 文件前綴* @param filename 文件名* @param inputStream 文件流* @return 文件全路徑*/public String uploadHtmlFile(String prefix, String filename,InputStream inputStream);/*** 刪除文件* @param pathUrl 文件全路徑*/public void delete(String pathUrl);/*** 下載文件* @param pathUrl 文件全路徑* @return**/public byte[] downLoadFile(String pathUrl);}MinIOFileStorageService
package com.heima.file.service.impl; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.text.SimpleDateFormat; import java.util.Date;@Slf4j @EnableConfigurationProperties(MinIOConfigProperties.class) @Import(MinIOConfig.class) public class MinIOFileStorageService implements FileStorageService {@Autowiredprivate MinioClient minioClient;@Autowiredprivate MinIOConfigProperties minIOConfigProperties;private final static String separator = "/";/*** @param dirPath* @param filename yyyy/mm/dd/file.jpg* @return*/public String builderFilePath(String dirPath,String filename) {StringBuilder stringBuilder = new StringBuilder(50);if(!StringUtils.isEmpty(dirPath)){stringBuilder.append(dirPath).append(separator);}SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");String todayStr = sdf.format(new Date());stringBuilder.append(todayStr).append(separator);stringBuilder.append(filename);return stringBuilder.toString();}/*** 上傳圖片文件* @param prefix 文件前綴* @param filename 文件名* @param inputStream 文件流* @return 文件全路徑*/@Overridepublic String uploadImgFile(String prefix, String filename,InputStream inputStream) {String filePath = builderFilePath(prefix, filename);try {PutObjectArgs putObjectArgs = PutObjectArgs.builder().object(filePath).contentType("image/jpg").bucket(minIOConfigProperties.getBucket()).stream(inputStream,inputStream.available(),-1).build();minioClient.putObject(putObjectArgs);StringBuilder urlPath = new StringBuilder(minIOConfigProperties.getReadPath());urlPath.append(separator+minIOConfigProperties.getBucket());urlPath.append(separator);urlPath.append(filePath);return urlPath.toString();}catch (Exception ex){log.error("minio put file error.",ex);throw new RuntimeException("上傳文件失敗");}}/*** 上傳html文件* @param prefix 文件前綴* @param filename 文件名* @param inputStream 文件流* @return 文件全路徑*/@Overridepublic String uploadHtmlFile(String prefix, String filename,InputStream inputStream) {String filePath = builderFilePath(prefix, filename);try {PutObjectArgs putObjectArgs = PutObjectArgs.builder().object(filePath).contentType("text/html").bucket(minIOConfigProperties.getBucket()).stream(inputStream,inputStream.available(),-1).build();minioClient.putObject(putObjectArgs);StringBuilder urlPath = new StringBuilder(minIOConfigProperties.getReadPath());urlPath.append(separator+minIOConfigProperties.getBucket());urlPath.append(separator);urlPath.append(filePath);return urlPath.toString();}catch (Exception ex){log.error("minio put file error.",ex);ex.printStackTrace();throw new RuntimeException("上傳文件失敗");}}/*** 刪除文件* @param pathUrl 文件全路徑*/@Overridepublic void delete(String pathUrl) {String key = pathUrl.replace(minIOConfigProperties.getEndpoint()+"/","");int index = key.indexOf(separator);String bucket = key.substring(0,index);String filePath = key.substring(index+1);// 刪除ObjectsRemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket(bucket).object(filePath).build();try {minioClient.removeObject(removeObjectArgs);} catch (Exception e) {log.error("minio remove file error. pathUrl:{}",pathUrl);e.printStackTrace();}}/*** 下載文件* @param pathUrl 文件全路徑* @return 文件流**/@Overridepublic byte[] downLoadFile(String pathUrl) {String key = pathUrl.replace(minIOConfigProperties.getEndpoint()+"/","");int index = key.indexOf(separator);String bucket = key.substring(0,index);String filePath = key.substring(index+1);InputStream inputStream = null;try {inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(minIOConfigProperties.getBucket()).object(filePath).build());} catch (Exception e) {log.error("minio down file error. pathUrl:{}",pathUrl);e.printStackTrace();}ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();byte[] buff = new byte[100];int rc = 0;while (true) {try {if (!((rc = inputStream.read(buff, 0, 100)) > 0)) break;} catch (IOException e) {e.printStackTrace();}byteArrayOutputStream.write(buff, 0, rc);}return byteArrayOutputStream.toByteArray();} }3.5.4 對(duì)外加入自動(dòng)配置
在resources中新建META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.heima.file.service.impl.MinIOFileStorageService3.5.5 其他微服務(wù)使用
第一,導(dǎo)入heima-file-starter的依賴
第二,在微服務(wù)中添加minio所需要的配置
minio:accessKey: miniosecretKey: minio123bucket: leadnewsendpoint: http://192.168.200.130:9000readPath: http://192.168.200.130:9000第三,在對(duì)應(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 {@Autowiredprivate FileStorageService fileStorageService;@Testpublic void testUpdateImgFile() {try {FileInputStream fileInputStream = new FileInputStream("E:\\tmp\\ak47.jpg");String filePath = fileStorageService.uploadImgFile("", "ak47.jpg", fileInputStream);System.out.println(filePath);} catch (FileNotFoundException e) {e.printStackTrace();}} }4)文章詳情
4.1)需求分析
4.2)實(shí)現(xiàn)方案
方案一
用戶某一條文章,根據(jù)文章的id去查詢文章內(nèi)容表,返回渲染頁(yè)面
方案二 效率高
報(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ù)中新增測(cè)試類(后期新增文章的時(shí)候創(chuàng)建詳情靜態(tài)頁(yè),目前暫時(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 {@Autowiredprivate Configuration configuration;@Autowiredprivate FileStorageService fileStorageService;@Autowiredprivate ApArticleMapper apArticleMapper;@Autowiredprivate ApArticleContentMapper apArticleContentMapper;@Testpublic 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)容通過(guò)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)成對(duì)象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ù)庫(kù)存的是富文本,然后直接把富文本丟給前端人家就可以直接顯示了,搞這么麻煩干嘛,
手機(jī)端適宜的屏幕大小
03自媒體文章發(fā)布
1)自媒體前后端搭建
1.1)后臺(tái)搭建
①:資料中找到heima-leadnews-wemedia.zip解壓
拷貝到heima-leadnews-service工程下,并指定子模塊
執(zhí)行l(wèi)eadnews-wemedia.sql腳本
添加對(duì)應(yīng)的nacos配置
spring:datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/leadnews_wemedia?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTCusername: rootpassword: root # 設(shè)置Mapper接口所對(duì)應(yīng)的XML文件位置,如果你在Mapper接口中有自定義方法,需要進(jìn)行該配置 mybatis-plus:mapper-locations: classpath*:mapper/*.xml# 設(shè)置別名包掃描路徑,通過(guò)該屬性可以給包中的類注冊(cè)別名type-aliases-package: com.heima.model.media.pojos②:資料中找到heima-leadnews-wemedia-gateway.zip解壓
拷貝到heima-leadnews-gateway工程下,并指定子模塊
添加對(duì)應(yīng)的nacos配置
spring:cloud:gateway:globalcors:cors-configurations:'[/**]': # 匹配所有請(qǐng)求allowedOrigins: "*" #跨域處理 允許所有的域allowedMethods: # 支持的方法- GET- POST- PUT- DELETEroutes:# 平臺(tái)管理- id: wemediauri: lb://leadnews-wemediapredicates:- Path=/wemedia/**filters:- StripPrefix= 1③:在資料中找到類文件夾
拷貝wemedia文件夾到heima-leadnews-model模塊下的com.heima.model
1.2)前臺(tái)搭建
通過(guò)nginx的虛擬主機(jī)功能,使用同一個(gè)nginx訪問(wèn)多個(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解壓的目錄)
訪問(wèn)端口修改(8802)
③:啟動(dòng)nginx,啟動(dòng)自媒體微服務(wù)和對(duì)應(yīng)網(wǎng)關(guān)
④:聯(lián)調(diào)測(cè)試登錄功能 測(cè)試成功
2)自媒體素材管理
2.1)素材上傳
2.2.1)需求分析
圖片上傳的頁(yè)面,首先是展示素材信息,可以點(diǎn)擊圖片上傳,彈窗后可以上傳圖片
2.2.2)素材管理-圖片上傳-表結(jié)構(gòu)
媒體圖文素材信息表wm_material
對(duì)應(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ā)送上傳圖片請(qǐng)求,類型為MultipartFile
②:網(wǎng)關(guān)進(jìn)行token解析后,把解析后的用戶信息存儲(chǔ)到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 {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//得到header中的信息String userId = request.getHeader("userId");Optional<String> optional = Optional.ofNullable(userId);if(optional.isPresent()){//把用戶id存入threadloacl中WmUser wmUser = new WmUser();wmUser.setId(Integer.valueOf(userId));WmThreadLocalUtils.setUser(wmUser);log.info("wmTokenFilter設(shè)置用戶信息到threadlocal中...");}return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {log.info("清理threadlocal...");WmThreadLocalUtils.clear();} }配置使攔截器生效,攔截所有的請(qǐng)求
package com.heima.wemedia.config;import com.heima.wemedia.interceptor.WmTokenInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration public class WebMvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new WmTokenInterceptor()).addPathPatterns("/**");} }④:先把圖片上傳到minIO中,獲取到圖片請(qǐng)求的路徑——(2.2.5查看具體功能實(shí)現(xiàn))
⑤:把用戶id和圖片上的路徑保存到素材表中——(2.2.5查看具體功能實(shí)現(xiàn))
2.2.4)接口定義
說(shuō)明 | |
接口路徑 | /api/v1/material/upload_picture |
請(qǐng)求方式 | POST |
參數(shù) | MultipartFile |
響應(yīng)結(jié)果 | ResponseResult |
MultipartFile :Springmvc指定的文件接收類型
ResponseResult :
成功需要回顯圖片,返回素材對(duì)象
{"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"} }失敗:
參數(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: miniosecretKey: minio123bucket: leadnewsendpoint: http://192.168.200.130:9000readPath: http://192.168.200.130:90002.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 {@Autowiredprivate FileStorageService fileStorageService;/*** 圖片上傳* @param multipartFile* @return*/@Overridepublic 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.jpgString originalFilename = multipartFile.getOriginalFilename();String postfix = originalFilename.substring(originalFilename.lastIndexOf("."));String fileId = null;try {fileId = fileStorageService.uploadImgFile("", fileName + postfix, multipartFile.getInputStream());log.info("上傳圖片到MinIO中,fileId:{}",fileId);} catch (IOException e) {e.printStackTrace();log.error("WmMaterialServiceImpl-上傳文件失敗");}//3.保存到數(shù)據(jù)庫(kù)中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 {@Autowiredprivate WmMaterialService wmMaterialService;@PostMapping("/upload_picture")public ResponseResult uploadPicture(MultipartFile multipartFile){return wmMaterialService.uploadPicture(multipartFile);} }⑤:測(cè)試
啟動(dòng)自媒體微服務(wù)和自媒體網(wǎng)關(guān),使用前端項(xiàng)目進(jìn)行測(cè)試
2.2)素材列表查詢
2.2.1)接口定義
說(shuō)明 | |
接口路徑 | /api/v1/material/list |
請(qǐng)求方式 | 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.分頁(yè)查詢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的分頁(yè)攔截器
@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 頻道信息表
對(duì)應(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)接口定義
說(shuō)明 | |
接口路徑 | /api/v1/channel/channels |
請(qǐng)求方式 | POST |
參數(shù) | 無(wú) |
響應(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*/@Overridepublic ResponseResult findAll() {return ResponseResult.okResult(list());} }控制層
package com.heima.wemedia.controller.v1;import com.heima.model.common.dtos.ResponseResult; import org.springframework.web.bind.annotation.RestController;@RestController @RequestMapping("/api/v1/channel") public class WmchannelController {@Autowiredprivate WmChannelService wmChannelService;@GetMapping("/channels")public ResponseResult findAll(){return wmChannelService.findAll();} }3.1.5)測(cè)試
3.2)查詢自媒體文章
3.2.1)需求說(shuō)明
3.2.2)表結(jié)構(gòu)分析
wm_news 自媒體文章表
對(duì)應(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 無(wú)圖文章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 人工審核通過(guò)8 審核通過(guò)(待發(fā)布)9 已發(fā)布*/@TableField("status")private Short status;/*** 定時(shí)發(fā)布時(shí)間,不定時(shí)則為空*/@TableField("publish_time")private Date publishTime;/*** 拒絕理由*/@TableField("reason")private String reason;/*** 發(fā)布庫(kù)文章ID*/@TableField("article_id")private Long articleId;/*** //圖片用逗號(hào)分隔*/@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)接口定義
說(shuō)明 | |
接口路徑 | /api/v1/news/list |
請(qǐng)求方式 | 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*/@Overridepublic ResponseResult findAll(WmNewsPageReqDto dto) {//1.檢查參數(shù)if(dto == null){return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);}//分頁(yè)參數(shù)檢查dto.checkParam();//獲取當(dāng)前登錄人的信息WmUser user = WmThreadLocalUtil.getUser();if(user == null){return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);}//2.分頁(yè)條件查詢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 {@Autowiredprivate WmNewsService wmNewsService;@PostMapping("/list")public ResponseResult findAll(@RequestBody WmNewsPageReqDto dto){return wmNewsService.findAll(dto);}}3.2.5)測(cè)試
啟動(dòng)后端自媒體微服務(wù)和自媒體網(wǎng)關(guān)微服務(wù),測(cè)試文章列表查詢
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表對(duì)應(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.后臺(tái)判斷請(qǐng)求中是否包含了文章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,則為修改請(qǐng)求
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)接口定義
說(shuō)明 | |
接口路徑 | /api/v1/channel/submit |
請(qǐng)求方式 | 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 無(wú)圖 1 單圖 3 多圖 -1 自動(dòng)*/private Short type;/*** 提交時(shí)間*/private Date submitedTime; /*** 狀態(tài) 提交為1 草稿為0*/private Short status;/*** 封面圖片列表 多張圖以逗號(hào)隔開*/private List<String> images; }前端給傳遞過(guò)來(lái)的json數(shù)據(jù)格式為:
{"title":"黑馬頭條項(xiàng)目背景","type":"1",//這個(gè) 0 是無(wú)圖 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í)慣于通過(guò)手機(jī)來(lái)看新聞。由于生活節(jié)奏的加快,很多人只能利用碎片時(shí)間來(lái)獲取信息,因此,對(duì)于移動(dòng)資訊客戶端的需求也越來(lái)越高。黑馬頭條項(xiàng)目正是在這樣背景下開發(fā)出來(lái)。黑馬頭條項(xiàng)目采用當(dāng)下火熱的微服務(wù)+大數(shù)據(jù)技術(shù)架構(gòu)實(shí)現(xiàn)。本項(xiàng)目主要著手于獲取最新最熱新聞資訊,通過(guò)大數(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文件和對(duì)應(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---> stringif(dto.getImages() != null && dto.getImages().size() > 0){//[1dddfsd.jpg,sdlfjldk.jpg]--> 1dddfsd.jpg,sdlfjldk.jpgString imageStr = StringUtils.join(dto.getImages(), ",");wmNews.setImages(imageStr);}//如果當(dāng)前封面類型為自動(dòng) -1if(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)容沒(méi)有圖片,無(wú)圖 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 {//無(wú)圖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ù)庫(kù)中* @param materials* @param newsId* @param type*/ private void saveRelativeInfo(List<String> materials, Integer newsId, Short type) {if(materials!=null && !materials.isEmpty()){//通過(guò)圖片的url查詢素材的idList<WmMaterial> dbMaterials = wmMaterialMapper.selectList(Wrappers.<WmMaterial>lambdaQuery().in(WmMaterial::getUrl, materials));//判斷素材是否有效if(dbMaterials==null || dbMaterials.size() == 0){//手動(dòng)拋出異常 第一個(gè)功能:能夠提示調(diào)用者素材失效了,第二個(gè)功能,進(jìn)行數(shù)據(jù)的回滾throw new CustomException(AppHttpCodeEnum.MATERIASL_REFERENCE_FAIL);}if(materials.size() != dbMaterials.size()){throw new CustomException(AppHttpCodeEnum.MATERIASL_REFERENCE_FAIL);}List<Integer> idList = dbMaterials.stream().map(WmMaterial::getId).collect(Collectors.toList());//批量保存wmNewsMaterialMapper.saveRelations(idList,newsId,type);}}/*** 提取文章內(nèi)容中的圖片信息* @param content* @return*/ private List<String> ectractUrlInfo(String content) {List<String> materials = new ArrayList<>();List<Map> maps = JSON.parseArray(content, Map.class);for (Map map : maps) {if(map.get("type").equals("image")){String imgUrl = (String) map.get("value");materials.add(imgUrl);}}return materials; }@Autowired private WmNewsMaterialMapper wmNewsMaterialMapper;/*** 保存或修改文章* @param wmNews*/ private void saveOrUpdateWmNews(WmNews wmNews) {//補(bǔ)全屬性wmNews.setUserId(WmThreadLocalUtil.getUser().getId());wmNews.setCreatedTime(new Date());wmNews.setSubmitedTime(new Date());wmNews.setEnable((short)1);//默認(rèn)上架if(wmNews.getId() == null){//保存save(wmNews);}else {//修改//刪除文章圖片與素材的關(guān)系wmNewsMaterialMapper.delete(Wrappers.<WmNewsMaterial>lambdaQuery().eq(WmNewsMaterial::getNewsId,wmNews.getId()));updateById(wmNews);}}④:控制器
@PostMapping("/submit") public ResponseResult submitNews(@RequestBody WmNewsDto dto){return wmNewsService.submitNews(dto); }3.3.6)測(cè)試
總結(jié)
以上是生活随笔為你收集整理的《黑马头条》SpringBoot+SpringCloud+ Nacos等企业级微服务架构项目的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 【数学建模】基于matlab三维海浪模型
- 下一篇: 海康威视 2024届实习生 应用软件开发